-
-
-
-
![]()
-
+
+
+ -
+
+
-
-
-
- {{ userInitials }}
-
-
-
-
{{ userInfo.username }}
-
Edit
+
+
+ {{ userInfo.name || userInfo.username }}
+ {{ roleName }}
+
+
{{ userInfo.email }}
+
{{ '@' + (userInfo.username || '') }}
-
-
-
+
+
+ -
+
+
+ 账户设置
+
+
+ -
+
+
+ 管理后台
+
+
+
+ -
+
+
+ 退出登录
+
+
+
-
+
-
@@ -88,41 +137,62 @@
\ No newline at end of file
+
diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css
index 99df19b..cfb670b 100644
--- a/frontend/src/styles/main.css
+++ b/frontend/src/styles/main.css
@@ -4,7 +4,87 @@
themes: light --default, dark, cupcake, emerald, pastel;
}
+/* 收紧 emerald 主题的圆角令牌:默认 selector 圆角过大,小尺寸复选框会渲染成正圆 */
+@plugin "daisyui/theme" {
+ name: "emerald";
+ --radius-selector: 0.5rem;
+ --radius-field: 0.5rem;
+ --radius-box: 0.75rem;
+}
+
+@theme {
+ --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
+ "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB",
+ "Microsoft YaHei", sans-serif;
+}
+
+/* 深浅色跟随 data-theme,修复滚动条与原生控件配色 */
+html {
+ color-scheme: light;
+}
+
+html[data-theme="dark"] {
+ color-scheme: dark;
+}
+
body {
margin: 0;
padding: 0;
+ background-color: var(--color-base-200);
+ color: var(--color-base-content);
+ -webkit-font-smoothing: antialiased;
+}
+
+/* 移动端去除双击缩放延迟与点击高亮 */
+button,
+a,
+[role="button"],
+input[type="checkbox"],
+input[type="radio"],
+summary {
+ touch-action: manipulation;
+ -webkit-tap-highlight-color: transparent;
+}
+
+/* 统一可见键盘焦点(:focus-visible 不干扰鼠标点击) */
+:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
+ outline: 2px solid var(--color-primary);
+ outline-offset: 2px;
+}
+
+/* 弹层内禁止滚动穿透 */
+.modal-box {
+ overscroll-behavior: contain;
+}
+
+/* 普通主按钮统一黑白:浅色主题黑底白字,深色主题白底黑字(特殊按钮 success/error/warning/outline 不受影响) */
+.btn-primary {
+ --btn-color: #171717;
+ --btn-fg: #ffffff;
+ --btn-soft-bg: initial;
+}
+
+.btn-primary:hover {
+ --btn-bg: #000000;
+}
+
+html[data-theme="dark"] .btn-primary {
+ --btn-color: #ffffff;
+ --btn-fg: #171717;
+}
+
+html[data-theme="dark"] .btn-primary:hover {
+ --btn-bg: #e4e4e4;
+}
+
+/* 尊重系统“减弱动态效果”偏好 */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ scroll-behavior: auto !important;
+ }
}
diff --git a/frontend/src/utils/router_menu.ts b/frontend/src/utils/router_menu.ts
index d7c5bd6..a210b8a 100644
--- a/frontend/src/utils/router_menu.ts
+++ b/frontend/src/utils/router_menu.ts
@@ -1,21 +1,14 @@
import type { Component } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import {
- LayoutDashboardIcon,
+ GaugeIcon,
UsersRoundIcon,
KeyRoundIcon,
SettingsIcon,
- UserIcon,
- CommandIcon,
- BracesIcon,
+ GlobeIcon,
} from '@lucide/vue'
-export type MenuLink = { type: 'link'; label: string; to: string; icon?: Component }
-
-export type MenuItem =
- | MenuLink
- | { type: 'title'; label: string }
- | { type: 'submenu'; label: string; to?: string; icon?: Component; open?: boolean; badge?: string; children?: MenuItem[] }
+export type MenuLink = { label: string; to: string; icon?: Component }
// 路由 meta 扩展字段
declare module 'vue-router' {
@@ -30,78 +23,57 @@ declare module 'vue-router' {
}
export const routes: RouteRecordRaw[] = [
- { path: '/', name: 'Home',component: () => import('@/views/Home.vue') },
- { path: '/404', name: '404',component: () => import('@/views/error/NotFound.vue') },
+ { path: '/', name: 'Home', component: () => import('@/views/Home.vue') },
+ { path: '/404', name: '404', component: () => import('@/views/error/NotFound.vue') },
{ path: '/login', name: 'Login', component: () => import('@/views/auth/Login.vue') },
{ path: '/signup', name: 'Signup', component: () => import('@/views/auth/Signup.vue') },
- { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/error/NotFound.vue')}, // Catch all 404
- { path: '/dashboard', name: 'Dashboard', component: ()=>import('@/layouts/DashboardLayout.vue'), meta: { requiresAuth: true, title: 'Dashboard', showInSidebar: false },redirect: '/dashboard/overview', children:[
- { path: 'overview', name: 'Overview', component: ()=>import('@/views/dashboard/Overview.vue'),meta: { title: 'Overview', icon: LayoutDashboardIcon, showInSidebar: true } },
- { path: 'tokens', name: 'Tokens', component: ()=>import('@/views/dashboard/Tokens.vue'),meta: { title: 'Tokens', icon: BracesIcon, showInSidebar: true } },
- { path: 'manager', name: 'Manager',meta: { title: 'Manager', icon: CommandIcon, showInSidebar: true, open: true, badge: 'Admin' }, redirect: '/dashboard/manager/users',children:[
- { path: 'users', name: 'User', component: ()=>import('@/views/dashboard/User.vue'),meta: { title: 'Users', icon: UsersRoundIcon, showInSidebar: true } },
- { path: 'users/new', name: 'UserNew', component: ()=>import('@/views/dashboard/UserNew.vue'),meta: { title: 'UserNew', icon: UsersRoundIcon, showInSidebar: false } },
- { path: 'users/view', name: 'UserView', component: ()=>import('@/views/dashboard/UserView.vue'),meta: { title: 'UserView', icon: UsersRoundIcon, showInSidebar: false } },
- { path: 'keys', name: 'ApiKey', component: ()=>import('@/views/dashboard/Keys.vue') ,meta: { title: 'ApiKeys', icon: KeyRoundIcon, showInSidebar: true } },
- { path: 'keys/view', name: 'ApiKeyView', component: ()=>import('@/views/dashboard/KeyView.vue') ,meta: { title: 'ApiKeyView', icon: KeyRoundIcon, showInSidebar: false } },
- ] },
- { path: 'settings', name: 'Settings', meta: { title: 'Settings', icon: SettingsIcon, showInSidebar: true, open: false } , redirect: '/dashboard/settings/profile',children:[
- { path: 'profile', name: 'Profile', component: ()=>import('@/views/dashboard/Profile.vue'),meta: { title: 'Profile', icon: UserIcon, showInSidebar: true } },
- ]},
- ]},
+ { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/error/NotFound.vue') }, // Catch all 404
+ {
+ path: '/dashboard',
+ name: 'Dashboard',
+ component: () => import('@/layouts/DashboardLayout.vue'),
+ meta: { requiresAuth: true, title: '控制台' },
+ redirect: '/dashboard/overview',
+ children: [
+ { path: 'overview', name: 'Overview', component: () => import('@/views/dashboard/Overview.vue'), meta: { title: '仪表盘' } },
+ { path: 'tokens', name: 'Tokens', component: () => import('@/views/dashboard/Tokens.vue'), meta: { title: 'API Keys' } },
+ {
+ path: 'manager',
+ name: 'Manager',
+ meta: { title: '管理后台' },
+ redirect: '/dashboard/manager/users',
+ children: [
+ { path: 'users', name: 'User', component: () => import('@/views/dashboard/User.vue'), meta: { title: '用户管理' } },
+ { path: 'users/new', name: 'UserNew', component: () => import('@/views/dashboard/UserNew.vue'), meta: { title: '新建用户' } },
+ { path: 'users/view', name: 'UserView', component: () => import('@/views/dashboard/UserView.vue'), meta: { title: '用户详情' } },
+ { path: 'keys', name: 'ApiKey', component: () => import('@/views/dashboard/Keys.vue'), meta: { title: '渠道管理' } },
+ { path: 'keys/view', name: 'ApiKeyView', component: () => import('@/views/dashboard/KeyView.vue'), meta: { title: '渠道详情' } },
+ ],
+ },
+ {
+ path: 'settings',
+ name: 'Settings',
+ meta: { title: '账户设置' },
+ redirect: '/dashboard/settings/profile',
+ children: [
+ { path: 'profile', name: 'Profile', component: () => import('@/views/dashboard/Profile.vue'), meta: { title: '账户设置' } },
+ ],
+ },
+ ],
+ },
];
-export function generateMenuItemsFromRoutes(routes: RouteRecordRaw[], userRole: number, parentPath = ''): MenuItem[] {
- const menuItems: MenuItem[] = [];
+// 控制台菜单(所有登录用户)
+export const consoleMenu: MenuLink[] = [
+ { label: '仪表盘', to: '/dashboard/overview', icon: GaugeIcon },
+ { label: 'API Keys', to: '/dashboard/tokens', icon: KeyRoundIcon },
+ { label: '账户设置', to: '/dashboard/settings/profile', icon: SettingsIcon },
+]
- for (const route of routes) {
- if (route.meta && route.meta.title && route.meta.showInSidebar) {
- const fullPath = parentPath + '/' + route.path.replace(/^\//, '');
-
- if (route.children && route.children.length > 0) {
- if (route.name === 'Manager' && userRole < 10) {
- continue;
- }
- const menuItem: MenuItem = {
- type: 'submenu',
- label: route.meta.title,
- to: fullPath,
- icon: route.meta.icon,
- open: route.meta.open !== undefined ? route.meta.open : false,
- badge: route.meta.badge,
- children: generateMenuItemsFromRoutes(route.children, userRole, fullPath),
- };
-
- if (route.name === 'Overview') {
- menuItems.push(menuItem);
- menuItems.push({ type: 'title', label: 'Apps' });
- continue
- }
-
- menuItems.push(menuItem);
- } else {
- const menuItem: MenuItem = {
- type: 'link',
- label: route.meta.title,
- to: fullPath,
- icon: route.meta.icon,
- };
-
- if (route.name === 'Overview') {
- menuItems.push(menuItem);
- menuItems.push({ type: 'title', label: 'Apps' });
- continue
- }
-
- menuItems.push(menuItem);
- }
- } else if (route.path === '/dashboard' && route.children) {
-
- menuItems.push(...generateMenuItemsFromRoutes(route.children, userRole, '/dashboard'));
- }
- }
-
- return menuItems;
-}
+// 管理后台菜单(role >= 10)
+export const adminMenu: MenuLink[] = [
+ { label: '用户管理', to: '/dashboard/manager/users', icon: UsersRoundIcon },
+ { label: '渠道管理', to: '/dashboard/manager/keys', icon: GlobeIcon },
+]
diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue
index e470262..8eea9a9 100644
--- a/frontend/src/views/Home.vue
+++ b/frontend/src/views/Home.vue
@@ -1,105 +1,149 @@
-
-
-
-

-
- OpenTeam
-
-
-
+
+
+
+
+ OpenTeam
+
+
+
+
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
-
+
+ Open Dashboard
+
-
+
-
-
-
-

-
-
-
- OpenTeam is an open-source, team-shared service. that is compatible with OpenAI API and can
- be safely shared with others for API usage.
-
-
-
-
-
-
-
- 👉Api-Keys: https://platform.openai.com/account/api-keys
+
+
+
+
+ OpenTeam
+
+ An open-source, team-shared service compatible with the OpenAI API — share LLM API usage across your team through a single endpoint.
-
-
-
-
-
使用OpenTeam 管理你的LLM API,仅需一个地址即可接入不同的大模型
-
+
-
-
-
欢迎加入我们的Telegram频道,获取最新动态和帮助
-
-
-
-
-
+
+ 👉 Api-Keys:
+ platform.openai.com/account/api-keys
+
+
+
+
+
+
+
+
One endpoint, every model
+
+ Connect clients on the left with LLM providers on the right — OpenTeam routes and accounts for every request.
+
+
+
+
-
+
+
+
+
+
Join our Telegram channel
+
Get the latest updates and help from the community.
+
+
+
+
+
+
-
-
-
\ No newline at end of file
diff --git a/frontend/src/views/auth/Login.vue b/frontend/src/views/auth/Login.vue
index 6936d4b..518a8fe 100644
--- a/frontend/src/views/auth/Login.vue
+++ b/frontend/src/views/auth/Login.vue
@@ -1,103 +1,60 @@
-
-
-
-

+
+
+
+
+
+
-
- Log in to Dashboard
-
+
Log in to Dashboard
-
-
OR
+
OR
-
-
-
-
-
-
-
-
-
-
-
-
-
+
Don't have an account?
-
- Sign up
-
-
+
Sign up
+
@@ -106,10 +63,10 @@
\ No newline at end of file
+
diff --git a/frontend/src/views/auth/Signup.vue b/frontend/src/views/auth/Signup.vue
index 028b0f2..13354f0 100644
--- a/frontend/src/views/auth/Signup.vue
+++ b/frontend/src/views/auth/Signup.vue
@@ -1,53 +1,53 @@
-
-
-
-

+
+
+
+
+
+
-
- Create Your Account
-
+
Create Your Account
-