<!DOCTYPE html>
|
<html>
|
<head>
|
<meta charset="UTF-8" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<title>极简electron终端</title>
|
<link
|
rel="stylesheet"
|
href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
|
/>
|
<script src="https://unpkg.com/vue"></script>
|
<!-- 引入element -->
|
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
<!-- css -->
|
<link rel="stylesheet" href="./index.css" />
|
</head>
|
<body>
|
<div id="app">
|
<div class="main-class">
|
<!-- 渲染过往的命令行 -->
|
<div v-for="item in commandArr">
|
<div class="command-action">
|
<!-- 执行成功或者失败图标切换 -->
|
<i
|
:class="['el-icon-right', 'command-action-icon', { 'error-icon': item.code !== 0 }]"
|
></i>
|
<!-- 过往执行地址和命令行、信息 -->
|
<span class="command-action-path">{{ item.path }} $</span>
|
<span class="command-action-contenteditable"
|
>{{ item.command }}</span
|
>
|
</div>
|
<div class="output-command">{{ item.commandMsg }}</div>
|
</div>
|
<!-- 当前输入的命令行 -->
|
<div
|
class="command-action command-action-editor"
|
@mouseup="timeoutFocusInput"
|
>
|
<i class="el-icon-right command-action-icon"></i>
|
<!-- 执行地址 -->
|
<span class="command-action-path">{{ path }} $</span>
|
<!-- 命令行输入 -->
|
<span
|
:contenteditable="action ? false : 'plaintext-only'"
|
class="command-action-contenteditable"
|
@input="onDivInput($event)"
|
@keydown="keyFn"
|
></span>
|
</div>
|
<!-- 当前命令行输出 -->
|
<div class="output-command">
|
<div v-for="item in commandMsg">{{item}}</div>
|
</div>
|
</div>
|
</div>
|
|
<script>
|
const { ipcRenderer } = require('electron')
|
const { spawn } = require('child_process')
|
const path = require('path')
|
|
var app = new Vue({
|
el: '#app',
|
data: {
|
path: '', // 命令行目录
|
command: '', // 用户输入命令
|
handleCommand: '', // 经过处理的用户命令 比如清除首尾空格、添加获取路径的命令
|
commandMsg: [], // 当前命令信息
|
commandArr: [], // 过往命令行输出保存
|
isActive: true, // 终端是否聚焦
|
action: false, // 是否正在执行命令
|
inputDom: null, // 输入框dom
|
addPath: '', // 不同系统 获取路径的命令 mac是pwd window是chdir
|
},
|
mounted() {
|
this.addGetPath()
|
this.inputDom = document.querySelector(
|
'.command-action-contenteditable'
|
)
|
this.path = process.cwd() // 初始化路径
|
this.watchFocus()
|
ipcRenderer.send('page-ready') // 告诉主进程页面准备好了
|
},
|
methods: {
|
// 回车执行命令
|
keyFn(e) {
|
if (e.keyCode == 13) {
|
this.actionCommand()
|
e.preventDefault()
|
}
|
},
|
// 执行命令
|
actionCommand() {
|
const command = this.command.trim()
|
this.isClear(command)
|
if (this.command === '') return
|
this.action = true
|
this.handleCommand = this.cdCommand(command)
|
const ls = spawn(this.handleCommand, {
|
encoding: 'utf8',
|
cwd: this.path, // 执行命令路径
|
shell: true, // 使用shell命令
|
})
|
// 监听命令行执行过程的输出
|
ls.stdout.on('data', (data) => {
|
const value = data.toString().trim()
|
this.commandMsg.push(value)
|
console.log(`stdout: ${value}`)
|
})
|
// 错误或详细状态进度报告 比如 git push、 git clone
|
ls.stderr.on('data', (data) => {
|
const value = data.toString().trim()
|
this.commandMsg.push(`stderr: ${data}`)
|
console.log(`stderr: ${data}`)
|
})
|
// 子进程关闭事件 保存信息 更新状态
|
ls.on('close', this.closeCommandAction)
|
},
|
// 执行完毕 保存信息 更新状态
|
closeCommandAction(code) {
|
// 保存执行信息
|
this.commandArr.push({
|
code, // 是否执行成功
|
path: this.path, // 执行路径
|
command: this.command, // 执行命令
|
commandMsg: this.commandMsg.join('\r'), // 执行信息
|
})
|
// 清空
|
this.updatePath(this.handleCommand, code)
|
this.commandFinish()
|
console.log(
|
`子进程退出,退出码 ${code}, 运行${code === 0 ? '成功' : '失败'}`
|
)
|
},
|
// cd命令处理
|
cdCommand(command) {
|
let pathCommand = ''
|
if (this.command.startsWith('cd ')) {
|
pathCommand = this.addPath
|
} else if (this.command.indexOf(' cd ') !== -1) {
|
pathCommand = this.addPath
|
}
|
return command + pathCommand
|
// 目录自动联想...等很多细节功能 可以做但没必要2
|
},
|
// 清空历史
|
isClear(command) {
|
if (command === 'clear') {
|
this.commandArr = []
|
this.commandFinish()
|
}
|
},
|
// 获取不同系统下的路径
|
addGetPath() {
|
const systemName = getOsInfo()
|
if (systemName === 'Mac') {
|
this.addPath = ' && pwd'
|
} else if (systemName === 'Windows') {
|
this.addPath = ' && chdir'
|
}
|
},
|
// 命令执行完毕 重置参数
|
commandFinish() {
|
this.commandMsg = []
|
this.command = ''
|
this.inputDom.textContent = ''
|
this.action = false
|
// 激活编辑器
|
this.$nextTick(() => {
|
this.focusInput()
|
this.scrollBottom()
|
})
|
},
|
// 判断命令是否添加过addPath
|
updatePath(command, code) {
|
if (code !== 0) return
|
const isPathChange = command.indexOf(this.addPath) !== -1
|
if (isPathChange) {
|
this.path = this.commandMsg[this.commandMsg.length - 1]
|
}
|
},
|
// 保存输入的命令行
|
onDivInput(e) {
|
this.command = e.target.textContent
|
},
|
// 点击div
|
timeoutFocusInput() {
|
setTimeout(() => {
|
this.focusInput()
|
}, 200)
|
},
|
// 聚焦输入
|
focusInput() {
|
this.inputDom.focus() //解决ff不获取焦点无法定位问题
|
var range = window.getSelection() //创建range
|
range.selectAllChildren(this.inputDom) //range 选择obj下所有子内容
|
range.collapseToEnd() //光标移至最后
|
this.inputDom.focus()
|
},
|
// 滚动到底部
|
scrollBottom() {
|
let dom = document.querySelector('#app')
|
dom.scrollTop = dom.scrollHeight // 滚动高度
|
dom = null
|
},
|
// 监听窗口聚焦、失焦
|
watchFocus() {
|
ipcRenderer.on('win-focus', (event, message) => {
|
this.isActive = message
|
if (message) {
|
this.focusInput()
|
}
|
})
|
},
|
},
|
})
|
|
// 获取操作系统信息
|
function getOsInfo() {
|
var userAgent = navigator.userAgent.toLowerCase()
|
var name = 'Unknown'
|
if (userAgent.indexOf('win') > -1) {
|
name = 'Windows'
|
} else if (userAgent.indexOf('iphone') > -1) {
|
name = 'iPhone'
|
} else if (userAgent.indexOf('mac') > -1) {
|
name = 'Mac'
|
} else if (
|
userAgent.indexOf('x11') > -1 ||
|
userAgent.indexOf('unix') > -1 ||
|
userAgent.indexOf('sunname') > -1 ||
|
userAgent.indexOf('bsd') > -1
|
) {
|
name = 'Unix'
|
} else if (userAgent.indexOf('linux') > -1) {
|
if (userAgent.indexOf('android') > -1) {
|
name = 'Android'
|
} else {
|
name = 'Linux'
|
}
|
}
|
return name
|
}
|
</script>
|
</body>
|
</html>
|