@spacego/fe-components 0.0.1-alpha.2 → 0.0.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/package.json +9 -1
  2. package/src/assets/styles/animate.css +0 -62
  3. package/src/assets/styles/index.css +0 -49
  4. package/src/assets/svg/chrome-bg-after.svg +0 -1
  5. package/src/assets/svg/chrome-bg-before.svg +0 -1
  6. package/src/assets/svg/icon-arrows-right.svg +0 -1
  7. package/src/assets/svg/icon-bell.svg +0 -1
  8. package/src/assets/svg/icon-custom.svg +0 -1
  9. package/src/assets/svg/icon-sun-moon.svg +0 -1
  10. package/src/assets/svg/icon-system.svg +0 -1
  11. package/src/assets/svg/loading.svg +0 -13
  12. package/src/assets/svg/login-view.svg +0 -193
  13. package/src/config/constants.ts +0 -19
  14. package/src/config/index.ts +0 -2
  15. package/src/config/theme.ts +0 -20
  16. package/src/fe-layouts/auth-layout/index.scss +0 -34
  17. package/src/fe-layouts/auth-layout/index.tsx +0 -121
  18. package/src/fe-layouts/basics-layout/components/basics-layout/header.tsx +0 -148
  19. package/src/fe-layouts/basics-layout/components/basics-layout/setting-custom-color.tsx +0 -52
  20. package/src/fe-layouts/basics-layout/components/basics-layout/setting-drawer.tsx +0 -165
  21. package/src/fe-layouts/basics-layout/components/basics-layout/sidebar.tsx +0 -88
  22. package/src/fe-layouts/basics-layout/components/basics-layout/tabs.tsx +0 -94
  23. package/src/fe-layouts/basics-layout/components/utils/index.ts +0 -142
  24. package/src/fe-layouts/basics-layout/index.scss +0 -110
  25. package/src/fe-layouts/basics-layout/index.tsx +0 -207
  26. package/src/fe-layouts/blank-layout/index.tsx +0 -12
  27. package/src/fe-layouts/context/context.ts +0 -11
  28. package/src/fe-layouts/context/global-context.d.ts +0 -241
  29. package/src/fe-layouts/context/global-context.provider.tsx +0 -81
  30. package/src/fe-layouts/context/index.ts +0 -10
  31. package/src/fe-layouts/index.ts +0 -13
  32. package/src/fe-layouts/layout.tsx +0 -74
  33. package/src/hooks/index.ts +0 -1
  34. package/src/hooks/use-auth.hook.ts +0 -54
  35. package/src/index.ts +0 -24
  36. package/src/router/index.ts +0 -110
  37. package/src/router/permission.tsx +0 -134
  38. package/src/router/routes.tsx +0 -94
  39. package/src/router/utils.ts +0 -283
  40. package/src/store/index.ts +0 -9
  41. package/src/store/modules/layout-config.store.ts +0 -343
  42. package/src/store/modules/theme.store.ts +0 -99
  43. package/src/typings/index.d.ts +0 -59
  44. package/src/typings/shims-axios.d.ts +0 -38
  45. package/src/utils/icon.tsx +0 -32
  46. package/src/utils/index.ts +0 -9
  47. package/src/utils/theme.ts +0 -219
  48. package/tsconfig.json +0 -28
  49. package/vite.config.ts +0 -85
@@ -1,142 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-28
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-01-28
6
- * @description: 布局工具函数
7
- */
8
- import type { RouteObject } from 'react-router-dom';
9
-
10
- import type { MenuItem } from '@/router/utils';
11
-
12
- /** RouteNode 类型定义 */
13
- export type RouteNode = RouteObject & {
14
- parentId?: string;
15
- children?: RouteNode[];
16
- handle?: {
17
- title?: string;
18
- isIndependentMenu?: boolean;
19
- activeRouteMenuId?: string;
20
- };
21
- };
22
-
23
- /**
24
- * 规范化路径(统一格式,去除末尾斜杠)
25
- */
26
- export function normalizePath(path?: string): string {
27
- if(!path) return '';
28
- return path === '/' ? '/' : path.replace(/\/+$/, '');
29
- }
30
-
31
- /**
32
- * 根据路径查找对应的路由节点
33
- * 优先查找子路由,避免匹配到布局路由(如 root)
34
- */
35
- export function findRouteByPath(path: string, routeList: RouteNode[]): RouteNode | null {
36
- const normalizedPath = normalizePath(path);
37
-
38
- for(const route of routeList) {
39
- // 先递归查找子路由(优先查找子路由,避免匹配到布局路由)
40
- if(route.children && route.children.length > 0) {
41
- const found = findRouteByPath(path, route.children);
42
- if(found) return found;
43
- }
44
-
45
- // 首页特殊处理
46
- if(route.id === 'Home' && normalizedPath === '/') {
47
- return route;
48
- }
49
-
50
- // 匹配路径(规范化后比较)
51
- // 排除布局路由(root、blank、auth 等),这些路由不应该作为页面路由
52
- const routePath = normalizePath(route.path);
53
- if(routePath && routePath === normalizedPath && !['root', 'blank', 'auth'].includes(route.id || '')) {
54
- return route;
55
- }
56
- }
57
- return null;
58
- }
59
-
60
- /**
61
- * 构建路由映射表(id -> RouteNode)
62
- */
63
- export function buildRouteMap(routeList: RouteNode[]): Map<string, RouteNode> {
64
- const routeMap = new Map<string, RouteNode>();
65
-
66
- const build = (routes: RouteNode[]) => {
67
- routes.forEach(route => {
68
- if(route.id) {
69
- routeMap.set(route.id, route);
70
- }
71
- if(route.children && route.children.length > 0) {
72
- build(route.children);
73
- }
74
- });
75
- };
76
-
77
- build(routeList);
78
- return routeMap;
79
- }
80
-
81
- /**
82
- * 根据 id 查找路由节点
83
- */
84
- export function getRouteById(id: string, routeMap: Map<string, RouteNode>): RouteNode | null {
85
- return routeMap.get(id) || null;
86
- }
87
-
88
- /**
89
- * 递归查找父级路由链(从根到当前),根据 isIndependentMenu 过滤层级
90
- */
91
- export function findParentRoutes(route: RouteNode, routeMap: Map<string, RouteNode>): RouteNode[] {
92
- const chain: RouteNode[] = [route];
93
- let current = route;
94
-
95
- // 向上查找父级
96
- while(current.parentId && current.parentId !== '/') {
97
- const parent = getRouteById(current.parentId, routeMap);
98
- if(parent) {
99
- // 如果当前路由是独立菜单,跳过父级目录,不添加到链中
100
- const isIndependent = current.handle?.isIndependentMenu === true;
101
- if(!isIndependent) {
102
- chain.unshift(parent); // 在数组开头插入,保持从根到当前的顺序
103
- }
104
- // 继续向上查找,即使当前是独立菜单也要继续,因为可能父级的父级需要显示
105
- current = parent;
106
- } else {
107
- break;
108
- }
109
- }
110
-
111
- return chain;
112
- }
113
-
114
- /**
115
- * 递归查找菜单的父级 key
116
- */
117
- export function findMenuParentKeys(targetKey: string, menuList: MenuItem[], parents: string[] = []): string[] | null {
118
- for(const menu of menuList) {
119
- if(menu.key === targetKey) {
120
- return parents; // 找到目标菜单,返回所有父级 key
121
- }
122
- if(menu.children && menu.children.length > 0) {
123
- const found = findMenuParentKeys(targetKey, menu.children, [...parents, menu.key]);
124
- if(found) return found;
125
- }
126
- }
127
- return null;
128
- }
129
-
130
- /**
131
- * 递归查找菜单项
132
- */
133
- export function findMenuByKey(key: string, menuList: MenuItem[]): MenuItem | null {
134
- for(const menu of menuList) {
135
- if(menu.key === key) return menu;
136
- if(menu.children) {
137
- const found = findMenuByKey(key, menu.children);
138
- if(found) return found;
139
- }
140
- }
141
- return null;
142
- }
@@ -1,110 +0,0 @@
1
- // 基础布局样式
2
- .basics-layout {
3
- --sidebar-width: 220px;
4
- --header-height: 64px;
5
- --tabs-height: 44px;
6
- --background-primary-light: color-mix(in srgb, var(--color-primary) 10%, transparent);
7
- --hover-background-color: #f4f4f5;
8
- --icon-text-color: #323639;
9
-
10
- [data-theme='dark'] & {
11
- --hover-background-color: #2e3033;
12
- --icon-text-color: #dddddd;
13
- --background-primary-light: #292a2d;
14
- }
15
-
16
- .layout-header {
17
- flex-shrink: 0;
18
-
19
- .basics-breadcrumb {
20
- .ant-breadcrumb-separator {
21
- display: flex;
22
- align-items: center;
23
- margin-inline: 6px;
24
- }
25
- }
26
- }
27
-
28
- // 侧边栏样式
29
- .layout-sidebar {
30
- z-index: 20;
31
- flex-shrink: 0;
32
-
33
- .sidebar-menu {
34
- background: var(--global-background-color);
35
- border: none;
36
-
37
- .ant-menu-item {
38
- margin: 4px 8px;
39
- width: calc(100% - 16px);
40
- border-radius: 8px;
41
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
42
- padding-inline: 30%;
43
-
44
- &:hover {
45
- background: var(--hover-background-color);
46
-
47
- .ant-menu-item-icon {
48
- transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
49
- transform: scale(1.18);
50
- }
51
- }
52
-
53
- &.ant-menu-item-selected {
54
- background-color: var(--background-primary-light);
55
- color: var(--color-primary);
56
- }
57
-
58
- .ant-menu-title-content {
59
- margin-inline-start: 8px;
60
- }
61
- }
62
-
63
- .ant-menu-submenu-title {
64
- margin: 4px 8px;
65
- width: calc(100% - 16px);
66
- border-radius: 8px;
67
- padding-inline: 30%;
68
-
69
- &:hover {
70
- background-color: var(--hover-background-color);
71
-
72
- .ant-menu-item-icon {
73
- transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
74
- transform: scale(1.18);
75
- }
76
- }
77
- }
78
-
79
- .ant-menu-sub {
80
- background: transparent;
81
- }
82
- }
83
- }
84
-
85
- // 自定义滚动条
86
- .custom-scrollbar {
87
- &::-webkit-scrollbar {
88
- width: 8px;
89
- height: 8px;
90
- }
91
-
92
- &::-webkit-scrollbar-track {
93
- background: transparent;
94
- }
95
-
96
- &::-webkit-scrollbar-thumb {
97
- background: rgba(0, 0, 0, 0.3);
98
- border-radius: 4px;
99
-
100
- &:hover {
101
- background: rgba(0, 0, 0, 0.5);
102
- }
103
- }
104
- }
105
-
106
- .layout-tabs {
107
- position: relative;
108
- scrollbar-width: thin; // Firefox
109
- }
110
- }
@@ -1,207 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-23 21:29:09
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-02-01 01:53:18
6
- * @description: 后台布局
7
- */
8
- import { useSelector } from '@spacego/zustand';
9
- import { Layout, Spin } from 'antd';
10
- import { useEffect, useMemo } from 'react';
11
- import { Outlet, useLocation, useNavigate } from 'react-router-dom';
12
-
13
- import { useLayoutConfigStore } from '@/store';
14
-
15
- import { useGlobal } from '../context';
16
- import Header from './components/basics-layout/header';
17
- import Sidebar from './components/basics-layout/sidebar';
18
- import TabsComponent from './components/basics-layout/tabs';
19
- import {
20
- buildRouteMap,
21
- findMenuByKey,
22
- findMenuParentKeys,
23
- findParentRoutes,
24
- findRouteByPath,
25
- type RouteNode
26
- } from './components/utils';
27
-
28
- import '../../assets/styles/index.css';
29
- import './index.scss';
30
-
31
- const { Content } = Layout;
32
-
33
- export default function BasicsLayout() {
34
- const location = useLocation();
35
- const navigate = useNavigate();
36
-
37
- const { token, menus, routes } = useGlobal();
38
- const { tabsAttribute, sidebarCollapsed, loadingConfig, ADD_TAB, REMOVE_TAB, SET_ACTIVE_TAB_KEY, SET_SIDEBAR_COLLAPSED } = useLayoutConfigStore(useSelector(['tabsAttribute', 'sidebarCollapsed', 'loadingConfig', 'ADD_TAB', 'REMOVE_TAB', 'SET_ACTIVE_TAB_KEY', 'SET_SIDEBAR_COLLAPSED']));
39
- const { tabsList, tabsActiveKey } = tabsAttribute;
40
-
41
- // 根据当前路由获取激活的菜单 ID(用于菜单高亮和展开)
42
- const activeMenuId = useMemo(() => {
43
- if(!routes || routes.length === 0) {
44
- return tabsActiveKey;
45
- }
46
-
47
- // 根据当前路径查找路由
48
- const currentRoute = findRouteByPath(location.pathname, routes as RouteNode[]);
49
- if(!currentRoute) {
50
- return tabsActiveKey;
51
- }
52
-
53
- // 获取激活的菜单 ID:优先使用 activeRouteMenuId,如果为空则使用自己的 menuId
54
- return currentRoute.handle?.activeRouteMenuId || currentRoute.id || tabsActiveKey;
55
- }, [location.pathname, routes, tabsActiveKey]);
56
-
57
- // 查找当前激活菜单的所有父级菜单 key(用于展开菜单)
58
- const openKeys = useMemo(() => {
59
- const keys: string[] = [];
60
-
61
- if(!menus || menus.length === 0 || !activeMenuId) {
62
- return keys;
63
- }
64
-
65
- // 根据激活的菜单 ID 查找父级菜单 key
66
- const parentKeys = findMenuParentKeys(activeMenuId, menus);
67
- if(parentKeys) {
68
- keys.push(...parentKeys);
69
- }
70
-
71
- return keys;
72
- }, [activeMenuId, menus]);
73
-
74
- // 构建面包屑 - 基于 routes 数据,根据 isIndependentMenu 判断是否需要展示层级
75
- const breadcrumbItems = useMemo(() => {
76
- const items: { title: string, href?: string }[] = [];
77
-
78
- if(!routes || routes.length === 0) {
79
- // 如果没有路由数据,至少显示首页
80
- if(location.pathname === '/') {
81
- items.push({ title: '首页' });
82
- }
83
- return items;
84
- }
85
-
86
- const routeList = routes as RouteNode[];
87
-
88
- // 构建路由映射表
89
- const routeMap = buildRouteMap(routeList);
90
-
91
- // 查找当前路径对应的路由
92
- const currentRoute = findRouteByPath(location.pathname, routeList);
93
-
94
- if(currentRoute) {
95
- // 找到路由,构建面包屑路径
96
- const routeChain = findParentRoutes(currentRoute, routeMap);
97
- routeChain.forEach(route => {
98
- const title = route.handle?.title || route.id || '';
99
- if(title) {
100
- items.push({ title });
101
- }
102
- });
103
- } else if(location.pathname === '/') {
104
- // 首页特殊处理
105
- items.push({ title: '首页' });
106
- }
107
-
108
- return items;
109
- }, [location.pathname, routes]);
110
-
111
- // 菜单点击处理
112
- const handleMenuClick = (key: string) => {
113
- const menu = findMenuByKey(key, menus!);
114
- navigate(menu?.routeNode?.path ?? '/');
115
- };
116
-
117
- // 标签页切换
118
- const handleTabChange = (key: string) => {
119
- SET_ACTIVE_TAB_KEY(key);
120
- const tab = tabsList.find((t: { key: string; path: string }) => t.key === key);
121
- if(tab) {
122
- navigate(tab.path);
123
- }
124
- };
125
-
126
- // 标签页关闭
127
- const handleTabClose = (targetKey: string) => {
128
- const tab = REMOVE_TAB(targetKey);
129
- navigate(tab.path ?? '/');
130
- };
131
-
132
- // 根据路径从 routes 中查找路由节点(包含隐藏路由)
133
- const findRouteByPathInRoutes = useMemo(() => {
134
- if(!routes || routes.length === 0) return null;
135
- return (path: string) => findRouteByPath(path, routes as RouteNode[]);
136
- }, [routes]);
137
-
138
- // 根据路由添加tabslist
139
- const addTabsList = () => {
140
- // 直接从 routes 中查找路由(包含隐藏路由)
141
- const currentRoute = findRouteByPathInRoutes?.(location.pathname);
142
- // 排除布局路由(如 root、blank、auth 等),只处理实际页面路由
143
- if(currentRoute && currentRoute.id && !['root', 'blank', 'auth'].includes(currentRoute.id)) {
144
- const tabKey = currentRoute.id;
145
- const tabLabel = currentRoute.handle?.title || currentRoute.id;
146
-
147
- const existingTab = tabsList.find((t: { key: string }) => t.key === tabKey);
148
- if(!existingTab) {
149
- ADD_TAB({
150
- key: tabKey,
151
- label: tabLabel,
152
- path: location.pathname
153
- });
154
- } else {
155
- SET_ACTIVE_TAB_KEY(tabKey);
156
- }
157
- }
158
- };
159
-
160
- // 路由变化时自动添加标签
161
- useEffect(() => {
162
- if(token) {
163
- addTabsList();
164
- }
165
- }, [location.pathname, token]);
166
-
167
- return (
168
- <Layout className="basics-layout w-full h-screen overflow-hidden flex flex-col">
169
- {/* 顶部导航 */}
170
- <Header breadcrumbItems={breadcrumbItems} />
171
-
172
- <Layout className="flex-1 overflow-hidden">
173
- {/* 左侧菜单 */}
174
- <Sidebar
175
- collapsed={sidebarCollapsed}
176
- onCollapse={SET_SIDEBAR_COLLAPSED}
177
- menus={menus!}
178
- activeKey={activeMenuId}
179
- openKeys={openKeys}
180
- onMenuClick={handleMenuClick}
181
- />
182
-
183
- {/* 内容区域 */}
184
- <Content className="flex flex-col flex-1 overflow-hidden bg-[#eff1f4] dark:bg-[#141619]">
185
- {/* 标签栏 */}
186
- <TabsComponent
187
- tabs={tabsList}
188
- activeKey={tabsActiveKey}
189
- onChange={handleTabChange}
190
- onClose={handleTabClose}
191
- />
192
-
193
- {/* 页面内容 - 固定高度,内部滚动 */}
194
- <div className="flex-1 overflow-auto p-3 custom-scrollbar relative">
195
- <Outlet />
196
-
197
- {loadingConfig.show && (
198
- <div className="absolute inset-0 z-9999 flex items-center justify-center bg-white/80 dark:bg-[#1a1c1f]/80">
199
- <Spin spinning />
200
- </div>
201
- )}
202
- </div>
203
- </Content>
204
- </Layout>
205
- </Layout>
206
- );
207
- }
@@ -1,12 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-23 21:29:09
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-01-25 23:50:49
6
- * @description: 空白布局
7
- */
8
- import { Outlet } from 'react-router-dom';
9
-
10
- export default function BlankLayout() {
11
- return <Outlet />;
12
- }
@@ -1,11 +0,0 @@
1
- import { createContext, useContext } from 'react';
2
-
3
- import type { IGlobalContext } from './global-context';
4
-
5
- export const GlobalContext = createContext<IGlobalContext>({} as IGlobalContext);
6
-
7
- /**
8
- * @name 使用全局上下文
9
- * @returns
10
- */
11
- export const useGlobal = () => useContext(GlobalContext);