diff --git a/apps/taro/src/app.tsx b/apps/taro/src/app.tsx
index 70483456..129af959 100644
--- a/apps/taro/src/app.tsx
+++ b/apps/taro/src/app.tsx
@@ -1,6 +1,7 @@
import { PropsWithChildren, useEffect, useState } from 'react';
import { Text, View } from '@tarojs/components';
import 'katex/dist/katex.min.css';
+import { AdminLegacyShell } from '@/components/AdminLegacyShell';
import { StudentLegacyShell } from '@/components/StudentLegacyShell';
import { appEnv, isH5Runtime } from '@/env';
import { currentPagePath, guardCurrentRoute, shouldGuardPath } from '@/services/routeGuard';
@@ -53,6 +54,12 @@ function shouldUseStudentShell(path: string) {
].includes(path);
}
+function shouldUseAdminShell(path: string) {
+ if (appEnv.portal === 'tenant-admin') return path.startsWith('/pages/tenant-admin/');
+ if (appEnv.portal === 'platform-admin') return path.startsWith('/pages/platform-admin/');
+ return false;
+}
+
export default function App({ children }: PropsWithChildren) {
const [routeReady, setRouteReady] = useState(() => !shouldGuardPath(currentPagePath()));
const [path, setPath] = useState(() => currentPagePath());
@@ -118,5 +125,9 @@ export default function App({ children }: PropsWithChildren) {
return {children};
}
+ if (shouldUseAdminShell(path)) {
+ return {children};
+ }
+
return children;
}
diff --git a/apps/taro/src/components/AdminLegacyShell.css b/apps/taro/src/components/AdminLegacyShell.css
new file mode 100644
index 00000000..cd38efe4
--- /dev/null
+++ b/apps/taro/src/components/AdminLegacyShell.css
@@ -0,0 +1,421 @@
+.backoffice-legacy-layout {
+ min-height: 100vh;
+ background:
+ linear-gradient(to right, rgba(15, 23, 42, 0.035) 1px, transparent 1px),
+ linear-gradient(to bottom, rgba(15, 23, 42, 0.035) 1px, transparent 1px),
+ #f4f6f9;
+ color: #111827;
+}
+
+.backoffice-legacy-main {
+ min-width: 0;
+}
+
+.backoffice-legacy-sidebar {
+ display: none;
+}
+
+.backoffice-mobile-nav {
+ position: sticky;
+ top: 0;
+ z-index: 28;
+ display: flex;
+ gap: 10px;
+ overflow-x: auto;
+ padding: 14px 16px;
+ border-bottom: 1px solid #e5e7eb;
+ background: rgba(255, 255, 255, 0.96);
+ box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
+}
+
+.backoffice-mobile-item {
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ flex: 0 0 auto;
+ min-height: 44px;
+ padding: 0 12px;
+ border-radius: 14px;
+ background: #f8fafc;
+ color: #64748b;
+}
+
+.backoffice-mobile-item.active {
+ background: #1e293b;
+ color: #fff;
+ box-shadow: 0 6px 15px rgba(15, 23, 42, 0.18);
+}
+
+.backoffice-mobile-mark {
+ font-size: 18px;
+ font-weight: 900;
+}
+
+.backoffice-mobile-text {
+ font-size: 20px;
+ font-weight: 780;
+ white-space: nowrap;
+}
+
+.backoffice-legacy-main .admin-page,
+.backoffice-legacy-main .platform-page {
+ min-height: auto;
+ padding: 18px;
+ background: transparent;
+}
+
+.backoffice-legacy-main .admin-shell,
+.backoffice-legacy-main .platform-shell {
+ gap: 18px;
+}
+
+.backoffice-legacy-main .admin-header,
+.backoffice-legacy-main .platform-header {
+ border: 1px solid #f1f5f9;
+ border-radius: 18px;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
+}
+
+.backoffice-legacy-main .admin-header::after,
+.backoffice-legacy-main .platform-header::after {
+ display: none;
+}
+
+.backoffice-legacy-main .admin-kicker,
+.backoffice-legacy-main .platform-kicker {
+ color: #2563eb;
+ font-size: 18px;
+ font-weight: 800;
+}
+
+.backoffice-legacy-main .admin-title,
+.backoffice-legacy-main .platform-title {
+ color: #111827;
+ font-size: 33px;
+ font-weight: 900;
+}
+
+.backoffice-legacy-main .admin-subtitle,
+.backoffice-legacy-main .platform-subtitle {
+ color: #64748b;
+ font-size: 21px;
+}
+
+.backoffice-legacy-main .admin-section,
+.backoffice-legacy-main .platform-section,
+.backoffice-legacy-main .admin-metric,
+.backoffice-legacy-main .platform-metric,
+.backoffice-legacy-main .admin-row,
+.backoffice-legacy-main .platform-row {
+ border-color: #f1f5f9;
+ border-radius: 16px;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
+}
+
+.backoffice-legacy-main .admin-section,
+.backoffice-legacy-main .platform-section {
+ padding: 20px;
+}
+
+.backoffice-legacy-main .admin-section-title,
+.backoffice-legacy-main .platform-section-title {
+ margin-bottom: 14px;
+ color: #111827;
+ font-size: 25px;
+ font-weight: 900;
+}
+
+.backoffice-legacy-main .admin-module-card,
+.backoffice-legacy-main .platform-module-card {
+ min-height: 132px;
+ border: 1px solid #f1f5f9;
+ border-radius: 16px;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
+}
+
+.backoffice-legacy-main .admin-module-card::after,
+.backoffice-legacy-main .platform-module-card::after {
+ display: none;
+}
+
+.backoffice-legacy-main .admin-module-label,
+.backoffice-legacy-main .platform-module-label {
+ color: #2563eb;
+ font-size: 17px;
+}
+
+.backoffice-legacy-main .admin-module-card .admin-row-main,
+.backoffice-legacy-main .platform-module-card .platform-row-main {
+ margin-top: 22px;
+ color: #111827;
+ font-size: 27px;
+}
+
+.backoffice-legacy-main .admin-module-card .admin-row-meta,
+.backoffice-legacy-main .platform-module-card .platform-row-meta {
+ color: #64748b;
+}
+
+.backoffice-legacy-main .admin-button,
+.backoffice-legacy-main .platform-button,
+.backoffice-legacy-main .admin-mini-button,
+.backoffice-legacy-main .platform-mini-button {
+ border-radius: 12px;
+}
+
+.backoffice-legacy-main .admin-button.primary,
+.backoffice-legacy-main .platform-button.primary,
+.backoffice-legacy-main .admin-mini-button.primary {
+ border-color: #1e293b;
+ background: #1e293b;
+ box-shadow: 0 6px 14px rgba(15, 23, 42, 0.14);
+}
+
+.backoffice-legacy-main .admin-button.active,
+.backoffice-legacy-main .platform-button.active,
+.backoffice-legacy-main .platform-mini-button.active {
+ border-color: #1e293b;
+ background: #1e293b;
+ color: #fff;
+}
+
+@media (min-width: 980px) {
+ .backoffice-legacy-layout {
+ display: grid;
+ grid-template-columns: 256px minmax(0, 1fr);
+ gap: 16px;
+ padding: 16px;
+ }
+
+ .backoffice-mobile-nav {
+ display: none;
+ }
+
+ .backoffice-legacy-sidebar {
+ position: sticky;
+ top: 16px;
+ display: flex;
+ height: calc(100vh - 32px);
+ flex-direction: column;
+ padding: 16px;
+ border: 1px solid #f1f5f9;
+ border-radius: 24px;
+ background: #fff;
+ box-shadow: 0 8px 30px rgba(15, 23, 42, 0.04);
+ }
+
+ .backoffice-brand {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 14px 10px 18px;
+ }
+
+ .backoffice-brand-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 42px;
+ width: 42px;
+ height: 42px;
+ border-radius: 12px;
+ background: #eff6ff;
+ color: #1d4ed8;
+ }
+
+ .backoffice-brand-mark {
+ font-size: 22px;
+ font-weight: 950;
+ }
+
+ .backoffice-brand-copy {
+ min-width: 0;
+ }
+
+ .backoffice-brand-title {
+ display: block;
+ color: #1f2937;
+ font-size: 20px;
+ font-weight: 900;
+ line-height: 1.25;
+ }
+
+ .backoffice-brand-sub {
+ display: block;
+ margin-top: 3px;
+ color: #64748b;
+ font-size: 13px;
+ font-weight: 720;
+ line-height: 1.25;
+ }
+
+ .backoffice-tenant-select {
+ padding: 0 10px 16px;
+ }
+
+ .backoffice-field-label {
+ display: block;
+ margin-bottom: 6px;
+ color: #9ca3af;
+ font-size: 12px;
+ font-weight: 900;
+ }
+
+ .backoffice-select-box {
+ display: flex;
+ align-items: center;
+ min-height: 42px;
+ padding: 0 12px;
+ border: 1px solid #bfdbfe;
+ border-radius: 13px;
+ background: #eff6ff;
+ }
+
+ .backoffice-select-text {
+ color: #1d4ed8;
+ font-size: 16px;
+ font-weight: 900;
+ }
+
+ .backoffice-nav {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ gap: 6px;
+ overflow-y: auto;
+ padding-right: 4px;
+ }
+
+ .backoffice-nav-block {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ }
+
+ .backoffice-nav-group {
+ display: block;
+ margin: 10px 0 3px;
+ padding: 0 12px;
+ color: #9ca3af;
+ font-size: 12px;
+ font-weight: 900;
+ }
+
+ .backoffice-nav-btn {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ min-height: 48px;
+ padding: 0 14px;
+ border-radius: 12px;
+ color: #475569;
+ }
+
+ .backoffice-nav-btn.active {
+ background: #1e293b;
+ color: #fff;
+ box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15);
+ }
+
+ .backoffice-nav-mark {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 8px;
+ background: #f3f4f6;
+ color: inherit;
+ font-size: 14px;
+ font-weight: 900;
+ }
+
+ .backoffice-nav-btn.active .backoffice-nav-mark {
+ background: rgba(255, 255, 255, 0.14);
+ }
+
+ .backoffice-nav-text {
+ font-size: 16px;
+ font-weight: 860;
+ }
+
+ .backoffice-footer {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-top: 12px;
+ padding: 14px 10px 4px;
+ border-top: 1px solid #f1f5f9;
+ }
+
+ .backoffice-footer-avatar {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 36px;
+ width: 36px;
+ height: 36px;
+ border-radius: 999px;
+ background: #f1f5f9;
+ color: #475569;
+ }
+
+ .backoffice-footer-mark {
+ font-size: 16px;
+ font-weight: 900;
+ }
+
+ .backoffice-footer-copy {
+ min-width: 0;
+ }
+
+ .backoffice-footer-name {
+ display: block;
+ color: #334155;
+ font-size: 15px;
+ font-weight: 850;
+ }
+
+ .backoffice-footer-meta {
+ display: block;
+ margin-top: 2px;
+ color: #94a3b8;
+ font-size: 12px;
+ font-weight: 720;
+ }
+
+ .backoffice-legacy-main {
+ overflow: hidden;
+ border: 1px solid #f1f5f9;
+ border-radius: 24px;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
+ }
+
+ .backoffice-legacy-main .admin-page,
+ .backoffice-legacy-main .platform-page {
+ max-width: none;
+ margin: 0;
+ padding: 24px;
+ }
+
+ .backoffice-legacy-main .admin-grid,
+ .backoffice-legacy-main .platform-grid {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 520px) {
+ .backoffice-legacy-main .admin-page,
+ .backoffice-legacy-main .platform-page {
+ padding: 16px;
+ }
+
+ .backoffice-legacy-main .admin-title,
+ .backoffice-legacy-main .platform-title {
+ font-size: 30px;
+ }
+}
diff --git a/apps/taro/src/components/AdminLegacyShell.tsx b/apps/taro/src/components/AdminLegacyShell.tsx
new file mode 100644
index 00000000..64a4c6b8
--- /dev/null
+++ b/apps/taro/src/components/AdminLegacyShell.tsx
@@ -0,0 +1,123 @@
+import { PropsWithChildren } from 'react';
+import Taro from '@tarojs/taro';
+import { Text, View } from '@tarojs/components';
+import { appEnv } from '@/env';
+import { currentPagePath } from '@/services/routeGuard';
+import './AdminLegacyShell.css';
+
+type AdminNavItem = {
+ name: string;
+ path: string;
+ mark: string;
+ group?: string;
+};
+
+const tenantNavItems: AdminNavItem[] = [
+ { group: '运营概览', name: '工作台', path: '/pages/tenant-admin/workbench/index', mark: '台' },
+ { name: '数据看板', path: '/pages/tenant-admin/dashboard/index', mark: '数' },
+ { group: '业务管理', name: '学生运营', path: '/pages/tenant-admin/students/index', mark: '生' },
+ { name: '题库内容', path: '/pages/tenant-admin/content/index', mark: '题' },
+ { name: '营销中心', path: '/pages/tenant-admin/marketing/index', mark: '销' },
+ { name: '财务运营', path: '/pages/tenant-admin/finance/index', mark: '财' },
+ { group: '系统', name: '租户设置', path: '/pages/tenant-admin/settings/index', mark: '设' },
+];
+
+const platformNavItems: AdminNavItem[] = [
+ { group: '平台概览', name: '工作台', path: '/pages/platform-admin/workbench/index', mark: '台' },
+ { group: 'SaaS 管理', name: '租户管理', path: '/pages/platform-admin/tenants/index', mark: '租' },
+ { name: '账务中心', path: '/pages/platform-admin/billing/index', mark: '账' },
+ { name: '公共题库', path: '/pages/platform-admin/question-banks/index', mark: '库' },
+ { group: '权限', name: '平台员工', path: '/pages/platform-admin/staff/index', mark: '员' },
+];
+
+function isActivePath(currentPath: string, itemPath: string) {
+ return currentPath === itemPath;
+}
+
+function navItemsForPortal() {
+ return appEnv.portal === 'platform-admin' ? platformNavItems : tenantNavItems;
+}
+
+function titleForPortal() {
+ if (appEnv.portal === 'platform-admin') {
+ return {
+ title: '平台管理后台',
+ subtitle: '租户、账务、题库授权、员工权限',
+ badge: 'Console',
+ mark: '管',
+ };
+ }
+ return {
+ title: '租户运营后台',
+ subtitle: '学生、题库、营销、财务与设置',
+ badge: 'Admin',
+ mark: '题',
+ };
+}
+
+export function AdminLegacyShell({ children }: PropsWithChildren) {
+ const currentPath = currentPagePath();
+ const navItems = navItemsForPortal();
+ const title = titleForPortal();
+ let activeGroup = '';
+
+ return (
+
+
+
+
+ {title.mark}
+
+
+ {title.title}
+ {title.subtitle}
+
+
+
+
+ 当前后台
+
+ {title.badge}
+
+
+
+
+ {navItems.map(item => {
+ const nextGroup = item.group && item.group !== activeGroup ? item.group : '';
+ if (item.group) activeGroup = item.group;
+ return (
+
+ {nextGroup ? {nextGroup} : null}
+ Taro.redirectTo({ url: item.path })}>
+ {item.mark}
+ {item.name}
+
+
+ );
+ })}
+
+
+
+
+ 管
+
+
+ 管理账号
+ 已启用权限校验
+
+
+
+
+
+ {navItems.map(item => (
+ Taro.redirectTo({ url: item.path })}>
+ {item.mark}
+ {item.name}
+
+ ))}
+
+
+ {children}
+
+ );
+}
diff --git a/apps/taro/src/index.html b/apps/taro/src/index.html
index aeb06d7b..7a343498 100644
--- a/apps/taro/src/index.html
+++ b/apps/taro/src/index.html
@@ -14,6 +14,8 @@