feat(terminal): 工具栏增加宝塔一键登录按钮

终端有打开会话时,在字号左侧提供宝塔登录,复用 useBtPanelLogin。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
r
2026-07-03 23:19:16 +08:00
parent 41bff9dff6
commit d976cd03a3
4 changed files with 114 additions and 1 deletions
@@ -0,0 +1,39 @@
# 审计 — 2026-07-03 Terminal 宝塔登录按钮
**Changelog**: `docs/changelog/2026-07-03-terminal-btpanel-login-button.md`
## Step 3 规则扫描
| 文件 | 规则 | 结论 |
|------|------|------|
| `TerminalToolbar.vue` | 仅 `session` 存在时显示;loading 防重复点击 | PASS |
| `TerminalPage.vue` | 复用 `useBtPanelLogin``serverId` 取自 `activeSession` | PASS |
| `useBtPanelLogin.ts` | 未改;并发去重与 `window.open` 行为不变 | PASS |
## Closure
| 项 | 状态 | 说明 |
|----|------|------|
| 终端内无法一键进宝塔 | FIXED | 工具栏「宝塔登录」+ 移动端菜单 |
| 与列表登录行为不一致 | N/A | 同一 composable / API |
## 文件清单
- `frontend/src/components/terminal/TerminalToolbar.vue`
- `frontend/src/pages/TerminalPage.vue`
## DoD
- [x] Step 3 规则扫描
- [x] Closure 表
- [x] 文件清单与本次批次一致
- [x] `npm run build-only` 通过
- [x] `pre_deploy_check.sh` 全门通过
## 验证
```bash
cd frontend && npm run build-only
bash deploy/pre_deploy_check.sh
bash deploy/deploy-frontend.sh
```
@@ -0,0 +1,29 @@
# Terminal 工具栏一键登录宝塔
**日期**2026-07-03
## 变更摘要
在 WebSSH 终端页,当已打开服务器会话时,于工具栏「字号」左侧增加 **宝塔登录** 按钮;复用服务器列表同一套 `useBtPanelLogin` 逻辑,对当前激活标签的 `serverId` 生成并打开宝塔面板登录链接。
## 动机
运维在终端排障时常需切到宝塔面板,此前只能回服务器列表点「一键登录」;在终端内直接登录可减少跳转。
## 涉及文件
**前端**
- `frontend/src/components/terminal/TerminalToolbar.vue` — 桌面按钮 + 移动端齿轮菜单项
- `frontend/src/pages/TerminalPage.vue` — 接入 `useBtPanelLogin`、loading 与点击处理
## 迁移 / 重启
- 仅前端静态资源:`bash deploy/deploy-frontend.sh`Docker 生产走 `nexus-1panel.sh upgrade` 重建镜像内前端)
## 验证
```bash
cd frontend && npm run build-only
# 打开 #/terminal,连接任意服务器后工具栏应显示「宝塔登录」
```
@@ -37,6 +37,20 @@
</div>
<div class="d-flex align-center ga-1 terminal-toolbar-actions shrink-0">
<div class="d-none d-md-flex align-center ga-2">
<v-btn
v-if="session"
variant="tonal"
color="deep-orange"
size="x-small"
prepend-icon="mdi-login-variant"
title="一键登录宝塔面板"
class="terminal-toolbar__bt-login"
:loading="btLoginLoading"
:disabled="btLoginLoading"
@click="$emit('bt-login')"
>
宝塔登录
</v-btn>
<span class="text-caption text-medium-emphasis shrink-0">字号</span>
<v-btn variant="tonal" size="x-small" @click="$emit('font-change', -1)"></v-btn>
<span class="text-body-2 font-weight-medium terminal-toolbar__font-size">
@@ -67,6 +81,17 @@
/>
</template>
<v-list density="compact" nav class="py-1">
<v-list-item
v-if="session"
:disabled="btLoginLoading"
@click="$emit('bt-login')"
>
<template #prepend>
<v-icon color="deep-orange">mdi-login-variant</v-icon>
</template>
<v-list-item-title>一键登录宝塔</v-list-item-title>
</v-list-item>
<v-divider v-if="session" class="my-1" />
<v-list-subheader>字号 {{ fontSize }}</v-list-subheader>
<v-list-item @click="$emit('font-change', -1)">
<v-list-item-title>缩小</v-list-item-title>
@@ -112,6 +137,7 @@ const props = defineProps<{
session: TermSessionData | null
fontSize: number
scrollback: number
btLoginLoading?: boolean
}>()
defineEmits<{
@@ -119,6 +145,7 @@ defineEmits<{
'scrollback-change': [value: number]
fullscreen: []
disconnect: []
'bt-login': []
}>()
const statusLabel = computed(() =>
@@ -146,6 +173,11 @@ const statusColor = computed(() =>
min-width: 6.5rem;
max-width: 7.5rem;
}
.terminal-toolbar__bt-login {
font-weight: 600;
letter-spacing: 0.01em;
text-transform: none;
}
.font-mono {
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', Menlo, monospace;
}
+14 -1
View File
@@ -34,10 +34,12 @@
:session="activeSession"
:font-size="fontSize"
:scrollback="scrollback"
:bt-login-loading="btLoginLoading"
@font-change="changeFontSize"
@scrollback-change="changeScrollback"
@fullscreen="toggleFullscreen"
@disconnect="disconnect"
@bt-login="onBtLogin"
/>
<TerminalArea
@@ -121,11 +123,12 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, onActivated, onDeactivated, watch, inject, nextTick, type Ref } from 'vue'
import { ref, computed, onMounted, onBeforeUnmount, onActivated, onDeactivated, watch, inject, nextTick, type Ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import '@xterm/xterm/css/xterm.css'
import { useSnackbar } from '@/composables/useSnackbar'
import { useBtPanelLogin } from '@/composables/btpanel/useBtPanelLogin'
import { useTerminalSessions } from '@/composables/terminal/useTerminalSessions'
import TerminalTabBar from '@/components/terminal/TerminalTabBar.vue'
import TerminalToolbar from '@/components/terminal/TerminalToolbar.vue'
@@ -143,6 +146,7 @@ const router = useRouter()
const auth = useAuthStore()
const snackbar = useSnackbar()
const nexusDrawer = inject<Ref<boolean>>('nexusDrawer', ref(true))
const { loadingId: btLoginLoadingId, openBtLogin } = useBtPanelLogin()
const {
sessions,
@@ -183,6 +187,10 @@ const {
refitAllTerminals,
} = useTerminalSessions(nexusDrawer)
const btLoginLoading = computed(
() => activeSession.value != null && btLoginLoadingId.value === activeSession.value.serverId,
)
const K_SERVER_RAIL = 'nexus_terminal_rail_open'
const serverRailOpen = ref(localStorage.getItem(K_SERVER_RAIL) !== '0')
@@ -203,6 +211,11 @@ function openTerminalQuickAdd() {
quickAddDialogsRef.value?.open()
}
function onBtLogin() {
const serverId = activeSession.value?.serverId
if (serverId != null) void openBtLogin(serverId)
}
async function onQuickAddSuccess(res: AddByIpResponse) {
await loadServers()
const id = res.server?.id