@zhin.js/client 1.0.3 → 1.0.5
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 +117 -3
- package/{src → client}/index.ts +0 -1
- package/{src → client}/store/index.ts +22 -1
- package/client/store/reducers/config.ts +135 -0
- package/{src → client}/store/reducers/index.ts +4 -1
- package/client/websocket/hooks.ts +280 -0
- package/client/websocket/index.ts +48 -0
- package/client/websocket/instance.ts +46 -0
- package/client/websocket/manager.ts +412 -0
- package/client/websocket/messageHandler.ts +166 -0
- package/client/websocket/types.ts +208 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -0
- package/dist/router/index.d.ts +25 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +49 -0
- package/dist/router/index.js.map +1 -0
- package/dist/store/index.d.ts +19 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +67 -0
- package/dist/store/index.js.map +1 -0
- package/dist/store/reducers/config.d.ts +54 -0
- package/dist/store/reducers/config.d.ts.map +1 -0
- package/dist/store/reducers/config.js +78 -0
- package/dist/store/reducers/config.js.map +1 -0
- package/dist/store/reducers/index.d.ts +13 -0
- package/dist/store/reducers/index.d.ts.map +1 -0
- package/dist/store/reducers/index.js +11 -0
- package/dist/store/reducers/index.js.map +1 -0
- package/dist/store/reducers/route.d.ts +37 -0
- package/dist/store/reducers/route.d.ts.map +1 -0
- package/dist/store/reducers/route.js +85 -0
- package/dist/store/reducers/route.js.map +1 -0
- package/dist/store/reducers/script.d.ts +17 -0
- package/dist/store/reducers/script.d.ts.map +1 -0
- package/dist/store/reducers/script.js +74 -0
- package/dist/store/reducers/script.js.map +1 -0
- package/dist/store/reducers/ui.d.ts +14 -0
- package/dist/store/reducers/ui.d.ts.map +1 -0
- package/dist/store/reducers/ui.js +23 -0
- package/dist/store/reducers/ui.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/websocket/hooks.d.ts +55 -0
- package/dist/websocket/hooks.d.ts.map +1 -0
- package/dist/websocket/hooks.js +225 -0
- package/dist/websocket/hooks.js.map +1 -0
- package/dist/websocket/index.d.ts +13 -0
- package/dist/websocket/index.d.ts.map +1 -0
- package/dist/websocket/index.js +31 -0
- package/dist/websocket/index.js.map +1 -0
- package/dist/websocket/instance.d.ts +18 -0
- package/dist/websocket/instance.d.ts.map +1 -0
- package/dist/websocket/instance.js +39 -0
- package/dist/websocket/instance.js.map +1 -0
- package/dist/websocket/manager.d.ts +110 -0
- package/dist/websocket/manager.d.ts.map +1 -0
- package/dist/websocket/manager.js +341 -0
- package/dist/websocket/manager.js.map +1 -0
- package/dist/websocket/messageHandler.d.ts +48 -0
- package/dist/websocket/messageHandler.d.ts.map +1 -0
- package/dist/websocket/messageHandler.js +140 -0
- package/dist/websocket/messageHandler.js.map +1 -0
- package/dist/websocket/types.d.ts +125 -0
- package/dist/websocket/types.d.ts.map +1 -0
- package/dist/websocket/types.js +43 -0
- package/dist/websocket/types.js.map +1 -0
- package/package.json +8 -18
- package/app/index.html +0 -13
- package/app/postcss.config.js +0 -5
- package/app/src/components/ThemeToggle.tsx +0 -21
- package/app/src/hooks/useTheme.ts +0 -17
- package/app/src/layouts/dashboard.tsx +0 -259
- package/app/src/main.tsx +0 -121
- package/app/src/pages/dashboard-bots.tsx +0 -198
- package/app/src/pages/dashboard-home.tsx +0 -301
- package/app/src/pages/dashboard-logs.tsx +0 -298
- package/app/src/pages/dashboard-plugin-detail.tsx +0 -349
- package/app/src/pages/dashboard-plugins.tsx +0 -166
- package/app/src/style.css +0 -1105
- package/app/src/theme/index.ts +0 -92
- package/app/tailwind.config.js +0 -70
- package/app/tsconfig.json +0 -16
- package/src/websocket/index.ts +0 -193
- package/src/websocket/useWebSocket.ts +0 -42
- /package/{src → client}/router/index.tsx +0 -0
- /package/{src → client}/store/reducers/route.ts +0 -0
- /package/{src → client}/store/reducers/script.ts +0 -0
- /package/{src → client}/store/reducers/ui.ts +0 -0
- /package/{src → client}/types.ts +0 -0
package/README.md
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
# Zhin Client -
|
|
1
|
+
# Zhin Client - 动态页面路由与插件配置系统
|
|
2
2
|
|
|
3
|
-
基于 React Router 7.0
|
|
3
|
+
基于 React Router 7.0 的动态页面管理系统,集成插件配置功能,支持基于 Schema 的自动表单生成。
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
|
+
### 路由系统
|
|
7
8
|
- 🌳 **树形路由结构** - 使用树形结构管理页面路由,支持任意深度的嵌套
|
|
8
9
|
- ✅ **动态页面管理** - 运行时添加、删除、更新页面
|
|
9
10
|
- ✅ **React Router 7.0** - 使用最新的 React Router
|
|
10
11
|
- ✅ **TypeScript 支持** - 完整的类型定义
|
|
11
12
|
- ✅ **WebSocket 集成** - 支持动态加载插件入口脚本
|
|
12
13
|
- ✅ **Redux 状态管理** - 集成 Redux 持久化
|
|
13
|
-
|
|
14
|
+
|
|
15
|
+
### 配置系统
|
|
16
|
+
- 🔧 **Schema 驱动** - 支持 15 种 Schema 数据类型
|
|
17
|
+
- 📝 **自动表单生成** - 根据 Schema 自动生成配置表单
|
|
18
|
+
- 🎨 **智能 UI 组件** - 针对不同类型自动选择最佳 UI 控件
|
|
19
|
+
- 🔄 **实时配置更新** - 支持配置文件的实时读取和保存
|
|
20
|
+
- 🧩 **模块化设计** - 17 个独立的字段渲染器,易于扩展
|
|
14
21
|
|
|
15
22
|
## 安装
|
|
16
23
|
|
|
@@ -361,6 +368,113 @@ routerManager.onRouteAdd((route) => {
|
|
|
361
368
|
3. **性能考虑** - 大量路由时考虑使用懒加载
|
|
362
369
|
4. **类型安全** - 使用 TypeScript 确保类型安全
|
|
363
370
|
|
|
371
|
+
## 插件配置系统
|
|
372
|
+
|
|
373
|
+
### Schema 支持的数据类型
|
|
374
|
+
|
|
375
|
+
Zhin Client 配置系统完整支持所有 15 种 Schema 数据类型:
|
|
376
|
+
|
|
377
|
+
#### 基础类型
|
|
378
|
+
- `string` - 字符串(支持枚举/多行/单行)
|
|
379
|
+
- `number` / `integer` - 数字(支持 min/max 限制)
|
|
380
|
+
- `boolean` - 布尔值(开关控件)
|
|
381
|
+
|
|
382
|
+
#### 特殊类型
|
|
383
|
+
- `percent` - 百分比(滑块 + 数字输入)
|
|
384
|
+
- `date` - 日期(日期选择器)
|
|
385
|
+
- `regexp` - 正则表达式(带验证)
|
|
386
|
+
- `const` - 常量(只读显示)
|
|
387
|
+
|
|
388
|
+
#### 集合类型
|
|
389
|
+
- `list` - 列表(支持嵌套)
|
|
390
|
+
- `tuple` - 元组(固定字段)
|
|
391
|
+
- `object` - 对象(嵌套结构)
|
|
392
|
+
- `dict` - 字典(JSON 编辑器)
|
|
393
|
+
|
|
394
|
+
#### 组合类型
|
|
395
|
+
- `union` - 联合类型(多选一)
|
|
396
|
+
- `intersect` - 交叉类型(满足所有)
|
|
397
|
+
|
|
398
|
+
#### 通用类型
|
|
399
|
+
- `any` - 任意类型(JSON 编辑器)
|
|
400
|
+
- `never` - 永不类型(警告提示)
|
|
401
|
+
|
|
402
|
+
### 使用插件配置组件
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import PluginConfigForm from '@zhin.js/client/components/PluginConfigForm'
|
|
406
|
+
|
|
407
|
+
<PluginConfigForm
|
|
408
|
+
pluginName="my-plugin"
|
|
409
|
+
onClose={() => setDialogOpen(false)}
|
|
410
|
+
onSuccess={() => refetchPlugin()}
|
|
411
|
+
/>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 定义插件配置 Schema
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import { Schema } from 'zhin.js'
|
|
418
|
+
|
|
419
|
+
export const config = Schema.object({
|
|
420
|
+
// 基础类型
|
|
421
|
+
name: Schema.string('插件名称').required(),
|
|
422
|
+
enabled: Schema.boolean('是否启用').default(true),
|
|
423
|
+
port: Schema.number('端口').min(1).max(65535).default(3000),
|
|
424
|
+
|
|
425
|
+
// 特殊类型
|
|
426
|
+
opacity: Schema.percent('透明度').default(0.8),
|
|
427
|
+
startDate: Schema.date('开始日期'),
|
|
428
|
+
pattern: Schema.regexp('匹配模式'),
|
|
429
|
+
|
|
430
|
+
// 集合类型
|
|
431
|
+
tags: Schema.list(Schema.string(), '标签'),
|
|
432
|
+
server: Schema.object({
|
|
433
|
+
host: Schema.string().default('localhost'),
|
|
434
|
+
port: Schema.number().default(3000)
|
|
435
|
+
}),
|
|
436
|
+
|
|
437
|
+
// 组合类型
|
|
438
|
+
mode: Schema.union([
|
|
439
|
+
Schema.const('auto'),
|
|
440
|
+
Schema.const('manual')
|
|
441
|
+
])
|
|
442
|
+
})
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### 配置表单特性
|
|
446
|
+
|
|
447
|
+
- **自动渲染**: 根据 Schema 类型自动选择合适的 UI 组件
|
|
448
|
+
- **智能分组**: 简单字段直接展示,复杂字段可折叠
|
|
449
|
+
- **嵌套支持**: 完整支持任意深度的嵌套结构
|
|
450
|
+
- **实时验证**: 输入时进行类型验证和格式检查
|
|
451
|
+
- **紧凑布局**: 使用 ScrollArea 和 Accordion 优化空间使用
|
|
452
|
+
|
|
453
|
+
## 组件架构
|
|
454
|
+
|
|
455
|
+
### PluginConfigForm 模块结构
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
PluginConfigForm/
|
|
459
|
+
├── types.ts - 类型定义
|
|
460
|
+
├── BasicFieldRenderers.tsx - 基础类型渲染器 (9个)
|
|
461
|
+
├── CollectionFieldRenderers.tsx - 集合类型渲染器 (5个)
|
|
462
|
+
├── CompositeFieldRenderers.tsx - 组合类型渲染器 (2个)
|
|
463
|
+
├── FieldRenderer.tsx - 字段渲染器主入口
|
|
464
|
+
├── NestedFieldRenderer.tsx - 嵌套字段渲染器
|
|
465
|
+
└── index.tsx - 主组件
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
17 个独立渲染器,职责单一,易于测试和扩展。
|
|
469
|
+
|
|
470
|
+
## 注意事项
|
|
471
|
+
|
|
472
|
+
1. **路由路径唯一性** - 确保路由路径的唯一性,避免冲突
|
|
473
|
+
2. **事件清理** - 记得清理事件监听器,避免内存泄漏
|
|
474
|
+
3. **性能考虑** - 大量路由时考虑使用懒加载
|
|
475
|
+
4. **类型安全** - 使用 TypeScript 确保类型安全
|
|
476
|
+
5. **Schema 定义** - 为插件配置定义清晰的 Schema,提供友好的描述信息
|
|
477
|
+
|
|
364
478
|
## 示例项目
|
|
365
479
|
|
|
366
480
|
查看 `app/src/main.tsx` 中的完整示例。
|
package/{src → client}/index.ts
RENAMED
|
@@ -108,4 +108,25 @@ export {
|
|
|
108
108
|
unloadScript
|
|
109
109
|
} from './reducers/script'
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
// 导出 Config actions 和 selectors
|
|
112
|
+
export {
|
|
113
|
+
setConnected,
|
|
114
|
+
setLoading,
|
|
115
|
+
setError,
|
|
116
|
+
updateConfig,
|
|
117
|
+
updateSchema,
|
|
118
|
+
updateConfigs,
|
|
119
|
+
updateSchemas,
|
|
120
|
+
removeConfig,
|
|
121
|
+
clearConfigs,
|
|
122
|
+
selectConfig,
|
|
123
|
+
selectSchema,
|
|
124
|
+
selectConfigLoading,
|
|
125
|
+
selectConfigError,
|
|
126
|
+
selectConfigConnected,
|
|
127
|
+
selectAllConfigs,
|
|
128
|
+
selectAllSchemas
|
|
129
|
+
} from './reducers/config'
|
|
130
|
+
|
|
131
|
+
export type { RouteMenuItem } from './reducers/route'
|
|
132
|
+
export type { ConfigMessage } from './reducers/config'
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置管理 Redux Slice
|
|
3
|
+
* 通过 WebSocket 管理插件配置
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'
|
|
7
|
+
|
|
8
|
+
export interface ConfigState {
|
|
9
|
+
// 所有插件的配置,按插件名分发
|
|
10
|
+
configs: Record<string, any>
|
|
11
|
+
// 所有插件的 Schema,按插件名分发
|
|
12
|
+
schemas: Record<string, any>
|
|
13
|
+
// 加载状态
|
|
14
|
+
loading: Record<string, boolean>
|
|
15
|
+
// 错误信息
|
|
16
|
+
errors: Record<string, string | null>
|
|
17
|
+
// WebSocket 连接状态
|
|
18
|
+
connected: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const initialState: ConfigState = {
|
|
22
|
+
configs: {},
|
|
23
|
+
schemas: {},
|
|
24
|
+
loading: {},
|
|
25
|
+
errors: {},
|
|
26
|
+
connected: false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// WebSocket 消息类型
|
|
30
|
+
export interface ConfigMessage {
|
|
31
|
+
type: 'config:get' | 'config:set' | 'config:updated' | 'schema:get' | 'schema:updated'
|
|
32
|
+
pluginName: string
|
|
33
|
+
data?: any
|
|
34
|
+
error?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const configSlice = createSlice({
|
|
38
|
+
name: 'config',
|
|
39
|
+
initialState,
|
|
40
|
+
reducers: {
|
|
41
|
+
// WebSocket 连接状态
|
|
42
|
+
setConnected: (state, action: PayloadAction<boolean>) => {
|
|
43
|
+
state.connected = action.payload
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// 设置配置加载状态
|
|
47
|
+
setLoading: (state, action: PayloadAction<{ pluginName: string; loading: boolean }>) => {
|
|
48
|
+
const { pluginName, loading } = action.payload
|
|
49
|
+
state.loading[pluginName] = loading
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// 设置错误信息
|
|
53
|
+
setError: (state, action: PayloadAction<{ pluginName: string; error: string | null }>) => {
|
|
54
|
+
const { pluginName, error } = action.payload
|
|
55
|
+
state.errors[pluginName] = error
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// 更新插件配置
|
|
59
|
+
updateConfig: (state, action: PayloadAction<{ pluginName: string; config: any }>) => {
|
|
60
|
+
const { pluginName, config } = action.payload
|
|
61
|
+
state.configs[pluginName] = config
|
|
62
|
+
state.loading[pluginName] = false
|
|
63
|
+
state.errors[pluginName] = null
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// 更新插件 Schema
|
|
67
|
+
updateSchema: (state, action: PayloadAction<{ pluginName: string; schema: any }>) => {
|
|
68
|
+
const { pluginName, schema } = action.payload
|
|
69
|
+
state.schemas[pluginName] = schema
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// 批量更新配置
|
|
73
|
+
updateConfigs: (state, action: PayloadAction<Record<string, any>>) => {
|
|
74
|
+
state.configs = { ...state.configs, ...action.payload }
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// 批量更新 Schema
|
|
78
|
+
updateSchemas: (state, action: PayloadAction<Record<string, any>>) => {
|
|
79
|
+
state.schemas = { ...state.schemas, ...action.payload }
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// 清除插件配置
|
|
83
|
+
removeConfig: (state, action: PayloadAction<string>) => {
|
|
84
|
+
const pluginName = action.payload
|
|
85
|
+
delete state.configs[pluginName]
|
|
86
|
+
delete state.schemas[pluginName]
|
|
87
|
+
delete state.loading[pluginName]
|
|
88
|
+
delete state.errors[pluginName]
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// 清除所有配置
|
|
92
|
+
clearConfigs: (state) => {
|
|
93
|
+
state.configs = {}
|
|
94
|
+
state.schemas = {}
|
|
95
|
+
state.loading = {}
|
|
96
|
+
state.errors = {}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
export const {
|
|
102
|
+
setConnected,
|
|
103
|
+
setLoading,
|
|
104
|
+
setError,
|
|
105
|
+
updateConfig,
|
|
106
|
+
updateSchema,
|
|
107
|
+
updateConfigs,
|
|
108
|
+
updateSchemas,
|
|
109
|
+
removeConfig,
|
|
110
|
+
clearConfigs
|
|
111
|
+
} = configSlice.actions
|
|
112
|
+
|
|
113
|
+
export default configSlice.reducer
|
|
114
|
+
|
|
115
|
+
// Selectors
|
|
116
|
+
export const selectConfig = (state: { config: ConfigState }, pluginName: string) =>
|
|
117
|
+
state.config.configs[pluginName]
|
|
118
|
+
|
|
119
|
+
export const selectSchema = (state: { config: ConfigState }, pluginName: string) =>
|
|
120
|
+
state.config.schemas[pluginName]
|
|
121
|
+
|
|
122
|
+
export const selectConfigLoading = (state: { config: ConfigState }, pluginName: string) =>
|
|
123
|
+
state.config.loading[pluginName] || false
|
|
124
|
+
|
|
125
|
+
export const selectConfigError = (state: { config: ConfigState }, pluginName: string) =>
|
|
126
|
+
state.config.errors[pluginName] || null
|
|
127
|
+
|
|
128
|
+
export const selectConfigConnected = (state: { config: ConfigState }) =>
|
|
129
|
+
state.config.connected
|
|
130
|
+
|
|
131
|
+
export const selectAllConfigs = (state: { config: ConfigState }) =>
|
|
132
|
+
state.config.configs
|
|
133
|
+
|
|
134
|
+
export const selectAllSchemas = (state: { config: ConfigState }) =>
|
|
135
|
+
state.config.schemas
|
|
@@ -2,15 +2,18 @@ import { Reducer } from "@reduxjs/toolkit"
|
|
|
2
2
|
import ui from "./ui"
|
|
3
3
|
import route from "./route"
|
|
4
4
|
import script from "./script"
|
|
5
|
+
import config from "./config"
|
|
5
6
|
|
|
6
7
|
export interface Reducers {
|
|
7
8
|
ui: ReturnType<typeof ui>
|
|
8
9
|
route: ReturnType<typeof route>
|
|
9
10
|
script: ReturnType<typeof script>
|
|
11
|
+
config: ReturnType<typeof config>
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export const reducers: Record<keyof Reducers, Reducer<any, any>> = {
|
|
13
15
|
ui,
|
|
14
16
|
route,
|
|
15
|
-
script
|
|
17
|
+
script,
|
|
18
|
+
config
|
|
16
19
|
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket React Hooks
|
|
3
|
+
* 提供在 React 组件中使用 WebSocket 功能的便捷接口
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
7
|
+
import {
|
|
8
|
+
useSelector,
|
|
9
|
+
useDispatch,
|
|
10
|
+
selectConfig,
|
|
11
|
+
selectSchema,
|
|
12
|
+
selectConfigLoading,
|
|
13
|
+
selectConfigError,
|
|
14
|
+
selectConfigConnected,
|
|
15
|
+
selectAllConfigs,
|
|
16
|
+
selectAllSchemas,
|
|
17
|
+
setLoading,
|
|
18
|
+
setError
|
|
19
|
+
} from '../store'
|
|
20
|
+
import { getWebSocketManager } from './instance'
|
|
21
|
+
import type { UseConfigOptions, UseWebSocketOptions, ConnectionState } from './types'
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// WebSocket 连接 Hook
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* WebSocket 连接状态和基础功能 Hook
|
|
29
|
+
*/
|
|
30
|
+
export function useWebSocket(options: UseWebSocketOptions = {}) {
|
|
31
|
+
const { autoConnect = true } = options
|
|
32
|
+
|
|
33
|
+
const wsManager = getWebSocketManager()
|
|
34
|
+
|
|
35
|
+
// 从 Redux 获取状态
|
|
36
|
+
const connected = useSelector(selectConfigConnected)
|
|
37
|
+
const entries = useSelector((state: any) => state.script?.entries || [])
|
|
38
|
+
const loadedScripts = useSelector((state: any) => state.script?.loadedScripts || [])
|
|
39
|
+
|
|
40
|
+
// 连接控制
|
|
41
|
+
const connect = useCallback(() => {
|
|
42
|
+
wsManager.connect()
|
|
43
|
+
}, [wsManager])
|
|
44
|
+
|
|
45
|
+
const disconnect = useCallback(() => {
|
|
46
|
+
wsManager.disconnect()
|
|
47
|
+
}, [wsManager])
|
|
48
|
+
|
|
49
|
+
// 消息发送
|
|
50
|
+
const send = useCallback((message: any) => {
|
|
51
|
+
wsManager.send(message)
|
|
52
|
+
}, [wsManager])
|
|
53
|
+
|
|
54
|
+
const sendRequest = useCallback(async <T = any>(message: any): Promise<T> => {
|
|
55
|
+
// 临时使用旧方法,后续完善
|
|
56
|
+
return (wsManager as any).sendRequest(message)
|
|
57
|
+
}, [wsManager])
|
|
58
|
+
|
|
59
|
+
// 状态查询
|
|
60
|
+
const isConnected = useCallback(() => {
|
|
61
|
+
return wsManager.isConnected()
|
|
62
|
+
}, [wsManager])
|
|
63
|
+
|
|
64
|
+
const getState = useCallback(() => {
|
|
65
|
+
// 临时返回连接状态,后续完善
|
|
66
|
+
return wsManager.isConnected() ? 'connected' : 'disconnected'
|
|
67
|
+
}, [wsManager])
|
|
68
|
+
|
|
69
|
+
// 自动连接
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (autoConnect && !connected) {
|
|
72
|
+
connect()
|
|
73
|
+
}
|
|
74
|
+
}, [autoConnect, connected, connect])
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
// 状态
|
|
78
|
+
connected,
|
|
79
|
+
entries,
|
|
80
|
+
loadedScripts,
|
|
81
|
+
|
|
82
|
+
// 方法
|
|
83
|
+
connect,
|
|
84
|
+
disconnect,
|
|
85
|
+
send,
|
|
86
|
+
sendRequest,
|
|
87
|
+
isConnected,
|
|
88
|
+
getState,
|
|
89
|
+
|
|
90
|
+
// WebSocket 实例(高级用法)
|
|
91
|
+
manager: wsManager
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// 配置管理 Hook
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 插件配置管理 Hook
|
|
101
|
+
*/
|
|
102
|
+
export function useConfig(pluginName: string, options: UseConfigOptions = {}) {
|
|
103
|
+
const { autoLoad = true, autoLoadSchema = true } = options
|
|
104
|
+
|
|
105
|
+
const dispatch = useDispatch()
|
|
106
|
+
const wsManager = getWebSocketManager()
|
|
107
|
+
|
|
108
|
+
// 状态选择器
|
|
109
|
+
const config = useSelector((state: any) => selectConfig(state, pluginName))
|
|
110
|
+
const schema = useSelector((state: any) => selectSchema(state, pluginName))
|
|
111
|
+
const loading = useSelector((state: any) => selectConfigLoading(state, pluginName))
|
|
112
|
+
const error = useSelector((state: any) => selectConfigError(state, pluginName))
|
|
113
|
+
const connected = useSelector(selectConfigConnected)
|
|
114
|
+
|
|
115
|
+
// 配置操作
|
|
116
|
+
const getConfig = useCallback(async () => {
|
|
117
|
+
dispatch(setLoading({ pluginName, loading: true }))
|
|
118
|
+
dispatch(setError({ pluginName, error: null }))
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const result = await wsManager.getConfig(pluginName)
|
|
122
|
+
return result
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.log('getConfig',error)
|
|
125
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
126
|
+
dispatch(setError({ pluginName, error: errorMessage }))
|
|
127
|
+
throw error
|
|
128
|
+
} finally {
|
|
129
|
+
dispatch(setLoading({ pluginName, loading: false }))
|
|
130
|
+
}
|
|
131
|
+
}, [pluginName, wsManager, dispatch])
|
|
132
|
+
|
|
133
|
+
const setConfig = useCallback(async (newConfig: any) => {
|
|
134
|
+
dispatch(setLoading({ pluginName, loading: true }))
|
|
135
|
+
dispatch(setError({ pluginName, error: null }))
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
await wsManager.setConfig(pluginName, newConfig)
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.log('setConfig',error)
|
|
141
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
142
|
+
dispatch(setError({ pluginName, error: errorMessage }))
|
|
143
|
+
throw error
|
|
144
|
+
} finally {
|
|
145
|
+
dispatch(setLoading({ pluginName, loading: false }))
|
|
146
|
+
}
|
|
147
|
+
}, [pluginName, wsManager, dispatch])
|
|
148
|
+
|
|
149
|
+
const getSchema = useCallback(async () => {
|
|
150
|
+
try {
|
|
151
|
+
return await wsManager.getSchema(pluginName)
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error(`Failed to get schema for plugin ${pluginName}:`, error)
|
|
154
|
+
throw error
|
|
155
|
+
}
|
|
156
|
+
}, [pluginName, wsManager])
|
|
157
|
+
|
|
158
|
+
// 重新加载配置和 Schema
|
|
159
|
+
const reload = useCallback(async () => {
|
|
160
|
+
const promises = []
|
|
161
|
+
|
|
162
|
+
if (autoLoad) {
|
|
163
|
+
promises.push(getConfig())
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (autoLoadSchema) {
|
|
167
|
+
promises.push(getSchema())
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await Promise.all(promises)
|
|
171
|
+
}, [autoLoad, autoLoadSchema, getConfig, getSchema])
|
|
172
|
+
|
|
173
|
+
// 自动加载
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (connected && autoLoad && !config && !loading) {
|
|
176
|
+
getConfig().catch(console.error)
|
|
177
|
+
}
|
|
178
|
+
}, [connected, autoLoad, config, loading, getConfig])
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (connected && autoLoadSchema && !schema) {
|
|
182
|
+
getSchema().catch(console.error)
|
|
183
|
+
}
|
|
184
|
+
}, [connected, autoLoadSchema, schema, getSchema])
|
|
185
|
+
|
|
186
|
+
return useMemo(() => ({
|
|
187
|
+
// 状态
|
|
188
|
+
config,
|
|
189
|
+
schema,
|
|
190
|
+
loading,
|
|
191
|
+
error,
|
|
192
|
+
connected,
|
|
193
|
+
|
|
194
|
+
// 方法
|
|
195
|
+
getConfig,
|
|
196
|
+
setConfig,
|
|
197
|
+
getSchema,
|
|
198
|
+
reload
|
|
199
|
+
}), [config, schema, loading, error, connected, getConfig, setConfig, getSchema, reload])
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// 批量配置管理 Hook
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 所有配置管理 Hook
|
|
208
|
+
*/
|
|
209
|
+
export function useAllConfigs() {
|
|
210
|
+
const wsManager = getWebSocketManager()
|
|
211
|
+
|
|
212
|
+
const allConfigs = useSelector(selectAllConfigs)
|
|
213
|
+
const allSchemas = useSelector(selectAllSchemas)
|
|
214
|
+
const connected = useSelector(selectConfigConnected)
|
|
215
|
+
|
|
216
|
+
const refreshAll = useCallback(async () => {
|
|
217
|
+
try {
|
|
218
|
+
const [configs, schemas] = await Promise.all([
|
|
219
|
+
(wsManager as any).getAllConfigs(),
|
|
220
|
+
(wsManager as any).getAllSchemas()
|
|
221
|
+
])
|
|
222
|
+
|
|
223
|
+
return { configs, schemas }
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error('Failed to refresh all configs:', error)
|
|
226
|
+
throw error
|
|
227
|
+
}
|
|
228
|
+
}, [wsManager])
|
|
229
|
+
|
|
230
|
+
return useMemo(() => ({
|
|
231
|
+
configs: allConfigs,
|
|
232
|
+
schemas: allSchemas,
|
|
233
|
+
connected,
|
|
234
|
+
refreshAll
|
|
235
|
+
}), [allConfigs, allSchemas, connected, refreshAll])
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ============================================================================
|
|
239
|
+
// 高级 Hook
|
|
240
|
+
// ============================================================================
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* WebSocket 连接状态监听 Hook
|
|
244
|
+
*/
|
|
245
|
+
export function useWebSocketState() {
|
|
246
|
+
const wsManager = getWebSocketManager()
|
|
247
|
+
const [state, setState] = useState(wsManager.getState())
|
|
248
|
+
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
const checkState = () => {
|
|
251
|
+
const newState = wsManager.getState()
|
|
252
|
+
if (newState !== state) {
|
|
253
|
+
setState(newState)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const interval = setInterval(checkState, 1000)
|
|
258
|
+
return () => clearInterval(interval)
|
|
259
|
+
}, [wsManager, state])
|
|
260
|
+
|
|
261
|
+
return state
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* WebSocket 消息监听 Hook
|
|
266
|
+
*/
|
|
267
|
+
export function useWebSocketMessages<T = any>(
|
|
268
|
+
filter?: (message: any) => boolean,
|
|
269
|
+
handler?: (message: T) => void
|
|
270
|
+
) {
|
|
271
|
+
const [messages, setMessages] = useState<T[]>([])
|
|
272
|
+
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
const wsManager = getWebSocketManager()
|
|
275
|
+
// 这里需要扩展 WebSocketManager 来支持消息监听
|
|
276
|
+
// 暂时留空,后续可以扩展
|
|
277
|
+
}, [filter, handler])
|
|
278
|
+
|
|
279
|
+
return messages
|
|
280
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket 客户端模块
|
|
3
|
+
* 提供统一的 WebSocket 连接管理、消息处理和 React Hook 接口
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// 类型定义
|
|
8
|
+
// ============================================================================
|
|
9
|
+
export * from './types'
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// 核心类
|
|
13
|
+
// ============================================================================
|
|
14
|
+
export { WebSocketManager } from './manager'
|
|
15
|
+
export { MessageHandler } from './messageHandler'
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// 实例管理
|
|
19
|
+
// ============================================================================
|
|
20
|
+
export {
|
|
21
|
+
getWebSocketManager,
|
|
22
|
+
destroyWebSocketManager,
|
|
23
|
+
resetWebSocketManager
|
|
24
|
+
} from './instance'
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// React Hooks
|
|
28
|
+
// ============================================================================
|
|
29
|
+
export {
|
|
30
|
+
useWebSocket,
|
|
31
|
+
useConfig,
|
|
32
|
+
useAllConfigs,
|
|
33
|
+
useWebSocketState,
|
|
34
|
+
useWebSocketMessages
|
|
35
|
+
} from './hooks'
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// 向后兼容的导出(保持与旧代码的兼容性)
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
// 为了保持与现有代码的兼容性,重新导出一些常用的接口
|
|
42
|
+
import { getWebSocketManager } from './instance'
|
|
43
|
+
|
|
44
|
+
// 兼容旧的 useConfig 导出
|
|
45
|
+
export { useConfig as useConfigLegacy } from './hooks'
|
|
46
|
+
|
|
47
|
+
// 兼容旧的 WebSocketManager 默认导出
|
|
48
|
+
export default getWebSocketManager
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket 实例管理
|
|
3
|
+
* 提供全局 WebSocket 管理器单例
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { WebSocketManager } from './manager'
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// 全局实例
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
let globalWebSocketManager: WebSocketManager | null = null
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 获取全局 WebSocket 管理器实例
|
|
16
|
+
*/
|
|
17
|
+
export function getWebSocketManager(): WebSocketManager {
|
|
18
|
+
if (!globalWebSocketManager) {
|
|
19
|
+
globalWebSocketManager = new WebSocketManager()
|
|
20
|
+
|
|
21
|
+
// 浏览器环境下自动连接
|
|
22
|
+
if (typeof window !== 'undefined') {
|
|
23
|
+
globalWebSocketManager.connect()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return globalWebSocketManager
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 销毁全局 WebSocket 管理器
|
|
32
|
+
*/
|
|
33
|
+
export function destroyWebSocketManager(): void {
|
|
34
|
+
if (globalWebSocketManager) {
|
|
35
|
+
globalWebSocketManager.disconnect()
|
|
36
|
+
globalWebSocketManager = null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 重置 WebSocket 管理器(主要用于测试)
|
|
42
|
+
*/
|
|
43
|
+
export function resetWebSocketManager(): void {
|
|
44
|
+
destroyWebSocketManager()
|
|
45
|
+
// 下次调用 getWebSocketManager() 时会创建新实例
|
|
46
|
+
}
|