@zhin.js/client 1.0.0 → 1.0.2
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.
- package/README.md +334 -67
- package/app/index.html +4 -3
- package/app/postcss.config.js +5 -0
- package/app/src/components/ThemeToggle.tsx +21 -0
- package/app/src/hooks/useTheme.ts +17 -0
- package/app/src/layouts/dashboard.tsx +259 -0
- package/app/src/main.tsx +121 -0
- package/app/src/pages/dashboard-bots.tsx +198 -0
- package/app/src/pages/dashboard-home.tsx +301 -0
- package/app/src/pages/dashboard-logs.tsx +298 -0
- package/app/src/pages/dashboard-plugin-detail.tsx +360 -0
- package/app/src/pages/dashboard-plugins.tsx +166 -0
- package/app/src/style.css +1105 -0
- package/app/src/theme/index.ts +92 -0
- package/app/tailwind.config.js +70 -0
- package/app/tsconfig.json +5 -0
- package/dist/index.js +15 -3
- package/package.json +20 -7
- package/src/index.ts +19 -3
- package/src/router/index.tsx +55 -0
- package/src/store/index.ts +111 -0
- package/src/store/reducers/index.ts +16 -0
- package/src/store/reducers/route.ts +122 -0
- package/src/store/reducers/script.ts +103 -0
- package/src/store/reducers/ui.ts +31 -0
- package/src/types.ts +11 -17
- package/src/websocket/index.ts +193 -0
- package/src/websocket/useWebSocket.ts +42 -0
- package/app/components.d.ts +0 -33
- package/app/src/App.vue +0 -7
- package/app/src/main.ts +0 -127
- package/app/src/pages/$.vue +0 -899
- package/app/src/pages/404.vue +0 -11
- package/app/src/pages/contexts/overview.vue +0 -177
- package/app/src/pages/dashboard.vue +0 -323
- package/app/src/pages/plugins/installed.vue +0 -734
- package/app/src/pages/system/status.vue +0 -241
- package/app/src/services/api.ts +0 -155
- package/app/src/styles/README.md +0 -202
- package/app/src/styles/common.css +0 -0
- package/global.d.ts +0 -19
- package/src/router.ts +0 -44
- package/src/store.ts +0 -53
package/README.md
CHANGED
|
@@ -1,99 +1,366 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Zhin Client - 动态页面路由系统
|
|
2
2
|
|
|
3
|
-
基于
|
|
3
|
+
基于 React Router 7.0 的动态页面管理系统,支持在 `main.tsx` 中直接进行页面路由操作。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 特性
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
## 技术栈
|
|
15
|
-
|
|
16
|
-
- **前端框架**: Vue 3 + TypeScript
|
|
17
|
-
- **UI 组件**: PrimeVue 4.x
|
|
18
|
-
- **状态管理**: Pinia
|
|
19
|
-
- **路由**: Vue Router
|
|
20
|
-
- **构建工具**: Vite
|
|
21
|
-
- **主题**: PrimeUI 主题系统
|
|
7
|
+
- 🌳 **树形路由结构** - 使用树形结构管理页面路由,支持任意深度的嵌套
|
|
8
|
+
- ✅ **动态页面管理** - 运行时添加、删除、更新页面
|
|
9
|
+
- ✅ **React Router 7.0** - 使用最新的 React Router
|
|
10
|
+
- ✅ **TypeScript 支持** - 完整的类型定义
|
|
11
|
+
- ✅ **WebSocket 集成** - 支持动态加载插件入口脚本
|
|
12
|
+
- ✅ **Redux 状态管理** - 集成 Redux 持久化
|
|
13
|
+
- ✅ **简单易用** - 在 `main.tsx` 中直接操作页面路由
|
|
22
14
|
|
|
23
15
|
## 安装
|
|
24
16
|
|
|
25
17
|
```bash
|
|
26
|
-
|
|
18
|
+
pnpm add react-router@7.0.0 events @types/events
|
|
27
19
|
```
|
|
28
20
|
|
|
29
|
-
##
|
|
21
|
+
## 基本使用
|
|
22
|
+
|
|
23
|
+
### 1. 设置页面路由
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// main.tsx
|
|
27
|
+
import { addPage, DynamicRouter } from '@zhin.js/client'
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
// 导入图标
|
|
30
|
+
import { Home, LayoutDashboard } from 'lucide-react'
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
// 添加页面
|
|
33
|
+
addPage({
|
|
34
|
+
key: 'home',
|
|
35
|
+
path: '/',
|
|
36
|
+
title: '首页',
|
|
37
|
+
icon: <Home className="w-5 h-5" />,
|
|
38
|
+
element: <HomePage />
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
addPage({
|
|
42
|
+
key: 'dashboard',
|
|
43
|
+
path: '/dashboard',
|
|
44
|
+
title: '仪表盘',
|
|
45
|
+
icon: <LayoutDashboard className="w-5 h-5" />,
|
|
46
|
+
element: <DashboardPage />
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// 渲染应用
|
|
50
|
+
createRoot(document.getElementById('root')).render(
|
|
51
|
+
<Provider store={store}>
|
|
52
|
+
<PersistGate loading={null} persistor={persistor}>
|
|
53
|
+
<DynamicRouter />
|
|
54
|
+
</PersistGate>
|
|
55
|
+
</Provider>
|
|
56
|
+
)
|
|
36
57
|
```
|
|
37
58
|
|
|
38
|
-
|
|
59
|
+
### 2. 页面操作
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { addPage, removePage, updatePage } from '@zhin.js/client'
|
|
63
|
+
|
|
64
|
+
import { Settings } from 'lucide-react'
|
|
39
65
|
|
|
40
|
-
|
|
66
|
+
// 添加页面
|
|
67
|
+
addPage({
|
|
68
|
+
key: 'settings',
|
|
69
|
+
path: '/settings',
|
|
70
|
+
title: '设置',
|
|
71
|
+
icon: <Settings className="w-5 h-5" />,
|
|
72
|
+
element: <SettingsPage />
|
|
73
|
+
})
|
|
41
74
|
|
|
75
|
+
// 删除页面
|
|
76
|
+
removePage('/settings')
|
|
77
|
+
|
|
78
|
+
// 更新页面
|
|
79
|
+
updatePage('/settings', {
|
|
80
|
+
key: 'settings',
|
|
81
|
+
path: '/settings',
|
|
82
|
+
title: '设置(更新)',
|
|
83
|
+
icon: <Settings className="w-5 h-5" />,
|
|
84
|
+
element: <UpdatedSettingsPage />
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// 向后兼容的旧 API(已废弃)
|
|
88
|
+
import { addRoute, removeRoute, updateRoute } from '@zhin.js/client'
|
|
89
|
+
// 这些 API 仍然可用,但推荐使用新的 addPage 等 API
|
|
42
90
|
```
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
91
|
+
|
|
92
|
+
### 3. 自动父路由查找
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
// main.tsx
|
|
96
|
+
import { addPage } from '@zhin.js/client'
|
|
97
|
+
import { Home, Users, Settings } from 'lucide-react'
|
|
98
|
+
|
|
99
|
+
// 1. 添加顶级页面
|
|
100
|
+
addPage({
|
|
101
|
+
key: 'home',
|
|
102
|
+
path: '/',
|
|
103
|
+
title: '首页',
|
|
104
|
+
icon: <Home className="w-5 h-5" />,
|
|
105
|
+
element: <HomePage />
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
addPage({
|
|
109
|
+
key: 'admin',
|
|
110
|
+
path: '/admin',
|
|
111
|
+
title: '管理',
|
|
112
|
+
element: <AdminLayout />
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// 2. 自动查找父路由:/admin/users
|
|
116
|
+
// 会查找 /admin 是否存在,如果存在,插入 users 到 /admin
|
|
117
|
+
addPage({
|
|
118
|
+
key: 'admin-users',
|
|
119
|
+
path: '/admin/users',
|
|
120
|
+
title: '用户管理',
|
|
121
|
+
icon: <Users className="w-5 h-5" />,
|
|
122
|
+
element: <UsersPage />
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
addPage({
|
|
126
|
+
key: 'admin-settings',
|
|
127
|
+
path: '/admin/settings',
|
|
128
|
+
title: '系统设置',
|
|
129
|
+
icon: <Settings className="w-5 h-5" />,
|
|
130
|
+
element: <AdminSettingsPage />
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
// 3. 自动查找父路由:/admin/users/detail
|
|
134
|
+
// 会查找 /admin/users 是否存在,如果存在,插入 detail 到 /admin/users
|
|
135
|
+
addPage({
|
|
136
|
+
key: 'user-detail',
|
|
137
|
+
path: '/admin/users/detail',
|
|
138
|
+
title: '用户详情',
|
|
139
|
+
element: <UserDetailPage />
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// 4. 动态添加嵌套页面
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
// 会自动查找 /admin 是否存在,如果存在,插入 analytics 到 /admin
|
|
145
|
+
addPage({
|
|
146
|
+
key: 'admin-analytics',
|
|
147
|
+
path: '/admin/analytics',
|
|
148
|
+
title: '分析',
|
|
149
|
+
element: <AnalyticsPage />
|
|
150
|
+
})
|
|
151
|
+
}, 2000)
|
|
152
|
+
|
|
153
|
+
// 5. 动态添加更深层嵌套
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
// 会自动查找 /admin/analytics 是否存在,如果存在,插入 reports 到 /admin/analytics
|
|
156
|
+
addPage({
|
|
157
|
+
key: 'analytics-reports',
|
|
158
|
+
path: '/admin/analytics/reports',
|
|
159
|
+
title: '分析报告',
|
|
160
|
+
element: <ReportsPage />
|
|
161
|
+
})
|
|
162
|
+
}, 4000)
|
|
53
163
|
```
|
|
54
164
|
|
|
55
|
-
###
|
|
165
|
+
### 4. 事件监听
|
|
56
166
|
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
|
|
167
|
+
```tsx
|
|
168
|
+
import { routerManager } from '@zhin.js/client'
|
|
169
|
+
|
|
170
|
+
// 监听路由变化
|
|
171
|
+
routerManager.onRouteChange(() => {
|
|
172
|
+
console.log('Routes changed')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
// 监听路由添加
|
|
176
|
+
routerManager.onRouteAdd((route) => {
|
|
177
|
+
console.log('Route added:', route.path)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// 监听路由删除
|
|
181
|
+
routerManager.onRouteRemove((path) => {
|
|
182
|
+
console.log('Route removed:', path)
|
|
183
|
+
})
|
|
60
184
|
```
|
|
61
185
|
|
|
62
|
-
## API
|
|
186
|
+
## API 参考
|
|
63
187
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
188
|
+
### 页面管理 API(推荐)
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
// 添加页面
|
|
192
|
+
addPage(route: RouteMenuItem): void
|
|
193
|
+
|
|
194
|
+
// 删除页面
|
|
195
|
+
removePage(path: string): void
|
|
196
|
+
|
|
197
|
+
// 更新页面
|
|
198
|
+
updatePage(path: string, updates: Partial<RouteMenuItem>): void
|
|
199
|
+
|
|
200
|
+
// 获取页面
|
|
201
|
+
getPage(path: string): RouteMenuItem | undefined
|
|
202
|
+
|
|
203
|
+
// 获取所有页面
|
|
204
|
+
getAllPages(): RouteMenuItem[]
|
|
205
|
+
|
|
206
|
+
// 清空所有页面
|
|
207
|
+
clearPages(): void
|
|
208
|
+
```
|
|
68
209
|
|
|
69
|
-
|
|
210
|
+
### 旧 API(已废弃,保留向后兼容)
|
|
70
211
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
- `/settings` - 系统设置
|
|
75
|
-
- `/404` - 错误页面
|
|
212
|
+
```tsx
|
|
213
|
+
/** @deprecated 请使用 addPage */
|
|
214
|
+
addRoute(route: RouteMenuItem): void
|
|
76
215
|
|
|
77
|
-
|
|
216
|
+
/** @deprecated 请使用 removePage */
|
|
217
|
+
removeRoute(path: string): void
|
|
78
218
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- `@primeuix/themes` - 主题系统
|
|
82
|
-
- `vue-router` - 路由管理
|
|
219
|
+
/** @deprecated 请使用 updatePage */
|
|
220
|
+
updateRoute(path: string, route: RouteMenuItem): void
|
|
83
221
|
|
|
84
|
-
|
|
222
|
+
/** @deprecated 请使用 getPage */
|
|
223
|
+
getRoute(path: string): RouteMenuItem | undefined
|
|
85
224
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
225
|
+
/** @deprecated 请使用 getAllPages */
|
|
226
|
+
getAllRoutes(): RouteMenuItem[]
|
|
227
|
+
|
|
228
|
+
/** @deprecated 请使用 clearPages */
|
|
229
|
+
clearRoutes(): void
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### routerManager 对象
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
// 事件监听方法
|
|
236
|
+
routerManager.onRouteChange(callback: () => void): () => void
|
|
237
|
+
routerManager.onRouteAdd(callback: (route: RouteConfig) => void): () => void
|
|
238
|
+
routerManager.onRouteRemove(callback: (path: string) => void): () => void
|
|
239
|
+
routerManager.onRouteUpdate(callback: (path: string, route: RouteConfig) => void): () => void
|
|
240
|
+
routerManager.onRouteClear(callback: () => void): () => void
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### RouteMenuItem 接口
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
interface RouteMenuItem {
|
|
247
|
+
key: string // 唯一标识
|
|
248
|
+
path: string // 路由路径
|
|
249
|
+
title: string // 页面标题
|
|
250
|
+
icon?: ReactNode // 图标元素(直接传入 React 元素,如 <Home className="w-5 h-5" />)
|
|
251
|
+
element?: ReactNode // React 组件
|
|
252
|
+
children?: RouteMenuItem[] // 子路由
|
|
253
|
+
meta?: {
|
|
254
|
+
order?: number // 排序
|
|
255
|
+
hideInMenu?: boolean // 是否在菜单中隐藏
|
|
256
|
+
requiresAuth?: boolean // 是否需要认证
|
|
257
|
+
[key: string]: any
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 高级用法
|
|
263
|
+
|
|
264
|
+
### 动态页面操作
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
import { addPage, updatePage, removePage } from '@zhin.js/client'
|
|
268
|
+
|
|
269
|
+
// 运行时动态添加页面
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
addPage({
|
|
272
|
+
key: 'dynamic',
|
|
273
|
+
path: '/dynamic',
|
|
274
|
+
title: '动态页面',
|
|
275
|
+
element: <div>Dynamic Page</div>
|
|
276
|
+
})
|
|
277
|
+
}, 2000)
|
|
278
|
+
|
|
279
|
+
// 动态更新页面
|
|
280
|
+
setTimeout(() => {
|
|
281
|
+
updatePage('/dynamic', {
|
|
282
|
+
key: 'dynamic',
|
|
283
|
+
path: '/dynamic',
|
|
284
|
+
title: '动态页面(已更新)',
|
|
285
|
+
element: <div>Updated Page</div>
|
|
286
|
+
})
|
|
287
|
+
}, 4000)
|
|
288
|
+
|
|
289
|
+
// 动态删除页面
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
removePage('/dynamic')
|
|
292
|
+
}, 6000)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### WebSocket 动态加载
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
import { useWebSocket, addPage } from '@zhin.js/client'
|
|
299
|
+
|
|
300
|
+
function App() {
|
|
301
|
+
// 连接 WebSocket,接收动态入口脚本
|
|
302
|
+
const ws = useWebSocket({
|
|
303
|
+
onMessage: (message) => {
|
|
304
|
+
console.log('收到消息:', message)
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<div>
|
|
310
|
+
<p>WebSocket 状态: {ws.connected ? '已连接' : '未连接'}</p>
|
|
311
|
+
<p>已加载入口: {ws.entries.length}</p>
|
|
312
|
+
<DynamicRouter />
|
|
313
|
+
</div>
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 插件入口脚本示例(my-plugin-entry.ts)
|
|
318
|
+
import { addPage } from '@zhin.js/client'
|
|
319
|
+
import { Puzzle } from 'lucide-react'
|
|
320
|
+
|
|
321
|
+
addPage({
|
|
322
|
+
key: 'my-plugin',
|
|
323
|
+
path: '/my-plugin',
|
|
324
|
+
title: '我的插件',
|
|
325
|
+
icon: <Puzzle className="w-5 h-5" />,
|
|
326
|
+
element: <MyPluginPage />
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 事件统计
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
// 路由统计
|
|
334
|
+
let routeCount = 0
|
|
335
|
+
routerManager.onRouteAdd(() => {
|
|
336
|
+
routeCount++
|
|
337
|
+
console.log(`Total routes: ${routeCount}`)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
routerManager.onRouteRemove(() => {
|
|
341
|
+
routeCount--
|
|
342
|
+
console.log(`Total routes: ${routeCount}`)
|
|
343
|
+
})
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 条件事件监听
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
// 只监听特定路径的路由变化
|
|
350
|
+
routerManager.onRouteAdd((route) => {
|
|
351
|
+
if (route.path.startsWith('/admin')) {
|
|
352
|
+
console.log('Admin route added:', route.path)
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
```
|
|
90
356
|
|
|
91
|
-
##
|
|
357
|
+
## 注意事项
|
|
92
358
|
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
359
|
+
1. **路由路径唯一性** - 确保路由路径的唯一性,避免冲突
|
|
360
|
+
2. **事件清理** - 记得清理事件监听器,避免内存泄漏
|
|
361
|
+
3. **性能考虑** - 大量路由时考虑使用懒加载
|
|
362
|
+
4. **类型安全** - 使用 TypeScript 确保类型安全
|
|
96
363
|
|
|
97
|
-
##
|
|
364
|
+
## 示例项目
|
|
98
365
|
|
|
99
|
-
|
|
366
|
+
查看 `app/src/main.tsx` 中的完整示例。
|
package/app/index.html
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
<html lang='en'>
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset='UTF-8'>
|
|
5
|
-
<meta name=
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Zhin</title>
|
|
7
7
|
</head>
|
|
8
8
|
<body>
|
|
9
|
-
<
|
|
10
|
-
<
|
|
9
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script src='/src/main.tsx' type='module'></script>
|
|
11
12
|
</body>
|
|
12
13
|
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useTheme } from '../hooks/useTheme'
|
|
2
|
+
import { Icons } from '@zhin.js/client'
|
|
3
|
+
|
|
4
|
+
export function ThemeToggle() {
|
|
5
|
+
const { theme, toggleTheme } = useTheme()
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<button
|
|
9
|
+
onClick={toggleTheme}
|
|
10
|
+
className="p-2 hover:bg-accent rounded-lg transition-colors text-foreground"
|
|
11
|
+
title={theme === 'light' ? '切换到暗色模式' : '切换到亮色模式'}
|
|
12
|
+
>
|
|
13
|
+
{theme === 'light' ? (
|
|
14
|
+
<Icons.Moon className="w-5 h-5" />
|
|
15
|
+
) : (
|
|
16
|
+
<Icons.Sun className="w-5 h-5" />
|
|
17
|
+
)}
|
|
18
|
+
</button>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import { type Theme, applyTheme, getInitialTheme } from '../theme'
|
|
3
|
+
|
|
4
|
+
export function useTheme() {
|
|
5
|
+
const [theme, setTheme] = useState<Theme>(getInitialTheme)
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
applyTheme(theme)
|
|
9
|
+
}, [theme])
|
|
10
|
+
|
|
11
|
+
const toggleTheme = () => {
|
|
12
|
+
setTheme(prev => prev === 'light' ? 'dark' : 'light')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return { theme, setTheme, toggleTheme }
|
|
16
|
+
}
|
|
17
|
+
|