feat: 添加设置入口并统一背景样式
新增设置菜单入口,将其放在侧边栏底部系统信息区下方 统一背景绘制到 html 元素上,避免元素高度差导致的留白 临时下架背景选项 bg-3,调整侧边栏布局与样式 新增中英文的设置翻译文本,优化系统信息区的间距
This commit is contained in:
@@ -11,10 +11,48 @@ body,
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 背景统一绘制在 <html> 上:
|
||||||
|
* 根元素的背景会被绘制到整个画布(canvas)上,铺满窗口的每一个像素,
|
||||||
|
* 不会出现“元素高度(100vh) 与实际窗口差几个像素”导致的上下留白。
|
||||||
|
*/
|
||||||
|
html {
|
||||||
|
background-color: #eef2ff;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(1200px 600px at 10% -10%, rgba(99, 102, 241, 0.28), transparent 60%),
|
||||||
|
radial-gradient(1000px 500px at 110% 10%, rgba(34, 211, 238, 0.28), transparent 55%),
|
||||||
|
linear-gradient(160deg, #eef2ff 0%, #f8fafc 45%, #eef7ff 100%);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark {
|
||||||
|
background-color: #0b1220;
|
||||||
|
background-image:
|
||||||
|
radial-gradient(1200px 600px at 10% -10%, rgba(99, 102, 241, 0.35), transparent 60%),
|
||||||
|
radial-gradient(1000px 500px at 110% 10%, rgba(14, 165, 233, 0.28), transparent 55%),
|
||||||
|
linear-gradient(160deg, #0b1220 0%, #0f172a 45%, #111827 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 选中自定义背景图时替换为图片:等比缩放铺满,不拉伸变形 */
|
||||||
|
html.has-bg-image {
|
||||||
|
background-image: var(--app-bg-image, none);
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: 'Inter', 'HarmonyOS Sans SC', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif;
|
font-family: 'Inter', 'HarmonyOS Sans SC', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* 背景交由 <html> 绘制,body 保持透明避免覆盖画布背景 */
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 窗口圆角固定 8px,与 Windows 11 系统默认圆角保持一致 */
|
||||||
|
#app {
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 悬浮磨砂玻璃面板 */
|
/* 悬浮磨砂玻璃面板 */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import bg1 from "@/assets/images/bg-1.webp"
|
import bg1 from "@/assets/images/bg-1.webp"
|
||||||
import bg2 from "@/assets/images/bg-2.webp"
|
import bg2 from "@/assets/images/bg-2.webp"
|
||||||
import bg3 from "@/assets/images/bg-3.webp"
|
// import bg3 from "@/assets/images/bg-3.webp" // 暂时下架「背景三」,需要时与下方选项一并恢复
|
||||||
|
|
||||||
/** 窗口背景图片标识 */
|
/** 窗口背景图片标识 */
|
||||||
export type BackgroundKey = "none" | "bg-1" | "bg-2" | "bg-3"
|
export type BackgroundKey = "none" | "bg-1" | "bg-2" | "bg-3"
|
||||||
@@ -19,7 +19,7 @@ export const backgroundOptions: BackgroundOption[] = [
|
|||||||
{ key: "none", label: "settings.appearance.none", url: "" },
|
{ key: "none", label: "settings.appearance.none", url: "" },
|
||||||
{ key: "bg-1", label: "settings.appearance.bg1", url: bg1 },
|
{ key: "bg-1", label: "settings.appearance.bg1", url: bg1 },
|
||||||
{ key: "bg-2", label: "settings.appearance.bg2", url: bg2 },
|
{ key: "bg-2", label: "settings.appearance.bg2", url: bg2 },
|
||||||
{ key: "bg-3", label: "settings.appearance.bg3", url: bg3 },
|
// { key: "bg-3", label: "settings.appearance.bg3", url: bg3 },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const DEFAULT_BACKGROUND: BackgroundKey = "none"
|
export const DEFAULT_BACKGROUND: BackgroundKey = "none"
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"servers": "Servers",
|
"servers": "Servers",
|
||||||
"tunnels": "Tunnels",
|
"tunnels": "Tunnels",
|
||||||
"download": "Download frpc"
|
"download": "Download frpc",
|
||||||
|
"setting": "Settings"
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"cpu": "CPU usage",
|
"cpu": "CPU usage",
|
||||||
|
|||||||
@@ -22,7 +22,8 @@
|
|||||||
"dashboard": "仪表盘",
|
"dashboard": "仪表盘",
|
||||||
"servers": "服务器",
|
"servers": "服务器",
|
||||||
"tunnels": "隧道列表",
|
"tunnels": "隧道列表",
|
||||||
"download": "Frpc 下载"
|
"download": "Frpc 下载",
|
||||||
|
"setting": "设置"
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"cpu": "CPU 占用",
|
"cpu": "CPU 占用",
|
||||||
|
|||||||
+2
-37
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- 应用整体:背景(渐变 / 图片) + 悬浮面板 -->
|
<!-- 应用整体:背景由 <html> 统一绘制,此处只负责布局 -->
|
||||||
<div class="app-shell flex h-screen w-screen flex-col overflow-hidden" :style="shellStyle">
|
<div class="app-shell flex h-full w-full flex-col overflow-hidden">
|
||||||
<!-- 顶部自定义 TitleBar -->
|
<!-- 顶部自定义 TitleBar -->
|
||||||
<Header @settings="goToSetting" />
|
<Header @settings="goToSetting" />
|
||||||
|
|
||||||
@@ -18,46 +18,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Header from "./header/Header.vue"
|
import Header from "./header/Header.vue"
|
||||||
import Aside from "./aside/Aside.vue"
|
import Aside from "./aside/Aside.vue"
|
||||||
import { useAppStore } from "@/stores/app"
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const appStore = useAppStore()
|
|
||||||
|
|
||||||
/** 点击标题栏“设置”按钮 → 跳转设置页 */
|
/** 点击标题栏“设置”按钮 → 跳转设置页 */
|
||||||
function goToSetting() {
|
function goToSetting() {
|
||||||
router.push("/setting")
|
router.push("/setting")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 选中背景图片时覆盖默认渐变背景。
|
|
||||||
* `cover` 让图片按原始比例缩放铺满窗口,不会变形拉伸。
|
|
||||||
*/
|
|
||||||
const shellStyle = computed(() =>
|
|
||||||
appStore.backgroundUrl
|
|
||||||
? {
|
|
||||||
backgroundImage: `url('${appStore.backgroundUrl}')`,
|
|
||||||
backgroundSize: "cover",
|
|
||||||
backgroundPosition: "center",
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
}
|
|
||||||
: {},
|
|
||||||
)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.app-shell {
|
|
||||||
background-color: #eef2ff;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(1200px 600px at 10% -10%, rgba(99, 102, 241, 0.28), transparent 60%),
|
|
||||||
radial-gradient(1000px 500px at 110% 10%, rgba(34, 211, 238, 0.28), transparent 55%),
|
|
||||||
linear-gradient(160deg, #eef2ff 0%, #f8fafc 45%, #eef7ff 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark .app-shell {
|
|
||||||
background-color: #0b1220;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(1200px 600px at 10% -10%, rgba(99, 102, 241, 0.35), transparent 60%),
|
|
||||||
radial-gradient(1000px 500px at 110% 10%, rgba(14, 165, 233, 0.28), transparent 55%),
|
|
||||||
linear-gradient(160deg, #0b1220 0%, #0f172a 45%, #111827 100%);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
+51
-37
@@ -3,51 +3,48 @@
|
|||||||
<aside class="glass-panel flex w-56 shrink-0 flex-col overflow-hidden">
|
<aside class="glass-panel flex w-56 shrink-0 flex-col overflow-hidden">
|
||||||
<!-- 菜单区 -->
|
<!-- 菜单区 -->
|
||||||
<div class="flex-1 overflow-y-auto p-2">
|
<div class="flex-1 overflow-y-auto p-2">
|
||||||
<a-menu
|
<a-menu class="aside-menu" mode="inline" :selected-keys="selectedKeys" :items="menuItems" @click="onMenuClick" />
|
||||||
class="aside-menu"
|
|
||||||
mode="inline"
|
|
||||||
:selected-keys="selectedKeys"
|
|
||||||
:items="menuItems"
|
|
||||||
@click="onMenuClick"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部系统信息区(与菜单之间有间隔) -->
|
<!-- 底部系统信息区(与菜单之间有间隔) -->
|
||||||
<div class="system-panel mt-2 border-t p-3">
|
<div class="system-panel mt-2 border-t px-2 pb-2 pt-3">
|
||||||
<!-- CPU -->
|
<div class="px-1">
|
||||||
<div class="mb-2">
|
<!-- CPU -->
|
||||||
<div class="mb-1 flex items-center justify-between text-xs">
|
<div class="mb-2">
|
||||||
<span class="opacity-70">{{ t("system.cpu") }}</span>
|
<div class="mb-1 flex items-center justify-between text-xs">
|
||||||
<span class="font-medium tabular-nums">{{ cpu }}%</span>
|
<span class="opacity-70">{{ t("system.cpu") }}</span>
|
||||||
|
<span class="font-medium tabular-nums">{{ cpu }}%</span>
|
||||||
|
</div>
|
||||||
|
<a-progress :percent="cpu" :show-info="false" :stroke-color="barColor(cpu)" size="small" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内存 -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="mb-1 flex items-center justify-between text-xs">
|
||||||
|
<span class="opacity-70">{{ t("system.memory") }}</span>
|
||||||
|
<span class="font-medium tabular-nums">{{ memory }}%</span>
|
||||||
|
</div>
|
||||||
|
<a-progress :percent="memory" :show-info="false" :stroke-color="barColor(memory)" size="small" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 运行状态 -->
|
||||||
|
<div class="flex items-center gap-2 text-xs">
|
||||||
|
<span class="h-2 w-2 shrink-0 rounded-full" :class="statusDotClass" />
|
||||||
|
<span class="opacity-70">{{ t("system.status") }}</span>
|
||||||
|
<span class="font-medium">{{ statusText }}</span>
|
||||||
</div>
|
</div>
|
||||||
<a-progress
|
|
||||||
:percent="cpu"
|
|
||||||
:show-info="false"
|
|
||||||
:stroke-color="barColor(cpu)"
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内存 -->
|
<!-- 设置入口(位于运行状态下方) -->
|
||||||
<div class="mb-3">
|
<div class="system-divider mt-3 border-t pt-2">
|
||||||
<div class="mb-1 flex items-center justify-between text-xs">
|
<a-menu
|
||||||
<span class="opacity-70">{{ t("system.memory") }}</span>
|
class="aside-menu"
|
||||||
<span class="font-medium tabular-nums">{{ memory }}%</span>
|
mode="inline"
|
||||||
</div>
|
:selected-keys="settingSelectedKeys"
|
||||||
<a-progress
|
:items="settingItems"
|
||||||
:percent="memory"
|
@click="onMenuClick"
|
||||||
:show-info="false"
|
|
||||||
:stroke-color="barColor(memory)"
|
|
||||||
size="small"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 运行状态 -->
|
|
||||||
<div class="flex items-center gap-2 text-xs">
|
|
||||||
<span class="h-2 w-2 shrink-0 rounded-full" :class="statusDotClass" />
|
|
||||||
<span class="opacity-70">{{ t("system.status") }}</span>
|
|
||||||
<span class="font-medium">{{ statusText }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
@@ -59,6 +56,7 @@ import {
|
|||||||
CloudServerOutlined,
|
CloudServerOutlined,
|
||||||
DashboardOutlined,
|
DashboardOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
|
SettingOutlined,
|
||||||
} from "@ant-design/icons-vue"
|
} from "@ant-design/icons-vue"
|
||||||
import { useI18n } from "vue-i18n"
|
import { useI18n } from "vue-i18n"
|
||||||
import type { MenuProps } from "ant-design-vue"
|
import type { MenuProps } from "ant-design-vue"
|
||||||
@@ -74,6 +72,9 @@ const { cpu, memory } = useSystemStats()
|
|||||||
|
|
||||||
const selectedKeys = computed(() => [route.path])
|
const selectedKeys = computed(() => [route.path])
|
||||||
|
|
||||||
|
/** 设置项在运行状态下方独立渲染,子路由(如 /setting/appearance)同样高亮 */
|
||||||
|
const settingSelectedKeys = computed(() => (route.path.startsWith("/setting") ? ["/setting"] : []))
|
||||||
|
|
||||||
const menuItems = computed<MenuProps["items"]>(() => [
|
const menuItems = computed<MenuProps["items"]>(() => [
|
||||||
{ key: "/dashboard", icon: () => h(DashboardOutlined), label: t("menu.dashboard") },
|
{ key: "/dashboard", icon: () => h(DashboardOutlined), label: t("menu.dashboard") },
|
||||||
{ key: "/servers", icon: () => h(CloudServerOutlined), label: t("menu.servers") },
|
{ key: "/servers", icon: () => h(CloudServerOutlined), label: t("menu.servers") },
|
||||||
@@ -81,6 +82,10 @@ const menuItems = computed<MenuProps["items"]>(() => [
|
|||||||
{ key: "/download", icon: () => h(DownloadOutlined), label: t("menu.download") },
|
{ key: "/download", icon: () => h(DownloadOutlined), label: t("menu.download") },
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const settingItems = computed<MenuProps["items"]>(() => [
|
||||||
|
{ key: "/setting", icon: () => h(SettingOutlined), label: t("menu.setting") },
|
||||||
|
])
|
||||||
|
|
||||||
const onMenuClick: MenuProps["onClick"] = ({ key }) => {
|
const onMenuClick: MenuProps["onClick"] = ({ key }) => {
|
||||||
router.push(String(key))
|
router.push(String(key))
|
||||||
}
|
}
|
||||||
@@ -124,6 +129,15 @@ const statusText = computed(() => {
|
|||||||
border-color: rgba(255, 255, 255, 0.08);
|
border-color: rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 系统信息与设置入口之间的分隔线 */
|
||||||
|
.system-divider {
|
||||||
|
border-color: rgba(15, 23, 42, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .system-divider {
|
||||||
|
border-color: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
/* 让 Antd 菜单透明化,融入磨砂玻璃面板 */
|
/* 让 Antd 菜单透明化,融入磨砂玻璃面板 */
|
||||||
.aside-menu {
|
.aside-menu {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
@@ -32,6 +32,19 @@ export const useAppStore = defineStore("app", () => {
|
|||||||
const background = useStorage<BackgroundKey>("frpilot:background", DEFAULT_BACKGROUND)
|
const background = useStorage<BackgroundKey>("frpilot:background", DEFAULT_BACKGROUND)
|
||||||
const backgroundUrl = computed(() => getBackgroundUrl(background.value))
|
const backgroundUrl = computed(() => getBackgroundUrl(background.value))
|
||||||
|
|
||||||
|
// 把背景图同步到 <html>(整窗背景,避免元素高度误差造成的上下留白)
|
||||||
|
watchEffect(() => {
|
||||||
|
const root = document.documentElement
|
||||||
|
const url = backgroundUrl.value
|
||||||
|
if (url) {
|
||||||
|
root.style.setProperty("--app-bg-image", `url('${url}')`)
|
||||||
|
root.classList.add("has-bg-image")
|
||||||
|
} else {
|
||||||
|
root.classList.remove("has-bg-image")
|
||||||
|
root.style.removeProperty("--app-bg-image")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Frpc 运行状态(后续由后端驱动)
|
// Frpc 运行状态(后续由后端驱动)
|
||||||
const runStatus = ref<RunStatus>("stopped")
|
const runStatus = ref<RunStatus>("stopped")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user