diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index c97d0c4acf..be42e140fc 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -13,6 +13,7 @@ import { Button } from '@/components/ui/Button'; import { PageTransition } from '@/components/common/PageTransition'; import { MainRoutes } from '@/router/MainRoutes'; import { + IconExternalLink, IconSidebarAuthFiles, IconSidebarConfig, IconSidebarDashboard, @@ -22,6 +23,7 @@ import { IconSidebarProviders, IconSidebarQuota, IconSidebarSystem, + IconX, } from '@/components/ui/icons'; import { INLINE_LOGO_JPEG } from '@/assets/logoInline'; import { @@ -50,6 +52,17 @@ const sidebarIcons: Record = { system: , }; +const CPA_MANAGER_PLUS_BASE_URL = 'https://seakee.github.io/CPA-Manager-Plus'; +const CPA_MANAGER_PLUS_SITE_URL = `${CPA_MANAGER_PLUS_BASE_URL}/`; + +const getCpampDocsBaseUrl = (language: string) => + language === 'zh-CN' || language === 'zh-TW' + ? `${CPA_MANAGER_PLUS_BASE_URL}/docs` + : `${CPA_MANAGER_PLUS_BASE_URL}/docs/en`; + +const getCpampDocUrl = (language: string, path: string) => + `${getCpampDocsBaseUrl(language)}/${path}.html`; + // Header action icons - smaller size for header buttons const headerIconProps: SVGProps = { width: 16, @@ -232,6 +245,10 @@ export function MainLayout() { 'mainLayout.sidebarCollapsed', false ); + const [migrationPromptDismissed, setMigrationPromptDismissed] = useLocalStorage( + 'mainLayout.cpaManagerPlusMigrationPromptDismissed', + false + ); const [languageMenuOpen, setLanguageMenuOpen] = useState(false); const [themeMenuOpen, setThemeMenuOpen] = useState(false); const contentRef = useRef(null); @@ -243,6 +260,24 @@ export function MainLayout() { const abbrBrandName = t('title.abbr'); const isLogsPage = location.pathname.startsWith('/logs'); const showSidebarLabels = !sidebarCollapsed || sidebarOpen; + const migrationGuideUrl = getCpampDocUrl(language, 'migration/from-cpa-manager'); + const migrationPromptLinks = [ + { + key: 'demo', + href: CPA_MANAGER_PLUS_SITE_URL, + label: t('migration_prompt.links.demo'), + }, + { + key: 'quick_start', + href: getCpampDocUrl(language, 'guide/getting-started'), + label: t('migration_prompt.links.quick_start'), + }, + { + key: 'backup', + href: getCpampDocUrl(language, 'operations/backup'), + label: t('migration_prompt.links.backup'), + }, + ]; // 将顶部悬浮控制区高度写入 CSS 变量,供移动端粘性元素和浮层避让。 useLayoutEffect(() => { @@ -679,6 +714,51 @@ export function MainLayout() {
+ {!migrationPromptDismissed && ( +
+
+
{t('migration_prompt.title')}
+
{t('migration_prompt.body')}
+
+ {migrationPromptLinks.map((link) => ( + + {link.label} + + + ))} +
+
+
+ + {t('migration_prompt.action')} + + + +
+
+ )} } getRouteOrder={getRouteOrder} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 5f6d4693f0..39b7f22914 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -70,6 +70,19 @@ "title": "CLI Proxy API", "subtitle": "Management Center" }, + "migration_prompt": { + "label": "CPA-Manager-Plus migration notice", + "title": "CPA-Manager-Plus is now available", + "body": "If you are considering migrating, review the demo, deployment path, and backup notes first. This panel can still be used as usual.", + "action": "View migration guide", + "links_label": "CPA-Manager-Plus evaluation links", + "links": { + "demo": "Live demo", + "quick_start": "Getting started", + "backup": "Backup notes" + }, + "dismiss": "Don't show again" + }, "auto_login": { "title": "Auto Login in Progress...", "message": "Attempting to connect to server using locally saved connection information" diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 30d7000532..7b35a9c7b9 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -69,6 +69,19 @@ "title": "CLI Proxy API", "subtitle": "Центр управления" }, + "migration_prompt": { + "label": "Уведомление о миграции на CPA-Manager-Plus", + "title": "CPA-Manager-Plus уже доступен", + "body": "Если вы рассматриваете переход, сначала посмотрите демо, варианты развёртывания и заметки о резервном копировании. Этой панелью по-прежнему можно пользоваться как обычно.", + "action": "Открыть руководство", + "links_label": "Ссылки для оценки CPA-Manager-Plus", + "links": { + "demo": "Демо", + "quick_start": "Быстрый старт", + "backup": "Резервное копирование" + }, + "dismiss": "Больше не показывать" + }, "auto_login": { "title": "Автовход...", "message": "Пытаемся подключиться к серверу, используя сохранённые данные" diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 82000ab304..8daed138ac 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -70,6 +70,19 @@ "title": "CLI Proxy API", "subtitle": "管理中心" }, + "migration_prompt": { + "label": "CPA-Manager-Plus 迁移提示", + "title": "CPA-Manager-Plus 已正式发布", + "body": "如果你准备升级,可以先看演示、部署方式和备份要点;当前面板仍可继续使用。", + "action": "查看迁移指南", + "links_label": "CPA-Manager-Plus 评估链接", + "links": { + "demo": "在线演示", + "quick_start": "快速开始", + "backup": "备份要点" + }, + "dismiss": "不再提示" + }, "auto_login": { "title": "正在自动登录...", "message": "正在使用本地保存的连接信息尝试连接服务器" diff --git a/src/i18n/locales/zh-TW.json b/src/i18n/locales/zh-TW.json index 95aa327c68..b6ff952777 100644 --- a/src/i18n/locales/zh-TW.json +++ b/src/i18n/locales/zh-TW.json @@ -69,6 +69,19 @@ "title": "CLI Proxy API", "subtitle": "管理中心" }, + "migration_prompt": { + "label": "CPA-Manager-Plus 遷移提示", + "title": "CPA-Manager-Plus 已正式發佈", + "body": "如果你準備升級,可以先看演示、部署方式和備份要點;目前面板仍可繼續使用。", + "action": "查看遷移指南", + "links_label": "CPA-Manager-Plus 評估連結", + "links": { + "demo": "線上演示", + "quick_start": "快速開始", + "backup": "備份要點" + }, + "dismiss": "不再提示" + }, "auto_login": { "title": "正在自動登入...", "message": "正在使用本地儲存的連線資訊嘗試連線伺服器" diff --git a/src/styles/layout.scss b/src/styles/layout.scss index 7685d6127a..c2458a679f 100644 --- a/src/styles/layout.scss +++ b/src/styles/layout.scss @@ -575,6 +575,153 @@ } } +.migration-prompt { + display: flex; + align-items: center; + justify-content: space-between; + gap: $spacing-md; + width: 100%; + padding: 12px 14px 12px 16px; + border: 1px solid color-mix(in srgb, var(--primary-color) 18%, var(--border-color)); + border-radius: $radius-md; + background: + linear-gradient(180deg, rgb(255 255 255 / 0.34), rgb(255 255 255 / 0)), + color-mix(in srgb, var(--bg-primary) 82%, var(--color-primary-light-9) 18%); + color: var(--text-primary); + box-shadow: 0 8px 18px -18px color-mix(in srgb, var(--primary-color) 65%, transparent); +} + +.migration-prompt__content { + min-width: 0; +} + +.migration-prompt__title { + font-size: 14px; + font-weight: 700; + line-height: 1.35; + color: var(--text-primary); +} + +.migration-prompt__body { + margin-top: 2px; + font-size: 13px; + line-height: 1.45; + color: var(--text-secondary); +} + +.migration-prompt__links { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 9px; +} + +.migration-prompt__secondary-link { + display: inline-flex; + align-items: center; + gap: 5px; + min-height: 28px; + padding: 5px 8px; + border: 1px solid color-mix(in srgb, var(--border-color) 76%, transparent); + border-radius: $radius-sm; + background: color-mix(in srgb, var(--bg-primary) 64%, transparent); + color: var(--text-secondary); + font-size: 12px; + font-weight: 650; + line-height: 1.2; + text-decoration: none; + transition: + background-color $transition-fast, + border-color $transition-fast, + color $transition-fast; + + &:hover { + background: color-mix(in srgb, var(--primary-color) 8%, var(--bg-primary)); + border-color: color-mix(in srgb, var(--primary-color) 28%, var(--border-color)); + color: var(--primary-active); + } + + &:focus-visible { + outline: none; + box-shadow: 0 0 0 3px var(--primary-ring); + } + + svg { + flex-shrink: 0; + } +} + +.migration-prompt__actions { + display: inline-flex; + align-items: center; + gap: $spacing-xs; + flex-shrink: 0; +} + +.migration-prompt__link { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-height: 32px; + padding: 7px 10px; + border: 1px solid color-mix(in srgb, var(--primary-color) 22%, var(--border-color)); + border-radius: $radius-md; + background: color-mix(in srgb, var(--bg-primary) 76%, var(--color-primary-light-9) 24%); + color: var(--primary-active); + font-size: 13px; + font-weight: 700; + line-height: 1.2; + text-decoration: none; + white-space: nowrap; + transition: + background-color $transition-fast, + border-color $transition-fast, + color $transition-fast; + + &:hover { + background: var(--color-primary-light-9); + border-color: var(--color-primary-light-5); + color: var(--primary-color); + } + + &:focus-visible { + outline: none; + box-shadow: 0 0 0 3px var(--primary-ring); + } + + svg { + flex-shrink: 0; + } +} + +.migration-prompt__dismiss { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + min-width: 32px; + padding: 0; + border: 1px solid transparent; + border-radius: $radius-md; + background: transparent; + color: var(--text-tertiary); + transition: + background-color $transition-fast, + color $transition-fast; + + &:hover { + background: color-mix(in srgb, var(--text-primary) 8%, transparent); + color: var(--text-primary); + } + + &:focus-visible { + outline: none; + box-shadow: 0 0 0 3px var(--primary-ring); + } +} + .grid { display: grid; gap: $spacing-lg; @@ -730,4 +877,27 @@ overflow: visible; } } + + .migration-prompt { + align-items: flex-start; + gap: $spacing-sm; + padding: 12px; + } + + .migration-prompt__actions { + align-self: stretch; + flex-direction: column-reverse; + justify-content: space-between; + } + + .migration-prompt__link { + min-height: 34px; + padding: 8px; + white-space: normal; + text-align: center; + } + + .migration-prompt__secondary-link { + max-width: 100%; + } }