feat: implement custom WebUI support with dynamic frontend replacement

- Add WebUI service and controller for managing custom frontend packages

- Allow overriding default static assets when custom WebUI is activated

- Add Make commands for packing custom WebUI distributions

- Update UI settings to support uploading, activating and deleting WebUIs

- Document WebUI feature in README and changelog
This commit is contained in:
duorameng
2026-05-29 11:49:01 +08:00
parent 41af3cf9e0
commit 323a58e041
19 changed files with 1004 additions and 35 deletions
+49 -2
View File
@@ -2,13 +2,17 @@
import { ref } from 'vue'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Button } from '@/components/ui/button'
import { UploadCloud, ExternalLink } from 'lucide-vue-next'
import PasswordSettings from './PasswordSettings.vue'
import SiteSettings from './SiteSettings.vue'
import SchedulerSettings from './SchedulerSettings.vue'
import BackupSettings from './BackupSettings.vue'
import AboutSettings from './AboutSettings.vue'
import WebUISettings from './WebUISettings.vue'
const activeTab = ref('password')
const webuiRef = ref<any>(null)
</script>
<template>
@@ -19,9 +23,10 @@ const activeTab = ref('password')
</div>
<Tabs v-model="activeTab" class="max-w-2xl">
<TabsList class="w-full grid grid-cols-5 h-auto p-1 bg-muted/50 rounded-lg">
<TabsList class="w-full grid grid-cols-3 sm:grid-cols-6 gap-y-1 sm:gap-y-0 h-auto p-1 bg-muted/50 rounded-lg">
<TabsTrigger value="password" class="text-xs sm:text-sm px-1 sm:px-3 py-1.5 whitespace-nowrap">密码修改</TabsTrigger>
<TabsTrigger value="site" class="text-xs sm:text-sm px-1 sm:px-3 py-1.5 whitespace-nowrap">站点设置</TabsTrigger>
<TabsTrigger value="webui" class="text-xs sm:text-sm px-1 sm:px-3 py-1.5 whitespace-nowrap">前端定制</TabsTrigger>
<TabsTrigger value="scheduler" class="text-xs sm:text-sm px-1 sm:px-3 py-1.5 whitespace-nowrap">调度设置</TabsTrigger>
<TabsTrigger value="backup" class="text-xs sm:text-sm px-1 sm:px-3 py-1.5 whitespace-nowrap">备份恢复</TabsTrigger>
<TabsTrigger value="about" class="text-xs sm:text-sm px-1 sm:px-3 py-1.5 whitespace-nowrap">关于</TabsTrigger>
@@ -43,7 +48,7 @@ const activeTab = ref('password')
<Card>
<CardHeader>
<CardTitle>站点设置</CardTitle>
<CardDescription>配置站点标题图标和系统参数</CardDescription>
<CardDescription>配置站点标题图标和系统常规参数</CardDescription>
</CardHeader>
<CardContent>
<SiteSettings />
@@ -51,6 +56,48 @@ const activeTab = ref('password')
</Card>
</TabsContent>
<TabsContent value="webui" class="mt-6">
<Card>
<CardHeader class="flex flex-row items-start justify-between space-y-0 gap-4">
<div class="space-y-1.5 flex-1 min-w-0">
<CardTitle class="flex flex-wrap items-center gap-x-3 gap-y-1.5">
<span>自定义前端包 (WebUI)</span>
<a href="https://engigu.github.io/baihu-panel/guide/webui" target="_blank" class="flex items-center gap-1 text-xs text-blue-600 hover:underline font-normal shrink-0">
开发文档
<ExternalLink class="w-3 h-3 shrink-0" />
</a>
</CardTitle>
<CardDescription class="line-clamp-2 sm:line-clamp-none">完全接管和替换默认的系统面板界面实现深度定制</CardDescription>
</div>
<div class="shrink-0 mt-0 sm:mt-1">
<Button
@click="webuiRef?.triggerUpload"
:disabled="webuiRef?.uploading"
variant="outline"
size="sm"
class="h-8 px-2 sm:px-3"
title="上传前端资源包"
>
<span v-if="webuiRef?.uploading" class="flex items-center justify-center">
<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="hidden sm:inline ml-2">上传中...</span>
</span>
<span v-else class="flex items-center">
<UploadCloud class="w-4 h-4" />
<span class="hidden sm:inline ml-2">上传资源包</span>
</span>
</Button>
</div>
</CardHeader>
<CardContent>
<WebUISettings ref="webuiRef" />
</CardContent>
</Card>
</TabsContent>
<TabsContent value="scheduler" class="mt-6">
<Card>
<CardHeader>