🐛 修复socket响应数据bug
This commit is contained in:
parent
db07d663e2
commit
eaa5e5e65d
@ -9,7 +9,7 @@ module.exports = {
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
// parser: 'babel-eslint',
|
||||
ecmaVersion: 2020,
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
'jsx': true
|
||||
|
@ -29,13 +29,24 @@ const useStore = defineStore({
|
||||
sessionStorage.clear('token')
|
||||
this.$patch({ token: null })
|
||||
},
|
||||
async getHostList() {
|
||||
async getMainData() {
|
||||
const { data: groupList } = await $api.getGroupList()
|
||||
const { data: hostList } = await $api.getHostList()
|
||||
// const { data: sshList } = await $api.getSshList()
|
||||
// console.log('hostList:', hostList)
|
||||
// console.log('groupList:', groupList)
|
||||
this.$patch({ hostList, groupList })
|
||||
},
|
||||
async getHostList() {
|
||||
const { data: hostList } = await $api.getHostList()
|
||||
// console.log('hostList:', hostList)
|
||||
this.$patch({ hostList })
|
||||
},
|
||||
async getGroupList() {
|
||||
const { data: groupList } = await $api.getGroupList()
|
||||
// console.log('groupList:', groupList)
|
||||
this.$patch({ groupList })
|
||||
},
|
||||
getHostPing() {
|
||||
setTimeout(() => {
|
||||
this.hostList.forEach((item) => {
|
||||
|
@ -122,13 +122,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue'
|
||||
import { ref, reactive, computed, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy: { $api, $message, $messageBox, $store } } = getCurrentInstance()
|
||||
|
||||
const loading = ref(false)
|
||||
const visible = ref(false)
|
||||
const groupList = ref([])
|
||||
const groupForm = reactive({
|
||||
name: '',
|
||||
index: ''
|
||||
@ -167,15 +166,7 @@ const list = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const getGroupList = () => {
|
||||
loading.value = true
|
||||
$api.getGroupList()
|
||||
.then(({ data }) => {
|
||||
groupList.value = data
|
||||
// groupForm.index = data.length
|
||||
})
|
||||
.finally(() => loading.value = false)
|
||||
}
|
||||
let groupList = computed(() => $store.groupList || [])
|
||||
|
||||
const addGroup = () => {
|
||||
groupFormRef.value.validate()
|
||||
@ -186,7 +177,8 @@ const addGroup = () => {
|
||||
$message.success('success')
|
||||
groupForm.name = ''
|
||||
groupForm.index = ''
|
||||
getGroupList()
|
||||
$store.getGroupList()
|
||||
groupFormRef.value.resetFields()
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -206,7 +198,7 @@ const updateGroup = () => {
|
||||
.then(() => {
|
||||
$message.success('success')
|
||||
visible.value = false
|
||||
getGroupList()
|
||||
$store.getGroupList()
|
||||
})
|
||||
})
|
||||
}
|
||||
@ -220,14 +212,11 @@ const deleteGroup = ({ id, name }) => {
|
||||
.then(async () => {
|
||||
await $api.deleteGroup(id)
|
||||
await $store.getHostList()
|
||||
await $store.getGroupList()
|
||||
$message.success('success')
|
||||
getGroupList()
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getGroupList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<AsideBox />
|
||||
<div class="main_container">
|
||||
<TopBar />
|
||||
<router-view v-slot="{ Component }" class="router_box">
|
||||
<router-view v-slot="{ Component }" v-loading="loading" class="router_box">
|
||||
<keep-alive>
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
@ -13,8 +13,26 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onBeforeMount, getCurrentInstance } from 'vue'
|
||||
import AsideBox from '@/components/aside-box.vue'
|
||||
import TopBar from '@/components/top-bar.vue'
|
||||
|
||||
const { proxy: { $store } } = getCurrentInstance()
|
||||
const loading = ref(true)
|
||||
|
||||
const getMainData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
await $store.getMainData()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await getMainData()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -113,9 +113,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, getCurrentInstance, nextTick } from 'vue'
|
||||
import { ref, reactive, computed, getCurrentInstance, nextTick } from 'vue'
|
||||
|
||||
const { proxy: { $api, $message } } = getCurrentInstance()
|
||||
const { proxy: { $api, $message, $store } } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
@ -143,7 +143,6 @@ const resetForm = () => ({
|
||||
|
||||
const hostForm = reactive(resetForm())
|
||||
const oldHost = ref('')
|
||||
const groupList = ref([])
|
||||
const rules = reactive({
|
||||
group: { required: true, message: '选择一个分组' },
|
||||
name: { required: true, message: '输入主机别名', trigger: 'change' },
|
||||
@ -164,17 +163,7 @@ const visible = computed({
|
||||
|
||||
const title = computed(() => props.defaultData ? '修改服务器' : '新增服务器')
|
||||
|
||||
watch(() => props.show, (newVal) => {
|
||||
if (!newVal) return
|
||||
getGroupList()
|
||||
})
|
||||
|
||||
const getGroupList = () => {
|
||||
$api.getGroupList()
|
||||
.then(({ data }) => {
|
||||
groupList.value = data
|
||||
})
|
||||
}
|
||||
let groupList = computed(() => $store.groupList || [])
|
||||
|
||||
const handleClosed = () => {
|
||||
// console.log('handleClosed')
|
||||
@ -185,7 +174,7 @@ const handleClosed = () => {
|
||||
|
||||
const setDefaultData = () => {
|
||||
if (!props.defaultData) return
|
||||
console.log(props.defaultData)
|
||||
// console.log(props.defaultData)
|
||||
let { name, host, index, expired, expiredNotify, consoleUrl, group, remark } = props.defaultData
|
||||
oldHost.value = host
|
||||
Object.assign(hostForm, { name, host, index, expired, expiredNotify, consoleUrl, group, remark })
|
||||
|
@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div class="server_group_collapse">
|
||||
<el-collapse v-model="activeGroup">
|
||||
<el-collapse-item v-for="(servers, groupName) in resList" :key="groupName" :name="groupName">
|
||||
<el-collapse-item v-for="(servers, groupName) in groupHostList" :key="groupName" :name="groupName">
|
||||
<template #title>
|
||||
<div class="group_title">
|
||||
{{ groupName }}
|
||||
@ -37,40 +37,21 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount, getCurrentInstance, reactive, computed, watch } from 'vue'
|
||||
import { ref, onBeforeUnmount, getCurrentInstance, computed, watch, onMounted } from 'vue'
|
||||
import { io } from 'socket.io-client'
|
||||
import HostForm from './components/host-form.vue'
|
||||
import Setting from './components/setting.vue'
|
||||
import HostCard from './components/host-card.vue'
|
||||
|
||||
const { proxy: { $api, $store, $message, $notification, $router, $serviceURI } } = getCurrentInstance()
|
||||
const { proxy: { $store, $notification, $router, $serviceURI, $message } } = getCurrentInstance()
|
||||
|
||||
const socket = ref(null)
|
||||
const loading = ref(true)
|
||||
const hostListStatus = ref([])
|
||||
const updateHostData = ref(null)
|
||||
const hostFormVisible = ref(false)
|
||||
const settingVisible = ref(false)
|
||||
const hiddenIp = ref(Number(localStorage.getItem('hiddenIp') || 0))
|
||||
const activeGroup = ref([])
|
||||
|
||||
const handleLogout = () => {
|
||||
$store.clearJwtToken()
|
||||
$message({ type: 'success', message: '已安全退出', center: true })
|
||||
$router.push('/login')
|
||||
}
|
||||
|
||||
const getHostList = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
await $store.getHostList()
|
||||
connectIo()
|
||||
} catch (err) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const connectIo = () => {
|
||||
if (socket.value) socket.value.close()
|
||||
let socketInstance = io($serviceURI, {
|
||||
path: '/clients',
|
||||
forceNew: true,
|
||||
@ -80,16 +61,15 @@ const connectIo = () => {
|
||||
socket.value = socketInstance
|
||||
socketInstance.on('connect', () => {
|
||||
let flag = 5
|
||||
loading.value = false
|
||||
console.log('clients websocket 已连接: ', socketInstance.id)
|
||||
let token = $store.token
|
||||
socketInstance.emit('init_clients_data', { token })
|
||||
socketInstance.on('clients_data', (data) => {
|
||||
if ((flag++ % 5) === 0) $store.getHostPing()
|
||||
hostListStatus.value = $store.hostList.map(item => {
|
||||
$store.hostList.forEach(item => {
|
||||
const { host } = item
|
||||
if (data[host] === null) return { ...item }
|
||||
return Object.assign({}, item, data[host])
|
||||
return Object.assign(item, data[host])
|
||||
})
|
||||
})
|
||||
socketInstance.on('token_verify_fail', (message) => {
|
||||
@ -105,14 +85,18 @@ const connectIo = () => {
|
||||
console.error('clients websocket 连接断开')
|
||||
})
|
||||
socketInstance.on('connect_error', (message) => {
|
||||
loading.value = false
|
||||
console.error('clients websocket 连接出错: ', message)
|
||||
})
|
||||
}
|
||||
|
||||
const handleUpdateList = () => {
|
||||
if (socket.value) socket.value.close()
|
||||
getHostList()
|
||||
const handleUpdateList = async () => {
|
||||
try {
|
||||
await $store.getHostList()
|
||||
// connectIo()
|
||||
} catch (err) {
|
||||
$message.error('获取主机列表失败')
|
||||
console.error('获取主机列表失败: ', err)
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateHost = (defaultData) => {
|
||||
@ -125,7 +109,7 @@ const handleHiddenIP = () => {
|
||||
localStorage.setItem('hiddenIp', String(hiddenIp.value))
|
||||
}
|
||||
|
||||
let resList = computed(() => {
|
||||
let groupHostList = computed(() => {
|
||||
let res = {}
|
||||
let hostList = $store.hostList
|
||||
let groupList = $store.groupList
|
||||
@ -146,15 +130,15 @@ let resList = computed(() => {
|
||||
return res
|
||||
})
|
||||
|
||||
watch(resList, () => {
|
||||
activeGroup.value = [...Object.keys(resList.value),]
|
||||
watch(groupHostList, () => {
|
||||
activeGroup.value = [...Object.keys(groupHostList.value),]
|
||||
}, {
|
||||
immediate: true,
|
||||
deep: false
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await getHostList()
|
||||
onMounted(() => {
|
||||
connectIo()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
264
web/src/views/terminal/index-old.vue
Normal file
264
web/src/views/terminal/index-old.vue
Normal file
@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<InfoSide
|
||||
ref="infoSideRef"
|
||||
v-model:show-input-command="showInputCommand"
|
||||
:token="token"
|
||||
:host="host"
|
||||
:visible="visible"
|
||||
@connect-sftp="connectSftp"
|
||||
@click-input-command="clickInputCommand"
|
||||
/>
|
||||
<section>
|
||||
<div class="terminals">
|
||||
<el-button class="full-screen-button" type="success" @click="handleFullScreen">
|
||||
{{ isFullScreen ? '退出全屏' : '全屏' }}
|
||||
</el-button>
|
||||
<div class="visible" @click="handleVisibleSidebar">
|
||||
<svg-icon name="icon-jiantou_zuoyouqiehuan" class="svg-icon" />
|
||||
</div>
|
||||
<el-tabs
|
||||
v-model="activeTab"
|
||||
type="border-card"
|
||||
addable
|
||||
tab-position="top"
|
||||
@tab-remove="removeTab"
|
||||
@tab-change="tabChange"
|
||||
@tab-add="tabAdd"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="item in terminalTabs"
|
||||
:key="item.key"
|
||||
:label="item.title"
|
||||
:name="item.key"
|
||||
:closable="closable"
|
||||
>
|
||||
<TerminalTab
|
||||
ref="terminalTabRefs"
|
||||
:token="token"
|
||||
:host="host"
|
||||
:tab-key="item.key"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div v-if="showSftp" class="sftp">
|
||||
<SftpFooter :token="token" :host="host" @resize="resizeTerminal" />
|
||||
</div>
|
||||
</section>
|
||||
<InputCommand v-model:show="showInputCommand" @input-command="handleInputCommand" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onBeforeMount, getCurrentInstance } from 'vue'
|
||||
import TerminalTab from './components/terminal-tab.vue'
|
||||
import InfoSide from './components/info-side.vue'
|
||||
import SftpFooter from './components/sftp-footer.vue'
|
||||
import InputCommand from '@/components/input-command/index.vue'
|
||||
|
||||
const { proxy: { $store, $router, $route, $nextTick } } = getCurrentInstance()
|
||||
|
||||
const name = ref('')
|
||||
const host = ref('')
|
||||
const token = $store.token
|
||||
const activeTab = ref('')
|
||||
const terminalTabs = reactive([])
|
||||
const isFullScreen = ref(false)
|
||||
const timer = ref(null)
|
||||
const showSftp = ref(false)
|
||||
const showInputCommand = ref(false)
|
||||
const visible = ref(true)
|
||||
const infoSideRef = ref(null)
|
||||
const terminalTabRefs = ref([])
|
||||
|
||||
const closable = computed(() => terminalTabs.length > 1)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (!token) return $router.push('login')
|
||||
let { host: routeHost, name: routeName } = $route.query
|
||||
name.value = routeName
|
||||
host.value = routeHost
|
||||
document.title = `${ document.title }-${ routeName }`
|
||||
let key = Date.now().toString()
|
||||
terminalTabs.push({ title: routeName, key })
|
||||
activeTab.value = key
|
||||
registryDbClick()
|
||||
})
|
||||
|
||||
// const windowBeforeUnload = () => {
|
||||
// window.onbeforeunload = () => {
|
||||
// return ''
|
||||
// }
|
||||
// }
|
||||
|
||||
const connectSftp = (flag) => {
|
||||
showSftp.value = flag
|
||||
resizeTerminal()
|
||||
}
|
||||
|
||||
const clickInputCommand = () => {
|
||||
showInputCommand.value = true
|
||||
}
|
||||
|
||||
const tabAdd = () => {
|
||||
if (timer.value) clearTimeout(timer.value)
|
||||
timer.value = setTimeout(() => {
|
||||
let title = name.value
|
||||
let key = Date.now().toString()
|
||||
terminalTabs.push({ title, key })
|
||||
activeTab.value = key
|
||||
tabChange(key)
|
||||
registryDbClick()
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const removeTab = (removeKey) => {
|
||||
let idx = terminalTabs.findIndex(({ key }) => removeKey === key)
|
||||
terminalTabs.splice(idx, 1)
|
||||
if (removeKey !== activeTab.value) return
|
||||
activeTab.value = terminalTabs[0].key
|
||||
}
|
||||
|
||||
const tabChange = async (key) => {
|
||||
await $nextTick()
|
||||
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => key === tabKey)
|
||||
curTabTerminal?.focusTab()
|
||||
}
|
||||
|
||||
const handleFullScreen = () => {
|
||||
if (isFullScreen.value) document.exitFullscreen()
|
||||
else document.getElementsByClassName('terminals')[0].requestFullscreen()
|
||||
isFullScreen.value = !isFullScreen.value
|
||||
}
|
||||
|
||||
const registryDbClick = () => {
|
||||
$nextTick(() => {
|
||||
let tabItems = Array.from(document.getElementsByClassName('el-tabs__item'))
|
||||
tabItems.forEach(item => {
|
||||
item.removeEventListener('dblclick', handleDblclick)
|
||||
item.addEventListener('dblclick', handleDblclick)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleDblclick = (e) => {
|
||||
if (terminalTabs.length > 1) {
|
||||
let key = e.target.id.substring(4)
|
||||
// console.log('dblclick', key)
|
||||
removeTab(key)
|
||||
}
|
||||
}
|
||||
|
||||
const handleVisibleSidebar = () => {
|
||||
visible.value = !visible.value
|
||||
resizeTerminal()
|
||||
}
|
||||
|
||||
const resizeTerminal = () => {
|
||||
for (let terminalTabRef of terminalTabRefs.value) {
|
||||
const { handleResize } = terminalTabRef || {}
|
||||
handleResize && handleResize()
|
||||
}
|
||||
}
|
||||
|
||||
const handleInputCommand = async (command) => {
|
||||
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => activeTab.value === tabKey)
|
||||
await $nextTick()
|
||||
curTabTerminal?.focusTab()
|
||||
curTabTerminal.handleInputCommand(`${ command }\n`)
|
||||
showInputCommand.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
|
||||
section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(100vw - 250px); // 减去左边栏
|
||||
|
||||
.terminals {
|
||||
min-height: 150px;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.full-screen-button {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 4px;
|
||||
z-index: 99999;
|
||||
}
|
||||
}
|
||||
|
||||
.sftp {
|
||||
border: 1px solid rgb(236, 215, 187);
|
||||
}
|
||||
|
||||
.visible {
|
||||
position: absolute;
|
||||
z-index: 999999;
|
||||
top: 13px;
|
||||
left: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.el-tabs {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-tabs--border-card>.el-tabs__content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// .el-tabs__nav-scroll {
|
||||
// .el-tabs__nav {
|
||||
// // padding-left: 60px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.el-tabs__new-tab {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
font-size: 50px;
|
||||
z-index: 98;
|
||||
}
|
||||
|
||||
// .el-tabs--border-card {
|
||||
// height: 100%;
|
||||
// overflow: hidden;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// }
|
||||
|
||||
.el-tabs__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.el-icon.is-icon-close {
|
||||
font-size: 13px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 2px;
|
||||
}
|
||||
</style>
|
@ -1,264 +1,53 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<InfoSide
|
||||
ref="infoSideRef"
|
||||
v-model:show-input-command="showInputCommand"
|
||||
:token="token"
|
||||
:host="host"
|
||||
:visible="visible"
|
||||
@connect-sftp="connectSftp"
|
||||
@click-input-command="clickInputCommand"
|
||||
/>
|
||||
<section>
|
||||
<div class="terminals">
|
||||
<el-button class="full-screen-button" type="success" @click="handleFullScreen">
|
||||
{{ isFullScreen ? '退出全屏' : '全屏' }}
|
||||
</el-button>
|
||||
<div class="visible" @click="handleVisibleSidebar">
|
||||
<svg-icon name="icon-jiantou_zuoyouqiehuan" class="svg-icon" />
|
||||
</div>
|
||||
<el-tabs
|
||||
v-model="activeTab"
|
||||
type="border-card"
|
||||
addable
|
||||
tab-position="top"
|
||||
@tab-remove="removeTab"
|
||||
@tab-change="tabChange"
|
||||
@tab-add="tabAdd"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="item in terminalTabs"
|
||||
:key="item.key"
|
||||
:label="item.title"
|
||||
:name="item.key"
|
||||
:closable="closable"
|
||||
>
|
||||
<TerminalTab
|
||||
ref="terminalTabRefs"
|
||||
:token="token"
|
||||
:host="host"
|
||||
:tab-key="item.key"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div v-if="showSftp" class="sftp">
|
||||
<SftpFooter :token="token" :host="host" @resize="resizeTerminal" />
|
||||
</div>
|
||||
</section>
|
||||
<InputCommand v-model:show="showInputCommand" @input-command="handleInputCommand" />
|
||||
<div class="terminal_container">
|
||||
<div v-if="showLinkTips" class="terminal_link_tips">
|
||||
<h2 class="quick_link_text">快速连接</h2>
|
||||
<el-table
|
||||
:data="hostList"
|
||||
:show-header="false"
|
||||
>
|
||||
<el-table-column prop="name" label="name" width="180" />
|
||||
<el-table-column prop="host" label="host" width="180" />
|
||||
<el-table-column prop="address" label="Address" />
|
||||
</el-table>
|
||||
<!-- <ul v-for="item in hostList" :key="item._id">
|
||||
{{ item.name }}
|
||||
{{ item.host }}
|
||||
{{ item.name }}
|
||||
</ul> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onBeforeMount, getCurrentInstance } from 'vue'
|
||||
import TerminalTab from './components/terminal-tab.vue'
|
||||
import InfoSide from './components/info-side.vue'
|
||||
import SftpFooter from './components/sftp-footer.vue'
|
||||
import InputCommand from '@/components/input-command/index.vue'
|
||||
import { ref, computed, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy: { $store, $router, $route, $nextTick } } = getCurrentInstance()
|
||||
const { proxy: { $store } } = getCurrentInstance()
|
||||
|
||||
const name = ref('')
|
||||
const host = ref('')
|
||||
const token = $store.token
|
||||
const activeTab = ref('')
|
||||
const terminalTabs = reactive([])
|
||||
const isFullScreen = ref(false)
|
||||
const timer = ref(null)
|
||||
const showSftp = ref(false)
|
||||
const showInputCommand = ref(false)
|
||||
const visible = ref(true)
|
||||
const infoSideRef = ref(null)
|
||||
const terminalTabRefs = ref([])
|
||||
let showLinkTips = ref(true)
|
||||
|
||||
const closable = computed(() => terminalTabs.length > 1)
|
||||
let hostList = computed(() => $store.hostList)
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (!token) return $router.push('login')
|
||||
let { host: routeHost, name: routeName } = $route.query
|
||||
name.value = routeName
|
||||
host.value = routeHost
|
||||
document.title = `${ document.title }-${ routeName }`
|
||||
let key = Date.now().toString()
|
||||
terminalTabs.push({ title: routeName, key })
|
||||
activeTab.value = key
|
||||
registryDbClick()
|
||||
})
|
||||
|
||||
// const windowBeforeUnload = () => {
|
||||
// window.onbeforeunload = () => {
|
||||
// return ''
|
||||
// }
|
||||
// }
|
||||
|
||||
const connectSftp = (flag) => {
|
||||
showSftp.value = flag
|
||||
resizeTerminal()
|
||||
}
|
||||
|
||||
const clickInputCommand = () => {
|
||||
showInputCommand.value = true
|
||||
}
|
||||
|
||||
const tabAdd = () => {
|
||||
if (timer.value) clearTimeout(timer.value)
|
||||
timer.value = setTimeout(() => {
|
||||
let title = name.value
|
||||
let key = Date.now().toString()
|
||||
terminalTabs.push({ title, key })
|
||||
activeTab.value = key
|
||||
tabChange(key)
|
||||
registryDbClick()
|
||||
}, 200)
|
||||
}
|
||||
|
||||
const removeTab = (removeKey) => {
|
||||
let idx = terminalTabs.findIndex(({ key }) => removeKey === key)
|
||||
terminalTabs.splice(idx, 1)
|
||||
if (removeKey !== activeTab.value) return
|
||||
activeTab.value = terminalTabs[0].key
|
||||
}
|
||||
|
||||
const tabChange = async (key) => {
|
||||
await $nextTick()
|
||||
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => key === tabKey)
|
||||
curTabTerminal?.focusTab()
|
||||
}
|
||||
|
||||
const handleFullScreen = () => {
|
||||
if (isFullScreen.value) document.exitFullscreen()
|
||||
else document.getElementsByClassName('terminals')[0].requestFullscreen()
|
||||
isFullScreen.value = !isFullScreen.value
|
||||
}
|
||||
|
||||
const registryDbClick = () => {
|
||||
$nextTick(() => {
|
||||
let tabItems = Array.from(document.getElementsByClassName('el-tabs__item'))
|
||||
tabItems.forEach(item => {
|
||||
item.removeEventListener('dblclick', handleDblclick)
|
||||
item.addEventListener('dblclick', handleDblclick)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const handleDblclick = (e) => {
|
||||
if (terminalTabs.length > 1) {
|
||||
let key = e.target.id.substring(4)
|
||||
// console.log('dblclick', key)
|
||||
removeTab(key)
|
||||
}
|
||||
}
|
||||
|
||||
const handleVisibleSidebar = () => {
|
||||
visible.value = !visible.value
|
||||
resizeTerminal()
|
||||
}
|
||||
|
||||
const resizeTerminal = () => {
|
||||
for (let terminalTabRef of terminalTabRefs.value) {
|
||||
const { handleResize } = terminalTabRef || {}
|
||||
handleResize && handleResize()
|
||||
}
|
||||
}
|
||||
|
||||
const handleInputCommand = async (command) => {
|
||||
const curTabTerminal = terminalTabRefs.value.find(({ tabKey }) => activeTab.value === tabKey)
|
||||
await $nextTick()
|
||||
curTabTerminal?.focusTab()
|
||||
curTabTerminal.handleInputCommand(`${ command }\n`)
|
||||
showInputCommand.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
|
||||
section {
|
||||
flex: 1;
|
||||
.terminal_container {
|
||||
.terminal_link_tips {
|
||||
width: 50%;
|
||||
// margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(100vw - 250px); // 减去左边栏
|
||||
|
||||
.terminals {
|
||||
min-height: 150px;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.full-screen-button {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 4px;
|
||||
z-index: 99999;
|
||||
}
|
||||
}
|
||||
|
||||
.sftp {
|
||||
border: 1px solid rgb(236, 215, 187);
|
||||
}
|
||||
|
||||
.visible {
|
||||
position: absolute;
|
||||
z-index: 999999;
|
||||
top: 13px;
|
||||
left: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
.quick_link_text {
|
||||
align-self: self-start;
|
||||
margin: 0 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.el-tabs {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-tabs--border-card>.el-tabs__content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-tabs__header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// .el-tabs__nav-scroll {
|
||||
// .el-tabs__nav {
|
||||
// // padding-left: 60px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.el-tabs__new-tab {
|
||||
position: absolute;
|
||||
left: 18px;
|
||||
font-size: 50px;
|
||||
z-index: 98;
|
||||
}
|
||||
|
||||
// .el-tabs--border-card {
|
||||
// height: 100%;
|
||||
// overflow: hidden;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// }
|
||||
|
||||
.el-tabs__content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.el-icon.is-icon-close {
|
||||
font-size: 13px;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 2px;
|
||||
}
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user