zt-admin-template 1.0.0

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 (80) hide show
  1. package/package.json +11 -0
  2. package/template/.env.development +2 -0
  3. package/template/.env.production +2 -0
  4. package/template/.env.test +2 -0
  5. package/template/.kiro/specs/course-backend-integration/.config.kiro +1 -0
  6. package/template/.kiro/specs/course-backend-integration/design.md +234 -0
  7. package/template/.kiro/specs/course-backend-integration/requirements.md +116 -0
  8. package/template/.kiro/specs/course-backend-integration/tasks.md +0 -0
  9. package/template/COMPLETION_CHECKLIST.md +305 -0
  10. package/template/DEPLOYMENT_GUIDE.md +391 -0
  11. package/template/FINAL_SUMMARY.md +428 -0
  12. package/template/IMPLEMENTATION_SUMMARY.md +382 -0
  13. package/template/INTEGRATION_GUIDE.md +458 -0
  14. package/template/PROJECT_OVERVIEW.md +343 -0
  15. package/template/QUICK_START.md +273 -0
  16. package/template/RBAC_Tutorial.md +424 -0
  17. package/template/README.md +16 -0
  18. package/template/React_Antd_TS_Tutorial.md +279 -0
  19. package/template/START_ALL.md +163 -0
  20. package/template/SYSTEM_MANAGEMENT.md +247 -0
  21. package/template/eslint.config.js +29 -0
  22. package/template/index.html +13 -0
  23. package/template/koa-server/README.md +65 -0
  24. package/template/koa-server/app.js +625 -0
  25. package/template/koa-server/package-lock.json +1547 -0
  26. package/template/koa-server/package.json +26 -0
  27. package/template/koa-server/public/assets/index-B1Cj4mG9.css +1 -0
  28. package/template/koa-server/public/assets/index-Mgxg-xqT.js +503 -0
  29. package/template/koa-server/public/favicon.svg +1 -0
  30. package/template/koa-server/public/icons.svg +24 -0
  31. package/template/koa-server/public/index.html +14 -0
  32. package/template/koa-server/uploads/1774265088480-962006467.png +0 -0
  33. package/template/koa-server/uploads/file-1774346891704-610962013.png +0 -0
  34. package/template/koa-server/uploads/file-1774346898887-58636533.png +0 -0
  35. package/template/koa-server/uploads/file-1774346912676-771862547.png +0 -0
  36. package/template/koa-server/uploads/file-1774347025308-130037894.png +0 -0
  37. package/template/koa-server/uploads/file-1774347031104-766499773.png +0 -0
  38. package/template/koa-server/uploads/file-1774347094969-731402203.png +0 -0
  39. package/template/koa-server/uploads/file-1774347101948-330296656.png +0 -0
  40. package/template/koa-server/uploads/file-1774351682377-932868720.png +0 -0
  41. package/template/koa-server/uploads/file-1774352037654-877426905.png +0 -0
  42. package/template/koa-server/uploads/file-1774352175463-386248997.png +0 -0
  43. package/template/koa-server/uploads/file-1774361446433-405859961.png +0 -0
  44. package/template/koa-server/uploads/file-1774361512207-465806267.png +0 -0
  45. package/template/lianxi.html +15 -0
  46. package/template/package-lock.json +6307 -0
  47. package/template/package.json +36 -0
  48. package/template/public/favicon.svg +1 -0
  49. package/template/public/icons.svg +24 -0
  50. package/template/src/App.css +184 -0
  51. package/template/src/App.tsx +44 -0
  52. package/template/src/api/course.ts +86 -0
  53. package/template/src/api/menu.ts +55 -0
  54. package/template/src/api/role.ts +58 -0
  55. package/template/src/api/user.ts +58 -0
  56. package/template/src/assets/hero.png +0 -0
  57. package/template/src/assets/react.svg +1 -0
  58. package/template/src/assets/vite.svg +1 -0
  59. package/template/src/components/Child.tsx +10 -0
  60. package/template/src/components/MainLayout.tsx +169 -0
  61. package/template/src/components/SunZi.tsx +13 -0
  62. package/template/src/contexts/ThemeContext.tsx +33 -0
  63. package/template/src/hooks/usePermission.tsx +62 -0
  64. package/template/src/index.css +111 -0
  65. package/template/src/main.tsx +13 -0
  66. package/template/src/pages/Dashboard.tsx +39 -0
  67. package/template/src/pages/Users.tsx +95 -0
  68. package/template/src/pages/banner/BannerList.tsx +182 -0
  69. package/template/src/pages/course/Course.tsx +586 -0
  70. package/template/src/pages/course/CourseList.tsx +168 -0
  71. package/template/src/pages/system/menu/Menu.tsx +501 -0
  72. package/template/src/pages/system/role/Role.tsx +458 -0
  73. package/template/src/pages/system/user/User.tsx +364 -0
  74. package/template/src/types/permission.ts +21 -0
  75. package/template/src/utils/request.tsx +94 -0
  76. package/template/src/vite-env.d.ts +1 -0
  77. package/template/tsconfig.app.json +32 -0
  78. package/template/tsconfig.json +7 -0
  79. package/template/tsconfig.node.json +13 -0
  80. package/template/vite.config.ts +30 -0
@@ -0,0 +1,169 @@
1
+ import React, { useState } from 'react';
2
+ import { Layout, Menu, Breadcrumb, theme } from 'antd';
3
+ import {
4
+ PieChartOutlined,
5
+ UserOutlined,
6
+ BookOutlined,
7
+ PictureOutlined,
8
+ } from '@ant-design/icons';
9
+ import { Outlet, useNavigate, useLocation } from 'react-router-dom';
10
+ import type { MenuProps } from 'antd';
11
+ import { usePermission } from '../hooks/usePermission';
12
+
13
+ const { Header, Content, Footer, Sider } = Layout;
14
+
15
+
16
+ const items: any[] = [
17
+ {
18
+ key: "/",
19
+ icon: <PieChartOutlined />,
20
+ label: "Dashboard",
21
+ permission: "admin"
22
+ },
23
+ {
24
+ key: "system",
25
+ label: "系统管理",
26
+ icon: <UserOutlined />,
27
+ permission: "admin",
28
+ children: [
29
+ {
30
+ label: "用户管理",
31
+ key: "/system/user",
32
+ permission: "admin",
33
+ },
34
+ {
35
+ label: "角色管理",
36
+ key: "/system/role",
37
+ permission: "admin",
38
+ },
39
+ {
40
+ label: "菜单管理",
41
+ key: "/system/menu",
42
+ permission: "admin",
43
+ }
44
+ ]
45
+ },
46
+ {
47
+ key: "course",
48
+ label: "课程管理",
49
+ icon: <BookOutlined />,
50
+ permission: "admin",
51
+ children: [
52
+ {
53
+ label: "添加课程",
54
+ key: "/course/add",
55
+ permission: "admin",
56
+ },
57
+ {
58
+ label: "课程列表",
59
+ key: "/course/list",
60
+ permission: "admin",
61
+ }
62
+ ]
63
+ },
64
+ {
65
+ key: "banner",
66
+ label: "Banner管理",
67
+ icon: <PictureOutlined />,
68
+ permission: "admin",
69
+ children: [
70
+ {
71
+ label: "Banner列表",
72
+ key: "/banner/list",
73
+ permission: "admin",
74
+ }
75
+ ]
76
+ }
77
+ ];
78
+
79
+ // 过滤用户拥有的菜单
80
+
81
+
82
+ const MainLayout: React.FC = () => {
83
+ const [collapsed, setCollapsed] = useState(false);
84
+ const {
85
+ token: { colorBgContainer, borderRadiusLG },
86
+ } = theme.useToken();
87
+ const navigate = useNavigate();
88
+ const location = useLocation();
89
+
90
+ const handleMenuClick: MenuProps['onClick'] = ({ key }) => {
91
+ if (key.startsWith('/')) {
92
+ navigate(key);
93
+ }
94
+ };
95
+
96
+
97
+
98
+ //2. 从所有菜单中过滤出当前用户能看到的
99
+ const { filterMenu } = usePermission()
100
+ const visibleMenus = filterMenu(items);
101
+ console.log(visibleMenus);
102
+
103
+ console.log("--------------");
104
+
105
+
106
+
107
+ return (
108
+ <Layout
109
+ style={{
110
+ minWidth: "100vw",
111
+ minHeight: '100vh',
112
+ }}
113
+ >
114
+ <Sider collapsible collapsed={collapsed} onCollapse={(value) => setCollapsed(value)}>
115
+ <div className="demo-logo-vertical" style={{ height: 32, margin: 16, background: 'rgba(255, 255, 255, 0.2)' }} />
116
+ <Menu
117
+ theme="dark"
118
+ defaultSelectedKeys={['/']}
119
+ selectedKeys={[location.pathname]}
120
+ mode="inline"
121
+ items={visibleMenus}
122
+ onClick={handleMenuClick}
123
+ />
124
+ </Sider>
125
+ <Layout>
126
+ <Header
127
+ style={{
128
+ padding: 0,
129
+ background: colorBgContainer,
130
+ }}
131
+ />
132
+ <Content
133
+ style={{
134
+ margin: '0 16px',
135
+ }}
136
+ >
137
+ <Breadcrumb
138
+ style={{
139
+ margin: '16px 0',
140
+ }}
141
+ items={[
142
+ { title: 'User' },
143
+ { title: 'Bill' },
144
+ ]}
145
+ />
146
+ <div
147
+ style={{
148
+ padding: 24,
149
+ minHeight: 360,
150
+ background: colorBgContainer,
151
+ borderRadius: borderRadiusLG,
152
+ }}
153
+ >
154
+ <Outlet />
155
+ </div>
156
+ </Content>
157
+ <Footer
158
+ style={{
159
+ textAlign: 'center',
160
+ }}
161
+ >
162
+ Ant Design ©{new Date().getFullYear()} Created by Ant UED
163
+ </Footer>
164
+ </Layout>
165
+ </Layout>
166
+ );
167
+ };
168
+
169
+ export default MainLayout;
@@ -0,0 +1,13 @@
1
+ import { useTheme } from '../contexts/ThemeContext'
2
+
3
+ export default function SunZi() {
4
+
5
+ const {theme, toggleTheme} = useTheme()
6
+
7
+ return (
8
+ <div>
9
+ 孙子组件:{theme}
10
+ <button onClick={toggleTheme} style={{ marginLeft: '10px' }}>切换主题</button>
11
+ </div>
12
+ )
13
+ }
@@ -0,0 +1,33 @@
1
+ import { createContext, useContext, useState, ReactNode } from "react";
2
+
3
+ interface ThemeContextType {
4
+ theme: string;
5
+ toggleTheme: () => void;
6
+ }
7
+
8
+ const ThemeContext = createContext<ThemeContextType>({
9
+ theme: "light",
10
+ toggleTheme: () => { }
11
+ })
12
+
13
+ interface ThemeProviderProps {
14
+ children: ReactNode;
15
+ }
16
+
17
+ export function ThemeProvider({ children }: ThemeProviderProps) {
18
+ const [theme, setTheme] = useState<string>("light");
19
+
20
+ const toggleTheme = () => {
21
+ setTheme(prev => prev === "light" ? "dark" : "light");
22
+ }
23
+
24
+ return (
25
+ <ThemeContext.Provider value={{ theme, toggleTheme }}>
26
+ {children}
27
+ </ThemeContext.Provider>
28
+ );
29
+ }
30
+
31
+ export function useTheme(){
32
+ return useContext(ThemeContext)
33
+ }
@@ -0,0 +1,62 @@
1
+ import React, { createContext, useContext, useMemo } from "react";
2
+ import { AuthRouteConfig, PermissionCode, UserPermission } from "../types/permission";
3
+
4
+
5
+ export const PermissionContext = createContext<UserPermission | null>(null);
6
+
7
+ // 导出一个权限Provider,通过这个Provider给后代组件传递数据
8
+ export const PermissionProvider: React.FC<{
9
+ children: React.ReactNode,
10
+ permission: UserPermission
11
+ }> = ({ children, permission }) => {
12
+ return (
13
+ <PermissionContext.Provider value={permission}>
14
+ {children}
15
+ </PermissionContext.Provider>
16
+ );
17
+ }
18
+
19
+ // 封装一个权限校验的hook
20
+ export const usePermission = () => {
21
+ //1. 先取出用户拥有的权限列表
22
+ const context = useContext(PermissionContext);
23
+
24
+ //2. 封装权限校验的函数
25
+ const hasPermission = (code: PermissionCode) => {
26
+ return context?.permissions.includes(code)
27
+ }
28
+
29
+ //3. 过滤带权限的菜单
30
+ const filterMenu = useMemo(() => {
31
+ const filter = (menus: any[]) => {
32
+ // 遍历所有的菜单
33
+ return menus.filter((menu: AuthRouteConfig) => {
34
+ // 如果菜单是隐藏的,就不显示
35
+ if (menu.hidden) return false;
36
+
37
+ if (!menu.permission) return true;
38
+
39
+ // 执行到这里,说明有权限标识符,判断一下是否有权限
40
+ const hasMenuPermission = hasPermission(menu.permission)
41
+
42
+ // 还需要递归遍历子菜单
43
+ if (menu.children && menu.children.length > 0) {
44
+ menu.children = filter(menu.children)
45
+ }
46
+
47
+ return hasMenuPermission;
48
+ })
49
+
50
+ }
51
+
52
+ return filter;
53
+ }, [hasPermission])
54
+
55
+
56
+ return {
57
+ hasPermission,
58
+ filterMenu,
59
+ context
60
+ }
61
+
62
+ }
@@ -0,0 +1,111 @@
1
+ :root {
2
+ --text: #6b6375;
3
+ --text-h: #08060d;
4
+ --bg: #fff;
5
+ --border: #e5e4e7;
6
+ --code-bg: #f4f3ec;
7
+ --accent: #aa3bff;
8
+ --accent-bg: rgba(170, 59, 255, 0.1);
9
+ --accent-border: rgba(170, 59, 255, 0.5);
10
+ --social-bg: rgba(244, 243, 236, 0.5);
11
+ --shadow:
12
+ rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
13
+
14
+ --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
15
+ --heading: system-ui, 'Segoe UI', Roboto, sans-serif;
16
+ --mono: ui-monospace, Consolas, monospace;
17
+
18
+ font: 18px/145% var(--sans);
19
+ letter-spacing: 0.18px;
20
+ color-scheme: light dark;
21
+ color: var(--text);
22
+ background: var(--bg);
23
+ font-synthesis: none;
24
+ text-rendering: optimizeLegibility;
25
+ -webkit-font-smoothing: antialiased;
26
+ -moz-osx-font-smoothing: grayscale;
27
+
28
+ @media (max-width: 1024px) {
29
+ font-size: 16px;
30
+ }
31
+ }
32
+
33
+ @media (prefers-color-scheme: dark) {
34
+ :root {
35
+ --text: #9ca3af;
36
+ --text-h: #f3f4f6;
37
+ --bg: #16171d;
38
+ --border: #2e303a;
39
+ --code-bg: #1f2028;
40
+ --accent: #c084fc;
41
+ --accent-bg: rgba(192, 132, 252, 0.15);
42
+ --accent-border: rgba(192, 132, 252, 0.5);
43
+ --social-bg: rgba(47, 48, 58, 0.5);
44
+ --shadow:
45
+ rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
46
+ }
47
+
48
+ #social .button-icon {
49
+ filter: invert(1) brightness(2);
50
+ }
51
+ }
52
+
53
+ body {
54
+ margin: 0;
55
+ }
56
+
57
+ #root {
58
+ box-sizing: border-box;
59
+ max-width: 100%;
60
+ margin: 0 auto;
61
+ text-align: center;
62
+ border-inline: 1px solid var(--border);
63
+ min-height: 100svh;
64
+ display: flex;
65
+ flex-direction: column;
66
+ box-sizing: border-box;
67
+ }
68
+
69
+ h1,
70
+ h2 {
71
+ font-family: var(--heading);
72
+ font-weight: 500;
73
+ color: var(--text-h);
74
+ }
75
+
76
+ h1 {
77
+ font-size: 56px;
78
+ letter-spacing: -1.68px;
79
+ margin: 32px 0;
80
+ @media (max-width: 1024px) {
81
+ font-size: 36px;
82
+ margin: 20px 0;
83
+ }
84
+ }
85
+ h2 {
86
+ font-size: 24px;
87
+ line-height: 118%;
88
+ letter-spacing: -0.24px;
89
+ margin: 0 0 8px;
90
+ @media (max-width: 1024px) {
91
+ font-size: 20px;
92
+ }
93
+ }
94
+ p {
95
+ margin: 0;
96
+ }
97
+
98
+ code,
99
+ .counter {
100
+ font-family: var(--mono);
101
+ display: inline-flex;
102
+ border-radius: 4px;
103
+ color: var(--text-h);
104
+ }
105
+
106
+ code {
107
+ font-size: 15px;
108
+ line-height: 135%;
109
+ padding: 4px 8px;
110
+ background: var(--code-bg);
111
+ }
@@ -0,0 +1,13 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { BrowserRouter } from 'react-router-dom'
4
+ import './index.css'
5
+ import App from './App'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <BrowserRouter>
10
+ <App />
11
+ </BrowserRouter>
12
+ </StrictMode>
13
+ )
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Card, Row, Col, Statistic } from 'antd';
3
+ import { ArrowUpOutlined, ArrowDownOutlined } from '@ant-design/icons';
4
+
5
+ const Dashboard: React.FC = () => {
6
+ return (
7
+ <div>
8
+ <h2>Dashboard</h2>
9
+ <Row gutter={16}>
10
+ <Col span={12}>
11
+ <Card>
12
+ <Statistic
13
+ title="Active Users"
14
+ value={112893}
15
+ precision={2}
16
+ valueStyle={{ color: '#3f8600' }}
17
+ prefix={<ArrowUpOutlined />}
18
+ suffix="%"
19
+ />
20
+ </Card>
21
+ </Col>
22
+ <Col span={12}>
23
+ <Card>
24
+ <Statistic
25
+ title="Idle Users"
26
+ value={9.3}
27
+ precision={2}
28
+ valueStyle={{ color: '#cf1322' }}
29
+ prefix={<ArrowDownOutlined />}
30
+ suffix="%"
31
+ />
32
+ </Card>
33
+ </Col>
34
+ </Row>
35
+ </div>
36
+ );
37
+ };
38
+
39
+ export default Dashboard;
@@ -0,0 +1,95 @@
1
+ import React from 'react';
2
+ import { Table, Tag, Space } from 'antd';
3
+ import type { ColumnsType } from 'antd/es/table';
4
+
5
+ interface DataType {
6
+ key: string;
7
+ name: string;
8
+ age: number;
9
+ address: string;
10
+ tags: string[];
11
+ }
12
+
13
+ const columns: ColumnsType<DataType> = [
14
+ {
15
+ title: 'Name',
16
+ dataIndex: 'name',
17
+ key: 'name',
18
+ render: (text) => <a>{text}</a>,
19
+ },
20
+ {
21
+ title: 'Age',
22
+ dataIndex: 'age',
23
+ key: 'age',
24
+ },
25
+ {
26
+ title: 'Address',
27
+ dataIndex: 'address',
28
+ key: 'address',
29
+ },
30
+ {
31
+ title: 'Tags',
32
+ key: 'tags',
33
+ dataIndex: 'tags',
34
+ render: (_, { tags }) => (
35
+ <>
36
+ {tags.map((tag) => {
37
+ let color = tag.length > 5 ? 'geekblue' : 'green';
38
+ if (tag === 'loser') {
39
+ color = 'volcano';
40
+ }
41
+ return (
42
+ <Tag color={color} key={tag}>
43
+ {tag.toUpperCase()}
44
+ </Tag>
45
+ );
46
+ })}
47
+ </>
48
+ ),
49
+ },
50
+ {
51
+ title: 'Action',
52
+ key: 'action',
53
+ render: (_, record) => (
54
+ <Space size="middle">
55
+ <a>Invite {record.name}</a>
56
+ <a>Delete</a>
57
+ </Space>
58
+ ),
59
+ },
60
+ ];
61
+
62
+ const data: DataType[] = [
63
+ {
64
+ key: '1',
65
+ name: 'John Brown',
66
+ age: 32,
67
+ address: 'New York No. 1 Lake Park',
68
+ tags: ['nice', 'developer'],
69
+ },
70
+ {
71
+ key: '2',
72
+ name: 'Jim Green',
73
+ age: 42,
74
+ address: 'London No. 1 Lake Park',
75
+ tags: ['loser'],
76
+ },
77
+ {
78
+ key: '3',
79
+ name: 'Joe Black',
80
+ age: 32,
81
+ address: 'Sidney No. 1 Lake Park',
82
+ tags: ['cool', 'teacher'],
83
+ },
84
+ ];
85
+
86
+ const Users: React.FC = () => {
87
+ return (
88
+ <div>
89
+ <h2>Users Management</h2>
90
+ <Table columns={columns} dataSource={data} />
91
+ </div>
92
+ );
93
+ };
94
+
95
+ export default Users;