wangbaotian 2024-08-22 09:03:01 +08:00
parent d43db88a1e
commit 2184e0c517
284 changed files with 54535 additions and 0 deletions

19
.gitignore vendored 100644
View File

@ -0,0 +1,19 @@
node_modules
out/
logs/
run/
.idea/
data/
.vscode/launch.json
public/electron/
pnpm-lock.yaml
.yalc/
yalc.lock
go/public/
go/go.sum
build/extraResources/java-app.jar
build/extraResources/jre1.8.0_201/
python/.venv/
python/*.spec
python/build/
python/dist/

201
LICENSE 100644
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 安徽烁景智能科技有限公司
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

173
README.zh-CN.md 100644
View File

@ -0,0 +1,173 @@
# EE框架 v3
[![star](https://gitee.com/dromara/electron-egg/badge/star.svg?theme=gvp)](https://gitee.com/dromara/electron-egg/stargazers)
<div align=center>
<img src="https://wallace5303.gitee.io/ee/images/electron-egg/logo.png" width="150" height="150" />
</div>
<div align=center>
<h3><strong>一个入门简单、跨平台、企业级桌面软件开发框架</strong></h3>
</div>
<br>
## 🌏 [English](https://www.yuque.com/u34495/ee-doc) | [中文](https://www.kaka996.com/)
## 📋 介绍
- 🍩 **为什么使用?** 桌面软件(办公方向、 个人工具仍然是未来十几年PC端需求之一提高工作效率
- 🍉 **简单:** 只需懂 JavaScript
- 🍑 **愿景:** 所有开发者都能学会桌面软件研发
- 🍰 **gitee** https://gitee.com/dromara/electron-egg **4100+**
- 🍨 **github** https://github.com/dromara/electron-egg **1200+**
- 🏆 码云最有价值开源项目
![](https://wallace5303.gitee.io/ee/images/electron-egg/ee-zs.png)
## 📚 文档
- [教程文档](https://www.kaka996.com/)
## 📦 特性
1. 🍄 跨平台一套代码可以打包成windows版、Mac版、Linux版、国产UOS、Deepin、麒麟等
2. 🌹 架构:单业务进程/模块化/多任务(进程,线程,渲染进程),让开发大型项目变的简单。
3. 🌱 简单高效:只需学习 js 语言
4. 🌴 前端独立理论上支持任何前端技术vue、react、html等等
5. 🍁 工程化:可以用前端、服务端的开发思维,来编写桌面软件
6. 🌷 高性能事件驱动、非阻塞式IO
7. 🌰 功能丰富:配置、通信、插件、数据库、升级、打包、工具... 应有尽有
8. 💐 安全:支持字节码加密、压缩混淆加密
9. 🌻 功能demo桌面软件常见功能框架集成或提供demo
## ✈️ 使用场景
### 1. 🚀 常规桌面软件
- 🚖 windows平台
![](https://wallace5303.gitee.io/ee/images/electron-egg/home.png)
- 🚍 macOS平台
![](https://wallace5303.gitee.io/ee/images/electron-egg/mac-socket.png)
- 🚔 linux平台 - 国产UOS、Deepin
![](https://wallace5303.gitee.io/ee/images/electron-egg/uos-home.png)
- 🚔 linux平台 - ubuntu
![](https://wallace5303.gitee.io/ee/images/electron-egg/ubuntu-db.png)
### 🚐 2. vue、react、angular、web 转换成桌面软件
- 🚙 vue-ant-design本地
![](https://wallace5303.gitee.io/ee/images/electron-egg/vue-antd.png)
- 🚙 禅道项目管理web项目地址
![](https://wallace5303.gitee.io/ee/images/electron-egg/ee-project-7.png)
### 🚂 3. 游戏h5相关技术开发
- 🚊 忍者100层
![](https://wallace5303.gitee.io/ee/images/electron-egg/ee_game_1.png)
## 📒 开始使用
- ✒️ [安装文档](https://www.kaka996.com/pages/e64ff6/)
## 🐶 项目案例
- 🐟 EE框架已经应用于医疗、学校、政务、股票交易、ERP、娱乐、视频、企业等领域客户端
- 🐸 英雄联盟助手
![](https://wallace5303.gitee.io/ee/images/electron-egg/serendipity/lol-zhanji.png)
- [更多项目](https://www.kaka996.com/pages/eadf46/)
## 💬 交流
1. [讨论](https://www.kaka996.com/pages/c2720e/)
## 📌 关于pr
请前往[GitHub项目](https://github.com/dromara/electron-egg)提pr避免代码同步后pr被覆盖掉感谢
地址https://github.com/dromara/electron-egg
## 📔 框架核心包 ee-core
ee-core[https://github.com/wallace5303/ee-core](https://github.com/wallace5303/ee-core)
## 📚 Dromara 成员项目
<p align="center">
<a href="https://gitee.com/dromara/TLog" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/tlog2.png" title="一个轻量级的分布式日志标记追踪神器10分钟即可接入自动对日志打标签完成微服务的链路追踪" width="15%">
</a>
<a href="https://gitee.com/dromara/liteFlow" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/liteflow.png" title="轻量,快速,稳定,可编排的组件式流程引擎" width="15%">
</a>
<a href="https://hutool.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hutool.jpg" title="小而全的Java工具类库使Java拥有函数式语言般的优雅让Java语言也可以“甜甜的”。" width="15%">
</a>
<a href="https://sa-token.dev33.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sa-token.png" title="一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!" width="15%">
</a>
<a href="https://gitee.com/dromara/hmily" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hmily.png" title="高性能一站式分布式事务解决方案。" width="15%">
</a>
<a href="https://gitee.com/dromara/Raincat" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/raincat.png" title="强一致性分布式事务解决方案。" width="15%">
</a>
</p>
<p align="center">
<a href="https://gitee.com/dromara/myth" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/myth.png" title="可靠消息分布式事务解决方案。" width="15%">
</a>
<a href="https://cubic.jiagoujishu.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/cubic.png" title="一站式问题定位平台以agent的方式无侵入接入应用完整集成arthas功能模块致力于应用级监控帮助开发人员快速定位问题" width="15%">
</a>
<a href="https://maxkey.top/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/maxkey.png" title="业界领先的身份管理和认证产品" width="15%">
</a>
<a href="http://forest.dtflyx.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/forest-logo.png" title="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" width="15%">
</a>
<a href="https://jpom.io/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/jpom.png" title="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件" width="15%">
</a>
<a href="https://su.usthe.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sureness.png" title="面向 REST API 的高性能认证鉴权框架" width="15%">
</a>
</p>
<p align="center">
<a href="https://easy-es.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/easy-es2.png" title="傻瓜级ElasticSearch搜索引擎ORM框架" width="15%">
</a>
<a href="https://gitee.com/dromara/northstar" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/northstar_logo.png" title="Northstar盈富量化交易平台" width="15%">
</a>
<a href="https://hertzbeat.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/hertzbeat_brand.jpg" title="易用友好的云监控系统" width="15%">
</a>
<a href="https://plugins.sheng90.wang/fast-request/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/fast-request.gif" title="Idea 版 Postman为简化调试API而生" width="15%">
</a>
<a href="https://www.jeesuite.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/mendmix.png" title="开源分布式云原生架构一站式解决方案" width="15%">
</a>
<a href="https://gitee.com/dromara/koalas-rpc" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/koalas-rpc2.png" title="企业生产级百亿日PV高可用可拓展的RPC框架。" width="15%">
</a>
</p>
<p align="center">
<a href="https://async.sizegang.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/gobrs-async.png" title="配置极简功能强大的异步任务动态编排框架" width="15%">
</a>
<a href="https://dynamictp.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dynamic-tp.png" title="基于配置中心的轻量级动态可监控线程池" width="15%">
</a>
<a href="https://www.x-easypdf.cn" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/x-easypdf.png" title="一个用搭积木的方式构建pdf的框架基于pdfbox" width="15%">
</a>
<a href="http://dromara.gitee.io/image-combiner" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/image-combiner.png" title="一个专门用于图片合成的工具,没有很复杂的功能,简单实用,却不失强大" width="15%">
</a>
<a href="https://www.herodotus.cn/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dante-cloud2.png" title="Dante-Cloud 是一款企业级微服务架构和服务能力开发平台。" width="15%">
</a>
<a href="https://dromara.org/zh/projects/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/dromara.png" title="让每一位开源爱好者,体会到开源的快乐。" width="15%">
</a>
</p>

Binary file not shown.

View File

@ -0,0 +1,5 @@
{
"deviceId":"A0-29-42-42-F6-3F",
"deviceType":1,
"deptId":101
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
建议第三方软件放置在此目录中,打包时会将资源加入安装包内。
1 config.json 放一个配置文件, 可以读取,
手动设置设备的唯一id
设备类型

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

View File

View File

@ -0,0 +1,170 @@
const { app: electronApp } = require('electron');
const { autoUpdater } = require("electron-updater");
const is = require('ee-core/utils/is');
const Log = require('ee-core/log');
const Conf = require('ee-core/config');
const CoreWindow = require('ee-core/electron/window');
const Electron = require('ee-core/electron');
/**
* 自动升级插件
* @class
*/
class AutoUpdaterAddon {
constructor() {
}
/**
* 创建
*/
create () {
Log.info('[addon:autoUpdater] load');
const cfg = Conf.getValue('addons.autoUpdater');
if ((is.windows() && cfg.windows)
|| (is.macOS() && cfg.macOS)
|| (is.linux() && cfg.linux))
{
// continue
} else {
return
}
// 是否检查更新
if (cfg.force) {
this.checkUpdate();
}
const status = {
error: -1,
available: 1,
noAvailable: 2,
downloading: 3,
downloaded: 4,
}
const version = electronApp.getVersion();
Log.info('[addon:autoUpdater] current version: ', version);
// 设置下载服务器地址
let server = cfg.options.url;
let lastChar = server.substring(server.length - 1);
server = lastChar === '/' ? server : server + "/";
//Log.info('[addon:autoUpdater] server: ', server);
cfg.options.url = server;
// 是否后台自动下载
autoUpdater.autoDownload = cfg.force ? true : false;
try {
autoUpdater.setFeedURL(cfg.options);
} catch (error) {
Log.error('[addon:autoUpdater] setFeedURL error : ', error);
}
autoUpdater.on('checking-for-update', () => {
//sendStatusToWindow('正在检查更新...');
})
autoUpdater.on('update-available', (info) => {
info.status = status.available;
info.desc = '有可用更新';
this.sendStatusToWindow(info);
})
autoUpdater.on('update-not-available', (info) => {
info.status = status.noAvailable;
info.desc = '没有可用更新';
this.sendStatusToWindow(info);
})
autoUpdater.on('error', (err) => {
let info = {
status: status.error,
desc: err
}
this.sendStatusToWindow(info);
})
autoUpdater.on('download-progress', (progressObj) => {
let percentNumber = parseInt(progressObj.percent);
let totalSize = this.bytesChange(progressObj.total);
let transferredSize = this.bytesChange(progressObj.transferred);
let text = '已下载 ' + percentNumber + '%';
text = text + ' (' + transferredSize + "/" + totalSize + ')';
let info = {
status: status.downloading,
desc: text,
percentNumber: percentNumber,
totalSize: totalSize,
transferredSize: transferredSize
}
Log.info('[addon:autoUpdater] progress: ', text);
this.sendStatusToWindow(info);
})
autoUpdater.on('update-downloaded', (info) => {
info.status = status.downloaded;
info.desc = '下载完成';
this.sendStatusToWindow(info);
// 托盘插件默认会阻止窗口关闭,这里设置允许关闭窗口
Electron.extra.closeWindow = true;
autoUpdater.quitAndInstall();
// const mainWindow = CoreWindow.getMainWindow();
// if (mainWindow) {
// mainWindow.destroy()
// }
// electronApp.appQuit()
});
}
/**
* 检查更新
*/
checkUpdate () {
autoUpdater.checkForUpdates();
}
/**
* 下载更新
*/
download () {
autoUpdater.downloadUpdate();
}
/**
* 向前端发消息
*/
sendStatusToWindow(content = {}) {
const textJson = JSON.stringify(content);
const channel = 'app.updater';
const win = CoreWindow.getMainWindow();
win.webContents.send(channel, textJson);
}
/**
* 单位转换
*/
bytesChange (limit) {
let size = "";
if(limit < 0.1 * 1024){
size = limit.toFixed(2) + "B";
}else if(limit < 0.1 * 1024 * 1024){
size = (limit/1024).toFixed(2) + "KB";
}else if(limit < 0.1 * 1024 * 1024 * 1024){
size = (limit/(1024 * 1024)).toFixed(2) + "MB";
}else{
size = (limit/(1024 * 1024 * 1024)).toFixed(2) + "GB";
}
let sizeStr = size + "";
let index = sizeStr.indexOf(".");
let dou = sizeStr.substring(index + 1 , index + 3);
if(dou == "00"){
return sizeStr.substring(0, index) + sizeStr.substring(index + 3, index + 5);
}
return size;
}
}
AutoUpdaterAddon.toString = () => '[class AutoUpdaterAddon]';
module.exports = AutoUpdaterAddon;

View File

@ -0,0 +1,67 @@
const { app: electronApp } = require('electron');
const Log = require('ee-core/log');
const Conf = require('ee-core/config');
/**
* 唤醒插件
* @class
*/
class AwakenAddon {
constructor() {
this.protocol = '';
}
/**
* 创建
*/
create () {
Log.info('[addon:awaken] load');
const cfg = Conf.getValue('addons.awaken');
this.protocol = cfg.protocol;
electronApp.setAsDefaultProtocolClient(this.protocol);
this.handleArgv(process.argv);
electronApp.on('second-instance', (event, argv) => {
if (process.platform === 'win32') {
this.handleArgv(argv)
}
})
// 仅用于macOS
electronApp.on('open-url', (event, urlStr) => {
this.handleUrl(urlStr)
})
}
/**
* 参数处理
*/
handleArgv(argv) {
const offset = electronApp.isPackaged ? 1 : 2;
const url = argv.find((arg, i) => i >= offset && arg.startsWith(this.protocol));
this.handleUrl(url)
}
/**
* url解析
*/
handleUrl(awakeUrlStr) {
if (!awakeUrlStr || awakeUrlStr.length === 0) {
return
}
const {hostname, pathname, search} = new URL(awakeUrlStr);
let awakeUrlInfo = {
urlStr: awakeUrlStr,
urlHost: hostname,
urlPath: pathname,
urlParams: search && search.slice(1)
}
Log.info('[addon:awaken] awakeUrlInfo:', awakeUrlInfo);
}
}
AwakenAddon.toString = () => '[class AwakenAddon]';
module.exports = AwakenAddon;

View File

@ -0,0 +1,94 @@
const { app, session } = require('electron');
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const Log = require('ee-core/log');
/**
* 扩展插件 electron自身对该功能并不完全支持官方也不建议使用
* @class
*/
class ChromeExtensionAddon {
constructor() {
}
/**
* 创建
*/
async create () {
Log.info('[addon:chromeExtension] load');
const extensionIds = this.getAllIds();
for (let i = 0; i < extensionIds.length; i++) {
await this.load(extensionIds[i]);
}
}
/**
* 获取扩展id列表crx解压后的目录名即是该扩展的id
*/
getAllIds () {
const extendsionDir = this.getDirectory();
const ids = this.getDirs(extendsionDir);
return ids;
}
/**
* 扩展所在目录
*/
getDirectory () {
let extensionDirPath = '';
let variablePath = 'build'; // 打包前路径
if (app.isPackaged) {
variablePath = '..'; // 打包后路径
}
extensionDirPath = path.join(app.getAppPath(), variablePath, "extraResources", "chromeExtension");
return extensionDirPath;
}
/**
* 加载扩展
*/
async load (extensionId = '') {
if (_.isEmpty(extensionId)) {
return false
}
try {
const extensionPath = path.join(this.getDirectory(), extensionId);
Log.info('[addon:chromeExtension] extensionPath:', extensionPath);
await session.defaultSession.loadExtension(extensionPath, { allowFileAccess: true });
} catch (e) {
Log.info('[addon:chromeExtension] load extension error extensionId:%s, errorInfo:%s', extensionId, e.toString());
return false
}
return true
}
/**
* 获取目录下所有文件夹
*/
getDirs(dir) {
if (!dir) {
return [];
}
const components = [];
const files = fs.readdirSync(dir);
files.forEach(function(item, index) {
const stat = fs.lstatSync(dir + '/' + item);
if (stat.isDirectory() === true) {
components.push(item);
}
});
return components;
};
}
ChromeExtensionAddon.toString = () => '[class ChromeExtensionAddon]';
module.exports = ChromeExtensionAddon;

View File

@ -0,0 +1,33 @@
const Log = require('ee-core/log');
const EE = require('ee-core/ee');
/**
* 安全插件
* @class
*/
class SecurityAddon {
constructor() {
}
/**
* 创建
*/
create () {
Log.info('[addon:security] load');
const { CoreApp } = EE;
const runWithDebug = process.argv.find(function(e){
let isHasDebug = e.includes("--inspect") || e.includes("--inspect-brk") || e.includes("--remote-debugging-port");
return isHasDebug;
})
// 不允许远程调试
if (runWithDebug) {
Log.error('[error] Remote debugging is not allowed, runWithDebug:', runWithDebug);
CoreApp.appQuit();
}
}
}
SecurityAddon.toString = () => '[class SecurityAddon]';
module.exports = SecurityAddon;

View File

@ -0,0 +1,85 @@
const { Tray, Menu, app ,BrowserWindow} = require('electron');
const path = require('path');
const Ps = require('ee-core/ps');
const Log = require('ee-core/log');
const Electron = require('ee-core/electron');
const CoreWindow = require('ee-core/electron/window');
const Conf = require('ee-core/config');
const EE = require('ee-core/ee');
/**
* 托盘插件
* @class
*/
class TrayAddon {
constructor() {
this.tray = null;
}
/**
* 创建托盘
*/
create () {
// 开发环境,代码热更新开启时,会导致托盘中有残影
if (Ps.isDev() && Ps.isHotReload()) return;
Log.info('[addon:tray] load');
const { CoreApp } = EE;
const cfg = Conf.getValue('addons.tray');
const mainWindow = CoreWindow.getMainWindow();
// 托盘图标
let iconPath = path.join(Ps.getHomeDir(), cfg.icon);
// 托盘菜单功能列表
let trayMenuTemplate = [
{
label: '显示',
click: function () {
mainWindow.show();
}
},
{
label: '退出',
click: function () {
console.log('exit click 事件')
// CoreApp.appQuit();
app.quit()
}
}
]
// 点击关闭,最小化到托盘 这里要是阻止关闭就完全无法关闭了
mainWindow.on('close', (event) => {
console.log('close 事件')
console.log(event.sender)
// if (Electron.extra.closeWindow == true) {
// return;
// }
// mainWindow.hide();
// 禁止关闭
// event.preventDefault();
//todo: 这个变量控制的方法来区分是否退出 还是不退出
if(global.isUserExit == false){
event.preventDefault();
}else{
app.quit()
}
});
// 实例化托盘
this.tray = new Tray(iconPath);
this.tray.setToolTip(cfg.title);
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);
this.tray.setContextMenu(contextMenu);
// 左键单击的时候能够显示主窗口
this.tray.on('click', () => {
mainWindow.show()
})
}
}
TrayAddon.toString = () => '[class TrayAddon]';
module.exports = TrayAddon;

View File

@ -0,0 +1,144 @@
/**
* ee-bin 配置
* 仅适用于开发环境
*/
module.exports = {
/**
* development serve ("frontend" "electron" )
* ee-bin dev
*/
dev: {
frontend: {
directory: './frontend',
cmd: 'npm',
args: ['run', 'dev'],
protocol: 'http://',
hostname: 'localhost',
port: 17680,
indexPath: 'index.html'
},
electron: {
directory: './',
cmd: 'electron',
args: ['.', '--env=local'],
loadingPage: '/public/html/loading.html',
}
},
/**
* 构建
* ee-bin build
*/
build: {
frontend: {
directory: './frontend',
cmd: 'npm',
args: ['run', 'build'],
},
go_w: {
directory: './go',
cmd: 'go',
args: ['build', '-o=../build/extraResources/goapp.exe'],
},
go_m: {
directory: './go',
cmd: 'go',
args: ['build', '-o=../build/extraResources/goapp'],
},
go_l: {
directory: './go',
cmd: 'go',
args: ['build', '-o=../build/extraResources/goapp'],
},
python: {
directory: './python',
cmd: 'python',
args: ['./setup.py', 'build'],
},
},
/**
* 移动资源
* ee-bin move
*/
move: {
frontend_dist: {
dist: './frontend/dist',
target: './public/dist'
},
go_static: {
dist: './frontend/dist',
target: './go/public/dist'
},
go_config: {
dist: './go/config',
target: './go/public/config'
},
go_package: {
dist: './package.json',
target: './go/public/package.json'
},
go_images: {
dist: './public/images',
target: './go/public/images'
},
python_dist: {
dist: './python/dist',
target: './build/extraResources/py'
},
},
/**
* 预发布模式prod
* ee-bin start
*/
start: {
directory: './',
cmd: 'electron',
args: ['.', '--env=prod']
},
/**
* 加密
*/
encrypt: {
type: 'confusion',
files: [
'electron/**/*.(js|json)',
'!electron/config/encrypt.js',
'!electron/config/nodemon.json',
'!electron/config/builder.json',
'!electron/config/bin.json',
],
fileExt: ['.js'],
confusionOptions: {
compact: true,
stringArray: true,
stringArrayEncoding: ['none'],
deadCodeInjection: false,
}
},
/**
* 执行自定义命令
* ee-bin exec
*/
exec: {
node_v: {
directory: './',
cmd: 'node',
args: ['-v'],
},
npm_v: {
directory: './',
cmd: 'npm',
args: ['-v'],
},
python: {
directory: './python',
cmd: 'python',
args: ['./main.py', '--port=7074'],
stdio: "inherit", // ignore
},
},
};

View File

@ -0,0 +1,56 @@
{
"productName": "appCtr",
"appId": "com.electron.appCtr",
"copyright": "© 2023 安徽烁景智能科技有限公司 Technology Co., Ltd.",
"directories": {
"output": "out"
},
"asar": true,
"files": [
"**/*",
"!frontend/",
"!run/",
"!logs/",
"!go/",
"!python/",
"!data/"
],
"extraResources": {
"from": "build/extraResources/",
"to": "extraResources"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "build/icons/icon.ico",
"uninstallerIcon": "build/icons/icon.ico",
"installerHeaderIcon": "build/icons/icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "appCtr"
},
"mac": {
"icon": "build/icons/icon.icns",
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"darkModeSupport": true,
"hardenedRuntime": false
},
"win": {
"icon": "build/icons/icon.ico",
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"target": [
{
"target": "nsis"
}
]
},
"linux": {
"icon": "build/icons",
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"target": [
"deb"
],
"category": "Utility"
}
}

View File

@ -0,0 +1,215 @@
'use strict';
const path = require('path');
/**
* 默认配置
*/
module.exports = (appInfo) => {
const config = {};
/**
* 开发者工具
*/
config.openDevTools = true;
/**
* 应用程序顶部菜单
*/
config.openAppMenu = true;
/**
* 主窗口
*/
config.windowsOption = {
title: 'appCtr',
width: 980,
height: 650,
minWidth: 400,
minHeight: 300,
// 禁止缩小
resizable: false,
// 禁止最小化
minimizable: false,
webPreferences: {
//webSecurity: false,
contextIsolation: false, // false -> 可在渲染进程中使用electron的apitrue->需要bridge.js(contextBridge)
nodeIntegration: true,
//preload: path.join(appInfo.baseDir, 'preload', 'bridge.js'),
},
frame: true,
show: false,
icon: path.join(appInfo.home, 'public', 'images', 'logo-32.png'),
};
/**
* ee框架日志
*/
config.logger = {
encoding: 'utf8',
level: 'INFO',
outputJSON: false,
buffer: true,
enablePerformanceTimer: false,
rotator: 'day',
appLogName: 'appCtr.log',
coreLogName: 'appCtr-core.log',
errorLogName: 'appCtr-error.log'
}
/**
* 远程模式-web地址
*/
config.remoteUrl = {
enable: false,
url: 'http://electron-egg.kaka996.com/'
};
/**
* 内置socket服务
*/
config.socketServer = {
enable: false,
port: 7070,
path: "/socket.io/",
connectTimeout: 45000,
pingTimeout: 30000,
pingInterval: 25000,
maxHttpBufferSize: 1e8,
transports: ["polling", "websocket"],
cors: {
origin: true,
}
};
/**
* 内置http服务
*/
config.httpServer = {
enable: false,
https: {
enable: false,
key: '/public/ssl/localhost+1.key',
cert: '/public/ssl/localhost+1.pem'
},
host: '127.0.0.1',
port: 7071,
cors: {
origin: "*"
},
body: {
multipart: true,
formidable: {
keepExtensions: true
}
},
filterRequest: {
uris: [
'favicon.ico'
],
returnData: ''
}
};
/**
* 主进程
*/
config.mainServer = {
protocol: 'file://',
indexPath: '/public/dist/index.html',
host: '127.0.0.1',
port: 7072,
};
/**
* Cross-language service
* 跨语言服务
* 例如执行go的二进制程序默认目录为 ./extraResources/
*/
config.cross = {
go: {
enable: false,
name: 'goapp',
args: ['--port=7073'],
appExit: true,
},
python: {
enable: false,
name: 'pyapp',
cmd: './py/pyapp',
directory: './py',
args: ['--port=7074'],
appExit: true,
},
};
/**
* 硬件加速
*/
config.hardGpu = {
enable: true
};
/**
* 异常捕获
*/
config.exception = {
mainExit: false,
childExit: true,
rendererExit: true,
};
/**
* jobs
*/
config.jobs = {
messageLog: true
};
/**
* 插件功能
*/
config.addons = {
window: {
enable: true,
},
tray: {
enable: true,
title: 'appCtr',
icon: '/public/images/tray.png'
},
security: {
enable: true,
},
awaken: {
enable: true,
protocol: 'appCtr',
args: []
},
autoUpdater: {
enable: true,
windows: false,
macOS: false,
linux: false,
options: {
provider: 'generic',
url: 'http://kodo.qiniu.com/'
},
force: false,
},
javaServer: {
enable: false,
port: 117680,
jreVersion: 'jre1.8.0_201',
opt: '-server -Xms512M -Xmx512M -Xss512k -Dspring.profiles.active=prod -Dserver.port=${port} -Dlogging.file.path="${path}" ',
name: 'java-app.jar'
}
};
return {
...config
};
}

View File

@ -0,0 +1,60 @@
'use strict';
/**
* 开发环境配置覆盖 config.default.js
*/
module.exports = (appInfo) => {
const config = {};
/**
* 开发者工具
*/
config.openDevTools = {
mode: 'undocked'
};
/**
* 应用程序顶部菜单
*/
config.openAppMenu = true;
/**
* jobs
*/
config.jobs = {
messageLog: true
};
/**
* Cross-language service
* 跨语言服务
* 如果有cmd参数则执行该命令且需要指定 directory
*/
config.cross = {
go: {
// 应用运行时启动
enable: false,
// 程序名
name: 'goapp',
// 可执行程序
cmd: 'go',
// 程序目录
directory: './go',
args: ['run', './main.go', '--env=dev','--basedir=../', '--port=7073'],
appExit: true,
},
python: {
enable: false,
name: 'pyapp',
cmd: 'python',
directory: './python',
args: ['./main.py', '--port=7074'],
stdio: "ignore",
appExit: true,
},
};
return {
...config
};
};

View File

@ -0,0 +1,29 @@
'use strict';
/**
* 生产环境配置覆盖 config.default.js
*/
module.exports = (appInfo) => {
const config = {};
/**
* 开发者工具
*/
config.openDevTools = false;
/**
* 应用程序顶部菜单
*/
config.openAppMenu = false;
/**
* jobs
*/
config.jobs = {
messageLog: false
};
return {
...config
};
};

View File

@ -0,0 +1,12 @@
{
"watch": [
"electron/"
],
"ignore": [],
"ext": "js,json",
"verbose": true,
"exec": "ee-bin dev",
"restartable": "hr",
"colours": true,
"events": {}
}

View File

@ -0,0 +1,100 @@
'use strict';
const { Controller } = require('ee-core');
const Cross = require('ee-core/cross');
const Log = require('ee-core/log');
const HttpClient = require('ee-core/httpclient');
const Services = require('ee-core/services');
/**
* Cross
* @class
*/
class CrossController extends Controller {
constructor(ctx) {
super(ctx);
}
/**
* View process service information
*/
info() {
const pids = Cross.getPids();
Log.info('cross pids:', pids);
let num = 1;
pids.forEach(pid => {
let entity = Cross.getProc(pid);
Log.info(`server-${num} name:${entity.name}`);
Log.info(`server-${num} config:`, entity.config);
num++;
})
return 'hello electron-egg';
}
/**
* Get service url
*/
async getUrl(args) {
const { name } = args;
const serverUrl = Cross.getUrl(name);
return serverUrl;
}
/**
* kill service
* By default (modifiable), killing the process will exit the electron application.
*/
async killServer(args) {
const { type, name } = args;
if (type == 'all') {
Cross.killAll();
} else {
Cross.killByName(name);
}
return;
}
/**
* create service
*/
async createServer(args) {
const { program } = args;
if (program == 'go') {
Services.get('cross').createGoServer();
} else if (program == 'java') {
Services.get('cross').createJavaServer();
} else if (program == 'python') {
Services.get('cross').createPythonServer();
}
return;
}
/**
* Access the api for the cross service
*/
async requestApi(args) {
const { name, urlPath, params} = args;
const hc = new HttpClient();
const serverUrl = Cross.getUrl(name);
console.log('Server Url:', serverUrl);
const apiHello = serverUrl + urlPath;
const options = {
method: 'GET',
data: params || {},
dataType: 'json',
timeout: 1000,
};
const result = await hc.request(apiHello, options);
return result.data;
}
}
CrossController.toString = () => '[class CrossController]';
module.exports = CrossController;

View File

@ -0,0 +1,71 @@
'use strict';
const { Controller } = require('ee-core');
const { dialog } = require('electron');
const _ = require('lodash');
const CoreWindow = require('ee-core/electron/window');
/**
* 特效 - 功能demo
* @class
*/
class EffectController extends Controller {
constructor(ctx) {
super(ctx);
}
/**
* 选择文件
*/
selectFile() {
const filePaths = dialog.showOpenDialogSync({
properties: ['openFile']
});
if (_.isEmpty(filePaths)) {
return null
}
return filePaths[0];
}
/**
* login window
*/
loginWindow(args) {
const { width, height } = args;
const win = CoreWindow.getMainWindow();
const size = {
width: width || 400,
height: height || 300
}
win.setSize(size.width, size.height);
win.setResizable(true);
win.center();
win.show();
win.focus();
}
/**
* restore window
*/
restoreWindow(args) {
const { width, height } = args;
const win = CoreWindow.getMainWindow();
const size = {
width: width || 980,
height: height || 650
}
win.setSize(size.width, size.height);
win.setResizable(true);
win.center();
win.show();
win.focus();
}
}
EffectController.toString = () => '[class EffectController]';
module.exports = EffectController;

View File

@ -0,0 +1,75 @@
'use strict';
const { Controller } = require('ee-core');
const Log = require('ee-core/log');
const Services = require('ee-core/services');
const Addon = require('ee-core/addon');
/**
* example
* @class
*/
class ExampleController extends Controller {
constructor(ctx) {
super(ctx);
}
/**
* 所有方法接收两个参数
* @param args 前端传的参数
* @param event - ipc通信时才有值详情见控制器文档
*/
/**
* test
*/
async test () {
// const result1 = await Services.get('example').test('electron');
// Log.info('service result1:', result1);
// Services.get('framework').test('electron');
return 'hello electron-egg';
}
/**
* test
*/
async testUtils () {
let mid = await Utils.machineIdSync(true);
Log.info('mid 11111111:', mid);
Utils.machineId().then((id) => {
Log.info('mid 222222222:', id);
});
return;
}
/**
* test
*/
async testService () {
const serviceResult2 = await Services.get('example').test('electron');
Log.info('service result2:', serviceResult2);
return;
}
/**
* test
*/
async testAddon () {
const trayResult2 = Addon.get('tray').hello();
Log.info('addon result2:', trayResult2);
return;
}
}
ExampleController.toString = () => '[class ExampleController]';
module.exports = ExampleController;

View File

@ -0,0 +1,547 @@
'use strict';
const path = require('path');
const fs = require('fs');
const { exec } = require('child_process');
const { Controller } = require('ee-core');
const { app: electronApp, shell } = require('electron');
const dayjs = require('dayjs');
const Ps = require('ee-core/ps');
const Log = require('ee-core/log');
const Services = require('ee-core/services');
const Conf = require('ee-core/config');
const Addon = require('ee-core/addon');
const EE = require('ee-core/ee');
const { getNetworkIFaceOne, getMac, getAllMac, getAllPhysicsMac } = require('@lzwme/get-physical-address');
const os = require('os');
// 网络
const net = require('net');
// 串口
const { SerialPort } = require('serialport')
// 保存串口实例
var GlobalSeriaPortIns = undefined;
/**
* electron-egg framework - 功能demo
* @class
*/
class FrameworkController extends Controller {
constructor(ctx) {
super(ctx);
}
/**
* 所有方法接收两个参数
* @param args 前端传的参数
* @param event - ipc通信时才有值详情见控制器文档
*/
/**
* 发送串口消息
* @param {*} seriaPort 串口实例
*/
sendSeriaPort(args) {
const port = GlobalSeriaPortIns;
const msg = args.msg;
port.write(msg, 'hex')
// console.log('测试发送消息'+msg);
}
/**
* 连接串口 只执行一次
* @param {*} options 串口参数
* @param {*} event 回调.
*/
connectSeriaPort(options, event) {
// 保证执行一次
if (GlobalSeriaPortIns != undefined) {
console.log("SerialPort is have");
return;
}
const channel = 'controller.hardware.connectSeriaPort';
const port = new SerialPort(options, (e) => {
console.log("SerialPort open");
console.log(e);
if (e === null) {
// 打开成功 把串口发送出去
let data2 = {
type: 'connect'
}
event.reply(`${channel}`, data2)
}
})
port.on('data', (data) => {
let data2 = {
type: 'received',
data: data
}
console.log(`Received data: ${data2}`)
event.reply(`${channel}`, data2)
})
setInterval(() => {
// console.log('setInterval')
if (!port.isOpen) {
// console.log('setInterval open')
port.open();
}
}, 1000)
port.on('close', () => {
let data2 = {
type: 'close'
}
console.log(`SerialPort close: ${data2}`)
event.reply(`${channel}`, data2)
})
port.on('error', (e) => {
let data2 = {
type: 'error'
}
console.log(`SerialPort error: ${e}`)
event.reply(`${channel}`, data2)
})
GlobalSeriaPortIns = port;
}
/**
* 发送tcp 消息
* @param {*} args 包含host port msg type { 1 hex, 2 ascii}
* @param {*} event
*/
sendTcpSocket(args, event) {
const channel = 'controller.example.sendTcpSocket';
console.log("tcp params")
console.log(args);
const client = new net.Socket();
// client.setEncoding('ascii');
const HOST = args.host;
const PORT = args.port;
const msg = args.msg;
// const HOST = '192.168.5.134';
// const PORT = 9080;
client.connect(PORT, HOST, function () {
console.log('Connected to: ' + HOST + ':' + PORT);
});
client.on('data', function (data) {
console.log('Received: ' + data);
event.sender.send(`${channel}`, data);
});
//两种码的发送 ok
// client.write('Hello, server!','ascii');
if (args.type == 1) {
client.write(msg, 'hex');
} else {
client.write(msg, 'ascii');
}
client.on('close', function () {
console.log('Connection closed');
});
client.end();
}
/**
* 获取extraResources 目录下的json 配置文件
* @param {*} name 配置文件名
* @returns 返回json 字符传
*/
getExResConfig(name) {
let configPath = path.join(Ps.getExtraResourcesDir(), name);;
console.log(configPath)
// let configJSON = null;
let dataString = null;
try {
// 同步读取配置文件
dataString = fs.readFileSync(configPath, 'utf8');
// 解析 JSON 格式的配置数据
// configJSON = JSON.parse(data);
// console.log('读取到的配置:', configJSON);
// 在这里可以根据需要使用配置数据进行操作
} catch (err) {
console.error('无法读取配置文件:', err);
}
return dataString;
}
/**
* json数据库操作
*/
async jsondbOperation(args) {
const { action, info, delete_name, update_name, update_age, search_age, data_dir } = args;
const data = {
action,
result: null,
all_list: []
};
switch (action) {
case 'add':
data.result = await Services.get('database.jsondb').addTestData(info);
break;
case 'del':
data.result = await Services.get('database.jsondb').delTestData(delete_name);
break;
case 'update':
data.result = await Services.get('database.jsondb').updateTestData(update_name, update_age);
break;
case 'get':
data.result = await Services.get('database.jsondb').getTestData(search_age);
break;
case 'getDataDir':
data.result = await Services.get('database.jsondb').getDataDir();
break;
case 'setDataDir':
data.result = await Services.get('database.jsondb').setCustomDataDir(data_dir);
break;
}
data.all_list = await Services.get('database.jsondb').getAllTestData();
return data;
}
/**
* sqlite数据库操作
*/
async sqlitedbOperation(args) {
const { action, info, delete_name, update_name, update_age, search_age, data_dir } = args;
const data = {
action,
result: null,
all_list: [],
code: 0
};
try {
// test
Services.get('database.sqlitedb').getDataDir();
} catch (err) {
console.log(err);
data.code = -1;
return data;
}
switch (action) {
case 'add':
data.result = await Services.get('database.sqlitedb').addTestDataSqlite(info);;
break;
case 'del':
data.result = await Services.get('database.sqlitedb').delTestDataSqlite(delete_name);;
break;
case 'update':
data.result = await Services.get('database.sqlitedb').updateTestDataSqlite(update_name, update_age);
break;
case 'get':
data.result = await Services.get('database.sqlitedb').getTestDataSqlite(search_age);
break;
case 'getDataDir':
data.result = await Services.get('database.sqlitedb').getDataDir();
break;
case 'setDataDir':
data.result = await Services.get('database.sqlitedb').setCustomDataDir(data_dir);
break;
}
data.all_list = await Services.get('database.sqlitedb').getAllTestDataSqlite();
return data;
}
/**
* 调用其它程序exebash等可执行程序
*/
openSoftware(softName) {
if (!softName) {
return false;
}
let softwarePath = path.join(Ps.getExtraResourcesDir(), softName);
Log.info('[openSoftware] softwarePath:', softwarePath);
// 检查程序是否存在
if (!fs.existsSync(softwarePath)) {
return false;
}
// 命令行字符串 并 执行
let cmdStr = 'start ' + softwarePath;
exec(cmdStr);
return true;
}
/**
* 检查是否有新版本
*/
checkForUpdater() {
Addon.get('autoUpdater').checkUpdate();
return;
}
/**
* 下载新版本
*/
downloadApp() {
Addon.get('autoUpdater').download();
return;
}
/**
* 检测http服务是否开启
*/
async checkHttpServer() {
const httpServerConfig = Conf.getValue('httpServer');
const url = httpServerConfig.protocol + httpServerConfig.host + ':' + httpServerConfig.port;
const data = {
enable: httpServerConfig.enable,
server: url
}
return data;
}
/**
* 一个http请求访问此方法
*/
async doHttpRequest() {
const { CoreApp } = EE;
// http方法
const method = CoreApp.request.method;
// http get 参数
let params = CoreApp.request.query;
params = (params instanceof Object) ? params : JSON.parse(JSON.stringify(params));
// http post 参数
const body = CoreApp.request.body;
const httpInfo = {
method,
params,
body
}
Log.info('httpInfo:', httpInfo);
if (!body.id) {
return false;
}
const dir = electronApp.getPath(body.id);
shell.openPath(dir);
return true;
}
/**
* 一个socket io请求访问此方法
*/
async doSocketRequest(args) {
if (!args.id) {
return false;
}
const dir = electronApp.getPath(args.id);
shell.openPath(dir);
return true;
}
/**
* 异步消息类型
*/
async ipcInvokeMsg(args, event) {
let timeNow = dayjs().format('YYYY-MM-DD HH:mm:ss');
const data = args + ' - ' + timeNow;
return data;
}
/**
* 同步消息类型
*/
async ipcSendSyncMsg(args) {
let timeNow = dayjs().format('YYYY-MM-DD HH:mm:ss');
const data = args + ' - ' + timeNow;
return data;
}
/**
* 双向异步通信
*/
async ipcSendMsg(args, event) {
const { type, content } = args;
const data = await Services.get('framework').bothWayMessage(type, content, event);
return data;
}
/**
* 不建议使用请使用electron的api来获取文件的本机路径然后读取并上传
* 使用http的files属性实际上多余拷贝一次文件
*/
async uploadFile() {
const { CoreApp } = EE;
let tmpDir = Ps.getLogDir();
const files = CoreApp.request.files;
let file = files.file;
let tmpFilePath = path.join(tmpDir, file.originalFilename);
try {
let tmpFile = fs.readFileSync(file.filepath);
fs.writeFileSync(tmpFilePath, tmpFile);
} finally {
await fs.unlink(file.filepath, function () { });
}
const fileStream = fs.createReadStream(tmpFilePath);
const uploadRes = await Services.get('framework').uploadFileToSMMS(fileStream);
return uploadRes;
}
/**
* 启动java项目
*/
async startJavaServer() {
let data = {
code: 0,
msg: '',
server: ''
}
const javaCfg = Conf.getValue('addons.javaServer') || {};
if (!javaCfg.enable) {
data.code = -1;
data.msg = 'addon not enabled!';
return data;
}
await Addon.get('javaServer').createServer();
data.server = 'http://localhost:' + javaCfg.port;
return data;
}
/**
* 关闭java项目
*/
async closeJavaServer() {
let data = {
code: 0,
msg: '',
}
const javaCfg = Conf.getValue('addons.javaServer') || {};
if (!javaCfg.enable) {
data.code = -1;
data.msg = 'addon not enabled!';
return data;
}
await Addon.get('javaServer').kill();
return data;
}
/**
* java运行状态
*/
async runStatus() {
let data = {
code: 0,
msg: '',
flag: false
}
const flag = await Addon.get('javaServer').check();
//Log.info("[FrameworkController:runStatus] flag-----------"+flag);
data.flag = flag;
return data;
}
/**
* 任务
*/
someJob(args, event) {
let jobId = args.id;
let action = args.action;
let result;
switch (action) {
case 'create':
result = Services.get('framework').doJob(jobId, action, event);
break;
case 'close':
Services.get('framework').doJob(jobId, action, event);
break;
default:
}
let data = {
jobId,
action,
result
}
return data;
}
/**
* 创建任务池
*/
async createPool(args, event) {
let num = args.number;
Services.get('framework').doCreatePool(num, event);
// test monitor
Services.get('framework').monitorJob();
return;
}
/**
* 通过进程池执行任务
*/
someJobByPool(args, event) {
let jobId = args.id;
let action = args.action;
let result;
switch (action) {
case 'run':
result = Services.get('framework').doJobByPool(jobId, action, event);
break;
default:
}
let data = {
jobId,
action,
result
}
return data;
}
/**
* 测试接口
*/
hello(args) {
Log.info('hello ', args);
}
}
FrameworkController.toString = () => '[class FrameworkController]';
module.exports = FrameworkController;

View File

@ -0,0 +1,72 @@
'use strict';
const { Controller } = require('ee-core');
const path = require('path');
const Ps = require('ee-core/ps');
const CoreWindow = require('ee-core/electron/window');
const Addon = require('ee-core/addon');
/**
* 硬件设备 - 功能demo
* @class
*/
class HardwareController extends Controller {
constructor(ctx) {
super(ctx);
}
/**
* 获取打印机列表
*/
async getPrinterList () {
//主线程获取打印机列表
const win = CoreWindow.getMainWindow();
const list = await win.webContents.getPrintersAsync();
return list;
}
/**
* 打印
*/
print (args, event) {
const { view, deviceName } = args;
let content = null;
if (view.type == 'html') {
content = path.join('file://', Ps.getHomeDir(), view.content)
} else {
content = view.content;
}
let opt = {
title: 'printer window',
x: 10,
y: 10,
width: 980,
height: 650
}
const name = 'window-printer';
const printWindow = Addon.get('window').create(name, opt);
printWindow.loadURL(content);
printWindow.webContents.once('did-finish-load', () => {
// 页面完全加载完成后,开始打印
printWindow.webContents.print({
silent: false, // 显示打印对话框
printBackground: true,
deviceName,
}, (success, failureReason) => {
const channel = 'controller.hardware.printStatus';
event.reply(`${channel}`, { success, failureReason });
printWindow.close();
});
});
return true;
}
}
HardwareController.toString = () => '[class HardwareController]';
module.exports = HardwareController;

View File

@ -0,0 +1,634 @@
'use strict';
const _ = require('lodash');
const path = require('path');
const { Controller } = require('ee-core');
const {
app: electronApp, dialog, shell, Notification,
powerMonitor, screen, nativeTheme
} = require('electron');
const Conf = require('ee-core/config');
const Ps = require('ee-core/ps');
const Services = require('ee-core/services');
const Addon = require('ee-core/addon');
const { getNetworkIFaceOne, getMac, getAllMac, getAllPhysicsMac } = require('@lzwme/get-physical-address');
const os = require('os');
const shutdown = require('electron-shutdown-command');
// 终端命令
const { exec, execSync, execFile } = require("child_process");
// 声音控制库 https://github.com/LinusU/node-loudness
const loudness = require("loudness");
// 文件处理
const fs = require('fs');
/**
* 操作系统 - 功能demo
* @class
*/
class OsController extends Controller {
constructor(ctx) {
super(ctx);
}
/**
* 所有方法接收两个参数
* @param args 前端传的参数
* @param event - ipc通信时才有值详情见控制器文档
*/
// 获取所有的网卡-无参数
async getAllMac(args) {
// 文档说明 https://github.com/lzwme/get-physical-address/blob/main/.github/README_zh-CN.md
const list = getAllPhysicsMac();
return list;
}
// 查找到指定进程并关闭? 注意 进程要全, 不然容易误杀
deviceKillName(name) {
// 最新 taskkill /F /IM program.exe 这个命令一句话能直接杀掉进程
const self = this;
let rebootShell = "tasklist|findstr " + name;
let command = exec(rebootShell, function (err, stdout, stderr) {
if (err || stderr) {
console.log("tasklist failed" + err + stderr);
} else {
const lines = stdout.split('\n')
console.log(lines);
for (let index = 0; index < lines.length; index++) {
const element = lines[index];
const strs = element.split(' ')
const firstNumber = strs.find(item => !isNaN(Number(item)) && item != '')
console.log('jincheng id :' + firstNumber);
self.deviceKillPid(firstNumber)
}
}
});
command.stdin.end();
command.on("close", function (code) {
console.log("tasklist", code);
});
}
// 杀掉pid 进程
deviceKillPid(pid) {
let rebootShell = "tskill " + pid;
let command = exec(rebootShell, function (err, stdout, stderr) {
if (err || stderr) {
console.log("tskill failed" + err + stderr);
}
})
command.stdin.end();
command.on("close", function (code) {
console.log("tskill", code);
});
}
// 启动指定目录的程序 可能有权限问题, 方案1 打包后管理员权限执行, 2 按照egg 的方案把需要执行的程序拷贝到安装包
deviceStarExe(path) {
let rebootShell = 'start ' + path;
exec(rebootShell);
}
/**
* 设备关机
*/
deviceShutdown() {
// 关机
// let shutdownShell = "shutdown -s -t 00";
console.log('deviceShutdown=============')
shutdown.shutdown();
return true;
}
deviceRestart(args) {
// 重启
let rebootShell = "shutdown -r -t 0";
let command = exec(rebootShell, function (err, stdout, stderr) {
if (err || stderr) {
console.log("shutdown failed" + err + stderr);
}
});
command.stdin.end();
command.on("close", function (code) {
console.log("shutdown", code);
});
}
// 同步执行 声音设置 支持0-100
async deviceLoudness(args) {
const value = args.value;
//操作系统平台
const pf = os.platform();
console.log("OS: " + pf)
// 特殊处理以下
if (pf == "linux") {
let shellStr = "amixer -D pulse set Master " + value + "% unmute"
if (value == 0) {
shellStr = "amixer -D pulse set Master mute"
}
// else{
// shellStr = "amixer -c 0 set Master,0 100%,80% unmute"
// }
let command = exec(shellStr, function (err, stdout, stderr) {
if (err || stderr) {
console.log("amixer failed" + err + stderr);
}
});
command.stdin.end();
command.on("close", function (code) {
console.log("amixer", code);
});
return;
} else {
/// 兼容模式, try catch 如果第一种方式报错, 在采用第二种
try {
//0 为静音
if (value == 0) {
await loudness.setMuted(true)
return await loudness.getMuted()
}
// 设置声音改为不静音 且设置声音
await loudness.setMuted(false)
await loudness.setVolume(value)
const newValue = await loudness.getVolume();
return newValue;
} catch {
const maxVolume = 65535;
let volumeValue = Math.round((value / 100) * maxVolume);
// 确保音量值在有效范围内
volumeValue = Math.max(0, Math.min(maxVolume, volumeValue));
// 将音量值转换为字符串
const valueStr = volumeValue.toString();
let excPath = path.join(Ps.getExtraResourcesDir(), 'nircmd.exe');
execFile(excPath, ['setsysvolume', valueStr], (error, stdout, stderr) => {
if (error) {
console.error(' Nircmd error: ', error);
return;
}
console.log('Nircmd ok ');
});
return value;
}
}
}
// 获取电脑信息
async getOSMessage(args) {
var OSDic = {};
var dealTime = (seconds) => {
var seconds = seconds | 0;
var day = (seconds / (3600 * 24)) | 0;
var hours = ((seconds - day * 3600) / 3600) | 0;
var minutes = ((seconds - day * 3600 * 24 - hours * 3600) / 60) | 0;
var second = seconds % 60;
(day < 10) && (day = '0' + day);
(hours < 10) && (hours = '0' + hours);
(minutes < 10) && (minutes = '0' + minutes);
(second < 10) && (second = '0' + second);
return [day, hours, minutes, second].join(':');
};
var dealMem = (mem) => {
var G = 0,
M = 0,
KB = 0;
(mem > (1 << 30)) && (G = (mem / (1 << 30)).toFixed(2));
(mem > (1 << 20)) && (mem < (1 << 30)) && (M = (mem / (1 << 20)).toFixed(2));
(mem > (1 << 10)) && (mem > (1 << 20)) && (KB = (mem / (1 << 10)).toFixed(2));
return G > 0 ? G + 'G' : M > 0 ? M + 'M' : KB > 0 ? KB + 'KB' : mem + 'B';
};
//cpu架构
const arch = os.arch();
// console.log("cpu架构" + arch);
OSDic["arch"] = arch;
//操作系统内核
const kernel = os.type();
// console.log("操作系统内核:" + kernel);
OSDic["kernel"] = kernel;
//操作系统平台
const pf = os.platform();
// console.log("平台:" + pf);
OSDic["pf"] = pf;
//系统开机时间
const uptime = os.uptime();
// console.log("开机时间:" + dealTime(uptime));
OSDic["uptime"] = uptime;
//主机名
const hn = os.hostname();
// console.log("主机名:" + hn);
OSDic["hostname"] = hn;
// //主目录
// const hdir = os.homedir();
// console.log("主目录:" + hdir);
// OSDic["homedir"] = hdir;
//内存
const totalMem = os.totalmem();
const freeMem = os.freemem();
// console.log("内存大小:" + dealMem(totalMem) + ' 空闲内存:' + dealMem(freeMem));
OSDic["totalmem"] = totalMem;
OSDic["freeMem"] = freeMem;
//cpu
const cpus = os.cpus();
OSDic["cpuModel"] = cpus[0]["model"];
// OSDic["cpus"] = cpus;
// console.log('*****cpu信息*******');
// cpus.forEach((cpu, idx, arr) => {
// var times = cpu.times;
// console.log(`cpu${idx}`);
// console.log(`型号:${cpu.model}`);
// console.log(`频率:${cpu.speed}MHz`);
// console.log(`使用率:${((1 - times.idle / (times.idle + times.user + times.nice + times.sys + times.irq)) * 100).toFixed(2)}%`);
// });
try {
const volumeValue = await loudness.getVolume()
OSDic["volume"] = volumeValue;
console.log('volumeValue' + volumeValue);
} catch (error) {
console.log(error)
}
return OSDic;
}
// const volume = await loudness.getVolume()
// OSDic["volume"] = volume;
// return OSDic;
// }
/**
* 获取当前目录的配置
* @param {*} name 配置文件名
* @returns
*/
getCurrentDirectoryConfig(name) {
let configPath = '';
configPath = path.join(Ps.getExtraResourcesDir(), name);
console.log(configPath)
// let configJSON = null;
let dataString = null;
try {
// 同步读取配置文件
dataString = fs.readFileSync(configPath, 'utf8');
// 解析 JSON 格式的配置数据
// configJSON = JSON.parse(data);
// console.log('读取到的配置:', configJSON);
// 在这里可以根据需要使用配置数据进行操作
} catch (err) {
console.error('无法读取配置文件:', err);
}
return dataString;
}
/**
* 消息提示对话框
*/
messageShow() {
dialog.showMessageBoxSync({
type: 'info', // "none", "info", "error", "question" 或者 "warning"
title: '自定义标题-message',
message: '自定义消息内容',
detail: '其它的额外信息'
})
return '打开了消息框';
}
/**
* 消息提示与确认对话框
*/
messageShowConfirm() {
const res = dialog.showMessageBoxSync({
type: 'info',
title: '自定义标题-message',
message: '自定义消息内容',
detail: '其它的额外信息',
cancelId: 1, // 用于取消对话框的按钮的索引
defaultId: 0, // 设置默认选中的按钮
buttons: ['确认', '取消'], // 按钮及索引
})
let data = (res === 0) ? '点击确认按钮' : '点击取消按钮';
return data;
}
/**
* 选择目录
*/
selectFolder() {
const filePaths = dialog.showOpenDialogSync({
properties: ['openDirectory', 'createDirectory']
});
if (_.isEmpty(filePaths)) {
return null
}
return filePaths[0];
}
/**
* 打开目录
*/
openDirectory(args) {
if (!args.id) {
return false;
}
let dir = '';
if (path.isAbsolute(args.id)) {
dir = args.id;
} else {
dir = electronApp.getPath(args.id);
}
shell.openPath(dir);
return true;
}
/**
* 选择图片
*/
selectPic() {
const filePaths = dialog.showOpenDialogSync({
title: 'select pic',
properties: ['openFile'],
filters: [
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
]
});
if (_.isEmpty(filePaths)) {
return null
}
return filePaths[0];
}
/**
* 加载视图内容
*/
loadViewContent(args) {
const { type, content } = args;
let contentUrl = content;
if (type == 'html') {
contentUrl = path.join('file://', electronApp.getAppPath(), content);
}
Services.get('os').createBrowserView(contentUrl);
return true
}
/**
* 移除视图内容
*/
removeViewContent() {
Services.get('os').removeBrowserView();
return true
}
/**
* 打开新窗口
*/
createWindow(args) {
const { type, content, windowName, windowTitle } = args;
let contentUrl = null;
if (type == 'html') {
contentUrl = path.join('file://', electronApp.getAppPath(), content)
} else if (type == 'web') {
contentUrl = content;
} else if (type == 'vue') {
let addr = 'http://localhost:17680'
if (Ps.isProd()) {
const mainServer = Conf.getValue('mainServer');
if (Conf.isFileProtocol(mainServer)) {
addr = mainServer.protocol + path.join(Ps.getHomeDir(), mainServer.indexPath);
} else {
addr = mainServer.protocol + mainServer.host + ':' + mainServer.port;
}
}
contentUrl = addr + content;
} else {
// some
}
console.log('contentUrl: ', contentUrl);
let opt = {
title: windowTitle
}
const win = Addon.get('window').create(windowName, opt);
const winContentsId = win.webContents.id;
// load page
win.loadURL(contentUrl);
return winContentsId;
}
/**
* 获取窗口contents id
*/
getWCid(args) {
// 主窗口的name默认是main其它窗口name开发者自己定义
const name = args;
const id = Addon.get('window').getWCid(name);
return id;
}
/**
* 加载扩展程序
*/
// async loadExtension (args) {
// const crxFile = args[0];
// if (_.isEmpty(crxFile)) {
// return false;
// }
// const extensionId = path.basename(crxFile, '.crx');
// const chromeExtensionDir = chromeExtension.getDirectory();
// const extensionDir = path.join(chromeExtensionDir, extensionId);
// Log.info("[api] [example] [loadExtension] extension id:", extensionId);
// unzip(crxFile, extensionDir).then(() => {
// Log.info("[api] [example] [loadExtension] unzip success!");
// chromeExtension.load(extensionId);
// });
// return true;
// }
/**
* 创建系统通知
*/
sendNotification(args, event) {
const { title, subtitle, body, silent } = args;
if (!Notification.isSupported()) {
return '当前系统不支持通知';
}
let options = {};
if (!_.isEmpty(title)) {
options.title = title;
}
if (!_.isEmpty(subtitle)) {
options.subtitle = subtitle;
}
if (!_.isEmpty(body)) {
options.body = body;
}
if (!_.isEmpty(silent)) {
options.silent = silent;
}
Services.get('os').createNotification(options, event);
return true
}
/**
* 电源监控
*/
initPowerMonitor(args, event) {
const channel = 'controller.os.initPowerMonitor';
powerMonitor.on('on-ac', (e) => {
let data = {
type: 'on-ac',
msg: '接入了电源'
}
event.reply(`${channel}`, data)
});
powerMonitor.on('on-battery', (e) => {
let data = {
type: 'on-battery',
msg: '使用电池中'
}
event.reply(`${channel}`, data)
});
powerMonitor.on('lock-screen', (e) => {
let data = {
type: 'lock-screen',
msg: '锁屏了'
}
event.reply(`${channel}`, data)
});
powerMonitor.on('unlock-screen', (e) => {
let data = {
type: 'unlock-screen',
msg: '解锁了'
}
event.reply(`${channel}`, data)
});
return true
}
/**
* 获取屏幕信息
*/
getScreen(args) {
let data = [];
let res = {};
if (args == 0) {
let res = screen.getCursorScreenPoint();
data = [
{
title: '横坐标',
desc: res.x
},
{
title: '纵坐标',
desc: res.y
},
]
return data;
}
if (args == 1) {
res = screen.getPrimaryDisplay();
}
if (args == 2) {
let resArr = screen.getAllDisplays();
// 数组,只取一个吧
res = resArr[0];
}
// Log.info('[electron] [ipc] [example] [getScreen] res:', res);
data = [
{
title: '分辨率',
desc: res.bounds.width + ' x ' + res.bounds.height
},
{
title: '单色显示器',
desc: res.monochrome ? '是' : '否'
},
{
title: '色深',
desc: res.colorDepth
},
{
title: '色域',
desc: res.colorSpace
},
{
title: 'scaleFactor',
desc: res.scaleFactor
},
{
title: '加速器',
desc: res.accelerometerSupport
},
{
title: '触控',
desc: res.touchSupport == 'unknown' ? '不支持' : '支持'
},
]
return data;
}
/**
* 获取系统主题
*/
getTheme() {
let theme = 'system';
if (nativeTheme.shouldUseHighContrastColors) {
theme = 'light';
} else if (nativeTheme.shouldUseInvertedColorScheme) {
theme = 'dark';
}
return theme;
}
/**
* 设置系统主题
*/
setTheme(args) {
// TODO 好像没有什么明显效果
nativeTheme.themeSource = args;
return args;
}
}
OsController.toString = () => '[class OsController]';
module.exports = OsController;

69
electron/index.js 100644
View File

@ -0,0 +1,69 @@
const { Application } = require('ee-core');
const { app, globalShortcut } = require('electron')
const EE = require('ee-core/ee');
// 是否用户关闭
global.isUserExit = false;
const { app: electronApp } = require("electron");
class Index extends Application {
constructor() {
super();
// this === eeApp;
}
/**
* core app have been loaded
*/
async ready () {
// do some things
electronApp.commandLine.appendSwitch('enable-webgl');
electronApp.commandLine.appendSwitch("disable-web-security");
}
/**
* electron app ready
*/
async electronAppReady () {
// do some things
}
/**
* main window have been loaded
*/
async windowReady () {
// todo: 注册一个全局快捷键退出
globalShortcut.register('CommandOrControl+Shift+Z', () => {
console.log("Shift+Alt+Z+M is click")
// const { CoreApp } = EE;
// CoreApp.appQuit();
global.isUserExit = true;
app.quit();
// const channel = 'shortcut-key';
// this.electron.mainWindow.webContents.send(channel, "Shift+Alt+Z+M");
})
// do some things
// 延迟加载,无白屏
const winOpt = this.config.windowsOption;
if (winOpt.show == false) {
const win = this.electron.mainWindow;
win.once('ready-to-show', () => {
win.show();
win.focus();
})
}
}
/**
* before app close
*/
async beforeClose () {
// do some things
console.log(" index.js beforeClose")
}
}
Index.toString = () => '[class Index]';
module.exports = Index;

View File

@ -0,0 +1,5 @@
const Log = require('ee-core/log');
exports.welcome = function () {
Log.info('[child-process] [jobs/example/hello] welcome ! ');
}

View File

@ -0,0 +1,55 @@
const Job = require('ee-core/jobs/baseJobClass');
const Loader = require('ee-core/loader');
const Log = require('ee-core/log');
const Ps = require('ee-core/ps');
const { childMessage } = require('ee-core/message');
const Hello = Loader.requireJobsModule('./example/hello');
/**
* example - TimerJob
* @class
*/
class TimerJob extends Job {
constructor(params) {
super();
this.params = params;
}
/**
* handle()方法是必要的且会被自动调用
*/
async handle () {
Log.info("[child-process] TimerJob params: ", this.params);
// 计时器任务
let number = 0;
let jobId = this.params.jobId;
let eventName = 'job-timer-progress-' + jobId;
let timer = setInterval(function() {
Hello.welcome();
childMessage.send(eventName, {jobId, number, end: false});
number++;
}, 1000);
// 用 setTimeout 模拟任务运行时长
setTimeout(() => {
// 关闭定时器
clearInterval(timer);
// 任务结束,重置前端显示
childMessage.send(eventName, {jobId, number:0, pid:0, end: true});
// 如果是childJob任务必须调用 Ps.exit() 方法,让进程退出,否则会常驻内存
// 如果是childPoolJob任务常驻内存等待下一个业务
if (Ps.isChildJob()) {
Ps.exit();
}
}, 10 * 1000)
}
}
TimerJob.toString = () => '[class TimerJob]';
module.exports = TimerJob;

View File

@ -0,0 +1,10 @@
/*
* 如果启用了上下文隔离渲染进程无法使用electron的api
* 可通过contextBridge 导出api给渲染进程使用
*/
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: ipcRenderer,
})

View File

@ -0,0 +1,19 @@
/*************************************************
** preload为预加载模块该文件将会在程序启动时加载 **
*************************************************/
const Addon = require('ee-core/addon');
const Services = require('ee-core/services');
/**
* 预加载模块入口
*/
module.exports = async () => {
// 已实现的功能模块,可选择性使用和修改
Addon.get('tray').create();
Addon.get('security').create();
Addon.get('awaken').create();
Addon.get('autoUpdater').create();
//Services.get('cross').createGoServer();
}

View File

@ -0,0 +1,104 @@
'use strict';
const { Service } = require('ee-core');
const Cross = require('ee-core/cross');
const Log = require('ee-core/log');
const Ps = require('ee-core/ps');
const path = require("path");
const Is = require('ee-core/utils/is');
/**
* crossservice层为单例
* @class
*/
class CrossService extends Service {
constructor(ctx) {
super(ctx);
}
/**
* create go service
* In the default configuration, services can be started with applications.
* Developers can turn off the configuration and create it manually.
*/
async createGoServer() {
// method 1: Use the default Settings
//const entity = await Cross.run(serviceName);
// method 2: Use custom configuration
const serviceName = "go";
const opt = {
name: 'goapp',
cmd: path.join(Ps.getExtraResourcesDir(), 'goapp'),
directory: Ps.getExtraResourcesDir(),
args: ['--port=7073'],
appExit: true,
}
const entity = await Cross.run(serviceName, opt);
Log.info('server name:', entity.name);
Log.info('server config:', entity.config);
Log.info('server url:', entity.getUrl());
return;
}
/**
* create java server
*/
async createJavaServer() {
const serviceName = "java";
const jarPath = path.join(Ps.getExtraResourcesDir(), 'java-app.jar');
const opt = {
name: 'javaapp',
cmd: path.join(Ps.getExtraResourcesDir(), 'jre1.8.0_201/bin/javaw.exe'),
directory: Ps.getExtraResourcesDir(),
args: ['-jar', '-server', '-Xms512M', '-Xmx512M', '-Xss512k', '-Dspring.profiles.active=prod', `-Dserver.port=18080`, `-Dlogging.file.path=${Ps.getLogDir()}`, `${jarPath}`],
appExit: false,
}
if (Is.macOS()) {
// Setup Java program
opt.cmd = path.join(Ps.getExtraResourcesDir(), 'jre1.8.0_201/Contents/Home/bin/java');
}
if (Is.linux()) {
// Setup Java program
}
const entity = await Cross.run(serviceName, opt);
Log.info('server name:', entity.name);
Log.info('server config:', entity.config);
Log.info('server url:', Cross.getUrl(entity.name));
return;
}
/**
* create python service
* In the default configuration, services can be started with applications.
* Developers can turn off the configuration and create it manually.
*/
async createPythonServer() {
// method 1: Use the default Settings
//const entity = await Cross.run(serviceName);
// method 2: Use custom configuration
const serviceName = "python";
const opt = {
name: 'pyapp',
cmd: path.join(Ps.getExtraResourcesDir(), 'py', 'pyapp'),
directory: path.join(Ps.getExtraResourcesDir(), 'py'),
args: ['--port=7074'],
windowsExtname: true,
appExit: true,
}
const entity = await Cross.run(serviceName, opt);
Log.info('server name:', entity.name);
Log.info('server config:', entity.config);
Log.info('server url:', entity.getUrl());
return;
}
}
CrossService.toString = () => '[class CrossService]';
module.exports = CrossService;

View File

@ -0,0 +1,138 @@
'use strict';
const { Service } = require('ee-core');
const Storage = require('ee-core/storage');
const _ = require('lodash');
const path = require('path');
/**
* json数据存储
* @class
*/
class JsondbService extends Service {
constructor (ctx) {
super(ctx);
// jsondb数据库
this.jsonFile = 'demo';
this.demoDB = Storage.connection(this.jsonFile);
this.demoDBKey = {
test_data: 'test_data'
};
}
/*
* Test data
*/
async addTestData(user) {
const key = this.demoDBKey.test_data;
if (!this.demoDB.db.has(key).value()) {
this.demoDB.db.set(key, []).write();
}
const data = this.demoDB.db
.get(key)
.push(user)
.write();
return data;
}
/*
* Test data
*/
async delTestData(name = '') {
const key = this.demoDBKey.test_data;
const data = this.demoDB.db
.get(key)
.remove({name: name})
.write();
return data;
}
/*
* Test data
*/
async updateTestData(name= '', age = 0) {
const key = this.demoDBKey.test_data;
const data = this.demoDB.db
.get(key)
.find({name: name}) // 修改找到的第一个数据,貌似无法批量修改 todo
.assign({age: age})
.write();
return data;
}
/*
* Test data
*/
async getTestData(age = 0) {
const key = this.demoDBKey.test_data;
let data = this.demoDB.db
.get(key)
//.find({age: age}) 查找单个
.filter(function(o) {
let isHas = true;
isHas = age === o.age ? true : false;
return isHas;
})
//.orderBy(['age'], ['name']) 排序
//.slice(0, 10) 分页
.value();
if (_.isEmpty(data)) {
data = []
}
return data;
}
/*
* all Test data
*/
async getAllTestData() {
const key = this.demoDBKey.test_data;
if (!this.demoDB.db.has(key).value()) {
this.demoDB.db.set(key, []).write();
}
let data = this.demoDB.db
.get(key)
.value();
if (_.isEmpty(data)) {
data = []
}
return data;
}
/*
* get data dir (sqlite)
*/
async getDataDir() {
const dir = this.demoDB.getStorageDir();
return dir;
}
/*
* set custom data dir (sqlite)
*/
async setCustomDataDir(dir) {
if (_.isEmpty(dir)) {
return;
}
// the absolute path of the db file
const dbFile = path.join(dir, this.jsonFile);
this.demoDB = Storage.connection(dbFile);
return;
}
}
JsondbService.toString = () => '[class JsondbService]';
module.exports = JsondbService;

View File

@ -0,0 +1,163 @@
'use strict';
const { Service } = require('ee-core');
const Storage = require('ee-core/storage');
const _ = require('lodash');
const path = require('path');
/**
* sqlite数据存储
* @class
*/
class SqlitedbService extends Service {
constructor (ctx) {
super(ctx);
this.sqliteFile = 'sqlite-demo.db';
let sqliteOptions = {
driver: 'sqlite',
default: {
timeout: 6000,
verbose: console.log // 打印sql语法
}
}
this.demoSqliteDB = Storage.connection(this.sqliteFile, sqliteOptions);
}
/*
* 检查并创建表 (sqlite)
*/
async checkAndCreateTableSqlite(tableName = '') {
if (_.isEmpty(tableName)) {
throw new Error(`table name is required`);
}
// 检查表是否存在
const userTable = this.demoSqliteDB.db.prepare('SELECT * FROM sqlite_master WHERE type=? AND name = ?');
const result = userTable.get('table', tableName);
//console.log('result:', result);
if (result) {
return;
}
// 创建表
const create_table_user =
`CREATE TABLE ${tableName}
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name CHAR(50) NOT NULL,
age INT
);`
this.demoSqliteDB.db.exec(create_table_user);
}
/*
* Test data (sqlite)
*/
async addTestDataSqlite(data) {
//console.log("add data:", data);
let table = 'user';
await this.checkAndCreateTableSqlite(table);
const insert = this.demoSqliteDB.db.prepare(`INSERT INTO ${table} (name, age) VALUES (@name, @age)`);
insert.run(data);
return true;
}
/*
* Test data (sqlite)
*/
async delTestDataSqlite(name = '') {
//console.log("delete name:", name);
let table = 'user';
await this.checkAndCreateTableSqlite(table);
const delUser = this.demoSqliteDB.db.prepare(`DELETE FROM ${table} WHERE name = ?`);
delUser.run(name);
return true;
}
/*
* Test data (sqlite)
*/
async updateTestDataSqlite(name= '', age = 0) {
//console.log("update :", {name, age});
let table = 'user';
await this.checkAndCreateTableSqlite(table);
const updateUser = this.demoSqliteDB.db.prepare(`UPDATE ${table} SET age = ? WHERE name = ?`);
updateUser.run(age, name);
return true;
}
/*
* Test data (sqlite)
*/
async getTestDataSqlite(age = 0) {
//console.log("select :", {age});
let table = 'user';
await this.checkAndCreateTableSqlite(table);
const selectUser = this.demoSqliteDB.db.prepare(`SELECT * FROM ${table} WHERE age = @age`);
const users = selectUser.all({age: age});
//console.log("select users:", users);
return users;
}
/*
* all Test data (sqlite)
*/
async getAllTestDataSqlite() {
//console.log("select all user");
let table = 'user';
await this.checkAndCreateTableSqlite(table);
const selectAllUser = this.demoSqliteDB.db.prepare(`SELECT * FROM ${table} `);
const allUser = selectAllUser.all();
//console.log("select allUser:", allUser);
return allUser;
}
/*
* get data dir (sqlite)
*/
async getDataDir() {
const dir = this.demoSqliteDB.getStorageDir();
return dir;
}
/*
* set custom data dir (sqlite)
*/
async setCustomDataDir(dir) {
if (_.isEmpty(dir)) {
return;
}
// the absolute path of the db file
const dbFile = path.join(dir, this.sqliteFile);
const sqliteOptions = {
driver: 'sqlite',
default: {
timeout: 6000,
verbose: console.log
}
}
this.demoSqliteDB = Storage.connection(dbFile, sqliteOptions);
return;
}
}
SqlitedbService.toString = () => '[class SqlitedbService]';
module.exports = SqlitedbService;

View File

@ -0,0 +1,30 @@
'use strict';
const { Service } = require('ee-core');
/**
* effectservice层为单例
* @class
*/
class EffectService extends Service {
constructor(ctx) {
super(ctx);
}
/**
* test
*/
async test(args) {
let obj = {
status:'ok',
params: args
}
return obj;
}
}
EffectService.toString = () => '[class EffectService]';
module.exports = EffectService;

View File

@ -0,0 +1,35 @@
'use strict';
const { Service } = require('ee-core');
const Services = require('ee-core/services');
const Log = require('ee-core/log');
/**
* 示例服务service层为单例
* @class
*/
class ExampleService extends Service {
constructor(ctx) {
super(ctx);
}
/**
* test
*/
async test(args) {
let obj = {
status:'ok',
params: args
}
Log.info('ExampleService obj:', obj);
Services.get('framework').test('egg');
return obj;
}
}
ExampleService.toString = () => '[class ExampleService]';
module.exports = ExampleService;

View File

@ -0,0 +1,195 @@
'use strict';
const { Service } = require('ee-core');
const Log = require('ee-core/log');
const { ChildJob, ChildPoolJob } = require('ee-core/jobs');
const HttpClient = require('ee-core/httpclient');
const Ps = require('ee-core/ps');
/**
* framework
* @class
*/
class FrameworkService extends Service {
constructor(ctx) {
super(ctx);
// 在构造函数中初始化一些变量
this.myTimer = null;
this.myJob = new ChildJob();
this.myJobPool = new ChildPoolJob();
this.taskForJob = {};
}
/**
* test
*/
async test(args) {
let obj = {
status:'ok',
params: args
}
Log.info('FrameworkService obj:', obj);
return obj;
}
/**
* ipc通信(双向)
*/
bothWayMessage(type, content, event) {
// 前端ipc频道 channel
const channel = 'controller.framework.ipcSendMsg';
if (type == 'start') {
// 每隔1秒向前端页面发送消息
// 用定时器模拟
this.myTimer = setInterval(function(e, c, msg) {
let timeNow = Date.now();
let data = msg + ':' + timeNow;
e.reply(`${c}`, data)
}, 1000, event, channel, content)
return '开始了'
} else if (type == 'end') {
clearInterval(this.myTimer);
return '停止了'
} else {
return 'ohther'
}
}
/**
* 执行任务
*/
doJob(jobId, action, event) {
let res = {};
let oneTask;
const channel = 'controller.framework.timerJobProgress';
if (action == 'create') {
// 执行任务及监听进度
let eventName = 'job-timer-progress-' + jobId;
const timerTask = this.myJob.exec('./jobs/example/timer', {jobId});
timerTask.emitter.on(eventName, (data) => {
Log.info('[main-process] timerTask, from TimerJob data:', data);
// 发送数据到渲染进程
event.sender.send(`${channel}`, data)
})
// 执行任务及监听进度 异步
// myjob.execPromise('./jobs/example/timer', {jobId}).then(task => {
// task.emitter.on(eventName, (data) => {
// Log.info('[main-process] timerTask, from TimerJob data:', data);
// // 发送数据到渲染进程
// event.sender.send(`${channel}`, data)
// })
// });
res.pid = timerTask.pid;
this.taskForJob[jobId] = timerTask;
}
if (action == 'close') {
oneTask = this.taskForJob[jobId];
oneTask.kill();
event.sender.send(`${channel}`, {jobId, number:0, pid:0});
}
return res;
}
/**
* 创建pool
*/
doCreatePool(num, event) {
const channel = 'controller.framework.createPoolNotice';
this.myJobPool.create(num).then(pids => {
event.reply(`${channel}`, pids);
});
}
/**
* 通过进程池执行任务
*/
doJobByPool(jobId, action, event) {
let res = {};
const channel = 'controller.framework.timerJobProgress';
if (action == 'run') {
// 异步-执行任务及监听进度
this.myJobPool.runPromise('./jobs/example/timer', {jobId}).then(task => {
// 监听器名称唯一,否则会出现重复监听。
// 任务完成时,需要移除监听器,防止内存泄漏
let eventName = 'job-timer-progress-' + jobId;
task.emitter.on(eventName, (data) => {
Log.info('[main-process] [ChildPoolJob] timerTask, from TimerJob data:', data);
// 发送数据到渲染进程
event.sender.send(`${channel}`, data)
// 如果收到任务完成的消息,移除监听器
if (data.end) {
task.emitter.removeAllListeners(eventName);
}
});
res.pid = task.pid;
});
}
return res;
}
/**
* test
*/
monitorJob() {
setInterval(() => {
let jobPids = this.myJob.getPids();
let jobPoolPids = this.myJobPool.getPids();
Log.info(`[main-process] [monitorJob] jobPids: ${jobPids}, jobPoolPids: ${jobPoolPids}`);
}, 5000)
}
/**
* 上传到smms
*/
async uploadFileToSMMS(tmpFile) {
const res = {
code: 1000,
message: 'unknown error',
};
try {
const headersObj = {
'Content-Type': 'multipart/form-data',
'Authorization': 'aaaaaaaaaaaaa' // 请修改这个token用你自己的账号token
};
const url = 'https://sm.ms/api/v2/upload';
const hc = new HttpClient();
const response = await hc.request(url, {
method: 'POST',
headers: headersObj,
files: {
smfile: tmpFile,
},
dataType: 'json',
timeout: 15000,
});
const result = response.data;
if (Ps.isDev()) {
Log.info('[FrameworkService] [uploadFileToSMMS]: info result:%j', result);
}
if (result.code !== 'success') {
Log.error('[FrameworkService] [uploadFileToSMMS]: res error result:%j', result);
}
return result;
} catch (e) {
Log.error('[FrameworkService] [uploadFileToSMMS]: ERROR ', e);
}
return res;
}
}
FrameworkService.toString = () => '[class FrameworkService]';
module.exports = FrameworkService;

View File

@ -0,0 +1,138 @@
'use strict';
const { Service } = require('ee-core');
// 串口的功能
const { SerialPort } = require('serialport')
// 保存串口实例
var seriaPortIns = undefined;
// 这个是处理时间的库
var moment = require('moment')
// Modbus TCP
// create an empty modbus client
const ModbusRTU = require("modbus-serial")
var client = new ModbusRTU();
// open connection to a tcp line
// 创建Modbus TCP连接IP是15.18.200.23,端口502
// client.connectTCP("15.18.200.23", { port: 502 });
//-------
/**
* hardwareservice层为单例
* @class
*/
class HardwareService extends Service {
constructor(ctx) {
super(ctx);
}
/**
* 发送消息
* @param {*} seriaPort 串口实例
*/
sendSeriaPort(args) {
const port = seriaPortIns;
const msg = args.msg;
port.write(msg, 'hex')
console.log('测试发送消息' + msg);
}
/**
* 连接串口 只执行一次
* @param {*} options 串口参数
* @param {*} event 回调.
*/
connectSeriaPort(options, event) {
if (seriaPortIns != undefined) {
console.log("SerialPort is have");
return;
}
const channel = 'controller.hardware.connectSeriaPort';
const port = new SerialPort(options, (e) => {
console.log("SerialPort open");
console.log(e);
if (e === null) {
// 打开成功 把串口发送出去
let data2 = {
type: 'connect'
}
event.reply(`${channel}`, data2)
}
})
port.on('data', (data) => {
let data2 = {
type: 'received',
data: data
}
console.log(`Received data: ${data2}`)
event.reply(`${channel}`, data2)
})
setInterval(() => {
// console.log('setInterval')
if (!port.isOpen) {
// console.log('setInterval open')
port.open();
}
}, 1000)
port.on('close', () => {
let data2 = {
type: 'close'
}
console.log(`SerialPort close: ${data2}`)
event.reply(`${channel}`, data2)
})
port.on('error', (e) => {
let data2 = {
type: 'error'
}
console.log(`SerialPort error: ${e}`)
event.reply(`${channel}`, data2)
})
seriaPortIns = port;
}
/**
* test
*/
async test(args) {
let obj = {
status: 'ok',
params: args
}
return obj;
}
// 样例 ModbusTCP
tempModbusTCP() {
//http://momentjs.cn/ 时间库
//https://www.jianshu.com/p/50954625b158
// 读取非甲烷总烃的关于总烃、甲烷、NMHC这3个寄存器寄存器地址分别为22,25,28中的浓度
// 每隔5秒钟读取保持寄存器的值从寄存器地址22开始读取读10个寄存器到data数组中
setInterval(function () {
// 要连接后才能用
client.readHoldingRegisters(22, 10, function (err, data) {
// 获取当前时间
//moment.locale('zh-cn');
// console.log("----------------------------------------------------------------------");
// console.log("数据时间是:" + moment().format('YYYY年MM月DD日 HH时mm分ss秒'));
// console.log("总烃的浓度是:" + data.data[0] * 0.01 + "ppmV"); // 总烃浓度对应的寄存器地址为22
// console.log("CH4的浓度是:" + data.data[3] * 0.01 + "ppmV"); // CH4浓度对应的寄存器地址为22
// console.log("NHMC的浓度是:" + data.data[6] * 0.01 + "ppmV"); // NHMC浓度对应的寄存器地址为22
// console.log("----------------------------------------------------------------------");
//console.log(data.data);
});
}, 5000);
}
}
HardwareService.toString = () => '[class HardwareService]';
module.exports = HardwareService;

View File

@ -0,0 +1,87 @@
'use strict';
const { Service } = require('ee-core');
const { BrowserView, Notification } = require('electron');
const CoreWindow = require('ee-core/electron/window');
/**
* osservice层为单例
* @class
*/
class OsService extends Service {
constructor(ctx) {
super(ctx);
this.myBrowserView = null;
this.myNotification = null;
}
/**
* createBrowserView
*/
createBrowserView(contentUrl) {
// electron 实验性功能,慎用
const win = CoreWindow.getMainWindow();
this.myBrowserView = new BrowserView();
win.setBrowserView(this.myBrowserView);
this.myBrowserView.setBounds({
x: 300,
y: 170,
width: 650,
height: 400
});
this.myBrowserView.webContents.loadURL(contentUrl);
}
/**
* removeBrowserView
*/
removeBrowserView() {
// one
this.myBrowserView.webContents.loadURL('about:blank')
// two - electron 11 remove destroy()
// this.myBrowserView.webContents.destroy();
// three
// this.myBrowserView.webContents.forcefullyCrashRenderer()
// fore
// this.myBrowserView.webContents.close
}
/**
* createNotification
*/
createNotification(options, event) {
const channel = 'controller.os.sendNotification';
this.myNotification = new Notification(options);
if (options.clickEvent) {
this.myNotification.on('click', (e) => {
let data = {
type: 'click',
msg: '您点击了通知消息'
}
event.reply(`${channel}`, data)
});
}
if (options.closeEvent) {
this.myNotification.on('close', (e) => {
let data = {
type: 'close',
msg: '您关闭了通知消息'
}
event.reply(`${channel}`, data)
});
}
this.myNotification.show();
}
}
OsService.toString = () => '[class OsService]';
module.exports = OsService;

View File

@ -0,0 +1,14 @@
# https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@ -0,0 +1,2 @@
VITE_TITLE=""
VITE_GO_URL="http://localhost:8081"

View File

@ -0,0 +1,2 @@
VITE_TITLE=""
VITE_GO_URL="http://www.test.com"

6
frontend/.gitignore vendored 100644
View File

@ -0,0 +1,6 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
package-lock.json

105
frontend/index.html 100644
View File

@ -0,0 +1,105 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
<title></title>
<!-- 优化vue渲染未完成之前先加一个css动画 -->
<style>
#loadingPage {
background-color: #dedede;
font-size: 12px;
}
.base {
height: 9em;
left: 50%;
margin: -7.5em;
padding: 3em;
position: absolute;
top: 50%;
width: 9em;
transform: rotateX(45deg) rotateZ(45deg);
transform-style: preserve-3d;
}
.cube,
.cube:after,
.cube:before {
content: '';
float: left;
height: 3em;
position: absolute;
width: 3em;
}
/* Top */
.cube {
background-color: #06cf68;
position: relative;
transform: translateZ(3em);
transform-style: preserve-3d;
transition: .25s;
box-shadow: 13em 13em 1.5em rgba(0, 0, 0, 0.1);
animation: anim 1s infinite;
}
.cube:after {
background-color: #05a151;
transform: rotateX(-90deg) translateY(3em);
transform-origin: 100% 100%;
}
.cube:before {
background-color: #026934;
transform: rotateY(90deg) translateX(3em);
transform-origin: 100% 0;
}
.cube:nth-child(1) {
animation-delay: 0.05s;
}
.cube:nth-child(2) {
animation-delay: 0.1s;
}
.cube:nth-child(3) {
animation-delay: 0.15s;
}
.cube:nth-child(4) {
animation-delay: 0.2s;
}
.cube:nth-child(5) {
animation-delay: 0.25s;
}
.cube:nth-child(6) {
animation-delay: 0.3s;
}
.cube:nth-child(7) {
animation-delay: 0.35s;
}
.cube:nth-child(8) {
animation-delay: 0.4s;
}
.cube:nth-child(9) {
animation-delay: 0.45s;
}
@keyframes anim {
50% {
transform: translateZ(0.5em);
}
}
</style>
</head>
<body>
<div id="loadingPage">
<div class='base'>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
<div class='cube'></div>
</div>
</div>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,34 @@
{
"name": "ee",
"version": "1.0.0",
"scripts": {
"dev": "vite --host --port 17680",
"serve": "vite --host --port 17680",
"build-staging": "vite build --mode staging",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@ant-design/icons-vue": "^6.1.0",
"ant-design-vue": "2.2.6",
"axios": "^0.21.1",
"element-plus": "^2.4.0",
"socket.io-client": "^4.4.1",
"store2": "^2.13.2",
"vue": "^3.2.33",
"vue-router": "^4.0.14",
"vuex": "^4.0.2",
"xgplayer": "^2.31.6"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"@vue/compiler-sfc": "^3.2.33",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"postcss": "^8.4.13",
"postcss-pxtorem": "^6.0.0",
"terser": "^5.19.1",
"vite": "^4.4.4",
"vite-plugin-compression": "^0.5.1"
}
}

View File

@ -0,0 +1,28 @@
<template>
<router-view/>
</template>
<script>
import { ipc } from '@/utils/ipcRenderer';
import { ipcApiRoute } from '@/api/main';
// websocket
import websocket from './utils/websocket'
import { updateConfgJson } from "@/utils/api"
export default {
name: 'App',
setup() {
document.getElementById('loadingPage').remove()
},
mounted() {
// DOM
ipc.invoke(ipcApiRoute.getExResConfig, "config.json").then(res => {
const configJSON = JSON.parse(res);
updateConfgJson(configJSON)
websocket.initWebSocket(configJSON);
})
},
}
</script>
<style lang="less"></style>

View File

@ -0,0 +1,100 @@
/**
* 主进程与渲染进程通信频道定义
* Definition of communication channels between main process and rendering process
*/
const ipcApiRoute = {
// framework
test: 'controller.example.test',
checkForUpdater: 'controller.framework.checkForUpdater',
downloadApp: 'controller.framework.downloadApp',
jsondbOperation: 'controller.framework.jsondbOperation',
sqlitedbOperation: 'controller.framework.sqlitedbOperation',
uploadFile: 'controller.framework.uploadFile',
checkHttpServer: 'controller.framework.checkHttpServer',
doHttpRequest: 'controller.framework.doHttpRequest',
doSocketRequest: 'controller.framework.doSocketRequest',
ipcInvokeMsg: 'controller.framework.ipcInvokeMsg',
ipcSendSyncMsg: 'controller.framework.ipcSendSyncMsg',
ipcSendMsg: 'controller.framework.ipcSendMsg',
startJavaServer: 'controller.framework.startJavaServer',
closeJavaServer: 'controller.framework.closeJavaServer',
someJob: 'controller.framework.someJob',
timerJobProgress: 'controller.framework.timerJobProgress',
createPool: 'controller.framework.createPool',
createPoolNotice: 'controller.framework.createPoolNotice',
someJobByPool: 'controller.framework.someJobByPool',
hello: 'controller.framework.hello',
openSoftware: 'controller.framework.openSoftware',
// 获取ex资源目录下的配置文件 参数唯文件名
getExResConfig: 'controller.framework.getExResConfig',
// 发送socket 消息
sendTcpSocket: 'controller.framework.sendTcpSocket',
// 串口 参数为 整个串口参数
connectSeriaPort: 'controller.framework.connectSeriaPort',
// 发送串口消息 参数为 msg
sendSeriaPort: 'controller.framework.sendSeriaPort',
// os
messageShow: 'controller.os.messageShow',
messageShowConfirm: 'controller.os.messageShowConfirm',
selectFolder: 'controller.os.selectFolder',
selectPic: 'controller.os.selectPic',
openDirectory: 'controller.os.openDirectory',
loadViewContent: 'controller.os.loadViewContent',
removeViewContent: 'controller.os.removeViewContent',
createWindow: 'controller.os.createWindow',
getWCid: 'controller.os.getWCid',
sendNotification: 'controller.os.sendNotification',
initPowerMonitor: 'controller.os.initPowerMonitor',
getScreen: 'controller.os.getScreen',
autoLaunch: 'controller.os.autoLaunch',
setTheme: 'controller.os.setTheme',
getTheme: 'controller.os.getTheme',
// 获取mac
getAllMac: 'controller.os.getAllMac',
// 获取系统信息
getOSMessage: 'controller.os.getOSMessage',
// 声音设置
deviceLoudness: 'controller.os.deviceLoudness',
//deviceRestart 重启
deviceRestart: 'controller.os.deviceRestart',
// 设备关机
deviceShutdown: 'controller.os.deviceShutdown',
// 杀掉进程 参数为进程名称, 模糊搜索,注意可能杀错
deviceKillName: 'controller.os.deviceKillName',
// 启动程序 参数为程序目录绝对地址
deviceStarExe: 'controller.os.deviceStarExe',
// hardware
getPrinterList: 'controller.hardware.getPrinterList',
print: 'controller.hardware.print',
printStatus: 'controller.hardware.printStatus',
// effect
selectFile: 'controller.effect.selectFile',
loginWindow: 'controller.effect.loginWindow',
restoreWindow: 'controller.effect.restoreWindow',
// cross
crossInfo: 'controller.cross.info',
getCrossUrl: 'controller.cross.getUrl',
killCrossServer: 'controller.cross.killServer',
createCrossServer: 'controller.cross.createServer',
requestApi: 'controller.cross.requestApi',
}
/**
* 自定义频道
* custom chennel
*/
const specialIpcRoute = {
appUpdater: 'app.updater', // updater channel
window1ToWindow2: 'window1-to-window2', // windows channel
window2ToWindow1: 'window2-to-window1', // windows channel
}
export {
ipcApiRoute, specialIpcRoute
}

View File

@ -0,0 +1,16 @@
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
height: 100%;
}
/* 滚动条 */
::-webkit-scrollbar{width:8px;height:4px}
::-webkit-scrollbar-button{width:10px;height:0}
::-webkit-scrollbar-track{background:0 0}
::-webkit-scrollbar-thumb{background:#E6FFEE;-webkit-transition:.3s;transition:.3s}
::-webkit-scrollbar-thumb:hover{background-color:#07C160}
::-webkit-scrollbar-thumb:active{background-color:#07C160}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,17 @@
@import 'ant-design-vue/dist/antd.less';
// 可自定义主题颜色
@primary-color: #07C160; // 全局主色
@link-color: #1890ff; // 链接色
@success-color: #52c41a; // 成功色
@warning-color: #faad14; // 警告色
@error-color: #f5222d; // 错误色
@font-size-base: 14px; // 主字号
@heading-color: rgba(0, 0, 0, 0.85); // 标题色
@text-color: rgba(0, 0, 0, 0.65); // 主文本色
@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色
@disabled-color: rgba(0, 0, 0, 0.25); // 失效色
@border-radius-base: 4px; // 组件/浮层圆角
@border-color-base: #dce3e8; // 边框色
@box-shadow-base: 0 2px 8px rgba(0, 0, 0, 0.15); // 浮层阴影

View File

@ -0,0 +1,18 @@
import { createFromIconfontCN } from '@ant-design/icons-vue'
import { h } from 'vue'
const IconFont = createFromIconfontCN({
scriptUrl: 'https://at.alicdn.com/t/font_2456157_4ovzopz659q.js',
extraCommonProps: {
type: 'icon-fengche',
style: {
fontSize: '18px',
},
},
})
const DynamicIconFont = props => {
return h(IconFont, { type: props.type || 'icon-fengche' })
}
export default DynamicIconFont

View File

@ -0,0 +1,12 @@
import iconFont from './iconFont'
const modules = import.meta.globEager('./*.vue')
const map = {}
Object.keys(modules).forEach(file => {
const modulesName = file.replace('./', '').replace('.vue', '')
map[modulesName] = modules[file].default
})
const globalComponents = {
...map,
iconFont,
}
export default globalComponents

View File

@ -0,0 +1,126 @@
<template>
<a-layout id="app-layout-sider">
<a-layout-sider
v-model="collapsed"
theme="light"
class="layout-sider"
width="100"
>
<div class="logo">
<img class="pic-logo" src="~@/assets/logo.png">
</div>
<a-menu
class="menu-item"
theme="light"
mode="inline"
:selectedKeys="[current]"
@click="menuHandle"
>
<a-menu-item v-for="(menuInfo, index) in menu" :key="index">
<icon-font :type="menuInfo.icon" />
{{ menuInfo.title }}
</a-menu-item>
</a-menu>
</a-layout-sider>
<a-layout>
<a-layout-content class="layout-content">
<router-view />
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script>
export default {
name: 'AppSider',
data() {
return {
collapsed: true,
current: 'menu_1',
menu: {
'menu_1' : {
icon: 'icon-fengche',
title: '框架',
pageName: 'Framework',
params: {
// test: 'hello'
},
},
'menu_2' : {
icon: 'icon-niudan',
title: '系统',
pageName: 'Os',
params: {},
},
'menu_3' : {
icon: 'icon-xiangji',
title: '硬件',
pageName: 'Hardware',
params: {},
},
'menu_4' : {
icon: 'icon-liuxing',
title: '特效',
pageName: 'Effect',
params: {},
},
'menu_5' : {
icon: 'icon-gouwu',
title: 'cross',
pageName: 'Cross',
params: {},
},
}
};
},
created () {
},
mounted () {
this.menuHandle()
},
methods: {
menuHandle (e) {
console.log('sider menu e:', e);
this.current = e ? e.key : this.current;
console.log('sider menu current:', this.current);
const linkInfo = this.menu[this.current]
console.log('[home] load linkInfo:', linkInfo);
this.$router.push({ name: linkInfo.pageName, params: linkInfo.params})
},
changeMenu(e) {
console.log('sider menu e:', e);
//this.current = e.key;
}
},
};
</script>
<style lang="less" scoped>
//
#app-layout-sider {
height: 100%;
.logo {
border-bottom: 1px solid #e8e8e8;
}
.pic-logo {
height: 32px;
//background: rgba(139, 137, 137, 0.2);
margin: 10px;
}
.layout-sider {
border-top: 1px solid #e8e8e8;
border-right: 1px solid #e8e8e8;
}
.menu-item {
.ant-menu-item {
background-color: #fff;
margin-top: 0px;
margin-bottom: 0px;
padding: 0 0px !important;
}
}
.layout-content {
//background-color: #fff;
}
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<a-layout id="app-menu">
<a-layout-sider
theme="light"
class="layout-sider"
>
<a-menu
theme="light"
mode="inline"
:selectedKeys="[current]"
@click="changeMenu">
<a-menu-item v-for="(menuInfo, subIndex) in menu" :key="subIndex">
<router-link :to="{ name: menuInfo.pageName, params: menuInfo.params}">
<span>{{ menuInfo.title }}</span>
</router-link>
</a-menu-item>
</a-menu>
</a-layout-sider>
<a-layout>
<a-layout-content>
<router-view />
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script>
// import { reactive } from 'vue';
import subMenu from '@/router/subMenu';
export default {
// setup() {
// const state = reactive({
// selectedKeys: ['menu_100'],
// });
// const handleClick = e => {
// state.selectedKeys = [e.key];
// };
// return {
// state,
// handleClick,
// };
// },
props: {
id: {
type: String,
default: ''
}
},
data() {
return {
menu:{},
//selectedKeys: ['menu_100'],
current: 'menu_100',
keys: []
};
},
watch: {
id: function () {
console.log('watch id ----- ', this.id);
this.current = 'menu_100';
this.menuHandle();
},
},
created () {
},
mounted () {
this.menuHandle();
},
methods: {
menuHandle () {
//
//console.log('params:', this.$route);
console.log('menu ------ id:', this.id);
this.menu = subMenu[this.id];
const linkInfo = this.menu[this.current];
this.$router.push({ name: linkInfo.pageName, params: linkInfo.params});
},
changeMenu(e) {
console.log('changeMenu e:', e);
this.current = e.key;
}
}
};
</script>
<style lang="less" scoped>
#app-menu {
height: 100%;
text-align: center;
.layout-sider {
border-top: 1px solid #e8e8e8;
border-right: 1px solid #e8e8e8;
background-color: #FAFAFA;
overflow: auto;
}
}
</style>

View File

@ -0,0 +1,7 @@
import AppSider from '@/layouts/AppSider'
import Menu from '@/layouts/Menu'
export {
AppSider,
Menu
}

View File

@ -0,0 +1,32 @@
import * as AntIcon from '@ant-design/icons-vue';
import Antd from 'ant-design-vue';
import { createApp } from 'vue';
import App from './App.vue';
import './assets/global.less';
import './assets/theme.less';
import components from './components/global';
import Router from './router/index';
// 导入ele
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.config.productionTip = false
// 挂载ele
app.use(ElementPlus)
// components
for (const i in components) {
app.component(i, components[i])
}
// icon
for (const i in AntIcon) {
const whiteList = ['createFromIconfontCN', 'getTwoToneColor', 'setTwoToneColor', 'default']
if (!whiteList.includes(i)) {
app.component(i, AntIcon[i])
}
}
app.use(Antd).use(Router).mount('#app')

View File

@ -0,0 +1,9 @@
import { createRouter, createWebHashHistory } from 'vue-router'
import routerMap from './routerMap'
const Router = createRouter({
history: createWebHashHistory(),
routes: routerMap,
})
export default Router

View File

@ -0,0 +1,201 @@
/**
* 基础路由
* @type { *[] }
*/
const constantRouterMap = [
{
path: '/',
component: () => import('@/layouts/AppSider.vue'),
children: [
{
path: '/framework',
name: 'Framework',
component: () => import('@/layouts/Menu.vue'),
props: { id: 'framework' },
//props: true,
redirect: { name: 'FrameworkSocketIpc' },
children: [
{
path: '/framework/socket/ipc',
name: 'FrameworkSocketIpc',
component: () => import('@/views/framework/socket/Ipc.vue')
},
{
path: '/framework/socket/httpserver',
name: 'FrameworkSocketHttpServer',
component: () => import('@/views/framework/socket/HttpServer.vue')
},
{
path: '/framework/socket/socketserver',
name: 'FrameworkSocketSocketServer',
component: () => import('@/views/framework/socket/SocketServer.vue')
},
{
path: '/framework/jsondb/index',
name: 'FrameworkJsonDBIndex',
component: () => import('@/views/framework/jsondb/Index.vue')
},
{
path: '/framework/sqlitedb/index',
name: 'FrameworkSqliteDBIndex',
component: () => import('@/views/framework/sqlitedb/Index.vue')
},
{
path: '/framework/jobs/index',
name: 'FrameworkJobsIndex',
component: () => import('@/views/framework/jobs/Index.vue')
},
{
path: '/framework/updater/index',
name: 'FrameworkUpdaterIndex',
component: () => import('@/views/framework/updater/Index.vue')
},
{
path: '/framework/software/index',
name: 'FrameworkSoftwareIndex',
component: () => import('@/views/framework/software/Index.vue')
},
{
path: '/framework/java/index',
name: 'FrameworkJavaIndex',
component: () => import('@/views/framework/java/Index.vue')
},
{
path: '/framework/testapi/index',
name: 'FrameworkTestApiIndex',
component: () => import('@/views/framework/testapi/Index.vue')
},
]
},
{
path: '/os',
name: 'Os',
component: () => import('@/layouts/Menu.vue'),
props: { id: 'os' },
redirect: { name: 'OsFileIndex' },
children: [
{
path: '/os/file/index',
name: 'OsFileIndex',
component: () => import('@/views/os/file/Index.vue')
},
{
path: '/os/file/pic',
name: 'OsFilePic',
component: () => import('@/views/os/file/Pic.vue')
},
{
path: '/os/windowview/index',
name: 'OsWindowViewIndex',
component: () => import('@/views/os/windowview/Index.vue')
},
{
path: '/os/window/index',
name: 'OsWindowIndex',
component: () => import('@/views/os/window/Index.vue')
},
{
path: '/os/notification/index',
name: 'OsNotificationIndex',
component: () => import('@/views/os/notification/Index.vue')
},
{
path: '/os/powermonitor/index',
name: 'OsPowerMonitorIndex',
component: () => import('@/views/os/powermonitor/Index.vue')
},
{
path: '/os/screen/index',
name: 'OsScreenIndex',
component: () => import('@/views/os/screen/Index.vue')
},
{
path: '/os/theme/index',
name: 'OsThemeIndex',
component: () => import('@/views/os/theme/Index.vue')
},
{
path: '/os/system/index',
name: 'OsSystemIndex',
component: () => import('@/views/os/system/Index.vue')
},
]
},
{
path: '/hardware',
name: 'Hardware',
component: () => import('@/layouts/Menu.vue'),
props: { id: 'hardware' },
redirect: { name: 'HardwarePrinterIndex' },
children: [
{
path: '/hardware/printer/index',
name: 'HardwarePrinterIndex',
component: () => import('@/views/hardware/printer/Index.vue')
},
]
},
{
path: '/effect',
name: 'Effect',
component: () => import('@/layouts/Menu.vue'),
props: { id: 'effect' },
redirect: { name: 'EffectVideoIndex' },
children: [
{
path: '/effect/video/index',
name: 'EffectVideoIndex',
component: () => import('@/views/effect/video/Index.vue')
},
{
path: '/effect/login/index',
name: 'EffectLoginIndex',
component: () => import('@/views/effect/login/Index.vue')
}
]
},
{
path: '/cross',
name: 'Cross',
component: () => import('@/layouts/Menu.vue'),
props: { id: 'cross' },
redirect: { name: 'CrossGoIndex' },
children: [
{
path: '/cross/go/index',
name: 'CrossGoIndex',
component: () => import('@/views/cross/go/Index.vue')
},
{
path: '/cross/java/index',
name: 'CrossJavaIndex',
component: () => import('@/views/cross/java/Index.vue')
},
{
path: '/cross/python/index',
name: 'CrossPythonIndex',
component: () => import('@/views/cross/python/Index.vue')
},
]
},
]
},
{
path: '/special',
children: [
{
path: 'subwindow',
name: 'SpecialSubwindowIpc',
component: () => import('@/views/os/subwindow/Ipc.vue')
},
{
path: '/login',
name: 'SpecialLoginWindow',
component: () => import('@/views/effect/login/Window.vue')
},
]
},
]
export default constantRouterMap

View File

@ -0,0 +1,153 @@
/**
* 子菜单
*/
export default {
framework: {
'menu_100' : {
icon: 'profile',
title: '通信',
pageName: 'FrameworkSocketIpc',
params: {}
},
'menu_101' : {
icon: 'profile',
title: 'http服务',
pageName: 'FrameworkSocketHttpServer',
params: {}
},
'menu_102' : {
icon: 'profile',
title: 'socket服务',
pageName: 'FrameworkSocketSocketServer',
params: {}
},
'menu_103' : {
icon: 'profile',
title: 'json数据库',
pageName: 'FrameworkJsonDBIndex',
params: {}
},
'menu_104' : {
icon: 'profile',
title: 'sqlite数据库',
pageName: 'FrameworkSqliteDBIndex',
params: {}
},
'menu_105' : {
icon: 'profile',
title: '任务',
pageName: 'FrameworkJobsIndex',
params: {}
},
'menu_106' : {
icon: 'profile',
title: '自动更新',
pageName: 'FrameworkUpdaterIndex',
params: {}
},
'menu_107' : {
icon: 'profile',
title: '软件调用',
pageName: 'FrameworkSoftwareIndex',
params: {}
},
'menu_109' : {
icon: 'profile',
title: '测试',
pageName: 'FrameworkTestApiIndex',
params: {}
},
},
os: {
'menu_100' : {
icon: 'profile',
title: '文件',
pageName: 'OsFileIndex',
params: {}
},
'menu_101' : {
icon: 'profile',
title: '视图',
pageName: 'OsWindowViewIndex',
params: {}
},
'menu_102' : {
icon: 'profile',
title: '窗口',
pageName: 'OsWindowIndex',
params: {}
},
'menu_103' : {
icon: 'profile',
title: '桌面通知',
pageName: 'OsNotificationIndex',
params: {}
},
'menu_104' : {
icon: 'profile',
title: '电源监控',
pageName: 'OsPowerMonitorIndex',
params: {}
},
'menu_105' : {
icon: 'profile',
title: '屏幕信息',
pageName: 'OsScreenIndex',
params: {}
},
'menu_106' : {
icon: 'profile',
title: '系统主题',
pageName: 'OsThemeIndex',
params: {}
},
'menu_110' : {
icon: 'profile',
title: '图片',
pageName: 'OsFilePic',
params: {}
},
},
hardware: {
'menu_100' : {
icon: 'profile',
title: '打印机',
pageName: 'HardwarePrinterIndex',
params: {}
}
},
effect: {
'menu_100' : {
icon: 'profile',
title: '视频播放器',
pageName: 'EffectVideoIndex',
params: {}
},
'menu_110' : {
icon: 'profile',
title: '登录',
pageName: 'EffectLoginIndex',
params: {}
}
},
cross: {
'menu_100' : {
icon: 'profile',
title: 'go服务',
pageName: 'CrossGoIndex',
params: {}
},
'menu_110' : {
icon: 'profile',
title: 'java服务',
pageName: 'CrossJavaIndex',
params: {}
},
'menu_120' : {
icon: 'profile',
title: 'python服务',
pageName: 'CrossPythonIndex',
params: {}
},
},
}

View File

@ -0,0 +1,77 @@
import axios from 'axios';
const apiBaseUrl = 'http://127.0.0.1:8086';
const webSocketUrl = 'ws://127.0.0.1:8086';
const apiService = axios.create({
baseURL: apiBaseUrl, // 设置基本URL
timeout: 5000, // 设置超时时间
});
// 全局设备配置文件的json
var GlobalConfig = {
};
// 请求拦截器
apiService.interceptors.request.use(
config => {
// 在发送请求之前做一些处理,例如添加请求头等
// 如果需要更详细的可以定义一个字符串的
config.headers["deviceType"] = GlobalConfig.deviceType;
return config;
},
error => {
// 请求错误时的处理
console.error(error);
return Promise.reject(error);
}
);
// 响应拦截器
apiService.interceptors.response.use(
response => {
// 对响应数据进行处理
return response;
},
error => {
// 响应错误时的处理
console.error(error);
return Promise.reject(error);
}
);
// 配置的更改
export function updateConfgJson(config){
GlobalConfig = config;
console.log('读取配置文件到内存');
console.log(GlobalConfig)
}
// 全局的config 配置, 频繁使用,可以用此函数获取,内存中的数据
export function getConfgJson(){
return GlobalConfig;
}
// todo: 可以直接导入到其他函数使用 也可以在此处定义函数后直接使用
/**
*
* api.get("/coreControl/deviceClient/creditCardHome?key=111&deptId=101").then(response=>{
console.log(response);
}).catch(error => {
console.error(error);
});
*
*
*/
/**
* 设备首页的接口, 根据设备类型,进行不同的修改url
* @returns
*/
export function getDeviceHome() {
return apiService({
url: '/coreControl/deviceClient/creditCardHome?key=' + GlobalConfig.deviceId +"&deptId" + GlobalConfig.deptId,
method: 'get'
})
}
export {
apiService, apiBaseUrl, webSocketUrl
};

View File

@ -0,0 +1,27 @@
export default [
{ name: '对话框', type: 'icon-duihuakuang' },
{ name: '闹钟', type: 'icon-naozhong' },
{ name: '笑脸', type: 'icon-xiaolian' },
{ name: 'ok', type: 'icon-ok' },
{ name: '风车', type: 'icon-fengche' },
{ name: '汗颜', type: 'icon-hanyan' },
{ name: '相机', type: 'icon-xiangji' },
{ name: '礼物', type: 'icon-liwu' },
{ name: '礼花', type: 'icon-lihua' },
{ name: '扭蛋', type: 'icon-niudan' },
{ name: '流星', type: 'icon-liuxing' },
{ name: '风筝', type: 'icon-fengzheng' },
{ name: '蛋糕', type: 'icon-dangao' },
{ name: '泡泡', type: 'icon-paopao' },
{ name: '购物', type: 'icon-gouwu' },
{ name: '饮料', type: 'icon-yinliao' },
{ name: '云彩', type: 'icon-yuncai' },
{ name: '彩铅', type: 'icon-caiqian' },
{ name: '纸飞机', type: 'icon-zhifeiji' },
{ name: '点赞', type: 'icon-dianzan' },
{ name: '煎蛋', type: 'icon-jiandan' },
{ name: '小熊', type: 'icon-xiaoxiong' },
{ name: '花', type: 'icon-hua' },
{ name: '眼睛', type: 'icon-yanjing' },
]

View File

@ -0,0 +1,33 @@
const Renderer = (window.require && window.require('electron')) || window.electron || {};
/**
* ipc
* 官方api说明https://www.electronjs.org/zh/docs/latest/api/ipc-renderer
*
* 属性/方法
* ipc.invoke(channel, param) - 发送异步消息invoke/handle 模型
* ipc.sendSync(channel, param) - 发送同步消息send/on 模型
* ipc.on(channel, listener) - 监听 channel, 当新消息到达调用 listener
* ipc.once(channel, listener) - 添加一次性 listener 函数
* ipc.removeListener(channel, listener) - 为特定的 channel 从监听队列中删除特定的 listener 监听者
* ipc.removeAllListeners(channel) - 移除所有的监听器当指定 channel 时只移除与其相关的所有监听器
* ipc.send(channel, ...args) - 通过channel向主进程发送异步消息
* ipc.postMessage(channel, message, [transfer]) - 发送消息到主进程
* ipc.sendTo(webContentsId, channel, ...args) - 通过 channel 发送消息到带有 webContentsId 的窗口
* ipc.sendToHost(channel, ...args) - 消息会被发送到 host 页面上的 <webview> 元素
*/
/**
* ipc
*/
const ipc = Renderer.ipcRenderer || undefined;
/**
* 是否为EE环境
*/
const isEE = ipc ? true : false;
export {
Renderer, ipc, isEE
};

View File

@ -0,0 +1,193 @@
// import { ipcApiRoute, specialIpcRoute } from "@/api/main";
// import vueMain from '../main'
import { ipc } from '@/utils/ipcRenderer';
import { ipcApiRoute } from '@/api/main';
import { webSocketUrl } from '@/utils/api'
var isOpen = false;
var authKey = ''
var socket = undefined
var socketHeartTimer = undefined
// 定时更新设备信息
var socketHeartTimer2 = undefined
// 用变量标记是否更新设备信息
var socketUpdateInfoCount = 0;
// 设备信息
var deviceInfo = {
volume: 0,
hostname: ''
};
var configJSON = null;
// 发送设备信息的间隔时间 s
const timerInterval = 10;
//屏幕设备类型IcbcDeviceType,1反诈屏 2互动屏 3弧形屏 4科技之眼 5光电玻璃 6 logo 设备类型 7 VR眼睛电脑
function initWebSocket(config) {
configJSON = config;
const wsUri = webSocketUrl+'/client/websocketServer' + '?deviceKey=' + configJSON.deviceId;
// const wsUri = 'ws://127.0.0.1:8080/resource/websocket' + '?deviceKey=' + e;
close();
socket = new WebSocket(wsUri)//这里面的this都指向vue
socket.onerror = webSocketOnError;
socket.onmessage = webSocketOnMessage;
socket.onclose = closeWebsocket;
socket.onopen = openWebSocket;
if (socketHeartTimer != undefined) {
clearInterval(socketHeartTimer);
}
if (socketHeartTimer2 != undefined){
clearInterval(socketHeartTimer2);
}
socketHeartTimer = setInterval(() => {
if (isOpen == false) {
console.log('重新连接' + isOpen)
initWebSocket(configJSON);
} else {
console.log('socket 心跳' + isOpen);
const msg = {
type: "heartbeat",// 心跳
content: "心跳",
deviceKey: configJSON.deviceId,
deviceType: configJSON.deviceType,
}
socket.send(JSON.stringify(msg));
}
}, 6000);
socketHeartTimer2 = setInterval(() => {
ipc.invoke(ipcApiRoute.getOSMessage, {}).then(res => {
// 判断数据不相同 提前更新掉
if ( deviceInfo.volume != res.volume || deviceInfo.hostname != res.hostname){
deviceInfo = res;
sendDeviceInfo();
socketUpdateInfoCount = 0;
return;
}
deviceInfo = res;
socketUpdateInfoCount = socketUpdateInfoCount+1;
if (socketUpdateInfoCount >= timerInterval){
sendDeviceInfo();
socketUpdateInfoCount = 0;
}
})
}, 1000);
}
// 发送设备信息
function sendDeviceInfo(){
const msg = {
type: "device_info_update",// 设备信息更新
content: deviceInfo,
deviceKey: configJSON.deviceId,
deviceType: configJSON.deviceType,
deptId: configJSON.deptId
}
socket.send(JSON.stringify(msg));
}
function webSocketOnError(e) {
// ElementUI.Notification({
// title: '',
// message: "WebSocket连接发生错误" + e,
// type: 'error',
// duration: 0,
// });
}
// socket 连接打开
function openWebSocket(e) {
console.log('socket 连接')
isOpen = true;
}
function webSocketOnMessage(e) {
const data = JSON.parse(e.data);
console.log('收到socket 消息')
console.log(data);
// 抛出消息, 全局控制消息直接处理
switch(data.type){
case 'heartbeat':
return;
case 'serve_heartbeat':
break
case 'device_shutdown':
ipc.invoke(ipcApiRoute.deviceShutdown, {}).then(res => {
})
return;
case 'device_restart':
ipc.invoke(ipcApiRoute.deviceRestart, {}).then(res => {
})
return;
case 'device_play_video':
break;
case 'device_update_data':
break;
case 'scan_qr_game_start':
break;
case 'game_start_ok':
break;
case 'game_progress':
break;
case 'game_result_post':
break;
case 'device_info_update':
break;
case 'device_volume_update':
console.log("声音设置")
console.log(data.content);
ipc.invoke(ipcApiRoute.deviceLoudness, {value:data.content}).then(res => {
console.log('返回声音设置结果'+res);
})
return;
default:
break
}
window.dispatchEvent(new CustomEvent('onMessageWS', {
detail: {
data: data
}
}))
// var event = document.createEvent("HTMLEvents");
// event.initEvent("onMessageWS", true, true);
// window.dispatchEvent(event);
}
// 关闭websiocket
function closeWebsocket() {
console.log('连接已关闭...')
// clearTimeout(socketHeartTimer);
isOpen = false;
}
function close() {
if (socket != undefined) {
socket.close() // 关闭 websocket
socket.onclose = function (e) {
console.log(e)//监听关闭事件
console.log('关闭')
}
}
socket = undefined;
isOpen = false;
}
function webSocketSend(agentData) {
socket.send(agentData);
}
export default {
initWebSocket, close, webSocketSend
}

View File

@ -0,0 +1,117 @@
<template>
<div id="app-cross-go">
<div class="one-block-1">
<span>
1. 基础控制
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="create()"> </a-button>
<a-button @click="getUrl()"> </a-button>
<a-button @click="kill()"> kill </a-button>
<a-button @click="info()"> test </a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
2. 发送http请求
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="request(1)"> </a-button>
<a-button @click="request(2)"> </a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
3. 多个服务
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="create()"> </a-button>
<a-button @click="killAll()"> kill </a-button>
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import axios from 'axios';
export default {
data() {
return {
type: 1,
serverUrl: ''
};
},
methods: {
info() {
ipc.invoke(ipcApiRoute.crossInfo, {}).then(res => {
console.log('res:', res);
})
},
getUrl() {
ipc.invoke(ipcApiRoute.getCrossUrl, {name: 'goapp'}).then(url => {
this.serverUrl = url;
this.$message.info(`服务地址: ${url}`);
})
},
kill() {
// name name
ipc.invoke(ipcApiRoute.killCrossServer, {type: 'one', name: 'goapp'})
},
killAll() {
ipc.invoke(ipcApiRoute.killCrossServer, {type: 'all', name: 'goapp'})
},
create() {
ipc.invoke(ipcApiRoute.createCrossServer, { program: 'go' })
},
request(type) {
if (type == 1 && this.serverUrl == "") {
this.$message.info("请先获取服务地址");
return
}
if (type == 1) {
const testApi = this.serverUrl + '/api/hello';
const cfg = {
method: 'get',
url: testApi,
params: { id: '111'},
timeout: 1000,
}
axios(cfg).then(res => {
console.log('res:', res);
const data = res.data.data || null;
this.$message.info(`服务返回: ${data}`);
})
} else {
ipc.invoke(ipcApiRoute.requestApi, {name: 'goapp', urlPath: '/api/hello'}).then(res => {
console.log('res:', res);
const data = res.data || null;
this.$message.info(`服务返回: ${data}`);
})
}
}
}
};
</script>
<style lang="less" scoped>
#app-cross-go {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,118 @@
<template>
<div id="app-cross-java">
<div class="one-block-1">
<span>
1. 基础控制
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="create()"> </a-button>
<a-button @click="getUrl()"> </a-button>
<a-button @click="kill()"> kill </a-button>
<a-button @click="info()"> </a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
2. 发送http请求
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="request(1)"> </a-button>
<a-button @click="request(2)"> </a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
3. 多个服务
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="create()"> </a-button>
<a-button @click="killAll()"> kill </a-button>
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import axios from 'axios';
export default {
data() {
return {
type: 1,
serverUrl: ''
};
},
methods: {
info() {
ipc.invoke(ipcApiRoute.crossInfo, {}).then(res => {
console.log('res:', res);
})
},
getUrl() {
ipc.invoke(ipcApiRoute.getCrossUrl, {name: 'javaapp'}).then(url => {
this.serverUrl = url;
this.$message.info(`服务地址: ${url}`);
})
},
kill() {
// name name
ipc.invoke(ipcApiRoute.killCrossServer, {type: 'one', name: 'javaapp'})
},
killAll() {
ipc.invoke(ipcApiRoute.killCrossServer, {type: 'all', name: 'javaapp'})
},
create() {
ipc.invoke(ipcApiRoute.createCrossServer, { program: 'java' })
},
request(type) {
if (type == 1 && this.serverUrl == "") {
this.$message.info("请先获取服务地址");
return
}
if (type == 1) {
const testApi = this.serverUrl + '/test1/get';
const cfg = {
method: 'get',
url: testApi,
params: { id: '1111111'},
timeout: 1000,
}
axios(cfg).then(res => {
console.log('res:', res);
const data = res.data || null;
this.$message.info(`服务返回: ${data}`);
})
} else {
ipc.invoke(ipcApiRoute.requestApi, {name: 'javaapp', urlPath: '/test1/get', params: { id: '1111111'}}).then(res => {
console.log('res:', res);
const data = res || null;
this.$message.info(`服务返回: ${data}`);
})
}
}
}
};
</script>
<style lang="less" scoped>
#app-cross-java {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,117 @@
<template>
<div id="app-cross-python">
<div class="one-block-1">
<span>
1. 基础控制
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="create()"> </a-button>
<a-button @click="getUrl()"> </a-button>
<a-button @click="kill()"> kill </a-button>
<a-button @click="info()"> test </a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
2. 发送http请求
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="request(1)"> </a-button>
<a-button @click="request(2)"> </a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
3. 多个服务
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="create()"> </a-button>
<a-button @click="killAll()"> kill all </a-button>
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import axios from 'axios';
export default {
data() {
return {
type: 1,
serverUrl: ''
};
},
methods: {
info() {
ipc.invoke(ipcApiRoute.crossInfo, {}).then(res => {
console.log('res:', res);
})
},
getUrl() {
ipc.invoke(ipcApiRoute.getCrossUrl, {name: 'pyapp'}).then(url => {
this.serverUrl = url;
this.$message.info(`服务地址: ${url}`);
})
},
kill() {
// name name
ipc.invoke(ipcApiRoute.killCrossServer, {type: 'one', name: 'pyapp'})
},
killAll() {
ipc.invoke(ipcApiRoute.killCrossServer, {type: 'all', name: 'pyapp'})
},
create() {
ipc.invoke(ipcApiRoute.createCrossServer, { program: 'python' })
},
request(type) {
if (type == 1 && this.serverUrl == "") {
this.$message.info("请先获取服务地址");
return
}
if (type == 1) {
const testApi = this.serverUrl + '/api/hello';
const cfg = {
method: 'get',
url: testApi,
params: { id: '111'},
timeout: 1000,
}
axios(cfg).then(res => {
console.log('res:', res);
const data = res.data || null;
this.$message.info(`服务返回: ${JSON.stringify(data)}`);
})
} else {
ipc.invoke(ipcApiRoute.requestApi, {name: 'pyapp', urlPath: '/api/hello'}).then(res => {
console.log('res:', res);
const data = res || null;
this.$message.info(`服务返回: ${JSON.stringify(data)}`);
})
}
}
}
};
</script>
<style lang="less" scoped>
#app-cross-python {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<div id="effect-login-index">
<div class="one-block-1">
<span>
1. 登录
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="loginWindow()"></a-button>
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
};
},
methods: {
loginWindow () {
this.$router.push({ name: 'SpecialLoginWindow', params: {}});
ipc.invoke(ipcApiRoute.loginWindow, {width: 400, height: 300}).then(r => {
//
})
},
}
};
</script>
<style lang="less" scoped>
#effect-login-index {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<div id="effect-login-window">
<div class="block-1">
<a v-if="!loading" @click="login">
<a-button type="primary">
登录
</a-button>
</a>
<span v-else>{{ loginText }}</span>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
loading: false,
loginText: '正在登陆......'
};
},
methods: {
login() {
this.loading = true;
setTimeout(() => {
this.$router.push({ name: 'Framework', params: {}});
ipc.invoke(ipcApiRoute.restoreWindow, {width: 980, height: 650}).then(r => {
//
})
}, 2000);
}
}
};
</script>
<style lang="less" scoped>
#effect-login-window {
width: 100%;
min-height: 100%;
background: #f0f2f5 url(/src/assets/login.png) no-repeat 50%;
display: flex;
.block-1 {
font-size: 16px;
align-items: center;
margin: auto;
display: inline-block;
}
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<div id="app-effect-video">
<div class="one-block-1">
<span>
1. 视频播放
</span>
</div>
<!-- <div class="one-block-2">
<a-button @click="selectFile()"> / </a-button>
</div> -->
<div class="one-block-2">
<div id="video-player"></div>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import { toRaw } from 'vue';
import Player from 'xgplayer';
export default {
data() {
return {
fileUrl: '',
p: {},
op: {
id: 'video-player',
volume: 0.3,
url:'//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',
poster: "//lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/byted-player-videos/1.0.0/poster.jpg",
playsinline: false,
danmu: {
comments: [
{
duration: 15000,
id: '1',
start: 3000,
txt: '这是一个弹幕',
style: { //
color: '#ff9500',
fontSize: '20px',
border: 'solid 1px #ff9500',
borderRadius: '50px',
padding: '5px 11px',
backgroundColor: 'rgba(255, 255, 255, 0.1)'
}
}
],
area: {
start: 0,
end: 1
}
},
},
};
},
mounted () {
this.init();
},
methods: {
init () {
this.p = new Player(toRaw(this.op));
},
selectFile () {
const params = {}
ipc.invoke(ipcApiRoute.selectFile, params).then(res => {
console.log('res:', res)
if (res) {
this.fileUrl = res;
this.p.start(self.fileUrl);
} else {
this.$message.warning('请选择视频');
}
})
},
}
};
</script>
<style lang="less" scoped>
#app-effect-video {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,85 @@
<template>
<div id="app-other">
<div class="one-block-1">
<span>
请求java服务接口(废弃请使用跨语言服务)
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="startServer()"> java </a-button>
<a-button @click="sendRequest()"> </a-button>
<a-button @click="closeServer()"> java </a-button>
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import axios from 'axios';
import storage from 'store2';
export default {
data() {
return {
server: '',
};
},
methods: {
startServer () {
ipc.invoke(ipcApiRoute.startJavaServer, {}).then(r => {
if (r.code != 0) {
this.$message.error(r.msg);
} else {
this.$message.info('异步启动');
storage.set('javaService', r.server);
}
})
},
closeServer () {
ipc.invoke(ipcApiRoute.closeJavaServer, {}).then(r => {
if (r.code != 0) {
this.$message.error(r.msg);
}
this.$message.info('异步关闭');
storage.remove('javaService');
})
},
sendRequest () {
const server = storage.get('javaService') || '';
if (server == '') {
this.$message.error('服务未开启 或 正在启动中');
return
}
const testApi = server + '/test1/get';
const cfg = {
method: 'get',
url: testApi,
params: { id: '1111111'},
timeout: 60000,
}
axios(cfg).then(res => {
const data = res.data || null;
this.$message.info(`java服务返回: ${data}`, );
})
},
}
};
</script>
<style lang="less" scoped>
#app-other {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,184 @@
<template>
<div id="app-base-jobs">
<div class="one-block-1">
<span>
1. 任务 / 并发任务
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="runJob(1, 'create')">执行任务1</a-button>
进度{{ progress1 }} 进程pid{{ progress1_pid }}
<a-button @click="runJob(1, 'close')">关闭</a-button>
</a-space>
<p></p>
<a-space>
<a-button @click="runJob(2, 'create')">执行任务2</a-button>
进度{{ progress2 }} 进程pid{{ progress2_pid }}
<a-button @click="runJob(2, 'close')">关闭</a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
2. 任务池 / 并发任务
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="createPool()"></a-button>
进程pids{{ processPids }}
</a-space>
<p></p>
<a-space>
<a-button @click="runJobByPool(3, 'run')">执行任务3</a-button>
进度{{ progress3 }} 进程pid{{ progress3_pid }}
</a-space>
<p></p>
<a-space>
<a-button @click="runJobByPool(4, 'run')">执行任务4</a-button>
进度{{ progress4 }} 进程pid{{ progress4_pid }}
</a-space>
<p></p>
<a-space>
<a-button @click="runJobByPool(5, 'run')">执行任务5</a-button>
进度{{ progress5 }} 进程pid{{ progress5_pid }}
</a-space>
<p></p>
<a-space>
<a-button @click="runJobByPool(6, 'run')">执行任务6</a-button>
进度{{ progress6 }} 进程pid{{ progress6_pid }}
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
processPids: '',
progress1: 0,
progress2: 0,
progress3: 0,
progress4: 0,
progress5: 0,
progress6: 0,
progress1_pid: 0,
progress2_pid: 0,
progress3_pid: 0,
progress4_pid: 0,
progress5_pid: 0,
progress6_pid: 0,
}
},
mounted () {
this.init();
},
methods: {
init () {
// on
ipc.removeAllListeners(ipcApiRoute.timerJobProgress);
ipc.removeAllListeners(ipcApiRoute.createPoolNotice);
//
ipc.on(ipcApiRoute.timerJobProgress, (event, result) => {
switch (result.jobId) {
case 1:
this.progress1 = result.number;
this.progress1_pid = result.pid == 0 ? result.pid : this.progress1_pid;
break;
case 2:
this.progress2 = result.number;
this.progress2_pid = result.pid == 0 ? result.pid : this.progress2_pid;
break;
case 3:
this.progress3 = result.number;
this.progress3_pid = result.pid == 0 ? result.pid : this.progress3_pid;
break;
case 4:
this.progress4 = result.number;
this.progress4_pid = result.pid == 0 ? result.pid : this.progress4_pid;
break;
case 5:
this.progress5 = result.number;
this.progress5_pid = result.pid == 0 ? result.pid : this.progress5_pid;
break;
case 6:
this.progress6 = result.number;
this.progress6_pid = result.pid == 0 ? result.pid : this.progress6_pid;
break;
}
})
// pool
ipc.on(ipcApiRoute.createPoolNotice, (event, result) => {
let pidsStr = JSON.stringify(result);
this.processPids = pidsStr;
})
},
runJob(jobId, operation) {
let params = {
id: jobId,
type: 'timer',
action: operation
}
ipc.invoke(ipcApiRoute.someJob, params).then(data => {
if (operation == 'close') return;
switch (data.jobId) {
case 1:
this.progress1_pid = data.result.pid;
break;
case 2:
this.progress2_pid = data.result.pid;
break;
}
})
},
createPool() {
let params = {
number: 3,
}
ipc.send(ipcApiRoute.createPool, params);
},
runJobByPool(jobId, operation) {
let params = {
id: jobId,
type: 'timer',
action: operation
}
ipc.invoke(ipcApiRoute.someJobByPool, params).then(data => {
switch (data.jobId) {
case 3:
this.progress3_pid = data.result.pid;
break;
case 4:
this.progress4_pid = data.result.pid;
break;
case 5:
this.progress5_pid = data.result.pid;
break;
case 6:
this.progress6_pid = data.result.pid;
break;
}
})
},
}
}
</script>
<style lang="less" scoped>
#app-base-jobs {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,271 @@
<template>
<div id="app-base-db">
<div class="one-block-1">
<span>
1. jsondb本地数据库
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="8">
小数据量: 0~100M(单库)
</a-col>
<a-col :span="8">
json数据库
</a-col>
<a-col :span="8">
兼容lodash语法
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
2. 数据目录
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="12">
<a-input v-model="data_dir" :value="data_dir" addon-before="" />
</a-col>
<a-col :span="2">
</a-col>
<a-col :span="5">
<a-button @click="selectDir">
修改目录
</a-button>
</a-col>
<a-col :span="5">
<a-button @click="openDir">
打开目录
</a-button>
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
3. 测试数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="24">
{{ all_list }}
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
4. 添加数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="6">
<a-input v-model="name" :value="name" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-input v-model="age" :value="age" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-button @click="dbOperation('add')">
添加
</a-button>
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
5. 获取数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="6">
<a-input v-model="search_age" :value="search_age" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-button @click="dbOperation('get')">
查找
</a-button>
</a-col>
</a-row>
<a-row>
<a-col :span="24">
{{ userList }}
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
6. 修改数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="6">
<a-input v-model="update_name" :value="update_name" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-input v-model="update_age" :value="update_age" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-button @click="dbOperation('update')">
更新
</a-button>
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
7. 删除数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="6">
<a-input v-model="delete_name" :value="delete_name" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-button @click="dbOperation('del')">
删除
</a-button>
</a-col>
</a-row>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
name: '张三',
age: 10,
userList: ['空'],
search_age: 10,
update_name: '张三',
update_age: 21,
delete_name: '张三',
all_list: ['空'],
data_dir: ''
};
},
mounted () {
this.init();
},
methods: {
init() {
const params = {
action: 'getDataDir',
}
ipc.invoke(ipcApiRoute.jsondbOperation, params).then(res => {
this.data_dir = res.result;
this.getAllTestData();
})
},
getAllTestData () {
const params = {
action: 'all',
}
ipc.invoke(ipcApiRoute.jsondbOperation, params).then(res => {
console.log('res:', res);
if (res.all_list.length == 0) {
return false;
}
this.all_list = res.all_list;
})
},
selectDir() {
ipc.invoke(ipcApiRoute.selectFolder, '').then(r => {
this.data_dir = r;
//
this.modifyDataDir(r);
})
},
openDir() {
// console.log('data_dir:', this.data_dir);
ipc.invoke(ipcApiRoute.openDirectory, {id: this.data_dir}).then(res => {
//
})
},
modifyDataDir(dir) {
const params = {
action: 'setDataDir',
data_dir: dir
}
ipc.invoke(ipcApiRoute.jsondbOperation, params).then(res => {
this.all_list = res.all_list;
})
},
dbOperation (ac) {
const params = {
action: ac,
info: {
name: this.name,
age: parseInt(this.age)
},
search_age: parseInt(this.search_age),
update_name: this.update_name,
update_age: parseInt(this.update_age),
delete_name: this.delete_name,
}
if (ac == 'add' && this.name.length == 0) {
this.$message.error(`请填写数据`);
}
ipc.invoke(ipcApiRoute.jsondbOperation, params).then(res => {
console.log('res:', res);
if (ac == 'get') {
if (res.result.length == 0) {
this.$message.error(`没有数据`);
return;
}
this.userList = res.result;
}
if (res.all_list.length == 0) {
this.all_list = ['空'];
return;
}
this.all_list = res.all_list;
this.$message.success(`success`);
})
},
}
};
</script>
<style lang="less" scoped>
#app-base-db {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<div id="app-base-httpserver">
<div class="one-block-1">
<span>
1. 使用http与主进程通信
</span>
</div>
<div class="one-block-2">
<p>* 状态{{ currentStatus }}</p>
<p>* 地址{{ servicAddress }}</p>
<p>* 发送请求
<a-button @click="sendRequest('pictures')"> </a-button>
</p>
</div>
<div class="one-block-1">
<span>
2. 使用http与服务端通信
</span>
</div>
<div class="one-block-2">
<p>
<a-button @click="backendRequest()"> </a-button>
请自行创建服务
</p>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import axios from 'axios';
import storage from 'store2';
export default {
data() {
return {
currentStatus: '关闭',
servicAddress: '无'
};
},
mounted () {
this.init();
},
methods: {
init () {
ipc.invoke(ipcApiRoute.checkHttpServer, {}).then(r => {
if (r.enable) {
this.currentStatus = '开启';
this.servicAddress = r.server;
storage.set('httpServiceConfig', r);
}
})
},
sendRequest (id) {
if (this.currentStatus == '关闭') {
this.$message.error('http服务未开启');
return;
}
this.requestHttp(ipcApiRoute.doHttpRequest, {id}).then(res => {
//console.log('res:', res)
})
},
/**
* Accessing built-in HTTP services
*/
requestHttp(uri, parameter) {
// URL conversion
const config = storage.get('httpServiceConfig');
const host = config.server || 'http://localhost:7071';
let url = uri.split('.').join('/');
url = host + '/' + url;
console.log('url:', url);
return axios({
url: url,
method: 'post',
data: parameter,
timeout: 60000,
})
},
/**
* Send back-end requests
*/
backendRequest() {
console.log('GO_URL:', import.meta.env.VITE_GO_URL);
const cfg = {
baseURL: import.meta.env.VITE_GO_URL,
method: 'get',
url: '/hello',
timeout: 60000,
}
axios(cfg).then(res => {
console.log('res:', res);
const data = res.data || null;
this.$message.info(`go服务返回: ${data}`, );
})
}
}
};
</script>
<style lang="less" scoped>
#app-base-httpserver {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<div id="app-base-socket-ipc">
<div class="one-block-1">
<span>
1. 发送异步消息
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="handleInvoke"> - </a-button>
结果{{ message1 }}
</a-space>
<p></p>
<a-space>
<a-button @click="handleInvoke2"> - async/await</a-button>
结果{{ message2 }}
</a-space>
</div>
<div class="one-block-1">
<span>
<!-- 尽量不要使用任何错误都容易引起卡死 -->
2. 同步消息不推荐阻塞执行
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="handleSendSync"></a-button>
结果{{ message3 }}
</a-space>
</div>
<div class="one-block-1">
<span>
3. 长消息 服务端持续向前端页面发消息
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="sendMsgStart"></a-button>
<a-button @click="sendMsgStop"></a-button>
结果{{ messageString }}
</a-space>
</div>
<div class="one-block-1">
<span>
4. 多窗口通信子窗口与主进程通信子窗口互相通信
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="createWindow(0)">2</a-button>
<a-button @click="sendTosubWindow()">2</a-button>
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute, specialIpcRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import { toRaw } from 'vue';
export default {
data() {
return {
messageString: '',
message1: '',
message2: '',
message3: '',
windowName: 'window-ipc',
newWcId: 0,
views: [
{
type: 'vue',
content: '#/special/subwindow',
windowName: 'window-ipc',
windowTitle: 'ipc window'
},
],
}
},
mounted () {
this.init();
},
methods: {
init () {
// on
ipc.removeAllListeners(ipcApiRoute.ipcSendMsg);
ipc.on(ipcApiRoute.ipcSendMsg, (event, result) => {
console.log('[ipcRenderer] [socketMsgStart] result:', result);
this.messageString = result;
//
event.sender.send(ipcApiRoute.hello, 'electron-egg');
})
// 2
ipc.removeAllListeners(specialIpcRoute.window2ToWindow1);
ipc.on(specialIpcRoute.window2ToWindow1, (event, arg) => {
this.$message.info(arg);
})
},
sendMsgStart() {
const params = {
type: 'start',
content: '开始'
}
ipc.send(ipcApiRoute.ipcSendMsg, params)
},
sendMsgStop() {
const params = {
type: 'end',
content: ''
}
ipc.send(ipcApiRoute.ipcSendMsg, params)
},
handleInvoke() {
ipc.invoke(ipcApiRoute.ipcInvokeMsg, '异步-回调').then(r => {
console.log('r:', r);
this.message1 = r;
});
},
async handleInvoke2() {
const msg = await ipc.invoke(ipcApiRoute.ipcInvokeMsg, '异步');
console.log('msg:', msg);
this.message2 = msg;
},
handleSendSync() {
const msg = ipc.sendSync(ipcApiRoute.ipcSendSyncMsg, '同步');
this.message3 = msg;
},
createWindow(index) {
ipc.invoke(ipcApiRoute.createWindow, toRaw(this.views[index])).then(id => {
console.log('[createWindow] id:', id);
})
},
async sendTosubWindow() {
// id
this.newWcId = await ipc.invoke(ipcApiRoute.getWCid, this.windowName);
ipc.sendTo(this.newWcId, specialIpcRoute.window1ToWindow2, '窗口1通过 sendTo 给窗口2发送消息');
},
}
}
</script>
<style lang="less" scoped>
#app-base-socket-ipc {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<div id="app-base-httpserver">
<div class="one-block-1">
<span>
1. 使用socket与主进程通信
</span>
</div>
<div class="one-block-2">
<a-space>
<p>* 状态{{ currentStatus }}</p>
</a-space>
<p>* 地址{{ servicAddress }}</p>
</div>
<div class="one-block-1">
<span>
2. 发送请求
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="sendRequest('downloads')"> </a-button>
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { io } from 'socket.io-client';
export default {
data() {
return {
currentStatus: '关闭',
servicAddress: 'ws://localhost:7070'
};
},
mounted () {
this.init();
},
methods: {
init () {
this.socket = io(this.servicAddress);
this.socket.on('connect', () => {
console.log('connect!!!!!!!!');
this.currentStatus = '开启';
});
},
sendRequest (id) {
if (this.currentStatus == '关闭') {
this.$message.error('socketio服务未开启');
return;
}
const method = ipcApiRoute.doSocketRequest;
this.socket.emit('c1', { cmd: method, params: {id: id} }, (response) => {
// response
console.log('response:', response)
});
},
}
};
</script>
<style lang="less" scoped>
#app-base-httpserver {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<div id="app-base-software-open">
<div class="one-block-1">
<span>
1. 调用其它软件exebash等可执行程序
</span>
<p/>
<span class="sub-content">
请先将powershell.exe复制到electron-egg/build/extraResources目录中
</span>
</div>
<div class="one-block-2">
<a-list bordered :data-source="data">
<template #renderItem="{ item }">
<a-list-item @click="openSoft(item.id)">
{{ item.content }}
<a-button type="link">
执行
</a-button>
</a-list-item>
</template>
</a-list>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
data: [
{
content: 'powershell.exe',
id: 'powershell.exe'
}
],
};
},
methods: {
openSoft(id) {
ipc.invoke(ipcApiRoute.openSoftware, id).then(result => {
if (!result) {
this.$message.error('程序不存在');
}
})
},
}
};
</script>
<style lang="less" scoped>
#app-base-software-open {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
.sub-content {
font-size: 14px;
}
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,275 @@
<template>
<div id="app-base-db">
<div class="one-block-1">
<span>
1. sqlite本地数据库
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="8">
大数据量: 0-1024GB(单库)
</a-col>
<a-col :span="8">
高性能
</a-col>
<a-col :span="8">
类mysql语法
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
2. 数据目录
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="12">
<a-input v-model="data_dir" :value="data_dir" addon-before="" />
</a-col>
<a-col :span="2">
</a-col>
<a-col :span="5">
<a-button @click="selectDir">
修改目录
</a-button>
</a-col>
<a-col :span="5">
<a-button @click="openDir">
打开目录
</a-button>
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
3. 测试数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="24">
{{ all_list }}
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
4. 添加数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="6">
<a-input v-model="name" :value="name" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-input v-model="age" :value="age" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-button @click="sqlitedbOperation('add')">
添加
</a-button>
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
4. 获取数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="6">
<a-input v-model="search_age" :value="search_age" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-button @click="sqlitedbOperation('get')">
查找
</a-button>
</a-col>
</a-row>
<a-row>
<a-col :span="24">
{{ userList }}
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
5. 修改数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="6">
<a-input v-model="update_name" :value="update_name" addon-before="()" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-input v-model="update_age" :value="update_age" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-button @click="sqlitedbOperation('update')">
更新
</a-button>
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
6. 删除数据
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="6">
<a-input v-model="delete_name" :value="delete_name" addon-before="" />
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
</a-col>
<a-col :span="3">
</a-col>
<a-col :span="6">
<a-button @click="sqlitedbOperation('del')">
删除
</a-button>
</a-col>
</a-row>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
name: '李四',
age: 20,
userList: ['空'],
search_age: 20,
update_name: '李四',
update_age: 31,
delete_name: '李四',
all_list: ['空'],
data_dir: ''
};
},
mounted () {
this.init();
},
methods: {
init() {
const params = {
action: 'getDataDir',
}
ipc.invoke(ipcApiRoute.sqlitedbOperation, params).then(res => {
if (res.code == -1) {
this.$message.error('请检查sqlite是否正确安装', 5);
return
}
this.data_dir = res.result;
this.getAllTestData();
})
},
getAllTestData () {
const params = {
action: 'all',
}
ipc.invoke(ipcApiRoute.sqlitedbOperation, params).then(res => {
if (res.all_list.length == 0) {
return false;
}
this.all_list = res.all_list;
})
},
selectDir() {
ipc.invoke(ipcApiRoute.selectFolder, '').then(r => {
this.data_dir = r;
//
this.modifyDataDir(r);
})
},
openDir() {
console.log('dd:', this.data_dir);
ipc.invoke(ipcApiRoute.openDirectory, {id: this.data_dir}).then(res => {
//
})
},
modifyDataDir(dir) {
const params = {
action: 'setDataDir',
data_dir: dir
}
ipc.invoke(ipcApiRoute.sqlitedbOperation, params).then(res => {
this.all_list = res.all_list;
})
},
sqlitedbOperation (ac) {
const params = {
action: ac,
info: {
name: this.name,
age: parseInt(this.age)
},
search_age: parseInt(this.search_age),
update_name: this.update_name,
update_age: parseInt(this.update_age),
delete_name: this.delete_name,
}
if (ac == 'add' && this.name.length == 0) {
this.$message.error(`请填写数据`);
}
ipc.invoke(ipcApiRoute.sqlitedbOperation, params).then(res => {
console.log('res:', res);
if (ac == 'get') {
if (res.result.length == 0) {
this.$message.error(`没有数据`);
return;
}
this.userList = res.result;
}
if (res.all_list.length == 0) {
this.all_list = ['空'];
return;
}
this.all_list = res.all_list;
this.$message.success(`success`);
})
},
}
};
</script>
<style lang="less" scoped>
#app-base-db {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,37 @@
<template>
<div id="app-other">
<div class="one-block-1">
<span>
待开发...
</span>
</div>
<div class="one-block-2">
</div>
</div>
</template>
<script>
export default {
data() {
return {};
},
methods: {
test () {
},
}
};
</script>
<style lang="less" scoped>
#app-other {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,132 @@
<template>
<div id="app-base-test-api">
<div class="one-block-1">
<span> 1. 测试一些操作系统api </span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="exec(1)"> </a-button>
<a-button @click="exec2(1)"> 2 </a-button>
<a-button @click="getMac()"> mac </a-button>
<a-button @click="getOSMessage()"> </a-button>
<a-button @click="setSound()"> 50</a-button>
<a-button @click="restart()"></a-button>
</a-space>
<a-space>
<input v-model="progressName" placeholder="请输入进程名称" />
<a-button @click="kill()"> kill </a-button>
</a-space>
<a-space>
<input v-model="exePath" placeholder="请输入程序地址" />
<a-button @click="starExe()"> </a-button>
</a-space>
<a-space>
<a-button @click="testNet()"> </a-button>
</a-space>
<!-- <el-button type="danger">Danger</el-button> -->
<!-- <a-space>
<a-list size="small" bordered :data-source="iconData">
<template #renderItem="{ item }">
<a-list-item>
<icon-font :type="item.type" style="font-size: 36px" />
</a-list-item>
</template>
</a-list>
</a-space> -->
</div>
</div>
</template>
<script>
import { ipcApiRoute } from "@/api/main";
import iconList from "@/utils/iconList";
import { ipc } from "@/utils/ipcRenderer";
import { apiBaseUrl , getDeviceHome} from "@/utils/api"
// 使, vite
const pngModules = import.meta.globEager("@/assets/*.png");
// , map ,
const urls = Object.values(pngModules).map((mod) => mod.default);
export default {
data() {
return {
type: 1,
iconData: iconList,
//
progressName: "",
exePath: '',
};
},
methods: {
exec(id) {
console.log("process:", process);
const params = {
id: id,
};
ipc.invoke(ipcApiRoute.test, params).then((res) => {
console.log("res:", res);
});
},
exec2(id) {
//
},
getMac() {
ipc.invoke(ipcApiRoute.getAllMac, {}).then((res) => {
console.log("macs :", res);
});
},
getOSMessage() {
ipc.invoke(ipcApiRoute.getOSMessage, {}).then((res) => {
console.log("OS :", res);
});
},
setSound() {
ipc.invoke(ipcApiRoute.deviceLoudness, { value: 100 }).then((res) => {
console.log("设置后的声音 :", res);
});
},
//
restart() {
ipc.invoke(ipcApiRoute.deviceRestart, {}).then((res) => {
console.log("重启 :", res);
});
},
kill() {
//deviceStarExe
console.log(this.progressName);
ipc.invoke(ipcApiRoute.deviceKillName, this.progressName).then((res) => {
console.log("关闭进程结果 :", res);
});
},
starExe(){
console.log(this.exePath);
ipc.invoke(ipcApiRoute.deviceStarExe, this.exePath).then((res) => {
console.log("启动程序 :", res);
});
},
testNet(){
console.log(apiBaseUrl);
getDeviceHome().then(response=>{
console.log(response);
}).catch(error => {
console.error(error);
});
}
},
};
</script>
<style lang="less" scoped>
#app-base-test-api {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,86 @@
<template>
<div id="app-demo-window">
<div class="one-block-1">
<span>
1. 自动更新
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="checkForUpdater()"></a-button>
<a-button @click="download()"></a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
2. 下载进度
</span>
</div>
<div class="one-block-2">
<a-progress :percent="percentNumber" status="active" />
<a-space>
{{ progress }}
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute, specialIpcRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
status: 0, // -1:123, 4
progress: '',
percentNumber: 0
};
},
mounted () {
this.init();
},
methods: {
init () {
ipc.removeAllListeners(specialIpcRoute.appUpdater);
ipc.on(specialIpcRoute.appUpdater, (event, result) => {
result = JSON.parse(result);
this.status = result.status;
if (result.status == 3) {
this.progress = result.desc;
this.percentNumber = result.percentNumber;
} else {
this.$message.info(result.desc);
}
})
},
checkForUpdater () {
ipc.invoke(ipcApiRoute.checkForUpdater).then(r => {
console.log(r);
})
},
download () {
if (this.status !== 1) {
this.$message.info('没有可用版本');
return
}
ipc.invoke(ipcApiRoute.downloadApp).then(r => {
console.log(r);
})
},
}
};
</script>
<style lang="less" scoped>
#app-demo-window {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,101 @@
<template>
<div id="app-hw-bluetooth">
<div class="one-block-1">
<span>
1. 打印机设备
</span>
</div>
<div class="one-block-2">
<a-button @click="getPrinter()"> </a-button>
</div>
<div class="one-block-2">
<a-list size="small" bordered :data-source="printerList">
<template #renderItem="{ item }">
<a-list-item>
{{ item.displayName }} {{ defaultDevice(item) }}
</a-list-item>
</template>
<template #header>
<div>设备列表</div>
</template>
</a-list>
</div>
<div class="one-block-1">
<span>
2. 打印内容
</span>
</div>
<div class="one-block-2">
<a-button @click="doPrint(0)"> </a-button>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import { toRaw } from 'vue';
export default {
data() {
return {
defaultDeviceName: '',
printerList: [],
views: [
{
type: 'html',
content: '/public/html/view_example.html'
},
],
};
},
mounted () {
this.init();
},
methods: {
init () {
// on
ipc.removeAllListeners(ipcApiRoute.printStatus);
ipc.on(ipcApiRoute.printStatus, (event, result) => {
console.log('result', result);
this.$message.info('打印中...');
})
},
getPrinter () {
ipc.invoke(ipcApiRoute.getPrinterList, {}).then(res => {
this.printerList = res;
})
},
doPrint (index) {
console.log('defaultDeviceName:', this.defaultDeviceName)
const params = {
view: toRaw(this.views[index]),
deviceName: this.defaultDeviceName
};
ipc.send(ipcApiRoute.print, params)
},
defaultDevice (item) {
let desc = "";
if (item.isDefault) {
desc = "- 默认";
this.defaultDeviceName = item.name;
}
return desc;
}
}
};
</script>
<style lang="less" scoped>
#app-hw-bluetooth {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<div id="app-base-extension">
<div class="one-block-1">
<span>
<!-- electron的扩展功能不完整官方也不建议使用 -->
1. 上传扩展程序crx文件格式
</span>
</div>
<div class="one-block-2">
<a-upload-dragger
name="file"
:multiple="true"
:action="action_url"
@change="handleChange"
>
<p class="ant-upload-drag-icon">
<a-icon type="inbox" />
</p>
<p class="ant-upload-text">
上传
</p>
<p class="ant-upload-hint">
</p>
</a-upload-dragger>
</div>
<div class="one-block-1">
2. chrome扩展商店crx下载
</div>
<div class="one-block-2">
<a-space>
极简插件https://chrome.zzzmh.cn/
</a-space>
</div>
</div>
</template>
<script>
export default {
data() {
return {
action_url: 'localhost:xxxx/api/example/uploadExtension',
};
},
methods: {
handleChange(info) {
const status = info.file.status;
if (status !== 'uploading') {
console.log(info.file);
}
if (status === 'done') {
const uploadRes = info.file.response;
console.log('uploadRes:', uploadRes)
} else if (status === 'error') {
this.$message.error(`${info.file.name} file upload failed.`);
}
},
}
};
</script>
<style lang="less" scoped>
#app-base-extension {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,202 @@
<template>
<div id="app-base-file">
<div class="one-block-1">
<span>
1. 系统原生对话框
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="messageShow()">(ipc)</a-button>
<a-button @click="messageShowConfirm()">(ipc)</a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
2. 选择保存目录
</span>
</div>
<div class="one-block-2">
<a-row>
<a-col :span="12">
<a-input v-model="dir_path" :value="dir_path" addon-before="" />
</a-col>
<a-col :span="12">
<a-button @click="selectDir">
修改目录
</a-button>
</a-col>
</a-row>
</div>
<div class="one-block-1">
<span>
3. 打开文件夹
</span>
</div>
<div class="one-block-2">
<a-list :grid="{ gutter: 16, column: 4 }" :data-source="file_list">
<template #renderItem="{ item }">
<a-list-item @click="openDirectry(item.id)">
<a-card :title="item.content">
<a-button type="link">
打开
</a-button>
</a-card>
</a-list-item>
</template>
<!-- <a-list-item slot="renderItem" slot-scope="item" @click="openDirectry(item.id)">
<a-card :title="item.content">
<a-button type="link">
打开
</a-button>
</a-card>
</a-list-item> -->
</a-list>
</div>
<div class="one-block-1">
<span>
4. 上传文件到图床
</span>
</div>
<div class="one-block-2">
<a-upload-dragger
name="file"
:multiple="true"
:action="action_url"
@change="handleFileChange"
>
<p class="ant-upload-drag-icon">
</p>
<p class="ant-upload-text">
点击 拖拽文件到这里
</p>
<p class="ant-upload-hint">
注意请使用您自己的图床token
</p>
</a-upload-dragger>
</div>
<div class="footer">
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import storage from 'store2';
const fileList = [
{
content: '【下载】目录',
id: 'downloads'
},
{
content: '【图片】目录',
id: 'pictures'
},
{
content: '【文档】目录',
id: 'documents'
},
{
content: '【音乐】目录',
id: 'music'
}
];
export default {
data() {
return {
file_list: fileList,
action_url: '',
image_info: [],
num: 0,
servicAddress: '',
dir_path: "D:\\www\\ee",
};
},
mounted () {
this.getHost();
},
methods: {
getHost () {
ipc.invoke(ipcApiRoute.checkHttpServer, {}).then(r => {
if (r.enable) {
this.servicAddress = r.server;
storage.set('httpServiceConfig', r);
// url
const host = r.server || 'http://localhost:7071';
let uri = ipcApiRoute.uploadFile;
let url = uri.split('.').join('/');
this.action_url = host + '/' + url;
}
})
},
openDirectry (id) {
ipc.invoke(ipcApiRoute.openDirectory, {id: id}).then(res => {
//console.log('res:', res)
})
},
selectDir() {
ipc.invoke(ipcApiRoute.selectFolder, '').then(r => {
this.dir_path = r;
this.$message.info(r);
})
},
messageShow() {
ipc.invoke(ipcApiRoute.messageShow, '').then(r => {
this.$message.info(r);
})
},
messageShowConfirm() {
ipc.invoke(ipcApiRoute.messageShowConfirm, '').then(r => {
this.$message.info(r);
})
},
handleFileChange(info) {
console.log('handleFileChange-----');
if (this.action_url == '') {
this.$message.error('http服务未开启');
return;
}
const status = info.file.status;
if (status !== 'uploading') {
console.log(info.file);
}
if (status === 'done') {
const uploadRes = info.file.response;
console.log('uploadRes:', uploadRes)
if (uploadRes.code !== 'success') {
this.$message.error(`file upload failed ${uploadRes.code} .`);
return false;
}
this.num++;
const picInfo = uploadRes.data;
picInfo.id = this.num;
picInfo.imageUrlText = 'image url';
this.image_info.push(picInfo);
this.$message.success(`${info.file.name} file uploaded successfully.`);
} else if (status === 'error') {
this.$message.error(`${info.file.name} file upload failed.`);
}
},
}
};
</script>
<style lang="less" scoped>
#app-base-file {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
.footer {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<div id="os-file-pic">
<div class="one-block-1">
<span>
1. 加载本机图片
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="selectPic(0)"></a-button>
</a-space>
<p></p>
<a-image
:width="500"
:src=picPath
/>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
picPath: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png'
};
},
methods: {
selectPic () {
ipc.invoke(ipcApiRoute.selectPic, {}).then(r => {
this.picPath = r;
})
},
}
};
</script>
<style lang="less" scoped>
#os-file-pic {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<div id="app-base-notification">
<div class="one-block-1">
<span>
1. 弹出桌面通知
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="sendNotification(0)"></a-button>
<a-button @click="sendNotification(1)"></a-button>
<a-button @click="sendNotification(2)"></a-button>
<a-button @click="sendNotification(3)"></a-button>
</a-space>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
import { toRaw } from 'vue';
export default {
data() {
return {
views: [
{
type: 'main',
title: '通知标题',
subtitle: '副标题', // macOS
body: '这是通知内容-默认',
silent: true,
},
{
type: 'main',
title: '提示音',
subtitle: '副标题-提示音',
body: '这是通知内容-提示音',
silent: false,
},
{
type: 'main',
title: '点击通知事件',
subtitle: '副标题-点击通知事件',
body: '这是通知内容-点击通知事件',
clickEvent: true
},
{
type: 'main',
title: '关闭通知事件',
subtitle: '副标题-关闭通知事件',
body: '这是通知内容-点击通知事件',
closeEvent: true
},
],
};
},
mounted () {
this.init();
},
methods: {
init () {
// on
ipc.removeAllListeners(ipcApiRoute.sendNotification);
ipc.on(ipcApiRoute.sendNotification, (event, result) => {
if (Object.prototype.toString.call(result) == '[object Object]') {
this.$message.info(result.msg);
}
})
},
sendNotification (index) {
ipc.send(ipcApiRoute.sendNotification, toRaw(this.views[index]));
},
}
};
</script>
<style lang="less" scoped>
#app-base-notification {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<div id="app-base-powermonitor">
<div class="one-block-1">
<span>
1. 监控电源状态
</span>
</div>
<div class="one-block-2">
<a-space>
<p>* 当前状态{{ currentStatus }}</p>
</a-space>
<p>* 拔掉电源使用电池供电</p>
<p>* 接入电源</p>
<p>* 锁屏</p>
<p>* 解锁</p>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
currentStatus: '无',
};
},
mounted () {
this.init();
},
methods: {
init () {
ipc.removeAllListeners(ipcApiRoute.initPowerMonitor);
ipc.on(ipcApiRoute.initPowerMonitor, (event, result) => {
if (Object.prototype.toString.call(result) == '[object Object]') {
this.currentStatus = result.msg;
this.$message.info(result.msg);
}
})
ipc.send(ipcApiRoute.initPowerMonitor, '');
}
}
};
</script>
<style lang="less" scoped>
#app-base-powermonitor {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

View File

@ -0,0 +1,61 @@
<template>
<div id="app-base-screen">
<div class="one-block-1">
<span>
1. 屏幕信息
</span>
</div>
<div class="one-block-2">
<a-space>
<a-button @click="getScreen(0)"></a-button>
<a-button @click="getScreen(1)"></a-button>
<a-button @click="getScreen(2)"></a-button>
</a-space>
</div>
<div class="one-block-1">
<span>
结果
</span>
</div>
<div class="one-block-2">
<a-descriptions title="">
<a-descriptions-item v-for="(info, index) in data" :key="index" :label="info.title" >
{{ info.desc }}
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</template>
<script>
import { ipcApiRoute } from '@/api/main';
import { ipc } from '@/utils/ipcRenderer';
export default {
data() {
return {
data: [],
};
},
methods: {
getScreen (index) {
ipc.invoke(ipcApiRoute.getScreen, index).then(result => {
this.data = result;
})
},
}
};
</script>
<style lang="less" scoped>
#app-base-screen {
padding: 0px 10px;
text-align: left;
width: 100%;
.one-block-1 {
font-size: 16px;
padding-top: 10px;
}
.one-block-2 {
padding-top: 10px;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More