@zhin.js/console 1.0.21 → 1.0.22
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/CHANGELOG.md +11 -0
- package/README.md +4 -4
- package/client/components.json +17 -0
- package/client/index.html +1 -1
- package/client/src/components/PluginConfigForm/BasicFieldRenderers.tsx +89 -180
- package/client/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +97 -200
- package/client/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +31 -70
- package/client/src/components/PluginConfigForm/FieldRenderer.tsx +27 -77
- package/client/src/components/PluginConfigForm/NestedFieldRenderer.tsx +33 -53
- package/client/src/components/PluginConfigForm/index.tsx +71 -173
- package/client/src/components/ui/accordion.tsx +54 -0
- package/client/src/components/ui/alert.tsx +62 -0
- package/client/src/components/ui/avatar.tsx +41 -0
- package/client/src/components/ui/badge.tsx +32 -0
- package/client/src/components/ui/button.tsx +50 -0
- package/client/src/components/ui/card.tsx +50 -0
- package/client/src/components/ui/checkbox.tsx +25 -0
- package/client/src/components/ui/dialog.tsx +87 -0
- package/client/src/components/ui/dropdown-menu.tsx +97 -0
- package/client/src/components/ui/input.tsx +21 -0
- package/client/src/components/ui/scroll-area.tsx +43 -0
- package/client/src/components/ui/select.tsx +127 -0
- package/client/src/components/ui/separator.tsx +23 -0
- package/client/src/components/ui/skeleton.tsx +12 -0
- package/client/src/components/ui/switch.tsx +26 -0
- package/client/src/components/ui/tabs.tsx +52 -0
- package/client/src/components/ui/textarea.tsx +20 -0
- package/client/src/components/ui/tooltip.tsx +27 -0
- package/client/src/layouts/dashboard.tsx +91 -221
- package/client/src/main.tsx +38 -42
- package/client/src/pages/dashboard-bots.tsx +91 -137
- package/client/src/pages/dashboard-home.tsx +133 -204
- package/client/src/pages/dashboard-logs.tsx +125 -196
- package/client/src/pages/dashboard-plugin-detail.tsx +261 -329
- package/client/src/pages/dashboard-plugins.tsx +108 -105
- package/client/src/style.css +156 -865
- package/client/src/theme/index.ts +60 -35
- package/client/tailwind.config.js +78 -69
- package/dist/client.js +1 -1
- package/dist/cva.js +47 -0
- package/dist/index.html +1 -1
- package/dist/index.js +6 -6
- package/dist/react-router.js +7121 -5585
- package/dist/react.js +192 -149
- package/dist/style.css +2 -2
- package/lib/bin.js +2 -2
- package/lib/build.js +2 -2
- package/lib/index.d.ts +0 -3
- package/lib/index.js +160 -205
- package/lib/transform.d.ts +26 -0
- package/lib/transform.js +78 -0
- package/lib/websocket.d.ts +0 -1
- package/package.json +8 -7
- package/dist/radix-ui-themes.js +0 -9305
- package/lib/dev.d.ts +0 -18
- package/lib/dev.js +0 -87
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @zhin.js/console
|
|
2
2
|
|
|
3
|
+
## 1.0.22
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 106d357: fix: ai
|
|
8
|
+
- Updated dependencies [106d357]
|
|
9
|
+
- @zhin.js/http@1.0.17
|
|
10
|
+
- @zhin.js/client@1.0.9
|
|
11
|
+
- @zhin.js/core@1.0.26
|
|
12
|
+
- zhin.js@1.0.26
|
|
13
|
+
|
|
3
14
|
## 1.0.21
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -221,7 +221,7 @@ Console 插件采用智能的构建优化策略,显著减少重复打包:
|
|
|
221
221
|
- 浏览器缓存复用,提升加载速度
|
|
222
222
|
- 开发和生产环境统一体验
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
<!-- 构建优化详见源码中的 build 逻辑 -->
|
|
225
225
|
|
|
226
226
|
### 实时数据同步
|
|
227
227
|
|
|
@@ -295,11 +295,11 @@ node ../../plugins/console/lib/bin.js build
|
|
|
295
295
|
|
|
296
296
|
```
|
|
297
297
|
console/
|
|
298
|
-
├──
|
|
298
|
+
├── src/ # 服务端源码
|
|
299
299
|
│ ├── index.ts # Console 插件主入口
|
|
300
|
-
│ ├── build.ts # 构建逻辑
|
|
300
|
+
│ ├── build.ts # 构建逻辑
|
|
301
301
|
│ ├── dev.ts # Vite 开发服务器
|
|
302
|
-
│ ├──
|
|
302
|
+
│ ├── transform.ts # TS/TSX/JSX 按需转译
|
|
303
303
|
│ └── bin.ts # CLI 工具
|
|
304
304
|
├── client/ # 前端应用
|
|
305
305
|
│ ├── src/ # React 应用源码
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "default",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "tailwind.config.js",
|
|
8
|
+
"css": "src/style.css",
|
|
9
|
+
"baseColor": "zinc",
|
|
10
|
+
"cssVariables": true
|
|
11
|
+
},
|
|
12
|
+
"aliases": {
|
|
13
|
+
"components": "@/components",
|
|
14
|
+
"utils": "@zhin.js/client",
|
|
15
|
+
"ui": "@/components/ui"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/client/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"react/jsx-dev-runtime": "./react-jsx-dev-runtime.js",
|
|
17
17
|
"lucide-react": "./lucide-react.js",
|
|
18
18
|
"radix-ui": "./radix-ui.js",
|
|
19
|
-
"
|
|
19
|
+
"class-variance-authority": "./cva.js",
|
|
20
20
|
"@zhin.js/client": "./client.js"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -1,252 +1,161 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* 处理: string, number, boolean, percent, date, regexp, const
|
|
2
|
+
* Basic type field renderers
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
import { Flex, Box, Text, TextField, TextArea, Switch, Select, Badge, Callout } from '@radix-ui/themes'
|
|
7
5
|
import type { FieldRendererProps } from './types.js'
|
|
6
|
+
import { Input } from '../ui/input'
|
|
7
|
+
import { Textarea } from '../ui/textarea'
|
|
8
|
+
import { Switch } from '../ui/switch'
|
|
9
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
|
10
|
+
import { Badge } from '../ui/badge'
|
|
8
11
|
import { Calendar, Info, Lock, Code } from 'lucide-react'
|
|
12
|
+
|
|
9
13
|
export function StringFieldRenderer({ field, value, onChange }: FieldRendererProps) {
|
|
10
|
-
// 枚举类型 - 下拉选择 - 优化样式
|
|
11
14
|
if (field.enum) {
|
|
12
15
|
return (
|
|
13
|
-
<Select
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
onValueChange={onChange}
|
|
17
|
-
>
|
|
18
|
-
<Select.Trigger className="w-full hover:border-blue-500 dark:hover:border-blue-400 transition-colors" />
|
|
19
|
-
<Select.Content className="shadow-lg">
|
|
16
|
+
<Select value={value?.toString() || ''} onValueChange={onChange}>
|
|
17
|
+
<SelectTrigger><SelectValue placeholder="请选择" /></SelectTrigger>
|
|
18
|
+
<SelectContent>
|
|
20
19
|
{field.enum.map((option) => (
|
|
21
|
-
<
|
|
22
|
-
key={option}
|
|
23
|
-
value={option.toString()}
|
|
24
|
-
className="hover:bg-blue-50 dark:hover:bg-blue-900/20 transition-colors"
|
|
25
|
-
>
|
|
26
|
-
{option.toString()}
|
|
27
|
-
</Select.Item>
|
|
20
|
+
<SelectItem key={option} value={option.toString()}>{option.toString()}</SelectItem>
|
|
28
21
|
))}
|
|
29
|
-
</
|
|
30
|
-
</Select
|
|
22
|
+
</SelectContent>
|
|
23
|
+
</Select>
|
|
31
24
|
)
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
// 多行文本 - 优化样式
|
|
26
|
+
|
|
35
27
|
if (field.description?.includes('多行') || field.key?.includes('description')) {
|
|
36
28
|
return (
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
onChange={(e) => onChange(e.target.value)}
|
|
41
|
-
placeholder={field.description || `请输入`}
|
|
42
|
-
rows={3}
|
|
43
|
-
className="font-sans resize-y hover:border-blue-500 dark:hover:border-blue-400 transition-colors focus:ring-2 focus:ring-blue-500/20"
|
|
29
|
+
<Textarea
|
|
30
|
+
value={value || ''} onChange={(e) => onChange(e.target.value)}
|
|
31
|
+
placeholder={field.description || '请输入'} rows={3}
|
|
44
32
|
/>
|
|
45
33
|
)
|
|
46
34
|
}
|
|
47
|
-
|
|
48
|
-
// 单行文本 - 优化样式
|
|
35
|
+
|
|
49
36
|
return (
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
onChange={(e) => onChange(e.target.value)}
|
|
54
|
-
placeholder={field.description || `请输入`}
|
|
55
|
-
className="hover:border-blue-500 dark:hover:border-blue-400 transition-colors focus-within:ring-2 focus-within:ring-blue-500/20"
|
|
37
|
+
<Input
|
|
38
|
+
value={value || ''} onChange={(e) => onChange(e.target.value)}
|
|
39
|
+
placeholder={field.description || '请输入'}
|
|
56
40
|
/>
|
|
57
41
|
)
|
|
58
42
|
}
|
|
59
43
|
|
|
60
44
|
export function NumberFieldRenderer({ field, value, onChange }: FieldRendererProps) {
|
|
61
45
|
return (
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
type="number"
|
|
65
|
-
value={value?.toString() || ''}
|
|
46
|
+
<Input
|
|
47
|
+
type="number" value={value?.toString() || ''}
|
|
66
48
|
onChange={(e) => onChange(parseFloat(e.target.value) || 0)}
|
|
67
|
-
placeholder={field.description ||
|
|
68
|
-
min={field.min}
|
|
69
|
-
max={field.max}
|
|
70
|
-
className="hover:border-blue-500 dark:hover:border-blue-400 transition-colors focus-within:ring-2 focus-within:ring-blue-500/20"
|
|
49
|
+
placeholder={field.description || '请输入数字'}
|
|
50
|
+
min={field.min} max={field.max}
|
|
71
51
|
/>
|
|
72
52
|
)
|
|
73
53
|
}
|
|
74
54
|
|
|
75
55
|
export function BooleanFieldRenderer({ value, onChange }: FieldRendererProps) {
|
|
76
56
|
return (
|
|
77
|
-
<
|
|
78
|
-
<Switch
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
onCheckedChange={onChange}
|
|
82
|
-
className="cursor-pointer"
|
|
83
|
-
/>
|
|
84
|
-
<Flex direction="column" gap="1">
|
|
85
|
-
<Text size="2" weight="bold" color={value ? 'green' : 'gray'}>
|
|
57
|
+
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted/50 border">
|
|
58
|
+
<Switch checked={value === true} onCheckedChange={onChange} />
|
|
59
|
+
<div className="flex flex-col gap-0.5">
|
|
60
|
+
<span className={`text-sm font-medium ${value ? 'text-emerald-600 dark:text-emerald-400' : 'text-muted-foreground'}`}>
|
|
86
61
|
{value ? '已启用' : '已禁用'}
|
|
87
|
-
</
|
|
88
|
-
<
|
|
62
|
+
</span>
|
|
63
|
+
<span className="text-xs text-muted-foreground">
|
|
89
64
|
{value ? '功能当前处于开启状态' : '功能当前处于关闭状态'}
|
|
90
|
-
</
|
|
91
|
-
</
|
|
92
|
-
</
|
|
65
|
+
</span>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
93
68
|
)
|
|
94
69
|
}
|
|
95
70
|
|
|
96
71
|
export function PercentFieldRenderer({ field, value, onChange }: FieldRendererProps) {
|
|
97
72
|
const percentValue = typeof value === 'number' ? value : (field.default || 0)
|
|
98
|
-
|
|
99
73
|
return (
|
|
100
|
-
<div className="p-
|
|
101
|
-
<
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
className="
|
|
111
|
-
/>
|
|
112
|
-
<TextField.Root
|
|
113
|
-
size="2"
|
|
114
|
-
type="number"
|
|
74
|
+
<div className="p-3 rounded-lg bg-muted/50 border space-y-3">
|
|
75
|
+
<div className="flex items-center gap-3">
|
|
76
|
+
<input
|
|
77
|
+
type="range" min={field.min || 0} max={field.max || 1}
|
|
78
|
+
step={field.step || 0.01} value={percentValue}
|
|
79
|
+
onChange={(e) => onChange(parseFloat(e.target.value))}
|
|
80
|
+
className="flex-1 h-2 rounded-lg appearance-none cursor-pointer bg-secondary"
|
|
81
|
+
/>
|
|
82
|
+
<div className="flex items-center gap-1">
|
|
83
|
+
<Input
|
|
84
|
+
type="number" className="w-20 h-8 text-xs"
|
|
115
85
|
value={(percentValue * 100).toFixed(0)}
|
|
116
86
|
onChange={(e) => onChange(parseFloat(e.target.value) / 100)}
|
|
117
|
-
min={(field.min || 0) * 100}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<Flex align="center" justify="between">
|
|
127
|
-
<Text size="1" color="gray">
|
|
128
|
-
当前值
|
|
129
|
-
</Text>
|
|
130
|
-
<Badge color="blue" size="2" variant="soft" className="font-mono">
|
|
131
|
-
{(percentValue * 100).toFixed(1)}%
|
|
132
|
-
</Badge>
|
|
133
|
-
</Flex>
|
|
134
|
-
</Flex>
|
|
87
|
+
min={(field.min || 0) * 100} max={(field.max || 1) * 100}
|
|
88
|
+
/>
|
|
89
|
+
<span className="text-xs font-bold text-muted-foreground">%</span>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="flex items-center justify-between">
|
|
93
|
+
<span className="text-xs text-muted-foreground">当前值</span>
|
|
94
|
+
<Badge variant="secondary" className="font-mono">{(percentValue * 100).toFixed(1)}%</Badge>
|
|
95
|
+
</div>
|
|
135
96
|
</div>
|
|
136
97
|
)
|
|
137
98
|
}
|
|
138
99
|
|
|
139
100
|
export function DateFieldRenderer({ field, value, onChange }: FieldRendererProps) {
|
|
140
|
-
const dateValue = value instanceof Date
|
|
141
|
-
? value.toISOString().split('T')[0]
|
|
142
|
-
: value || ''
|
|
143
|
-
|
|
101
|
+
const dateValue = value instanceof Date ? value.toISOString().split('T')[0] : value || ''
|
|
144
102
|
return (
|
|
145
103
|
<div className="relative">
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
type="date"
|
|
149
|
-
value={dateValue}
|
|
104
|
+
<Calendar className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" />
|
|
105
|
+
<Input
|
|
106
|
+
type="date" value={dateValue}
|
|
150
107
|
onChange={(e) => onChange(new Date(e.target.value))}
|
|
151
|
-
placeholder={field.description || '选择日期'}
|
|
152
|
-
|
|
153
|
-
>
|
|
154
|
-
<TextField.Slot side="left">
|
|
155
|
-
<Calendar className="w-4 h-4 text-gray-500" />
|
|
156
|
-
</TextField.Slot>
|
|
157
|
-
</TextField.Root>
|
|
108
|
+
placeholder={field.description || '选择日期'} className="pl-9"
|
|
109
|
+
/>
|
|
158
110
|
</div>
|
|
159
111
|
)
|
|
160
112
|
}
|
|
161
113
|
|
|
162
114
|
export function RegexpFieldRenderer({ field, value, onChange }: FieldRendererProps) {
|
|
163
|
-
const regexpValue = value instanceof RegExp
|
|
164
|
-
? value.source
|
|
165
|
-
: (typeof value === 'string' ? value : '')
|
|
166
|
-
|
|
115
|
+
const regexpValue = value instanceof RegExp ? value.source : (typeof value === 'string' ? value : '')
|
|
167
116
|
return (
|
|
168
|
-
<div className="p-3 rounded-lg bg-amber-50 dark:bg-amber-
|
|
169
|
-
<
|
|
170
|
-
<
|
|
171
|
-
|
|
172
|
-
value={regexpValue}
|
|
173
|
-
onChange={(e) => {
|
|
174
|
-
try {
|
|
175
|
-
onChange(new RegExp(e.target.value))
|
|
176
|
-
} catch {
|
|
177
|
-
onChange(e.target.value)
|
|
178
|
-
}
|
|
179
|
-
}}
|
|
117
|
+
<div className="p-3 rounded-lg bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-800 space-y-2">
|
|
118
|
+
<div className="flex items-center gap-1">
|
|
119
|
+
<span className="text-sm font-bold text-amber-600 dark:text-amber-400">/</span>
|
|
120
|
+
<Input
|
|
121
|
+
value={regexpValue} className="font-mono text-sm"
|
|
122
|
+
onChange={(e) => { try { onChange(new RegExp(e.target.value)) } catch { onChange(e.target.value) } }}
|
|
180
123
|
placeholder={field.description || '请输入正则表达式'}
|
|
181
|
-
|
|
182
|
-
>
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
<Text size="1" className="text-amber-600 dark:text-amber-400 font-bold">/</Text>
|
|
188
|
-
</TextField.Slot>
|
|
189
|
-
</TextField.Root>
|
|
190
|
-
<Flex align="center" gap="2">
|
|
191
|
-
<Info className="w-3 h-3 text-amber-600 dark:text-amber-400" />
|
|
192
|
-
<Text size="1" className="text-amber-700 dark:text-amber-300">
|
|
193
|
-
输入正则表达式模式 (省略斜杠)
|
|
194
|
-
</Text>
|
|
195
|
-
</Flex>
|
|
196
|
-
</Flex>
|
|
124
|
+
/>
|
|
125
|
+
<span className="text-sm font-bold text-amber-600 dark:text-amber-400">/</span>
|
|
126
|
+
</div>
|
|
127
|
+
<p className="flex items-center gap-1 text-xs text-amber-700 dark:text-amber-300">
|
|
128
|
+
<Info className="w-3 h-3" /> 输入正则表达式模式 (省略斜杠)
|
|
129
|
+
</p>
|
|
197
130
|
</div>
|
|
198
131
|
)
|
|
199
132
|
}
|
|
200
133
|
|
|
201
134
|
export function ConstFieldRenderer({ field, value }: FieldRendererProps) {
|
|
202
135
|
const constValue = field.default || value
|
|
203
|
-
|
|
204
136
|
return (
|
|
205
|
-
<div className="p-3 rounded-lg bg-
|
|
206
|
-
<
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
{String(constValue)}
|
|
210
|
-
</Badge>
|
|
211
|
-
<Text size="1" color="gray" className="ml-auto">
|
|
212
|
-
(常量,不可修改)
|
|
213
|
-
</Text>
|
|
214
|
-
</Flex>
|
|
137
|
+
<div className="flex items-center gap-3 p-3 rounded-lg bg-muted border">
|
|
138
|
+
<Lock className="w-4 h-4 text-muted-foreground" />
|
|
139
|
+
<Badge variant="secondary" className="font-mono">{String(constValue)}</Badge>
|
|
140
|
+
<span className="text-xs text-muted-foreground ml-auto">(常量,不可修改)</span>
|
|
215
141
|
</div>
|
|
216
142
|
)
|
|
217
143
|
}
|
|
218
144
|
|
|
219
145
|
export function AnyFieldRenderer({ field, value, onChange }: FieldRendererProps) {
|
|
220
146
|
return (
|
|
221
|
-
<div className="p-3 rounded-lg bg-
|
|
222
|
-
<
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
onChange(JSON.parse(e.target.value))
|
|
235
|
-
} catch {
|
|
236
|
-
onChange(e.target.value)
|
|
237
|
-
}
|
|
238
|
-
}}
|
|
239
|
-
placeholder={field.description || '支持任意类型 (JSON 格式)'}
|
|
240
|
-
rows={4}
|
|
241
|
-
className="font-mono text-sm bg-white dark:bg-gray-950 hover:border-purple-500 dark:hover:border-purple-400 transition-colors"
|
|
242
|
-
/>
|
|
243
|
-
<Flex align="center" gap="2">
|
|
244
|
-
<Info className="w-3 h-3 text-purple-600 dark:text-purple-400" />
|
|
245
|
-
<Text size="1" className="text-purple-700 dark:text-purple-300">
|
|
246
|
-
支持: 字符串、数字、布尔值、对象、数组
|
|
247
|
-
</Text>
|
|
248
|
-
</Flex>
|
|
249
|
-
</Flex>
|
|
147
|
+
<div className="p-3 rounded-lg bg-muted/50 border space-y-2">
|
|
148
|
+
<p className="flex items-center gap-1 text-xs font-semibold text-muted-foreground">
|
|
149
|
+
<Code className="w-3 h-3" /> JSON 格式输入
|
|
150
|
+
</p>
|
|
151
|
+
<Textarea
|
|
152
|
+
value={typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value || '')}
|
|
153
|
+
onChange={(e) => { try { onChange(JSON.parse(e.target.value)) } catch { onChange(e.target.value) } }}
|
|
154
|
+
placeholder={field.description || '支持任意类型 (JSON 格式)'} rows={4} className="font-mono text-sm"
|
|
155
|
+
/>
|
|
156
|
+
<p className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
157
|
+
<Info className="w-3 h-3" /> 支持: 字符串、数字、布尔值、对象、数组
|
|
158
|
+
</p>
|
|
250
159
|
</div>
|
|
251
160
|
)
|
|
252
161
|
}
|