New file |
| | |
| | | /* eslint-env node */ |
| | | require('@rushstack/eslint-patch/modern-module-resolution') |
| | | |
| | | module.exports = { |
| | | root: true, |
| | | 'extends': [ |
| | | 'plugin:vue/vue3-essential', |
| | | 'eslint:recommended', |
| | | '@vue/eslint-config-typescript', |
| | | '@vue/eslint-config-prettier/skip-formatting' |
| | | ], |
| | | parserOptions: { |
| | | ecmaVersion: 'latest' |
| | | } |
| | | } |
| | |
| | | # Object files |
| | | *.o |
| | | *.ko |
| | | *.obj |
| | | *.elf |
| | | # Logs |
| | | logs |
| | | *.log |
| | | npm-debug.log* |
| | | yarn-debug.log* |
| | | yarn-error.log* |
| | | pnpm-debug.log* |
| | | lerna-debug.log* |
| | | |
| | | # Libraries |
| | | *.lib |
| | | *.a |
| | | # 依赖 |
| | | node_modules |
| | | *.lock |
| | | package-lock.json |
| | | |
| | | # Shared objects (inc. Windows DLLs) |
| | | *.dll |
| | | *.so |
| | | *.so.* |
| | | *.dylib |
| | | # 编译文件 |
| | | dist |
| | | dist-electron |
| | | electron-commonJS |
| | | |
| | | # Executables |
| | | *.exe |
| | | *.out |
| | | *.app |
| | | *.i*86 |
| | | *.x86_64 |
| | | *.hex |
| | | # 打包文件 |
| | | release |
| | | |
| | | # Editor directories and files |
| | | .vscode/* |
| | | !.vscode/extensions.json |
| | | |
| | | |
| | | |
| | | |
New file |
| | |
| | | { |
| | | "$schema": "https://json.schemastore.org/prettierrc", |
| | | "semi": false, |
| | | "tabWidth": 2, |
| | | "singleQuote": true, |
| | | "printWidth": 100, |
| | | "trailingComma": "none" |
| | | } |
| | |
| | | ## TextbookReader |
| | | # 数字教材阅读器 |
| | | |
| | | 电子教材阅读器 |
| | | # 环境 |
| | | Node.js 要求 >= 18.0.0 |
| | | |
| | | # 安装依赖 |
| | | yarn |
| | | |
| | | # 启动项目 |
| | | npm run dev |
| | | |
| | | # 编译打包 |
| | | windows: npm run build |
| | | mac: npm run build_mac |
| | | |
| | | # 技术栈 |
| | | Vue3 Vite Electron NodeJs ElementUI Qiankun TypeScript Less |
| | | |
New file |
| | |
| | | !macro customInstall |
| | | DetailPrint "Register DigitalTextbookReader URI Handler" |
| | | DeleteRegKey HKCR "DigitalTextbookReader" |
| | | WriteRegStr HKCR "DigitalTextbookReader" "" "URL:DigitalTextbookReader" |
| | | WriteRegStr HKCR "DigitalTextbookReader" "URL Protocol" "" |
| | | WriteRegStr HKCR "DigitalTextbookReader\shell" "" "" |
| | | WriteRegStr HKCR "DigitalTextbookReader\shell\Open" "" "" |
| | | WriteRegStr HKCR "DigitalTextbookReader\shell\Open\command" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME} %1" |
| | | !macroend |
New file |
| | |
| | | // 测试 |
| | | export const ctx = "http://182.92.203.7:5001"; |
| | | export const downloaderFileCtx = "http://182.92.203.7:3007/DigitalTextbookReader"; |
| | | |
New file |
| | |
| | | import { ipcMain, shell } from 'electron' |
| | | import fs from 'fs' |
| | | import os from 'os' |
| | | import axios from 'axios' |
| | | import path from 'path' |
| | | import moment from 'moment' |
| | | import { uuid } from './toolClass' |
| | | |
| | | const Store = require('electron-store') |
| | | const FileDownloadTasks = new Store('FileDownloadTasks') |
| | | const DownloadConfig = new Store('DownloadConfig') |
| | | const DOWNLOAD_SAVE_FOLDER = 'FileDownloadSaveFolder' |
| | | const DOWNLOAD_TASKS = 'FileDownloadTasks' |
| | | // 下载列表 |
| | | let downloadTasks = [] |
| | | |
| | | // 默认路径 |
| | | const homeDir = os.homedir() |
| | | let DOWNLOAD_PATH = '' |
| | | const FileDownloadSaveFolder = DownloadConfig.get(DOWNLOAD_SAVE_FOLDER) |
| | | if (FileDownloadSaveFolder) { |
| | | DOWNLOAD_PATH = FileDownloadSaveFolder |
| | | } else { |
| | | if (os.platform() === 'win32') { |
| | | // Windows 系统下的 "Downloads" 文件夹路径 |
| | | DOWNLOAD_PATH = path.join(homeDir, 'Downloads') |
| | | } else { |
| | | // macOS 和 Linux 系统下的 "Downloads" 文件夹路径 |
| | | DOWNLOAD_PATH = path.join(homeDir, 'Downloads') |
| | | } |
| | | } |
| | | |
| | | let mainWindow: any = null |
| | | let request: any = null |
| | | |
| | | let token = '' |
| | | |
| | | export const init = (win, req) => { |
| | | mainWindow = win |
| | | request = req |
| | | loadTasks() |
| | | } |
| | | |
| | | // 下载任务实例 |
| | | class DownloadTask { |
| | | constructor(data) { |
| | | this.initData(data) |
| | | updateTask(this.data) |
| | | } |
| | | |
| | | public data |
| | | |
| | | // 初始化数据 |
| | | initData(data) { |
| | | if (data.id) { |
| | | // id:唯一标识 |
| | | // state:状态 |
| | | // savePath:储存路径 |
| | | // fileInfoMap:所有下载文件信息map |
| | | // name:任务名称 |
| | | // size:任务大小 |
| | | // offset:任务已完成偏移量 |
| | | // msg:任务消息 |
| | | // md5:下载文件md5 |
| | | // taskDesc:任务描述,与md5二选一,任务描述会以文件夹形式下载并储存 |
| | | // 例如: |
| | | // [ |
| | | // { |
| | | // name: '文件夹1', |
| | | // type: 'folder', |
| | | // childer: [ |
| | | // { |
| | | // name: '文件夹2', |
| | | // type: 'folder', |
| | | // childer: [ |
| | | // { |
| | | // name: '文件1', |
| | | // type: 'file', |
| | | // md5: "a654f654das6f5465ds4f65sa4f" |
| | | // } |
| | | // ] |
| | | // }, |
| | | // { |
| | | // name: '文件2', |
| | | // type: 'file', |
| | | // md5: "asdfsadf564s644544er4g65f4h" |
| | | // } |
| | | // ] |
| | | // } |
| | | // ] |
| | | |
| | | this.data = { ...data } |
| | | } else { |
| | | this.data = { |
| | | id: uuid(8), |
| | | state: 'init', |
| | | fileState: 'init', |
| | | savePath: '', |
| | | fileInfoMap: {}, |
| | | name: data.name ? data.name : data.taskDesc ? data.taskDesc[0].name : '文件', |
| | | size: 0, |
| | | offset: 0, |
| | | msg: '', |
| | | md5: data.md5 ? data.md5 : null, |
| | | taskDesc: data.taskDesc ? data.taskDesc : null, |
| | | oldTaskDesc: data.taskDesc ? JSON.stringify(data.taskDesc) : null, |
| | | createDate: moment().format('YYYY-MM-DD HH:mm:ss'), |
| | | completeDate: '' |
| | | } |
| | | } |
| | | } |
| | | |
| | | async start() { |
| | | if (this.data.md5) { |
| | | // 单个下载 |
| | | if (!this.data.fileInfoMap[this.data.md5]) { |
| | | // 没有文件信息时获取 |
| | | this.data.state = 'getFileInfo' |
| | | const info: any = await this.getFileInfo(this.data.md5) |
| | | if (info) { |
| | | this.data.fileInfoMap[this.data.md5] = info |
| | | this.data.size = info.size |
| | | this.data.name = info.name + '.' + info.extension |
| | | } |
| | | } |
| | | if (this.data.name) { |
| | | if (fs.existsSync(path.join(DOWNLOAD_PATH, this.data.name))) { |
| | | // 文件已存在 |
| | | const stats = fs.statSync(path.join(DOWNLOAD_PATH, this.data.name)) |
| | | if (stats.size == this.data.size) { |
| | | // 文件已下载完成 |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'warning', |
| | | msg: '文件已下载完成,请在已完成或下载目录中查看。' |
| | | }) |
| | | ) |
| | | // 删除当前下载任务 |
| | | this.data = false |
| | | return false |
| | | } else { |
| | | // 存在旧的文件未完成或正在下载的任务 |
| | | const oldTask = downloadTasks.find( |
| | | (item) => |
| | | item.md5 == this.data.md5 && |
| | | item.id != this.data.id && |
| | | (item.state == 'download' || item.state == 'pause') |
| | | ) |
| | | if (oldTask) { |
| | | // 存在任务 |
| | | if (oldTask.state == 'download') { |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'warning', |
| | | msg: '已存在相同的下载任务' |
| | | }) |
| | | ) |
| | | // 删除当前下载任务 |
| | | this.data = false |
| | | return false |
| | | } |
| | | if (oldTask.state == 'pause') { |
| | | const task = new DownloadTask(oldTask) |
| | | task.start() |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'warning', |
| | | msg: '已存在相同的下载任务,已自动开始下载' |
| | | }) |
| | | ) |
| | | // 删除当前下载任务 |
| | | this.data = false |
| | | return false |
| | | } |
| | | } |
| | | } |
| | | } else { |
| | | // 文件不存在 |
| | | const oldTasks = downloadTasks.filter( |
| | | (item) => |
| | | item.md5 == this.data.md5 && |
| | | item.id != this.data.id && |
| | | (item.state == 'download' || item.state == 'pause') |
| | | ) |
| | | if (oldTasks.length) { |
| | | // 存在相同文件的任务,分析这些任务状态,并标记这些任务的文件已不存在 |
| | | for (let i = 0; i < oldTasks.length; i++) { |
| | | const oldTask = oldTasks[i] |
| | | if (oldTask.state == 'pause') { |
| | | const task = new DownloadTask(oldTask) |
| | | task.start() |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'warning', |
| | | msg: '已存在相同的下载任务,已自动开始下载' |
| | | }) |
| | | ) |
| | | // 删除当前下载任务 |
| | | this.data = false |
| | | return false |
| | | } |
| | | if (oldTask.state == 'success') { |
| | | oldTask.fileState = 'fileMissing' |
| | | } |
| | | } |
| | | } else { |
| | | this.data.offset = 0 |
| | | } |
| | | } |
| | | |
| | | this.data.savePath = path.join(DOWNLOAD_PATH, this.data.name) |
| | | this.data.state = 'download' |
| | | this.data.fileState = 'download' |
| | | this.data.fileInfoMap[this.data.md5].downloadState = 'download' |
| | | updateTask(this.data) |
| | | this.download(this.data, (type) => { |
| | | this.data.state = type |
| | | this.data.fileInfoMap[this.data.md5].downloadState = type |
| | | if (type == 'success') { |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'success', |
| | | msg: `${this.data.name}已下载完成,请在已完成或下载目录中查看。` |
| | | }) |
| | | ) |
| | | this.data.completeDate = moment().format('YYYY-MM-DD HH:mm:ss') |
| | | } |
| | | }) |
| | | } |
| | | } else if (this.data.taskDesc && this.data.taskDesc.length) { |
| | | // 文件夹下载 |
| | | this.data.state = 'download' |
| | | this.data.offset = 0 |
| | | this.data.name = this.data.taskDesc[0].name |
| | | this.data.savePath = path.join(DOWNLOAD_PATH, this.data.name) |
| | | // 存在旧的文件未完成或正在下载的任务 |
| | | const oldTask = downloadTasks.find( |
| | | (item) => |
| | | item.taskDesc && |
| | | item.oldTaskDesc == this.data.oldTaskDesc && |
| | | item.id != this.data.id && |
| | | (item.state == 'download' || item.state == 'pause') |
| | | ) |
| | | if (oldTask) { |
| | | // 存在任务 |
| | | if (oldTask.state == 'download') { |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'warning', |
| | | msg: '已存在相同的下载任务' |
| | | }) |
| | | ) |
| | | // 删除当前下载任务 |
| | | this.data = false |
| | | return false |
| | | } |
| | | if (oldTask.state == 'pause') { |
| | | const task = new DownloadTask(oldTask) |
| | | task.start() |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'warning', |
| | | msg: '已存在相同的下载任务,已自动开始下载' |
| | | }) |
| | | ) |
| | | // 删除当前下载任务 |
| | | this.data = false |
| | | return false |
| | | } |
| | | } |
| | | await this.handleFolderData(this.data.taskDesc, DOWNLOAD_PATH) |
| | | await this.handleFolderDownload(this.data.taskDesc, DOWNLOAD_PATH) |
| | | } |
| | | return false |
| | | } |
| | | |
| | | // 先处理总体大小,不然会导致下载过程中判断为已完成 |
| | | async handleFolderData(dataList, savePath) { |
| | | for (let i = 0; i < dataList.length; i++) { |
| | | const dataItem = dataList[i] |
| | | if (!dataItem.md5) { |
| | | // 文件夹 |
| | | dataItem.type = 'folder' |
| | | // 判断文件夹是否存在,创建文件夹 |
| | | const folderPath = path.join(savePath, dataItem.name) |
| | | if (!fs.existsSync(folderPath)) { |
| | | fs.mkdirSync(folderPath) |
| | | } |
| | | // 处理子 |
| | | if (dataItem.childer && dataItem.childer.length) { |
| | | await this.handleFolderData(dataItem.childer, folderPath) |
| | | } |
| | | } else { |
| | | // 文件 |
| | | dataItem.type = 'file' |
| | | if (!this.data.fileInfoMap[dataItem.md5]) { |
| | | // 没有文件信息时获取 |
| | | dataItem.state = 'getFileInfo' |
| | | const info: any = await this.getFileInfo(dataItem.md5) |
| | | if (info) { |
| | | this.data.fileInfoMap[dataItem.md5] = info |
| | | dataItem.size = info.size |
| | | dataItem.name = info.name + '.' + info.extension |
| | | this.data.size += dataItem.size |
| | | } |
| | | } |
| | | if (dataItem.name) { |
| | | dataItem.offset = 0 |
| | | dataItem.savePath = path.join(savePath, dataItem.name) |
| | | if (fs.existsSync(dataItem.savePath)) { |
| | | // 文件已存在 |
| | | const stats = fs.statSync(dataItem.savePath) |
| | | if (stats.size == this.data.size) { |
| | | // 已完成 |
| | | dataItem.state = 'success' |
| | | this.data.fileInfoMap[dataItem.md5].downloadState = 'success' |
| | | } else { |
| | | // 未完成 |
| | | dataItem.state = 'download' |
| | | this.data.fileInfoMap[dataItem.md5].downloadState = 'download' |
| | | dataItem.offset = stats.size |
| | | this.data.offset += dataItem.offset |
| | | } |
| | | } else { |
| | | // 文件不存在 |
| | | dataItem.state = 'download' |
| | | this.data.fileInfoMap[dataItem.md5].downloadState = 'download' |
| | | } |
| | | updateTask(this.data) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 处理folder数据 |
| | | async handleFolderDownload(dataList, savePath) { |
| | | for (let i = 0; i < dataList.length; i++) { |
| | | const dataItem = dataList[i] |
| | | if (!dataItem.md5) { |
| | | // 文件夹 |
| | | if (dataItem.childer && dataItem.childer.length) { |
| | | await this.handleFolderDownload(dataItem.childer, path.join(savePath, dataItem.name)) |
| | | } |
| | | } else { |
| | | // 文件 |
| | | this.download( |
| | | dataItem, |
| | | (type) => { |
| | | this.data.fileInfoMap[dataItem.md5].downloadState = type |
| | | dataItem.state = type |
| | | if (type == 'success' && this.data.state != 'success') { |
| | | if (this.data.offset == this.data.size) { |
| | | this.data.state = type |
| | | this.data.completeDate = moment().format('YYYY-MM-DD HH:mm:ss') |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'success', |
| | | msg: `${this.data.name}已下载完成,请在已完成或下载目录中查看。` |
| | | }) |
| | | ) |
| | | } |
| | | } else { |
| | | this.data.state = type |
| | | } |
| | | }, |
| | | (length) => { |
| | | this.data.offset += length |
| | | } |
| | | ) |
| | | } |
| | | } |
| | | } |
| | | |
| | | pause() { |
| | | if (this.data.md5) { |
| | | // 暂停单个文件下载 |
| | | if (this.data.cancelToken && this.data.cancelToken.cancel) this.data.cancelToken.cancel() |
| | | if (this.data.cancelPipe) this.data.cancelPipe() |
| | | this.data.status = 'pause' |
| | | if (this.data.fileInfoMap[this.data.md5]) |
| | | this.data.fileInfoMap[this.data.md5].downloadState = 'pause' |
| | | updateTask(this.data) |
| | | } else { |
| | | // 暂停整本下载 |
| | | this.pauseAll(this.data.taskDesc) |
| | | } |
| | | } |
| | | |
| | | pauseAll(data) { |
| | | for (let i = 0; i < data.length; i++) { |
| | | const item = data[i] |
| | | if (item.md5) { |
| | | if (item.cancelToken && item.cancelToken.cancel) item.cancelToken.cancel() |
| | | if (item.cancelPipe) item.cancelPipe() |
| | | item.status = 'pause' |
| | | if (this.data.fileInfoMap[item.md5]) { |
| | | this.data.fileInfoMap[item.md5].downloadState = 'pause' |
| | | } |
| | | } |
| | | if (item.childer && item.childer.length) { |
| | | this.pauseAll(item.childer) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 获取文件信息 |
| | | async getFileInfo(md5) { |
| | | try { |
| | | const resp = await request({ |
| | | url: `/file/FileUpload/GetFileInfo`, |
| | | method: 'post', |
| | | headers: { |
| | | Authorization: 'bearer ' + token |
| | | }, |
| | | data: { |
| | | md5: md5 |
| | | } |
| | | }) |
| | | return resp.data |
| | | } catch (error) { |
| | | this.data.state = 'error' |
| | | updateTask(this.data) |
| | | return false |
| | | } |
| | | } |
| | | |
| | | async download(fileItem, endCallback, ongoingCallback?) { |
| | | try { |
| | | if (fileItem.offset === 0) fs.unlink(fileItem.savePath, () => {}) |
| | | fileItem.cancelToken = axios.CancelToken.source() |
| | | const resp = await request({ |
| | | url: `http://182.92.203.7:5004/file/FileDownload/Download?md5=${fileItem.md5}&offset=${fileItem.offset}`, |
| | | method: 'get', |
| | | cancelToken: fileItem.cancelToken.token, |
| | | responseType: 'stream', |
| | | headers: { |
| | | Authorization: 'bearer ' + token |
| | | } |
| | | }) |
| | | const writer = fs.createWriteStream(fileItem.savePath, { |
| | | start: fileItem.offset, |
| | | flags: 'a+', |
| | | autoClose: true |
| | | }) |
| | | resp.pipe(writer) |
| | | fileItem.cancelPipe = () => { |
| | | resp.unpipe(writer) |
| | | writer.end('停止写入!') |
| | | } |
| | | resp.on('data', (data) => { |
| | | fileItem.offset += data.length |
| | | // 调用渲染进程 |
| | | updateTask(this.data) |
| | | if (ongoingCallback) ongoingCallback(data.length) |
| | | }) |
| | | |
| | | resp.on('end', () => { |
| | | if (fileItem.offset == fileItem.size) { |
| | | endCallback('success') |
| | | } else { |
| | | // 完成后没有达到实际大小 |
| | | endCallback('error') |
| | | } |
| | | }) |
| | | resp.on('error', (err) => { |
| | | if (err.name == 'CanceledError') { |
| | | // 暂停,取消请求 |
| | | endCallback('pause') |
| | | } else { |
| | | // 下载错误 |
| | | endCallback('error') |
| | | } |
| | | updateTask(this.data) |
| | | }) |
| | | resp.on('close', () => { |
| | | updateTask(this.data) |
| | | }) |
| | | } catch (error) { |
| | | updateTask(this.data) |
| | | // 下载错误 |
| | | endCallback('error') |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 本地缓存加载任务列表 |
| | | const loadTasks = () => { |
| | | const txt = FileDownloadTasks.get(DOWNLOAD_TASKS, '[]') |
| | | try { |
| | | downloadTasks = JSON.parse(txt) |
| | | } catch (error) { |
| | | downloadTasks = [] |
| | | } |
| | | for (let i = 0; i < downloadTasks.length; i++) { |
| | | const item = downloadTasks[i] |
| | | if (item.state == 'download') item.state = 'pause' |
| | | } |
| | | } |
| | | |
| | | // 本地缓存记录任务列表 |
| | | const saveTasks = () => { |
| | | FileDownloadTasks.set(DOWNLOAD_TASKS, JSON.stringify(downloadTasks)) |
| | | } |
| | | |
| | | const updateTask = (data?) => { |
| | | // 调用渲染进程 |
| | | if (data) { |
| | | mainWindow.webContents.send('downloadTaskChange', JSON.stringify(data)) |
| | | const index = downloadTasks.findIndex((item) => item.id == data.id) |
| | | downloadTasks[index] = data |
| | | } else { |
| | | mainWindow.webContents.send('downloadTaskChange') |
| | | } |
| | | saveTasks() |
| | | } |
| | | |
| | | // 获取任务列表 |
| | | ipcMain.on('getDownloadTasks', (ev, data) => { |
| | | // 页面会先获取任务列表,在获取列表时将token挂载 |
| | | mainWindow.webContents |
| | | .executeJavaScript('localStorage.getItem("token");', true) |
| | | .then((result) => { |
| | | token = result |
| | | }) |
| | | const returnData = JSON.parse(JSON.stringify(downloadTasks)) |
| | | ev.returnValue = returnData.map((item: any) => { |
| | | if (item.md5) { |
| | | return { |
| | | ...item, |
| | | cancelToken: '', // 去掉方法,通信避免报错 |
| | | cancelPipe: '' // 去掉方法,通信避免报错 |
| | | } |
| | | } else { |
| | | return { |
| | | ...item, |
| | | taskDesc: handleTasksInfo(item.taskDesc) |
| | | } |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | const handleTasksInfo = (dataList) => { |
| | | for (let i = 0; i < dataList.length; i++) { |
| | | const item = dataList[i] |
| | | if (item.cancelToken) item.cancelToken = '' |
| | | if (item.cancelPipe) item.cancelPipe = '' |
| | | if (item.childer) { |
| | | item.childer = handleTasksInfo(item.childer) |
| | | } |
| | | } |
| | | return dataList |
| | | } |
| | | |
| | | // 新建任务 |
| | | ipcMain.on('newDownloadTask', async (ev, data) => { |
| | | // 判断下载目录是否存在 |
| | | if (!fs.existsSync(DOWNLOAD_PATH)) { |
| | | // 未获取到默认下载目录 |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'notDownloadFolder', |
| | | msg: JSON.stringify(data) |
| | | }) |
| | | ) |
| | | } else { |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'showState', |
| | | msg: '检测到下载任务,正在创建...' |
| | | }) |
| | | ) |
| | | const task = new DownloadTask(data) |
| | | if (!data.id && data.autoPlay) { |
| | | await task.start() |
| | | } |
| | | if (task.data) { |
| | | downloadTasks.push(task.data) |
| | | updateTask(task) |
| | | } |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'showState', |
| | | msg: '' |
| | | }) |
| | | ) |
| | | } |
| | | }) |
| | | |
| | | // 暂停任务 |
| | | ipcMain.on('pauseTask', (ev, id) => { |
| | | const index = downloadTasks.findIndex((item) => item.id == id) |
| | | if (index === -1) { |
| | | ev.returnValue = false |
| | | } else { |
| | | const task = new DownloadTask(downloadTasks[index]) |
| | | task.pause() |
| | | ev.returnValue = true |
| | | } |
| | | }) |
| | | ipcMain.on('pauseAllTask', (ev) => { |
| | | for (let i = 0; i < downloadTasks.length; i++) { |
| | | const item = downloadTasks[i] |
| | | if (item.state == 'download') { |
| | | const task = new DownloadTask(item) |
| | | task.pause() |
| | | } |
| | | } |
| | | ev.returnValue = true |
| | | }) |
| | | |
| | | // 开始任务 |
| | | ipcMain.on('startTask', (ev, id) => { |
| | | const index = downloadTasks.findIndex((item) => item.id == id) |
| | | if (index === -1) { |
| | | ev.returnValue = false |
| | | } else { |
| | | const task = new DownloadTask(downloadTasks[index]) |
| | | task.start() |
| | | ev.returnValue = true |
| | | } |
| | | }) |
| | | ipcMain.on('startAllTask', (ev) => { |
| | | for (let i = 0; i < downloadTasks.length; i++) { |
| | | const item = downloadTasks[i] |
| | | if (item.state != 'success' && item.state != 'download') { |
| | | const task = new DownloadTask(item) |
| | | task.start() |
| | | } |
| | | } |
| | | ev.returnValue = true |
| | | }) |
| | | |
| | | ipcMain.on('cleanTask', (ev, id) => { |
| | | const item = downloadTasks.find((item) => item.id == id) |
| | | if (item.state == 'download') { |
| | | const task = new DownloadTask(item) |
| | | task.pause() |
| | | } |
| | | |
| | | downloadTasks = downloadTasks.filter((item) => item.id != id) |
| | | updateTask() |
| | | ev.returnValue = true |
| | | }) |
| | | |
| | | ipcMain.on('cleanAll', (ev) => { |
| | | for (let i = 0; i < downloadTasks.length; i++) { |
| | | const item = downloadTasks[i] |
| | | if (item.state == 'download') { |
| | | const task = new DownloadTask(item) |
| | | task.pause() |
| | | } |
| | | } |
| | | |
| | | downloadTasks = downloadTasks.filter((item) => item.state == 'success') |
| | | updateTask() |
| | | ev.returnValue = true |
| | | }) |
| | | |
| | | ipcMain.on('openPath', (ev, path) => { |
| | | if (fs.existsSync(path)) { |
| | | shell.openPath(path) |
| | | ev.returnValue = { state: true } |
| | | } else { |
| | | ev.returnValue = { state: false, msg: '目录不存在!' } |
| | | } |
| | | }) |
| | | |
| | | ipcMain.on('openPathByTaskId', (ev, id) => { |
| | | const item = downloadTasks.find((item) => item.id == id) |
| | | if (fs.existsSync(item.savePath)) { |
| | | if (item.md5) { |
| | | shell.showItemInFolder(item.savePath) |
| | | } else { |
| | | shell.openPath(item.savePath) |
| | | } |
| | | } else { |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'warning', |
| | | msg: '本地文件目录不存在或者已被删除' |
| | | }) |
| | | ) |
| | | } |
| | | ev.returnValue = true |
| | | }) |
| | | |
| | | ipcMain.on('getDownloadPath', async (ev) => { |
| | | ev.returnValue = DOWNLOAD_PATH |
| | | }) |
| | | |
| | | ipcMain.on('setDownloadPath', async (ev, data) => { |
| | | if (fs.existsSync(data.path)) { |
| | | // 设置下载目录 |
| | | DOWNLOAD_PATH = data.path |
| | | DownloadConfig.set(DOWNLOAD_SAVE_FOLDER, data.path) |
| | | if (data.dataStr) { |
| | | // 如果有任务,开始下载任务 |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'showState', |
| | | msg: '检测到下载任务,正在创建...' |
| | | }) |
| | | ) |
| | | const taskData = JSON.parse(data.dataStr) |
| | | const task = new DownloadTask(taskData) |
| | | if (!taskData.id && taskData.autoPlay) { |
| | | await task.start() |
| | | } |
| | | if (task.data) { |
| | | downloadTasks.push(task.data) |
| | | updateTask(task) |
| | | } |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | type: 'showState', |
| | | msg: '' |
| | | }) |
| | | ) |
| | | } |
| | | ev.returnValue = { state: true } |
| | | } else { |
| | | ev.returnValue = { state: false, msg: '目录不存在!' } |
| | | } |
| | | }) |
New file |
| | |
| | | import { ipcMain } from 'electron' |
| | | import fs from 'fs' |
| | | import os from 'os' |
| | | import moment from 'moment' |
| | | import path from 'path' |
| | | import { uuid } from './toolClass' |
| | | const xlsx = require('node-xlsx') |
| | | const Store = require('electron-store') |
| | | const ExportTasks = new Store('ExportTasks') |
| | | const DownloadConfig = new Store('DownloadConfig') |
| | | const EXPORT_TASKS = 'ExportTasks' |
| | | const DOWNLOAD_SAVE_FOLDER = 'FileDownloadSaveFolder' |
| | | |
| | | // 默认路径 |
| | | const homeDir = os.homedir() |
| | | let DOWNLOAD_PATH = '' |
| | | const FileDownloadSaveFolder = DownloadConfig.get(DOWNLOAD_SAVE_FOLDER) |
| | | if (FileDownloadSaveFolder) { |
| | | DOWNLOAD_PATH = FileDownloadSaveFolder |
| | | } else { |
| | | if (os.platform() === 'win32') { |
| | | // Windows 系统下的 "Downloads" 文件夹路径 |
| | | DOWNLOAD_PATH = path.join(homeDir, 'Downloads') |
| | | } else { |
| | | // macOS 和 Linux 系统下的 "Downloads" 文件夹路径 |
| | | DOWNLOAD_PATH = path.join(homeDir, 'Downloads') |
| | | } |
| | | } |
| | | |
| | | let mainWindow: any = null |
| | | let request: any = null |
| | | |
| | | let token = '' |
| | | let taskList: any = [] |
| | | |
| | | export const init = (win, req) => { |
| | | mainWindow = win |
| | | request = req |
| | | loadTasks() |
| | | } |
| | | |
| | | type CmsItemProps = { |
| | | path: string |
| | | storeId?: number |
| | | repositoryId?: number |
| | | type?: string |
| | | paging: { |
| | | Start: number |
| | | Size: number |
| | | } |
| | | sort?: Record<string, any> |
| | | linkTypes?: string[] |
| | | fields?: { |
| | | [key: string]: [] |
| | | } |
| | | filters?: { |
| | | [key: string]: string[] |
| | | } |
| | | subQuery?: {} |
| | | keyword?: string |
| | | itemId?: number |
| | | resType?: any |
| | | } |
| | | |
| | | class ExportTask { |
| | | // 初始状态 |
| | | static INIT = 0 |
| | | // 处理中 |
| | | static LOADING = 1 |
| | | // 暂停中 |
| | | static PAUSE = 2 |
| | | // 完成 |
| | | static FINISHED = 3 |
| | | // 失败 |
| | | static FAILED = 4 |
| | | |
| | | public data |
| | | |
| | | constructor(data) { |
| | | this.initData(data) |
| | | this.start() |
| | | } |
| | | |
| | | // 新建任务 |
| | | initData = (data) => { |
| | | if (data.id) { |
| | | this.data = { ...data } |
| | | } else { |
| | | this.data = { |
| | | id: uuid(8), |
| | | startDate: moment().format('YYYY-MM-DD HH:mm:ss'), |
| | | endDate: '', |
| | | bookTpl: null, // 图书模板(Excel模板) |
| | | fileTpl: null, // 文件导出模板 |
| | | tplInfo: data.tplInfo, |
| | | bookInfo: data.bookInfo, // 导出的图书 |
| | | typeKeyMap: null, |
| | | state: ExportTask.INIT, // 任务状态 |
| | | fileTypeList: [], // 需下载文件类型 |
| | | tabHeader: [], // Excel表头数据 |
| | | tabData: [], // Excel表头数据 |
| | | temporaryFolderPath: '', // 临时文件夹目录 |
| | | bookFolderPath: null, // 图书文件夹path |
| | | fileMd5List: {}, // 图书文件 |
| | | taskPath: '', |
| | | progressInfo: { |
| | | // 进程信息 |
| | | createTask: ExportTask.INIT, |
| | | temporaryFolder: ExportTask.INIT, |
| | | handleFile: {}, |
| | | handleBook: { |
| | | state: ExportTask.INIT, |
| | | info: { |
| | | success: 0, |
| | | failed: 0, |
| | | total: 0 |
| | | } |
| | | }, |
| | | handleFolder: {}, |
| | | handleExcel: ExportTask.INIT |
| | | } |
| | | } |
| | | } |
| | | this.preloadData(this.data) |
| | | } |
| | | |
| | | // 开始任务 |
| | | start = () => { |
| | | const task = this.data |
| | | task.state = ExportTask.LOADING |
| | | // 请求数据 |
| | | if ( |
| | | !task.fileTypeList || |
| | | !task.fileTypeList.length || |
| | | !task.tabHeader || |
| | | !task.tabHeader.length |
| | | ) { |
| | | this.preloadData(task) |
| | | } else { |
| | | // 临时文件夹 |
| | | if (!task.temporaryFolderPath) { |
| | | this.createTemporaryFolder(task) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 预处理数据 |
| | | preloadData = (task) => { |
| | | // 先请求相关数据,并预处理 |
| | | task.progressInfo.createTask = ExportTask.LOADING |
| | | this.getTplInfo(task.tplInfo).then((data) => { |
| | | console.log(data) |
| | | if (data) { |
| | | let fileTypeList = [] |
| | | for (let i = 0; i < task.fileTpl.fileData.length; i++) { |
| | | const folderItem = task.fileTpl.fileData[i] |
| | | fileTypeList = fileTypeList.concat(folderItem.fileType) |
| | | } |
| | | task.fileTypeList = fileTypeList |
| | | // 处理图书模板创建excel表头 |
| | | const tabHeader: any = [] |
| | | for (let j = 0; j < task.bookTpl.fieldKeys.length; j++) { |
| | | const field = task.bookTpl.fieldKeys[j] |
| | | tabHeader.push({ |
| | | key: field.typeField.refCode, |
| | | label: field.title |
| | | }) |
| | | } |
| | | task.tabHeader = tabHeader |
| | | task.progressInfo.handleBook.info.total = task.bookInfo.length |
| | | this.sendProgressInfo() |
| | | // 临时文件夹 |
| | | if (!task.temporaryFolderPath) { |
| | | this.createTemporaryFolder(task) |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 获取模板信息 |
| | | getTplInfo = (data) => { |
| | | if (data.type == 'user') { |
| | | return request({ |
| | | url: '/identity/User/GetUserKey', |
| | | method: 'post', |
| | | headers: { |
| | | Authorization: 'bearer ' + token |
| | | }, |
| | | data: { |
| | | userKeyList: [{ storeKey: 'personImportTemplate', key: data.key }] |
| | | } |
| | | }) |
| | | .then((res) => { |
| | | if (res && res.length > 0) { |
| | | try { |
| | | return JSON.parse(res[0].value) |
| | | } catch (error) { |
| | | return null |
| | | } |
| | | } |
| | | }) |
| | | .catch((error) => { |
| | | console.log(error) |
| | | }) |
| | | } |
| | | if (data.type == 'sys') { |
| | | return request({ |
| | | url: '/identity/Sys/GetSysKey', |
| | | method: 'post', |
| | | headers: { |
| | | Authorization: 'bearer ' + token |
| | | }, |
| | | data: { |
| | | userKeyList: [{ storeKey: 'sysImportTemplate', key: data.key }] |
| | | } |
| | | }).then((res) => { |
| | | if (res && res.length > 0) { |
| | | try { |
| | | return JSON.parse(res[0].value) |
| | | } catch (error) { |
| | | return null |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // 创建临时文件夹 |
| | | createTemporaryFolder = (task) => { |
| | | task.progressInfo.temporaryFolder = ExportTask.LOADING |
| | | this.sendProgressInfo() |
| | | // 临时文件夹名称 |
| | | const folderName = 'TF' + moment().valueOf() |
| | | // 创建临时文件夹 |
| | | const folderPath = path.join(DOWNLOAD_PATH, folderName) |
| | | fs.mkdirSync(folderPath, { recursive: true }) |
| | | task.temporaryFolderPath = folderPath |
| | | // 更新状态 |
| | | task.progressInfo.temporaryFolder = ExportTask.FINISHED |
| | | this.sendProgressInfo() |
| | | this.createBookFolder(task) |
| | | } |
| | | |
| | | // 创建图书文件夹 |
| | | createBookFolder = (task) => { |
| | | if (!task.bookFolderPath) task.bookFolderPath = {} |
| | | // 在临时文件夹中创建不同书籍文件夹 |
| | | for (let i = 0; i < task.bookInfo.length; i++) { |
| | | const book = task.bookInfo[i] |
| | | if (book.ISBN) book.ISBN = book.ISBN.replace(new RegExp('-', 'gm'), '') |
| | | const folderPath = path.join( |
| | | task.temporaryFolderPath, |
| | | book.name + (book.ISBN ? '(' + book.ISBN + ')' : '') |
| | | ) |
| | | fs.mkdirSync(folderPath, { recursive: true }) |
| | | task.bookFolderPath[book.id] = folderPath |
| | | task.fileMd5List[book.id] = [] |
| | | task.progressInfo.handleFile[book.id] = { |
| | | state: ExportTask.INIT, |
| | | info: { |
| | | success: 0, |
| | | failed: 0, |
| | | total: 0 |
| | | } |
| | | } |
| | | task.progressInfo.handleFolder[book.id] = ExportTask.INIT |
| | | this.sendProgressInfo() |
| | | // 获取图书信息 |
| | | this.getBookData(book, task) |
| | | } |
| | | } |
| | | |
| | | // 获取图书信息 |
| | | getBookData(book, task) { |
| | | task.progressInfo.handleBook.state = ExportTask.LOADING |
| | | this.sendProgressInfo() |
| | | // const idPath = book.idPath.slice(0, book.idPath.lastIndexOf('\\')) |
| | | // this.getCmsItem(book.storeId, book.repoId, idPath, book.id, task.typeKeyMap) |
| | | // .then((result) => { |
| | | // const bookInfo = result.data.data[0].datas[0].datas |
| | | // for (const key in bookInfo) { |
| | | // if (bookInfo[key]) { |
| | | // try { |
| | | // bookInfo[key] = JSON.parse(bookInfo[key])[0].Value |
| | | // } catch (error) { |
| | | // console.log(error) |
| | | // } |
| | | // } |
| | | // } |
| | | // task.tabData.push(bookInfo) |
| | | // task.progressInfo.handleBook.info.success++ |
| | | // // 获取文件信息 |
| | | // this.getFileData(book, task) |
| | | // this.sendProgressInfo() |
| | | // if (task.progressInfo.handleBook.info.success == task.progressInfo.handleBook.info.total) { |
| | | // task.progressInfo.handleBook.state = ExportTask.FINISHED |
| | | // this.sendProgressInfo() |
| | | // this.exportExcel(task) |
| | | // } |
| | | // }) |
| | | // .catch((e) => { |
| | | // console.error(e) |
| | | // }) |
| | | } |
| | | |
| | | // 获取文件信息 |
| | | getFileData(book, task) { |
| | | task.progressInfo.handleFile[book.id].state = ExportTask.LOADING |
| | | this.sendProgressInfo() |
| | | // let requestIndex = 0 |
| | | // let bookFile = [] |
| | | for (let i = 0; i < book.getFilePaths.length; i++) { |
| | | // const filePath = book.getFilePaths[i] |
| | | // this.getCmsItem({ |
| | | // repo: { |
| | | // storeId: book.storeId, |
| | | // repoId: book.repoId |
| | | // }, |
| | | // parent: { idPath: filePath }, |
| | | // paging: { Start: 0, Size: 10 }, |
| | | // sysTypes: ['CmsFile'], |
| | | // recursion: true |
| | | // }) |
| | | // .then((result) => { |
| | | // bookFile = bookFile.concat(result.datas) |
| | | // requestIndex++ |
| | | // if (requestIndex == book.getFilePaths.length) { |
| | | // this.handleBookFile(book, task, bookFile) |
| | | // } |
| | | // }) |
| | | // .catch((e) => { |
| | | // console.error(e) |
| | | // }) |
| | | } |
| | | } |
| | | |
| | | // 处理图书文件 |
| | | handleBookFile = (book, task, bookFile) => { |
| | | // 获取到图书下所有文件信息 |
| | | const bookFileList = bookFile.filter((item) => { |
| | | return item.datas.LinkFile && task.fileTypeList.indexOf(item.type) > -1 |
| | | }) |
| | | // 需判断去重,同样的文件只下载一个(如果同样的文件移动两次,第二次会报错;如果是要移动至多个文件夹,移动时能兼容) |
| | | const newBookFileList: any = [] |
| | | for (let i = 0; i < bookFileList.length; i++) { |
| | | const exists = newBookFileList.filter((item) => { |
| | | return bookFileList[i].linkFile[0].File.Md5 == item.linkFile[0].File.Md5 |
| | | }) |
| | | if (!exists.length) { |
| | | newBookFileList.push(bookFileList[i]) |
| | | } |
| | | } |
| | | task.progressInfo.handleFile[book.id].info.total = newBookFileList.length |
| | | this.sendProgressInfo() |
| | | if (newBookFileList && newBookFileList.length) { |
| | | for (let i = 0; i < newBookFileList.length; i++) { |
| | | const item = newBookFileList[i] |
| | | const linkInfo = JSON.parse(item.datas.LinkFile)[0] |
| | | linkInfo.File.MetaData = JSON.parse(linkInfo.File.MetaData) |
| | | linkInfo.File.itemType = item.type |
| | | task.fileMd5List[book.id].push(linkInfo.File) |
| | | // 调用下载队列进行下载(异步) |
| | | // const downloadPath = path.join( |
| | | // task.bookFolderPath[book.id], |
| | | // linkInfo.File.MetaData.fileName |
| | | // ) |
| | | // const info = new DownloadInfo( |
| | | // apps.resAppId, |
| | | // linkInfo.File.Md5, |
| | | // 0, |
| | | // linkInfo.File.MetaData.size, |
| | | // downloadPath |
| | | // ) |
| | | // const downloadTask = new DownloadTask(info) |
| | | // downloadTask.start((downloadInfo) => { |
| | | // task.progressInfo.handleFile[book.id].info.success++ |
| | | // this.sendProgressInfo() |
| | | // // let isbn = path.basename(path.dirname(downloadInfo.path)); |
| | | // for (let i = 0; i < task.fileMd5List[book.id].length; i++) { |
| | | // const item = task.fileMd5List[book.id][i] |
| | | // if (item.Md5 == downloadInfo.md5 && !item.path) { |
| | | // item.path = downloadInfo.path |
| | | // break |
| | | // } |
| | | // } |
| | | // if ( |
| | | // task.progressInfo.handleFile[book.id].info.success == |
| | | // task.progressInfo.handleFile[book.id].info.total |
| | | // ) { |
| | | // task.progressInfo.handleFile[book.id].state = ExportTask.FINISHED |
| | | // this.sendProgressInfo() |
| | | // this.handleBookFileMerge(task, book.id) |
| | | // } |
| | | // }) |
| | | } |
| | | } else { |
| | | task.progressInfo.handleFile[book.id].state = ExportTask.FINISHED |
| | | task.progressInfo.handleFolder[book.id] = ExportTask.FINISHED |
| | | this.sendProgressInfo() |
| | | this.taskSucess(task) |
| | | } |
| | | } |
| | | |
| | | // 按文件模板合并图书文件 |
| | | handleBookFileMerge = (task, key) => { |
| | | task.progressInfo.handleFolder[key] = ExportTask.LOADING |
| | | this.sendProgressInfo() |
| | | const fileTpl = task.fileTpl |
| | | const typeToFolderList = {} |
| | | if (task.bookTpl.tplType == 1 || !task.bookTpl.tplType) { |
| | | // 按图书导出 |
| | | // 创建模板对应的文件夹 |
| | | for (let i = 0; i < fileTpl.fileData.length; i++) { |
| | | const item = fileTpl.fileData[i] |
| | | // 创建文件夹 |
| | | const folderPath = path.join(task.bookFolderPath[key], item.fileName) |
| | | fs.mkdirSync(folderPath, { recursive: true }) |
| | | for (let z = 0; z < item.fileType.length; z++) { |
| | | const typeItem = item.fileType[z] |
| | | if (!typeToFolderList[typeItem]) { |
| | | typeToFolderList[typeItem] = [] |
| | | } |
| | | typeToFolderList[typeItem].push({ |
| | | folderName: item.fileName, |
| | | folderPath: folderPath |
| | | }) |
| | | } |
| | | } |
| | | } else if (task.bookTpl.tplType == 2) { |
| | | // 按文件夹导出 |
| | | // 创建模板对应的文件夹 |
| | | for (let i = 0; i < fileTpl.fileData.length; i++) { |
| | | const item = fileTpl.fileData[i] |
| | | // 创建文件夹 |
| | | const folderPath = path.join(task.temporaryFolderPath, item.fileName) |
| | | const stat = fs.existsSync(folderPath) |
| | | if (!stat) { |
| | | fs.mkdirSync(folderPath, { recursive: true }) |
| | | } |
| | | for (let z = 0; z < item.fileType.length; z++) { |
| | | const typeItem = item.fileType[z] |
| | | if (!typeToFolderList[typeItem]) { |
| | | typeToFolderList[typeItem] = [] |
| | | } |
| | | typeToFolderList[typeItem].push({ |
| | | folderName: item.fileName, |
| | | folderPath: folderPath |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | // 移动文件对应类型文件至文件夹 |
| | | for (let j = 0; j < task.fileMd5List[key].length; j++) { |
| | | let file = task.fileMd5List[key][j] |
| | | if (typeToFolderList[file.itemType] && typeToFolderList[file.itemType].length > 0) { |
| | | // 处理重命名 |
| | | file = this.handleRename(task, file, key) |
| | | if (typeToFolderList[file.itemType].length > 1) { |
| | | // 一个类型对应多个文件夹时,先复制后移动 |
| | | for (let n = 0; n < typeToFolderList[file.itemType].length; n++) { |
| | | const folder = typeToFolderList[file.itemType][n] |
| | | if (n < typeToFolderList[file.itemType].length - 1) { |
| | | // 复制 |
| | | fs.copyFileSync(file.path, path.join(folder.folderPath, file.MetaData.fileName)) |
| | | } else { |
| | | // 移动 |
| | | fs.renameSync(file.path, path.join(folder.folderPath, file.MetaData.fileName)) |
| | | } |
| | | } |
| | | } else { |
| | | // 直接移动 |
| | | fs.renameSync( |
| | | file.path, |
| | | path.join(typeToFolderList[file.itemType][0].folderPath, file.MetaData.fileName) |
| | | ) |
| | | } |
| | | } |
| | | } |
| | | // 如果按文件夹导出,需删除图书文件夹 |
| | | if (task.bookTpl.tplType == 2) { |
| | | try { |
| | | fs.rmdirSync(task.bookFolderPath[key]) |
| | | } catch (error) { |
| | | console.log(error) |
| | | } |
| | | } |
| | | |
| | | task.progressInfo.handleFolder[key] = ExportTask.FINISHED |
| | | this.sendProgressInfo() |
| | | this.taskSucess(task) |
| | | } |
| | | |
| | | // 处理重命名 |
| | | handleRename = (task, file, bookId) => { |
| | | // 获取图书信息 |
| | | const bookInfo = task.tabData.filter((item) => { |
| | | return item.Id == bookId |
| | | }) |
| | | const renameData = task.fileTpl.renameData |
| | | if (!renameData) { |
| | | return file |
| | | } |
| | | for (let i = 0; i < renameData.length; i++) { |
| | | const renameItem = renameData[i] |
| | | if (renameItem.fileType == file.itemType) { |
| | | let newFileName = '' |
| | | const oldFileName = file.MetaData.fileName |
| | | const suffix = oldFileName.substring(oldFileName.lastIndexOf('.') + 1, oldFileName.length) |
| | | // 拼接前置字符 |
| | | if (renameItem.firstTxt) { |
| | | newFileName += renameItem.firstTxt |
| | | } |
| | | // 拼接替换名称 |
| | | const field = renameItem.renameField.split('/')[0] |
| | | if (bookInfo.length && bookInfo[0][field]) { |
| | | let fieldTxt = bookInfo[0][field] |
| | | // 处理特殊字符 |
| | | const reg = new RegExp('[\\/:*?"<>]', 'g') |
| | | fieldTxt = fieldTxt.replace(reg, '') |
| | | for (let z = 0; z < renameItem.formatTxt.length; z++) { |
| | | const formatItem = renameItem.formatTxt[z] |
| | | if (formatItem == 'nbsp') { |
| | | fieldTxt = fieldTxt.replace(new RegExp(' ', 'gm'), '') |
| | | } else { |
| | | fieldTxt = fieldTxt.replace(new RegExp(formatItem, 'gm'), '') |
| | | } |
| | | } |
| | | newFileName += fieldTxt |
| | | } else { |
| | | const oldName = oldFileName.substring(0, oldFileName.lastIndexOf('.')) |
| | | newFileName += oldName |
| | | } |
| | | // 拼接后置字符 |
| | | if (renameItem.lastTxt) { |
| | | newFileName += renameItem.lastTxt |
| | | } |
| | | // 拼接后缀 |
| | | newFileName = newFileName + '.' + suffix |
| | | // 返回数据 |
| | | file.MetaData.fileName = newFileName |
| | | return file |
| | | } |
| | | } |
| | | return file |
| | | } |
| | | |
| | | // 导出excel |
| | | exportExcel = (task) => { |
| | | task.progressInfo.handleExcel = ExportTask.LOADING |
| | | this.sendProgressInfo() |
| | | const excelPath = path.join(task.temporaryFolderPath, '图书信息.xls') |
| | | console.log(task) |
| | | const excelData: any = [] |
| | | const headerData: any = [] |
| | | for (let i = 0; i < task.tabHeader.length; i++) { |
| | | const header = task.tabHeader[i] |
| | | headerData.push(header.label) |
| | | } |
| | | const tableData: any = [] |
| | | for (let z = 0; z < task.tabData.length; z++) { |
| | | const tabItem = task.tabData[z] |
| | | const itemData: any = [] |
| | | for (let j = 0; j < task.tabHeader.length; j++) { |
| | | const headerItem = task.tabHeader[j] |
| | | itemData.push(tabItem[headerItem.key]) |
| | | } |
| | | tableData.push(itemData) |
| | | } |
| | | excelData.push(headerData) |
| | | excelData.push(...tableData) |
| | | console.log(excelData) |
| | | const buffer = xlsx.build([{ name: 'Sheet', data: excelData }]) |
| | | fs.writeFileSync(excelPath, buffer) |
| | | task.progressInfo.handleExcel = ExportTask.FINISHED |
| | | this.sendProgressInfo() |
| | | this.taskSucess(task) |
| | | } |
| | | |
| | | // 完成 |
| | | taskSucess = (task) => { |
| | | let bookFileState = true |
| | | for (const key in task.progressInfo.handleFolder) { |
| | | if (task.progressInfo.handleFolder[key] !== ExportTask.FINISHED) { |
| | | bookFileState = false |
| | | break |
| | | } |
| | | } |
| | | if (bookFileState && task.progressInfo.handleExcel === ExportTask.FINISHED) { |
| | | // const obj = path.parse(task.temporaryFolderPath) |
| | | // const creactTime = parseInt(obj.base.replace('TF', '')) |
| | | const newFolderName = task.bookTpl.name + '_' + task.key |
| | | const newPath = path.join(path.dirname(task.temporaryFolderPath), newFolderName) |
| | | fs.renameSync(task.temporaryFolderPath, newPath) |
| | | task.taskPath = newPath |
| | | task.endDate = moment().format('YYYY-MM-DD HH:mm:ss') |
| | | task.state = ExportTask.FINISHED |
| | | this.sendProgressInfo() |
| | | } |
| | | } |
| | | |
| | | // 发送进度信息 |
| | | sendProgressInfo = () => {} |
| | | |
| | | // 获取用于显示的数据 |
| | | // getShowData = () => { |
| | | // const showDataList = [] |
| | | // for (let i = 0; i < this.taskList.length; i++) { |
| | | // const taskItem = this.taskList[i] |
| | | // const obj = { |
| | | // id: taskItem.id, |
| | | // bookTplName: taskItem.bookTpl.name, |
| | | // fileTplName: taskItem.fileTpl.name, |
| | | // bookInfo: { |
| | | // name: taskItem.bookInfo.name, |
| | | // ISBN: taskItem.bookInfo.ISBN |
| | | // }, |
| | | // state: taskItem.state, |
| | | // progressInfo: taskItem.progressInfo, |
| | | // startDate: taskItem.startDate, |
| | | // endDate: taskItem.endDate, |
| | | // taskPath: taskItem.taskPath |
| | | // } |
| | | // showDataList.unshift(obj) |
| | | // } |
| | | // return showDataList |
| | | // } |
| | | |
| | | getCmsItem = ({ |
| | | path, |
| | | storeId, |
| | | repositoryId, |
| | | type, |
| | | paging, |
| | | sort, |
| | | linkTypes, |
| | | fields, |
| | | filters, |
| | | subQuery, |
| | | keyword, |
| | | itemId, |
| | | resType |
| | | }: CmsItemProps) => { |
| | | const query = { |
| | | AccessControl: { |
| | | Path: path, |
| | | StoreId: storeId + '', |
| | | RepositoryId: repositoryId + '', |
| | | Type: type ? type : '\\' |
| | | }, |
| | | PageQuery: paging, |
| | | SortQuery: sort ? [sort] : [], |
| | | CreateDate: [], |
| | | Description: [], |
| | | Name: [], |
| | | RefCode: [], |
| | | Type: [], |
| | | TypeId: [], |
| | | State: [], |
| | | Tag: [], |
| | | LinkDepartment: [], |
| | | LinkOrg: [], |
| | | LinkInfo: [], |
| | | LinkId: [], |
| | | LinkOrder: [], |
| | | LinkParentId: [], |
| | | LinkFile: [], |
| | | LinkType: linkTypes ?? [], |
| | | LinkStore: [], |
| | | LinkRepository: [], |
| | | LinkPath: [], |
| | | LinkAppId: [], |
| | | Creator: [], |
| | | ...fields, |
| | | ...filters, |
| | | ...subQuery |
| | | } |
| | | |
| | | if (keyword) { |
| | | delete query.Name |
| | | query['Name*'] = [keyword] |
| | | } |
| | | if (itemId) query['Id='] = [`${itemId}`] |
| | | const body = { query: JSON.stringify({ Query: [{ Q1: query }] }) } |
| | | return request({ |
| | | url: '/resource/ResourceItem/QueryCmsItem', |
| | | method: 'post', |
| | | headers: { |
| | | Authorization: 'bearer ' + token |
| | | }, |
| | | data: body |
| | | }).then((res) => { |
| | | if (res && res.length > 0) { |
| | | const data = res[0] |
| | | const datas = this.handleCmsItemListRequestData( |
| | | data.datas, |
| | | fields, |
| | | path, |
| | | storeId, |
| | | repositoryId |
| | | ) |
| | | return { datas, total: data.totalCount } |
| | | } else { |
| | | return { datas: [], total: 0 } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | handleCmsItemListRequestData = (datas, fields, path?, storeId?, repositoryId?) => { |
| | | const dataList = [] |
| | | for (let i = 0; i < datas.length; i++) { |
| | | const item = datas[i] |
| | | const _fields = {} |
| | | const _datas = [] |
| | | if (fields != null) { |
| | | for (let fieldKey in fields) { |
| | | // 兼容筛选条件的字段值获取,因为后台筛选和取值只能传一个,都会返回值 |
| | | fieldKey = fieldKey.replace(/[!=<>*]/g, '') |
| | | if (item.datas[fieldKey]) { |
| | | let values = [] |
| | | if (typeof item.datas[fieldKey] == 'string') { |
| | | values = JSON.parse(item.datas[fieldKey]) |
| | | } else { |
| | | values = item.datas[fieldKey] |
| | | } |
| | | if (values?.length > 0) { |
| | | // 用字段名处理返回的字段值 |
| | | if (values[0].Value) { |
| | | _fields[fieldKey] = values[0].Value |
| | | values[0].sequenceNum = values[0].SequenceNum |
| | | } |
| | | // 兼容处理数据返回的key是CmsItemData |
| | | // if (values[0].CmsItemData) { |
| | | // _fields[fieldKey] = values[0].CmsItemData.Value; |
| | | // values[0].sequenceNum = values[0].CmsItemData.SequenceNum; |
| | | // } |
| | | |
| | | item.datas[fieldKey] = values[0] |
| | | if (values?.length > 1) { |
| | | const isFile = values.find((citem) => citem.FileList?.length > 0) |
| | | const dataItems = this.deduplicateArray(values, 'FieldId') |
| | | if (!isFile) { |
| | | _datas.push(dataItems[0]) |
| | | } else { |
| | | const customFile = { |
| | | customFileList: values, |
| | | name: fieldKey, |
| | | md5: _fields[fieldKey] |
| | | } |
| | | _datas.push(customFile) |
| | | } |
| | | } else { |
| | | _datas.push(values[0]) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (item.datas.LogQuery) { |
| | | item.datas.LogQuery = JSON.parse(item.datas.LogQuery) |
| | | } |
| | | |
| | | const subDatas = {} |
| | | if (item.subDatas) { |
| | | for (let subData of item.subDatas) { |
| | | const tag = subData.queryTag.replace('Query', '') |
| | | subDatas[tag] = subData.datas |
| | | } |
| | | } |
| | | |
| | | dataList.push({ |
| | | ...item, |
| | | id: item.id, |
| | | name: item.datas.Name, |
| | | icon: item.datas.Icon, |
| | | storeId: storeId, |
| | | repositoryId: repositoryId, |
| | | refCode: item.datas.RefCode === '[]' ? null : item.datas.RefCode, |
| | | state: item.datas.State, |
| | | type: item.datas.Type, |
| | | tag: item.datas.Tag, |
| | | creator: item.datas.Creator ? JSON.parse(item.datas.Creator) : undefined, |
| | | linkType: item.datas.LinkType, |
| | | childrenCount: parseInt(item.datas.ChildrenCount ?? '0'), |
| | | childrenFolderCount: parseInt(item.datas.ChildrenFolderCount ?? '0'), |
| | | childrenChannelCount: parseInt(item.datas.ChildrenChannelCount ?? '0'), |
| | | childrenCmsItemCount: parseInt(item.datas.ChildrenCmsItemCount ?? '0'), |
| | | childrenFileCount: parseInt(item.datas.ChildrenFileCount ?? '0'), |
| | | createDate: moment(item.datas.CreateDate).format('YYYY-MM-DD HH:mm:ss'), |
| | | description: item.datas.Description, |
| | | sysType: item.datas.SysType, |
| | | idPath: path + '\\' + item.id, |
| | | typeId: parseInt(item.datas.TypeId), |
| | | linkAppId: item.datas.linkAppId, |
| | | linkFile: JSON.parse(item.datas.LinkFile ?? '[]'), |
| | | linkInfo: item.datas.LinkInfo ? JSON.parse(item.datas.LinkInfo) : [], |
| | | linkPath: item.datas.LinkPath ?? null, |
| | | linkOrg: item.datas.linkOrg ? JSON.parse(item.datas.linkOrg) : [], |
| | | linkDepartment: item.datas.linkDepartment ? JSON.parse(item.datas.linkDepartment) : [], |
| | | ..._fields, |
| | | datas: item.datas, |
| | | fieldList: _datas, |
| | | subDatas |
| | | }) |
| | | } |
| | | return dataList |
| | | } |
| | | |
| | | //数组去重 |
| | | deduplicateArray = (arr, idKey) => { |
| | | const seen = {} |
| | | const deduplicatedArray = arr.filter((item) => { |
| | | const id = item[idKey] |
| | | if (!seen[id]) { |
| | | seen[id] = true |
| | | return true |
| | | } |
| | | return false |
| | | }) |
| | | return deduplicatedArray |
| | | } |
| | | } |
| | | |
| | | // 本地缓存加载任务列表 |
| | | const loadTasks = () => { |
| | | const txt = ExportTasks.get(EXPORT_TASKS, '[]') |
| | | try { |
| | | taskList = JSON.parse(txt) |
| | | } catch (error) { |
| | | taskList = [] |
| | | } |
| | | } |
| | | |
| | | // 本地缓存记录任务列表 |
| | | const saveTasks = () => { |
| | | ExportTasks.set(EXPORT_TASKS, JSON.stringify(taskList)) |
| | | } |
| | | |
| | | const updateTask = (data?) => { |
| | | // 调用渲染进程 |
| | | if (data) { |
| | | mainWindow.webContents.send('exportTaskChange', JSON.stringify(data)) |
| | | const index = taskList.findIndex((item) => item.id == data.id) |
| | | taskList[index] = data |
| | | } else { |
| | | mainWindow.webContents.send('exportTaskChange') |
| | | } |
| | | saveTasks() |
| | | } |
| | | |
| | | // 获取任务列表 |
| | | ipcMain.on('getExportTasks', (ev, data) => { |
| | | // 页面会先获取任务列表,在获取列表时将token挂载 |
| | | mainWindow.webContents |
| | | .executeJavaScript('localStorage.getItem("token");', true) |
| | | .then((result) => { |
| | | token = result |
| | | }) |
| | | const returnData = JSON.parse(JSON.stringify(taskList)) |
| | | ev.returnValue = returnData |
| | | }) |
| | | |
| | | // 新建任务 |
| | | ipcMain.on('newExportTask', async (ev, data) => { |
| | | // 判断下载目录是否存在 |
| | | if (!fs.existsSync(DOWNLOAD_PATH)) { |
| | | // 未获取到默认下载目录 |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | showType: 'ExportTask', |
| | | type: 'notDownloadFolder', |
| | | msg: JSON.stringify(data) |
| | | }) |
| | | ) |
| | | } else { |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | showType: 'ExportTask', |
| | | type: 'showState', |
| | | msg: '检测到导出任务,正在创建...' |
| | | }) |
| | | ) |
| | | const task = new ExportTask(data) |
| | | if (!data.id && data.autoPlay) { |
| | | await task.start() |
| | | } |
| | | if (task.data) { |
| | | taskList.push(task.data) |
| | | updateTask(task) |
| | | } |
| | | mainWindow.webContents.send( |
| | | 'showMessage', |
| | | JSON.stringify({ |
| | | showType: 'ExportTask', |
| | | type: 'showState', |
| | | msg: '' |
| | | }) |
| | | ) |
| | | } |
| | | }) |
| | | |
| | | // 删除任务 |
| | | ipcMain.on('cleanExportTask', (ev, id) => { |
| | | // const item = taskList.find((item) => item.id == id) |
| | | // if (item.state == 'download') { |
| | | // const task = new DownloadTask(item) |
| | | // task.pause() |
| | | // } |
| | | |
| | | taskList = taskList.filter((item) => item.id != id) |
| | | updateTask() |
| | | ev.returnValue = true |
| | | }) |
| | | |
| | | // 清空任务 |
| | | ipcMain.on('cleanAllExportTask', (ev) => { |
| | | // for (let i = 0; i < taskList.length; i++) { |
| | | // const item = taskList[i] |
| | | // if (item.state == 'download') { |
| | | // const task = new DownloadTask(item) |
| | | // task.pause() |
| | | // } |
| | | // } |
| | | |
| | | taskList = [] |
| | | updateTask() |
| | | ev.returnValue = true |
| | | }) |
New file |
| | |
| | | import { app, BrowserWindow, globalShortcut, dialog, ipcMain } from 'electron' |
| | | import path from 'path' |
| | | // import fs from 'fs' |
| | | import { init as initDownloadTask } from './downloadTask' |
| | | // import { init as initExportTask } from './exportTask' |
| | | import { checkUpdate } from './update' |
| | | |
| | | import axios from 'axios' |
| | | import { ctx } from './config' |
| | | |
| | | // 用于本地测试更新 |
| | | // 手动设置为已打包 |
| | | Object.defineProperty(app, 'isPackaged', { |
| | | get() { |
| | | return true |
| | | } |
| | | }) |
| | | |
| | | let myWindow: any = null |
| | | |
| | | const request = axios.create() |
| | | |
| | | const development = process.env.NODE_ENV == 'development' ? true : false |
| | | |
| | | const createWindow = () => { |
| | | const win = new BrowserWindow({ |
| | | minWidth: development ? 1800 : 1300, |
| | | minHeight: development ? 1000 : 800, |
| | | width: development ? 1400 : 1300, |
| | | height: development ? 1000 : 800, |
| | | //窗口是否在屏幕居中. 默认值为 false |
| | | center: true, |
| | | //窗口是否在创建时显示。 默认值为 true。 |
| | | show: true, |
| | | title: '数字教材阅读器', |
| | | icon: 'public/favicon.ico', |
| | | webPreferences: { |
| | | // nodeIntegration: true, |
| | | // nodeIntegrationInWorker: true, |
| | | // contextIsolation: true, |
| | | // sandbox: false, |
| | | preload: path.join(__dirname, 'preload.js') |
| | | } |
| | | }) |
| | | win.setMenu(null) |
| | | if (development) { |
| | | const elePath = path.join(__dirname, '../node_modules/electron') |
| | | require('electron-reload')('../', { |
| | | electron: require(elePath) |
| | | }) |
| | | win.loadURL('http://localhost:8005') |
| | | win.webContents.openDevTools() |
| | | } else { |
| | | win.loadURL(path.join(__dirname, 'index.html')) |
| | | } |
| | | |
| | | globalShortcut.register('CommandOrControl+Shift+i', function () { |
| | | win.webContents.openDevTools() |
| | | }) |
| | | |
| | | // 解决应用启动白屏问题 |
| | | win.on('ready-to-show', () => { |
| | | win.show() |
| | | win.focus() |
| | | }) |
| | | |
| | | request.interceptors.request.use((config) => { |
| | | if (config.url && config.url.indexOf('http') == -1) { |
| | | config.url = ctx + config.url |
| | | } |
| | | return config |
| | | }) |
| | | |
| | | // 响应拦截器 |
| | | request.interceptors.response.use( |
| | | (response) => { |
| | | return response.data |
| | | }, |
| | | (error) => { |
| | | if (error.response && error.response.status == 401) { |
| | | win.webContents.send('logout') |
| | | } else { |
| | | if (error.response && error.response.data && error.response.data.error) { |
| | | console.error(error.response.data.error.msg) |
| | | } else { |
| | | console.error('请求发生错误') |
| | | } |
| | | // dialog.showMessageBox({ |
| | | // type: 'error', |
| | | // message: '错误', |
| | | // detail: JSON.stringify(error.response) |
| | | // }) |
| | | } |
| | | return Promise.reject(error) |
| | | } |
| | | ) |
| | | |
| | | // 业务主体 |
| | | // 下载器 |
| | | initDownloadTask(win, request) |
| | | // initExportTask(win, request) |
| | | |
| | | checkUpdate(win, ipcMain) |
| | | |
| | | ipcMain.on('openSelectFileOrFolderDialog', (ev, opt) => { |
| | | const path = dialog.showOpenDialogSync(win, opt) |
| | | ev.returnValue = path |
| | | }) |
| | | |
| | | return win |
| | | } |
| | | |
| | | // 获取单实例锁 |
| | | const gotTheLock = app.requestSingleInstanceLock() |
| | | if (!gotTheLock) { |
| | | // 如果获取失败,说明已经有实例在运行了,直接退出 |
| | | app.quit() |
| | | } else { |
| | | app.whenReady().then(() => { |
| | | myWindow = createWindow() |
| | | |
| | | app.on('activate', () => { |
| | | if (BrowserWindow.getAllWindows().length === 0) createWindow() |
| | | }) |
| | | |
| | | // 注册自定义协议 |
| | | const agreement = 'DigitalTextbookReader' // 开发环境 |
| | | |
| | | app.removeAsDefaultProtocolClient(agreement) // 每次运行都删除自定义协议 然后再重新注册 |
| | | // 开发模式下在window运行需要做兼容 |
| | | if (development) { |
| | | // 设置electron.exe 和 app的路径 |
| | | app.setAsDefaultProtocolClient(agreement, process.execPath, [ |
| | | path.resolve(process.argv[1]), |
| | | '--' |
| | | ]) |
| | | } |
| | | // 验证是否为自定义协议的链接 |
| | | const AGREEMENT_REGEXP = new RegExp(`^${agreement}://`) |
| | | |
| | | // mac唤醒应用 会激活open-url事件 在open-url中判断是否为自定义协议打开事件 |
| | | app.on('open-url', (event, url) => { |
| | | const isProtocol = AGREEMENT_REGEXP.test(url) |
| | | if (isProtocol) { |
| | | dialog.showMessageBox({ |
| | | type: 'info', |
| | | message: 'Mac protocol 自定义协议打开', |
| | | detail: `自定义协议链接:${url}` |
| | | }) |
| | | } |
| | | }) |
| | | |
| | | // window系统下唤醒应用会激活second-instance事件 它在ready执行之后才能被监听 |
| | | app.on('second-instance', (event, argv) => { |
| | | if (myWindow) { |
| | | if (myWindow.isMinimized()) myWindow.restore() |
| | | myWindow.focus() |
| | | } |
| | | if (process.platform === 'win32') { |
| | | // 开发阶段,跳过前两个参数(`electron.exe .`) |
| | | // 打包后,跳过第一个参数(`myapp.exe`) |
| | | const offset = app.isPackaged ? 1 : 2 |
| | | let url: any = argv.find((arg, i) => i >= offset && arg.startsWith(agreement)) |
| | | if (url) { |
| | | url = url.replace(`${agreement}://`, '') |
| | | url = url.substr(0, url.length - 1) |
| | | } |
| | | myWindow.webContents.send('openUrl', url) |
| | | } |
| | | }) |
| | | }) |
| | | |
| | | app.on('window-all-closed', () => { |
| | | if (process.platform !== 'darwin') app.quit() |
| | | }) |
| | | } |
New file |
| | |
| | | import { contextBridge, ipcRenderer } from 'electron' |
| | | |
| | | //桥接渲染进程与主进程 |
| | | const newDownloadTask = (data) => { |
| | | ipcRenderer.send('newDownloadTask', data) |
| | | } |
| | | const getDownloadTasks = () => { |
| | | return ipcRenderer.sendSync('getDownloadTasks') |
| | | } |
| | | const onDownloadTaskChange = (callback) => { |
| | | ipcRenderer.on('downloadTaskChange', (ev, data) => { |
| | | if (data) { |
| | | callback(JSON.parse(data)) |
| | | } else { |
| | | callback() |
| | | } |
| | | }) |
| | | } |
| | | const onShowMessage = (callback) => { |
| | | ipcRenderer.on('showMessage', (ev, data) => { |
| | | callback(JSON.parse(data)) |
| | | }) |
| | | } |
| | | const onOpenUrl = (callback) => { |
| | | ipcRenderer.on('openUrl', (ev, data) => { |
| | | callback(data) |
| | | }) |
| | | } |
| | | const onLogout = (callback) => { |
| | | ipcRenderer.on('logout', (ev, data) => { |
| | | callback(data) |
| | | }) |
| | | } |
| | | const cleanTask = (data) => { |
| | | ipcRenderer.send('cleanTask', data) |
| | | } |
| | | const cleanAll = () => { |
| | | ipcRenderer.send('cleanAll') |
| | | } |
| | | const pauseTask = (data) => { |
| | | ipcRenderer.send('pauseTask', data) |
| | | } |
| | | const pauseAllTask = () => { |
| | | ipcRenderer.send('pauseAllTask') |
| | | } |
| | | const startTask = (data) => { |
| | | ipcRenderer.send('startTask', data) |
| | | } |
| | | const startAllTask = () => { |
| | | ipcRenderer.send('startAllTask') |
| | | } |
| | | const openPath = (data) => { |
| | | return ipcRenderer.sendSync('openPath', data) |
| | | } |
| | | const openPathByTaskId = (data) => { |
| | | ipcRenderer.send('openPathByTaskId', data) |
| | | } |
| | | const openSelectFileOrFolderDialog = (data) => { |
| | | return ipcRenderer.sendSync('openSelectFileOrFolderDialog', data) |
| | | } |
| | | const getDownloadPath = () => { |
| | | return ipcRenderer.sendSync('getDownloadPath') |
| | | } |
| | | const setDownloadPath = (path, dataStr) => { |
| | | return ipcRenderer.sendSync('setDownloadPath', { path, dataStr }) |
| | | } |
| | | const onUpdateDownloadProgress = (callback) => { |
| | | ipcRenderer.on('updateDownloadProgress', (ev, data) => { |
| | | callback(data) |
| | | }) |
| | | } |
| | | const onUpdateDownloadSuccess = (callback) => { |
| | | ipcRenderer.on('updateDownloadSuccess', (ev) => { |
| | | callback() |
| | | }) |
| | | } |
| | | const updateApp = () => { |
| | | ipcRenderer.send('updateApp') |
| | | } |
| | | // 发行导出 |
| | | const onExportTaskChange = (callback) => { |
| | | ipcRenderer.on('exportTaskChange', (ev, data) => { |
| | | if (data) { |
| | | callback(JSON.parse(data)) |
| | | } else { |
| | | callback() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const newExportTask = (data) => { |
| | | ipcRenderer.send('newExportTask', data) |
| | | } |
| | | |
| | | const getExportTasks = () => { |
| | | return ipcRenderer.sendSync('getExportTasks') |
| | | } |
| | | const cleanExportTask = (data) => { |
| | | ipcRenderer.send('cleanExportTask', data) |
| | | } |
| | | const cleanAllExportTask = () => { |
| | | ipcRenderer.send('cleanAllExportTask') |
| | | } |
| | | |
| | | contextBridge.exposeInMainWorld('electronAPI', { |
| | | newDownloadTask, |
| | | getDownloadTasks, |
| | | onDownloadTaskChange, |
| | | onOpenUrl, |
| | | onShowMessage, |
| | | onLogout, |
| | | cleanTask, |
| | | cleanAll, |
| | | pauseTask, |
| | | pauseAllTask, |
| | | startTask, |
| | | startAllTask, |
| | | openPath, |
| | | openPathByTaskId, |
| | | openSelectFileOrFolderDialog, |
| | | getDownloadPath, |
| | | setDownloadPath, |
| | | onUpdateDownloadProgress, |
| | | onUpdateDownloadSuccess, |
| | | updateApp, |
| | | onExportTaskChange, |
| | | newExportTask, |
| | | getExportTasks, |
| | | cleanExportTask, |
| | | cleanAllExportTask, |
| | | }) |
New file |
| | |
| | | export function uuid(len = 32, radix = 16) { |
| | | const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); |
| | | let uuid = [], |
| | | i; |
| | | radix = radix || chars.length; |
| | | |
| | | if (len) { |
| | | // Compact form |
| | | for (i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)]; |
| | | } else { |
| | | // rfc4122, version 4 form |
| | | let r; |
| | | |
| | | // rfc4122 requires these characters |
| | | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; |
| | | uuid[14] = '4'; |
| | | |
| | | // Fill in random data. At i==19 set the high bits of clock sequence as |
| | | // per rfc4122, sec. 4.1.5 |
| | | for (i = 0; i < 36; i++) { |
| | | if (!uuid[i]) { |
| | | r = 0 | (Math.random() * 16); |
| | | uuid[i] = chars[i === 19 ? (r & 0x3) | 0x8 : r]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return uuid.join(''); |
| | | } |
| | | |
New file |
| | |
| | | import { autoUpdater } from 'electron-updater' |
| | | import { downloaderFileCtx } from './config' |
| | | let mainWin = null |
| | | |
| | | export const checkUpdate = (win, ipcMain) => { |
| | | mainWin = win |
| | | autoUpdater.autoDownload = true // 自动下载 |
| | | autoUpdater.autoInstallOnAppQuit = true // 应用退出后自动安装 |
| | | // 检测是否有更新包并通知 |
| | | // 测试下载更新 |
| | | autoUpdater.setFeedURL(downloaderFileCtx) |
| | | autoUpdater.checkForUpdates() |
| | | autoUpdater.on('download-progress', (prog) => { |
| | | mainWin.webContents.send('updateDownloadProgress', { |
| | | speed: Math.ceil(prog.bytesPerSecond / 1000), // 网速 |
| | | percent: Math.ceil(prog.percent) // 百分比 |
| | | }) |
| | | }) |
| | | autoUpdater.on('update-downloaded', (info) => { |
| | | mainWin.webContents.send('updateDownloadSuccess') |
| | | // 下载完成后强制用户安装,不推荐 |
| | | // autoUpdater.quitAndInstall(); |
| | | }) |
| | | |
| | | ipcMain.on('updateApp', () => { |
| | | autoUpdater.quitAndInstall() |
| | | }) |
| | | } |
New file |
| | |
| | | /// <reference types="vite/client" /> |
New file |
| | |
| | | <!DOCTYPE html> |
| | | <html lang="zh-cn"> |
| | | <head> |
| | | <meta charset="UTF-8"> |
| | | <link rel="icon" href="/favicon.ico"> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | | <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline';"> --> |
| | | <title>数字教材阅读器</title> |
| | | </head> |
| | | <body> |
| | | <div id="app"></div> |
| | | <script type="module" src="/src/main.ts"></script> |
| | | </body> |
| | | </html> |
New file |
| | |
| | | { |
| | | "name": "DigitalTextbookReader", |
| | | "version": "1.0.1", |
| | | "private": true, |
| | | "main": "electron-commonJS/main.js", |
| | | "scripts": { |
| | | "ele-ts-build": "tsc --project tsconfig.electron.json", |
| | | "hot-update-ts": "gulp watch:ts", |
| | | "hot-update-electron": "gulp watch:electron", |
| | | "dev": "SET NODE_ENV=development&& yarn ele-ts-build && vite", |
| | | "build": "yarn ele-ts-build && vite build && electron-builder", |
| | | "build_mac": "yarn ele-ts-build && vite build && electron-builder --mac", |
| | | "preview": "vite preview", |
| | | "build-only": "vite build", |
| | | "type-check": "vue-tsc --build --force", |
| | | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", |
| | | "format": "prettier --write src/" |
| | | }, |
| | | "dependencies": { |
| | | "@element-plus/icons-vue": "^2.3.1", |
| | | "axios": "^1.6.2", |
| | | "cross-env": "^7.0.3", |
| | | "electron-connect": "^0.6.3", |
| | | "electron-reload": "^2.0.0-alpha.1", |
| | | "electron-store": "^8.1.0", |
| | | "electron-updater": "^6.1.7", |
| | | "element-plus": "^2.4.3", |
| | | "less": "^4.2.0", |
| | | "less-loader": "^11.1.3", |
| | | "moment": "^2.29.4", |
| | | "node-xlsx": "^0.23.0", |
| | | "pinia": "^2.1.7", |
| | | "style-resources-loader": "^1.5.0", |
| | | "vite-plugin-electron": "^0.15.5", |
| | | "vue": "^3.3.10", |
| | | "vue-cli-plugin-style-resources-loader": "^0.1.5", |
| | | "vue-router": "^4.2.5" |
| | | }, |
| | | "devDependencies": { |
| | | "@rushstack/eslint-patch": "^1.3.3", |
| | | "@tsconfig/node18": "^18.2.2", |
| | | "@types/node": "^18.19.2", |
| | | "@vitejs/plugin-vue": "^4.5.1", |
| | | "@vue/eslint-config-prettier": "^8.0.0", |
| | | "@vue/eslint-config-typescript": "^12.0.0", |
| | | "@vue/tsconfig": "^0.4.0", |
| | | "electron": "21.4.4", |
| | | "electron-builder": "^24.9.1", |
| | | "electron-updater": "^6.1.7", |
| | | "eslint": "^8.49.0", |
| | | "eslint-plugin-vue": "^9.17.0", |
| | | "npm-run-all2": "^6.1.1", |
| | | "prettier": "^3.0.3", |
| | | "typescript": "~5.2.0", |
| | | "vite": "^5.0.5", |
| | | "vue-tsc": "^1.8.25" |
| | | }, |
| | | "build": { |
| | | "appId": "DigitalTextbookReader", |
| | | "productName": "数字教材阅读器", |
| | | "asar": true, |
| | | "directories": { |
| | | "output": "release/" |
| | | }, |
| | | "files": [ |
| | | "electron-commonJS", |
| | | { |
| | | "from": "dist", |
| | | "to": "electron-commonJS" |
| | | } |
| | | ], |
| | | "mac": { |
| | | "artifactName": "${productName}_${version}.${ext}", |
| | | "target": [ |
| | | "dmg" |
| | | ] |
| | | }, |
| | | "win": { |
| | | "target": [ |
| | | { |
| | | "target": "nsis", |
| | | "arch": [ |
| | | "x64" |
| | | ] |
| | | } |
| | | ], |
| | | "icon": "./public/favicon.ico", |
| | | "artifactName": "${productName}_${version}.${ext}" |
| | | }, |
| | | "nsis": { |
| | | "oneClick": false, |
| | | "perMachine": true, |
| | | "allowToChangeInstallationDirectory": true, |
| | | "deleteAppDataOnUninstall": true, |
| | | "include": "./build/installer.nsh" |
| | | }, |
| | | "publish": [ |
| | | { |
| | | "provider": "generic", |
| | | "url": "http://182.92.203.7:3007/" |
| | | } |
| | | ], |
| | | "releaseInfo": { |
| | | "releaseNotes": "这里填写具体的版本更新内容" |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | <template> |
| | | <div class="updateDownloadInfo" v-if="showUpdateInfo"> |
| | | <el-alert |
| | | :title="`检测到新版本,正在下载安装包${ |
| | | updateDownloadInfo?.percent ? ',进度:' + updateDownloadInfo?.percent + '%' : '...' |
| | | }`" |
| | | type="info" |
| | | effect="dark" |
| | | /> |
| | | </div> |
| | | <RouterView /> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, inject } from 'vue' |
| | | import { RouterView, useRouter } from 'vue-router' |
| | | import { useDownloadTask, useExportTask } from '@/store' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | const router = useRouter() |
| | | |
| | | const request = inject('request') |
| | | |
| | | const downloadTask = useDownloadTask() |
| | | const ExportTask = useExportTask() |
| | | |
| | | window.electronAPI.onLogout((data) => { |
| | | localStorage.clear() |
| | | router.replace({ |
| | | path: '/login' |
| | | }) |
| | | }) |
| | | |
| | | window.electronAPI.onOpenUrl((data) => { |
| | | let taskInfo = null |
| | | let downloadInfo = decodeURI(data) |
| | | console.log(downloadInfo, '接受到的taskInfo') |
| | | try { |
| | | taskInfo = JSON.parse(downloadInfo) |
| | | } catch (error) { |
| | | taskInfo = null |
| | | } |
| | | if (taskInfo) { |
| | | router.replace({ |
| | | path: '/transmission' |
| | | }) |
| | | window.electronAPI.newDownloadTask(taskInfo) |
| | | } |
| | | }) |
| | | |
| | | // 绑定消息提醒 |
| | | window.electronAPI.onShowMessage((data) => { |
| | | // 获取到消息后修改全局数据,页面监听全局数据进行变化 |
| | | if (data.showType) { |
| | | switch (data.showType) { |
| | | case 'DownloadTask': |
| | | downloadTask.setMsgData(data) |
| | | break |
| | | case 'ExportTask': |
| | | ExportTask.setMsgData(data) |
| | | break |
| | | } |
| | | } else { |
| | | downloadTask.setMsgData(data) |
| | | } |
| | | }) |
| | | |
| | | // 绑定下载任务变化更新 |
| | | window.electronAPI.onDownloadTaskChange((task) => { |
| | | downloadTask.setUpdateList() |
| | | }) |
| | | |
| | | // 绑定导出任务变化更新 |
| | | window.electronAPI.onExportTaskChange((task) => { |
| | | ExportTask.setUpdateList() |
| | | }) |
| | | |
| | | const showUpdateInfo = ref(false) |
| | | const updateDownloadInfo = ref() |
| | | |
| | | // 监听程序更新下载 |
| | | window.electronAPI.onUpdateDownloadProgress((data) => { |
| | | showUpdateInfo.value = true |
| | | console.log(data, 'updateDownloadInfo') |
| | | updateDownloadInfo.value = data |
| | | }) |
| | | |
| | | // 监听程序更新下载完成 |
| | | window.electronAPI.onUpdateDownloadSuccess((data) => { |
| | | showUpdateInfo.value = false |
| | | ElMessageBox.confirm('检测到新版本,安装包已下载完成,是否立即更新?', '检查更新', { |
| | | confirmButtonText: '更新', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }) |
| | | .then(() => { |
| | | window.electronAPI.updateApp() |
| | | }) |
| | | .catch(() => {}) |
| | | }) |
| | | |
| | | const token = localStorage.getItem('token') |
| | | if (token) { |
| | | request({ |
| | | url: '/identity/User/GetCurrentUser', |
| | | method: 'post' |
| | | }).then((res) => { |
| | | // console.log(res) |
| | | }) |
| | | } else { |
| | | router.replace({ |
| | | path: '/login' |
| | | }) |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | .updateDownloadInfo { |
| | | position: fixed; |
| | | top: 2px; |
| | | left: 82px; |
| | | right: 2px; |
| | | z-index: 999; |
| | | } |
| | | </style> |
New file |
| | |
| | | /* color palette from <https://github.com/vuejs/theme> */ |
| | | :root { |
| | | --vt-c-white: #ffffff; |
| | | --vt-c-white-soft: #f8f8f8; |
| | | --vt-c-white-mute: #f2f2f2; |
| | | |
| | | --vt-c-black: #181818; |
| | | --vt-c-black-soft: #222222; |
| | | --vt-c-black-mute: #282828; |
| | | |
| | | --vt-c-indigo: #2c3e50; |
| | | |
| | | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); |
| | | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); |
| | | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); |
| | | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); |
| | | |
| | | --vt-c-text-light-1: var(--vt-c-indigo); |
| | | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); |
| | | --vt-c-text-dark-1: var(--vt-c-white); |
| | | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); |
| | | } |
| | | |
| | | /* semantic color variables for this project */ |
| | | :root { |
| | | --color-background: var(--vt-c-white); |
| | | --color-background-soft: var(--vt-c-white-soft); |
| | | --color-background-mute: var(--vt-c-white-mute); |
| | | |
| | | --color-border: var(--vt-c-divider-light-2); |
| | | --color-border-hover: var(--vt-c-divider-light-1); |
| | | |
| | | --color-heading: var(--vt-c-text-light-1); |
| | | --color-text: var(--vt-c-text-light-1); |
| | | |
| | | --section-gap: 160px; |
| | | } |
| | | |
| | | @media (prefers-color-scheme: dark) { |
| | | :root { |
| | | --color-background: var(--vt-c-black); |
| | | --color-background-soft: var(--vt-c-black-soft); |
| | | --color-background-mute: var(--vt-c-black-mute); |
| | | |
| | | --color-border: var(--vt-c-divider-dark-2); |
| | | --color-border-hover: var(--vt-c-divider-dark-1); |
| | | |
| | | --color-heading: var(--vt-c-text-dark-1); |
| | | --color-text: var(--vt-c-text-dark-2); |
| | | } |
| | | } |
| | | |
| | | *, |
| | | *::before, |
| | | *::after { |
| | | box-sizing: border-box; |
| | | margin: 0; |
| | | font-weight: normal; |
| | | } |
| | | |
| | | html { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | body { |
| | | width: 100%; |
| | | height: 100%; |
| | | min-height: 100vh; |
| | | color: var(--color-text); |
| | | background: var(--color-background); |
| | | transition: |
| | | color 0.5s, |
| | | background-color 0.5s; |
| | | font-family: |
| | | Inter, |
| | | -apple-system, |
| | | BlinkMacSystemFont, |
| | | 'Segoe UI', |
| | | Roboto, |
| | | Oxygen, |
| | | Ubuntu, |
| | | Cantarell, |
| | | 'Fira Sans', |
| | | 'Droid Sans', |
| | | 'Helvetica Neue', |
| | | sans-serif; |
| | | font-size: 14px; |
| | | text-rendering: optimizeLegibility; |
| | | -webkit-font-smoothing: antialiased; |
| | | -moz-osx-font-smoothing: grayscale; |
| | | } |
New file |
| | |
| | | export const getFileSize = (fileByte: number) => { |
| | | const fileSizeByte = fileByte |
| | | let fileSizeMsg = '' |
| | | if (fileSizeByte < 1048576) fileSizeMsg = (fileSizeByte / 1024).toFixed(2) + 'KB' |
| | | else if (fileSizeByte == 1048576) fileSizeMsg = '1MB' |
| | | else if (fileSizeByte > 1048576 && fileSizeByte < 1073741824) |
| | | fileSizeMsg = (fileSizeByte / (1024 * 1024)).toFixed(2) + 'MB' |
| | | else if (fileSizeByte > 1048576 && fileSizeByte == 1073741824) fileSizeMsg = '1GB' |
| | | else if (fileSizeByte > 1073741824 && fileSizeByte < 1099511627776) |
| | | fileSizeMsg = (fileSizeByte / (1024 * 1024 * 1024)).toFixed(2) + 'GB' |
| | | else fileSizeMsg = '' |
| | | return fileSizeMsg |
| | | } |
| | | |
| | | export default { |
| | | getFileSize |
| | | } |
New file |
| | |
| | | @import './base.css'; |
| | | |
| | | #app { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | |
| | | page { |
| | | display: block; |
| | | } |
New file |
| | |
| | | @theme-color: #366aec; |
New file |
| | |
| | | <template> |
| | | <div class="layoutBox"> |
| | | <div class="menuBox"> |
| | | <div :class="['menuItem', activeMenu == index ? 'active' : '']" v-for="(item, index) in menuData" :key="index" |
| | | @click="menuItemClick(index)"> |
| | | <div class="menuIcon"> |
| | | <el-icon v-if="item.icon == 'FolderOpened'" :size="24"> |
| | | <FolderOpened /> |
| | | </el-icon> |
| | | <el-icon v-if="item.icon == 'Files'" :size="24"> |
| | | <Files /> |
| | | </el-icon> |
| | | <el-icon v-if="item.icon == 'Switch'" :size="24"> |
| | | <Switch /> |
| | | </el-icon> |
| | | <el-icon v-if="item.icon == 'Setting'" :size="24"> |
| | | <Setting /> |
| | | </el-icon> |
| | | </div> |
| | | <p>{{ item.name }}</p> |
| | | </div> |
| | | </div> |
| | | <div class="pageBox"> |
| | | <RouterView /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, watch } from 'vue' |
| | | import { useRouter, RouterView } from 'vue-router' |
| | | const router = useRouter() |
| | | |
| | | // 菜单 |
| | | const menuData = reactive([ |
| | | // { |
| | | // name: '文件', |
| | | // icon: 'FolderOpened', |
| | | // router: '/home' |
| | | // }, |
| | | { |
| | | name: '传输', |
| | | icon: 'Switch', |
| | | router: '/transmission' |
| | | }, |
| | | // { |
| | | // name: '导出', |
| | | // icon: 'Files', |
| | | // router: '/exportTask' |
| | | // }, |
| | | { |
| | | name: '设置', |
| | | icon: 'Setting', |
| | | router: '/setting' |
| | | } |
| | | ]) |
| | | |
| | | // 选中菜单 |
| | | const activeMenu = ref(0) |
| | | |
| | | // 监听路由变化,默认选中菜单 |
| | | watch( |
| | | () => router.currentRoute.value, (newRoute) => { |
| | | console.log(newRoute.path) |
| | | |
| | | const index = menuData.findIndex((item) => item.router == newRoute.path) |
| | | activeMenu.value = index > -1 ? index : 0 |
| | | } |
| | | ) |
| | | |
| | | // 菜单点击 |
| | | const menuItemClick = (index) => { |
| | | activeMenu.value = index |
| | | router.push(menuData[index].router) |
| | | } |
| | | </script> |
| | | |
| | | <style lang="less"> |
| | | .layoutBox { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | |
| | | .menuBox { |
| | | width: 80px; |
| | | background-color: #f5f5f6; |
| | | border-right: 1px solid #e6e7e8; |
| | | padding: 10px; |
| | | box-sizing: border-box; |
| | | |
| | | .menuItem { |
| | | padding: 8px 0; |
| | | text-align: center; |
| | | border-radius: 8px; |
| | | line-height: 1; |
| | | cursor: pointer; |
| | | margin-bottom: 10px; |
| | | |
| | | &.active, |
| | | &:hover { |
| | | background-color: #e3e3e5; |
| | | } |
| | | |
| | | .menuIcon { |
| | | margin-bottom: 4px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pageBox { |
| | | flex: 1; |
| | | overflow: auto; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | import './assets/main.css' |
| | | |
| | | import { createApp } from 'vue' |
| | | import pinia from '@/store/index' |
| | | import ElementPlus from 'element-plus' |
| | | import 'element-plus/dist/index.css' |
| | | import App from './App.vue' |
| | | import router from './router' |
| | | import * as ElementPlusIconsVue from '@element-plus/icons-vue' |
| | | import toolClass from '@/assets/js/toolClass' |
| | | import request from "@/plugin/axios/index.ts"; |
| | | |
| | | const handleGetToken = () => { |
| | | return localStorage.getItem("token"); |
| | | } |
| | | |
| | | // 路由执行之前的一些操作 |
| | | router.beforeEach((to, from, next) => { |
| | | // 如果有token |
| | | if (handleGetToken()) { |
| | | // 是否是登录页面,直接到首页 |
| | | if (to.path === "/login") { |
| | | next({ path: "/transmission" }); |
| | | } else { |
| | | // 如果不是登录页面,跳转到目标的页面 |
| | | next(); |
| | | } |
| | | } else { |
| | | // 没有token |
| | | if (!to.meta || !to.meta.auth) { |
| | | // 在免登录白名单,直接进入 |
| | | next(); |
| | | } else { |
| | | next(`/login?redirect=${to.fullPath}`); // 否则全部重定向到登录页 |
| | | } |
| | | } |
| | | }); |
| | | |
| | | const app = createApp(App) |
| | | app.provide('toolClass', toolClass) |
| | | app.provide('request', request) |
| | | app.use(router) |
| | | app.use(ElementPlus) |
| | | app.use(pinia) |
| | | for (const [key, component] of Object.entries(ElementPlusIconsVue)) { |
| | | app.component(key, component) |
| | | } |
| | | |
| | | app.mount('#app') |
New file |
| | |
| | | import axios from 'axios' |
| | | |
| | | import { ctx } from '../../../electron/config' |
| | | import router from '@/router' |
| | | |
| | | // 创建 axios 实例 |
| | | const service = axios.create({ |
| | | baseURL: ctx, |
| | | timeout: 300000 // 请求超时时间 |
| | | }) |
| | | |
| | | // 请求拦截器 |
| | | service.interceptors.request.use( |
| | | (config) => { |
| | | const token = localStorage.getItem('token') |
| | | if (token) config.headers['Authorization'] = `bearer ${token}` |
| | | return config |
| | | }, |
| | | (error) => { |
| | | // 发送失败 |
| | | Promise.reject(error) |
| | | } |
| | | ) |
| | | |
| | | // 响应拦截器 |
| | | service.interceptors.response.use( |
| | | (response) => { |
| | | if (response.status == 200) { |
| | | if (response.request.responseURL.indexOf('/FileDownload') > -1) { |
| | | return response.data; |
| | | } |
| | | return response.data.data; |
| | | } |
| | | }, |
| | | (error) => { |
| | | let msg = '请求发生错误'; |
| | | if (error.response && error.response.status == 401) { |
| | | localStorage.clear() |
| | | router.replace({ |
| | | path: '/login' |
| | | }) |
| | | } else { |
| | | if (error.response && error.response.data) { |
| | | msg = error.response.data.msg |
| | | } |
| | | } |
| | | throw msg; |
| | | } |
| | | ) |
| | | |
| | | export default service |
New file |
| | |
| | | import { createRouter, createWebHashHistory } from 'vue-router' |
| | | import Layout from '@/layout/layout.vue' |
| | | const Transmission = () => import('@/views/transmission.vue') |
| | | const ExportTask = () => import('@/views/exportTask.vue') |
| | | const Setting = () => import('@/views/setting.vue') |
| | | const Login = () => import('@/views/login.vue') |
| | | |
| | | const router = createRouter({ |
| | | history: createWebHashHistory(import.meta.env.BASE_URL), |
| | | routes: [ |
| | | { |
| | | path: '/', |
| | | redirect: 'transmission' |
| | | }, |
| | | { |
| | | path: '/login', |
| | | name: 'login', |
| | | component: Login |
| | | }, |
| | | { |
| | | path: '/', |
| | | component: Layout, |
| | | children: [ |
| | | // { |
| | | // path: '/home', |
| | | // name: 'home', |
| | | // meta: { auth: true }, |
| | | // component: Home |
| | | // }, |
| | | { |
| | | path: '/transmission', |
| | | name: 'transmission', |
| | | meta: { auth: true }, |
| | | component: Transmission |
| | | }, |
| | | { |
| | | path: '/exportTask', |
| | | name: 'exportTask', |
| | | meta: { auth: true }, |
| | | component: ExportTask |
| | | }, |
| | | { |
| | | path: '/setting', |
| | | name: 'setting', |
| | | meta: { auth: true }, |
| | | component: Setting |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | }) |
| | | |
| | | export default router |
New file |
| | |
| | | import { defineStore } from 'pinia' |
| | | export const useDownloadTask = defineStore('downloadTask', { |
| | | state: () => ({ |
| | | updateList: 0, |
| | | msgData: {}, |
| | | }), |
| | | actions: { |
| | | setUpdateList() { |
| | | if (this.updateList === 0) { |
| | | this.updateList = 1 |
| | | } else { |
| | | this.updateList = 0 |
| | | } |
| | | }, |
| | | setMsgData(data) { |
| | | this.msgData = data |
| | | } |
| | | } |
| | | }) |
| | | |
| | | export const useExportTask = defineStore('exportTask', { |
| | | state: () => ({ |
| | | updateList: 0, |
| | | msgData: {}, |
| | | }), |
| | | actions: { |
| | | setUpdateList() { |
| | | if (this.updateList === 0) { |
| | | this.updateList = 1 |
| | | } else { |
| | | this.updateList = 0 |
| | | } |
| | | }, |
| | | setMsgData(data) { |
| | | this.msgData = data |
| | | } |
| | | } |
| | | }) |
New file |
| | |
| | | import { createPinia } from 'pinia' |
| | | |
| | | // 创建pinia实例 |
| | | const pinia = createPinia() |
| | | |
| | | export default pinia |
| | | |
| | | export * from './downloadTask' |
New file |
| | |
| | | <template> |
| | | <div class="exportTaskBox"> |
| | | <div class="taskList"> |
| | | <p class="blockTitle">任务列表</p> |
| | | <p @click="newTask">测试创建任务</p> |
| | | <div |
| | | :class="'taskItem ' + selectedTaskIndex == index ? 'active' : ''" |
| | | v-for="(task, index) in tasks" |
| | | :key="index" |
| | | > |
| | | <p class="taskItemTitle">{{ task.bookTplName + '_' + task.id }}</p> |
| | | <p class="taskItemDesc"> |
| | | <span class="time">{{ task.startDate }}</span> |
| | | <span class="state"> |
| | | <span style="color: #909399" v-if="task.state == 0">初始化</span> |
| | | <span style="color: #909399" v-if="task.state == 1">处理中...</span> |
| | | <span style="color: #e6a23c" v-if="task.state == 2">暂停</span> |
| | | <span style="color: #67c23a" v-if="task.state == 3">已完成</span> |
| | | <span style="color: #f56c6c" v-if="task.state == 4">失败</span> |
| | | </span> |
| | | </p> |
| | | </div> |
| | | </div> |
| | | <div class="contentBox"> |
| | | <p class="blockTitle">任务详情</p> |
| | | <div class="taskDetailBox" v-if="selectedTask"> |
| | | <div class="baseInfo"> |
| | | <span> |
| | | 导出模板:<span>{{ selectedTask.bookTplName }}</span> |
| | | </span> |
| | | <span> |
| | | 文件模板:<span>{{ selectedTask.fileTplName }}</span> |
| | | </span> |
| | | <span> |
| | | 创建时间:<span>{{ selectedTask.startDate }}</span> |
| | | </span> |
| | | <span v-if="selectedTask.state == 3"> |
| | | 完成时间:<span>{{ selectedTask.endDate }}</span> |
| | | </span> |
| | | <span style="width: 100%"> |
| | | 文件路径: |
| | | <span style="width: auto"> |
| | | {{ selectedTask.taskPath }} |
| | | <el-icon><Search /></el-icon> |
| | | </span> |
| | | </span> |
| | | </div> |
| | | <div class="progressInfo"> |
| | | <p class="stepItem" v-if="selectedTask.progressInfo.createTask"> |
| | | <span>创建任务...</span> |
| | | <el-icon v-if="selectedTask.progressInfo.createTask != 3"><Loading /></el-icon> |
| | | <el-icon v-if="selectedTask.progressInfo.createTask == 3"><CircleCheck /></el-icon> |
| | | </p> |
| | | <p class="stepItem" v-if="selectedTask.progressInfo.temporaryFolder"> |
| | | <span>创建临时文件夹</span> |
| | | <el-icon v-if="selectedTask.progressInfo.temporaryFolder != 3"><Loading /></el-icon> |
| | | <el-icon v-if="selectedTask.progressInfo.temporaryFolder == 3"><CircleCheck /></el-icon> |
| | | </p> |
| | | <div class="bookInfoBox" v-if="selectedTask.progressInfo.handleBook.state"> |
| | | <p class="progressItem"> |
| | | <span>获取图书信息:</span> |
| | | <span style="display: inline-block; width: 170px"> |
| | | <el-progress |
| | | :percentage="selectedTask.progressInfo.handleBook.progress" |
| | | :status="selectedTask.progressInfo.handleBook.progress < 100 ? '' : 'success'" |
| | | /> |
| | | </span> |
| | | <span> |
| | | {{ selectedTask.progressInfo.handleBook.showProgress }} |
| | | </span> |
| | | </p> |
| | | </div> |
| | | <p class="stepItem" v-if="selectedTask.progressInfo.handleExcel"> |
| | | <span>生成Excel</span> |
| | | <el-icon v-if="selectedTask.progressInfo.handleExcel != 3"><Loading /></el-icon> |
| | | <el-icon v-if="selectedTask.progressInfo.handleExcel == 3"><CircleCheck /></el-icon> |
| | | </p> |
| | | <div class="fileInfoBox" v-if="selectedTask.progressInfo.showHandleFile"> |
| | | <p class="stepItem"> |
| | | <span>开始下载文件...</span> |
| | | </p> |
| | | <p |
| | | class="progressItem" |
| | | v-if="bookKey in Object.keys(selectedTask.progressInfo.handleFile)" |
| | | :key="bookKey" |
| | | > |
| | | <span :title="selectedTask.progressInfo.handleFile[bookKey].bookName"> |
| | | {{ |
| | | selectedTask.progressInfo.handleFile[bookKey].bookName + |
| | | (selectedTask.progressInfo.handleFile[bookKey].ISBN |
| | | ? '(' + selectedTask.progressInfo.handleFile[bookKey].ISBN + ')' |
| | | : '') |
| | | }} |
| | | : |
| | | </span> |
| | | <span style="display: inline-block; width: 170px"> |
| | | <el-progress |
| | | :percentage="selectedTask.progressInfo.handleFile[bookKey].progress" |
| | | :status=" |
| | | selectedTask.progressInfo.handleFile[bookKey].progress < 100 ? '' : 'success' |
| | | " |
| | | /> |
| | | </span> |
| | | <span> |
| | | {{ selectedTask.progressInfo.handleFile[bookKey].showProgress }} |
| | | </span> |
| | | </p> |
| | | </div> |
| | | <div class="fileInfoBox" v-if="selectedTask.progressInfo.showHandleFolder"> |
| | | <p class="stepItem"> |
| | | <span>处理文件夹...</span> |
| | | </p> |
| | | <p class="stepItem"> |
| | | <span :title="selectedTask.progressInfo.handleFolder[bookKey].bookName"> |
| | | {{ |
| | | selectedTask.progressInfo.handleFolder[bookKey].bookName + |
| | | (selectedTask.progressInfo.handleFolder[bookKey].ISBN |
| | | ? '(' + selectedTask.progressInfo.handleFolder[bookKey].ISBN + ')' |
| | | : '') |
| | | }} |
| | | </span> |
| | | <el-icon v-if="selectedTask.progressInfo.handleFolder[bookKey].state != 3" |
| | | ><Loading |
| | | /></el-icon> |
| | | <el-icon v-if="selectedTask.progressInfo.handleFolder[bookKey].state == 3" |
| | | ><CircleCheck |
| | | /></el-icon> |
| | | </p> |
| | | </div> |
| | | <p class="stepItem" v-if="selectedTask.state == 3"> |
| | | <span>导出成功!</span> |
| | | <el-icon><CircleCheck /></el-icon> |
| | | </p> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, inject, onMounted, watch } from 'vue' |
| | | import { useRouter, RouterView } from 'vue-router' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useExportTask } from '@/store' |
| | | const ExportTask = useExportTask() |
| | | const router = useRouter() |
| | | |
| | | const selectedTaskIndex = ref(0) |
| | | const tasks = ref([]) |
| | | const selectedTask = ref() |
| | | |
| | | onMounted(() => { |
| | | updateListData() |
| | | }) |
| | | |
| | | watch( |
| | | () => ExportTask.updateList, |
| | | (newValue, oldValue) => { |
| | | updateListData() |
| | | } |
| | | ) |
| | | |
| | | watch( |
| | | () => ExportTask.msgData, |
| | | (newValue, oldValue) => { |
| | | console.log(newValue) |
| | | // if (newValue.type == 'showState') { |
| | | // stateInfo.value = newValue.msg |
| | | // } else if (newValue.type == 'notDownloadFolder') { |
| | | // console.log('notDownloadFolder') |
| | | // // 下载目录检测不存在,设置下载目录 |
| | | // const path = window.electronAPI.openSelectFileOrFolderDialog({ |
| | | // title: '选择下载目录', |
| | | // properties: ['openDirectory'] |
| | | // }) |
| | | // if (path && path.length > 0) { |
| | | // const returnData = window.electronAPI.setDownloadPath(path[0], newValue.msg) |
| | | // } |
| | | // } else { |
| | | // ElMessage({ |
| | | // message: newValue.msg, |
| | | // type: newValue.type |
| | | // }) |
| | | // } |
| | | } |
| | | ) |
| | | |
| | | const newTask = () => { |
| | | window.electronAPI.newExportTask({ |
| | | bookInfo: [ |
| | | { |
| | | id: 23627, |
| | | idPath: '22288\\26631\\23627', |
| | | storeId: 9, |
| | | repoId: 16, |
| | | typeId: 3 |
| | | } |
| | | ], |
| | | tplInfo: { |
| | | type: "user", // sys & user |
| | | key: "9102ABDCE3C5CD294E832EBDA51948FF" |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const updateListData = () => { |
| | | // 获取导出任务列表 |
| | | const taskListData = window.electronAPI.getExportTasks() |
| | | console.log(taskListData); |
| | | } |
| | | |
| | | const handleData = (data) => { |
| | | // 文件信息 |
| | | if (Object.keys(data.progressInfo.handleFile).length) { |
| | | let start = false |
| | | for (const key in data.progressInfo.handleFile) { |
| | | let bookFile = data.progressInfo.handleFile[key] |
| | | let bookInfo = data.bookInfo.filter((item) => { |
| | | return item.id == key |
| | | }) |
| | | if (bookInfo.length) { |
| | | bookFile.bookName = bookInfo[0].name |
| | | if (bookFile.bookName) bookFile.bookName = bookFile.bookName.replace(/[/|\\]/g, '_') |
| | | bookFile.ISBN = bookInfo[0].ISBN |
| | | if (bookFile.ISBN) bookFile.ISBN = bookFile.ISBN.replace(/[/|\\]/g, '_') |
| | | } |
| | | if (bookFile.state) { |
| | | if (bookFile.info.total) { |
| | | bookFile.progress = parseInt((bookFile.info.success / bookFile.info.total) * 100) |
| | | } else { |
| | | bookFile.progress = 100 |
| | | } |
| | | bookFile.showProgress = '(' + bookFile.info.success + ' / ' + bookFile.info.total + ')' |
| | | start = true |
| | | } |
| | | } |
| | | |
| | | if (start) { |
| | | data.progressInfo.showHandleFile = true |
| | | } |
| | | } |
| | | // 处理文件夹 |
| | | if (Object.keys(data.progressInfo.handleFolder).length) { |
| | | let start = false |
| | | for (const key in data.progressInfo.handleFolder) { |
| | | let folderState = data.progressInfo.handleFolder[key] |
| | | data.progressInfo.handleFolder[key] = { |
| | | state: folderState |
| | | } |
| | | let bookInfo = data.bookInfo.filter((item) => { |
| | | return item.id == key |
| | | }) |
| | | if (bookInfo.length) { |
| | | data.progressInfo.handleFolder[key].bookName = bookInfo[0].name |
| | | data.progressInfo.handleFolder[key].ISBN = bookInfo[0].ISBN |
| | | } |
| | | if (folderState) { |
| | | start = true |
| | | } |
| | | } |
| | | if (start) { |
| | | data.progressInfo.showHandleFolder = true |
| | | } |
| | | } |
| | | // 书籍信息 |
| | | data.progressInfo.handleBook.progress = parseInt( |
| | | (data.progressInfo.handleBook.info.success / data.progressInfo.handleBook.info.total) * 100 |
| | | ) |
| | | data.progressInfo.handleBook.showProgress = |
| | | '(' + |
| | | data.progressInfo.handleBook.info.success + |
| | | ' / ' + |
| | | data.progressInfo.handleBook.info.total + |
| | | ')' |
| | | |
| | | return data |
| | | } |
| | | </script> |
| | | |
| | | <style lang="less"> |
| | | .exportTaskBox { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | .taskList { |
| | | width: 280px; |
| | | padding: 40px 20px; |
| | | border-right: 1px solid #e6e7e8; |
| | | .taskItem { |
| | | padding: 10px; |
| | | cursor: pointer; |
| | | .taskItemTitle { |
| | | color: #000; |
| | | font-weight: 500; |
| | | font-size: 14px; |
| | | margin-bottom: 6px; |
| | | } |
| | | .taskItemDesc { |
| | | color: #666; |
| | | font-size: 12px; |
| | | overflow: hidden; |
| | | .time { |
| | | float: left; |
| | | } |
| | | .state { |
| | | float: right; |
| | | } |
| | | } |
| | | &.active { |
| | | background: #efefef; |
| | | } |
| | | } |
| | | } |
| | | .contentBox { |
| | | flex: 1; |
| | | padding: 50px; |
| | | .taskDetailBox { |
| | | width: 100%; |
| | | height: 100%; |
| | | overflow: auto; |
| | | padding: 20px; |
| | | .baseInfo { |
| | | margin-bottom: 20px; |
| | | padding-bottom: 20px; |
| | | border-bottom: 1px solid #ccc; |
| | | span { |
| | | display: inline-block; |
| | | width: 50%; |
| | | color: #999; |
| | | margin-bottom: 5px; |
| | | span { |
| | | color: #000; |
| | | } |
| | | } |
| | | } |
| | | .progressInfo { |
| | | .stepItem { |
| | | margin-bottom: 6px; |
| | | span { |
| | | margin-right: 8px; |
| | | } |
| | | } |
| | | .fileInfoBox { |
| | | .progressItem { |
| | | margin-bottom: 6px; |
| | | } |
| | | } |
| | | .bookInfoBox { |
| | | .progressItem { |
| | | margin-bottom: 6px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .blockTitle { |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | line-height: 32px; |
| | | margin-bottom: 20px; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <div class="homeBox"> |
| | | <div class="herderBox"> |
| | | <p>文件</p> |
| | | <div class="viewChangeBox"> |
| | | <el-icon :size="16" v-if="viewMode == 0" @click="setViewMode"><Calendar /></el-icon> |
| | | <el-icon :size="16" v-if="viewMode == 1" @click="setViewMode"><Menu /></el-icon> |
| | | </div> |
| | | <div class="search"> |
| | | <el-input v-model="searchKey" size="small" placeholder="关键字搜索"> |
| | | <template #append> |
| | | <el-button :icon="Search" /> |
| | | </template> |
| | | </el-input> |
| | | </div> |
| | | </div> |
| | | <div class="toolBox"> |
| | | <div class="checkBox" v-if="viewMode == 0"> |
| | | <el-checkbox |
| | | v-model="checkBoxState.check" |
| | | :indeterminate="checkBoxState.indeterminate" |
| | | @change="handleCheckAllChange" |
| | | /> |
| | | <span class="checkText">{{ |
| | | checkBoxState.selectCount > 0 |
| | | ? `已选 ${checkBoxState.selectCount} 项` |
| | | : `共 ${checkBoxState.totalCount} 项` |
| | | }}</span> |
| | | </div> |
| | | <div class="sortBox" v-if="viewMode == 0"> |
| | | <el-dropdown trigger="click" @command="sortChange"> |
| | | <span class="sortText"> |
| | | <el-icon :size="16"><Sort /></el-icon> |
| | | <span> |
| | | 按{{ sortState.fields[sortState.selectFieldIndex].name |
| | | }}{{ sortState.types[sortState.selectTypeIndex].name }}排序 |
| | | </span> |
| | | </span> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item |
| | | v-for="(item, index) in sortState.fields" |
| | | :key="item.value" |
| | | :command="'fields.' + item.value" |
| | | > |
| | | <p> |
| | | <span> |
| | | <el-icon v-if="sortState.selectFieldIndex == index" :size="16" color="#409EFF"> |
| | | <Check /> |
| | | </el-icon> |
| | | </span> |
| | | <span>{{ item.name }}</span> |
| | | </p> |
| | | </el-dropdown-item> |
| | | <el-dropdown-item |
| | | v-for="(item, index) in sortState.types" |
| | | :key="item.value" |
| | | :divided="index == 0" |
| | | :command="'types.' + item.value" |
| | | > |
| | | <p> |
| | | <span> |
| | | <el-icon v-if="sortState.selectTypeIndex == index" :size="16" color="#409EFF"> |
| | | <Check /> |
| | | </el-icon> |
| | | </span> |
| | | <span>{{ item.name }}</span> |
| | | </p> |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | </div> |
| | | </div> |
| | | <div class="fileList"> |
| | | <div v-if="viewMode == 0" class="blockBox"> |
| | | <div class="fileItem" v-for="item in tableData"> |
| | | <div class="iconBox"> |
| | | <img :src="item.img" alt="" /> |
| | | </div> |
| | | <p class="name">{{ item.name }}</p> |
| | | <p class="time">{{ item.createDate }}</p> |
| | | </div> |
| | | </div> |
| | | <el-table v-if="viewMode == 1" :data="tableData" style="width: 100%"> |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column type="index" width="80" /> |
| | | <el-table-column prop="name" label="名称" sortable /> |
| | | <el-table-column prop="createDate" label="创建时间" width="200" sortable /> |
| | | <el-table-column prop="size" label="大小" width="200" sortable /> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive } from 'vue' |
| | | import { Search, Check } from '@element-plus/icons-vue' |
| | | import { useRouter, RouterView } from 'vue-router' |
| | | const router = useRouter() |
| | | |
| | | // 搜索 |
| | | const searchKey = ref('') |
| | | |
| | | // 选择框 |
| | | const checkBoxState = reactive({ |
| | | selectCount: 0, |
| | | totalCount: 0, |
| | | check: false, |
| | | indeterminate: false |
| | | }) |
| | | |
| | | const handleCheckAllChange = (val) => { |
| | | if (val) { |
| | | checkBoxState.check = true |
| | | } else { |
| | | checkBoxState.check = false |
| | | } |
| | | checkBoxState.indeterminate = false |
| | | } |
| | | |
| | | // 排序 |
| | | const sortState = reactive({ |
| | | fields: [ |
| | | { |
| | | name: '名称', |
| | | value: 'Name' |
| | | }, |
| | | { |
| | | name: '创建时间', |
| | | value: 'CreateDate' |
| | | }, |
| | | { |
| | | name: '文件大小', |
| | | value: 'Size' |
| | | } |
| | | ], |
| | | types: [ |
| | | { |
| | | name: '升序', |
| | | value: 'Asc' |
| | | }, |
| | | { |
| | | name: '降序', |
| | | value: 'Desc' |
| | | } |
| | | ], |
| | | selectFieldIndex: 1, |
| | | selectTypeIndex: 1 |
| | | }) |
| | | |
| | | const sortChange = (command) => { |
| | | const type = command.split('.')[0] |
| | | const data = command.split('.')[1] |
| | | if (type == 'fields') { |
| | | sortState.selectFieldIndex = sortState.fields.findIndex((item) => item.value == data) |
| | | } else { |
| | | sortState.selectTypeIndex = sortState.types.findIndex((item) => item.value == data) |
| | | } |
| | | } |
| | | |
| | | // 视图模式 0:块状视图 1:表格视图 |
| | | const viewMode = ref(0) |
| | | const setViewMode = () => { |
| | | if (viewMode.value == 0) { |
| | | viewMode.value = 1 |
| | | } else { |
| | | viewMode.value = 0 |
| | | } |
| | | } |
| | | |
| | | // 文件列表 |
| | | const tableData = ref([]) |
| | | </script> |
| | | |
| | | <style lang="less"> |
| | | .homeBox { |
| | | width: 100%; |
| | | height: 100%; |
| | | padding: 50px; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | flex-direction: column; |
| | | .herderBox { |
| | | overflow: hidden; |
| | | margin-bottom: 20px; |
| | | p { |
| | | float: left; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | line-height: 32px; |
| | | } |
| | | .search { |
| | | float: right; |
| | | margin-right: 20px; |
| | | } |
| | | .viewChangeBox { |
| | | float: right; |
| | | line-height: 32px; |
| | | i { |
| | | cursor: pointer; |
| | | vertical-align: sub; |
| | | } |
| | | } |
| | | } |
| | | .toolBox { |
| | | overflow: hidden; |
| | | margin-bottom: 10px; |
| | | line-height: 32px; |
| | | .checkBox { |
| | | float: left; |
| | | .checkText { |
| | | display: inline-block; |
| | | line-height: 32px; |
| | | vertical-align: top; |
| | | margin-left: 8px; |
| | | } |
| | | } |
| | | .sortBox { |
| | | float: right; |
| | | .sortText { |
| | | width: 150px; |
| | | line-height: 32px; |
| | | cursor: pointer; |
| | | i, |
| | | span { |
| | | vertical-align: middle; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .fileList { |
| | | flex: 1; |
| | | overflow: auto; |
| | | .blockBox { |
| | | overflow: hidden; |
| | | .fileItem { |
| | | width: 140px; |
| | | float: left; |
| | | margin: 20px; |
| | | padding: 15px; |
| | | border-radius: 10px; |
| | | cursor: context-menu; |
| | | &:hover { |
| | | background-color: #f1f1f1; |
| | | } |
| | | .iconBox { |
| | | width: 100%; |
| | | height: 140px; |
| | | margin-bottom: 10px; |
| | | position: relative; |
| | | img { |
| | | width: auto; |
| | | height: auto; |
| | | max-width: 100%; |
| | | max-height: 100%; |
| | | position: absolute; |
| | | top: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | left: 0; |
| | | margin: auto; |
| | | border-radius: 6px; |
| | | } |
| | | } |
| | | .name { |
| | | display: -webkit-box; |
| | | -webkit-box-orient: vertical; |
| | | -webkit-line-clamp: 2; |
| | | overflow: hidden; |
| | | margin-bottom: 8px; |
| | | } |
| | | .time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <div class="loginPage"> |
| | | <div class="loginForm"> |
| | | <p>数字教材阅读器</p> |
| | | <el-form ref="ruleFormRef" :model="loginData" :rules="rules" label-width="80px"> |
| | | <el-form-item label="用户名:" prop="username"> |
| | | <el-input v-model="loginData.username"></el-input> |
| | | </el-form-item> |
| | | <el-form-item label="密码:" prop="password"> |
| | | <el-input type="password" v-model="loginData.password"></el-input> |
| | | </el-form-item> |
| | | <div class="btnBox"> |
| | | <el-button |
| | | style="width: 120px" |
| | | type="primary" |
| | | @click="submitForm(ruleFormRef)" |
| | | :loading="loading" |
| | | >登 录</el-button |
| | | > |
| | | </div> |
| | | </el-form> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, inject } from 'vue' |
| | | import { useRoute, useRouter } from 'vue-router' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | |
| | | const request = inject('request') |
| | | |
| | | const ruleFormRef = ref() |
| | | |
| | | const loginData = ref({ |
| | | username: '', |
| | | password: '' |
| | | }) |
| | | |
| | | const rules = reactive({ |
| | | username: [{ required: true, message: '请填写用户名', trigger: 'blur' }], |
| | | password: [{ required: true, message: '请填写密码', trigger: 'blur' }] |
| | | }) |
| | | |
| | | const loading = ref(false) |
| | | |
| | | const submitForm = async (formEl) => { |
| | | if (!formEl) return |
| | | await formEl.validate((valid, fields) => { |
| | | if (valid) { |
| | | loading.value = true |
| | | request({ |
| | | url: '/identity/Login/LoginByLoginNameAndPassword', |
| | | method: 'post', |
| | | data: { |
| | | loginName: loginData.value.username, |
| | | password: loginData.value.password, |
| | | platform: 'textbookReader', |
| | | appId: '-1' |
| | | } |
| | | }) |
| | | .then((res) => { |
| | | console.log(res) |
| | | localStorage.setItem('token', res.token) |
| | | if (route.query.redirect) { |
| | | router.push(route.query.redirect) |
| | | } else { |
| | | router.push('/') |
| | | } |
| | | }) |
| | | .catch((errorMsg) => { |
| | | ElMessage.error(errorMsg) |
| | | loading.value = false |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | </script> |
| | | |
| | | <style lang="less"> |
| | | .loginPage { |
| | | width: 100%; |
| | | height: 100%; |
| | | position: relative; |
| | | .loginForm { |
| | | width: 400px; |
| | | height: 300px; |
| | | position: absolute; |
| | | top: 50%; |
| | | left: 50%; |
| | | margin-top: -150px; |
| | | margin-left: -200px; |
| | | border: 1px solid #ccc; |
| | | border-radius: 10px; |
| | | padding: 20px; |
| | | p { |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | text-align: center; |
| | | margin-bottom: 50px; |
| | | } |
| | | .btnBox { |
| | | text-align: center; |
| | | margin-top: 50px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <page> |
| | | <div class="setting"> |
| | | <el-divider content-position="left">设置下载目录</el-divider> |
| | | <div class="setDownloadPathBox"> |
| | | <el-input style="width: 300px; margin-right: 10px" v-model="downloadPath" disabled /> |
| | | <el-button type="primary" @click="setPath">设置目录</el-button> |
| | | <el-button type="primary" @click="openPath">打开目录</el-button> |
| | | </div> |
| | | </div> |
| | | </page> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, inject, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | const downloadPath = ref('') |
| | | onMounted(() => { |
| | | const path = window.electronAPI.getDownloadPath() |
| | | downloadPath.value = path |
| | | }) |
| | | const setPath = () => { |
| | | const path = window.electronAPI.openSelectFileOrFolderDialog({ |
| | | title: '选择下载目录', |
| | | properties: ['openDirectory'] |
| | | }) |
| | | downloadPath.value = path[0] |
| | | const data = window.electronAPI.setDownloadPath(path[0]) |
| | | if (data.state) { |
| | | ElMessage({ |
| | | message: '设置成功!', |
| | | type: 'success' |
| | | }) |
| | | } else { |
| | | ElMessage({ |
| | | message: data.msg, |
| | | type: 'error' |
| | | }) |
| | | } |
| | | } |
| | | const openPath = () => { |
| | | const data = window.electronAPI.openPath(downloadPath.value) |
| | | if (!data.state) { |
| | | ElMessage({ |
| | | message: data.msg, |
| | | type: 'error' |
| | | }) |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="less"> |
| | | .setting { |
| | | width: 100%; |
| | | height: 100%; |
| | | box-sizing: border-box; |
| | | padding: 10px 50px; |
| | | .setDownloadPathBox { |
| | | display: flex; |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <div class="transmissionBox"> |
| | | <div class="subMenuBox"> |
| | | <div |
| | | :class="['menuItem', selectMenuIndex == index ? 'active' : '']" |
| | | v-for="(item, index) in menuData" |
| | | @click="selectMenu(index)" |
| | | > |
| | | <div class="iconBox"> |
| | | <el-icon v-if="item.icon == 'download'" :size="20"><Download /></el-icon> |
| | | <el-icon v-if="item.icon == 'success'" :size="20"><CircleCheck /></el-icon> |
| | | </div> |
| | | <p>{{ item.name }}</p> |
| | | <span>{{ item.num }}</span> |
| | | </div> |
| | | </div> |
| | | <div class="pageBox"> |
| | | <div class="herderBox"> |
| | | <p>{{ menuData[selectMenuIndex].name }}</p> |
| | | </div> |
| | | <div class="toolBox" v-if="selectMenuIndex == 0"> |
| | | <p> |
| | | 下载列表 · <span>已下载 {{ totalProgress }}%{{ allPause ? ',已全部暂停' : '' }}</span> |
| | | </p> |
| | | <div class="toolBtnBox"> |
| | | <el-button |
| | | :icon="Download" |
| | | size="small" |
| | | type="primary" |
| | | :disabled="allStart" |
| | | @click="newTask" |
| | | >新增下载</el-button |
| | | > |
| | | <el-button :icon="CaretRight" size="small" :disabled="allStart" @click="startAll" |
| | | >全部开始</el-button |
| | | > |
| | | <el-button :icon="VideoPause" size="small" :disabled="allPause" @click="pauseAll" |
| | | >全部暂停</el-button |
| | | > |
| | | <el-button :icon="CloseBold" size="small" @click="cleanAll">全部取消</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="fileList"> |
| | | <el-table |
| | | :data="tableData" |
| | | style="width: 100%" |
| | | :height="'100%'" |
| | | empty-text="暂无数据" |
| | | @cell-mouse-enter="mouseEnter" |
| | | @cell-mouse-leave="mouseLeave" |
| | | > |
| | | <el-table-column prop="name" label="名称"> |
| | | <template #default="scope"> |
| | | <p class="title"> |
| | | <span>{{ scope.row.name }}</span> |
| | | <span class="tableItemBtnBox" v-show="showId == scope.row.id"> |
| | | <el-tooltip |
| | | v-if="scope.row.state == 'pause'" |
| | | effect="dark" |
| | | content="开始" |
| | | placement="top" |
| | | > |
| | | <el-button :icon="CaretRight" circle @click="start(scope.row.id)" /> |
| | | </el-tooltip> |
| | | <el-tooltip |
| | | v-if="scope.row.state == 'download'" |
| | | effect="dark" |
| | | content="暂停" |
| | | placement="top" |
| | | > |
| | | <el-button :icon="VideoPause" circle @click="pause(scope.row.id)" /> |
| | | </el-tooltip> |
| | | <el-tooltip |
| | | effect="dark" |
| | | :content="scope.row.state == 'success' ? '删除记录' : '取消'" |
| | | placement="top" |
| | | > |
| | | <el-button :icon="CloseBold" circle @click="clean(scope.row.id)" /> |
| | | </el-tooltip> |
| | | <el-tooltip effect="dark" content="在本地目录查看" placement="top"> |
| | | <el-button :icon="Search" circle @click="openFile(scope.row.id)" /> |
| | | </el-tooltip> |
| | | </span> |
| | | </p> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="size" label="大小" width="180"> |
| | | <template #default="scope"> |
| | | <span v-if="selectMenuIndex == 0" |
| | | >{{ getFileSize(scope.row.offset) }} / {{ getFileSize(scope.row.size) }}</span |
| | | > |
| | | <span v-if="selectMenuIndex == 1">{{ getFileSize(scope.row.size) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="createDate" label="创建时间" width="180" sortable> |
| | | </el-table-column> |
| | | <el-table-column |
| | | v-if="selectMenuIndex == 1" |
| | | prop="completeDate" |
| | | label="完成时间" |
| | | width="180" |
| | | sortable |
| | | > |
| | | </el-table-column> |
| | | <el-table-column v-if="selectMenuIndex == 0" prop="createDate" label="状态" width="300"> |
| | | <template #default="scope"> |
| | | <p class="itemState" v-if="scope.row.state == 'download'" style="color: #409eff"> |
| | | 正在下载 |
| | | </p> |
| | | <p class="itemState" v-if="scope.row.state == 'pause'" style="color: #e6a23c"> |
| | | 已暂停 |
| | | </p> |
| | | <p class="itemState" v-if="scope.row.state == 'getFileInfo'" style="color: #409eff"> |
| | | 获取文件信息 |
| | | </p> |
| | | <p class="itemState" v-if="scope.row.state == 'error'" style="color: #f56c6c"> |
| | | 下载错误 |
| | | </p> |
| | | <p class="itemState" v-if="scope.row.state == 'success'" style="color: #67c23a"> |
| | | 已完成 |
| | | </p> |
| | | <el-progress |
| | | :show-text="false" |
| | | :striped="scope.row.state == 'download'" |
| | | :duration="10" |
| | | :striped-flow="scope.row.state == 'download'" |
| | | :percentage="scope.row.progress" |
| | | :color=" |
| | | () => { |
| | | if (scope.row.state == 'download') { |
| | | return '#409EFF' |
| | | } else if (scope.row.state == 'success') { |
| | | return '#67C23A' |
| | | } else { |
| | | return '#E6A23C' |
| | | } |
| | | } |
| | | " |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <div class="stateInfoBox" v-if="stateInfo"> |
| | | {{ stateInfo }} |
| | | </div> |
| | | </div> |
| | | <el-dialog |
| | | v-model="newTaskDialogVisible" |
| | | title="新增下载任务" |
| | | width="400" |
| | | align-center |
| | | :modal="false" |
| | | > |
| | | <el-input |
| | | v-model="newTaskInfo" |
| | | :autosize="{ minRows: 4, maxRows: 8 }" |
| | | type="textarea" |
| | | placeholder="请填写任务字符" |
| | | /> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" @click="submitTask">确定</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, reactive, inject, onMounted, watch } from 'vue' |
| | | import { Check } from '@element-plus/icons-vue' |
| | | import { useRouter, RouterView } from 'vue-router' |
| | | import { Download, CaretRight, CloseBold, VideoPause, Search } from '@element-plus/icons-vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useDownloadTask } from '@/store' |
| | | const router = useRouter() |
| | | const { getFileSize } = inject('toolClass') |
| | | const downloadTask = useDownloadTask() |
| | | |
| | | // 菜单 |
| | | const menuData = ref([ |
| | | { |
| | | name: '下载', |
| | | num: 0, |
| | | icon: 'download' |
| | | }, |
| | | { |
| | | name: '已完成', |
| | | num: 0, |
| | | icon: 'success' |
| | | } |
| | | ]) |
| | | const selectMenuIndex = ref(0) |
| | | const selectMenu = (index) => { |
| | | selectMenuIndex.value = index |
| | | updateListData() |
| | | } |
| | | |
| | | // 总进度 |
| | | const totalProgress = ref(0) |
| | | // 全部暂停 |
| | | const allPause = ref(false) |
| | | // 全部开始 |
| | | const allStart = ref(false) |
| | | |
| | | // 文件列表 |
| | | const tableData = ref([]) |
| | | |
| | | const stateInfo = ref() |
| | | |
| | | onMounted(() => { |
| | | updateListData() |
| | | |
| | | // 绑定消息提醒 |
| | | // window.electronAPI.onShowMessage((data) => { |
| | | // console.log(data) |
| | | // if (data.type == 'showState') { |
| | | // stateInfo.value = data.msg |
| | | // } else if (data.type == 'notDownloadFolder') { |
| | | // console.log('notDownloadFolder') |
| | | // // 下载目录检测不存在,设置下载目录 |
| | | // const path = window.electronAPI.openSelectFileOrFolderDialog({ |
| | | // title: '选择下载目录', |
| | | // properties: ['openDirectory'] |
| | | // }) |
| | | // if (path && path.length > 0) { |
| | | // console.log('setDownloadPath') |
| | | // debugger |
| | | // const returnData = window.electronAPI.setDownloadPath(path[0], data.msg) |
| | | // } |
| | | // } else { |
| | | // ElMessage({ |
| | | // message: data.msg, |
| | | // type: data.type |
| | | // }) |
| | | // } |
| | | // }) |
| | | }) |
| | | |
| | | watch( |
| | | () => downloadTask.updateList, |
| | | (newValue, oldValue) => { |
| | | updateListData() |
| | | } |
| | | ) |
| | | |
| | | watch( |
| | | () => downloadTask.msgData, |
| | | (newValue, oldValue) => { |
| | | if (newValue.type == 'showState') { |
| | | stateInfo.value = newValue.msg |
| | | } else if (newValue.type == 'notDownloadFolder') { |
| | | console.log('notDownloadFolder') |
| | | // 下载目录检测不存在,设置下载目录 |
| | | const path = window.electronAPI.openSelectFileOrFolderDialog({ |
| | | title: '选择下载目录', |
| | | properties: ['openDirectory'] |
| | | }) |
| | | if (path && path.length > 0) { |
| | | const returnData = window.electronAPI.setDownloadPath(path[0], newValue.msg) |
| | | } |
| | | } else { |
| | | ElMessage({ |
| | | message: newValue.msg, |
| | | type: newValue.type |
| | | }) |
| | | } |
| | | } |
| | | ) |
| | | |
| | | const updateListData = () => { |
| | | // 获取下载任务列表 |
| | | const taskListData = window.electronAPI.getDownloadTasks() |
| | | if (selectMenuIndex.value == 0) { |
| | | tableData.value = taskListData |
| | | .filter((item) => item.state != 'success') |
| | | .map((item) => { |
| | | // if(item.state == "download") alert(JSON.stringify(item)) |
| | | return { |
| | | ...item, |
| | | progress: (item.offset / item.size) * 100 |
| | | } |
| | | }) |
| | | } else if (selectMenuIndex.value == 1) { |
| | | tableData.value = taskListData.filter((item) => item.state == 'success') |
| | | } |
| | | |
| | | menuData.value[0].num = taskListData.filter((item) => item.state != 'success').length |
| | | menuData.value[1].num = taskListData.filter((item) => item.state == 'success').length |
| | | |
| | | let totalOffset = 0 |
| | | let totalSize = 0 |
| | | taskListData.forEach((item) => { |
| | | if (item.state != 'success') { |
| | | totalOffset += item.offset |
| | | totalSize += item.size |
| | | } |
| | | }) |
| | | |
| | | totalProgress.value = totalSize ? ((totalOffset / totalSize) * 100).toFixed(2) : 0 |
| | | } |
| | | |
| | | const newTaskDialogVisible = ref(false) |
| | | const newTaskInfo = ref('') |
| | | const newTask = () => { |
| | | newTaskInfo.value = '' |
| | | newTaskDialogVisible.value = true |
| | | } |
| | | |
| | | const submitTask = () => { |
| | | console.log(newTaskInfo.value) |
| | | if (newTaskInfo.value) { |
| | | let infoData = null |
| | | try { |
| | | infoData = JSON.parse(newTaskInfo.value) |
| | | } catch (error) { |
| | | infoData = null |
| | | } |
| | | if (infoData) { |
| | | // 新建任务 |
| | | window.electronAPI.newDownloadTask(infoData) |
| | | newTaskDialogVisible.value = false |
| | | } else { |
| | | ElMessage({ |
| | | message: '无法识别您输入的内容,请确认后重新填写', |
| | | type: 'warning' |
| | | }) |
| | | } |
| | | } else { |
| | | ElMessage({ |
| | | message: '请填写新建数据', |
| | | type: 'warning' |
| | | }) |
| | | } |
| | | } |
| | | |
| | | const clean = (id) => { |
| | | window.electronAPI.cleanTask(id) |
| | | } |
| | | |
| | | const cleanAll = () => { |
| | | tableData.value = [] |
| | | window.electronAPI.cleanAll() |
| | | } |
| | | |
| | | const start = (id) => { |
| | | window.electronAPI.startTask(id) |
| | | } |
| | | |
| | | const startAll = () => { |
| | | window.electronAPI.startAllTask() |
| | | } |
| | | |
| | | const pause = (id) => { |
| | | window.electronAPI.pauseTask(id) |
| | | } |
| | | |
| | | const pauseAll = () => { |
| | | window.electronAPI.pauseAllTask() |
| | | } |
| | | |
| | | const openFile = (id) => { |
| | | window.electronAPI.openPathByTaskId(id) |
| | | } |
| | | |
| | | const showId = ref() |
| | | const mouseEnter = (row) => { |
| | | showId.value = row.id //赋值行id,便于页面判断 |
| | | } |
| | | //鼠标移出单元格事件 |
| | | const mouseLeave = (row) => { |
| | | showId.value = '' |
| | | } |
| | | </script> |
| | | |
| | | <style lang="less"> |
| | | .transmissionBox { |
| | | width: 100%; |
| | | height: 100%; |
| | | display: flex; |
| | | .subMenuBox { |
| | | width: 180px; |
| | | height: 100%; |
| | | overflow: auto; |
| | | border-right: 1px solid #e6e7e8; |
| | | padding: 40px 20px; |
| | | .menuItem { |
| | | display: flex; |
| | | width: 100%; |
| | | padding: 10px; |
| | | border-radius: 10px; |
| | | cursor: pointer; |
| | | margin-bottom: 10px; |
| | | position: relative; |
| | | &.active { |
| | | background-color: #ebebed; |
| | | span { |
| | | background-color: #409eff; |
| | | color: #fff; |
| | | border-color: #409eff; |
| | | } |
| | | } |
| | | &:hover { |
| | | background-color: #ebebed; |
| | | } |
| | | .iconBox { |
| | | display: inline-block; |
| | | width: 20px; |
| | | height: 20px; |
| | | vertical-align: middle; |
| | | margin-right: 16px; |
| | | } |
| | | p { |
| | | vertical-align: middle; |
| | | line-height: 20px; |
| | | } |
| | | span { |
| | | position: absolute; |
| | | right: 10px; |
| | | top: 11px; |
| | | font-size: 12px; |
| | | background-color: #f6f6f7; |
| | | display: inline-block; |
| | | width: 30px; |
| | | padding: 1px 0; |
| | | border-radius: 50px; |
| | | text-align: center; |
| | | color: #999; |
| | | border: 1px solid #ccc; |
| | | } |
| | | } |
| | | } |
| | | .pageBox { |
| | | flex: 1; |
| | | height: 100%; |
| | | padding: 50px; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: auto; |
| | | position: relative; |
| | | .herderBox { |
| | | overflow: hidden; |
| | | margin-bottom: 20px; |
| | | p { |
| | | float: left; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | line-height: 32px; |
| | | } |
| | | } |
| | | .toolBox { |
| | | border-bottom: 1px solid #e6e7e8; |
| | | padding: 16px 0; |
| | | margin-bottom: 10px; |
| | | overflow: hidden; |
| | | p { |
| | | float: left; |
| | | font-size: 14px; |
| | | color: #333; |
| | | line-height: 24px; |
| | | span { |
| | | font-size: 12px; |
| | | color: #666; |
| | | } |
| | | } |
| | | .toolBtnBox { |
| | | float: right; |
| | | } |
| | | } |
| | | .fileList { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | .title { |
| | | line-height: 34px; |
| | | .tableItemBtnBox { |
| | | float: right; |
| | | padding: 0 10px; |
| | | } |
| | | } |
| | | } |
| | | .stateInfoBox { |
| | | position: absolute; |
| | | bottom: 18px; |
| | | left: 60px; |
| | | font-size: 12px; |
| | | color: #409eff; |
| | | } |
| | | .itemState { |
| | | font-size: 12px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | { |
| | | "extends": "@vue/tsconfig/tsconfig.dom.json", |
| | | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], |
| | | "exclude": ["src/**/__tests__/*"], |
| | | "compilerOptions": { |
| | | "composite": true, |
| | | "noEmit": true, |
| | | "baseUrl": ".", |
| | | "paths": { |
| | | "@/*": ["./src/*"] |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | { |
| | | "compilerOptions": { |
| | | "outDir": "electron-commonJS/", |
| | | // 指定编译后文件所在目录。 |
| | | "module": "CommonJS", |
| | | // 指定编译后代码使用的模块化规范。 |
| | | "target": "esnext", |
| | | // 目标语言的版本 |
| | | "lib": [ |
| | | "esnext", "dom" |
| | | ], |
| | | // TS需要引用的库 |
| | | "sourceMap": true, |
| | | // 是否生成相应的Map映射的文件,默认:false。 |
| | | "baseUrl": ".", |
| | | // 用于解析非绝对模块名的基本目录,相对模块不受影响。 |
| | | "resolveJsonModule": true, |
| | | //是否解析 JSON 模块,默认:false。 |
| | | "allowSyntheticDefaultImports": true, |
| | | //是否允许从没有默认导出的模块中默认导入,默认:false。 |
| | | "moduleResolution": "node", |
| | | //指定模块解析策略,node或classic |
| | | "forceConsistentCasingInFileNames": true, |
| | | //是否区分文件系统大小写规则,默认:false。 |
| | | "noImplicitReturns": true, |
| | | //检查函数是否不含有隐式返回值,默认:false。 |
| | | "noUnusedLocals": true, |
| | | //是否检查未使用的局部变量 |
| | | "allowJs": true, |
| | | //是否对js文件进行编译,默认:false。 |
| | | "skipLibCheck": true, |
| | | //是否跳过声明文件的类型检查,这可以在编译期间以牺牲类型系统准确性为代价来节省时间,默认:false。 |
| | | "experimentalDecorators": true, |
| | | //是否启用对装饰器的实验性支持,装饰器是一种语言特性,还没有完全被 JavaScript 规范批准,默认:false。 |
| | | "strict": false, |
| | | "esModuleInterop": true, |
| | | //是否启动所有严格检查的总开关,默认:false,启动后将开启所有的严格检查选项。 |
| | | "paths": { |
| | | } |
| | | }, |
| | | "include": [ |
| | | //指定被编译文件所在的目录 |
| | | "electron/*" |
| | | ], |
| | | } |
New file |
| | |
| | | { |
| | | "files": [], |
| | | "references": [ |
| | | { |
| | | "path": "./tsconfig.node.json" |
| | | }, |
| | | { |
| | | "path": "./tsconfig.app.json" |
| | | } |
| | | ] |
| | | } |
New file |
| | |
| | | { |
| | | "extends": "@tsconfig/node18/tsconfig.json", |
| | | "include": [ |
| | | "vite.config.*", |
| | | "vitest.config.*", |
| | | "cypress.config.*", |
| | | "nightwatch.conf.*", |
| | | "playwright.config.*" |
| | | ], |
| | | "compilerOptions": { |
| | | "composite": true, |
| | | "noEmit": true, |
| | | "module": "ESNext", |
| | | "moduleResolution": "Bundler", |
| | | "types": ["node"] |
| | | } |
| | | } |
New file |
| | |
| | | import { fileURLToPath, URL } from 'node:url' |
| | | |
| | | import { defineConfig } from 'vite' |
| | | import vue from '@vitejs/plugin-vue' |
| | | import electron from 'vite-plugin-electron' |
| | | |
| | | // https://vitejs.dev/config/ |
| | | export default defineConfig({ |
| | | plugins: [ |
| | | vue(), |
| | | electron({ |
| | | // 配置 Electron 入口文件 |
| | | entry: 'electron-commonJS/main.js' |
| | | }), |
| | | ], |
| | | resolve: { |
| | | alias: { |
| | | '@': fileURLToPath(new URL('./src', import.meta.url)) |
| | | } |
| | | }, |
| | | server: { |
| | | host: true, |
| | | port: 8005, |
| | | strictPort: true, |
| | | hmr: true |
| | | }, |
| | | define: { |
| | | 'process.env': process.env |
| | | }, |
| | | css: { |
| | | preprocessorOptions: { |
| | | less: { |
| | | charset: false, |
| | | additionalData: '@import "./src/assets/style/global.less";' |
| | | } |
| | | } |
| | | } |
| | | }) |