✨ 完善终端UI与逻辑
This commit is contained in:
parent
6a13c961c3
commit
851d063773
@ -5,14 +5,12 @@ import CryptoJS from 'crypto-js'
|
|||||||
|
|
||||||
export const EventBus = reactive({})
|
export const EventBus = reactive({})
|
||||||
|
|
||||||
// 在组件中触发事件
|
|
||||||
EventBus.$emit = (event, data) => {
|
EventBus.$emit = (event, data) => {
|
||||||
if (EventBus[event]) {
|
if (EventBus[event]) {
|
||||||
EventBus[event].forEach(callback => callback(data))
|
EventBus[event].forEach(callback => callback(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在组件中监听事件
|
|
||||||
EventBus.$on = (event, callback) => {
|
EventBus.$on = (event, callback) => {
|
||||||
if (!EventBus[event]) {
|
if (!EventBus[event]) {
|
||||||
EventBus[event] = []
|
EventBus[event] = []
|
||||||
|
@ -1,17 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="info_container" :style="{ width: visible ? `250px` : 0 }">
|
<div class="info_container" :style="{ width: visible ? `250px` : 0 }">
|
||||||
<header>
|
<!-- <el-divider class="first-divider" content-position="center">地理位置</el-divider> -->
|
||||||
<a href="/">
|
|
||||||
<img src="@/assets/logo-easynode.png" alt="logo">
|
|
||||||
</a>
|
|
||||||
<!-- <div class="visible" @click="visibleSidebar">
|
|
||||||
<svg-icon
|
|
||||||
name="icon-xianshi"
|
|
||||||
class="svg-icon"
|
|
||||||
/>
|
|
||||||
</div> -->
|
|
||||||
</header>
|
|
||||||
<el-divider class="first-divider" content-position="center">地理位置</el-divider>
|
|
||||||
<el-descriptions
|
<el-descriptions
|
||||||
class="margin-top"
|
class="margin-top"
|
||||||
:column="1"
|
:column="1"
|
||||||
@ -47,7 +36,9 @@
|
|||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<el-divider content-position="center">实时监控</el-divider>
|
<!-- <el-divider content-position="center">实时监控</el-divider> -->
|
||||||
|
<br>
|
||||||
|
|
||||||
<el-descriptions
|
<el-descriptions
|
||||||
class="margin-top"
|
class="margin-top"
|
||||||
:column="1"
|
:column="1"
|
||||||
@ -118,7 +109,9 @@
|
|||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<el-divider content-position="center">系统信息</el-divider>
|
<!-- <el-divider content-position="center">系统信息</el-divider> -->
|
||||||
|
<br>
|
||||||
|
|
||||||
<el-descriptions
|
<el-descriptions
|
||||||
class="margin-top"
|
class="margin-top"
|
||||||
:column="1"
|
:column="1"
|
||||||
@ -188,13 +181,13 @@
|
|||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<el-divider content-position="center">FEATURE</el-divider>
|
<el-divider content-position="center">FEATURE</el-divider>
|
||||||
<el-button
|
<!-- <el-button
|
||||||
:type="sftpStatus ? 'primary' : 'success'"
|
:type="sftpStatus ? 'primary' : 'success'"
|
||||||
style="display: block;width: 80%;margin: 30px auto;"
|
style="display: block;width: 80%;margin: 30px auto;"
|
||||||
@click="handleSftp"
|
@click="handleSftp"
|
||||||
>
|
>
|
||||||
{{ sftpStatus ? '关闭SFTP' : '连接SFTP' }}
|
{{ sftpStatus ? '关闭SFTP' : '连接SFTP' }}
|
||||||
</el-button>
|
</el-button> -->
|
||||||
<el-button
|
<el-button
|
||||||
:type="inputCommandStyle ? 'primary' : 'success'"
|
:type="inputCommandStyle ? 'primary' : 'success'"
|
||||||
style="display: block;width: 80%;margin: 30px auto;"
|
style="display: block;width: 80%;margin: 30px auto;"
|
||||||
@ -267,10 +260,10 @@ const inputCommandStyle = computed({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleSftp = () => {
|
// const handleSftp = () => {
|
||||||
sftpStatus.value = !sftpStatus.value
|
// sftpStatus.value = !sftpStatus.value
|
||||||
emit('connect-sftp', sftpStatus.value)
|
// emit('connect-sftp', sftpStatus.value)
|
||||||
}
|
// }
|
||||||
|
|
||||||
const clickInputCommand = () => {
|
const clickInputCommand = () => {
|
||||||
inputCommandStyle.value = true
|
inputCommandStyle.value = true
|
||||||
@ -353,24 +346,25 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.info_container {
|
.info_container {
|
||||||
|
// border-top: 1px solid var(--el-border-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
background-color: #fff; //#E0E2EF;
|
background-color: #fff; //#E0E2EF;
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
|
|
||||||
header {
|
// header {
|
||||||
display: flex;
|
// display: flex;
|
||||||
justify-content: space-between;
|
// justify-content: space-between;
|
||||||
align-items: center;
|
// align-items: center;
|
||||||
height: 30px;
|
// height: 30px;
|
||||||
margin: 10px;
|
// margin: 10px;
|
||||||
position: relative;
|
// position: relative;
|
||||||
|
|
||||||
img {
|
// img {
|
||||||
cursor: pointer;
|
// cursor: pointer;
|
||||||
height: 80%;
|
// height: 80%;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 表格中系统标识的title
|
// 表格中系统标识的title
|
||||||
.item-title {
|
.item-title {
|
||||||
|
@ -533,6 +533,7 @@ const getPath = (name = '') => {
|
|||||||
.sftp_tab_container {
|
.sftp_tab_container {
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
border: 1px solid var(--el-border-color);
|
||||||
.adjust {
|
.adjust {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -1,12 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="terminal_wrap">
|
<div class="terminal_wrap">
|
||||||
<InfoSide
|
<div class="info_box">
|
||||||
ref="infoSideRef"
|
<div class="top">
|
||||||
v-model:show-input-command="showInputCommand"
|
<el-dropdown trigger="click">
|
||||||
:host-info="curHost"
|
<div class="action_wrap">
|
||||||
:visible="visible"
|
<span class="link_host">连接<el-icon class="el-icon--right"><arrow-down /></el-icon></span>
|
||||||
@click-input-command="clickInputCommand"
|
</div>
|
||||||
/>
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="(item, index) in hostList" :key="index" @click="handleCommandHost(item)">
|
||||||
|
{{ item.name }} {{ item.host }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<InfoSide
|
||||||
|
ref="infoSideRef"
|
||||||
|
v-model:show-input-command="showInputCommand"
|
||||||
|
:host-info="curHost"
|
||||||
|
:visible="visible"
|
||||||
|
@click-input-command="clickInputCommand"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="terminals_sftp_wrap">
|
<div class="terminals_sftp_wrap">
|
||||||
<!-- <el-button class="full-screen-button" type="success" @click="handleFullScreen">
|
<!-- <el-button class="full-screen-button" type="success" @click="handleFullScreen">
|
||||||
{{ isFullScreen ? '退出全屏' : '全屏' }}
|
{{ isFullScreen ? '退出全屏' : '全屏' }}
|
||||||
@ -40,13 +56,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineEmits, computed, defineProps, getCurrentInstance, watch, onMounted } from 'vue'
|
import { ref, defineEmits, computed, defineProps, getCurrentInstance, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import { ArrowDown } from '@element-plus/icons-vue'
|
||||||
import TerminalTab from './terminal-tab.vue'
|
import TerminalTab from './terminal-tab.vue'
|
||||||
import InfoSide from './info-side.vue'
|
import InfoSide from './info-side.vue'
|
||||||
import Sftp from './sftp.vue'
|
import Sftp from './sftp.vue'
|
||||||
import InputCommand from '@/components/input-command/index.vue'
|
import InputCommand from '@/components/input-command/index.vue'
|
||||||
|
|
||||||
const { proxy: { $nextTick } } = getCurrentInstance()
|
const { proxy: { $nextTick, $store } } = getCurrentInstance()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
terminalTabs: {
|
terminalTabs: {
|
||||||
@ -55,13 +72,12 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['closed', 'removeTab',])
|
const emit = defineEmits(['closed', 'removeTab', 'add-host',])
|
||||||
|
|
||||||
const activeTabIndex = ref(0)
|
const activeTabIndex = ref(0)
|
||||||
// const terminalTabs = reactive([])
|
// const terminalTabs = reactive([])
|
||||||
const isFullScreen = ref(false)
|
const isFullScreen = ref(false)
|
||||||
const timer = ref(null)
|
const timer = ref(null)
|
||||||
const showSftp = ref(false)
|
|
||||||
const showInputCommand = ref(false)
|
const showInputCommand = ref(false)
|
||||||
const visible = ref(true)
|
const visible = ref(true)
|
||||||
const infoSideRef = ref(null)
|
const infoSideRef = ref(null)
|
||||||
@ -71,15 +87,29 @@ let mainHeight = ref('')
|
|||||||
const terminalTabs = computed(() => props.terminalTabs)
|
const terminalTabs = computed(() => props.terminalTabs)
|
||||||
const terminalTabsLen = computed(() => props.terminalTabs.length)
|
const terminalTabsLen = computed(() => props.terminalTabs.length)
|
||||||
const curHost = computed(() => terminalTabs.value[activeTabIndex.value])
|
const curHost = computed(() => terminalTabs.value[activeTabIndex.value])
|
||||||
|
let hostList = computed(() => $store.hostList)
|
||||||
|
|
||||||
// const closable = computed(() => terminalTabs.length > 1)
|
// const closable = computed(() => terminalTabs.length > 1)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
$nextTick(() => {
|
handleResizeTerminalSftp()
|
||||||
mainHeight.value = document.querySelector('.terminals_sftp_wrap').offsetHeight - 45 // 45 is tab-header height+10
|
window.addEventListener('resize', handleResizeTerminalSftp)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', handleResizeTerminalSftp)
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleResizeTerminalSftp() {
|
||||||
|
$nextTick(() => {
|
||||||
|
mainHeight.value = document.querySelector('.terminals_sftp_wrap').offsetHeight - 45 // 45 is tab-header height+15
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCommandHost = (host) => {
|
||||||
|
emit('add-host', host)
|
||||||
|
}
|
||||||
|
|
||||||
const tabChange = async (index) => {
|
const tabChange = async (index) => {
|
||||||
await $nextTick()
|
await $nextTick()
|
||||||
const curTabTerminal = terminalTabRefs.value[index]
|
const curTabTerminal = terminalTabRefs.value[index]
|
||||||
@ -178,10 +208,40 @@ const handleInputCommand = async (command) => {
|
|||||||
:deep(.el-tabs__content) {
|
:deep(.el-tabs__content) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px;
|
padding: 0 5px 5px 0;
|
||||||
padding-top: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs--border-card) {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info_box {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.top {
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
padding-right: 15px;
|
||||||
|
display: flex;
|
||||||
|
:deep(.el-dropdown) {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.action_wrap {
|
||||||
|
height: 39px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.link_host {
|
||||||
|
font-size: var(--el-font-size-base);
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.terminals_sftp_wrap {
|
.terminals_sftp_wrap {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -228,13 +288,6 @@ const handleInputCommand = async (command) => {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
// .el-tabs {
|
|
||||||
// border: none;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .el-tabs--border-card>.el-tabs__content {
|
|
||||||
// padding: 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .el-tabs__header {
|
// .el-tabs__header {
|
||||||
// position: sticky;
|
// position: sticky;
|
||||||
@ -243,12 +296,6 @@ const handleInputCommand = async (command) => {
|
|||||||
// user-select: none;
|
// user-select: none;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// .el-tabs__nav-scroll {
|
|
||||||
// .el-tabs__nav {
|
|
||||||
// // padding-left: 60px;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .el-tabs__new-tab {
|
// .el-tabs__new-tab {
|
||||||
// position: absolute;
|
// position: absolute;
|
||||||
// left: 18px;
|
// left: 18px;
|
||||||
|
@ -35,7 +35,12 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<Terminal ref="terminalRef" :terminal-tabs="terminalTabs" @remove-tab="handleRemoveTab" />
|
<Terminal
|
||||||
|
ref="terminalRef"
|
||||||
|
:terminal-tabs="terminalTabs"
|
||||||
|
@remove-tab="handleRemoveTab"
|
||||||
|
@add-host="linkTerminal"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<HostForm
|
<HostForm
|
||||||
v-model:show="hostFormVisible"
|
v-model:show="hostFormVisible"
|
||||||
@ -47,12 +52,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onActivated, getCurrentInstance, reactive, nextTick, watch } from 'vue'
|
import { ref, computed, onActivated, getCurrentInstance, reactive, nextTick } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import Terminal from './components/terminal.vue'
|
import Terminal from './components/terminal.vue'
|
||||||
import HostForm from '../server/components/host-form.vue'
|
import HostForm from '../server/components/host-form.vue'
|
||||||
|
|
||||||
const { proxy: { $store, $route, $message } } = getCurrentInstance()
|
const { proxy: { $store, $message } } = getCurrentInstance()
|
||||||
|
|
||||||
let terminalTabs = reactive([])
|
let terminalTabs = reactive([])
|
||||||
const hostFormVisible = ref(false)
|
const hostFormVisible = ref(false)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user