imean-service-engine-htmx-plugin 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.
- package/README.md +2373 -0
- package/dist/index.d.mts +1484 -0
- package/dist/index.d.ts +1484 -0
- package/dist/index.js +3099 -0
- package/dist/index.mjs +3087 -0
- package/docs/README.md +87 -0
- package/docs/architecture.md +564 -0
- package/docs/quick-reference.md +559 -0
- package/package.json +47 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# HtmxAdminPlugin 快速参考指南
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
- [插件配置](#插件配置)
|
|
6
|
+
- [模块定义](#模块定义)
|
|
7
|
+
- [数据源](#数据源)
|
|
8
|
+
- [常用方法](#常用方法)
|
|
9
|
+
- [组件速查](#组件速查)
|
|
10
|
+
- [响应头速查](#响应头速查)
|
|
11
|
+
- [常见场景](#常见场景)
|
|
12
|
+
|
|
13
|
+
## 插件配置
|
|
14
|
+
|
|
15
|
+
### 基本配置
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
const adminPlugin = new HtmxAdminPlugin({
|
|
19
|
+
title: "管理后台",
|
|
20
|
+
prefix: "/admin",
|
|
21
|
+
logo: "/logo.png", // 可选
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 完整配置
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const adminPlugin = new HtmxAdminPlugin({
|
|
29
|
+
title: "管理后台",
|
|
30
|
+
prefix: "/admin",
|
|
31
|
+
logo: "/logo.png",
|
|
32
|
+
homePath: "/admin/dashboard", // 首页路径,访问 /admin 时重定向到此路径
|
|
33
|
+
navigation: [
|
|
34
|
+
{
|
|
35
|
+
label: "用户管理",
|
|
36
|
+
moduleName: "users",
|
|
37
|
+
icon: "👥",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
getUserInfo: async (ctx) => ({
|
|
41
|
+
name: "管理员",
|
|
42
|
+
email: "admin@example.com",
|
|
43
|
+
}),
|
|
44
|
+
generateBreadcrumb: (currentPath, navItems) => [
|
|
45
|
+
{ label: "首页", href: "/admin" },
|
|
46
|
+
{ label: "当前页" },
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 模块定义
|
|
52
|
+
|
|
53
|
+
### List 模块
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
@Module("products", {
|
|
57
|
+
type: "list",
|
|
58
|
+
})
|
|
59
|
+
class ProductListModule extends ListPageModule<Product> {
|
|
60
|
+
constructor() {
|
|
61
|
+
super(productDatasource);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
renderColumn(field: string, value: any, item: Product): any {
|
|
65
|
+
// 自定义列渲染
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getActions(item: Product) {
|
|
69
|
+
return [
|
|
70
|
+
this.action.detail("查看"),
|
|
71
|
+
this.action.delete("删除"),
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Detail 模块
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
@Module("users", {
|
|
81
|
+
type: "detail",
|
|
82
|
+
})
|
|
83
|
+
class UserDetailModule extends DetailPageModule<User> {
|
|
84
|
+
constructor() {
|
|
85
|
+
super(userDatasource);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getFieldLabel(field: string): string {
|
|
89
|
+
// 自定义字段标签
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
renderField(field: string, value: any, item: User): any {
|
|
93
|
+
// 自定义字段渲染
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getFieldGroups(item: User) {
|
|
97
|
+
// 字段分组
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Form 模块
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
@Module("articles", {
|
|
106
|
+
type: "form",
|
|
107
|
+
})
|
|
108
|
+
class ArticleFormModule extends FormPageModule<Article> {
|
|
109
|
+
constructor() {
|
|
110
|
+
super(articleDatasource);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getFormFields(item: Article | null) {
|
|
114
|
+
return [
|
|
115
|
+
{ name: "title", label: "标题", type: "text", required: true },
|
|
116
|
+
{ name: "content", label: "内容", type: "textarea", required: true },
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
validateFormData(data: Record<string, any>, item: Article | null): string | null {
|
|
121
|
+
// 表单验证
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Custom 模块
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
@Module("dashboard", {
|
|
130
|
+
type: "custom",
|
|
131
|
+
})
|
|
132
|
+
class DashboardModule extends PageModule {
|
|
133
|
+
async render() {
|
|
134
|
+
return <div>自定义内容</div>;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 数据源
|
|
140
|
+
|
|
141
|
+
### 内存数据源
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
const datasource = new MemoryListDatasource<User>([
|
|
145
|
+
{ id: 1, name: "张三", email: "zhangsan@example.com" },
|
|
146
|
+
{ id: 2, name: "李四", email: "lisi@example.com" },
|
|
147
|
+
]);
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 自定义数据源
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
class DatabaseDatasource<T> implements ListDatasource<T> {
|
|
154
|
+
async getList(params: ListParams): Promise<ListResult<T>> {
|
|
155
|
+
// 数据库查询
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async deleteItem(id: string | number): Promise<boolean> {
|
|
159
|
+
// 数据库删除
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## 常用方法
|
|
165
|
+
|
|
166
|
+
### PathHelper
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// 在模块中使用(通过 this.paths 访问)
|
|
170
|
+
this.paths.detail(id) // 生成详情页路径
|
|
171
|
+
this.paths.edit(id) // 生成编辑页路径
|
|
172
|
+
this.paths.create() // 生成新建页路径
|
|
173
|
+
this.paths.delete(id) // 生成删除操作路径
|
|
174
|
+
this.paths.list() // 生成列表页路径
|
|
175
|
+
this.paths.detail(id, true) // Dialog 模式
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 错误处理
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// 在模块的 render 或 __handle 方法中
|
|
182
|
+
this.context.sendError("错误标题", "错误消息");
|
|
183
|
+
this.context.sendWarning("警告标题", "警告消息");
|
|
184
|
+
this.context.sendInfo("信息标题", "信息消息");
|
|
185
|
+
this.context.sendSuccess("成功标题", "成功消息");
|
|
186
|
+
|
|
187
|
+
// 或使用通用方法
|
|
188
|
+
this.context.sendNotification("error", "错误标题", "错误消息");
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### URL 控制
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// 设置推送 URL
|
|
195
|
+
this.context.setPushUrl("/admin/users/list");
|
|
196
|
+
|
|
197
|
+
// 设置页面刷新
|
|
198
|
+
this.context.setRefresh(true);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 组件速查
|
|
202
|
+
|
|
203
|
+
### 页面组件
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { Components } from "imean-service-engine";
|
|
207
|
+
const { Detail, Form, PageHeader, EmptyState, Dialog, ErrorAlert } = Components;
|
|
208
|
+
|
|
209
|
+
<Detail item={item} fieldLabels={{...}} actions={[...]} />
|
|
210
|
+
<Form fields={[...]} submitUrl="..." method="POST" />
|
|
211
|
+
<PageHeader title="标题" description="描述" />
|
|
212
|
+
<EmptyState message="暂无数据" />
|
|
213
|
+
<Dialog title="标题">{content}</Dialog>
|
|
214
|
+
<ErrorAlert message="错误消息" type="error" />
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 布局组件
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const { Card, Button, ActionButton, Pagination, FilterCard } = Components;
|
|
221
|
+
|
|
222
|
+
<Card>{content}</Card>
|
|
223
|
+
<Button variant="primary" href="..." hxGet="...">按钮</Button>
|
|
224
|
+
<ActionButton label="操作" href="..." method="DELETE" />
|
|
225
|
+
<Pagination page={1} totalPages={10} basePath="/admin/users/list" />
|
|
226
|
+
<FilterCard fields={[...]} />
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 表单组件
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const { FormField, Input, Textarea, Select, DateInput } = Components;
|
|
233
|
+
|
|
234
|
+
<FormField id="name" label="姓名" required>
|
|
235
|
+
<Input name="name" placeholder="请输入姓名" />
|
|
236
|
+
</FormField>
|
|
237
|
+
|
|
238
|
+
<FormField id="bio" label="简介">
|
|
239
|
+
<Textarea name="bio" rows={4} />
|
|
240
|
+
</FormField>
|
|
241
|
+
|
|
242
|
+
<FormField id="status" label="状态">
|
|
243
|
+
<Select name="status" options={[...]} />
|
|
244
|
+
</FormField>
|
|
245
|
+
|
|
246
|
+
<FormField id="date" label="日期">
|
|
247
|
+
<DateInput type="date" name="date" />
|
|
248
|
+
</FormField>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 响应头速查
|
|
252
|
+
|
|
253
|
+
### HX-Retarget
|
|
254
|
+
|
|
255
|
+
指定响应内容的目标容器:
|
|
256
|
+
|
|
257
|
+
- `#main-content`: 主内容区域(默认)
|
|
258
|
+
- `#dialog-container`: 对话框容器(dialog 模式)
|
|
259
|
+
- `#error-container`: 错误通知容器(错误响应)
|
|
260
|
+
- `#sidebar`: 侧边栏(侧边栏切换)
|
|
261
|
+
|
|
262
|
+
### HX-Push-Url
|
|
263
|
+
|
|
264
|
+
控制是否更新 URL:
|
|
265
|
+
|
|
266
|
+
- 正常模式:设置 `HX-Push-Url` 更新 URL
|
|
267
|
+
- Dialog 模式:不设置 `HX-Push-Url`,保持当前 URL
|
|
268
|
+
|
|
269
|
+
### HX-Reswap
|
|
270
|
+
|
|
271
|
+
控制交换方式:
|
|
272
|
+
|
|
273
|
+
- `innerHTML`: 替换内容(默认)
|
|
274
|
+
- `beforeend`: 追加内容(错误消息)
|
|
275
|
+
|
|
276
|
+
## 常见场景
|
|
277
|
+
|
|
278
|
+
### 场景 1: 列表页自定义列渲染
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
renderColumn(field: string, value: any, item: T): any {
|
|
282
|
+
if (field === "status") {
|
|
283
|
+
return (
|
|
284
|
+
<span className={`px-2 py-1 rounded ${getStatusColor(value)}`}>
|
|
285
|
+
{getStatusLabel(value)}
|
|
286
|
+
</span>
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 场景 2: 详情页字段分组
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
getFieldGroups(item: T) {
|
|
297
|
+
return [
|
|
298
|
+
{ title: "基本信息", fields: ["id", "name", "email"] },
|
|
299
|
+
{ title: "其他信息", fields: ["createdAt", "updatedAt"] },
|
|
300
|
+
];
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 场景 3: 表单验证
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
validateFormData(data: Record<string, any>, item: T | null): string | null {
|
|
308
|
+
if (!data.name || data.name.trim().length === 0) {
|
|
309
|
+
return "名称不能为空";
|
|
310
|
+
}
|
|
311
|
+
if (data.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
|
|
312
|
+
return "邮箱格式不正确";
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 场景 4: Dialog 模式
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// 在操作按钮中使用
|
|
322
|
+
getActions(item: T) {
|
|
323
|
+
return [
|
|
324
|
+
{ label: "查看", href: this.paths.detail(item.id) }, // 正常模式
|
|
325
|
+
{ label: "快速查看", href: this.paths.detail(item.id, true) }, // Dialog 模式
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 场景 5: 自定义页面
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
async render() {
|
|
334
|
+
const { Card, Button, PageHeader, StatCard } = Components;
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<div>
|
|
338
|
+
<PageHeader title="仪表盘" />
|
|
339
|
+
<div className="grid grid-cols-3 gap-4">
|
|
340
|
+
<StatCard title="总数" value="100" />
|
|
341
|
+
<StatCard title="今日" value="10" />
|
|
342
|
+
<StatCard title="待处理" value="5" />
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### 场景 6: 错误处理
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
async render() {
|
|
353
|
+
try {
|
|
354
|
+
const result = await processData();
|
|
355
|
+
this.context.sendSuccess("操作成功", "数据已处理");
|
|
356
|
+
return <div>{result}</div>;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
this.context.sendError("操作失败", error.message);
|
|
359
|
+
return <div>处理失败</div>;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 场景 7: 统计信息
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
async getStats(params: ListParams) {
|
|
368
|
+
const result = await this.getList({ ...params, pageSize: 1000 });
|
|
369
|
+
|
|
370
|
+
return [
|
|
371
|
+
{
|
|
372
|
+
title: "总数",
|
|
373
|
+
value: result.total,
|
|
374
|
+
iconColor: "blue" as const,
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
title: "已完成",
|
|
378
|
+
value: result.items.filter(item => item.status === "completed").length,
|
|
379
|
+
change: 12.5,
|
|
380
|
+
changeLabel: "%",
|
|
381
|
+
iconColor: "green" as const,
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### 场景 8: 筛选器
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
getFilters(params: ListParams) {
|
|
391
|
+
return [
|
|
392
|
+
{
|
|
393
|
+
name: "status",
|
|
394
|
+
label: "状态",
|
|
395
|
+
options: [
|
|
396
|
+
{ value: "pending", label: "待处理" },
|
|
397
|
+
{ value: "completed", label: "已完成" },
|
|
398
|
+
],
|
|
399
|
+
value: params.filters?.status,
|
|
400
|
+
},
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## 类型定义速查
|
|
406
|
+
|
|
407
|
+
### 模块选项
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
interface HtmxAdminModuleOptions {
|
|
411
|
+
type: "list" | "detail" | "form" | "custom";
|
|
412
|
+
title?: string; // 模块标题(可选,默认使用模块名)
|
|
413
|
+
description?: string; // 模块描述(可选)
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### 列表参数
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
interface ListParams {
|
|
421
|
+
page?: number;
|
|
422
|
+
pageSize?: number;
|
|
423
|
+
sortBy?: string;
|
|
424
|
+
sortOrder?: "asc" | "desc";
|
|
425
|
+
filters?: Record<string, any>;
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### 列表结果
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
interface ListResult<T> {
|
|
433
|
+
items: T[];
|
|
434
|
+
total: number;
|
|
435
|
+
page: number;
|
|
436
|
+
pageSize: number;
|
|
437
|
+
totalPages: number;
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### 表单字段
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
interface FormField {
|
|
445
|
+
name: string;
|
|
446
|
+
label: string;
|
|
447
|
+
type?: "text" | "email" | "number" | "textarea" | "select" | "date" | "datetime-local";
|
|
448
|
+
required?: boolean;
|
|
449
|
+
placeholder?: string;
|
|
450
|
+
options?: Array<{ value: string | number; label: string }>;
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## 路由速查
|
|
455
|
+
|
|
456
|
+
### List 模块
|
|
457
|
+
|
|
458
|
+
- `GET /{basePath}/list`: 列表页
|
|
459
|
+
- `DELETE /{basePath}/:id`: 删除操作
|
|
460
|
+
|
|
461
|
+
### Detail 模块
|
|
462
|
+
|
|
463
|
+
- `GET /{basePath}/detail/:id`: 详情页
|
|
464
|
+
- `DELETE /{basePath}/detail/:id`: 删除操作
|
|
465
|
+
|
|
466
|
+
### Form 模块
|
|
467
|
+
|
|
468
|
+
- `GET /{basePath}/new`: 新建表单
|
|
469
|
+
- `POST /{basePath}`: 创建操作
|
|
470
|
+
- `GET /{basePath}/edit/:id`: 编辑表单
|
|
471
|
+
- `PUT /{basePath}/:id`: 更新操作
|
|
472
|
+
- `DELETE /{basePath}/:id`: 删除操作(如果数据源支持)
|
|
473
|
+
|
|
474
|
+
### Dialog 模式
|
|
475
|
+
|
|
476
|
+
- `GET /{basePath}/detail/:id?dialog=true`: 对话框详情
|
|
477
|
+
- `GET /{basePath}/new?dialog=true`: 对话框新建
|
|
478
|
+
- `GET /{basePath}/edit/:id?dialog=true`: 对话框编辑
|
|
479
|
+
|
|
480
|
+
## 调试技巧
|
|
481
|
+
|
|
482
|
+
### 1. 查看响应头
|
|
483
|
+
|
|
484
|
+
在浏览器开发者工具的 Network 标签中查看响应头:
|
|
485
|
+
|
|
486
|
+
- `HX-Retarget`: 响应目标
|
|
487
|
+
- `HX-Push-Url`: URL 更新
|
|
488
|
+
- `HX-Reswap`: 交换方式
|
|
489
|
+
|
|
490
|
+
### 2. 查看 HTMX 请求
|
|
491
|
+
|
|
492
|
+
HTMX 请求会包含以下请求头:
|
|
493
|
+
|
|
494
|
+
- `HX-Request: true`: 标识 HTMX 请求
|
|
495
|
+
- `HX-Target`: 请求目标(如果指定)
|
|
496
|
+
- `Referer`: 来源页面
|
|
497
|
+
|
|
498
|
+
### 3. 查看 OOB 元素
|
|
499
|
+
|
|
500
|
+
在响应 HTML 中查找 `hx-swap-oob` 属性:
|
|
501
|
+
|
|
502
|
+
```html
|
|
503
|
+
<div id="nav-item-xxx" hx-swap-oob="true">...</div>
|
|
504
|
+
<div id="error-container" hx-swap-oob="innerHTML"></div>
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### 4. 调试 Dialog
|
|
508
|
+
|
|
509
|
+
- 检查 URL 是否包含 `dialog=true`
|
|
510
|
+
- 检查 `#dialog-container` 是否存在
|
|
511
|
+
- 检查响应头 `HX-Retarget` 是否为 `#dialog-container`
|
|
512
|
+
|
|
513
|
+
### 5. 调试错误
|
|
514
|
+
|
|
515
|
+
- 检查错误响应状态码
|
|
516
|
+
- 检查 `#error-container` 是否显示错误
|
|
517
|
+
- 检查错误消息格式是否正确
|
|
518
|
+
|
|
519
|
+
## 常见问题
|
|
520
|
+
|
|
521
|
+
### Q: Dialog 模式不工作?
|
|
522
|
+
|
|
523
|
+
A: 检查:
|
|
524
|
+
1. URL 是否包含 `dialog=true`
|
|
525
|
+
2. 响应头 `HX-Retarget` 是否为 `#dialog-container`
|
|
526
|
+
3. `#dialog-container` 是否存在于 DOM 中
|
|
527
|
+
|
|
528
|
+
### Q: 错误消息不显示?
|
|
529
|
+
|
|
530
|
+
A: 检查:
|
|
531
|
+
1. 是否使用 `createErrorResponse`
|
|
532
|
+
2. 响应头 `HX-Retarget` 是否为 `#error-container`
|
|
533
|
+
3. `#error-container` 是否存在于 DOM 中
|
|
534
|
+
|
|
535
|
+
### Q: 导航不更新?
|
|
536
|
+
|
|
537
|
+
A: 检查:
|
|
538
|
+
1. OOB 响应是否包含导航更新元素
|
|
539
|
+
2. 导航项的 `id` 是否正确
|
|
540
|
+
3. `hx-swap-oob` 属性是否正确
|
|
541
|
+
|
|
542
|
+
### Q: URL 状态不保存?
|
|
543
|
+
|
|
544
|
+
A: 检查:
|
|
545
|
+
1. 是否设置了 `HX-Push-Url` 响应头
|
|
546
|
+
2. URL 查询参数是否正确构建
|
|
547
|
+
3. 筛选器和分页是否使用正确的参数名
|
|
548
|
+
|
|
549
|
+
## 最佳实践
|
|
550
|
+
|
|
551
|
+
1. **使用 PathHelper**: 通过 `this.paths` 简化路径生成
|
|
552
|
+
2. **统一错误处理**: 使用 `context.sendNotification()` 发送通知
|
|
553
|
+
3. **类型安全**: 充分利用 TypeScript 类型
|
|
554
|
+
4. **组件复用**: 使用 `Components` 命名空间中的组件
|
|
555
|
+
5. **响应式设计**: 使用 Tailwind CSS 响应式类
|
|
556
|
+
6. **性能优化**: 使用分页和筛选减少数据量
|
|
557
|
+
7. **安全性**: 在数据源层面实现权限检查
|
|
558
|
+
8. **上下文访问**: 通过 `this.context` 访问请求状态和 helper 方法
|
|
559
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "imean-service-engine-htmx-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "HtmxAdminPlugin for IMean Service Engine",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsup",
|
|
9
|
+
"dev": "tsx examples/index.ts",
|
|
10
|
+
"test": "vitest --run",
|
|
11
|
+
"test:ui": "vitest --ui",
|
|
12
|
+
"test:coverage": "vitest --run --coverage",
|
|
13
|
+
"prepublishOnly": "npm run build && npm run test"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"microservice",
|
|
17
|
+
"hono",
|
|
18
|
+
"framework",
|
|
19
|
+
"typescript"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"docs",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@hono/node-server": "^1.19.7",
|
|
31
|
+
"hono": "^4.10.8",
|
|
32
|
+
"imean-service-engine": "^2.3.0",
|
|
33
|
+
"prettier": "^3.7.4",
|
|
34
|
+
"winston": "^3.19.0",
|
|
35
|
+
"zod": "^4.1.13"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^25.0.1",
|
|
39
|
+
"@vitest/coverage-v8": "4.0.15",
|
|
40
|
+
"@vitest/ui": "^4.0.15",
|
|
41
|
+
"imean-service-client": "^1.6.0",
|
|
42
|
+
"tsup": "^8.5.1",
|
|
43
|
+
"tsx": "^4.21.0",
|
|
44
|
+
"typescript": "^5.9.3",
|
|
45
|
+
"vitest": "^4.0.15"
|
|
46
|
+
}
|
|
47
|
+
}
|