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

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 (59) hide show
  1. package/lib/assets/svg/chrome-bg-after.svg.js +3 -2
  2. package/lib/assets/svg/chrome-bg-before.svg.js +2 -1
  3. package/lib/assets/svg/icon-arrows-right.svg.js +3 -2
  4. package/lib/assets/svg/icon-bell.svg.js +3 -2
  5. package/lib/assets/svg/icon-custom.svg.js +3 -2
  6. package/lib/assets/svg/icon-sun-moon.svg.js +3 -2
  7. package/lib/assets/svg/icon-system.svg.js +3 -2
  8. package/lib/assets/svg/login-view.svg.js +871 -2
  9. package/lib/fe-layouts/auth-layout/index.js +8 -8
  10. package/lib/fe-layouts/basics-layout/components/basics-layout/tabs.js +5 -5
  11. package/package.json +9 -1
  12. package/src/assets/styles/animate.css +0 -62
  13. package/src/assets/styles/index.css +0 -49
  14. package/src/assets/svg/chrome-bg-after.svg +0 -1
  15. package/src/assets/svg/chrome-bg-before.svg +0 -1
  16. package/src/assets/svg/icon-arrows-right.svg +0 -1
  17. package/src/assets/svg/icon-bell.svg +0 -1
  18. package/src/assets/svg/icon-custom.svg +0 -1
  19. package/src/assets/svg/icon-sun-moon.svg +0 -1
  20. package/src/assets/svg/icon-system.svg +0 -1
  21. package/src/assets/svg/loading.svg +0 -13
  22. package/src/assets/svg/login-view.svg +0 -193
  23. package/src/config/constants.ts +0 -19
  24. package/src/config/index.ts +0 -2
  25. package/src/config/theme.ts +0 -20
  26. package/src/fe-layouts/auth-layout/index.scss +0 -34
  27. package/src/fe-layouts/auth-layout/index.tsx +0 -121
  28. package/src/fe-layouts/basics-layout/components/basics-layout/header.tsx +0 -148
  29. package/src/fe-layouts/basics-layout/components/basics-layout/setting-custom-color.tsx +0 -52
  30. package/src/fe-layouts/basics-layout/components/basics-layout/setting-drawer.tsx +0 -165
  31. package/src/fe-layouts/basics-layout/components/basics-layout/sidebar.tsx +0 -88
  32. package/src/fe-layouts/basics-layout/components/basics-layout/tabs.tsx +0 -94
  33. package/src/fe-layouts/basics-layout/components/utils/index.ts +0 -142
  34. package/src/fe-layouts/basics-layout/index.scss +0 -110
  35. package/src/fe-layouts/basics-layout/index.tsx +0 -207
  36. package/src/fe-layouts/blank-layout/index.tsx +0 -12
  37. package/src/fe-layouts/context/context.ts +0 -11
  38. package/src/fe-layouts/context/global-context.d.ts +0 -241
  39. package/src/fe-layouts/context/global-context.provider.tsx +0 -81
  40. package/src/fe-layouts/context/index.ts +0 -10
  41. package/src/fe-layouts/index.ts +0 -13
  42. package/src/fe-layouts/layout.tsx +0 -74
  43. package/src/hooks/index.ts +0 -1
  44. package/src/hooks/use-auth.hook.ts +0 -54
  45. package/src/index.ts +0 -24
  46. package/src/router/index.ts +0 -110
  47. package/src/router/permission.tsx +0 -134
  48. package/src/router/routes.tsx +0 -94
  49. package/src/router/utils.ts +0 -283
  50. package/src/store/index.ts +0 -9
  51. package/src/store/modules/layout-config.store.ts +0 -343
  52. package/src/store/modules/theme.store.ts +0 -99
  53. package/src/typings/index.d.ts +0 -59
  54. package/src/typings/shims-axios.d.ts +0 -38
  55. package/src/utils/icon.tsx +0 -32
  56. package/src/utils/index.ts +0 -9
  57. package/src/utils/theme.ts +0 -219
  58. package/tsconfig.json +0 -28
  59. package/vite.config.ts +0 -85
@@ -1,121 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-26 22:13:52
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-02-01 02:00:04
6
- * @description: login-layout
7
- */
8
- import { useSelector } from '@spacego/zustand';
9
- import { useState } from 'react';
10
- import { FaCheck } from 'react-icons/fa6';
11
- import { IoMoon } from 'react-icons/io5';
12
- import { LuPalette } from 'react-icons/lu';
13
- import { MdWbSunny } from 'react-icons/md';
14
- import { Outlet } from 'react-router-dom';
15
-
16
- import LoginSvg from '@/assets/svg/login-view.svg?react';
17
- import { applyThemeWithTransition, THEME_COLORS, useGlobal, useThemeStore } from '@/index';
18
-
19
- import './index.scss';
20
-
21
- export default function LoginLayout() {
22
-
23
- const { appName, logo } = useGlobal();
24
-
25
- const { theme, themeColor, TOGGLE_THEME, SET_THEME_COLOR } = useThemeStore(useSelector(['theme', 'themeColor', 'TOGGLE_THEME', 'SET_THEME_COLOR']));
26
- // 记录调色板图标的 hover 状态,可在组件中使用 isPaletteHovered
27
- const [isPaletteHovered, setIsPaletteHovered] = useState(false);
28
-
29
- // 处理主题切换
30
- const handleToggleTheme = (e: React.MouseEvent<HTMLDivElement>) => {
31
- const rect = e.currentTarget.getBoundingClientRect();
32
- const x = rect.left + rect.width / 2;
33
- const y = rect.top + rect.height / 2;
34
-
35
- // 使用带过渡的主题切换
36
- const newTheme = theme === 'light' ? 'dark' : 'light';
37
- applyThemeWithTransition(newTheme, x, y).then(() => {
38
- TOGGLE_THEME();
39
- });
40
- };
41
-
42
- return (
43
- <div className="flex min-h-full flex-1 select-none overflow-x-hidden">
44
-
45
- <div
46
- className="bg-[#f4f4f5] dark:bg-[#2e3033] rounded-3xl px-3 py-1 flex-center absolute right-3 top-4 z-10 text-[#323639cc] dark:text-[#f2f2f2] flex justify-end transition-all duration-300 ease-in-out"
47
- >
48
- <div
49
- className="flex overflow-hidden items-center gap-3 whitespace-nowrap origin-right"
50
- style={{
51
- width: isPaletteHovered ? '224px' : '0', // 7个颜色(20px) + 6个gap(12px) = 140 + 72 = 212px
52
- transform: `scaleX(${isPaletteHovered ? 1 : 0})`,
53
- opacity: isPaletteHovered ? 1 : 0,
54
- transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), width 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
55
- }}
56
- onMouseEnter={() => setIsPaletteHovered(true)}
57
- onMouseLeave={() => setIsPaletteHovered(false)}
58
- >
59
- {
60
- THEME_COLORS.map((color) => (
61
- <div
62
- key={color}
63
- className="w-5 h-5 rounded-full flex items-center justify-center cursor-pointer hover-scale-animation shrink-0"
64
- style={{ backgroundColor: color }}
65
- onClick={() => SET_THEME_COLOR(color)}
66
- >
67
- {themeColor === color && <FaCheck className="size-[10px] text-white" />}
68
- </div>
69
- ))
70
- }
71
- </div>
72
-
73
- <div className="flex relative z-10 shrink-0">
74
- <div
75
- className="w-8 h-8 flex items-center justify-center cursor-pointer hover-scale-animation"
76
- onMouseEnter={() => setIsPaletteHovered(true)}
77
- onMouseLeave={() => setIsPaletteHovered(false)}
78
- >
79
- <LuPalette className="h-[16px] w-[16px] text-(--color-primary)" />
80
- </div>
81
-
82
- <div className="w-8 h-8 flex items-center justify-center cursor-pointer hover-scale-animation" onClick={handleToggleTheme}>
83
- {theme === 'dark' ? <MdWbSunny className="h-[18px] w-[18px]" /> : <IoMoon className="h-[18px] w-[18px]" />}
84
- </div>
85
- </div>
86
- </div>
87
-
88
- <div className="absolute left-0 top-0 z-10 flex flex-1">
89
- <div className="text-foreground lg:text-foreground ml-4 mt-4 flex flex-1 items-center sm:left-6 sm:top-6">
90
- <div className="m-0 text-xl font-medium flex items-center gap-2 text-[#333] dark:text-[#f2f2f2]">
91
- {logo}
92
- {appName}
93
- </div>
94
- </div>
95
- </div>
96
-
97
- <div className="relative hidden w-0 flex-1 lg:block">
98
- <div className="bg-background-deep absolute inset-0 h-full w-full dark:bg-[#070709]!">
99
- <div className="login-background absolute left-0 top-0 size-full" />
100
-
101
- <div className="flex flex-col items-center justify-center mr-20 h-full -enter-x">
102
- <LoginSvg className="animate-float h-64 w-2/5" />
103
-
104
- <div className="text-1xl mt-6 font-sans lg:text-2xl text-[#333] dark:text-[#f2f2f2]">企业级的大型中后台管理系统</div>
105
- <div className="mt-2 text-[#323639] dark:text-[#a1a1aa]">高效、安全、易用的管理系统</div>
106
- </div>
107
- </div>
108
- </div>
109
-
110
- <div className="login-side flex flex-col items-center justify-center relative px-6 py-10 lg:flex-initial lg:px-8 min-h-full w-2/5 flex-1 dark:bg-[#14161a]">
111
- <Outlet />
112
-
113
- <div className="text-[#71717a] absolute bottom-3 flex text-center text-xs">
114
- <div className="text-md flex justify-center mt-4">
115
- Copyright © 2024 <a href="https://dshuais.netlify.app/" className="text-(--color-primary)! hover:text-(--hover-primary-color)! mx-1" target="_blank">DuShuai</a>
116
- </div>
117
- </div>
118
- </div>
119
- </div>
120
- );
121
- }
@@ -1,148 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-27
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-02-01 02:00:08
6
- * @description: 顶部导航栏组件
7
- */
8
- import { LogoutOutlined } from '@ant-design/icons';
9
- import { useSelector } from '@spacego/zustand';
10
- import { Avatar, Badge, Breadcrumb, Dropdown, type MenuProps, Popover, Space } from 'antd';
11
- import { useState } from 'react';
12
- import { IoMoon } from 'react-icons/io5';
13
- import { MdWbSunny } from 'react-icons/md';
14
- import { Link } from 'react-router-dom';
15
-
16
- import IconArrowsRightSvg from '@/assets/svg/icon-arrows-right.svg?react';
17
- import IconBellSvg from '@/assets/svg/icon-bell.svg?react';
18
- import IconSystemSvg from '@/assets/svg/icon-system.svg?react';
19
- import { useAuth } from '@/hooks';
20
- import { useThemeStore } from '@/store';
21
- import { applyThemeWithTransition } from '@/utils';
22
-
23
- import { useGlobal } from '../../../context';
24
- import SettingDrawer from './setting-drawer';
25
-
26
- interface HeaderProps {
27
- breadcrumbItems: { title: string }[]
28
- }
29
-
30
- export default function Header({ breadcrumbItems }: HeaderProps) {
31
- const { logout } = useAuth();
32
- const { appName, logo, headerActionsRender, userInfo, userMenuItems: _userMenuItems = [] } = useGlobal();
33
-
34
- const { theme, TOGGLE_THEME } = useThemeStore(useSelector(['theme', 'TOGGLE_THEME']));
35
-
36
- const [settingOpen, setSettingOpen] = useState(false);
37
-
38
- // 处理主题切换
39
- const handleToggleTheme = (e: React.MouseEvent<HTMLDivElement>) => {
40
- const rect = e.currentTarget.getBoundingClientRect();
41
- const x = rect.left + rect.width / 2;
42
- const y = rect.top + rect.height / 2;
43
-
44
- // 使用带过渡的主题切换
45
- const newTheme = theme === 'light' ? 'dark' : 'light';
46
- applyThemeWithTransition(newTheme, x, y).then(() => {
47
- TOGGLE_THEME();
48
- });
49
- };
50
-
51
- // 用户菜单
52
- const userMenuItems: MenuProps['items'] = [
53
- ..._userMenuItems,
54
- {
55
- key: 'logout',
56
- label: '退出登录',
57
- icon: <LogoutOutlined />,
58
- onClick: () => {
59
- logout();
60
- }
61
- }
62
- ];
63
-
64
- return (
65
- <header className="layout-header h-[50px] px-4 flex items-center justify-between bg-(--global-background-color) border-b border-(--border-color) z-10">
66
- <div className="flex items-center gap-2">
67
- {/* Logo */}
68
- <div className="min-w-[204px]">
69
- <Link to="/" className="flex items-center gap-2 no-underline">
70
- {logo}
71
- <span className="text-lg font-semibold text-gray-800 dark:text-[#f2f2f2] tracking-wide">
72
- {appName}
73
- </span>
74
- </Link>
75
- </div>
76
-
77
- {/* 面包屑 */}
78
- <Breadcrumb
79
- items={breadcrumbItems}
80
- className="items-center basics-breadcrumb text-sm! hidden md:flex"
81
- separator={<IconArrowsRightSvg className="w-4 h-4 text-gray-400" />}
82
- />
83
- </div>
84
-
85
- <div className="flex items-center">
86
- <Space size={6}>
87
- {/* 头部按钮组 */}
88
- {headerActionsRender}
89
-
90
- {/* 设置 */}
91
- <div
92
- className="cursor-pointer w-8 h-8 text-(--icon-text-color) hover:text-primary transition-colors p-2 rounded-full hover:bg-(--hover-background-color) hover-scale-animation"
93
- onClick={() => setSettingOpen(true)}
94
- >
95
- <IconSystemSvg />
96
- </div>
97
-
98
- {/* 主题切换 */}
99
- <div
100
- onClick={handleToggleTheme}
101
- className="cursor-pointer w-8 h-8 text-(--icon-text-color) hover:text-primary transition-colors p-2 rounded-full hover:bg-(--hover-background-color) hover-scale-animation flex items-center justify-center"
102
- title={theme === 'light' ? '切换到暗色主题' : '切换到亮色主题'}
103
- >
104
- {theme === 'light' ? (
105
- <IoMoon className="text-lg" />
106
- ) : (
107
- <MdWbSunny className="text-lg" />
108
- )}
109
- </div>
110
-
111
- {/* 通知 */}
112
- <div className="cursor-pointer w-8 h-8 text-(--icon-text-color) hover:text-primary transition-colors p-2 rounded-full hover:bg-(--hover-background-color) hover-scale-animation">
113
- <Popover
114
- placement="bottom"
115
- arrow={false}
116
- trigger="click"
117
- classNames={{
118
- container: 'p-0!'
119
- }}
120
- content={1}
121
- >
122
- <Badge dot color="var(--color-primary)">
123
- <IconBellSvg />
124
- </Badge>
125
- </Popover>
126
- </div>
127
-
128
- {/* 用户头像 */}
129
- <Dropdown menu={{ items: userMenuItems }} placement="bottomRight" arrow>
130
- <div className="flex items-center gap-2 cursor-pointer hover:bg-(--hover-background-color) px-2 py-1 rounded-full transition-all border border-transparent">
131
- <Avatar
132
- size="default"
133
- className="bg-primary/10 text-primary border border-primary/20"
134
- src={userInfo?.avatar}
135
- />
136
- <div className="hidden lg:flex flex-col items-start">
137
- <span className="text-sm font-medium text-gray-700 dark:text-[#f2f2f2] leading-tight">{userInfo?.username}</span>
138
- <span className="text-xs text-gray-400 dark:text-[#a1a1aa] leading-tight">{userInfo?.roleName}</span>
139
- </div>
140
- </div>
141
- </Dropdown>
142
- </Space>
143
- </div>
144
-
145
- <SettingDrawer open={settingOpen} onClose={() => setSettingOpen(false)} />
146
- </header>
147
- );
148
- }
@@ -1,52 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-31 23:03:47
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-02-01 02:00:11
6
- * @description: 自定义主题色
7
- */
8
- import { useSelector } from '@spacego/zustand';
9
- import { memo, useCallback, useRef } from 'react';
10
-
11
- import IconCustomSvg from '@/assets/svg/icon-custom.svg?react';
12
- import { useThemeStore } from '@/store';
13
- import { applyThemeColor } from '@/utils';
14
-
15
- function SettingCustomColor() {
16
- const { themeColor, SET_THEME_COLOR } = useThemeStore(useSelector(['themeColor', 'SET_THEME_COLOR']));
17
-
18
- const inputRef = useRef<HTMLInputElement>(null);
19
- const localValueRef = useRef<string>(themeColor);
20
-
21
- const handleInput = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
22
- const value = e.target.value;
23
- localValueRef.current = value;
24
-
25
- // 直接更新 CSS 变量,不更新 store,避免重新渲染
26
- applyThemeColor(value);
27
- }, []);
28
-
29
- // onBlur 事件在颜色选择面板关闭时触发,确保最终值被保存
30
- const handleBlur = useCallback(() => {
31
- // 确保最终值被保存到 store
32
- if(localValueRef.current) {
33
- SET_THEME_COLOR(localValueRef.current);
34
- }
35
- }, []);
36
-
37
- return (
38
- <div className="w-5 h-5 relative z-10">
39
- <IconCustomSvg />
40
- <input
41
- ref={inputRef}
42
- type="color"
43
- value={themeColor}
44
- className="absolute w-full h-full z-10 opacity-0 cursor-pointer"
45
- onInput={handleInput}
46
- onBlur={handleBlur}
47
- />
48
- </div>
49
- );
50
- }
51
-
52
- export default memo(SettingCustomColor);
@@ -1,165 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-29
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-01-30 21:13:40
6
- * @description: 设置抽屉
7
- */
8
- import { CloseOutlined } from '@ant-design/icons';
9
- import { useSelector } from '@spacego/zustand';
10
- import { Drawer } from 'antd';
11
- import { IoMoon } from 'react-icons/io5';
12
- import { MdWbSunny } from 'react-icons/md';
13
-
14
- import IconSunMoonSvg from '@/assets/svg/icon-sun-moon.svg?react';
15
- import { THEME_COLORS } from '@/config';
16
- import { useThemeStore } from '@/store';
17
-
18
- import SettingCustomColor from './setting-custom-color';
19
-
20
- interface SettingDrawerProps {
21
- open: boolean
22
- onClose: () => void
23
- }
24
-
25
- export default function SettingDrawer({ open, onClose }: SettingDrawerProps) {
26
- const { theme, themeColor, isCustomThemeColor, SET_THEME_COLOR, SET_IS_CUSTOM_THEME_COLOR } =
27
- useThemeStore(useSelector(['theme', 'themeColor', 'isCustomThemeColor', 'SET_THEME_COLOR', 'SET_IS_CUSTOM_THEME_COLOR']));
28
-
29
- const list = [
30
- { mode: 'light', icon: MdWbSunny, label: '浅色' },
31
- { mode: 'dark', icon: IoMoon, label: '深色' },
32
- { mode: 'system', icon: IconSunMoonSvg, label: '跟随系统' }
33
- ] as const;
34
-
35
- const colorNames = ['默认', '紫罗兰', '樱花粉', '柠檬黄', '天蓝色', '浅绿色', '中性色', '自定义'];
36
- const themeColorList = [
37
- ...THEME_COLORS.map((color, index) => ({ color, name: colorNames[index], isCustom: false })),
38
- { color: themeColor, name: '自定义', isCustom: true }
39
- ] as const;
40
-
41
- // 处理主题切换
42
- const handleToggleTheme = (mode: 'light' | 'dark' | 'system') => {
43
- if(theme === mode) return;
44
- useThemeStore.getState().SET_THEME(mode);
45
- };
46
-
47
- return (
48
- <Drawer
49
- title={
50
- <div className="flex items-end">
51
- 偏好设置
52
- <span className="text-xs text-[#71717a] dark:text-[#a1a1aa] mb-[2px]">
53
- 自定义偏好设置 & 实时预览
54
- </span>
55
- </div>
56
- }
57
- placement="right"
58
- onClose={onClose}
59
- open={open}
60
- width={380}
61
- closable={false}
62
- maskStyle={{ backdropFilter: 'none' }}
63
- className="dark:bg-[#1a1c1f]!"
64
- extra={
65
- <div className="cursor-pointer hover:text-primary transition-colors" onClick={onClose}>
66
- <CloseOutlined className="text-sm" />
67
- </div>
68
- }
69
- styles={{
70
- header: {
71
- padding: '18px',
72
- borderBottom: '1px solid var(--border-color)'
73
- },
74
- body: {
75
- padding: '18px'
76
- }
77
- }}
78
- >
79
- {/* 主题模块 */}
80
- <div className="mb-8">
81
- <h3 className="text-base font-medium mb-4 text-(--text-color)">主题</h3>
82
- <div className="grid grid-cols-3 gap-6">
83
- {list.map(({ mode, icon: Icon, label }) => {
84
- const isSelected = theme === mode;
85
-
86
- return (
87
- <div
88
- key={mode}
89
- className="group flex flex-col items-center gap-2 cursor-pointer"
90
- onClick={() => handleToggleTheme(mode)}
91
- >
92
- <div className={`w-full aspect-[2] rounded-lg outline flex items-center justify-center transition-all relative overflow-hidden ${isSelected ? 'outline-(--color-primary) outline-2' : 'outline-[#e4e4e7] dark:outline-[#36363a]'}`}>
93
- {
94
- !isSelected && <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
95
- <div className="w-full h-full rounded-lg border-2 border-(--color-primary) opacity-0 scale-0 transition-all duration-300 group-hover:opacity-100 group-hover:scale-100" />
96
- </div>
97
- }
98
- <Icon className="text-xl text-[#2d2f32] dark:text-[#f0f0f0] relative z-10" />
99
- </div>
100
- <span className="text-xs text-[#71717a] dark:text-[#a1a1aa]">{label}</span>
101
- </div>
102
- );
103
- })}
104
- </div>
105
- </div>
106
-
107
- {/* 内置主题模块 */}
108
- <div>
109
- <h3 className="text-base font-medium mb-4 text-(--text-color)">内置主题</h3>
110
- <div className="grid grid-cols-3 gap-6">
111
- {themeColorList.map((item) => {
112
- const isSelected = themeColor === item.color && !isCustomThemeColor;
113
-
114
- return (
115
- !item.isCustom ?
116
- <div
117
- key={item.color}
118
- className="cursor-pointer group flex flex-col items-center gap-2"
119
- onClick={() => {
120
- SET_THEME_COLOR(item.color);
121
- SET_IS_CUSTOM_THEME_COLOR(false);
122
- }}
123
- >
124
- <div className={`w-full aspect-[2] rounded-lg outline flex items-center justify-center transition-all relative overflow-hidden ${isSelected ? 'outline-(--color-primary) outline-2' : 'outline-[#e4e4e7] dark:outline-[#36363a]'}`}>
125
- {/* 未选中时显示hover效果 */}
126
- {
127
- !isSelected && <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
128
- <div className="w-full h-full rounded-lg border-2 border-(--color-primary) opacity-0 scale-0 transition-all duration-300 group-hover:opacity-100 group-hover:scale-100" />
129
- </div>
130
- }
131
-
132
- {/* 中心色块 */}
133
- <div
134
- className="w-5 h-5 rounded-md shadow-sm relative z-10"
135
- style={{ backgroundColor: item.color }}
136
- />
137
- </div>
138
- <span className="text-xs text-[#71717a] dark:text-[#a1a1aa]">{item.name}</span>
139
- </div>
140
- :
141
- <div
142
- key={`custom-${item.color}`}
143
- className="cursor-pointer group flex flex-col items-center gap-2"
144
- onClick={() => SET_IS_CUSTOM_THEME_COLOR(true)}
145
- >
146
- <div className={`w-full aspect-[2] rounded-lg outline flex items-center justify-center transition-all relative overflow-hidden ${isCustomThemeColor ? 'outline-(--color-primary) outline-2' : 'outline-[#e4e4e7] dark:outline-[#36363a]'}`}>
147
- {/* 未选中时显示hover效果 */}
148
- {
149
- !isCustomThemeColor && <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
150
- <div className="w-full h-full rounded-lg border-2 border-(--color-primary) opacity-0 scale-0 transition-all duration-300 group-hover:opacity-100 group-hover:scale-100" />
151
- </div>
152
- }
153
-
154
- {/* 中心色块 */}
155
- <SettingCustomColor />
156
- </div>
157
- <span className="text-xs text-[#71717a] dark:text-[#a1a1aa]">{item.name}</span>
158
- </div>
159
- );
160
- })}
161
- </div>
162
- </div>
163
- </Drawer>
164
- );
165
- }
@@ -1,88 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-27
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-02-01 01:49:11
6
- * @description: 侧边栏组件
7
- */
8
- import { Layout, Menu, type MenuProps } from 'antd';
9
- import { useEffect, useMemo, useState } from 'react';
10
- import { MdOutlineKeyboardDoubleArrowLeft, MdOutlineKeyboardDoubleArrowRight } from 'react-icons/md';
11
-
12
- import type { MenuItem } from '@/router';
13
- import { renderIcon } from '@/utils';
14
-
15
- const { Sider } = Layout;
16
-
17
- interface SidebarProps {
18
- collapsed: boolean
19
- menus: MenuItem[]
20
- activeKey: string
21
- openKeys?: string[]
22
- onMenuClick: (key: string) => void
23
- onCollapse: (collapsed: boolean) => void
24
- }
25
-
26
- export default function Sidebar(props: SidebarProps) {
27
- const { collapsed, menus, activeKey, openKeys: initialOpenKeys = [], onMenuClick, onCollapse } = props;
28
-
29
- // 使用内部状态管理 openKeys,使其成为非受控变量
30
- const [openKeys, setOpenKeys] = useState<string[]>(initialOpenKeys);
31
-
32
- // 当初始 openKeys 变化时(如路由变化),更新内部状态
33
- useEffect(() => {
34
- if(initialOpenKeys.length > 0) {
35
- setOpenKeys(initialOpenKeys);
36
- }
37
- }, [initialOpenKeys]);
38
-
39
- // 转换菜单数据为 antd Menu 格式
40
- const menuItems: MenuProps['items'] = useMemo(() => {
41
- const convertMenu = (menuList: MenuItem[]): MenuProps['items'] => {
42
- return menuList.map(menu => ({
43
- key: menu.key,
44
- label: menu.label,
45
- icon: renderIcon(menu.icon),
46
- children: menu.children ? convertMenu(menu.children) : undefined
47
- }));
48
- };
49
- return convertMenu(menus);
50
- }, [menus]);
51
-
52
- return (
53
- <Sider
54
- trigger={null}
55
- collapsible
56
- collapsed={collapsed}
57
- width={224}
58
- className="layout-sidebar border-r border-(--border-color) bg-(--global-background-color)! h-full relative"
59
- theme="light"
60
- >
61
- <div className="h-full flex flex-col">
62
- {/* 菜单区域 */}
63
- <div className="flex-1 overflow-y-auto overflow-x-hidden py-2 custom-scrollbar">
64
- <Menu
65
- mode="inline"
66
- selectedKeys={[activeKey]}
67
- openKeys={openKeys}
68
- onOpenChange={setOpenKeys}
69
- items={menuItems}
70
- onClick={({ key }) => onMenuClick(key)}
71
- className="sidebar-menu border-none"
72
- inlineIndent={16}
73
- />
74
- </div>
75
-
76
- {/* 底部折叠按钮 */}
77
- <div className="flex items-center justify-start pl-4 mb-2">
78
- <div
79
- onClick={() => onCollapse(!collapsed)}
80
- className="text-gray-500 dark:text-[#a1a1a2] text-xl hover:text-[#323639] dark:hover:text-[#f0f0f0] hover:bg-[#dfe3e4] dark:hover:bg-[#333538] transition-all duration-300 w-7 h-7 rounded-sm bg-[#f4f4f5] dark:bg-[#292a2d] cursor-pointer flex items-center justify-center"
81
- >
82
- {collapsed ? <MdOutlineKeyboardDoubleArrowRight /> : <MdOutlineKeyboardDoubleArrowLeft />}
83
- </div>
84
- </div>
85
- </div>
86
- </Sider>
87
- );
88
- }
@@ -1,94 +0,0 @@
1
- /*
2
- * @Author: dushuai
3
- * @Date: 2026-01-27
4
- * @LastEditors: dushuai
5
- * @LastEditTime: 2026-02-01 02:00:18
6
- * @description: 标签页组件
7
- */
8
- import { classnames } from '@spacego/turbo-utils';
9
- import { Divider } from 'antd';
10
- import { useEffect, useRef, useState } from 'react';
11
- import { Fragment } from 'react/jsx-runtime';
12
- import { MdClear } from 'react-icons/md';
13
-
14
- import ChromeBgAfter from '@/assets/svg/chrome-bg-after.svg?react';
15
- import ChromeBgBefore from '@/assets/svg/chrome-bg-before.svg?react';
16
- import type { TabItem } from '@/store/modules/layout-config.store';
17
-
18
- interface TabsComponentProps {
19
- tabs: TabItem[]
20
- activeKey: string
21
- onChange: (key: string) => void
22
- onClose: (key: string) => void
23
- }
24
-
25
- export default function TabsComponent(props: TabsComponentProps) {
26
- const { tabs, activeKey, onChange, onClose } = props;
27
-
28
- const containerRef = useRef<HTMLDivElement>(null);
29
- const [hasScroll, setHasScroll] = useState(false);
30
-
31
- // 检查是否出现滚动条
32
- const checkScroll = () => {
33
- const container = containerRef.current;
34
- if(container) {
35
- // 如果 scrollWidth 大于 clientWidth,说明有内容溢出,出现了横向滚动条
36
- const isScroll = container.scrollWidth > container.clientWidth;
37
- setHasScroll(isScroll);
38
- }
39
- };
40
-
41
- useEffect(() => {
42
- checkScroll();
43
- window.addEventListener('resize', checkScroll);
44
- return () => {
45
- window.removeEventListener('resize', checkScroll);
46
- };
47
- }, [tabs]);
48
-
49
- return (
50
- <div
51
- ref={containerRef}
52
- className={classnames('layout-tabs bg-(--global-background-color) border-b border-(--border-color) overflow-x-auto overflow-y-hidden relative transition-all duration-300 shrink-0', {
53
- 'h-[38px]': !hasScroll,
54
- // 出现滚动条时增加高度,留出滚动条的空间,防止挤压内容
55
- 'h-[46px]': hasScroll
56
- })}
57
- >
58
- <div className="flex items-center h-[38px] px-2 pt-1 min-w-fit">
59
- {
60
- tabs.map((item, index) => {
61
- return <Fragment key={`${item.key}-${index}`}>
62
- <div
63
- key={`${item.key}-${index}`}
64
- className={classnames('layout-tabs-item group cursor-pointer px-[16px] h-full flex items-center justify-center rounded-tl-[8px] rounded-tr-[8px] transition-all duration-500 relative hover:bg-[#f4f4f5] dark:hover:bg-[#292a2d] shrink-0', {
65
- 'bg-(--background-primary-light)! text-(--color-primary) dark:text-[#f9f9f9]! is-active': activeKey === item.key
66
- })}
67
- onClick={() => onChange(item.key)}
68
- >
69
- <ChromeBgBefore />
70
- {item.label}
71
- <ChromeBgAfter />
72
-
73
- {
74
- item.closable !== false && <div
75
- className="w-4 h-4 flex ml-1 mr-[-10px] items-center justify-center text-[#18181bcc] hover:text-[#18181b] hover:bg-[#f4f4f5] dark:hover:bg-transparent rounded-full transition-all duration-300"
76
- onClick={e => {
77
- e.stopPropagation();
78
- onClose(item.key);
79
- }}
80
- >
81
- <MdClear className="text-xs text-[#333333] dark:text-[#cfd0d0] hover:text-black dark:hover:text-white transition-all" />
82
- </div>
83
- }
84
- </div>
85
- {
86
- index !== tabs.length - 1 && <Divider orientation="vertical" className="mx-1! shrink-0" />
87
- }
88
- </Fragment>;
89
- })
90
- }
91
- </div>
92
- </div>
93
- );
94
- }