完善终端UI与逻辑

This commit is contained in:
chaoszhu 2024-07-23 00:34:31 +08:00
parent 6a13c961c3
commit 851d063773
5 changed files with 111 additions and 66 deletions

View File

@ -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] = []

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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)