diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..cc8df9de --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +node-linker=hoisted \ No newline at end of file diff --git a/package.json b/package.json index b052442f..48857ea2 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,9 @@ "gulp": "gulp --allowEmpty" }, "dependencies": { + "@playwright/test": "^1.18.1", + "dayjs": "^1.10.7", + "depcheck": "^1.4.3", "electron-store": "^8.0.1", "playwright": "^1.18.1" }, diff --git a/packages/app/electron.builder.json b/packages/app/electron.builder.json index 2c8a504c..02da4ea7 100644 --- a/packages/app/electron.builder.json +++ b/packages/app/electron.builder.json @@ -23,5 +23,10 @@ "icon": "public/favicon.ico", "artifactName": "${productName}-${version}-setup-${os}-${arch}.${ext}", "target": "default" + }, + "fileAssociations": { + "ext": "ocs", + "icon": "public/favicon.ico", + "name": "ocs 脚本" } } diff --git a/packages/app/index.js b/packages/app/index.js index a4712bc5..9806348c 100644 --- a/packages/app/index.js +++ b/packages/app/index.js @@ -1,54 +1,61 @@ -const { app, BrowserWindow, ipcMain } = require("electron"); +// @ts-check + +const { app, ipcMain, BrowserWindow } = require("electron"); const Store = require("electron-store"); -const path = require("path"); +const Logger = require("./src/logger"); +const { handleOpenFile } = require("./src/tasks/handle.file.open"); +const { openWindow } = require("./src/main"); +const { remoteRegister } = require("./src/tasks/remote.register"); +const { initStore } = require("./src/tasks/init.store"); +const { autoLaunch } = require("./src/tasks/auto.launch"); -/** - * electron store - */ +const logger = Logger("main"); const store = new Store(); -exports.store = store; -require("./src/init.store")(store); - -function createWindow() { - return new BrowserWindow({ - minHeight: 400, - minWidth: 600, - show: false, - backgroundColor: "#f8f8f8", - titleBarStyle: "hidden", - titleBarOverlay: { - color: "#fff", - symbolColor: "black", - }, - center: true, - - webPreferences: { - // 关闭拼写矫正 - spellcheck: false, - webSecurity: true, - // 开启node - nodeIntegration: true, - contextIsolation: false, - }, + +task("OCS启动程序", () => + Promise.all([ + task("初始化错误处理", () => handleError()), + task("初始化本地设置", () => initStore()), + task("初始化自动启动", () => autoLaunch()), + task("检测启动文件", () => handleOpenFile(logger)), + (async () => { + /** @type {BrowserWindow} */ + const win = await task("启动软件", () => open()); + await task("初始化远程通信模块", () => remoteRegister(win)); + win.webContents.send("ready"); + })(), + ]) +); + +/** 处理错误 */ +function handleError() { + app.on("render-process-gone", (e) => { + logger.error("render-process-gone", e); + process.exit(0); + }); + app.on("child-process-gone", (e) => { + logger.error("child-process-gone", e); + process.exit(0); }); } -app.whenReady().then(async () => { - const win = createWindow(); - - if (!app.isPackaged) { - /** - * using `mode` options to prevent issue : {@link https://github.com/electron/electron/issues/32702} - */ - await win.loadURL("http://localhost:3000"); - win.webContents.openDevTools({ mode: "detach" }); - } else { - await win.loadFile("./public/index.html"); - } - win.show(); - app.on("activate", () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } +/** 等待显示 */ +async function open() { + return new Promise((resolve) => { + app.whenReady().then(async () => { + const win = await openWindow(); + + win.setAlwaysOnTop(Boolean(store.get("alwaysOnTop") || false)); + + resolve(win); + }); }); -}); +} + +/** 注册任务 */ +async function task(name, func) { + const time = Date.now(); + const res = await func(); + logger.debug({ task: name, time: Date.now() - time }); + return res; +} diff --git a/packages/app/package.json b/packages/app/package.json index 6a386869..6a9c9f1d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -9,12 +9,14 @@ "dist": "electron-builder --config ./electron.builder.json" }, "devDependencies": { + "electron": "17.0.0", "electron-builder": "^22.14.13" }, "dependencies": { "@ocsjs/scripts": "workspace:^1.0.0", + "dayjs": "^1.10.7", "electron-store": "^8.0.1", - "pino": "^7.6.5" + "pino": "^7.8.0" }, "repository": { "type": "git", diff --git a/packages/app/src/init.store.js b/packages/app/src/init.store.js deleted file mode 100644 index 455a0070..00000000 --- a/packages/app/src/init.store.js +++ /dev/null @@ -1,15 +0,0 @@ -const { Store } = require("electron-store"); -const { app } = require("electron"); - -/** - * - * @param {Store>} store - */ -module.exports = function (store) { - store.set("version", app.getVersion()); - store.set("path", app.getAppPath()); - store.set("name", app.getName()); - store.set("user-data-path", app.getPath("userData")); - store.set("exe-path", app.getPath("exe")); - store.set("logs-path", app.getPath("logs")); -}; diff --git a/packages/app/src/logger.js b/packages/app/src/logger.js new file mode 100644 index 00000000..2dd2682f --- /dev/null +++ b/packages/app/src/logger.js @@ -0,0 +1,29 @@ +//@ts-check + +const { pino } = require("pino"); +const { app } = require("electron"); +const path = require("path"); +const dayjs = require("dayjs"); + +module.exports = function logger(name) { + const dest = path.join(app.getPath("logs"), "/", dayjs().format("YYYY-MM-DD"), "/", name + ".log"); + + return pino( + { + base: null, + formatters: { + level(label, number) { + return { level: label }; + }, + }, + timestamp: () => `,"time":"${new Date(Date.now()).toLocaleString()}"`, + level: "debug", + }, + pino.destination({ + dest, + append: true, + mkdir: true, + sync: false, + }) + ); +}; diff --git a/packages/app/src/main.js b/packages/app/src/main.js new file mode 100644 index 00000000..61193334 --- /dev/null +++ b/packages/app/src/main.js @@ -0,0 +1,50 @@ +// @ts-check +const { BrowserWindow, app } = require("electron"); + + +app.disableHardwareAcceleration() + +function createWindow() { + return new BrowserWindow({ + minHeight: 400, + minWidth: 600, + center: true, + autoHideMenuBar: true, + show: false, + webPreferences: { + // 关闭拼写矫正 + spellcheck: false, + webSecurity: true, + // 开启node + nodeIntegration: true, + contextIsolation: false, + }, + }); +} + +async function openWindow() { + const win = createWindow(); + + if (!app.isPackaged) { + /** + * using `mode` options to prevent issue : {@link https://github.com/electron/electron/issues/32702} + */ + await win.loadURL("http://localhost:3000"); + win.webContents.openDevTools({ mode: "detach" }); + } else { + await win.loadFile("./public/index.html"); + } + + app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); + + win.show() + + return win; +} + +exports.createWindow = createWindow; +exports.openWindow = openWindow; diff --git a/packages/app/src/tasks/auto.launch.js b/packages/app/src/tasks/auto.launch.js new file mode 100644 index 00000000..1e37688e --- /dev/null +++ b/packages/app/src/tasks/auto.launch.js @@ -0,0 +1,12 @@ +const { app } = require("electron"); +const Store = require("electron-store"); + +/** 配置自动启动 */ +function autoLaunch() { + const store = new Store(); + app.setLoginItemSettings({ + openAtLogin: Boolean(store.get("auto-launch") || false), + }); +} + +exports.autoLaunch = autoLaunch; diff --git a/packages/app/src/tasks/handle.file.open.js b/packages/app/src/tasks/handle.file.open.js new file mode 100644 index 00000000..1770ed60 --- /dev/null +++ b/packages/app/src/tasks/handle.file.open.js @@ -0,0 +1,35 @@ +// @ts-check + +const { app } = require("electron"); +const fs = require("fs"); +const Store = require("electron-store"); + +/** + * 检测是否打开了(.ocs)拓展文件 + */ +exports.handleOpenFile = function (logger) { + const file = getFile(); + if (file) { + logger.info({ msg: "open file", path: file.path, content: file.content }); + const store = new Store(); + /** + * 添加新的文件到编辑文件区 + */ + const files = store.get("files"); + const newFiles = Array.isArray(files) ? Array.from(files).concat(file) : [file]; + store.set("files", newFiles); + } +}; + +function getFile() { + if (app.isPackaged) { + if (process.platform === "win32" && process.argv[1]) { + if (fs.existsSync(process.argv[1])) { + return { + content: fs.readFileSync(process.argv[1]).toString(), + path: process.argv[1], + }; + } + } + } +} diff --git a/packages/app/src/tasks/init.store.js b/packages/app/src/tasks/init.store.js new file mode 100644 index 00000000..5b7cc928 --- /dev/null +++ b/packages/app/src/tasks/init.store.js @@ -0,0 +1,34 @@ +// @ts-check + +const Store = require("electron-store"); +const { app } = require("electron"); +const path = require("path"); +const fs = require("fs"); + +/** + * 初始化配置 + */ +exports.initStore = function () { + const store = new Store(); + if (store.get("version") === undefined) { + store.set("version", app.getVersion()); + store.set("name", app.getName()); + store.set("user-data-path", app.getPath("userData")); + store.set("exe-path", app.getPath("exe")); + store.set("logs-path", app.getPath("logs")); + + /** 配置文件路径 */ + store.set("config-path", path.resolve(app.getPath("userData"), "./config.json")); + + /** 工作台路径 */ + const workspace = path.resolve(app.getPath("userData"), "./workspace"); + fs.mkdirSync(workspace, { recursive: true }); + store.set("workspace", workspace); + + /** 自动启动 */ + store.set("auto-launch", false); + + /** 置顶 */ + store.set("alwaysOnTop", false); + } +}; diff --git a/packages/app/src/tasks/remote.register.js b/packages/app/src/tasks/remote.register.js new file mode 100644 index 00000000..c7719fa8 --- /dev/null +++ b/packages/app/src/tasks/remote.register.js @@ -0,0 +1,37 @@ +const { ipcMain, app, dialog, BrowserWindow } = require("electron"); +const { autoLaunch } = require("./auto.launch"); + +/** + * 注册主进程远程通信事件 + * @param name 事件前缀名称 + * @param target 事件目标 + */ +function registerRemoteEvent(name, target) { + ipcMain + .on(name + "-get", (event, args) => { + const property = args[0]; + event.returnValue = target[property]; + }) + .on(name + "-get", (event, args) => { + const [property, value] = [args[0], args[1]]; + event.returnValue = target[property] = value; + }) + .on(name + "-call", async (event, args) => { + const [property, ...value] = [args.shift(), ...args]; + event.returnValue = await target[property](...value); + }); +} + +/** + * + * @param {BrowserWindow} win + */ +exports.remoteRegister = function (win) { + registerRemoteEvent("win", win); + registerRemoteEvent("webContents", win.webContents); + registerRemoteEvent("app", app); + registerRemoteEvent("dialog", dialog); + registerRemoteEvent("methods", { + autoLaunch, + }); +}; diff --git a/packages/app/test.js b/packages/app/test.js deleted file mode 100644 index e418cbb1..00000000 --- a/packages/app/test.js +++ /dev/null @@ -1,16 +0,0 @@ -const { spawn } = require("child_process"); -const ls = spawn("npm", ["-v"], { - shell: true, -}); - -ls.stdout.on("data", (data) => { - console.log(`stdout: ${data}`); -}); - -ls.stderr.on("data", (data) => { - console.error(`stderr: ${data}`); -}); - -ls.on("close", (code) => { - console.log(`child process exited with code ${code}`); -}); diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 74d319dd..22a8052a 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -5,8 +5,7 @@ "main": "./lib/index.js", "types": "./lib/index.d.ts", "files": [ - "lib", - "dist" + "lib" ], "publishConfig": { "access": "public" @@ -19,6 +18,7 @@ "dependencies": { "@playwright/test": "^1.18.1", "axios": "^0.25.0", + "chalk": "4.1.0", "commander": "^9.0.0", "dotenv": "^16.0.0", "playwright": "^1.18.1" diff --git a/packages/scripts/src/browser.entry.ts b/packages/scripts/src/browser.entry.ts index fcfb5ce6..73001c06 100644 --- a/packages/scripts/src/browser.entry.ts +++ b/packages/scripts/src/browser.entry.ts @@ -8,10 +8,11 @@ export * from "./browser"; export * from "./tampermonkey"; - -import json from "../package.json"; +import { readFileSync } from "fs"; +import { resolve } from "path"; import { logger } from "./browser"; +const json = JSON.parse(readFileSync(resolve(__dirname, "../package.json")).toString()); (() => { logger("info", "OCS-v" + json.version + " 载入成功"); })(); diff --git a/packages/scripts/src/nodejs/cli.ts b/packages/scripts/src/nodejs/cli.ts new file mode 100644 index 00000000..d7b6ef85 --- /dev/null +++ b/packages/scripts/src/nodejs/cli.ts @@ -0,0 +1,37 @@ +import { Command } from "commander"; +import { launchScripts } from "./script"; +import fs from "fs"; +import path from "path"; +import { prefix } from "../browser/logger"; +import chalk from "chalk"; + +const ocs = new Command(); + +ocs.name("ocs") + .addHelpText( + "afterAll", + ` +Example: + ocs ./test.ocs + ocs D:/ocs/test.ocs +` + ) + .argument("file", "path of ocs file") + .option("--cwd [path]", "working directory of the Node.js process (default: 'process.cwd()')") + .action(async (config, options) => { + const cwd = options.cwd || process.cwd(); + const filePath = path.resolve(cwd, config); + try { + const file = fs.readFileSync(filePath).toString(); + try { + const data = JSON.parse(file); + await launchScripts(data.launch, data.scripts); + } catch { + console.log(`\n\t${chalk.bgRedBright(prefix("error"))} 文件格式错误 : ${filePath}\n`); + } + } catch { + console.log(`\n\t${chalk.bgRedBright(prefix("error"))} 文件不存在 : ${filePath}\n`); + } + }) + + .parse(); diff --git a/packages/scripts/src/nodejs/script.ts b/packages/scripts/src/nodejs/script.ts index 83ce4f94..1b4746a3 100644 --- a/packages/scripts/src/nodejs/script.ts +++ b/packages/scripts/src/nodejs/script.ts @@ -2,7 +2,7 @@ import { chromium, LaunchOptions, Page } from "playwright"; import { CX, ZHS } from "."; import { ScriptFunction, ScriptOptions } from "./types"; -const scripts: Record = { +export const scripts: Record = { "cx-login-other": CX.otherLogin, "cx-login-phone": CX.phoneLogin, "cx-login-phone-code": CX.phoneCodeLogin, @@ -12,12 +12,15 @@ const scripts: Record = { "zhs-login-school": ZHS.schoolLogin, }; -export async function launchScripts(launchOptions: LaunchOptions, ...scripts: { (page: Page): Promise }[]) { +export async function launchScripts( + launchOptions: LaunchOptions, + ...scripts: { name: keyof ScriptOptions; options: ScriptOptions[keyof ScriptOptions] }[] +) { const browser = await chromium.launch(launchOptions); let page = await browser.newPage(); - for (const func of scripts) { - page = await func(page); + for (const item of scripts) { + page = await script(item.name, item.options)(page); } return { @@ -26,8 +29,8 @@ export async function launchScripts(launchOptions: LaunchOptions, ...scripts: { }; } -export function script(name: T, opts: ScriptOptions[T]) { +export function script(name: T, options: ScriptOptions[T]) { return function (page: Page) { - return scripts[name](page, opts); + return scripts[name](page, options); }; } diff --git a/packages/web/index.html b/packages/web/index.html index 11603f87..83d461f3 100644 --- a/packages/web/index.html +++ b/packages/web/index.html @@ -4,7 +4,7 @@ - Vite App + ocs
diff --git a/packages/web/package.json b/packages/web/package.json index 5db26898..f883491e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -8,12 +8,13 @@ "serve": "vite preview" }, "dependencies": { + "@ant-design/icons-vue": "6.0.1", + "@ocsjs/scripts": "workspace:^1.0.0", "ant-design-vue": "^2.2.8", "axios": "^0.25.0", + "interactjs": "^1.10.11", "vue": "^3.2.6", - "vue-router": "^4.0.12", - "electron": "../..", - "@ocsjs/scripts": "../scripts" + "vue-router": "^4.0.12" }, "devDependencies": { "@vitejs/plugin-vue": "^1.6.1", diff --git a/packages/web/public/js/iconfont.js b/packages/web/public/js/iconfont.js new file mode 100644 index 00000000..1333c7ad --- /dev/null +++ b/packages/web/public/js/iconfont.js @@ -0,0 +1 @@ +!function(c){var h,l,v,o,a,i='',t=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss"),p=function(c,h){h.parentNode.insertBefore(c,h)};if(t&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}function z(){a||(a=!0,v())}function s(){try{o.documentElement.doScroll("left")}catch(c){return void setTimeout(s,50)}z()}h=function(){var c,h=document.createElement("div");h.innerHTML=i,i=null,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(c=document.body).firstChild?p(h,c.firstChild):c.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(l=function(){document.removeEventListener("DOMContentLoaded",l,!1),h()},document.addEventListener("DOMContentLoaded",l,!1)):document.attachEvent&&(v=h,o=c.document,a=!1,s(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,z())})}(window); \ No newline at end of file diff --git a/packages/web/src/App.vue b/packages/web/src/App.vue index 46a8b43f..f61fd8a7 100644 --- a/packages/web/src/App.vue +++ b/packages/web/src/App.vue @@ -1,13 +1,24 @@ diff --git a/packages/web/src/assets/css/common.css b/packages/web/src/assets/css/common.css index c3cc8676..61bb122a 100644 --- a/packages/web/src/assets/css/common.css +++ b/packages/web/src/assets/css/common.css @@ -7,6 +7,8 @@ html, height: 100%; max-width: 100%; max-height: 100%; + min-width: 500px; + min-height: 300px; } #app { font-family: Avenir, Helvetica, Arial, sans-serif; @@ -21,7 +23,7 @@ html, /** bootstrap 和 ant design 冲突,导致图标移位 */ -body .anticon { +body .ocsicon { transform: translate(-0.5px, -3px); } /* 设置滚动条的样式 */ diff --git a/packages/web/src/assets/less/common.less b/packages/web/src/assets/less/common.less index e918fd04..62e3e507 100644 --- a/packages/web/src/assets/less/common.less +++ b/packages/web/src/assets/less/common.less @@ -7,7 +7,8 @@ html, height: 100%; max-width: 100%; max-height: 100%; - + min-width: 500px; + min-height: 300px; } #app { @@ -25,7 +26,7 @@ html, /** bootstrap 和 ant design 冲突,导致图标移位 */ -body .anticon { +body .ocsicon { transform: translate(-0.5px, -3px); } diff --git a/packages/web/src/components/Description.vue b/packages/web/src/components/Description.vue index 5951f00b..66de074c 100644 --- a/packages/web/src/components/Description.vue +++ b/packages/web/src/components/Description.vue @@ -1,25 +1,32 @@ diff --git a/packages/web/src/components/Icon.vue b/packages/web/src/components/Icon.vue new file mode 100644 index 00000000..4b1bbc9c --- /dev/null +++ b/packages/web/src/components/Icon.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/packages/web/src/components/Title.vue b/packages/web/src/components/Title.vue index fb1f1ff2..7d5c48ec 100644 --- a/packages/web/src/components/Title.vue +++ b/packages/web/src/components/Title.vue @@ -1,10 +1,6 @@