first commit

This commit is contained in:
编码猿
2024-09-27 01:11:40 +08:00
commit 7ac6f553c6
147 changed files with 10620 additions and 0 deletions

21
.gitignore vendored Executable file
View File

@@ -0,0 +1,21 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/npm-debug.log*
/yarn-error.log
/yarn.lock
/package-lock.json
/build
/demos
/dist
问题.txt
plugin.jpg
# misc
.DS_Store
# umi
/src/.umi/
/.env.local
.idea/

2
.npmrc Executable file
View File

@@ -0,0 +1,2 @@
ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/
ELECTRON_BUILDER_BINARIES_MIRROR=http://npm.taobao.org/mirrors/electron-builder-binaries/

1
.pydio Normal file
View File

@@ -0,0 +1 @@
a85a8356-93f5-4240-a73c-f5d1c8098128

69
README.md Executable file
View File

@@ -0,0 +1,69 @@
<p align="center">
<img style="width: 100px;height: 100px;" src="./website/img/logo.png"/>
</p>
<p align="center">
一款前端工程化项目的GUI管理工具
</p>
<p align="center">
<a href="https://nodejs.org/en/download/">
<img src="https://img.shields.io/badge/node.js-v14.19.0-blue.svg" alt="nodejs">
</a>
<a href="https://www.electronjs.org/">
<img src="https://img.shields.io/badge/electron-v13.5.1-brightgreen" alt="electron">
</a>
<a href="https://www.typescriptlang.org/">
<img src="https://img.shields.io/badge/typescript-v4.4.4-blue" alt="typescript">
</a>
<a href="https://gulpjs.com/">
<img src="https://img.shields.io/badge/gulp-v4.0.2-orange" alt="gulp">
</a>
<a href="https://xtermjs.org/">
<img src="https://img.shields.io/badge/xterm-v4.9.0-brightgreen" alt="xterm">
</a>
</p>
## 简介
`EasyProject` 是一款`Mac`平台下的前端项目管理工具能够一键导入本地的前端项目帮助您将零碎零散的前端项目进行统一化GUI管理和配置协助您提高项目开发的效率。
![install.png](./website/img/doc/install-min.png)
![main-min.png](./website/img/doc/main-min.png)
![package-min.png](./website/img/doc/package-min.png)
![theme.png](./website/img/theme.png)
### 官网
> [https://helpcode.github.io/EasyProject/website/index.html](https://helpcode.github.io/EasyProject/website/index.html)
### 下载
打开链接,选最新版本,然后从`Assets`中选择`EasyProject-x.x.x.dmg`进行下载即可。
> [releases](https://github.com/helpcode/EasyProject/releases)
## 功能介绍
已实现功能:
- [x] 1: 欢迎页面
- [x] 2: 导入项目,包含从 `menu``touchbar``tray`
- [x] 4: 移除项目,修改项目名称,图标,在访达打开,在终端打开。
- [x] 5: 读取展示 项目的 `package.json`,并实时同步软件中修改的`package.json`到本地。
- [x] 6: 读取展示 项目的 `script` 脚本,可以一键启停,复制 & 删除 终端日志,打开链接,滚动日志到底部。
- [x] 7: 当停止 `script` 脚本时,增加"进程锁",等待子进程结束才能再次使用命令。
- [x] 8: 读取展示 项目的 `devDependencies``dependencies` 如果当前项目没有初始化依赖,软件会自动弹窗提示询问是否进行安装,安装成功后:提供 一键安装插件,一键升级,一键删除,查看插件官方网址等功能。
- [x] 9: 自动监听已导入的项目路径,路径发生改变,软件自动移除相关项目。
- [x] 10: 提供自定义主题功能,支持本地 & 网络 上传手机壁纸,拖动调节 透明度,背景模糊度等。
- [x] 11: 可以自定义 `Node.js`解释器安装路径,避免`MacOS`通过`GUI`启动`xx.app`时出现的无法继承用户环境变量。
- [x] 12: 可根据喜好在软件中全局设置 你想要的 包管理工具。
- [x] 13: 一键勾选 是否 开机自启。
- [x] 14: 打开应用自动升级检测,或是 点击检查更新 进行新版本下载。
- [x] 15: 增加状态栏顶部 图标,实现 **杀死EasyProject** 时正在被运行的 命令进程 可以被全部关闭。
- [x] 16: 实现隐藏应用GUI只保留顶部状态栏图标简化操作随取随用。
待实现功能:
- [ ] 1: 支持从`Git``SVN` 仓库导入项目
- [ ] 2: 把当前正在使用项目的所有命令放到`Touchbar上`,可以在`Touchbar`上启停。
- [ ] 3: 把项目命令运行时的内存占用CPU占用显示在顶部菜单栏。
- [ ] 4: 修改框架创建窗口的数据存储方式,让升级弹窗基于`BrowserWindow`实现。

54
gulpfile.js Executable file
View File

@@ -0,0 +1,54 @@
const gulp = require('gulp')
// 编译jade文件为hrml
const jade = require('gulp-jade')
// gulp 文件监听
const watch = require('gulp-watch')
// 处理gulp错误防止程序报错终止
const plumber = require('gulp-plumber')
const path = require('path')
const less = require('gulp-less')
/**
* 编译jade为html
*/
gulp.task('jade', function () {
return gulp.src("./src/application/page/**/*.jade")
.pipe(plumber({
errHandler: e => {
gutil.beep()
gutil.log(e)
}
}))
.pipe(jade({
pretty: true
}))
.pipe(gulp.dest("./dist/application/page"))
})
/**
* 编译less为css
*/
gulp.task('less', function () {
return gulp.src('src/application/assets/less/**/*.less')
.pipe(less())
.pipe(gulp.dest('./dist/application/assets/css'))
});
gulp.task('copy', function() {
return gulp.src(['src/application/assets/**/*','!src/**/less/**','!src/**/less'])
.pipe(gulp.dest('./dist/application/assets'))
});
/**
* 监听用户在 src/ 的所有文件操作
*/
gulp.task('file', function () {
gulp.watch(
['src/application/page/**/*','src/application/layout/*','src/application/components/*'],
gulp.parallel('jade')
)
gulp.watch('src/application/assets/less/**/*.less', gulp.parallel('less'))
gulp.watch('src/application/assets/**/*', gulp.parallel('copy'))
});
gulp.task('default', gulp.series('file'))

15
moduleAlias.js Normal file
View File

@@ -0,0 +1,15 @@
const moduleAlias = require('module-alias')
moduleAlias.addAlias('@', __dirname + '/')
moduleAlias.addAlias('@type', __dirname + '/core/types')
moduleAlias.addAlias('@run', __dirname + '/core/run')
moduleAlias.addAlias('@controller', __dirname + '/core/controller')
moduleAlias.addAlias('@utils', __dirname + '/core/utils')
moduleAlias.addAlias('@config', __dirname + '/core/config')
moduleAlias.addAlias('@model', __dirname + '/core/model')
moduleAlias.addAlias('@ipc', __dirname + '/core/ipc')
moduleAlias.addAlias('@service', __dirname + '/core/service')
moduleAlias.addAlias('@net', __dirname + '/core/net')
moduleAlias.addAlias('@annotation', __dirname + '/core/annotation')
moduleAlias.addAlias('@interactive', __dirname + '/core/interactive')
moduleAlias.addAlias('@img', __dirname + '/view/assets/img')

95
package.json Executable file
View File

@@ -0,0 +1,95 @@
{
"name": "EasyProject",
"version": "1.0.4",
"description": "一款前端项目管理工具",
"main": "dist/App.js",
"scripts": {
"dev": "concurrently \"tsc -w\" \"gulp\" ",
"el": "/Applications/Electron.app/Contents/MacOS/Electron .",
"mac": "electron-builder --mac --x64",
"win": "electron-builder --win"
},
"build": {
"appId": "com.bmy.EasyProject",
"productName": "EasyProject",
"asar": true,
"copyright": "Copyright © 2022 bmy",
"files": [
"dist/**/*"
],
"directories": {
"output": "./build"
},
"dmg": {
"background": "build/data/back.png",
"window": {
"width": 514,
"height": 409
},
"contents": [
{
"x": 410,
"y": 190,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 190,
"type": "file"
}
]
},
"mac": {
"icon": "build/data/EasyProject.icns",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"target": [
"dmg",
"7z"
]
},
"win": {
"icon": "pic.ico",
"target": [
"7z"
]
}
},
"keywords": [],
"author": "bmy",
"license": "ISC",
"devDependencies": {
"@types/deep-diff": "^1.0.0",
"@types/jade": "^0.0.30",
"@types/lowdb": "^1.0.9",
"@types/node": "^17.0.10",
"@types/fs-extra": "^9.0.13",
"electron-reload": "^2.0.0-alpha.1",
"gulp": "^4.0.2",
"gulp-jade": "^1.1.0",
"gulp-less": "^4.0.1",
"gulp-plumber": "^1.2.1",
"gulp-watch": "^5.0.1",
"gulp-watch-path": "^0.1.0",
"electron": "^17.4.3"
},
"dependencies": {
"axios": "^0.21.0",
"chokidar": "^3.5.3",
"deep-diff": "^1.0.2",
"execa": "^4.1.0",
"fs-extra": "^9.0.1",
"globby": "^11.0.1",
"jade": "^1.11.0",
"lowdb": "^1.0.0",
"module-alias": "^2.2.2",
"moment": "^2.29.1",
"monaco-editor": "^0.33.0",
"monaco-themes": "^0.4.1",
"reflect-metadata": "^0.1.13",
"tree-kill": "^1.2.2",
"xterm": "^4.9.0",
"xterm-addon-web-links": "^0.4.0"
}
}

1
src/.pydio Normal file
View File

@@ -0,0 +1 @@
69cbd5c4-abd0-4e6b-82a5-1d2e269b0d71

29
src/App.ts Executable file
View File

@@ -0,0 +1,29 @@
require('./moduleAlias');
import { app, dialog } from "electron";
import { Run } from "@run/Init.run";
import Windows from "@/core/model/Windows.model";
import { WhichBin } from "@utils/whichBin.utils"
app.on('ready', async () => {
/**
* 解决 Electron 打包发布后通过GUI点击 xx.app 启动成程序
* child_process 无法执行导致应用的 命令 无法启动。主要原因
* 是从GUI启动时 $PATH 环境变量错误,这边需要手动合并环境变量。
* windows 不会出问题linux 和 macos 都存在
*/
if (process.platform != 'win32') {
let which = new WhichBin()
await which.initEnvPath();
}
new Run()
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
});
app.on('activate', () => {
Windows.CurrentBrowserWindow.show()
});

1
src/application/.pydio Normal file
View File

@@ -0,0 +1 @@
b79ca5c5-2f7b-4625-9c9f-3afa4987dba8

View File

@@ -0,0 +1 @@
df80fe02-b8bf-46f7-a46b-62d5a7133404

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
41c66e59-5634-4703-b835-9c99eae570a3

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1 @@
23b479ab-9234-45a0-9523-6d163a593114

View File

@@ -0,0 +1 @@
af27e24b-3568-4ac6-b7b0-a29d7871be4f

View File

@@ -0,0 +1,69 @@
const {readFileSync} = require("fs");
const {join, resolve} = require("path");
const amdLoader = require('monaco-editor/min/vs/loader.js');
class Utils {
// 保存 require 对象
#amdRequire = amdLoader.require;
#module = null;
#themeData = null;
constructor() {
// 使用插件 monaco-themes 里面提供的json配置文件
// this.#themeData = require("monaco-themes/themes/GitHub.json")
this.#amdRequire.config({
baseUrl: this.uriFromPath(join(__dirname, '../../../../../node_modules/monaco-editor/min'))
});
// 设置中文语言
this.#amdRequire.config({
'vs/nls': { availableLanguages: {'*':'zh-cn'}}
});
}
uriFromPath(_path) {
var pathName = resolve(_path).replace(/\\/g, '/');
if (pathName.length > 0 && pathName.charAt(0) !== '/') {
pathName = '/' + pathName;
}
return encodeURI('file://' + pathName);
}
Html(path) {
return readFileSync(join(__dirname, `../../../page${path}`)).toString("utf-8")
}
initVscodeEdit() {
return new Promise((success, error) => {
this.module = undefined;
this.#amdRequire(['vs/editor/editor.main'], () => {
// 自定义主题
// monaco.editor.defineTheme('mytheme', this.#themeData);
// monaco.editor.setTheme('mytheme');
let el = document.querySelector(".package");
// 当切换tabs的时候清空上一个项目的package.json配置
el.innerHTML = ""
var editor = monaco.editor.create(
el,
{
value: '',
language: 'json',
autoIndent: true,
formatOnPaste: true,
formatOnType: true,
tabSize: 2,
fontSize: "16px",
scrollBeyondLastColumn: 2,
automaticLayout: true
}
);
success(editor)
});
})
}
}
module.exports = new Utils();

View File

@@ -0,0 +1 @@
adb86b38-623e-4625-a2ae-cff5b2b01c72

View File

@@ -0,0 +1,7 @@
module.exports = {
env: [
{
name: 'Utils', path: '../../assets/js/Utils/'
}
]
}

View File

@@ -0,0 +1 @@
94f4d244-3df7-4561-a854-4d2405a0f8ae

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
class Utils {
/**
* 发送GET 请求
* @param URL 地址
* @param params 参数
* @param callback 回调
* @constructor
*/
Http(URL, params,callback) {
$.ajax({
url: `http://www.bmycode.com:3000/api${URL}`,
type: 'GET',
data: params,
success: (res)=> {
callback(res)
}
})
}
/**
* 根据时间戳返回多久之前的时间
* @param timespan 时间戳
* @returns {string}
*/
formatTime (timespan) {
var dateTime = new Date(timespan);
var year = dateTime.getFullYear();
var month = dateTime.getMonth() + 1;
var day = dateTime.getDate();
var hour = dateTime.getHours();
var minute = dateTime.getMinutes();
var second = dateTime.getSeconds();
var now = new Date();
var now_new = Date.parse(now.toDateString()); //typescript转换写法
var milliseconds = 0;
var timeSpanStr;
milliseconds = now_new - timespan;
if (milliseconds <= 1000 * 60 * 1) {
timeSpanStr = '刚刚';
}
else if (1000 * 60 * 1 < milliseconds && milliseconds <= 1000 * 60 * 60) {
timeSpanStr = Math.round((milliseconds / (1000 * 60))) + '分钟前';
}
else if (1000 * 60 * 60 * 1 < milliseconds && milliseconds <= 1000 * 60 * 60 * 24) {
timeSpanStr = Math.round(milliseconds / (1000 * 60 * 60)) + '小时前';
}
else if (1000 * 60 * 60 * 24 < milliseconds && milliseconds <= 1000 * 60 * 60 * 24 * 15) {
timeSpanStr = Math.round(milliseconds / (1000 * 60 * 60 * 24)) + '天前';
}
else if (milliseconds > 1000 * 60 * 60 * 24 * 15 && year == now.getFullYear()) {
timeSpanStr = month + '月' + day + '号' + hour + ':' + minute;
} else {
timeSpanStr = year + '年' + month + '月' + day + '号' + hour + ':' + minute;
}
return timeSpanStr;
};
}
window.$Utils = new Utils()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
839a8d36-2a7b-4fef-b510-4e4e02728428

View File

@@ -0,0 +1,336 @@
const {ipcRenderer} = require("electron");
const router = require("../../assets/js/router/index.js");
const store = require("../../assets/js/store/index.js");
const config = require("../../assets/js/config/index.js");
let Home = new Vue({
el: '#app',
router,
store,
data: {
// 左侧菜单的状态 false 非全屏, true 全屏
menuStatus: false,
menuIconRotate: 180,
// 控制重命名项目弹窗是否显示
isEditDialog: false,
// 项目图标选择
isIconDialog: false,
// 选择主题的下标
selectThemeIndex: 0,
// 你点击的项目下标
clickProjectIndex: -1,
// 保存 重命名项目 的输入值
EditProjectName: "",
// 操作的当前项目 就是 name 名称
CurrentProjectName: "",
// 保存用户选择的图片类名
ProjectIcon: "",
ProjectListData: [],
IconList: [
{icon: '#icon-vue', title: 'Vue'},
{icon: '#icon-react', title: 'React'},
{icon: '#icon-angular', title: 'Angular'},
{icon: '#icon-typescript', title: 'TypeScript'},
{icon: '#icon-nodejs', title: 'Nodejs'},
{icon: '#icon-npm1', title: 'Npm'},
{icon: '#icon-console', title: 'Cli'},
{icon: '#icon-gulp', title: 'Gulp'},
{icon: '#icon-graphql', title: 'Graphql'},
{icon: '#icon-webpack', title: 'Webpack'},
{icon: '#icon-nuxt', title: 'Nuxt'},
{icon: '#icon-nest', title: 'Nest'},
],
MoreList: [
{title: '修改图标', icon: 'el-icon-magic-stick', id: 'editIcon'},
{title: '重命名项目', icon: 'el-icon-edit', id: 'rename'},
{title: '移除项目', icon: 'el-icon-delete', id: 'delete'},
]
},
created() {
// 调用升级检测
this.onMainWebContents();
this.GetProjectList();
},
mounted() {
// this.$store.dispatch('checkUpdate','default')
},
methods: {
downNowDmg(url) {
ipcRenderer.sendSync('openExternal', {
action: '浏览器打开',
url: url,
});
},
// 帮设置页面提前获取当前应用版本号
changeMenu() {
if (this.menuStatus) {
this.menuStatus = false
} else {
this.menuStatus = true
}
},
Mouseover(e) {
console.log("全屏按钮被悬浮")
if (this.menuStatus) {
this.menuIconRotate = 0
} else {
this.menuIconRotate = 180
}
},
/**
* 监听主进程发过来的消息
*/
onMainWebContents() {
// 主进程发送过来的消息,打开设置页面
ipcRenderer.on('openSetting', (event, message) => {
this.$router.push("/setting")
})
// 主进程发送过来的消息,在 应用内打开
ipcRenderer.on('openInfo', (event, arg) => {
console.log("应用内打开: ", arg)
this.openInfo(arg.item, arg.index)
})
// 文件被删除
ipcRenderer.on('DirRemove', (event, arg) => {
console.log("检测到您的文件路径发生变化: ", arg)
ipcRenderer.send('removeListProject', {
action: '从列表移除',
name: arg.name,
});
this.removeProjectItem("autoWatch", arg.FolderName);
// this.$confirm(`该路径下文件:${arg.Fullpath} 发生变化,即将移除项目!`, '提示', {
// confirmButtonText: '确定',
// cancelButtonText: '取消',
// type: 'error'
// }).then(() => {
//
// }).catch(() => {
// });
})
// 使用 touchbar导入项目
ipcRenderer.on('TouchBarImportProject', (event, message) => {
if (message != undefined) {
this.$store.commit('SET_LIST', {
res: message,
type: 'ChoiceFile'
});
}
})
let result = ipcRenderer.sendSync('getVersion', {
action: '获取设置页面的当前版本号'
});
this.$store.commit('set_vsersion', result.version);
let res = ipcRenderer.sendSync('getSettingConfig', {
action: '获取设置页面配置参数'
});
// 把从json数据库中获取到的配置向 vuex 中合并
this.$store.replaceState(Object.assign({}, this.$store.state, res))
console.log("设置的参数:", this.$store.state)
},
openInfo(v, i) {
this.clickProjectIndex = i;
this.$router.push({
name: 'info',
query: {
name: v.FolderName,
active: v.CurrentTabs,
isNeedWatch: true
}
})
},
/**
* 项目列表 悬浮下拉项被点击
* @constructor
*/
ShowActionClick(item) {
console.log("item: ", item)
// 保存当前操作的项目文件夹名称
this.CurrentProjectName = item[0]
switch (item[1]) {
case "delete": // 移除项目
this.$confirm('【从磁盘删除】将会从电脑上删除您的项目,【从列表移除】将从项目列表删除选中的项目。', '移除项目', {
distinguishCancelAndClose: true,
confirmButtonText: '从列表移除',
cancelButtonText: '从磁盘删除',
roundButton: true,
type: 'warning'
}).then(() => {
ipcRenderer.send('removeListProject', {
action: '从列表移除',
name: this.CurrentProjectName,
});
this.removeProjectItem();
}).catch((action) => {
if (action == "cancel") {
this.$confirm('项目将会被删除到【废纸篓】,确定继续?', '删除提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(() => {
let status = ipcRenderer.sendSync('removeDistProject', {
action: '从磁盘删除',
name: this.CurrentProjectName,
});
if (status == "success") {
this.$notify({title: '提示', message: '项目删除成功', type: 'success'});
} else {
this.$notify.error({title: '提示', message: status, type: 'error'});
}
this.removeProjectItem();
}).catch(() => {
});
}
});
break;
case "rename": // 重命名项目
this.isEditDialog = true;
break;
case "editIcon": // 修改图标
this.isIconDialog = true;
break;
}
},
/**
* 从列表删除 和 从 磁盘删除,需要减少列表数据
* @param type
* default 表示从 列表 和 磁盘进行删除
* autoWatch 表示检测到的自动删除
* @param condition autoWatch 的筛选条件
*/
removeProjectItem(type = "default", condition) {
// 从原数组中去除你要删除的
let list = this.$store.getters.GET_PROJECT.filter((v, i) => {
if (type == "default") {
if (i != this.clickProjectIndex) {
return v;
}
} else {
if (v.FolderName != condition) {
return v;
} else {
// 将你当前移动文件位置的项目路径保存起来,为下面自动激活做准备
this.clickProjectIndex = i
}
}
});
this.$store.commit('SET_LIST', {
res: list,
type: "ProjectList"
})
// 当前项目被你删除,自动跳转激活到上一个项目
// 处理越界
let index = this.clickProjectIndex - 1;
index < 0 ? index = 0 : index - 1
// 如果所有项目都被删完了,调回 main 主页
list.length == 0
? this.$router.replace("/main")
: this.openInfo(
this.$store.getters.GET_PROJECT[index],
index
)
},
/**
* 选择图标
* @param item
* @constructor
*/
SelectIcon(item) {
this.$store.getters.GET_PROJECT[this.clickProjectIndex].FolderIcon = item.icon
let status = ipcRenderer.sendSync('ChangeIcon', {
action: '修改项目图标',
name: this.CurrentProjectName,
newIcon: item.icon
});
if (status == "success") {
this.isIconDialog = false;
}
},
/**
* 重命名项目
* @constructor
*/
SubmitEdit() {
if (this.EditProjectName.length != 0) {
this.$store.getters.GET_PROJECT[this.clickProjectIndex].name = this.EditProjectName
let status = ipcRenderer.sendSync('reNameProject', {
action: '重命名项目',
oldName: this.CurrentProjectName,
newName: this.EditProjectName
});
if (status == "success") {
this.EditProjectName = ""
this.isEditDialog = false;
}
}
},
OpenPropject(type, item) {
console.log(item)
ipcRenderer.sendSync('OpenProject', {
action: '选择项目',
type: type,
path: item.Fullpath
});
},
/**
* 添加项目 按钮被点击,弹窗选择文件
* @constructor
*/
ChoiceFile() {
let list = ipcRenderer.sendSync('openDirectory', {
action: '选择项目'
})
if (list != undefined) {
console.log("导入项目:", list)
this.$store.commit('SET_LIST', {
res: list,
type: 'ChoiceFile'
});
}
},
/**
* 获取项目列表
* @constructor
*/
GetProjectList() {
let list = ipcRenderer.sendSync('ProjectList', {
action: '获取默认列表'
});
console.log("GetProjectList ==== ", list)
// 如果没有项目列表了,那么就不需要展示项目详情页面
list.length == 0 ? this.$router.push("/") : ""
// 请求到的数据存入vuex中
this.$store.commit('SET_LIST', {
res: list,
type: "ProjectList"
})
},
},
computed: {
key() {
return this.$route.name ? this.$route.name + +new Date() : this.$route + +new Date()
}
}
});

View File

@@ -0,0 +1,12 @@
module.exports = {
data() {
return {
}
},
created() {
},
methods: {
}
}

View File

@@ -0,0 +1,10 @@
const { ipcRenderer } = require('electron');
let PlugInfo = new Vue({
el: '#app',
data: {
msg: '测试'
},
methods: {
}
});

View File

@@ -0,0 +1,610 @@
const {ipcRenderer, shell} = require("electron");
const {Terminal} = require("xterm");
const {WebLinksAddon} = require("xterm-addon-web-links");
const Utils = require("../Utils/index.js");
const {ProcessUtils} = require("../../../../core/utils/process.utils");
module.exports = {
data() {
return {
isNeedWatch: true,
isShowError: false, // 是否显示错误弹窗
ErrorText: '', // 错误具体信息
activeName: '',
// 保存的就是 FolderName
projectName: '',
isRunScript: false, // 是否启动过命令
titleTips: '默认显示 [任务] 中展开的命令', // 提示文字
currentSrcipt: '', // 保存当前你点击过的折叠面板,也就是 ScriptName
isShowInstallDev: false, // 安装依赖弹窗是否显示
Environment: '', // 你选择安装插件的环境
PlugInput: '', // 用户输入的插件名称
currentProcess: [], // 当前命令的进程占用
PlugList: [],
TerminalConfig: {
scrollback: 5000,
disableStdin: true,
useFlowControl: true,
cols: 80,
rows: 16,
convertEol: true,
theme: {
background: '#1d2935',
cursor: 'rgba(255, 255, 255, .4)',
selection: 'rgba(255, 255, 255, 0.3)',
magenta: '#e83030',
brightMagenta: '#e83030',
black: "#000000",
red: "#e83030",
brightRed: "#e83030",
green: "#42b983",
brightGreen: "#42b983",
brightYellow: "#ea6e00",
yellow: "#ea6e00",
cyan: "#03c2e6",
brightBlue: "#03c2e6",
brightCyan: "#03c2e6",
blue: "#03c2e6",
white: "#d0d0d0",
brightBlack: "#808080",
brightWhite: "#ffffff",
}
}
}
},
created() {
this.GetShellMessage();
this.reDependentSuccess();
this.onClose();
this.projectName = this.$route.query.name;
this.activeName = this.$route.query.active;
this.isNeedWatch = this.$route.query.isNeedWatch;
this.SwitcTabs(this.activeName)
},
watch: {
$route: async function (to, from) {
console.log("1 watch监听的$route为: ", this.$route)
this.projectName = this.$route.query.name;
this.activeName = this.$route.query.active;
this.isNeedWatch = this.$route.query.isNeedWatch;
// 如果进入页面是 home配置的话就获取最新的package.json
if (this.activeName == "home") {
this.SwitcTabs(this.$route.query.active)
}
if (this.isNeedWatch) {
this.currentSrcipt = ""
}
// 如果进入tabs的 task 任务栏
// 1从其他项目切换回来RunLogs 不为空但是 Terminal 所对应的html被刷没了
// 这时候 this.isHasTerminal() 返回 false所以重新初始化终端创建html并载入日志
// 2在当前项目中切换tab因为页面被 keep-alive 缓存所以Terminal 的 html还在
// 这时候 this.isHasTerminal() 返回 true所以不需要重新创建终端
if (this.activeName == "task") {
let current = this.CurrentTask(this.currentSrcipt);
if (current && current.Terminal != null &&
!(await this.isHasTerminal())) {
console.warn("2: watch 创建终端")
// 为默认展开的 折叠面板 重新创建终端写入之前的日志
if (this.currentSrcipt == current.ScriptName) {
current.Terminal = null
console.warn("3: 为默认展开的 折叠面板 创建终端")
this.newTerminal(current)
this.setContent(current.Terminal, current.RunLogs)
}
}
}
},
deep: true,
},
methods: {
ShowSetting() {
let res = this.CurrentTask(this.currentSrcipt);
if (res != null && res.pid != 0) {
console.log("进程的pid进程: ", res)
let process = ipcRenderer.sendSync('getProcess', {
action: '通过pid获取进程的CPU内存占用',
pid: res.pid
});
console.log("进程信息为:", process)
if (process.hasOwnProperty("isRunScript")) {
this.titleTips = process.message;
this.isRunScript = false;
this.$forceUpdate();
} else {
this.currentProcess = [process];
this.isRunScript = true;
}
} else {
this.isRunScript = false;
}
},
/**
* 展示package.json 配置文件
* @returns {Promise<void>}
* @constructor
*/
async ShowPackageJson() {
this.$nextTick(async () => {
// 避免每次点击回 配置 时候,重复初始化创建编辑器导致删除的时候多删文字的问题
let el = document.querySelector(`.package`);
if (el.children.length == 0) {
let JsonCode = ipcRenderer.sendSync('getPackageJson', {
action: '获取项目的配置文件',
project: this.projectName
});
console.log("读取到的 package.json文件代码为 : ", JsonCode)
let editor = await Utils.initVscodeEdit(this.projectName);
editor.setValue(JSON.stringify(JsonCode))
// 格式化json代码
setTimeout(() => editor.getAction('editor.action.formatDocument').run(), 300)
// 监听文件修改,实时获取代码,传给 主进程 来写到对应项目的package.json中
editor.onDidChangeModelContent((e) => {
// console.log(editor.getValue())
ipcRenderer.send('setPackageJson', {
action: '保存前端修改的配置文件',
project: this.projectName,
code: editor.getValue()
});
});
}
})
},
/**
* 安装插件
* @constructor
*/
async SearchList() {
if (this.PlugInput.length != 0 && this.Environment.length != 0) {
this.$store.dispatch('loading', "安装插件很慢,请耐心等待...")
ipcRenderer.send('installPlug', {
action: '安装插件',
project: this.projectName,
name: this.PlugInput,
status: this.Environment
});
this.isShowInstallDev = false
} else {
this.$notify.error({
title: '提示',
message: '插件名和安装环境都不能为空'
});
}
},
/**
* 判断tabs点击类型
* @param active
* @constructor
*/
SwitcTabs(active) {
console.log("你点击的tabs类型: ", active)
switch (active) {
case 'home':
this.ShowPackageJson()
break;
case 'task':
this.GetTaskListData()
break;
case 'dependent':
this.GetDependentListData()
break;
case 'outline':
console.log("概要...")
// this.ShowSetting()
break;
}
},
/**
* 在你点击的命令运行前,先初始化终端
*/
initTerminal(item) {
// 如果没有运行过终端,那么创建,避免重复创建
if (item.Terminal == null) {
this.newTerminal(item)
}
},
newTerminal(item) {
item.Terminal = new Terminal(this.TerminalConfig);
// 终端链接被点击时候,打开链接
item.Terminal.loadAddon(new WebLinksAddon((event, uri) => {
this.WebSite(uri)
}));
let refKey = this.projectName + "_" + item.ScriptName;
this.$nextTick(() => {
item.Terminal.open(this.$refs[refKey][0]);
})
},
/**
* 命令运行后nodejs 会通过 sendMessage 实时输出日志,
* @constructor
*/
GetShellMessage() {
let setLogCallback = (event, arg) => {
let e = this.$store.getters.GET_CURRENTTASK(arg.projectName)
let curr = e.filter(v => {
if (v.ScriptName == arg.ScriptName) {
return v;
}
});
let res = curr[0]
res.RunLogs += arg.log;
res.pid = arg.pid;
this.setContent(res.Terminal, arg.log)
}
ipcRenderer.on('sendMessage', setLogCallback);
this.removeListener('sendMessage', setLogCallback)
},
// 监听进程关闭的通知
onClose() {
let closeCallback = async (event, arg) => {
let e = this.$store.getters.GET_CURRENTTASK(arg.projectName)
console.log("***监听进程关闭的通知****: ", e)
let curr = e.filter(v => {
if (v.ScriptName == arg.ScriptName) {
return v;
}
});
let res = curr[0]
res.lock = true;
res.pid = 0;
res.IsRuning = "idle";
res.RunLogs += "命令已结束...";
res.Terminal.writeln(`命令已结束..`)
res.Terminal.writeln(`运行时间: ${arg.time}s..`)
}
ipcRenderer.on('close', closeCallback);
this.removeListener('close', closeCallback)
},
/**
* ipcRenderer.on 使用的是 EventEmitter
* Nodejs 的 EventEmitter.on 没有做重复检查,所以会出现 进入设置页面,再返回的时候
* ipcRenderer.on 多次监听导致命令运行+停止的日志出现重复。
* 所以这里需要对 on 的次数进行判断,多了则删除。
* @param channel 事件名
* @param listener 回调函数
*/
removeListener(channel, listener) {
let count = ipcRenderer.listenerCount(channel);
if (count > 1) {
ipcRenderer.removeListener(channel, listener)
}
},
/**
* 停止命令运行
* @param item
* @constructor
*/
StopCmd(item) {
item.lock = false;
let StopStatus = ipcRenderer.send('StopCmd', {
action: '运行项目',
name: this.projectName,
ScriptName: item.ScriptName
});
if (StopStatus != null) {
this.$notify.error({
title: '命令执行失败',
message: StopStatus
});
} else {
item.IsRuning = "idle";
}
},
/**
* 运行命令
* @param key
* @constructor
*/
RunCmd(item) {
if (item.lock) {
item.IsRuning = "runing";
this.initTerminal(item);
ipcRenderer.send('RunCmd', {
action: '运行项目',
name: this.projectName,
ScriptName: item.ScriptName
});
}
},
/**
* 格式化日志
* @param term
* @param value
* @param ln
*/
setContent(term, value, ln = true) {
if (value != undefined && value.indexOf('\n') !== -1) {
value.split('\n').forEach(
t => this.setContent(term, t)
)
return
}
if (typeof value === 'string') {
term[ln ? 'writeln' : 'write'](value)
} else {
term.writeln('')
}
},
/**
* 清除日志
* @param v
*/
clearLog(v) {
v.Terminal.clear()
v.RunLogs = ""
},
/**
* 滚动到底部
* @param v
* @constructor
*/
ToBottom(v) {
v.Terminal.scrollToBottom()
},
/**
* 复制日志
* @param v
*/
copyContent(v) {
const textarea = v.Terminal.textarea
const textValue = textarea.value
const emptySelection = !v.Terminal.hasSelection()
try {
if (emptySelection) {
v.Terminal.selectAll()
}
var selection = v.Terminal.getSelection()
textarea.value = selection
textarea.select()
document.execCommand('copy')
this.$notify({
title: '提示',
message: '日志复制成功',
type: 'success'
});
} finally {
textarea.value = textValue
if (emptySelection) {
v.Terminal.clearSelection()
}
}
},
/**
* 打开依赖的详情页
* @constructor
*/
PlugsInfo(item) {
// ipcRenderer.send('openWindow', {
// action: 'Home.controller/PlugInfo',
// parent: true,
// data: item
// });
console.log("item :", item)
},
/**
* 删除依赖包
* @param va
* @param type
* @param PackName
*/
deletePack(va, type, PackName) {
this.$confirm(`确定删除依赖:${PackName}吗?删除后,请修改项目代码!`, '删除提示', {
distinguishCancelAndClose: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('loading', "删除依赖很慢,请耐心等待...")
va.class = type
ipcRenderer.send('dDependencies', {
action: '删除依赖',
type: va,
project: this.projectName
});
})
},
// type == 0 生产环境依赖, == 1 开发环境依赖
/**
* 更新依赖包
* @param type
* @param PackName
*/
updatePack(type, PackName) {
this.$store.dispatch('loading', "更新依赖很慢,请耐心等待...")
ipcRenderer.send('uDependencies', {
action: '更新依赖',
type: type,
name: PackName,
project: this.projectName
});
},
/**
* 打开依赖包主页
* @param url
* @constructor
*/
WebSite(url) {
ipcRenderer.sendSync('openExternal', {
action: '浏览器打开',
url: url,
});
},
/**
* 获取启动命令
* @constructor
*/
GetTaskListData() {
if (this.CurrentTaskList.length == 0) {
let a = ipcRenderer.sendSync('GetTaskList', {
action: '获取项目的运行命令',
name: this.projectName
});
console.log("项目的运行命令:", a)
this.$store.commit('SET_TASKRUNLIST', {
id: this.projectName,
TaskRunList: a
});
console.log("ProjectList: ", this.$store.state.ProjectList)
}
},
/**
* 获取项目的依赖信息
* @constructor
*/
GetDependentListData() {
let status = ipcRenderer.sendSync('GetDependentList', {
action: '依赖信息',
name: this.projectName
});
if (status == undefined) {
this.$confirm(`项目的 node_modules 缺失部分依赖,点击确定重新安装依赖!`, '依赖缺失', {
distinguishCancelAndClose: true,
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
}).then(() => {
this.$store.dispatch('loading', "安装依赖很慢,请耐心等待...")
ipcRenderer.send('reDependent', {
action: '更新项目',
name: this.projectName
});
})
} else {
this.$store.commit('SET_DEPENDENTLIST', {
id: this.projectName,
DependentList: status
});
}
},
/**
* 重新安装依赖完成,后端发送的回调
*/
reDependentSuccess() {
let success = (event, arg) => {
this.$store.state.loadingService.close();
if (arg == "ok") {
this.GetDependentListData();
} else {
this.isShowError = true;
this.ErrorText = arg.message
}
}
ipcRenderer.on('reDependentSuccess', success)
this.removeListener('reDependentSuccess', success)
},
/**
* 顶部tabs点击事件
* @param v
*/
handleClick(v) {
console.log("handleClick 顶部tabs点击事件: ", v)
this.isNeedWatch = false;
// 切换tabs的时候将这个项目用户打开的对应tabs name 保存到 ProjectList 中
this.$store.commit('SET_CURRENTTABS', {id: this.projectName, currentTabs: this.activeName});
this.$router.push({query: {name: this.projectName, active: this.activeName, isNeedWatch: true}})
this.SwitcTabs(v.name)
},
/**
* 折叠面板被激活时候触发
* @param activeNames 折叠面板被点击 serve_2
* 解释serve 是这个项目所有命令的名称
* 2 是这个命令在数组中的下标
*/
async collapseChange(activeNames) {
console.log("1 折叠面板被激活时候触发: ", activeNames)
// !=0 表示你是展开折叠面板
if (activeNames.length != 0) {
let d = activeNames.split("_")
console.log("2 折叠面板被激活时候触发 d : ", d)
// 获取你点击的命令
this.currentSrcipt = d[0];
// 数组加下标获取到数组中对应的那个命令对象
let cTask = this.CurrentTaskList[d[1]]
console.log("3 折叠面板被激活时候触发 cTask : ", cTask)
// 你点击折叠面板标题的时候,获取 折叠面板下终端的html
// 如果 你点击的 折叠面板 表示曾经运行过了终端
// 并且 折叠面板 对应的 终端 没有html出现这种情况的原因是因为你切换了项目导致的
if (
cTask.Terminal != null &&
!(await this.isHasTerminal())
) {
console.log("4 折叠面板被激活时候触发 cTask.Terminal != null : ", cTask)
cTask.Terminal = null
this.newTerminal(cTask)
this.setContent(cTask.Terminal, cTask.RunLogs)
}
} else {
this.currentSrcipt = activeNames
}
},
// 判断是否有已经终端
async isHasTerminal() {
await this.$nextTick()
let el = document.querySelector(`.${this.projectName}_${this.currentSrcipt}`);
// 返回 true 表示有终端false 表示没有
return el && el.children.length == 0 ? false : true
},
},
computed: {
// 返回当前项目中的所有script运行命令
CurrentTaskList() {
return this.$store.getters.GET_CURRENTTASK(this.projectName)
},
// 返回当前你打开的命令
CurrentTask() {
return ScriptName => {
console.log("返回当前你打开的命令 ScriptName: ", ScriptName)
let curr = this.CurrentTaskList.filter(v => {
if (v.ScriptName == ScriptName) {
return v;
}
});
console.log("curr: [] ", curr)
return curr[0]
}
},
// 返回当前的依赖
CurrentDependenList() {
return this.$store.getters.GET_DEPENDENTLIST(this.projectName)
}
}
}

View File

@@ -0,0 +1,200 @@
const {ipcRenderer,app} = require("electron");
module.exports = {
data() {
return {
isShowDialog: false,
visible: false,
networkURL: '',
slider0pacity: '',
typeText: this.$store.state.packageType[this.$store.state.packageTypeSelect],
custom: {
type: "img",
text: "",
BgColor: "#000000",
textColor: "#ffffff",
activeBgColor: "#000000",
system: true
},
}
},
created() {
},
methods: {
// 重新选择nodejs的路径
selectEnvPath() {
ipcRenderer.send('selectEnvPath', {
action: '重新选择nodejs的路径'
});
},
TypeChange(index) {
let res = ipcRenderer.sendSync('setPackageTypeindex', {
action: '更新你选择的包管理工具',
keyName: "packageTypeSelect",
value: index,
packageType: this.$store.state.packageType[index]
});
console.log(res)
if (!res.status) {
this.$confirm(`出现错误:${res.message}`, '提示', {
distinguishCancelAndClose: true,
confirmButtonText: '我知道了',
showCancelButton: false,
roundButton: true,
type: 'error'
}).then(() => {
this.typeText = this.$store.state.packageType[this.$store.state.packageTypeSelect]
})
} else {
this.$store.state.packageTypeSelect = index;
}
},
// 开启透明度 的 checkbox 被选择的时候
openChange(val) {
ipcRenderer.send('setSettingConfig', {
action: '更新 checkbox 是否开启透明度的开关',
keyName: "mask.open",
value: val
});
},
// slider 滑动的时候 改变 body 上的 style
change(e) {
ipcRenderer.send('setSettingConfig', {
action: '更新 透明的的数值',
keyName: e == "opacity"
? "mask.opacity"
: "mask.blur"
,
value: e == "opacity"
? this.$store.state.mask.opacity
: this.$store.state.mask.blur
});
// 因为 --fill-color 这种方式需要通过style把颜色变量定义在父组件上
// 然后 子组件 使用变量,而这边 el-slider 组件的数值提示 tooltip 框
// 在 body 下,所以这边为 body 加 --fill-color
let body = document.querySelector("body")
body.style = `--fill-color: ${
this.$store.getters.get_list[this.$store.getters.get_theme].type == 'color'
? `${this.$store.getters.get_list[this.$store.getters.get_theme].BgColor}`
: `${this.$store.getters.get_list[this.$store.getters.get_theme].activeBgColor}`
}`
},
// 格式化选中的 透明度 数值
formatTooltip(val) {
return val / 10;
},
// 网络图片输入框被输入的时候触发
networkChange(value) {
if (value.length == 0 || !value.includes("http")) {
return false;
}
this.visible = false;
this.networkURL = ""
this.custom.BgColor = value;
this.custom.text = "网络图片";
},
// 删除主题
deleteTheme(item, index) {
console.log("item: ", item)
console.log("index: ", index)
console.log("index - 1: ", index - 1)
// 如果当前主题正是你点击要删除的主题
if (this.$store.getters.get_theme == index) {
// 那么设置当前主题为你点击主题的上一个
this.changeTheme(index - 1);
}
// 删除你点击的主题
this.$store.commit("set_themeList", this.$store.getters.get_list.filter(v => {
if (v.BgColor != item.BgColor) {
return v
}
}));
ipcRenderer.send('delete_themeList', {
action: '删除你点击的主题',
keyName: "list",
value: item
});
// this.$store.commit("push_themeList", this.$store.getters.get_list)
},
// 完成选择
completeSelection() {
if (/^#/.test(this.custom.BgColor)) {
this.$notify({
title: '提示',
duration: 2000,
type: 'warning',
message: '请上传图片!'
});
return false;
}
// 深拷贝一下,不能直接改变 BgColor要不然BgColor变了页面上的图片无法显示
let color = JSON.parse(JSON.stringify(this.custom))
color.BgColor = `url("${color.BgColor}")`;
this.$store.commit("push_themeList", color)
this.resetCustom()
this.isShowDialog = false;
},
// 重新选图
reselect() {
this.custom.text = ""
this.custom.BgColor = "#000000"
this.custom.textColor = "#ffffff"
this.custom.activeBgColor = "#000000"
},
// 选择上传文件
uploadFile(e) {
let file = e.target.files[0];
this.custom.text = file.name.split(".")[0];
this.custom.BgColor = file.path;
console.log(this.custom)
},
// 是否开机自启
changeIsOpen(status) {
this.$store.commit("set_isOpen", status)
ipcRenderer.send('setSettingConfig', {
action: '更新设置',
keyName: "isOpen",
value: status
});
},
// 点击切换主题
changeTheme(index) {
this.$store.commit('set_themeIndex', index)
ipcRenderer.send('setSettingConfig', {
action: '更新设置选中的主题下标',
keyName: "selectThemeIndex",
value: index
});
// 每次点击切换主题同时修改body
this.change()
},
// 重置样式的变量
resetCustom() {
this.custom = {
type: "img",
text: "",
BgColor: "#000000",
textColor: "#ffffff",
activeBgColor: "#000000",
system: true
}
}
}
}

View File

@@ -0,0 +1,16 @@
const { ipcRenderer } = require('electron');
let Welcome = new Vue({
el: '#app',
data: {
msg: '测试'
},
methods: {
OpenHome () {
ipcRenderer.send('openWindow', {
action: 'Home.controller/Home',
closeParent: true,
data: {}
});
}
}
});

View File

@@ -0,0 +1 @@
21274de1-398c-4c09-8da1-9da1510ed73e

View File

@@ -0,0 +1,52 @@
const Utils = require("../Utils/index.js");
const routes = [
{
path: '/',
redirect: {
name: 'main'
},
meta: {
keepAlive: false
}
},
{
path: '/main',
name: 'main',
component: {
template: Utils.Html("/Home/Main.html"),
...require("../page/Main.js")
},
meta: {
keepAlive: false
}
},
{
path: '/info',
name: 'info',
component: {
template: Utils.Html("/Home/ProjectInfo.html"),
...require("../page/ProjectInfo.js")
},
meta: {
keepAlive: true
}
},
{
path: '/setting',
name: 'setting',
component: {
template: Utils.Html("/Setting/Setting.html"),
...require("../page/Setting.js")
},
meta: {
keepAlive: true
}
}
];
const router = new VueRouter({
routes
})
module.exports = router

View File

@@ -0,0 +1 @@
fff304ef-3209-4fbf-b4d8-dffbb8bde3b6

View File

@@ -0,0 +1,147 @@
const {ipcRenderer} = require("electron");
Vue.use(Vuex)
console.log(window)
const store = new Vuex.Store({
state: {
loadingService: null,
ProjectList: [],
currentVsersion: null, // 保存当前版本号
isUpdate: false, // 决定 升级弹窗是否显示,
updateInfo: {}// 如果需要升级,那么这里面保存的就是升级的一些详细信息
},
getters: {
get_vsersion: state => {
return state.currentVsersion;
},
get_isOpen: state => {
return state.isOpen;
},
get_list: state => {
return state.list;
},
get_theme: state => {
return state.selectThemeIndex;
},
GET_PROJECT: state => {
return state.ProjectList
},
GET_CURRENTSCRIPT: (state) => (projectName, currentScript) => {
console.log(projectName, currentScript)
let a = state.ProjectList.find(v => v.FolderName == projectName);
console.log(a.TaskRunList.find(v => v.ScriptName = currentScript))
},
GET_CURRENTTASK: (state) => (id) => {
try {
let a = state.ProjectList.find(v => v.FolderName == id)
return a.TaskRunList;
} catch (e) {}
},
GET_DEPENDENTLIST: (state) => (id) => {
try {
let a = state.ProjectList.find(v => v.FolderName == id)
return a.DependentList;
} catch (e) {}
}
},
mutations: {
set_isUpdate(state, status) {
state.isUpdate = status
},
set_updateInfo(state, info) {
state.updateInfo = info
},
set_vsersion(state, vsersion) {
state.currentVsersion = vsersion
},
set_isOpen(state, status) {
state.isOpen = status
},
set_themeIndex(state, index) {
state.selectThemeIndex = index;
},
// 向主题数组里面追加用户新增的样式
push_themeList(state, data) {
state.list.push(data)
// 将用户自定义的配置保存到json数据库
ipcRenderer.send('push_themeList', {
action: '保存自定义主题配置',
keyName: "list",
value: data
});
},
set_themeList(state, data) {
state.list = data;
},
// 保存项目的数据
SET_LIST(state, data) {
if (data.type == "ChoiceFile") {
state.ProjectList.push(data.res);
} else {
if (data.res) {
state.ProjectList = data.res;
}
}
},
// 进入详情页后,点击 任务将从后端获取到的项目script命令
// 数据保存到对应 ProjectList 中
SET_TASKRUNLIST(state, params) {
state.ProjectList.forEach(v => {
if (v.FolderName == params.id) {
// v.TaskRunList.forEach(j => {
// if (j.Terminal != null)
// })
v.TaskRunList = params.TaskRunList
}
});
},
SET_DEPENDENTLIST(state, params) {
state.ProjectList.forEach(v => {
if (v.FolderName == params.id) {
v.DependentList = params.DependentList
}
});
},
// 修改 ProjectList 中的激活tabs
SET_CURRENTTABS(state, params) {
state.ProjectList.forEach(v => {
if (v.FolderName == params.id) {
v.CurrentTabs = params.currentTabs
}
})
},
set_loading(state, intloading) {
state.loadingService = intloading;
},
},
actions: {
loading({ commit }, title) {
let load = ELEMENT.Loading.service({
text: `${title}..`,
fullscreen: true
});
commit("set_loading", load)
},
checkUpdate({ commit }, type) {
let res = ipcRenderer.sendSync('checkUpdate', {
action: '检查新版本更新',
});
if (res.isUpdate) {
commit("set_updateInfo", res)
commit("set_isUpdate", res.isUpdate)
} else {
if (type != "default")
ELEMENT.Notification({
title: '检查升级',
message: '已是最新版本,无需升级',
type: 'warning'
});
}
}
},
modules: {}
})
module.exports = store

View File

@@ -0,0 +1 @@
5678f8c1-8086-4b5b-8d8f-33607e1cca0b

View File

@@ -0,0 +1,476 @@
@import "color";
[v-cloak]{
display: none;
}
.home {
display: flex;
box-shadow: 7px 7px 9px #1f1d1d47;
width: 100vw;
height: 100vh;
border-top: 0.5px solid @lineColoe;
border-radius: 10px;
user-select: none;
box-sizing: border-box;
position: relative;
.td {
height: 30px;
width: 100vw;
position: fixed;
top: 0;
left: 0;
z-index: 99;
}
.project_list {
height: 100%;
width: 21%;
border-right: 1px solid @lineColoe;
display: flex;
flex-direction: column;
position: relative;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
.back {
width: 100%;
height: 100%;
position: absolute;
z-index: -1;
border-bottom-left-radius: 10px;
background: rgba(0,0,0,0.4);
}
.mask {
width: 100%;
height: 100%;
position: absolute;
background-size: cover !important;
background-position: center !important;
z-index: -2;
border-bottom-left-radius: 10px;
}
.list_title {
font-size: 15px;
color: @textColor2;
}
.list_item {
overflow-y: scroll;
padding-right: 15px;
padding-left: 15px;
flex: 1;
padding-top: 30px;
li {
color: @textColor2;
padding: 9px 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 4px;
padding-left: 10px;
.left {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
img {
margin-right: 10px;
width: 20px;
height: 20px;
vertical-align: sub;
}
.icon {
font-size: 20px;
margin-right: 5px;
vertical-align: sub;
}
}
.right {
padding-right: 10px;
.icon {
margin-right: 5px;
}
}
}
}
.addfile {
display: flex;
align-items: center;
justify-content: space-between;
height: 35px;
padding: 0 15px;
font-size: 14px;
line-height: 35px;
cursor: pointer;
.left {
span {
margin-left: 7px;
font-size: 13px;
}
}
i {
font-size: 16px;
}
}
}
.project_info {
transition: all 0.8s cubic-bezier(0.58, -0.09, 0.45, 0.96);
overflow-y: scroll;
border-bottom-right-radius: 10px;
border-top-right-radius: 10px;
background: @while;
position: absolute;
right: 0;
top: 0;
height: 100%;
.full_screen {
position: absolute;
height: 100%;
width: 20px;
top: 0;
left: 0px;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
svg {
font-size: 18px;
opacity: 0;
cursor: pointer;
color: @textColor;
transition: all 0.8s;
}
&:hover {
svg {
opacity: 1;
}
}
}
}
.full {
width: 100%;
}
.half {
width: 79%;
}
}
.update_content {
flex: 1;
display: flex;
width: 100%;
img {
width: 100px;
height: 100px;
}
.c_text {
margin-left: 30px;
flex: 1;
height: 284px;
display: flex;
flex-direction: column;
.title {
font-size: 23px;
margin-bottom: 3px;
font-weight: 400;
}
.version {
font-size: 14px;
margin-bottom: 10px;
color: #464646;
}
ul {
padding-left: 22px;
flex: 1;
height: 219px;
overflow-y: scroll;
li {
margin-bottom: 5px;
color: #464646;
list-style: auto;
}
}
}
}
.tabContent {
padding-left: 18px;
}
.ProjectInfo,
.dependentList {
li {
display: flex;
align-items: center;
justify-content: space-between;
//border: 1px solid #eaeaea;
margin-bottom: 5px;
padding: 5px 8px;
border-radius: 5px;
cursor: pointer;
&:hover {
background-color: #d0d0d0;
}
}
}
.ProjectInfo {
height: 100%;
.icons {
display: flex;
align-items: center;
.icon {
width: 23px;
height: 23px;
}
}
#pane-home {
height: 100%;
.package {
width: 100%;
height: 100%;
}
}
.runList {
-webkit-app-region: no-drag;
border-top: none !important;
.terminals {
width: 100%;
color: #ccc;
border-radius: 4px;
box-sizing: border-box;
background: #1d2935;
height: 336px;
.xterm-render {
height: 294px;
padding-left: 5px;
}
.terminal_list {
padding-left: 15px;
}
.terminal_action {
background: #1d2935;
width: 100%;
height: 40px;
display: flex;
align-items: center;
box-sizing: border-box;
padding: 0 15px;
display: flex;
justify-content: space-between;
align-items: center;
.a_left {
.run {
font-size: 20px;
color: #c4e791;
cursor: pointer;
margin-right: 10px;
}
}
.a_right {
display: flex;
align-items: center;
.icon {
font-size: 20px;
margin-right: 10px;
cursor: pointer;
&:last-child {
margin-right: 0 !important;
font-size: 18px !important;
}
}
}
}
}
.run_icon {
width: 10px;
height: 10px;
border-radius: 100%;
margin-right: 20px;
background: #c4e791;
//background: var(--fill-color);
}
.icon-npm1 {
margin-right: 10px;
color: #d52a29;
font-size: 20px;
}
.text {
font-size: 14px;
flex: 1;
display: flex;
width: 90%;
.ScriptName {
}
.ScriptShell {
font-size: 12px !important;
margin-left: 9px;
color: @textColor;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
//.left {
// display: flex;
// align-items: center;
// flex: 1;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
//
// i {
// margin-right: 10px;
// color: #d52a29;
// font-size: 25px;
// }
//
// span {
// font-size: 12px;
// margin-left: 9px;
// color: @textColor;
// }
//}
}
}
.dependent_action {
margin-bottom: 25px;
.title {
font-size: 17px;
//font-weight: bold;
margin-bottom: 9px;
//border-bottom: 1px solid #E4E7ED;
padding-bottom: 8px;
}
.empty {
text-align: center;
margin-top: 15%;
i {
font-size: 45px;
margin-bottom: 15px;
color: #706f6f;
}
.title_tips {
font-size: 14px;
color: @textColor3;
margin-bottom: 6px;
}
.tips {
font-size: 12px;
color: @lineColoe;
}
}
.action_list {
height: 45px;
display: flex;
align-items: center;
padding-left: 28px;
.icon {
cursor: pointer;
font-size: 25px;
color: @textColor;
}
}
}
.dependent {
margin-bottom: 30px;
.title {
font-size: 17px;
//font-weight: bold;
margin-bottom: 9px;
border-bottom: 1px solid #E4E7ED;
padding-bottom: 8px;
}
.dependentList {
padding-left: 20px;
.left {
display: flex;
align-items: center;
flex: 1;
img {
margin-right: 10px;
width: 25px;
height: 25px;
}
.text {
display: flex;
flex-direction: column;
.dTitle {
margin-bottom: 4px;
}
.version {
font-size: 12px;
color: @textColor;
}
}
}
.right {
.icon {
font-size: 20px;
margin-right: 15px;
&:last-child {
margin-right: 0 !important;
}
}
}
.no_dependent {
margin-top: 50px;
color: @textColor;
.icon-empt {
font-size: 35px;
}
}
}
}
.chiicon {
text-align: center;
padding: 15px;
border-radius: 4px;
&:hover {
box-shadow: 0px 2px 11px #e4dddd;
}
.icon {
font-size: 40px;
}
}
.main {
h3 {
margin-bottom: 20px;
}
p {
margin-bottom: 30px;
color: @textColor;
}
ul {
padding-left: 40px;
li {
list-style: decimal;
margin-bottom: 15px;
color: @textColor;
}
}
.logo {
text-align: center;
img {
width: 188px;
margin-top: 5%;
margin-bottom: 6%;
margin-left: auto;
margin-right: auto;
}
}
}
.Dependencies {
.Dependencies_item {
display: flex;
align-items: center;
margin-bottom: 20px;
}
}

View File

@@ -0,0 +1,3 @@
.PlugInfo {
}

View File

@@ -0,0 +1,210 @@
@import "color";
.Setting {
.title {
font-size: 18px;
align-items: center;
display: flex;
padding-bottom: 15px;
border-bottom: 0.6px solid @lineColoe;
i {
font-size: 15px;
cursor: pointer;
}
span {
margin-left: 10px;
}
}
.warp {
margin: 20px 0;
border-bottom: 0.5px solid @lineColoe;
.warp_title {
font-size: 17px;
color: #303133;
margin-bottom: 20px;
display: flex;
align-items: flex-end;
justify-content: space-between;
//.custom {
// margin-right: 15px;
// font-size: 14px;
// color: #8d8d8d;
// cursor: pointer;
//}
}
.theme_list {
display: flex;
flex-wrap: wrap;
margin-bottom: 20px;
li {
width: 18%;
height: 50px;
border-radius: 6px;
margin-bottom: 11px;
text-align: center;
margin-right: 15px;
cursor: pointer;
position: relative;
display: flex;
justify-content: flex-end;
background-size: cover !important;
background-position: center !important;
span {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
font-size: 14px;
}
i {
position: absolute;
right: 7px;
top: 6px;
font-size: 20px;
}
.back {
width: 50%;
height: 100%;
border-radius: 6px;
}
}
.add {
width: 18%;
.custom {
height: 50px;
border-radius: 6px;
margin-bottom: 11px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
border: 0.6px solid #8b8b8b;
color: #8b8b8b;
cursor: pointer;
}
}
}
.login_start {
font-size: 14px;
display: flex;
align-items: center;
margin-bottom: 20px;
.silder_opacity {
width: 20% ;
.el-slider__runway {
margin: 0 !important;
.el-slider__button-wrapper {
width: 18px !important;
height: 18px !important;
top: -9px !important;
}
}
.el-slider__bar {
background-color: var(--fill-color) !important;
}
.el-slider__button {
border-color: var(--fill-color) !important;
}
}
.el-checkbox {
margin-right: 10px;
}
.version {
display: flex;
align-items: center;
.current_version {
margin-right: 15px;
}
.check_version {
padding: 1px 8px;
border: 1px solid var(--fill-color);
border-radius: 10px;
font-size: 12px;
color: var(--fill-color);
cursor: pointer;
}
}
.path {
}
.el-link {
margin-left: 15px;
color: var(--fill-color) !important;
&:hover::after {
border-bottom: 1px solid var(--fill-color) !important;
}
}
}
.children {
margin-left: 15px;
}
}
.custom_dialog {
.el-dialog__body {
padding-bottom: 15px !important;
}
.custom_wrap {
height: 300px;
.action {
height: 100%;
display: flex;
.upload_local,
.upload_network {
width: 50%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
flex-direction: column;
cursor: pointer;
text-align: center;
input {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
}
}
.upload_network {
}
p {
margin-top: 9px;
}
i {
font-size: 30px;
}
}
.el-image {
overflow: scroll !important;
.el-image__inner {
height: auto !important;
}
}
}
.custom_footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 17px;
.left {
display: flex;
align-items: center;
.color_block {
display: flex;
align-items: center;
margin-right: 12px;
span {}
input {
width: 18px;
height: 22px;
}
}
}
.right {}
}
}
}

View File

@@ -0,0 +1,37 @@
@import "color";
.welcome {
padding: 55px 30px 30px 30px;
text-align: center;
border-radius: 10px;
//background: #fff;
//width: calc(100% - 15px);
//height: calc(100% - 15px);
box-sizing: border-box;
//border: 0.5px solid @lineColoe;
//box-shadow: 7px 7px 9px #1f1d1d47;
user-select: none;
.title {
font-size: 25px;
letter-spacing: 2px;
margin-top: 15px;
margin-bottom: 20px;
}
.desc {
font-size: 15px;
color: @textColor3;
width: 74%;
margin: auto;
margin-bottom: 11%;
}
.version {
font-size: 13px;
color: @textColor;
margin-top: 8px;
}
.logo {
width: 188px;
margin-top: 5%;
margin-bottom: 7%;
}
}

View File

@@ -0,0 +1,174 @@
@import "color";
html, body {
width: 100%;
height: 100%;
-webkit-app-region: drag;
margin: 0;
}
ul, li, h1,h2,h3,h4,h5,h6,p{
padding: 0;
margin: 0;
list-style: none;
}
a {
text-decoration: none;
}
input {
border: none;
outline: none;
background: #fff0;
padding: 0;
cursor: pointer;
}
#app {
height: 100%;
}
.nodata {
text-align: center;
margin-top: 22px;
color: @textColor;
}
//不可以拖动也不可以选中文字
.nodragText {
-webkit-user-select: none;
-webkit-app-region: no-drag;
}
.dragText {
-webkit-user-select: text;
-webkit-app-region: no-drag;
}
click {
cursor: pointer;
}
//不可以拖动
.nodrag {
-webkit-app-region: no-drag;
}
//可以拖动
.drag {
-webkit-app-region: drag !important;
}
// 页面切换动画
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
::-webkit-scrollbar{
width : 0;
height: 0;
}
.padding30 {
padding: 30px 30px 0px 30px;
box-sizing: border-box;
}
//复写ui框架的样式
.el-tabs__nav-wrap::after,
.el-tabs__active-bar {
height: 1px !important;
}
.is-dark {
background: var(--fill-color) !important;
}
.el-input__inner {
border: none !important;
}
.el-button {
padding: 5px 12px !important;
border-radius: 13px !important;
user-select: none !important;
box-shadow: 0.5px 1px 1px #e4e4e4 !important;
}
.el-tabs {
display: flex;
height: 100%;
flex-direction: column;
}
.el-form-item {
margin-bottom: 0 !important;
}
.el-tabs__content {
flex: 1;
overflow-y: scroll !important;
}
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.el-dropdown {
color: inherit !important;
}
.el-message-box,
.el-dialog {
border-radius: 10px !important;
background: #ededed;
}
.el-tabs__item:focus.is-active.is-focus:not(:active) {
box-shadow: none !important;
}
.el-tabs__item.is-active,
.el-tabs__item:hover{
color: var(--fill-color) !important;
}
.el-tabs__active-bar {
background-color: var(--fill-color) !important;
}
.el-checkbox {
.el-checkbox__label {
color: var(--fill-color) !important;
}
}
.el-checkbox__input.is-checked .el-checkbox__inner,
.el-checkbox__input.is-indeterminate .el-checkbox__inner {
background-color: var(--fill-color) !important;
border-color: var(--fill-color) !important;
}
.el-checkbox__input.is-focus .el-checkbox__inner,
.el-checkbox__inner:hover {
border-color: var(--fill-color) !important;
}
.el-dialog__footer {
padding: 10px 20px 16px !important;
}
.el-dialog__body {
padding: 39px 20px 1px 20px !important;
}
.el-dialog__header {
border-bottom: 0.5px solid #e9e9e9;
}

View File

@@ -0,0 +1,6 @@
@while: #fff;
@themeBack: #e5e3e4;
@textColor: #777c83;
@textColor2: #222;
@textColor3: #929192;
@lineColoe: #d8d6d7;

View File

@@ -0,0 +1,172 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 5;
}
.xterm .xterm-helper-textarea {
padding: 0;
border: 0;
margin: 0;
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer {
cursor: pointer;
color: #3a8ee6;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
opacity: 0.5;
}
.xterm-underline {
text-decoration: underline;
}

View File

@@ -0,0 +1 @@
7e4b2960-67b6-4e37-b953-861eb2422c55

View File

@@ -0,0 +1 @@
h2 底部

View File

@@ -0,0 +1 @@
.action header

View File

@@ -0,0 +1 @@
ad4c9df0-ddf8-4ce6-9c8e-0ca94e36c2e8

View File

@@ -0,0 +1,21 @@
doctype html
html
head
meta(charset="utf-8")
meta(http-equiv="X-UA-Compatible" content="IE=edge")
meta(name="viewport" content="width=device-width, initial-scale=1")
meta(name="renderer" content="webkit")
meta(http-equiv="Cache-Control" content="no-siteapp")
link(rel='stylesheet', href='../../assets/css/clear.css')
each v in globals !== undefined ? globals.css : []
link(rel='stylesheet', href=`${v}`)
each v in globals !== undefined ? globals.js : []
script(src=`${v}`)
block title
body
// - if (header)
// include ../components/header
block content
block script

View File

@@ -0,0 +1 @@
5f13dc73-1b47-47f3-8e03-d9d8b06fea3c

View File

@@ -0,0 +1 @@
68dc1f7b-f790-4826-aedb-632b7bbb9ad1

View File

@@ -0,0 +1,92 @@
extends ../../layout/index
block title
title #{title}
link(rel='stylesheet', href='../../assets/css/Home.css')
link(rel='stylesheet', href='../../assets/css/xterm.css')
block content
#app(v-cloak)
.home
.project_list.nodrag
.mask(v-bind:style="{ background : $store.getters.get_list[$store.getters.get_theme].BgColor, filter: `blur(${$store.state.mask.blur}px)`}")
.back(v-if="$store.getters.get_list[$store.getters.get_theme].type == 'img' && $store.state.mask.open " v-bind:style="{ background: `rgba(0,0,0,${$store.state.mask.opacity})` }")
ul.list_item.nodrag
li(:title="v.name" v-bind:style="{ color : $store.getters.get_list[$store.getters.get_theme].textColor, background: i == clickProjectIndex ? $store.getters.get_list[$store.getters.get_theme].activeBgColor : '' }"
@click="openInfo(v,i)"
v-for="(v,i) in $store.getters.GET_PROJECT")
.left
img(v-if="v.FolderIcon.includes('.png')"
src="../../assets/img/GenericFolder.png")
svg.icon(v-else="" aria-hidden='true' @click.stop="OpenPropject('cmd', v)" title="在终端打开")
use(v-bind:xlink:href='v.FolderIcon')
| {{ v.name }}
.right(v-if="i == clickProjectIndex" @click.stop="")
svg.icon(aria-hidden='true' @click.stop="OpenPropject('file', v)" title="在文件管理打开")
use(xlink:href='#icon-wenjianjia2')
svg.icon(aria-hidden='true' @click.stop="OpenPropject('cmd', v)" title="在终端打开")
use(xlink:href='#icon-config-command')
el-dropdown(size="medium" @command="ShowActionClick")
svg.icon(aria-hidden='true')
use(xlink:href='#icon-gengduo')
el-dropdown-menu(slot='dropdown')
el-dropdown-item(v-for="(item,index) in MoreList" v-bind:icon="item.icon" v-bind:key="index"
v-bind:command="[ v.name, item.id ]") {{ item.title }}
.addfile(v-bind:style="{ color : $store.getters.get_list[$store.getters.get_theme].textColor}")
.left(@click="ChoiceFile")
i.el-icon-circle-plus-outline
span 导入项目
i.el-icon-setting(@click="$router.push('/setting')")
// 需要根据当前是否全屏,动态添加类名。同时 因为全屏后左下角是直角,所以需要动态判断处理圆角
.project_info.nodrag(v-bind:class=" menuStatus ? 'full' : 'half' "
v-bind:style="{ 'border-bottom-left-radius': menuStatus ? '10px': '0px' }")
keep-alive
transition(name="fade")
router-view(v-if="$route.meta.keepAlive" class="padding30")
router-view(v-if="!$route.meta.keepAlive" class="padding30")
//全屏箭头
.full_screen
svg.icon(aria-hidden='true' v-bind:style="{ transform : `rotate(${menuIconRotate}deg)`}"
@mouseover="Mouseover"
@click="changeMenu"
@mouseout="Mouseover")
use(xlink:href='#icon-jinru')
.td.drag
el-dialog.nodrag(title="重命名项目"
v-bind:close-on-click-modal="true"
v-bind:visible.sync="isEditDialog")
el-input(v-model="EditProjectName" placeholder="请输入新的文件夹名称")
.dialog-footer(slot='footer')
el-button(@click='SubmitEdit' round) 确 定
el-dialog.nodrag(title="请选择项目图标" v-bind:visible.sync="isIconDialog")
el-row
el-col.chiicon(v-bind:span="6"
v-for="(item,index) in IconList"
v-bind:title="item.title")
svg.icon(aria-hidden='true' @click="SelectIcon(item)")
use(v-bind:xlink:href="item.icon")
el-dialog(title='检查升级', v-bind:show-close="false", v-bind:close-on-click-modal="false" ,v-bind:visible.sync='$store.state.isUpdate', width='50%',)
.update_content
img(src="../../assets/img/logo.png")
.c_text
.title {{ $store.state.updateInfo.title }}
.version 版本v{{ $store.state.updateInfo.version }}
ul
li(v-for="(item,index) in $store.state.updateInfo.detailed" v-bind:key="index") {{ item }}
span.dialog-footer(slot='footer')
el-button(@click='$store.state.isUpdate = false ') 下次一定
el-button(type='primary', @click='downNowDmg($store.state.updateInfo.down)') 立即更新
block script
script(src="../../assets/js/page/Home.js")

View File

@@ -0,0 +1,17 @@
.main
.logo
img(src="../../assets/img/project_1.png")
h3 Easy Project
p 一款前端工程化项目的GUI管理工具能够一键导入本地的前端项目帮助您将零碎零散的前端项目统一化管理和配置。
h3 功能列表
ul
li 支持实时解析项目的开发和正式环境依赖,通过简洁的页面展示给开发者,开发者可以通过鼠标操作,对依赖包进行一键升级,删除,更新到指定版本,查看依赖包的详情和使用文档等。
li 支持自动解析项目的运行命令,通过鼠标快速点击实现项目的运行,停止,查看终端日志,点击终端日志打开网页链接。
li 能够查看项目的CPU内存等内存占用情况项目依赖关系分析等最大限度用简单的方式去避免开发者频繁打开各种命令行窗口输入各种命令在无数的命令行窗口中迷失自我的尴尬情况。
li 能够对项目进行个性化的设置,可以通过负载均衡最大限度发挥电脑的性能来提高我们前端项目的访问速度,一键中止被恶意占用的端口,在项目仪表盘中一键添加常用命令,设置开机自启,夜间模式,切换主题,查看最新技术文章等等等...
//h3 项目概览
//ul
// li
// a() Github: http://github.com/helpcode

View File

@@ -0,0 +1,130 @@
.ProjectInfo
el-tabs(v-model="activeName" @tab-click="handleClick"
v-bind:style="{'--fill-color': $store.getters.get_list[$store.getters.get_theme].type == 'color' ? $store.getters.get_list[$store.getters.get_theme].BgColor : $store.getters.get_list[$store.getters.get_theme].activeBgColor }")
el-tab-pane(name='outline' v-bind:key="projectName")
span(slot='label' class="icons")
svg.icon(aria-hidden='true')
use(xlink:href="#icon-shouyeshouye")
span &nbsp;概要
.tabContent
el-form(ref='form', label-width='90px')
el-form-item(label='项目名称:')
span.dragText uni-project
el-form-item(label='文件夹名:')
span.dragText uni-project
el-form-item(label='磁盘路径:')
span.dragText /Users/bmy/Desktop/uni-project
el-divider
el-form-item(label='源码大小:')
span.dragText 3MB
el-form-item(label='依赖统计:')
span.dragText 13 个依赖
el-link(v-bind:underline='false') 查看
el-tab-pane(name='home')
span(slot='label' class="icons")
//i(class="iconfont icon-yibiaopan") &nbsp;仪表盘
//img(src="../../assets/img/SidebarHomeFolder.png")
svg.icon(aria-hidden='true')
use(xlink:href="#icon-10json")
span &nbsp; 配置
div(class="package" v-bind:key="projectName")
el-tab-pane(name='task' v-bind:key="`${projectName}`")
span(slot='label' class="icons")
//i(class="iconfont icon-config-command") &nbsp;任务
//img(src="../../assets/img/SidebarAllMyFiles.png")
svg.icon(aria-hidden='true')
use(xlink:href="#icon-config-command")
span &nbsp;任务
.tabContent
el-collapse.runList(@change="collapseChange" v-bind:accordion="true")
el-collapse-item(v-bind:name="`${v.ScriptName}_${i}`" v-for="(v,i) in CurrentTaskList"
v-bind:key="`${projectName}_${v.ScriptName}_${i}`")
template(slot="title")
.run_icon(v-if="v.IsRuning == 'runing'")
svg.icon.icon-npm1(aria-hidden='true' v-else)
use(xlink:href='#icon-npm1')
.text
span.ScriptName {{ v.ScriptName }}
span(v-if="v.IsRuning == 'runing'") &nbsp;Pid{{ v.pid }}
span.ScriptShell {{ v.ScriptShell }}
.terminals
.terminal_action
.a_left
svg.icon.run(aria-hidden='true' v-bind:title="v.IsRuning == 'idle' ? '点击运行' : '点击停止' "
@click="v.IsRuning == 'idle' ? RunCmd(v) : StopCmd(v) ")
use(v-bind:xlink:href=" v.IsRuning == 'idle' ? '#icon-qidong' : '#icon-zanting2' ")
.a_right
svg.icon.icon-copy(aria-hidden='true' title="复制" @click="copyContent(v)")
use(xlink:href='#icon-copy')
svg.icon.icon-dibu(aria-hidden='true' title="到底部" @click="ToBottom(v)")
use(xlink:href='#icon-dibu')
svg.icon.icon-clearup(aria-hidden='true' title="清除日志" @click="clearLog(v)")
use(xlink:href='#icon-delete')
div(v-bind:ref="`${projectName}_${v.ScriptName}`"
v-bind:key="projectName+v.ScriptName"
v-bind:class="`terminal_list ${projectName}_${v.ScriptName}`")
el-tab-pane(name='dependent')
span(slot='label' class="icons")
//i(class="iconfont icon-yilaiguanxi") &nbsp;依赖
//img(src="../../assets/img/SidebarDropBoxFolder.png")
svg.icon(aria-hidden='true')
use(xlink:href="#icon-gengduoguanxi")
span &nbsp;依赖
.tabContent
.dependent_action
.title 安装依赖
.action_list
el-tooltip(effect='dark', content='安装依赖', placement='bottom-start')
svg.icon(aria-hidden='true' @click="isShowInstallDev = true")
use(xlink:href='#icon-financial_install')
//- 正式环境依赖
.dependent(v-for="(v,i) in CurrentDependenList" v-bind:key="i")
.title {{ v.title }}
ul.dependentList
div(v-if="v.list.length != 0")
li.item.nodrag(v-for="(va,index) in v.list" v-bind:key="index" @click="PlugsInfo(va)")
.left
img(v-bind:src="va.logoImg")
.text
span.dTitle
| {{ va.name }}
span.version &nbsp;&nbsp; 版本:{{ va.currentVersion }}
span.version {{ va.description }}
.right
svg.icon.icon-shengji(aria-hidden='true' v-bind:title="'升级 '+va.name" @click.stop="updatePack(i, va.name)")
use(xlink:href='#icon-shengji')
svg.icon.icon-delete(aria-hidden='true' v-bind:title="'删除:'+va.name" @click.stop="deletePack(va,i, va.name)")
use(xlink:href='#icon-delete')
svg.icon.icon-shouyeshouye(aria-hidden='true' title="官网" @click.stop="WebSite(va.website)")
use(xlink:href='#icon-shouyeshouye')
.no_dependent(v-else)
svg.icon.icon-empt(aria-hidden='true')
use(xlink:href='#icon-empty')
div 无依赖
el-dialog(title='操作错误', v-bind:destroy-on-close="true"
,v-bind:show-close="false"
,v-bind:visible.sync='isShowError', width='60%')
.errorText(v-html="ErrorText")
span.dialog-footer(slot='footer')
el-button(type='primary', @click='isShowError = false') 确 定
el-dialog.Dependencies.nodrag(title="安装依赖" v-bind:visible.sync="isShowInstallDev"
@close="isShowInstallDev = false"
width="60%" v-bind:close-on-click-modal="false" v-bind:destroy-on-close="true")
.Dependencies_item
el-input.input-with-select(v-model="PlugInput" placeholder='请输入插件名' clearable)
el-select(placeholder='选择安装环境' v-model="Environment" clearable)
el-option(label='生产环境', v-bind:value='0')
el-option(label='开发环境', v-bind:value='1')
el-button(plain @click="SearchList") 安 装
.nodata(v-else) 没有数据

View File

@@ -0,0 +1 @@
2c7b3364-a2c8-4c89-a043-190ec49c950d

View File

@@ -0,0 +1,13 @@
extends ../../layout/index
block title
title #{title}
link(rel='stylesheet', href='../../assets/css/PlugInfo.css')
block content
#app
.PlugInfo PlugInfo
block script
script(src="../../assets/js/page/PlugInfo.js")

View File

@@ -0,0 +1 @@
a5a530a8-507a-4fa1-9518-27ff186de94e

View File

@@ -0,0 +1,109 @@
.Setting
.title
i(class="el-icon-arrow-left" @click="$router.back()")
span 偏好设置
div.warp
.warp_title
span 主题设置
//- span.custom 自定义
ul.theme_list
li(v-for="(item,index) in $store.getters.get_list"
@click="changeTheme(index)"
v-bind:style="{ background : item.BgColor, color : item.textColor }")
span {{ item.text }}
i.el-icon-circle-close(v-if="item.system" title="删除" @click.stop="deleteTheme(item,index)")
.back(v-bind:style="{ background : item.activeBgColor }")
el-popover(class="add" placement='bottom-start', title='注意(必看):', width='280', trigger='hover')
div 1建议使用竖图尺寸壁纸显示效果最佳
div 2本地图片改文件名为主题名长度<=8
div 3本地图片上传后勿要再移动图片位置
div 4自定义主题不会上传您的图片请安心
.custom(slot='reference' @click="isShowDialog = true")
i.el-icon-plus
.login_start
span 开启透明度:
el-checkbox(v-model='$store.state.mask.open'
@change="openChange"
v-bind:style="{'--fill-color': $store.getters.get_list[$store.getters.get_theme].type == 'color' ? $store.getters.get_list[$store.getters.get_theme].BgColor : $store.getters.get_list[$store.getters.get_theme].activeBgColor }")
el-slider(v-if="$store.state.mask.open"
class="silder_opacity"
input-size="mini"
@input="change('opacity')"
v-bind:style="{'--fill-color': $store.getters.get_list[$store.getters.get_theme].type == 'color' ? $store.getters.get_list[$store.getters.get_theme].BgColor : $store.getters.get_list[$store.getters.get_theme].activeBgColor }"
v-bind:max="1"
tooltip-class="tooltip"
v-bind:step="0.025"
v-model='$store.state.mask.opacity')
.login_start
span(style="margin-right: 9px;") 背景模糊度:
el-slider(class="silder_opacity"
input-size="mini"
@input="change('blur')"
v-bind:style="{'--fill-color': $store.getters.get_list[$store.getters.get_theme].type == 'color' ? $store.getters.get_list[$store.getters.get_theme].BgColor : $store.getters.get_list[$store.getters.get_theme].activeBgColor }"
v-bind:max="20"
tooltip-class="tooltip"
v-bind:step="0.025"
v-model='$store.state.mask.blur')
.login_start
span(style="margin-right: 9px;") 包管理工具选择:
el-select(v-model="typeText", placeholder="请选择", @change="TypeChange")
el-option(v-for='(item, index) in $store.state.packageType', v-bind:key="index", v-bind:label="item", v-bind:value="index")
.login_start
span(style="margin-right: 9px;") Node 解释器:
.path {{ $store.state.envVariable }}
el-link(@click="selectEnvPath" target='_blank' v-bind:style="{'--fill-color': $store.getters.get_list[$store.getters.get_theme].type == 'color' ? $store.getters.get_list[$store.getters.get_theme].BgColor : $store.getters.get_list[$store.getters.get_theme].activeBgColor }") 自定义
div.warp
.warp_title 系统设置
.login_start
span 是否开机自启:
el-checkbox(v-model='$store.state.isOpen' v-bind:key="$store.getters.get_theme"
@change="changeIsOpen"
v-bind:style="{'--fill-color': $store.getters.get_list[$store.getters.get_theme].type == 'color' ? $store.getters.get_list[$store.getters.get_theme].BgColor : $store.getters.get_list[$store.getters.get_theme].activeBgColor }")
.login_start
span 版本:
.version
.current_version 当前版本 v{{$store.getters.get_vsersion}}
.check_version(@click="$store.dispatch('checkUpdate')") 检查更新
el-dialog.custom_dialog(title='自定义主题', :visible.sync='isShowDialog', width='80%')
.custom_wrap
.action(v-if="/^#/.test(custom.BgColor)")
.upload_local
input(type="file" @change="uploadFile")
i.el-icon-upload2
p 本地上传
.upload_network
el-popover(placement='left', width='180', v-model='visible')
el-input(v-model='networkURL', clearable, placeholder='粘贴图片地址'
@input="networkChange")
div(slot='reference')
i.el-icon-upload
p 网络上传
el-image(style="width: 100%;height: 100%;" v-else v-bind:src='custom.BgColor | ', fit='cover')
.custom_footer
.left
.color_block
span 文字色
input(type="color"
v-bind:value="custom.textColor"
v-model="custom.textColor")
.color_block
span 激活背景色
input(type="color" v-bind:value="custom.activeBgColor" v-model="custom.activeBgColor")
.right
el-button(@click='reselect') 重新选图
el-button(type='primary', @click='completeSelection') 完成选择
link(rel='stylesheet', href='../../assets/css/Setting.css')

View File

@@ -0,0 +1 @@
26594073-16dc-4101-9ea5-3c6d379d7b47

View File

@@ -0,0 +1,17 @@
extends ../../layout/index
block title
title #{title}
link(rel='stylesheet', href='../../assets/css/Welcome.css')
block content
#app
.welcome
.title Easy Project
img.logo(src="../../assets/img/project_1.png")
.desc 统一管理组织凌乱无序的软件项目,带来一站式的快感。
el-button.nodrag(type="text" @click="OpenHome") 开始使用
block script
script(src="../../assets/js/page/Welcome.js")

1
src/core/.pydio Normal file
View File

@@ -0,0 +1 @@
42927cff-8842-4b5c-98b9-4ef72d76be57

View File

@@ -0,0 +1 @@
ae18ca61-49b8-41bd-b4e6-8d01591c8336

View File

@@ -0,0 +1,63 @@
import Config from "@config/Index.config";
import Utils from "@utils/Index.utils";
import {Http} from "@net/Http.net";
/**
* @Ipc()
* 实现居中IPC的注册用在controller方法上
* @param IpcParams 传入需要被注册的ipc类
* @constructor
*/
export function Ipc(IpcParams: { new (...args: any[]): {}; }[] ) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
/**
* 因为TS没有提供像java那种可以直接提取某个类中所有方法的系统方法
* 这边原本使用 Object.getOwnPropertyNames 能够正常提取类中方法,但是如果类中有被 @Inject() 依赖注入的属性
* 那么这个属性也会被 (new IpcParams())[val](),这样是肯定报错的,所以做下面约定!
* 在IPC 类中,如果在类的属性上使用 @Inject() 注入。那么请在类的属性名前加上 _例如:
* @Inject()
* private readonly _Net!: Http;
*/
IpcParams.forEach((val: { new (): any; } ) => {
Object.getOwnPropertyNames(val.prototype).splice(1)
.filter(f => !f.includes("_"))
.forEach(v => (new val())[v]())
})
}
}
/**
* @CreateApplicationIpc()
* 实现全局IPC的注册
* 能够自动实例化传入的类数组中的所有类,并自动调用类中的所有的方法
* @param IpcParams 类数组,实例:[ classIpc1, classIpc2, classIpc3 ]
* @constructor
*/
export function CreateApplicationIpc(IpcParams: { new (...args: any[]): {}; }[]): any {
return (_constructor: { new (...args: any[]): {}; } ) => {
IpcParams.forEach((val: { new (): any; } ) => {
Object.getOwnPropertyNames(val.prototype).splice(1)
.filter(f => !f.includes("_"))
.forEach(v => (new val())[v]())
})
}
}
/**
* @Render()
* 创建窗口
* @param templateName 要渲染的模板名称
* @constructor
*/
export function Render(templateName?: string) {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
/**
* 如果被打上注解的类中某个方法是配置文件中的默认启动窗口,那么调用这个方法来创建窗体
* 其他的方法一律不管由js前端渲染进程触发ipc创建
*/
if (propertyKey.includes((Config.StartPage.split("/"))[1])) {
// 如果没有传参数,那么采用方法名来创建窗体!
templateName == undefined ? Utils.startWindows(target,propertyKey) : Utils.startWindows(target,templateName)
}
}
}

View File

@@ -0,0 +1,40 @@
import Config from "@config/Index.config";
import {Http} from "@net/Http.net";
/**
* @GET()
* 用在service类的方法上。
* 如果不传参数将自动根据方法名获取接口请求的地址
* 如果传参了,那么使用用户传入的地址去发送网络请求
*
* 如果 useHandle 为 true 那么方法将获得ajax请求的数据
* 否则 不会将数据给方法
* @param RequestParams
* @constructor
*/
export function GET (RequestParams?: { url?: string, useHandle?: boolean, header?: { [index: string]: any } }) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let url: string = Config.ApiUrl.ApiList[propertyKey],
handle: boolean = false,
header: object = {};
(RequestParams && RequestParams.url)
? url = RequestParams.url
: (RequestParams && RequestParams.useHandle)
? handle = RequestParams.useHandle
: (RequestParams && RequestParams.header)
? header = RequestParams.header
: '';
let OldMethods = descriptor.value;
if (handle) {
descriptor.value = async (data: { [key:string]: any }) =>
await OldMethods.apply(target, [ await (new Http).GET({ url: url, data: data, header: header }) ])
return descriptor
} else {
descriptor.value = async (data: { [key:string]: any }) =>
await (new Http).GET({ url: url, data: data, header: header })
return descriptor
}
}
}

View File

@@ -0,0 +1,37 @@
import 'reflect-metadata';
import IocModel from "@model/Ioc.model";
/**
* 收集类依赖
* @constructor
*/
export function Injectable() {
return (_constructor: { new (...args: any[]): {}; }) => {
if(IocModel.classPool.indexOf(_constructor) !== -1) {
throw new Error('无需重复收集类');
} else {
//注册
IocModel.classPool = [_constructor]
}
}
}
/**
* 将类依赖实例化然后注入到被装饰的属性中
* @constructor
*/
export function Inject() {
return function (target: any, propertyName: string) {
/**
* 使用 reflect-metadata 提供的内置 类型元数据键 design:type 通过反射拿到被装饰属性的类型
* 也就是 类属性要实例化 的 service 类
*/
const propertyType: any = Reflect.getMetadata('design:type', target, propertyName);
if (IocModel.classPool.indexOf(propertyType) == -1) {
throw new Error('被装饰的属性所属的变量类型类,没有被装饰器@Injectable()注入,请检查!');
} else {
// 从存取器的数组中通过下标取出被装饰属性对应的service类然后实例化这个类在放入被装饰的属性中
target[propertyName] = new (IocModel.classPool[IocModel.classPool.indexOf(propertyType)])()
}
}
}

View File

@@ -0,0 +1,16 @@
import Utils from "@utils/Index.utils";
import {ApplicationMenu} from "@interactive/ApplicationMenu.interactive";
export function StartWindow(): any {
return (_constructor: {new(...args:any[]):{}} ) => {
return class extends _constructor {
constructor() {
// 这里只要动态 require 导入类即可,然后类上的装饰器就会运行
Utils.GetController()
// 创建顶部菜单
new ApplicationMenu()
super();
}
}
}
}

1
src/core/config/.pydio Normal file
View File

@@ -0,0 +1 @@
3392576f-5dfc-4270-9335-0fcac5cdbeb3

401
src/core/config/Index.config.ts Executable file
View File

@@ -0,0 +1,401 @@
import { PagePath, PageSize } from "@type/PageConfig.types";
import { TrayConfig } from "@type/Tray.types";
import { ApiUrlConfig } from "@type/ApiConfig";
import {
AboutPanelOptionsOptions,
app,
dialog,
MenuItem,
MenuItemConstructorOptions,
nativeImage,
shell,
TouchBar,
TouchBarConstructorOptions
} from "electron";
import Windows from "@/core/model/Windows.model";
import { join } from "path";
import { JadeOptions } from "jade";
import Utils from "@utils/Index.utils";
import JsonDB from "@utils/db.utils";
import { spawn } from "child_process";
const { TouchBarButton, TouchBarPopover } = TouchBar;
export default class Config {
/**
* 启动页面默认为 Home.controller/Home
* 程序内部根据这个配置自动载入Home.controller.ts文件并自动调用类里面的Home()方法实现窗口创建
* 可以改成其他,例如 PlayVideo.controller/Theme
*/
public static StartPage: string = 'Home.controller/Home';
public static isMac: Boolean = process.platform === 'darwin';
public static isWindows: Boolean = process.platform === 'win32';
public static isLinux: Boolean = process.platform === 'linux';
public static LogsPath: string = "../../../logs/";
public static isShowDockIcon: boolean = JsonDB.findOne("isShowDockIcon", "setting");
// 是否开机自启 默认 false
public static isLoginOpen: boolean = JsonDB.findOne("isOpen", "setting");
public static default = Config.isShowDockIcon ? app.dock.hide() : app.dock.show()
// 升级地址
public static UpdateUrl: string = "https://api.github.com/repos/helpcode/EasyProject/releases/latest";
// ajax 请求地址
public static ApiUrl: ApiUrlConfig = {
BaseUrl: 'https://registry.npmjs.org',
ApiList: {
PlayList: '/list', // 获取视频列表
PalyVideo: '/play', // 获取视频播放地址
VideoComment: '/CommentList', // 获取视频评论
}
};
// jade 模板引擎配置,更多参数自行阅读声明文件
public static jadeCompile0ptions: JadeOptions = {
cache: true,
pretty: true, // 编译输出后是否保留源码格式true 保持
globals: {
css: [
'../../assets/js/lib/index.css',
],
js: [
'../../assets/js/lib/vue.js',
'../../assets/js/lib/vuex.js',
'../../assets/js/lib/vue-router.js',
'../../assets/js/lib/axios.js',
'../../assets/js/lib/index.js',
'../../assets/js/lib/font.js'
]
}
};
// 应用程序的页面地址,地址为打包后的相对路径
public static PagePath: PagePath = {
Welcome: join(__dirname, '../../application/page/Welcome/Welcome.html'),
Home: join(__dirname, '../../application/page/Home/Home.html'),
PlugInfo: join(__dirname, '../../application/page/PlugInfo/PlugInfo.html')
};
// 关于App
public static appAbout: AboutPanelOptionsOptions = {
applicationName: "EasyProject",
copyright: "copyright @bmy",
website: "https://github.com/helpcode",
credits: "统一管理组织凌乱无序的软件项目,带来一站式的快感。"
};
// 所有页面窗体配置
public static PageSize: PageSize = {
Welcome: {
width: 330,
height: 520,
frame: false,
backgroundColor: '#00000000',
titleBarStyle: 'hidden',
transparent: true,
simpleFullscreen: true,
vibrancy: 'menu',
hasShadow: false,
show: false,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false
}
},
Home: {
width: 1290,
height: 810,
minHeight: 630,
minWidth: 954,
frame: false,
backgroundColor: '#00000000',
titleBarStyle: 'hidden',
transparent: true,
simpleFullscreen: true,
hasShadow: true,
show: false,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false
}
},
PlugInfo: {
width: 860,
height: 500,
frame: false,
modal: true,
backgroundColor: '#fff',
titleBarStyle: 'hidden',
transparent: true,
simpleFullscreen: true,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false
}
}
};
// Mac系统顶部全局菜单 右边图标
public static TrayConfig: TrayConfig = {
TopMenuRightImage: join(__dirname, '../../application/assets/img/logoTemplate.png'),
TopMenuRightDropdown: [
{
label: '显示主窗口',
click: (menuItem: any, browserWindow: any, event: any) => {
Windows.CurrentBrowserWindow.show();
}
},
{ type: 'separator' },
{
label: '导入项目',
accelerator: "Cmd+i",
click: async (menuItem: any, browserWindow: any, event: any) => {
let res = await Utils.ImportProject((item) => {
console.log("Tray 导入的项目监听后被修改路径:", item)
// event.reply('DirRemove', item)
Windows.CurrentBrowserWindow.webContents.send('DirRemove', item)
});
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.webContents.send('TouchBarImportProject', res)
}
}
},
{
// TODO 导入 项目 的时候,这边项目列表不更新
// label: '项目列表(功能暂未全实现)',
label: '项目列表',
submenu: (JsonDB.project() as Array<any>).map((v:any,index) => {
return {
label: `${v.name}`,
submenu: [
{
label: '在访达打开',
click: async (menuItem: any, browserWindow: any, event: any) => {
await shell.openPath(v.Fullpath);
}
},
{
label: '在终端打开',
click: async (menuItem: any, browserWindow: any, event: any) => {
spawn ('open', [ '-a', 'Terminal', v.Fullpath ])
}
},
{
label: '在应用打开',
click: async (menuItem: any, browserWindow: any, event: any) => {
Windows.CurrentBrowserWindow.show();
Windows.CurrentBrowserWindow.webContents.send('openInfo', {
item: v, index
})
}
},
]
}
}),
},
{ type: 'separator' },
{
label: "设置...",
accelerator: "Cmd+,",
click: async (menuItem: any, browserWindow: any, event: any) => {
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.show();
Windows.CurrentBrowserWindow.webContents.send('openSetting')
}
}
},
{
label: "开机自启",
type: "checkbox",
checked: Config.isLoginOpen,
click: async (menuItem: any, browserWindow: any, event: any) => {
console.log("menuItem: ", menuItem.checked)
JsonDB.update("isOpen", menuItem.checked, "setting");
}
},
{
label: '隐藏Dock图标',
type: 'checkbox',
checked: Config.isShowDockIcon,
click: async (menuItem: any, browserWindow: any, event: any) => {
JsonDB.update("isShowDockIcon", menuItem.checked, "setting");
menuItem.checked ? app.dock.hide() : app.dock.show()
}
},
{ type: 'separator' },
{
label: `重启应用`,
click: () => {
Utils.killAllTask(() => {
app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
app.exit(0)
})
}
},
{
label: `杀死 ${ app.name }`,
click: () => {
Utils.killAllTask(() => {
app.exit(0)
})
}
}
],
TopMenuRightTips: '测试提醒'
};
// Mac or win 系统顶部全局菜单
public static TemplateMenu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
{
label: app.name,
submenu: [
{ label: `关于 ${ app.name }`, role: 'about' },
{ type: 'separator' },
{
label: "偏好设置",
accelerator: "Cmd+,",
click: async (menuItem: any, browserWindow: any, event: any) => {
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.show();
Windows.CurrentBrowserWindow.webContents.send('openSetting')
}
}
},
{ type: 'separator' },
{ label: '服务', role: 'services' },
{ type: 'separator' },
{ label: `隐藏 ${ app.name }`, role: 'hide' },
{ label: '隐藏其他', role: 'hideOthers' },
{ label: '隐藏其他2', role: 'unhide' },
{ type: 'separator' },
{ label: `退出${ app.name }`, role: 'quit' }
]
},
{
label: "文件",
submenu: [
{
label: '导入项目',
accelerator: "Cmd+i",
click: async (menuItem: any, browserWindow: any, event: any) => {
let res = await Utils.ImportProject(item => {
Windows.CurrentBrowserWindow.webContents.send('DirRemove', item)
});
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.show();
Windows.CurrentBrowserWindow.webContents.send('TouchBarImportProject', res)
}
}
},
]
},
{
label: '编辑',
submenu: [
{ label: '撤销', role: 'undo' },
{ label: '恢复', role: 'redo' },
{ type: 'separator' },
{ label: '剪切', role: 'cut' },
{ label: '复制', role: 'copy' },
{ label: '粘贴', role: 'paste' },
{ type: 'separator' },
{ label: '粘贴保留样式', role: 'pasteAndMatchStyle' },
{ label: '删除', role: 'delete' },
{ label: '全选', role: 'selectAll' },
{ type: 'separator' },
{ label: '听写', submenu: [ { label: '开始听写', role: 'startSpeaking' }, { label: '停止听写', role: 'stopSpeaking' } ] }
]
},
{
label: '窗口',
submenu: [
{ label: '最小化', role: 'minimize' },
{ label: '最大化', role: 'zoom' },
{ label: '关闭', role: 'close' }
]
},
{
label: '帮助',
role: 'help',
submenu: [
{
label: '联系作者',
click: async () => {
await dialog.showMessageBox({
type: "info",
title: '联系作者',
message: "如果你有任何使用建议或者反馈Bug\n请添加QQ2271608011"
})
}
},
app.isPackaged
? {
label: 'GitHub 主页',
click: async () => {
await shell.openExternal("https://github.com/helpcode")
}
}
: { label: '切换开发人员工具', role: 'toggleDevTools' },
]
}
];
// Mac touchbar 菜单
public static TouchBarConfig: TouchBarConstructorOptions = {
items: [
new TouchBarButton({
label: '导入项目',
iconPosition: "left",
icon: join(__dirname, '../../application/assets/img/+normal@2x.png'),
click: async () => {
let res = await Utils.ImportProject((item) => {
console.log("TouchBar 导入的项目监听后被修改路径:", item)
Windows.CurrentBrowserWindow.webContents.send('DirRemove', item)
});
if (!Windows.CurrentBrowserWindow.webContents.getURL().includes("Welcome.html")) {
Windows.CurrentBrowserWindow.webContents.send('TouchBarImportProject', res)
}
}
}),
new TouchBarPopover({
label: '帮助',
showCloseButton: true,
icon: (nativeImage.createFromPath(join(__dirname, '../../application/assets/img/help.png'))).resize({
width: 16,
height: 16
}),
items: new TouchBar({
items: [
new TouchBarButton({
label: '联系作者',
backgroundColor: '#3a3a3c',
click: async () => {
await dialog.showMessageBox({
type: "info",
title: '联系作者',
message: "如果你有任何使用建议或者反馈Bug\n请添加 QQ2271608011"
})
}
}),
new TouchBarButton({
label: 'Github主页',
backgroundColor: '#3a3a3c',
click: async () => {
await shell.openExternal("https://github.com/helpcode")
}
})
]
})
}),
]
};
}

View File

@@ -0,0 +1 @@
d66ac9d5-fd1e-43c7-9839-9363bad2256d

View File

@@ -0,0 +1,47 @@
import { Render, Ipc } from "@annotation/Creted.annotation";
import { HomeImplService } from "@service/impl/Home.impl.service";
import { Inject } from "@annotation/Ioc.annotation";
import { FileChice } from "@ipc/module/File.ipc";
import {app, Menu, shell, MenuItemConstructorOptions, MenuItem} from "electron";
import { Http } from "@net/Http.net";
import Config from "@config/Index.config";
export class HomeController {
@Inject()
public readonly HomeImplService!: HomeImplService;
@Inject()
public readonly _Http!: Http;
/**
* 启动欢迎页面
* @constructor
*/
@Render()
public async Welcome() {
return {
title: '欢迎'
}
}
/**
* 主页面
* @constructor
*/
@Render()
@Ipc([ FileChice ])
public async Home(params?: { [key:string]:any }) {
return {
title: '首页'
}
}
@Render()
public async PlugInfo(params?: { [ key:string ] :any }) {
console.log("插件详情页面接收到的参数:", params);
return {
title: '插件详情页面'
}
}
}

View File

@@ -0,0 +1 @@
c4c2b685-cd72-4295-bff5-a47af45c77a9

View File

@@ -0,0 +1,16 @@
import {app, Menu, shell, MenuItemConstructorOptions, MenuItem} from "electron";
import Config from "@config/Index.config";
export class ApplicationMenu {
private AppMenu!: Menu;
constructor() {
this.buildFromTemplate();
}
buildFromTemplate(): void {
this.AppMenu = Menu.buildFromTemplate(Config.TemplateMenu);
Menu.setApplicationMenu(this.AppMenu);
}
}

View File

@@ -0,0 +1,26 @@
import { dialog, Notification, OpenDialogOptions} from "electron";
import Windows from "@model/Windows.model"
export class Dialog {
/**
* 错误弹窗
* @param title
* @param content
* @constructor
*/
public static async ErrorBox(title: string, content: string): Promise<void> {
await dialog.showErrorBox(title, content)
}
/**
* 选择文件
* @param title
* @param message
*/
public static async showDialog(params: OpenDialogOptions): Promise<any> {
let SaveFile = await dialog.showOpenDialog(Windows.CurrentBrowserWindow, params);
if(!SaveFile.canceled) {
return SaveFile.filePaths
}
}
}

View File

@@ -0,0 +1,50 @@
import { shell, TouchBar, TouchBarButton as TB, nativeImage } from "electron";
import Windows from "@/core/model/Windows.model";
import Config from "@config/Index.config";
import JsonDB from "@utils/db.utils";
import { join } from "path";
const { TouchBarLabel, TouchBarButton, TouchBarPopover } = TouchBar;
export class Touchbar {
constructor() {
this.createdAllProject()
}
public createdAllProject(): void {
let allProject = JsonDB.project();
let touchBarButtonArr: Array<TB> = [];
// 生成 项目列表 下的项目按钮
(allProject as Array<any>).forEach((v, i) => {
touchBarButtonArr.push(
new TouchBarButton({
label: v.name,
backgroundColor: '#3a3a3c',
click: async () => {
await shell.openPath(v.Fullpath)
}
}),
)
});
// 项目列表 Icon
let projectIcon = nativeImage.createFromPath(join(__dirname, '../../application/assets/img/project.png'));
// @ts-ignore
if (Config.TouchBarConfig.items[1].label == "项目列表") {
// @ts-ignore
Config.TouchBarConfig.items.splice(1, 1)
}
// @ts-ignore
Config.TouchBarConfig.items.splice(1, 0, new TouchBarPopover({
label: '项目列表',
icon: projectIcon.resize({ width: 18, height: 18 }),
showCloseButton: true,
items: new TouchBar({
items: touchBarButtonArr
})
}))
Windows.CurrentBrowserWindow.setTouchBar(new TouchBar(Config.TouchBarConfig))
}
}

View File

@@ -0,0 +1,32 @@
import { nativeImage, NativeImage, Menu, Tray } from "electron";
import Config from "@config/Index.config";
import JsonDB from "@utils/db.utils";
import Windows from "@/core/model/Windows.model";
import Utils from "@utils/Index.utils";
export class TrayInteractive {
private tray!: Tray;
constructor() {
this.tray = new Tray(Config.TrayConfig.TopMenuRightImage);
Windows.tray = this.tray;
this.buildTrayMenu()
}
private buildTrayMenu() {
let contextMenu: Menu = Menu.buildFromTemplate(Config.TrayConfig.TopMenuRightDropdown);
this.tray.setToolTip(Config.TrayConfig.TopMenuRightTips)
Utils.setTrayTitleNums();
this.tray.setContextMenu(contextMenu)
}
/**
* 创建 NativeImage 图片图片会随着Mac系统主题的切换而自定变纯黑或纯白
*/
// private setTemplateImage(): NativeImage {
// let image: NativeImage = nativeImage.createFromPath(Config.TrayConfig.TopMenuRightImage);
// image.setTemplateImage(true)
// return image.resize({ width: 20, height: 20 })
// }
}

View File

1
src/core/ipc/.pydio Normal file
View File

@@ -0,0 +1 @@
f5e29150-31cc-40cb-99ed-6527fc3038a9

67
src/core/ipc/Application.ipc.ts Executable file
View File

@@ -0,0 +1,67 @@
import {ipcMain, shell} from "electron";
import Utils from "@utils/Index.utils";
import Windows from "@model/Windows.model";
import Config from "@config/Index.config";
import { spawn, exec} from "child_process";
export class Application {
CreatedWindow() {
ipcMain.on('openWindow', async (event, arg: any) => {
let ControllerName: string = arg.action.split("/")[0],
MethodsName: string = arg.action.split("/")[1],
data: object = arg.data || {}
// parent false 创建单独的窗体
if (!arg.parent || !arg.hasOwnProperty("parent")) {
// 是否要关闭上一级窗口
if(arg.hasOwnProperty("closeParent") && arg.closeParent) {
Windows.CurrentBrowserWindow.hide()
}
let a = require(`../controller/${ControllerName}.js`);
Utils.startWindows(new a[Utils.toUpperCase(ControllerName)](), MethodsName, data)
event.returnValue = ""
return false
}
// parent true 创建默认依附主窗口的子窗口
if (arg.hasOwnProperty("parent") || arg.parent) {
// @ts-ignore 合并配置参数,注入 parent: 当前默认启动的父类窗口实例
Object.assign(Config.PageSize[MethodsName], { parent: Windows.CurrentBrowserWindow });
// 实例化子窗口类,创建窗口
let a = require(`../controller/${ControllerName}.js`);
Utils.startWindows(new a[Utils.toUpperCase(ControllerName)](), MethodsName, data)
event.returnValue = ""
return false
// 创建默认依附在自定义窗口的子窗口
} else {
console.log("创建默认依附在自定义窗口的子窗口")
}
})
}
/**
* 打开浏览器窗口
*/
shellWindows() {
ipcMain.on("openExternal", async (event, arg:any) => {
await shell.openExternal(arg.url)
event.returnValue = "success"
});
}
/**
* 在文件管理器打开
* 在终端打开mac系统和win
*/
shellOpen() {
ipcMain.on("OpenProject", async (event, arg:any) => {
arg.type == 'file'
? await shell.openPath(arg.path)
: Config.isMac
? spawn ('open', [ '-a', 'Terminal', arg.path ])
: exec(`start cmd.exe /K cd /D ${arg.path}`)
event.returnValue = "success"
});
}
}

View File

@@ -0,0 +1 @@
74566167-ef2f-4b0c-9692-89f4aeef3960

543
src/core/ipc/module/File.ipc.ts Executable file
View File

@@ -0,0 +1,543 @@
import { app, ipcMain, shell } from "electron";
import { Dialog } from "@interactive/Dialog.interactive";
import { Inject } from "@annotation/Ioc.annotation";
import { Http } from "@net/Http.net";
import Utils from "@utils/Index.utils";
import JsonDB from "@utils/db.utils";
import { existsSync, readJsonSync, remove, watch } from "fs-extra";
import { exec, execSync, spawn } from "child_process";
import Config from "@config/Index.config";
import kill from "tree-kill";
import { basename, dirname, resolve } from "path";
import { ProcessUtils } from "@utils/process.utils";
import { Package } from "@utils/package.utils";
import Windows from "@model/Windows.model";
import chokidar, { FSWatcher } from "chokidar";
import { WhichBin } from "@utils/whichBin.utils"
import moment from "moment"
export class FileChice {
@Inject()
private readonly _Net!: Http;
@Inject()
private readonly _Package!: Package;
@Inject()
private readonly _WhichBin!: WhichBin;
// @Inject()
private readonly _process: ProcessUtils = new ProcessUtils();
sendSettingConfig() {
ipcMain.on("checkUpdate", async (event, args) => {
console.log("升级...")
let returnVal: any = {};
let res = await this._Net.GET({
url: Config.UpdateUrl,
custom: undefined,
header: {
'Authorization': 'Basic aGVscGNvZGU6Z2hwXzBEZmx3cDFQY2l6VVRNOFkxTTNBMDZxQlhTZGpvSjRHak4xMw=='
}
});
if (res != undefined) {
// 如果需要更新
if (Utils.compare(res.tag_name, app.getVersion())) {
returnVal.isUpdate = true;
returnVal.detailed = res.body.split("\r\n");
returnVal.version = res.tag_name;
returnVal.title = res.name;
returnVal.down = res.assets[ 0 ].browser_download_url;
returnVal.updatedTime = res.assets[ 0 ].updated_at;
} else {
returnVal.isUpdate = false;
}
event.returnValue = returnVal
} else {
returnVal.isUpdate = false;
}
})
ipcMain.on("selectEnvPath", async (event, args) => {
let DirectoryPath = await Dialog.showDialog({
message: "选择您的Node脚本所在路径",
buttonLabel: '选择',
properties: [ 'openDirectory', 'showHiddenFiles' ]
});
if (DirectoryPath) {
JsonDB.update("envVariable", DirectoryPath[0])
await this._WhichBin.initEnvPath();
Utils.killAllTask(() => {
app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
app.exit(0)
})
}
})
ipcMain.on("getVersion", async (event, args) => {
event.returnValue = { version: app.getVersion() }
})
ipcMain.on("delete_themeList", async (event, args) => {
event.returnValue = JsonDB.remove(args.value, `setting.${ args.keyName }`)
})
ipcMain.on("push_themeList", async (event, args) => {
event.returnValue = JsonDB.saveProject(args.value, `setting.${ args.keyName }`)
})
ipcMain.on("setPackageTypeindex", async (event, args) => {
try {
let res = execSync(`which ${args.packageType}`);
console.log("res: ", res.toString())
JsonDB.update(args.keyName, args.value)
event.returnValue = { status: true, message: null }
} catch (e) {
event.returnValue = { status: false, message: e.message }
}
})
ipcMain.on("getSettingConfig", async (event, args) => {
event.returnValue = JsonDB.project("setting")
})
ipcMain.on("setSettingConfig", async (event, args) => {
JsonDB.update(args.keyName, args.value)
if (args.keyName == "isOpen") {
const appFolder = dirname(process.execPath)
const updateExe = resolve(appFolder, '..', 'EasyProject.app')
const exeName = basename(process.execPath)
app.setLoginItemSettings({
openAtLogin: args.value,
path: updateExe,
args: [
'--processStart', `"${ exeName }"`,
'--process-start-args', `"--hidden"`
]
});
}
// event.returnValue = JsonDB.project("setting")
})
}
getProcess() {
ipcMain.on("getProcess", async (event, args) => {
let res = await this._process.GetAllProcess(args.pid);
event.returnValue = res
})
}
setPackageJson() {
ipcMain.on("setPackageJson", async (event, args) => {
let pkg = JsonDB.findName(args.project);
let packagePath = `${ pkg.Fullpath }/package.json`;
Utils.writePackage(packagePath, args.code)
})
}
getPackageJson() {
ipcMain.on("getPackageJson", async (event, args) => {
let pkg = JsonDB.findName(args.project);
let packagePath = `${ pkg.Fullpath }/package.json`;
try {
event.returnValue = Utils.readPackage(packagePath)
} catch (e) {
event.returnValue = e.message
}
})
}
SaveFile() {
ipcMain.on('SaveFile', async (event, arg) => {
let SaveFilePath = await Dialog.showDialog({
title: "选择路径",
message: "选择下载路径",
buttonLabel: '亲,点我确认选择!',
filters: [
{ name: 'All', extensions: [ '*' ] },
],
properties: [
'openDirectory',
'createDirectory'
]
});
if (SaveFilePath != undefined) {
Utils.DownFile(arg.url, SaveFilePath)
}
})
}
/**
* 从项目列表删除项目
*/
removeListProject() {
// 从项目列表删除项目
ipcMain.on("removeListProject", async (event, args) => {
let { Fullpath } = JsonDB.findName(args.name,"name");
Utils.removeDirWatch(Fullpath);
let a = JsonDB.remove({ name: args.name });
if (a != undefined) {
Utils.updateTouchbarList();
Utils.setTrayTitleNums();
event.returnValue = "success"
} else {
event.returnValue = undefined
}
});
// 从磁盘删除项目
ipcMain.on("removeDistProject", async (event, args) => {
let project = JsonDB.findName(args.name, "name");
Utils.removeDirWatch(project.Fullpath);
try {
// 删除到回收站
await shell.trashItem(resolve(project.Fullpath))
// await remove();
// 从本地数据库删除
JsonDB.remove({ name: args.name });
event.returnValue = "success"
} catch (err) {
event.returnValue = err.message
}
});
}
/**
* 重命名项目
*/
reNameProject() {
ipcMain.on("reNameProject", async (event, args) => {
JsonDB.db.get('projects')
.find({ name: args.oldName })
.assign({ name: args.newName })
.write()
Utils.updateTouchbarList();
event.returnValue = "success"
});
}
changeIcon() {
ipcMain.on("ChangeIcon", async (event, args) => {
JsonDB.db.get('projects')
.find({ name: args.name })
.assign({ FolderIcon: args.newIcon })
.write()
event.returnValue = "success"
})
}
/**
* 导入项目
*/
openDirectory() {
ipcMain.on("openDirectory", async (event, args) => {
let res = await Utils.ImportProject((item) => {
console.log("导入的项目监听后被修改路径:", item)
event.reply('DirRemove', item)
});
event.returnValue = res;
})
}
/**
* 停止命令
* @constructor
*/
StopProject(): void {
ipcMain.on("StopCmd", async (event, args) => {
console.log("停止命令 args: ", args)
let currentTask: any = Utils.findOneTask(args.name, args.ScriptName);
kill(currentTask.Child.pid, (err: Error | undefined) => {
if (err) {
event.returnValue = err.message
} else {
currentTask.Child = null;
currentTask.IsRuning = "idle"
currentTask.RunLogs = ""
event.returnValue = null
}
});
})
}
/**
* 运行命令
* @constructor
*/
RunProject(): void {
ipcMain.on("RunCmd", async (event, args) => {
console.log("运行命令: ", args)
let currentTask: any = Utils.findOneTask(args.name, args.ScriptName);
let cmd = args.ScriptName == 'install'
? `cd ${ currentTask.path } && ${this._Package.install("i")}`
: `cd ${ currentTask.path } && ${this._Package.run(currentTask.ScriptName)}`;
try {
currentTask.Child = spawn(cmd, {
cwd: process.cwd(),
stdio: [ 'inherit', 'pipe', 'pipe' ],
shell: true
});
// 修改运行状态
currentTask.IsRuning = "runing"
currentTask.time = Date.now()
currentTask.Child.stdout.on('data', (data: Buffer) => {
currentTask.RunLogs += data.toString();
if (currentTask.Child != null) {
currentTask.pid = currentTask.Child.pid;
// 保存 你正在运行的 pid
Windows.runPids.add(currentTask.pid);
console.log("运行命令添加pid: ", Windows.runPids)
// 后端返回 ScriptName 为前端助力,让前端的 sendMessage 监听
// 中知道把 log 日志存放到数组的哪个 v.Terminal 中
event.sender.send('sendMessage', {
log: data.toString(),
projectName: args.name,
ScriptName: currentTask.ScriptName,
pid: currentTask.Child.pid
})
}
});
// 命令自动运行完成的时候,向前端发送进程关闭的通知
currentTask.Child.stdout.on('close', (code: Number, signal: string) => {
console.log("5 进程结束")
// 删除已经停止的 pid
Windows.runPids.delete(currentTask.pid);
currentTask.pid = 0;
console.log("停止命令删除pid: ", Windows.runPids)
// 关闭进程
currentTask.IsRuning = "idle"
currentTask.RunLogs = ""
currentTask.Child = null;
currentTask.time = Date.now() - currentTask.time
event.sender.send('close', {
ScriptName: currentTask.ScriptName,
projectName: args.name,
time: moment(currentTask.time).format("mm:ss")
});
});
currentTask.Child.stdout.on('error', (e: any) => {
console.log("------121 运行命令产生错误了------",e)
});
} catch (e) {
console.log("------2 运行命令产生错误了------",e)
}
event.returnValue = ""
})
}
/**
* 获取项目的运行命令
* @constructor
*/
GetTaskList() {
ipcMain.on("GetTaskList", async (event, args) => {
let project = JsonDB.findName(args.name);
let scriptList = (Utils.readPackage(`${ project.Fullpath }/package.json`)).scripts;
let NewSrciptList = []
for (const key in scriptList) {
NewSrciptList.push({
/**
* 进程状态锁
* true 表示命令未运行进程停止时会变为true前端运行按钮可以点击
* false 在你点击停止按钮后,进程还未被完全杀死前,再次点击运行按钮将无反应
* 必须等待进程被完全杀死后,运行按钮才能再次使用
* 如果不加锁,在前端 急速反复点击 运行停止按钮 的时候
* 会导致进程运行后直接闪退Mccos上存在
*/
lock: true,
ScriptName: key, // 命令的名称
IsRuning: "idle", // idle 未运行 runing 运行
RunLogs: '', // 命令运行产生的日志
ScriptShell: scriptList[ key ], // 命令的脚本
Terminal: null,
Child: null,
pid: 0,
time: 0,
})
}
NewSrciptList.unshift({
lock: true,
ScriptName: 'install',
IsRuning: "idle",
RunLogs: '',
ScriptShell: this._Package.install("i"),
Terminal: null,
Child: null,
pid: 0,
time: 0,
});
// 深拷贝去除再次回到【任务模块】的时候Child中保存的子进程无法被ipc发送的问题
let newItem = Utils.DeepCopy(Utils.setTask(project.Fullpath, NewSrciptList))
newItem.forEach((v: any) => v.Child != null ? v.Child = null : '');
event.returnValue = newItem
})
}
/**
* 应用一打开的时候获取项目列表
* @constructor
*/
ProjectList() {
ipcMain.on("ProjectList", async (event, args) => {
let allProject = JsonDB.project();
event.returnValue = allProject.filter((v:any,index: number) => {
// 打开软件的时候,判断本地项目路径是否存在
// 把存在的返回数据给前端,来展示项目列表
if (existsSync(v.Fullpath)) {
Utils.addDirWatch(v.Fullpath, () => {
event.reply('DirRemove', v)
})
return v
} else {
// 路径已经不存在了删除json
console.log("路径已经不存在了删除json")
JsonDB.remove({ name: v.name });
}
});
})
}
/**
* 获取依赖
* @constructor
*/
GetDependentList() {
ipcMain.on("GetDependentList", async (event, args) => {
console.log("获取依赖 ==== args: ", args)
let project = JsonDB.findName(args.name);
let pkg = Utils.readPackage(`${ project.Fullpath }/package.json`);
let ListArr: { title: string, list: { [ key: string ]: any }[] }[] = [];
// 检查项目中是否有 node_modules
if (existsSync(`${project.Fullpath}/node_modules`)) {
// 头像地址https://avatars.dicebear.com/v2/identicon/插件名.svg
// 请求地址https://registry.npmjs.org/插件名
[ pkg.dependencies, pkg.devDependencies ].forEach((v, i) => {
ListArr.push({
title: i == 0 ? '生产环境依赖' : '开发环境依赖',
list: []
})
for (const key in v) {
let otherPath = `${project.Fullpath}/node_modules/${key}/package.json`
if (existsSync(otherPath)) {
const modeulePackage = Utils.readPackage(otherPath);
ListArr[ i ].list.push({
name: key,
logoImg: `https://avatars.dicebear.com/v2/identicon/${ key.replace("/", "-") }.svg`,
currentVersion: v[ key ],
description: modeulePackage.description,
website: modeulePackage.homepage || (modeulePackage.repository && modeulePackage.repository.url) || `https://www.npmjs.com/package/${ key.replace('/', '%2F') }`
})
}
}
});
} else {
event.returnValue = undefined
}
event.returnValue = ListArr
})
}
/**
* 安装依赖
* @constructor
*/
InstallPlug() {
ipcMain.on("installPlug", async (event, args) => {
let pkg = JsonDB.findName(args.project);
let cmd = `cd ${ pkg.Fullpath } && ${this._Package.install("other", args)}`
exec(cmd, (error, stdout, stderr) => {
if (error == null) {
event.reply('reDependentSuccess', 'ok')
} else {
// event.returnValue = stderr
event.reply('reDependentSuccess', {
message: error.message
})
}
})
})
}
/**
* 更新依赖包
*/
updateDependencies() {
ipcMain.on("uDependencies", async (event, args) => {
let cmd, pkg = JsonDB.findName(args.project);
cmd = `cd ${ pkg.Fullpath } && ${this._Package.up(args)}`
exec(cmd, (error, stdout, stderr) => {
if (error == null) {
event.reply('reDependentSuccess', 'ok')
} else {
console.log(error.message)
// event.returnValue = stderr
event.reply('reDependentSuccess', {
message: error.message
})
}
})
})
}
/**
* 删除依赖包
*/
deleteDependencies() {
ipcMain.on("dDependencies", async (event, args) => {
let cmd, pkg = JsonDB.findName(args.project);
cmd = `cd ${ pkg.Fullpath } && ${this._Package.uninstall(args.type)}`;
console.log("删除依赖包: ", cmd)
exec(cmd, (error, stdout, stderr) => {
console.log(stdout)
if (error == null) {
event.reply('reDependentSuccess', 'ok')
} else {
event.reply('reDependentSuccess', {
message: error.message
})
}
})
})
}
/**
* 初始化依赖
*/
installDependent() {
ipcMain.on("reDependent", async (event, args) => {
console.log("args.name: ", args.name)
let pkg = JsonDB.findName(args.name)
let cmd = `cd ${ pkg.Fullpath } && rm -rf node_modules && ${this._Package.install("i")}`
console.log("初始化依赖: ",cmd)
exec(cmd, (error, stdout, stderr) => {
if (error == null) {
event.reply('reDependentSuccess', 'ok')
} else {
// event.returnValue = stderr
event.reply('reDependentSuccess', {
message: error.message
})
}
})
})
}
}

View File

@@ -0,0 +1,12 @@
#!/bin/bash
terminateTree() {
for cpid in $(/usr/bin/pgrep -P $1); do
terminateTree $cpid
done
kill -9 $1 > /dev/null 2>&1
}
for pid in $*; do
terminateTree $pid
done

1
src/core/model/.pydio Normal file
View File

@@ -0,0 +1 @@
0aab50fe-8286-4b21-b0e4-396e502553da

13
src/core/model/Ioc.model.ts Executable file
View File

@@ -0,0 +1,13 @@
class IocModel {
private _classPool: Array<{ new (...args: any[]): {}; }> = [];
get classPool(): Array<{ new(...args: any[]): {} }> {
return this._classPool;
}
set classPool(value: Array<{ new(...args: any[]): {} }>) {
this._classPool = [...value, ...this._classPool]
}
}
export default new IocModel();

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