From 654f169ed96148247fda48765b47a75ccb2143ab Mon Sep 17 00:00:00 2001 From: YM <479443481@qq.com> Date: 星期四, 11 四月 2024 18:33:36 +0800 Subject: [PATCH] 初始化 --- src/views/setting.vue | 62 tsconfig.app.json | 13 src/views/exportTask.vue | 363 +++++ .gitignore | 46 src/plugin/axios/index.ts | 51 vite.config.ts | 38 electron/preload.ts | 131 + electron/toolClass.ts | 30 index.html | 14 src/views/transmission.vue | 495 ++++++ electron/main.ts | 176 ++ electron/update.ts | 28 public/favicon.ico | 0 src/router/index.ts | 53 src/assets/style/global.less | 1 .eslintrc.cjs | 15 build/installer.nsh | 9 src/assets/main.css | 11 electron/downloadTask.ts | 738 ++++++++++ src/assets/js/toolClass.ts | 17 README.md | 18 src/main.ts | 49 src/store/index.ts | 8 tsconfig.electron.json | 45 src/layout/layout.vue | 115 + tsconfig.node.json | 17 src/views/login.vue | 111 + electron/config.ts | 4 src/assets/base.css | 92 + tsconfig.json | 11 package.json | 107 + .prettierrc.json | 8 electron/exportTask.ts | 930 +++++++++++++ env.d.ts | 1 src/views/home.vue | 281 +++ src/App.vue | 124 + src/store/downloadTask.ts | 38 37 files changed, 4,228 insertions(+), 22 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..6f40582 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,15 @@ +/* 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' + } +} diff --git a/.gitignore b/.gitignore index 4d40434..af0e3e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,29 @@ -# 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 + + + + diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..66e2335 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "semi": false, + "tabWidth": 2, + "singleQuote": true, + "printWidth": 100, + "trailingComma": "none" +} \ No newline at end of file diff --git a/README.md b/README.md index 442387a..72aea48 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,18 @@ -## 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 diff --git a/build/installer.nsh b/build/installer.nsh new file mode 100644 index 0000000..e99d1f3 --- /dev/null +++ b/build/installer.nsh @@ -0,0 +1,9 @@ +!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 \ No newline at end of file diff --git a/electron/config.ts b/electron/config.ts new file mode 100644 index 0000000..8593eeb --- /dev/null +++ b/electron/config.ts @@ -0,0 +1,4 @@ +// 娴嬭瘯 +export const ctx = "http://182.92.203.7:5001"; +export const downloaderFileCtx = "http://182.92.203.7:3007/DigitalTextbookReader"; + diff --git a/electron/downloadTask.ts b/electron/downloadTask.ts new file mode 100644 index 0000000..c66f549 --- /dev/null +++ b/electron/downloadTask.ts @@ -0,0 +1,738 @@ +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锛氭墍鏈変笅杞芥枃浠朵俊鎭痬ap + // name锛氫换鍔″悕绉� + // size锛氫换鍔″ぇ灏� + // offset锛氫换鍔″凡瀹屾垚鍋忕Щ閲� + // msg锛氫换鍔℃秷鎭� + // md5锛氫笅杞芥枃浠秏d5 + // taskDesc锛氫换鍔℃弿杩帮紝涓巑d5浜岄�変竴锛屼换鍔℃弿杩颁細浠ユ枃浠跺す褰㈠紡涓嬭浇骞跺偍瀛� + // 渚嬪锛� + // [ + // { + // 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 { + // 瀛樺湪鏃х殑鏂囦欢鏈畬鎴愭垨姝e湪涓嬭浇鐨勪换鍔� + 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) + // 瀛樺湪鏃х殑鏂囦欢鏈畬鎴愭垨姝e湪涓嬭浇鐨勪换鍔� + 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: '鐩綍涓嶅瓨鍦紒' } + } +}) diff --git a/electron/exportTask.ts b/electron/exportTask.ts new file mode 100644 index 0000000..3d986a0 --- /dev/null +++ b/electron/exportTask.ts @@ -0,0 +1,930 @@ +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, // 鍥句功妯℃澘锛圗xcel妯℃澘锛� + fileTpl: null, // 鏂囦欢瀵煎嚭妯℃澘 + tplInfo: data.tplInfo, + bookInfo: data.bookInfo, // 瀵煎嚭鐨勫浘涔� + typeKeyMap: null, + state: ExportTask.INIT, // 浠诲姟鐘舵�� + fileTypeList: [], // 闇�涓嬭浇鏂囦欢绫诲瀷 + tabHeader: [], // Excel琛ㄥご鏁版嵁 + tabData: [], // Excel琛ㄥご鏁版嵁 + temporaryFolderPath: '', // 涓存椂鏂囦欢澶圭洰褰� + bookFolderPath: null, // 鍥句功鏂囦欢澶筽ath + 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 + } + // 鍏煎澶勭悊鏁版嵁杩斿洖鐨刱ey鏄疌msItemData + // 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 +}) diff --git a/electron/main.ts b/electron/main.ts new file mode 100644 index 0000000..e1a7f61 --- /dev/null +++ b/electron/main.ts @@ -0,0 +1,176 @@ +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() + }) + + // 瑙e喅搴旂敤鍚姩鐧藉睆闂 + 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) // 姣忔杩愯閮藉垹闄よ嚜瀹氫箟鍗忚 鐒跺悗鍐嶉噸鏂版敞鍐� + // 寮�鍙戞ā寮忎笅鍦╳indow杩愯闇�瑕佸仛鍏煎 + if (development) { + // 璁剧疆electron.exe 鍜� app鐨勮矾寰� + app.setAsDefaultProtocolClient(agreement, process.execPath, [ + path.resolve(process.argv[1]), + '--' + ]) + } + // 楠岃瘉鏄惁涓鸿嚜瀹氫箟鍗忚鐨勯摼鎺� + const AGREEMENT_REGEXP = new RegExp(`^${agreement}://`) + + // mac鍞ら啋搴旂敤 浼氭縺娲籵pen-url浜嬩欢 鍦╫pen-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绯荤粺涓嬪敜閱掑簲鐢ㄤ細婵�娲籹econd-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() + }) +} diff --git a/electron/preload.ts b/electron/preload.ts new file mode 100644 index 0000000..c2d740d --- /dev/null +++ b/electron/preload.ts @@ -0,0 +1,131 @@ +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, +}) diff --git a/electron/toolClass.ts b/electron/toolClass.ts new file mode 100644 index 0000000..cbcbf29 --- /dev/null +++ b/electron/toolClass.ts @@ -0,0 +1,30 @@ +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(''); +} + diff --git a/electron/update.ts b/electron/update.ts new file mode 100644 index 0000000..befac86 --- /dev/null +++ b/electron/update.ts @@ -0,0 +1,28 @@ +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() + }) +} diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite/client" /> diff --git a/index.html b/index.html new file mode 100644 index 0000000..c538241 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ +<!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> diff --git a/package.json b/package.json new file mode 100644 index 0000000..cd802bb --- /dev/null +++ b/package.json @@ -0,0 +1,107 @@ +{ + "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": "杩欓噷濉啓鍏蜂綋鐨勭増鏈洿鏂板唴瀹�" + } + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..b6b8f65 --- /dev/null +++ b/public/favicon.ico Binary files differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..3a97f95 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,124 @@ +<template> + <div class="updateDownloadInfo" v-if="showUpdateInfo"> + <el-alert + :title="`妫�娴嬪埌鏂扮増鏈紝姝e湪涓嬭浇瀹夎鍖�${ + 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> diff --git a/src/assets/base.css b/src/assets/base.css new file mode 100644 index 0000000..8f5972d --- /dev/null +++ b/src/assets/base.css @@ -0,0 +1,92 @@ +/* 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; +} diff --git a/src/assets/js/toolClass.ts b/src/assets/js/toolClass.ts new file mode 100644 index 0000000..7ffae70 --- /dev/null +++ b/src/assets/js/toolClass.ts @@ -0,0 +1,17 @@ +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 +} diff --git a/src/assets/main.css b/src/assets/main.css new file mode 100644 index 0000000..857b72e --- /dev/null +++ b/src/assets/main.css @@ -0,0 +1,11 @@ +@import './base.css'; + +#app { + width: 100%; + height: 100%; +} + + +page { + display: block; +} \ No newline at end of file diff --git a/src/assets/style/global.less b/src/assets/style/global.less new file mode 100644 index 0000000..9b2b567 --- /dev/null +++ b/src/assets/style/global.less @@ -0,0 +1 @@ +@theme-color: #366aec; \ No newline at end of file diff --git a/src/layout/layout.vue b/src/layout/layout.vue new file mode 100644 index 0000000..aff9cda --- /dev/null +++ b/src/layout/layout.vue @@ -0,0 +1,115 @@ +<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> \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..24ed5fb --- /dev/null +++ b/src/main.ts @@ -0,0 +1,49 @@ +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) => { + // 濡傛灉鏈塼oken + 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') diff --git a/src/plugin/axios/index.ts b/src/plugin/axios/index.ts new file mode 100644 index 0000000..6ff4491 --- /dev/null +++ b/src/plugin/axios/index.ts @@ -0,0 +1,51 @@ +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 diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..ca3da48 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,53 @@ +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 diff --git a/src/store/downloadTask.ts b/src/store/downloadTask.ts new file mode 100644 index 0000000..c5002ae --- /dev/null +++ b/src/store/downloadTask.ts @@ -0,0 +1,38 @@ +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 + } + } +}) diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..0e191d8 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,8 @@ +import { createPinia } from 'pinia' + +// 鍒涘缓pinia瀹炰緥 +const pinia = createPinia() + +export default pinia + +export * from './downloadTask' diff --git a/src/views/exportTask.vue b/src/views/exportTask.vue new file mode 100644 index 0000000..7feec2c --- /dev/null +++ b/src/views/exportTask.vue @@ -0,0 +1,363 @@ +<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> diff --git a/src/views/home.vue b/src/views/home.vue new file mode 100644 index 0000000..4d72ddf --- /dev/null +++ b/src/views/home.vue @@ -0,0 +1,281 @@ +<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> diff --git a/src/views/login.vue b/src/views/login.vue new file mode 100644 index 0000000..db63c47 --- /dev/null +++ b/src/views/login.vue @@ -0,0 +1,111 @@ +<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> diff --git a/src/views/setting.vue b/src/views/setting.vue new file mode 100644 index 0000000..bbcdaac --- /dev/null +++ b/src/views/setting.vue @@ -0,0 +1,62 @@ +<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> diff --git a/src/views/transmission.vue b/src/views/transmission.vue new file mode 100644 index 0000000..97b45f6 --- /dev/null +++ b/src/views/transmission.vue @@ -0,0 +1,495 @@ +<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"> + 姝e湪涓嬭浇 + </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> diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..491e093 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "composite": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/tsconfig.electron.json b/tsconfig.electron.json new file mode 100644 index 0000000..41e6581 --- /dev/null +++ b/tsconfig.electron.json @@ -0,0 +1,45 @@ +{ + "compilerOptions": { + "outDir": "electron-commonJS/", + // 鎸囧畾缂栬瘧鍚庢枃浠舵墍鍦ㄧ洰褰曘�� + "module": "CommonJS", + // 鎸囧畾缂栬瘧鍚庝唬鐮佷娇鐢ㄧ殑妯″潡鍖栬鑼冦�� + "target": "esnext", + // 鐩爣璇█鐨勭増鏈� + "lib": [ + "esnext", "dom" + ], + // TS闇�瑕佸紩鐢ㄧ殑搴� + "sourceMap": true, + // 鏄惁鐢熸垚鐩稿簲鐨凪ap鏄犲皠鐨勬枃浠讹紝榛樿锛歠alse銆� + "baseUrl": ".", + // 鐢ㄤ簬瑙f瀽闈炵粷瀵规ā鍧楀悕鐨勫熀鏈洰褰曪紝鐩稿妯″潡涓嶅彈褰卞搷銆� + "resolveJsonModule": true, + //鏄惁瑙f瀽 JSON 妯″潡锛岄粯璁わ細false銆� + "allowSyntheticDefaultImports": true, + //鏄惁鍏佽浠庢病鏈夐粯璁ゅ鍑虹殑妯″潡涓粯璁ゅ鍏ワ紝榛樿锛歠alse銆� + "moduleResolution": "node", + //鎸囧畾妯″潡瑙f瀽绛栫暐锛宯ode鎴朿lassic + "forceConsistentCasingInFileNames": true, + //鏄惁鍖哄垎鏂囦欢绯荤粺澶у皬鍐欒鍒欙紝榛樿锛歠alse銆� + "noImplicitReturns": true, + //妫�鏌ュ嚱鏁版槸鍚︿笉鍚湁闅愬紡杩斿洖鍊硷紝榛樿锛歠alse銆� + "noUnusedLocals": true, + //鏄惁妫�鏌ユ湭浣跨敤鐨勫眬閮ㄥ彉閲� + "allowJs": true, + //鏄惁瀵筳s鏂囦欢杩涜缂栬瘧锛岄粯璁わ細false銆� + "skipLibCheck": true, + //鏄惁璺宠繃澹版槑鏂囦欢鐨勭被鍨嬫鏌ワ紝杩欏彲浠ュ湪缂栬瘧鏈熼棿浠ョ壓鐗茬被鍨嬬郴缁熷噯纭�т负浠d环鏉ヨ妭鐪佹椂闂达紝榛樿锛歠alse銆� + "experimentalDecorators": true, + //鏄惁鍚敤瀵硅楗板櫒鐨勫疄楠屾�ф敮鎸侊紝瑁呴グ鍣ㄦ槸涓�绉嶈瑷�鐗规�э紝杩樻病鏈夊畬鍏ㄨ JavaScript 瑙勮寖鎵瑰噯锛岄粯璁わ細false銆� + "strict": false, + "esModuleInterop": true, + //鏄惁鍚姩鎵�鏈変弗鏍兼鏌ョ殑鎬诲紑鍏筹紝榛樿锛歠alse锛屽惎鍔ㄥ悗灏嗗紑鍚墍鏈夌殑涓ユ牸妫�鏌ラ�夐」銆� + "paths": { + } + }, + "include": [ + //鎸囧畾琚紪璇戞枃浠舵墍鍦ㄧ殑鐩綍 + "electron/*" + ], +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..46cf2e1 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,17 @@ +{ + "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"] + } +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..1b47c8f --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,38 @@ +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";' + } + } + } +}) -- Gitblit v1.9.1