🐛 修复socket响应数据bug
This commit is contained in:
parent
db07d663e2
commit
eaa5e5e65d
@ -9,7 +9,7 @@ module.exports = {
|
|||||||
parser: 'vue-eslint-parser',
|
parser: 'vue-eslint-parser',
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
// parser: 'babel-eslint',
|
// parser: 'babel-eslint',
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2022,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
'jsx': true
|
'jsx': true
|
||||||
|
@ -29,13 +29,24 @@ const useStore = defineStore({
|
|||||||
sessionStorage.clear('token')
|
sessionStorage.clear('token')
|
||||||
this.$patch({ token: null })
|
this.$patch({ token: null })
|
||||||
},
|
},
|
||||||
async getHostList() {
|
async getMainData() {
|
||||||
const { data: groupList } = await $api.getGroupList()
|
const { data: groupList } = await $api.getGroupList()
|
||||||
const { data: hostList } = await $api.getHostList()
|
const { data: hostList } = await $api.getHostList()
|
||||||
|
// const { data: sshList } = await $api.getSshList()
|
||||||
// console.log('hostList:', hostList)
|
// console.log('hostList:', hostList)
|
||||||
// console.log('groupList:', groupList)
|
// console.log('groupList:', groupList)
|
||||||
this.$patch({ hostList, 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() {
|
getHostPing() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.hostList.forEach((item) => {
|
this.hostList.forEach((item) => {
|
||||||
|
@ -122,13 +122,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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 { proxy: { $api, $message, $messageBox, $store } } = getCurrentInstance()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const groupList = ref([])
|
|
||||||
const groupForm = reactive({
|
const groupForm = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
index: ''
|
index: ''
|
||||||
@ -167,15 +166,7 @@ const list = computed(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const getGroupList = () => {
|
let groupList = computed(() => $store.groupList || [])
|
||||||
loading.value = true
|
|
||||||
$api.getGroupList()
|
|
||||||
.then(({ data }) => {
|
|
||||||
groupList.value = data
|
|
||||||
// groupForm.index = data.length
|
|
||||||
})
|
|
||||||
.finally(() => loading.value = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addGroup = () => {
|
const addGroup = () => {
|
||||||
groupFormRef.value.validate()
|
groupFormRef.value.validate()
|
||||||
@ -186,7 +177,8 @@ const addGroup = () => {
|
|||||||
$message.success('success')
|
$message.success('success')
|
||||||
groupForm.name = ''
|
groupForm.name = ''
|
||||||
groupForm.index = ''
|
groupForm.index = ''
|
||||||
getGroupList()
|
$store.getGroupList()
|
||||||
|
groupFormRef.value.resetFields()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -206,7 +198,7 @@ const updateGroup = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
$message.success('success')
|
$message.success('success')
|
||||||
visible.value = false
|
visible.value = false
|
||||||
getGroupList()
|
$store.getGroupList()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -220,14 +212,11 @@ const deleteGroup = ({ id, name }) => {
|
|||||||
.then(async () => {
|
.then(async () => {
|
||||||
await $api.deleteGroup(id)
|
await $api.deleteGroup(id)
|
||||||
await $store.getHostList()
|
await $store.getHostList()
|
||||||
|
await $store.getGroupList()
|
||||||
$message.success('success')
|
$message.success('success')
|
||||||
getGroupList()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getGroupList()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<AsideBox />
|
<AsideBox />
|
||||||
<div class="main_container">
|
<div class="main_container">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
<router-view v-slot="{ Component }" class="router_box">
|
<router-view v-slot="{ Component }" v-loading="loading" class="router_box">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
@ -13,8 +13,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, onBeforeMount, getCurrentInstance } from 'vue'
|
||||||
import AsideBox from '@/components/aside-box.vue'
|
import AsideBox from '@/components/aside-box.vue'
|
||||||
import TopBar from '@/components/top-bar.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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -113,9 +113,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
@ -143,7 +143,6 @@ const resetForm = () => ({
|
|||||||
|
|
||||||
const hostForm = reactive(resetForm())
|
const hostForm = reactive(resetForm())
|
||||||
const oldHost = ref('')
|
const oldHost = ref('')
|
||||||
const groupList = ref([])
|
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
group: { required: true, message: '选择一个分组' },
|
group: { required: true, message: '选择一个分组' },
|
||||||
name: { required: true, message: '输入主机别名', trigger: 'change' },
|
name: { required: true, message: '输入主机别名', trigger: 'change' },
|
||||||
@ -164,17 +163,7 @@ const visible = computed({
|
|||||||
|
|
||||||
const title = computed(() => props.defaultData ? '修改服务器' : '新增服务器')
|
const title = computed(() => props.defaultData ? '修改服务器' : '新增服务器')
|
||||||
|
|
||||||
watch(() => props.show, (newVal) => {
|
let groupList = computed(() => $store.groupList || [])
|
||||||
if (!newVal) return
|
|
||||||
getGroupList()
|
|
||||||
})
|
|
||||||
|
|
||||||
const getGroupList = () => {
|
|
||||||
$api.getGroupList()
|
|
||||||
.then(({ data }) => {
|
|
||||||
groupList.value = data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClosed = () => {
|
const handleClosed = () => {
|
||||||
// console.log('handleClosed')
|
// console.log('handleClosed')
|
||||||
@ -185,7 +174,7 @@ const handleClosed = () => {
|
|||||||
|
|
||||||
const setDefaultData = () => {
|
const setDefaultData = () => {
|
||||||
if (!props.defaultData) return
|
if (!props.defaultData) return
|
||||||
console.log(props.defaultData)
|
// console.log(props.defaultData)
|
||||||
let { name, host, index, expired, expiredNotify, consoleUrl, group, remark } = props.defaultData
|
let { name, host, index, expired, expiredNotify, consoleUrl, group, remark } = props.defaultData
|
||||||
oldHost.value = host
|
oldHost.value = host
|
||||||
Object.assign(hostForm, { name, host, index, expired, expiredNotify, consoleUrl, group, remark })
|
Object.assign(hostForm, { name, host, index, expired, expiredNotify, consoleUrl, group, remark })
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="server_group_collapse">
|
<div class="server_group_collapse">
|
||||||
<el-collapse v-model="activeGroup">
|
<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>
|
<template #title>
|
||||||
<div class="group_title">
|
<div class="group_title">
|
||||||
{{ groupName }}
|
{{ groupName }}
|
||||||
@ -37,40 +37,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<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 { io } from 'socket.io-client'
|
||||||
import HostForm from './components/host-form.vue'
|
import HostForm from './components/host-form.vue'
|
||||||
import Setting from './components/setting.vue'
|
|
||||||
import HostCard from './components/host-card.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 socket = ref(null)
|
||||||
const loading = ref(true)
|
|
||||||
const hostListStatus = ref([])
|
|
||||||
const updateHostData = ref(null)
|
const updateHostData = ref(null)
|
||||||
const hostFormVisible = ref(false)
|
const hostFormVisible = ref(false)
|
||||||
const settingVisible = ref(false)
|
|
||||||
const hiddenIp = ref(Number(localStorage.getItem('hiddenIp') || 0))
|
const hiddenIp = ref(Number(localStorage.getItem('hiddenIp') || 0))
|
||||||
const activeGroup = ref([])
|
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 = () => {
|
const connectIo = () => {
|
||||||
|
if (socket.value) socket.value.close()
|
||||||
let socketInstance = io($serviceURI, {
|
let socketInstance = io($serviceURI, {
|
||||||
path: '/clients',
|
path: '/clients',
|
||||||
forceNew: true,
|
forceNew: true,
|
||||||
@ -80,16 +61,15 @@ const connectIo = () => {
|
|||||||
socket.value = socketInstance
|
socket.value = socketInstance
|
||||||
socketInstance.on('connect', () => {
|
socketInstance.on('connect', () => {
|
||||||
let flag = 5
|
let flag = 5
|
||||||
loading.value = false
|
|
||||||
console.log('clients websocket 已连接: ', socketInstance.id)
|
console.log('clients websocket 已连接: ', socketInstance.id)
|
||||||
let token = $store.token
|
let token = $store.token
|
||||||
socketInstance.emit('init_clients_data', { token })
|
socketInstance.emit('init_clients_data', { token })
|
||||||
socketInstance.on('clients_data', (data) => {
|
socketInstance.on('clients_data', (data) => {
|
||||||
if ((flag++ % 5) === 0) $store.getHostPing()
|
if ((flag++ % 5) === 0) $store.getHostPing()
|
||||||
hostListStatus.value = $store.hostList.map(item => {
|
$store.hostList.forEach(item => {
|
||||||
const { host } = item
|
const { host } = item
|
||||||
if (data[host] === null) return { ...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) => {
|
socketInstance.on('token_verify_fail', (message) => {
|
||||||
@ -105,14 +85,18 @@ const connectIo = () => {
|
|||||||
console.error('clients websocket 连接断开')
|
console.error('clients websocket 连接断开')
|
||||||
})
|
})
|
||||||
socketInstance.on('connect_error', (message) => {
|
socketInstance.on('connect_error', (message) => {
|
||||||
loading.value = false
|
|
||||||
console.error('clients websocket 连接出错: ', message)
|
console.error('clients websocket 连接出错: ', message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdateList = () => {
|
const handleUpdateList = async () => {
|
||||||
if (socket.value) socket.value.close()
|
try {
|
||||||
getHostList()
|
await $store.getHostList()
|
||||||
|
// connectIo()
|
||||||
|
} catch (err) {
|
||||||
|
$message.error('获取主机列表失败')
|
||||||
|
console.error('获取主机列表失败: ', err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdateHost = (defaultData) => {
|
const handleUpdateHost = (defaultData) => {
|
||||||
@ -125,7 +109,7 @@ const handleHiddenIP = () => {
|
|||||||
localStorage.setItem('hiddenIp', String(hiddenIp.value))
|
localStorage.setItem('hiddenIp', String(hiddenIp.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
let resList = computed(() => {
|
let groupHostList = computed(() => {
|
||||||
let res = {}
|
let res = {}
|
||||||
let hostList = $store.hostList
|
let hostList = $store.hostList
|
||||||
let groupList = $store.groupList
|
let groupList = $store.groupList
|
||||||
@ -146,15 +130,15 @@ let resList = computed(() => {
|
|||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(resList, () => {
|
watch(groupHostList, () => {
|
||||||
activeGroup.value = [...Object.keys(resList.value),]
|
activeGroup.value = [...Object.keys(groupHostList.value),]
|
||||||
}, {
|
}, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
deep: false
|
deep: false
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
await getHostList()
|
connectIo()
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
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>
|
<template>
|
||||||
<div class="container">
|
<div class="terminal_container">
|
||||||
<InfoSide
|
<div v-if="showLinkTips" class="terminal_link_tips">
|
||||||
ref="infoSideRef"
|
<h2 class="quick_link_text">快速连接</h2>
|
||||||
v-model:show-input-command="showInputCommand"
|
<el-table
|
||||||
:token="token"
|
:data="hostList"
|
||||||
:host="host"
|
:show-header="false"
|
||||||
:visible="visible"
|
>
|
||||||
@connect-sftp="connectSftp"
|
<el-table-column prop="name" label="name" width="180" />
|
||||||
@click-input-command="clickInputCommand"
|
<el-table-column prop="host" label="host" width="180" />
|
||||||
/>
|
<el-table-column prop="address" label="Address" />
|
||||||
<section>
|
</el-table>
|
||||||
<div class="terminals">
|
<!-- <ul v-for="item in hostList" :key="item._id">
|
||||||
<el-button class="full-screen-button" type="success" @click="handleFullScreen">
|
{{ item.name }}
|
||||||
{{ isFullScreen ? '退出全屏' : '全屏' }}
|
{{ item.host }}
|
||||||
</el-button>
|
{{ item.name }}
|
||||||
<div class="visible" @click="handleVisibleSidebar">
|
</ul> -->
|
||||||
<svg-icon name="icon-jiantou_zuoyouqiehuan" class="svg-icon" />
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onBeforeMount, getCurrentInstance } from 'vue'
|
import { ref, computed, 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 { proxy: { $store } } = getCurrentInstance()
|
||||||
|
|
||||||
const name = ref('')
|
let showLinkTips = ref(true)
|
||||||
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)
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.container {
|
.terminal_container {
|
||||||
display: flex;
|
.terminal_link_tips {
|
||||||
height: 100vh;
|
width: 50%;
|
||||||
|
// margin: 0 auto;
|
||||||
section {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: calc(100vw - 250px); // 减去左边栏
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
.terminals {
|
padding: 20px;
|
||||||
min-height: 150px;
|
.quick_link_text {
|
||||||
flex: 1;
|
align-self: self-start;
|
||||||
position: relative;
|
margin: 0 10px;
|
||||||
|
font-size: 14px;
|
||||||
.full-screen-button {
|
font-weight: 600;
|
||||||
position: absolute;
|
line-height: 22px;
|
||||||
right: 10px;
|
margin-bottom: 15px;
|
||||||
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>
|
||||||
|
|
||||||
<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