From 70796e53793af992b0e2f061331ff6eb6581b20f Mon Sep 17 00:00:00 2001 From: mrlee <470349233@qq.com> Date: Wed, 23 Sep 2026 15:44:46 +0800 Subject: [PATCH] =?UTF-8?q?style(web):=20FRP=20=E7=A9=BF=E9=80=8F=E9=A1=B5?= =?UTF-8?q?=E6=95=B4=E4=BD=93=E6=A0=B7=E5=BC=8F=E5=AF=B9=E9=BD=90=E6=A0=87?= =?UTF-8?q?=E5=87=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 状态卡改为标准 .stats-row/.stat-card(4 张:客户端状态/启用规则/frpc 版本/最近应用) - 修 运行中 状态颜色:statusKind 返回合法 StatusKind 且改用 :kind(原先当 value 传导致被判为 unknown 灰色),详情弹窗同 bug 一并修 - 公网服务端列表改为 n-data-table(8 列,操作列含启动/停止/查看/日志/测速/校验/Agent 管理/编辑/删除) - 4 个弹窗统一 .frp-card-modal 外壳(运行日志/导出配置/frps 同源配置/FRP 应用配置),服务端详情用 .frp-detail-card 对齐排版 - 单元格样式补 :deep()(render 创建的节点不带 scoped 属性),修好 table-muted/type-chip/mono 此前失效的问题 - 硬编码色值 13 处换令牌、内联样式清零、删死样式 15 条 --- web/src/views/FrpView.vue | 294 ++++++++++++++++++++++---------------- 1 file changed, 171 insertions(+), 123 deletions(-) diff --git a/web/src/views/FrpView.vue b/web/src/views/FrpView.vue index f255d10..bd024f6 100644 --- a/web/src/views/FrpView.vue +++ b/web/src/views/FrpView.vue @@ -23,37 +23,31 @@ @@ -887,6 +891,7 @@ import { AddOutline, CloudOutline, CloseOutline, DocumentOutline, DocumentTextOutline, GitNetworkOutline, InformationCircleOutline, FlashOutline, LayersOutline, LocationOutline, LockClosedOutline, PeopleOutline, PlayOutline, RefreshOutline, ServerOutline, SettingsOutline, StopOutline, SwapHorizontalOutline, TerminalOutline, TimeOutline, } from '@vicons/ionicons5' +import { RouterLink } from 'vue-router' import { api, asList } from '../api/client' import { formatBytes, formatDate, formatRate } from '../utils/format' @@ -1076,7 +1081,9 @@ const transportOptions = [ ] const proxyServerOptions = computed(() => servers.value.map((server) => ({ label: `${server.name} (${server.server_addr}:${server.server_port})`, value: server.id }))) const statusLabel = computed(() => ({ running: '运行中', stopped: '已停止', unknown: '未配置' })[status.value.status] ?? '异常') -const statusKind = computed(() => status.value.status === 'running' ? 'success' : status.value.status === 'stopped' ? 'default' : 'warning') +// 返回的必须是 StatusKind 合法值(success/warning/error/disabled/unknown), +// 之前用 'default' 且当 value 传给 StatusBadge,导致“运行中”被判为 unknown(灰色) +const statusKind = computed(() => (status.value.status === 'running' ? 'success' : status.value.status === 'stopped' ? 'disabled' : 'warning') as 'success' | 'disabled' | 'warning') const downloadInProgress = computed(() => ['queued', 'downloading', 'verifying', 'extracting'].includes(downloadTask.value.status)) const downloadPercent = computed(() => downloadTask.value.total > 0 ? Math.min(100, Math.round(downloadTask.value.downloaded / downloadTask.value.total * 100)) : 0) const downloadProgressStatus = computed(() => downloadTask.value.status === 'completed' ? 'success' : downloadTask.value.status === 'failed' ? 'error' : 'default') @@ -1093,6 +1100,32 @@ const frpTypeTagType: Record>(() => [ + { title: '名称', key: 'name', width: 190, render: (row) => h('div', { class: 'cell-name' }, [h(NIcon, { component: CloudOutline }), h('span', row.name)]) }, + { title: '主机', key: 'host', width: 170, render: (row) => h('span', { class: 'mono cell-wrap' }, row.server_addr) }, + { title: '端口', key: 'port', width: 90, render: (row) => h(NTag, { size: 'small', bordered: false }, { default: () => String(row.server_port) }) }, + { title: '延迟', key: 'latency', width: 130, render: (row) => h('div', { class: 'cell-inline', title: latencyDetail(row) }, [h(NIcon, { component: FlashOutline }), h('span', latencyText(row))]) }, + { title: '位置', key: 'location', width: 150, render: (row) => h('div', { class: 'cell-inline' }, [h(NIcon, { component: LocationOutline }), h('span', locationText(row))]) }, + { title: '配置', key: 'config', width: 230, render: (row) => h('div', { class: 'cell-inline' }, [ + h(NTag, { size: 'small', bordered: false, type: row.tls_enabled ? 'success' : 'warning' }, { default: () => `TLS ${row.tls_enabled ? '已启用' : '未启用'}` }), + h(NTag, { size: 'small', bordered: false, type: row.has_token ? 'success' : 'error' }, { default: () => `Token ${row.has_token ? '已配置' : '缺失'}` }), + ]) }, + { title: '启动状态', key: 'startup', width: 170, render: (row) => h(NTag, { size: 'small', bordered: false, type: serverStartupType(row), title: serverStateTooltip(row) }, { default: () => `${serverStartupText(row)} · ${connectionStateText(row)}` }) }, + { title: '操作', key: 'actions', width: 560, align: 'right', render: (row) => h('div', { class: 'cell-actions' }, [ + row.boot_status === 'running' + ? h(NButton, { size: 'small', type: 'error', ghost: true, loading: serverStartingId.value === row.id, onClick: () => stopServer(row) }, { default: () => '停止' }) + : h(NButton, { size: 'small', type: 'success', ghost: true, loading: serverStartingId.value === row.id, onClick: () => startServer(row) }, { default: () => '启动' }), + h(NButton, { size: 'small', quaternary: true, onClick: () => openServerDetail(row) }, { default: () => '查看' }), + h(NButton, { size: 'small', quaternary: true, onClick: () => openServerLogs(row) }, { default: () => '日志' }), + h(NButton, { size: 'small', quaternary: true, loading: measuringId.value === row.id, onClick: () => measureServer(row) }, { default: () => '测速' }), + h(NButton, { size: 'small', quaternary: true, loading: testingId.value === row.id, onClick: () => testServer(row) }, { default: () => '校验' }), + row.agent_configured ? h(RouterLink, { to: '/frp-agent' }, { default: () => h(NButton, { size: 'small', quaternary: true }, { default: () => 'Agent 管理' }) }) : null, + h(NButton, { size: 'small', quaternary: true, onClick: () => openServerModal(row) }, { default: () => '编辑' }), + h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => confirmDeleteServer(row) }, { default: () => '删除' }), + ]) }, +]) + const columns = computed>(() => [ { title: '名称', key: 'name', minWidth: 150, render: (row) => h('div', [h('strong', row.name), row.remark ? h('div', { class: 'table-muted' }, row.remark) : null]) }, { title: '服务端', key: 'server_id', minWidth: 130, render: (row) => servers.value.find((server) => server.id === row.server_id)?.name ?? `#${row.server_id}` }, @@ -1789,33 +1822,31 @@ onUnmounted(() => { if (timer) window.clearInterval(timer); stopDownloadPolling(