diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba98385 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +task.json +username.json diff --git a/CommandTrayHost/CommandTrayHost.exe b/CommandTrayHost/CommandTrayHost.exe new file mode 100644 index 0000000..861c2fa Binary files /dev/null and b/CommandTrayHost/CommandTrayHost.exe differ diff --git a/CommandTrayHost/command_tray_host.cache b/CommandTrayHost/command_tray_host.cache new file mode 100644 index 0000000..dda0d04 --- /dev/null +++ b/CommandTrayHost/command_tray_host.cache @@ -0,0 +1 @@ +{"configs":[{"alpha":170,"bottom":708,"enabled":true,"left":384,"name":"cmd例子","right":1344,"start_show":true,"top":200,"valid":11},{"alpha":255,"bottom":0,"enabled":false,"left":0,"name":"cmd例子3","right":0,"start_show":false,"top":0,"valid":0}]} \ No newline at end of file diff --git a/CommandTrayHost/config.json b/CommandTrayHost/config.json new file mode 100644 index 0000000..d9b52d4 --- /dev/null +++ b/CommandTrayHost/config.json @@ -0,0 +1,154 @@ +{ + /** + * 0. 常见样例可以参考项目wiki. + * 1. "cmd"必须包含.exe.如果要运行批处理.bat, 可以使用 cmd.exe /c或者cmd.exe /k. + * 2. 所有的路径必须要是C:\\Windows这样的双斜杠分割,这是json的字符串规定。 + * 3. 所有的路径都可以是相对路径,比如 ..\..\icons\icon.ico这种形式。 + * 但是参考各有不同: + * cmd里面的子程序工作路径由working_directory指定 + * 其他路径则是CommandTrayHost.exe所在目录指定 + * 4. 相对路径规则。首先绝对路径不会改变,规则只对相对路径有效,其次cmd只会相对path。 + * path: i) path为空且cmd为绝对路径,此时path会从cmd提取路径 + * ii) 除 i) 以外的相对路径都是相对于CommandTrayHost.exe所在路径 + * working_directory: + * i) 如果working_directory以>开头,那么它会相对于CommandTrayHost.exe所在目录 + * ii) 除 i) 以外的情况都是相对于经过处理过的path + * 5. 本文可以用系统自带的记事本编辑,然后保存选Unicode(大小端无所谓)或者UTF-8都可以 + * 如果用VS Code或者Sublime Text编辑,可以用JSON with Comments语法着色 + * 6. 多个CommandTrayHost.exe只要放到不同目录,就可以同时运行与开机启动,互相不影响. 当然了默认配置是为了演示用, + * 启用了全部热键,第二个启动时会提示热键冲突,禁用或者修改第二个的热键即可。 + * 7. 如果改成 "enable_cache": true ,则会将用户操作缓存到command_tray_host.cache + * 可以缓存用户的启用停用状态,窗口的位置大小,以及显示隐藏状态。作用下次启动 + * CommandTrayHost.exe时,会忽略config.json里面的值。 + * 缓存失效判定是与config.json之间的时间戳先后对比。缓存写入磁盘只会在全部操作(全部启用 + * 全部禁用,全部显示隐藏),以及退出时发现缓存发生有效更改时才会写入磁盘。 + * 8. 全局热键格式: 可以使用alt win shift ctrl的任意个组合加上一个按键 + * 加上的按键支持0-9的数字 A-Z的字母,其他特殊按钮,鼠标左右键,滚轮,甚至手柄按钮也是可以的.比如上方向键0x26 + * 键盘码参考这里 https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx + * 大小写无关,顺序无关,如果多个非修饰符的按钮,最后的那个按钮会起作用。 + * 热键注册失败,一般是与系统中存在的冲突了,换一个再试 + * 9. crontab语法,秒 分 时 日期 月份 星期,比常规crontab多了个秒钟,具体语法使用搜索引擎 + * 例子 0 0/10 * * * * 每10分钟运行一次 + * 例子 0 1,11,21 * * * 每小时的1分 11分 21分运行一次 + * 例子 0 2/10 12-14 * * * 12点到14点,每小时从2分钟开始每10分钟运行一次 + * 日志文件超过10M会进行rotate,最多支持rotate文件数为500 + */ + "configs": [ + { + // 下面8个一个不能少 + "name": "cmd例子", // 系统托盘菜单名字 + "path": "C:\\Windows\\System32", // cmd的exe所在目录,相对路径是可以的,参考目录是CommandTrayHost.exe所在目录 + "cmd": "cmd.exe", // cmd命令,必须含有.exe + "working_directory": "", // 命令行的工作目录. 如果是相对路径,>开头意味着相对于CommandTrayHost.exe,否则相对于path。 + "addition_env_path": "", // dll搜索目录,暂时没用到 + "use_builtin_console": false, // 是否用CREATE_NEW_CONSOLE,暂时没用到 + "is_gui": false, // 是否是 GUI图形界面程序 + "enabled": true, // 是否当CommandTrayHost启动时,自动开始运行 + // 下面的是可选参数 + // 当CommandTrayHost不是以管理员运行的情况下,由于UIPI,显示/隐藏会失效,其他功能正常。 + "require_admin": false, // 是否要用管理员运行 + "start_show": false, // 是否以显示(而不是隐藏)的方式启动子程序 + "ignore_all": false, // 是否忽略全部启用禁用操作 + "position": [ // 显示窗口的初始位置 + 0.2, // STARTUPINFO.dwX 大于1就是数值,0-1之间的数值代表相对屏幕分辨率的百分比 + 200 // STARTUPINFO.dwY, 同上 + ], + "size": [ // 显示窗口的初始大小 + 0.5, // STARTUPINFO.dwXSize, 同上 + 0.5 // STARTUPINFO.dwYSize, 同上 + ], + "icon": "", // 命令行窗口的图标 + "alpha": 170, // 命令行窗口的透明度,0-255之间的整数,0为完全看不见,255完全不透明 + "topmost": false, // 命令行窗口置顶 + // 具体说明参考顶部注释7 + "hotkey": { // 下面并不需要都出现,可以只设置部分 + "disable_enable": "Shift+Win+D", // 启用/禁用切换 + "hide_show": "Shift+Win+H", // 显示/隐藏切换 + "restart": "Shift+Win+R", // 重启程序 + "elevate": "Shift+Win+A", // 以管理员运行本程序 + }, + "not_host_by_commandtrayhost": false, // 如果设置成了true,那么CommandTrayHost就不会监控它的运行了 + "not_monitor_by_commandtrayhost": false, // 如果设置成true同上,但是会随着CommandTrayHost退出而关闭。 + "kill_timeout": 200, // 执行关闭操作时,先尝试通知程序自己关闭然后等多少ms,然后再杀进程,默认是200ms + "exclusion_id": 1, // 互斥id,要求是大于0的整数。相同的互斥id启动时,会先杀掉其他。 + "kill_process_tree": false, // 杀进程的时候同时杀掉其子进程,用于nginx. 为true时,kill_timeout无效 + }, + { + "name": "cmd例子3", + "path": "C:\\Windows\\System32", + "cmd": "cmd.exe", + "working_directory": "", + "addition_env_path": "", + "use_builtin_console": false, + "is_gui": false, + "enabled": false, + }, + ], + "global": true, + // 可选参数 + "require_admin": false, // 是否让CommandTrayHost运行时弹出UAC对自身提权 + "icon": "", // 托盘图标路径,只支持ico文件,可以是多尺寸的ico; 空为内置图标 + "icon_size": 256, // 图标尺寸 可以用值有256 32 16 + "cmd_menu_max_length": 0, // cmd和path最大字符串个数,0表示不限制,大于0整数表示超过之后用...标出 + "lang": "auto", // zh-CN en-US https://msdn.microsoft.com/en-us/library/cc233982.aspx + "groups": [ // groups的值是一个数组(方括号),可以有两种类型,一种为数值,一种为object(花括号)。object代表下级菜单。最多可以40层嵌套。object必须有name字段 + { + "name": "cmd例子分组1", // 分级菜单的名字 + "groups": [ + 0, // 编号,是configs的编号。数组下标,从0开始 + 1, + ], + }, + { + "name": "cmd例子分组2", + "groups": [ + 2, + 1, + ], + }, + 2, + { + "name": "empty test", // 可以没有groups,但是不能没有name + }, + ], + "enable_groups": true, // 启用分组菜单 + "groups_menu_symbol": "+", // 分组菜单标志 + "left_click": [ + 0, + 1 + ], // 左键单击显示/隐藏程序 configs序号,从0开始.空数组或者注释掉,则显示CommandTrayHost本体 + "enable_cache": true, // 启用cache + "conform_cache_expire": true, // CommandTrayHost是否检查cache文件和配置文件,设为false时热加载被禁用 + "disable_cache_position": false, // 禁止缓存窗口位置 + "disable_cache_size": false, // 禁止缓存窗口大小 + "disable_cache_enabled": true, // 禁止缓存启用禁用状态 + "disable_cache_show": false, // 禁止缓存显示隐藏状态 + "disable_cache_alpha": false, // 禁止缓存透明度 (缓存时只对有alpha值的configs有作用) + // 具体说明参考顶部注释7 + "hotkey": { // 并不要求全部配置,可以只配置需要的 + "disable_all": "Alt+Win+Shift+D", + "enable_all": "Alt Win + Shift +E", // 空格或者+号都可以 + "hide_all": "Alt+WIN+Shift+H", // 大小写无关的 + "show_all": "AlT Win Shift s", // 甚至这种都可以识别 + "restart_all": "ALT+Win+Shift+U", + "elevate": "Alt+wIn+Shift+a", + "exit": "Alt+Win+Shift+X", // 但是比较推荐这种格式 + "left_click": "Alt+Win+Shift+L", + "right_click": "Alt+Win+Shift+R", + // 下面的五个快捷键,可以对外部程序生效 + "add_alpha": "Alt+Ctrl+Win+0x26", // 修改当前激活的任何窗口(要可能)透明度,不仅仅只对本程序托管的有效,其他程序也行 + "minus_alpha": "Alt+Ctrl+Win+0x28", //上面上箭头 这里下箭头 Alt+Ctrl+Win+↑↓ + "topmost": "Alt+Ctrl+Win+T", // 切换当前窗口的置顶状态 + "hide_current": "Alt+Ctrl+Win+H", // 隐藏当前窗口,可以在托盘图标上找到对应项目 + "show_all_docked": "Alt+Ctrl+Win+S", // 显示所有被上面这个快捷键隐藏的窗口 + }, + "repeat_mod_hotkey": false, // 是否长按算多次,Windows XP下面无效 + "global_hotkey_alpha_step": 5, // 上面透明度调节的幅度 + "show_hotkey_in_menu": true, // 在菜单后面加上成功注册的热键 + "enable_hotkey": true, + "start_show_silent": true, // 启动的时候屏幕不会闪(也就是等到获取到窗口才显示) + "auto_hot_reloading_config": true, // 这个为true时,相当于自动点击加载配置弹窗的否 + "auto_update": true, + "skip_prerelease": true, + "keep_update_history": false, // 是否保留自动更新时的临时文件 +} diff --git a/main.js b/main.js index 10b2054..d2dc0fb 100644 --- a/main.js +++ b/main.js @@ -1,12 +1,144 @@ // Modules to control application life and create native browser window -const {app, BrowserWindow} = require('electron') -process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; +const { app, dialog, BrowserWindow, Menu, Tray, ipcMain } = require('electron') + const path = require('path') +const fs = require('fs'); + + + + +var result = [{ + label: "title", + sublabel: "title", + type: "submenu", + submenu: [ + { + label: "切换到该任务分支", + }, + ] +} + +]; +let tray = null +function createMenu() { + + const iconPath = path.join(__dirname, 'windows-icon.png') + tray = new Tray(iconPath) + + tray.setToolTip('This is my application.') + + var menu_data = ""; + var project_map_data = ""; + + + tray.on("right-click", function () { + + // tray.popUpContextMenu(Menu.buildFromTemplate(result.concat({ + // type: "separator" + // }, { + // label: "退出", + // click: function () { + // for (var i in BrowserWindow.getAllWindows()) { + // BrowserWindow.getAllWindows()[i].close() + // } + // app.quit() + // } + // }))) + + // return + + + console.log("right-click") + if (!fs.existsSync("task.json") || !fs.existsSync("project-map.json")) { + return + } + + // if (!fs.existsSync("project-map.json")) fs.writeFileSync("project-map.json", JSON.stringify({})) -function createWindow () { + + var project_map_str = fs.readFileSync("project-map.json"); + var project_map = JSON.parse(fs.readFileSync("project-map.json")); + var data = fs.readFileSync("task.json") + ""; + if (data != menu_data && menu_data != "") { + tray.displayBalloon({ + title: "你有一个新任务", + content: "你有一个新任务,请及时查看" + }) + } + project_map_data = project_map_str + menu_data = data + data = JSON.parse(data); + result = []; + for (var i in data) { + let submenu = []; + for (var k in data[i]) { + submenu.push({ + label: data[i][k]["title"], + type: "submenu", + submenu: [ + { + label: "切换到该任务分支", + click: (function () { + i, data[i][k]["title"] + return function () { + + } + })() + }, + ] + }); + } + + let project_dir = project_map[i] + if (!project_dir) { + project_dir = "" + } else { + project_dir = `(${project_dir})` + } + let item = { + label: i, + type: "submenu", + submenu: submenu.concat([ + { + type: "separator" + }, + { + label: "关联项目路径" + project_dir, + click: (function (i) { + return function (menuItem, browserWindow, event) { + let project_map = JSON.parse(fs.readFileSync("project-map.json")); + let a = dialog.showOpenDialogSync({ properties: ['openDirectory'] }) + if (a) { + project_map[i] = a[0] + fs.writeFileSync("project-map.json", JSON.stringify(project_map)) + } + } + })(i) + }, + ]) + } + result.push(item); + } + + tray.popUpContextMenu(Menu.buildFromTemplate(result.concat({ + type: "separator" + }, { + label: "退出", + click: function () { + for (var i in BrowserWindow.getAllWindows()) { + BrowserWindow.getAllWindows()[i].close() + } + app.quit() + } + }))) + }) + + +} +function createWindow() { // Create the browser window. const mainWindow = new BrowserWindow({ - width: 800, + width: 300, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') @@ -15,7 +147,23 @@ function createWindow () { // and load the index.html of the app. mainWindow.loadURL('http://oa.ouxuan.net/?d=we&m=login') + mainWindow.webContents.on('crashed', function () { + const options = { + type: 'info', + title: '渲染器进程崩溃', + message: '这个进程已经崩溃.', + buttons: ['重载', '关闭'] + } + dialog.showMessageBox(options, function (index) { + if (index === 0) mainWindow.reload() + else mainWindow.close() + }) + }) + mainWindow.on('minimize', function (event) { + event.preventDefault(); + mainWindow.hide(); + }); // Open the DevTools. // mainWindow.webContents.openDevTools() } @@ -23,12 +171,20 @@ function createWindow () { // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) +app.whenReady().then(function () { + createWindow() + createMenu() +}) // Quit when all windows are closed. app.on('window-all-closed', function () { // On macOS it is common for applications and their menu bar // to stay active until the user quits explicitly with Cmd + Q + + + for (var i in BrowserWindow.getAllWindows()) { + BrowserWindow.getAllWindows()[i].close() + } if (process.platform !== 'darwin') app.quit() }) @@ -38,5 +194,7 @@ app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) + + // In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. diff --git a/preload.js b/preload.js index c73ba26..b68b4b5 100644 --- a/preload.js +++ b/preload.js @@ -2,6 +2,10 @@ // It has the same sandbox as a Chrome extension. +const fs = require('fs'); + +const ipc = require('electron').ipcRenderer + function get(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); @@ -9,28 +13,96 @@ function get(name) { if (r != null) return (r[2]); return null; } +function inits() { + function p(page, cb) { + $.get("http://oa.ouxuan.net/api.php?m=index&a=getyydata&adminid=2&cfrom=mweb&event=wwc&num=work&key=&loadci=1&page=" + page, function (data) { + var data = JSON.parse(data); + if (data.code == 200) { + if (page < data["data"]["maxpage"]) { + p(page + 1, function (rows) { + cb(data["data"]["rows"].concat(rows)) + }) + } else { + for (var i in data["data"]["rows"]) { + $.ajax({ + url: "http://oa.ouxuan.net/task.php?a=x&num=work&show=we&mid=" + data["data"]["rows"][i]["id"], + async: false, + success: function (html) { + var last = "" + $(html).find("div").each(function (item) { + if (last == "所属项目") { + a = ($(this).text()).split("(") + a.pop() + a = a.join("(") + data["data"]["rows"][i]["project"] = a + console.log(a) + } + last = $(this).text() + }) + } + }); + } + cb(data["data"]["rows"]) + } + } + }) + } + var start = function () { + + p(1, function (data) { + // alert( JSON.stringify(data)); + result = {}; + for (let i in data) { + if (!result[data[i]["project"]]) { + result[data[i]["project"]] = []; + } + result[data[i]["project"]].push(data[i]) + } + fs.writeFileSync("task.json", JSON.stringify(result)) + setTimeout(function () { + + location.reload() + }, 1 * 1000); + }) + } + start() +} + + + + + window.addEventListener('DOMContentLoaded', () => { - let m = get("m"); + let m = get("m"); + // inits() if (PageJs[m]) { PageJs[m]() } else { - alert("未有指定动作:" + m); + // alert("未有指定动作:" + m); } }) PageJs = { login() { - const adminuser = document.getElementById("adminuser") - adminuser.value = "郑荣升" - const adminpass = document.getElementById("adminpass") - adminpass.value = "as19940108" - loginsubmit(0) - }, - index() { - const fs = require("fs") - $.get("http://oa.ouxuan.net/api.php?m=index&a=getyydata&adminid=2&cfrom=mweb&page=1&event=wwc&num=work&key=&loadci=1&t=123", function (data) { - - fs.writeFileSync("task.json", data); - }) + if (fs.existsSync("username.json")) { + let data = fs.readFileSync("username.json"); + data = JSON.parse(data) + const adminuser = document.getElementById("adminuser") + adminuser.value = data["username"]; + const adminpass = document.getElementById("adminpass") + adminpass.value = data["password"]; + loginsubmit(0) + } else { + _loginsubmit = loginsubmit + loginsubmit = function () { + const adminuser = document.getElementById("adminuser") + const adminpass = document.getElementById("adminpass") + fs.writeFileSync("username.json", JSON.stringify({ + username: adminuser.value, + password: adminpass.value, + })) + _loginsubmit(0) + } + } } } \ No newline at end of file diff --git a/project-map.json b/project-map.json new file mode 100644 index 0000000..9231368 --- /dev/null +++ b/project-map.json @@ -0,0 +1 @@ +{"同济人脸识别签到系统授权库放到云上(离线版及在线版)":"C:\\Users\\admin\\Desktop\\临时项目\\tjrl","壁球馆智能系统":["C:\\Users\\admin\\go\\src\\git.ouxuan.net\\3136352472\\ouxuanac"]} \ No newline at end of file diff --git a/task.json b/task.json deleted file mode 100644 index 73004fa..0000000 --- a/task.json +++ /dev/null @@ -1 +0,0 @@ -{"code":200,"msg":"","data":{"wdtotal":0,"event":"wwc","num":"work","rows":[{"id":"133","uid":"10","title":"\u4fee\u6539\u540e\u7aef\u62a2\u5355\u6a21\u5f0f\u4e3a\u5206\u914d\u6a21\u5f0f","optdt":"2020-04-09 16:39:53","cont":"\u7c7b\u578b\uff1a\u5f00\u53d1\n\u7b49\u7ea7\uff1a\u4e2d\n\u5206\u914d\u7ed9\uff1a\u90d1\u8363\u5347\n\u521b\u5efa\u8005\uff1a\u5218\u5927\u94ed\n\u622a\u6b62\u65f6\u95f4\uff1a2020-04-16 16:39:00","modename":"\u4efb\u52a1","modenum":"work","statustext":"","statuscolor":"","face":"images\/noface.png"},{"id":"41","uid":"10","title":"\u6b27\u8f69\u667a\u80fd\u7f51\u5173\u4e2d\u95f4\u4ef6\u7814\u53d1","optdt":"2019-09-27 14:56:10","cont":"\u7c7b\u578b\uff1a\u5f00\u53d1\n\u7b49\u7ea7\uff1a\u9ad8\n\u5206\u914d\u7ed9\uff1a\u90d1\u8363\u5347\n\u521b\u5efa\u8005\uff1a\u5218\u5927\u94ed\n\u622a\u6b62\u65f6\u95f4\uff1a2019-10-30 14:55:00","modename":"\u4efb\u52a1","modenum":"work","statustext":"","statuscolor":"","face":"images\/noface.png"}],"page":1,"limit":10,"agentid":"12","count":2,"maxpage":1,"prevpage":0,"nextpage":2,"url":"","stotal":{"myrw":2}},"success":true} \ No newline at end of file diff --git a/windows-icon@2x.png b/windows-icon@2x.png new file mode 100644 index 0000000..463f374 Binary files /dev/null and b/windows-icon@2x.png differ