imean-service-engine-htmx-plugin 1.0.1 → 1.1.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 +66 -32
- package/dist/index.d.mts +129 -3
- package/dist/index.d.ts +129 -3
- package/dist/index.js +1386 -657
- package/dist/index.mjs +1387 -658
- package/docs/architecture.md +57 -11
- package/docs/quick-reference.md +12 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,12 +2,689 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('hono/jsx/jsx-runtime');
|
|
4
4
|
var imeanServiceEngine = require('imean-service-engine');
|
|
5
|
+
var cookie = require('hono/cookie');
|
|
5
6
|
|
|
6
7
|
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
7
12
|
var __export = (target, all) => {
|
|
8
13
|
for (var name in all)
|
|
9
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
15
|
};
|
|
16
|
+
var Button;
|
|
17
|
+
var init_Button = __esm({
|
|
18
|
+
"src/components/Button.tsx"() {
|
|
19
|
+
Button = (props) => {
|
|
20
|
+
const {
|
|
21
|
+
children,
|
|
22
|
+
variant = "primary",
|
|
23
|
+
size = "md",
|
|
24
|
+
disabled = false,
|
|
25
|
+
className = "",
|
|
26
|
+
hxGet,
|
|
27
|
+
hxPost,
|
|
28
|
+
hxPut,
|
|
29
|
+
hxDelete,
|
|
30
|
+
hxTarget,
|
|
31
|
+
hxSwap,
|
|
32
|
+
hxPushUrl,
|
|
33
|
+
hxIndicator,
|
|
34
|
+
hxConfirm,
|
|
35
|
+
hxHeaders,
|
|
36
|
+
...rest
|
|
37
|
+
} = props;
|
|
38
|
+
const baseClasses = "inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2";
|
|
39
|
+
const variantClasses = {
|
|
40
|
+
primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
|
|
41
|
+
secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500",
|
|
42
|
+
danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
|
|
43
|
+
ghost: "bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-500"
|
|
44
|
+
};
|
|
45
|
+
const sizeClasses = {
|
|
46
|
+
sm: "px-3 py-1.5 text-sm",
|
|
47
|
+
md: "px-4 py-2 text-sm",
|
|
48
|
+
lg: "px-6 py-3 text-base"
|
|
49
|
+
};
|
|
50
|
+
const classes = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${disabled ? "opacity-50 cursor-not-allowed" : ""} ${className}`;
|
|
51
|
+
const htmxAttrs = {};
|
|
52
|
+
if (hxGet) htmxAttrs["hx-get"] = hxGet;
|
|
53
|
+
if (hxPost) htmxAttrs["hx-post"] = hxPost;
|
|
54
|
+
if (hxPut) htmxAttrs["hx-put"] = hxPut;
|
|
55
|
+
if (hxDelete) htmxAttrs["hx-delete"] = hxDelete;
|
|
56
|
+
if (hxTarget) htmxAttrs["hx-target"] = hxTarget;
|
|
57
|
+
if (hxSwap) htmxAttrs["hx-swap"] = hxSwap;
|
|
58
|
+
if (hxPushUrl !== void 0)
|
|
59
|
+
htmxAttrs["hx-push-url"] = hxPushUrl === true ? "true" : hxPushUrl;
|
|
60
|
+
if (hxIndicator) htmxAttrs["hx-indicator"] = hxIndicator;
|
|
61
|
+
if (hxConfirm) htmxAttrs["hx-confirm"] = hxConfirm;
|
|
62
|
+
if (hxHeaders) htmxAttrs["hx-headers"] = hxHeaders;
|
|
63
|
+
const Tag = hxGet || hxPost || hxPut || hxDelete ? "a" : "button";
|
|
64
|
+
const href = rest.href ?? hxGet ?? "#";
|
|
65
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
66
|
+
Tag,
|
|
67
|
+
{
|
|
68
|
+
className: classes,
|
|
69
|
+
disabled,
|
|
70
|
+
href,
|
|
71
|
+
...htmxAttrs,
|
|
72
|
+
...rest,
|
|
73
|
+
children
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
var Card;
|
|
80
|
+
var init_Card = __esm({
|
|
81
|
+
"src/components/Card.tsx"() {
|
|
82
|
+
Card = (props) => {
|
|
83
|
+
const {
|
|
84
|
+
children,
|
|
85
|
+
title,
|
|
86
|
+
className = "",
|
|
87
|
+
shadow = true,
|
|
88
|
+
bordered = false,
|
|
89
|
+
noPadding = false
|
|
90
|
+
} = props;
|
|
91
|
+
const baseClasses = "bg-white rounded-lg";
|
|
92
|
+
const shadowClass = shadow ? "shadow-sm hover:shadow-md transition-shadow" : "";
|
|
93
|
+
const borderClass = bordered ? "border border-gray-200" : "";
|
|
94
|
+
const paddingClass = noPadding ? "" : "p-6";
|
|
95
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
96
|
+
"div",
|
|
97
|
+
{
|
|
98
|
+
className: `${baseClasses} ${shadowClass} ${borderClass} ${className}`,
|
|
99
|
+
children: [
|
|
100
|
+
title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title }) }),
|
|
101
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: noPadding ? "" : paddingClass, children })
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
var Breadcrumb;
|
|
109
|
+
var init_Breadcrumb = __esm({
|
|
110
|
+
"src/components/Breadcrumb.tsx"() {
|
|
111
|
+
Breadcrumb = (props) => {
|
|
112
|
+
const { items } = props;
|
|
113
|
+
if (items.length === 0) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "flex", "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "flex items-center space-x-2", children: items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center", children: [
|
|
117
|
+
index > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
118
|
+
"svg",
|
|
119
|
+
{
|
|
120
|
+
className: "w-5 h-5 text-gray-400 mx-2",
|
|
121
|
+
fill: "currentColor",
|
|
122
|
+
viewBox: "0 0 20 20",
|
|
123
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
124
|
+
"path",
|
|
125
|
+
{
|
|
126
|
+
fillRule: "evenodd",
|
|
127
|
+
d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z",
|
|
128
|
+
clipRule: "evenodd"
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
),
|
|
133
|
+
item.href ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
134
|
+
"a",
|
|
135
|
+
{
|
|
136
|
+
href: item.href,
|
|
137
|
+
className: "text-sm font-medium text-gray-500 hover:text-gray-700",
|
|
138
|
+
"hx-get": item.href,
|
|
139
|
+
"hx-push-url": "true",
|
|
140
|
+
children: item.label
|
|
141
|
+
}
|
|
142
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-900", children: item.label })
|
|
143
|
+
] }, index)) }) });
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
var Header;
|
|
148
|
+
var init_Header = __esm({
|
|
149
|
+
"src/components/Header.tsx"() {
|
|
150
|
+
init_Breadcrumb();
|
|
151
|
+
Header = (props) => {
|
|
152
|
+
const { breadcrumbs = [], userInfo, sidebarCollapsed = false } = props;
|
|
153
|
+
return /* @__PURE__ */ jsxRuntime.jsx("header", { className: "bg-white border-b border-gray-200 shadow-sm sticky top-0 z-40", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
154
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4 flex-1 min-w-0", children: [
|
|
155
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
156
|
+
"a",
|
|
157
|
+
{
|
|
158
|
+
className: "p-2 rounded-lg hover:bg-gray-100 transition-colors inline-block flex-shrink-0",
|
|
159
|
+
title: sidebarCollapsed ? "\u5C55\u5F00\u4FA7\u8FB9\u680F" : "\u6298\u53E0\u4FA7\u8FB9\u680F",
|
|
160
|
+
"hx-get": "",
|
|
161
|
+
children: sidebarCollapsed ? (
|
|
162
|
+
// 展开图标(向右箭头)
|
|
163
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
164
|
+
"svg",
|
|
165
|
+
{
|
|
166
|
+
className: "w-5 h-5 text-gray-600",
|
|
167
|
+
fill: "none",
|
|
168
|
+
stroke: "currentColor",
|
|
169
|
+
viewBox: "0 0 24 24",
|
|
170
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
171
|
+
"path",
|
|
172
|
+
{
|
|
173
|
+
strokeLinecap: "round",
|
|
174
|
+
strokeLinejoin: "round",
|
|
175
|
+
strokeWidth: 2,
|
|
176
|
+
d: "M9 5l7 7-7 7"
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
) : (
|
|
182
|
+
// 折叠图标(三条横线)
|
|
183
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
184
|
+
"svg",
|
|
185
|
+
{
|
|
186
|
+
className: "w-5 h-5 text-gray-600",
|
|
187
|
+
fill: "none",
|
|
188
|
+
stroke: "currentColor",
|
|
189
|
+
viewBox: "0 0 24 24",
|
|
190
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
191
|
+
"path",
|
|
192
|
+
{
|
|
193
|
+
strokeLinecap: "round",
|
|
194
|
+
strokeLinejoin: "round",
|
|
195
|
+
strokeWidth: 2,
|
|
196
|
+
d: "M4 6h16M4 12h16M4 18h16"
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
breadcrumbs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Breadcrumb, { items: breadcrumbs })
|
|
205
|
+
] }),
|
|
206
|
+
userInfo && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [
|
|
207
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right hidden sm:block", children: [
|
|
208
|
+
userInfo.name && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-900", children: userInfo.name }),
|
|
209
|
+
userInfo.email && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: userInfo.email })
|
|
210
|
+
] }),
|
|
211
|
+
userInfo.avatar ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
212
|
+
"img",
|
|
213
|
+
{
|
|
214
|
+
src: userInfo.avatar,
|
|
215
|
+
alt: userInfo.name || "\u7528\u6237",
|
|
216
|
+
className: "w-8 h-8 rounded-full"
|
|
217
|
+
}
|
|
218
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-sm font-medium", children: (userInfo.name || userInfo.email || "U").charAt(0).toUpperCase() })
|
|
219
|
+
] })
|
|
220
|
+
] }) }) });
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
var LoadingBar;
|
|
225
|
+
var init_LoadingBar = __esm({
|
|
226
|
+
"src/components/LoadingBar.tsx"() {
|
|
227
|
+
LoadingBar = () => {
|
|
228
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
229
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
230
|
+
"div",
|
|
231
|
+
{
|
|
232
|
+
id: "loading-bar",
|
|
233
|
+
className: "fixed top-0 left-0 right-0 h-1 bg-transparent z-50 opacity-0 transition-opacity duration-200",
|
|
234
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full bg-gradient-to-r from-blue-500 via-blue-600 to-blue-500 loading-bar-progress" })
|
|
235
|
+
}
|
|
236
|
+
),
|
|
237
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: `
|
|
238
|
+
@keyframes loading-progress {
|
|
239
|
+
0% { transform: translateX(-100%); width: 0%; }
|
|
240
|
+
50% { width: 70%; }
|
|
241
|
+
100% { transform: translateX(100%); width: 100%; }
|
|
242
|
+
}
|
|
243
|
+
#loading-bar.htmx-request {
|
|
244
|
+
opacity: 1 !important;
|
|
245
|
+
}
|
|
246
|
+
#loading-bar.htmx-request .loading-bar-progress {
|
|
247
|
+
animation: loading-progress 1.5s ease-in-out infinite;
|
|
248
|
+
}
|
|
249
|
+
` })
|
|
250
|
+
] });
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
function renderNavItem(item, currentPath, collapsed = false, isChild = false) {
|
|
255
|
+
const isActive = currentPath === item.href || currentPath && currentPath.startsWith(item.href + "/");
|
|
256
|
+
const hasActiveChild = item.children?.some(
|
|
257
|
+
(child) => currentPath === child.href || currentPath && currentPath.startsWith(child.href + "/")
|
|
258
|
+
);
|
|
259
|
+
const navItemId = `nav-item-${item.href.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
|
260
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("li", { id: navItemId, className: "relative group", children: [
|
|
261
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
262
|
+
"a",
|
|
263
|
+
{
|
|
264
|
+
href: item.href,
|
|
265
|
+
className: `flex items-center ${collapsed ? "justify-center px-2" : "px-4"} py-2 rounded-lg transition-colors ${isActive || hasActiveChild ? "bg-blue-600 text-white shadow-md" : "text-gray-300 hover:bg-gray-700 hover:text-white"}`,
|
|
266
|
+
"hx-get": item.href,
|
|
267
|
+
"hx-push-url": "true",
|
|
268
|
+
title: collapsed ? item.label : void 0,
|
|
269
|
+
children: [
|
|
270
|
+
item.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: collapsed ? "" : "mr-2", children: item.icon }),
|
|
271
|
+
!collapsed && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-nowrap overflow-hidden text-ellipsis", children: item.label })
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
),
|
|
275
|
+
collapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute left-full ml-2 px-3 py-2 bg-gray-900 text-white text-sm rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 pointer-events-none z-50 whitespace-nowrap", children: [
|
|
276
|
+
item.label,
|
|
277
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-full top-1/2 -translate-y-1/2 border-4 border-transparent border-r-gray-900" })
|
|
278
|
+
] }),
|
|
279
|
+
!collapsed && item.children && item.children.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "ml-4 mt-1 space-y-1", children: item.children.map((child) => {
|
|
280
|
+
const isChildActive = currentPath === child.href || currentPath && currentPath.startsWith(child.href + "/");
|
|
281
|
+
const childNavItemId = `nav-item-${child.href.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
|
282
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
283
|
+
"li",
|
|
284
|
+
{
|
|
285
|
+
id: childNavItemId,
|
|
286
|
+
className: "relative group",
|
|
287
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
288
|
+
"a",
|
|
289
|
+
{
|
|
290
|
+
href: child.href,
|
|
291
|
+
className: `flex items-center px-4 py-2 rounded-lg text-sm transition-colors ${isChildActive ? "bg-blue-500 text-white" : "text-gray-400 hover:bg-gray-700 hover:text-white"}`,
|
|
292
|
+
"hx-get": child.href,
|
|
293
|
+
"hx-push-url": "true",
|
|
294
|
+
children: [
|
|
295
|
+
child.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-2", children: child.icon }),
|
|
296
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-nowrap overflow-hidden text-ellipsis", children: child.label })
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
},
|
|
301
|
+
child.href
|
|
302
|
+
);
|
|
303
|
+
}) })
|
|
304
|
+
] }, item.href);
|
|
305
|
+
}
|
|
306
|
+
var init_NavItem = __esm({
|
|
307
|
+
"src/components/NavItem.tsx"() {
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
var BaseLayout, AdminLayout;
|
|
311
|
+
var init_Layout = __esm({
|
|
312
|
+
"src/components/Layout.tsx"() {
|
|
313
|
+
init_Header();
|
|
314
|
+
init_LoadingBar();
|
|
315
|
+
init_NavItem();
|
|
316
|
+
BaseLayout = (props) => {
|
|
317
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("html", { children: [
|
|
318
|
+
/* @__PURE__ */ jsxRuntime.jsxs("head", { children: [
|
|
319
|
+
/* @__PURE__ */ jsxRuntime.jsx("meta", { charset: "UTF-8" }),
|
|
320
|
+
/* @__PURE__ */ jsxRuntime.jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
|
|
321
|
+
/* @__PURE__ */ jsxRuntime.jsx("title", { children: props.title }),
|
|
322
|
+
props.description && /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "description", content: props.description }),
|
|
323
|
+
/* @__PURE__ */ jsxRuntime.jsx("script", { src: "https://unpkg.com/htmx.org@latest" }),
|
|
324
|
+
/* @__PURE__ */ jsxRuntime.jsx("script", { src: "https://unpkg.com/hyperscript.org@latest" }),
|
|
325
|
+
/* @__PURE__ */ jsxRuntime.jsx("script", { src: "https://cdn.tailwindcss.com" }),
|
|
326
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
327
|
+
"style",
|
|
328
|
+
{
|
|
329
|
+
dangerouslySetInnerHTML: {
|
|
330
|
+
__html: `
|
|
331
|
+
@keyframes fadeIn {
|
|
332
|
+
from { opacity: 0;}
|
|
333
|
+
to { opacity: 1;}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
@keyframes slideIn {
|
|
337
|
+
from { opacity: 0; transform: scale(0.95) translateY(-10px); }
|
|
338
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@keyframes slideInRight {
|
|
342
|
+
from { opacity: 0; transform: translateX(100%); }
|
|
343
|
+
to { opacity: 1; transform: translateX(0); }
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@keyframes slideOutRight {
|
|
347
|
+
from { opacity: 1; transform: translateX(0); }
|
|
348
|
+
to { opacity: 0; transform: translateX(100%); }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@keyframes fadeOut {
|
|
352
|
+
from { opacity: 1; }
|
|
353
|
+
to { opacity: 0; }
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
@keyframes scaleOut {
|
|
357
|
+
from { opacity: 1; transform: scale(1) translateY(0); }
|
|
358
|
+
to { opacity: 0; transform: scale(0.95) translateY(-10px); }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Dialog \u9000\u51FA\u52A8\u753B */
|
|
362
|
+
.dialog-exit {
|
|
363
|
+
animation: fadeOut 0.2s ease-in forwards !important;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.dialog-content-exit {
|
|
367
|
+
animation: scaleOut 0.2s ease-in forwards !important;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/* ErrorAlert \u9000\u51FA\u52A8\u753B */
|
|
371
|
+
.error-alert-exit {
|
|
372
|
+
animation: slideOutRight 0.3s ease-in forwards, fadeOut 0.3s ease-in forwards;
|
|
373
|
+
}
|
|
374
|
+
`
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
] }),
|
|
379
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
380
|
+
"body",
|
|
381
|
+
{
|
|
382
|
+
className: "bg-gray-50",
|
|
383
|
+
"hx-indicator": "#loading-bar",
|
|
384
|
+
"hx-target": "#main-content",
|
|
385
|
+
"hx-swap": "outerHTML",
|
|
386
|
+
children: [
|
|
387
|
+
/* @__PURE__ */ jsxRuntime.jsx(LoadingBar, {}),
|
|
388
|
+
props.children,
|
|
389
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
390
|
+
"div",
|
|
391
|
+
{
|
|
392
|
+
id: "error-container",
|
|
393
|
+
className: "fixed top-4 right-4 z-[200] w-full max-w-2xl px-4"
|
|
394
|
+
}
|
|
395
|
+
),
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { id: "dialog-container" })
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
)
|
|
400
|
+
] });
|
|
401
|
+
};
|
|
402
|
+
AdminLayout = (props) => {
|
|
403
|
+
const logo = props.adminContext.pluginOptions.logo;
|
|
404
|
+
const navItems = props.adminContext.pluginOptions.navigation;
|
|
405
|
+
const referer = props.adminContext.ctx.req.header("Referer");
|
|
406
|
+
let currentPath = props.adminContext.ctx.req.path;
|
|
407
|
+
if (referer) {
|
|
408
|
+
try {
|
|
409
|
+
const refererUrl = new URL(referer);
|
|
410
|
+
const method = props.adminContext.ctx.req.method;
|
|
411
|
+
if (["POST", "PUT", "DELETE"].includes(method)) {
|
|
412
|
+
currentPath = refererUrl.pathname;
|
|
413
|
+
}
|
|
414
|
+
} catch (e) {
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const sidebarCollapsed = props.sidebarCollapsed || false;
|
|
418
|
+
const breadcrumbs = props.adminContext.breadcrumbs;
|
|
419
|
+
const userInfo = props.adminContext.userInfo;
|
|
420
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-screen", id: "main-content", children: [
|
|
421
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
422
|
+
"aside",
|
|
423
|
+
{
|
|
424
|
+
className: `${props.sidebarCollapsed ? "w-16" : "w-64"} bg-gradient-to-b from-gray-900 to-gray-800 text-white shadow-lg transition-all duration-300 ease-in-out overflow-hidden`,
|
|
425
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${props.sidebarCollapsed ? "p-2" : "p-6"}`, children: [
|
|
426
|
+
!props.sidebarCollapsed && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: logo ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logo, alt: "Logo", className: "h-10 mb-6" }) : /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-bold mb-6 text-white whitespace-nowrap overflow-hidden text-ellipsis", children: props.adminContext.pluginOptions.title }) }),
|
|
427
|
+
/* @__PURE__ */ jsxRuntime.jsx("nav", { children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "space-y-1", children: navItems && navItems.length > 0 ? navItems.map(
|
|
428
|
+
(item) => renderNavItem(
|
|
429
|
+
item,
|
|
430
|
+
currentPath,
|
|
431
|
+
props.sidebarCollapsed || false
|
|
432
|
+
)
|
|
433
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
434
|
+
"li",
|
|
435
|
+
{
|
|
436
|
+
className: `${props.sidebarCollapsed ? "px-2" : "px-4"} py-2 text-gray-400 text-sm`,
|
|
437
|
+
children: "\u6682\u65E0\u5BFC\u822A\u9879"
|
|
438
|
+
}
|
|
439
|
+
) }) })
|
|
440
|
+
] })
|
|
441
|
+
}
|
|
442
|
+
),
|
|
443
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
|
|
444
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
445
|
+
Header,
|
|
446
|
+
{
|
|
447
|
+
breadcrumbs,
|
|
448
|
+
userInfo,
|
|
449
|
+
sidebarCollapsed: sidebarCollapsed || false
|
|
450
|
+
}
|
|
451
|
+
),
|
|
452
|
+
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: props.children }) })
|
|
453
|
+
] })
|
|
454
|
+
] });
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
// src/utils/context.tsx
|
|
460
|
+
var context_exports = {};
|
|
461
|
+
__export(context_exports, {
|
|
462
|
+
HtmxAdminContext: () => HtmxAdminContext
|
|
463
|
+
});
|
|
464
|
+
var HtmxAdminContext;
|
|
465
|
+
var init_context = __esm({
|
|
466
|
+
"src/utils/context.tsx"() {
|
|
467
|
+
HtmxAdminContext = class {
|
|
468
|
+
/** 模块元数据 */
|
|
469
|
+
moduleMetadata;
|
|
470
|
+
/** 插件选项 */
|
|
471
|
+
pluginOptions;
|
|
472
|
+
/** 服务名(用于生成权限ID) */
|
|
473
|
+
serviceName;
|
|
474
|
+
/** Hono Context */
|
|
475
|
+
ctx;
|
|
476
|
+
/** 之前的模块名 */
|
|
477
|
+
previousModuleName;
|
|
478
|
+
/** 是否是片段请求(HTMX 请求) */
|
|
479
|
+
isFragment;
|
|
480
|
+
/** 是否是对话框请求 */
|
|
481
|
+
isDialog;
|
|
482
|
+
/** 用户信息 */
|
|
483
|
+
userInfo;
|
|
484
|
+
/** 通知队列 */
|
|
485
|
+
notifications = [];
|
|
486
|
+
/** 页面标题(用于 HX-Title 和页面展示) */
|
|
487
|
+
title = "";
|
|
488
|
+
/** 页面描述(用于SEO和页面展示) */
|
|
489
|
+
description = "";
|
|
490
|
+
/** 面包屑项 */
|
|
491
|
+
breadcrumbs = [];
|
|
492
|
+
/** 主要内容 */
|
|
493
|
+
content = null;
|
|
494
|
+
/** 需要重定向的 URL */
|
|
495
|
+
redirectUrl;
|
|
496
|
+
/** 是否需要刷新页面(用于 HX-Refresh) */
|
|
497
|
+
refresh = false;
|
|
498
|
+
constructor(ctx, userInfo, moduleMetadata, pluginOptions, serviceName = "") {
|
|
499
|
+
this.ctx = ctx;
|
|
500
|
+
this.userInfo = userInfo;
|
|
501
|
+
this.moduleMetadata = moduleMetadata;
|
|
502
|
+
this.pluginOptions = pluginOptions;
|
|
503
|
+
this.serviceName = serviceName;
|
|
504
|
+
const url = new URL(ctx.req.url);
|
|
505
|
+
this.isFragment = ctx.req.header("HX-Request") === "true";
|
|
506
|
+
this.isDialog = url.searchParams.get("dialog") === "true" || ctx.req.header("HX-Target") === "#dialog-container" || (ctx.req.header("Referer") || "").includes("dialog=true");
|
|
507
|
+
const referer = ctx.req.header("Referer");
|
|
508
|
+
this.previousModuleName = referer ? new URL(referer).pathname.replace(pluginOptions.prefix, "").split("/").pop() : void 0;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* 发送通知
|
|
512
|
+
* 通知将在响应时通过 OOB 更新到错误容器
|
|
513
|
+
*/
|
|
514
|
+
sendNotification(type, title, message) {
|
|
515
|
+
this.notifications.push({ type, title, message });
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* 发送错误通知(便捷方法)
|
|
519
|
+
*/
|
|
520
|
+
sendError(title, message) {
|
|
521
|
+
this.sendNotification("error", title, message);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* 发送警告通知(便捷方法)
|
|
525
|
+
*/
|
|
526
|
+
sendWarning(title, message) {
|
|
527
|
+
this.sendNotification("warning", title, message);
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* 发送信息通知(便捷方法)
|
|
531
|
+
*/
|
|
532
|
+
sendInfo(title, message) {
|
|
533
|
+
this.sendNotification("info", title, message);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* 发送成功通知(便捷方法)
|
|
537
|
+
*/
|
|
538
|
+
sendSuccess(title, message) {
|
|
539
|
+
this.sendNotification("success", title, message);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* 设置需要推送的 URL
|
|
543
|
+
* 用于 HX-Push-Url 响应头
|
|
544
|
+
*/
|
|
545
|
+
redirect(url) {
|
|
546
|
+
this.redirectUrl = url;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* 设置需要刷新页面
|
|
550
|
+
* 用于 HX-Refresh 响应头
|
|
551
|
+
*/
|
|
552
|
+
setRefresh(refresh = true) {
|
|
553
|
+
this.refresh = refresh;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* 检查是否有列表页面
|
|
557
|
+
* 封装 moduleMetadata 访问,避免直接访问内部属性
|
|
558
|
+
*/
|
|
559
|
+
hasList() {
|
|
560
|
+
return this.moduleMetadata.hasList;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* 检查是否有详情页面
|
|
564
|
+
* 封装 moduleMetadata 访问,避免直接访问内部属性
|
|
565
|
+
*/
|
|
566
|
+
hasDetail() {
|
|
567
|
+
return this.moduleMetadata.hasDetail;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* 检查是否有表单页面
|
|
571
|
+
* 封装 moduleMetadata 访问,避免直接访问内部属性
|
|
572
|
+
*/
|
|
573
|
+
hasForm() {
|
|
574
|
+
return this.moduleMetadata.hasForm;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* 检查是否有自定义页面
|
|
578
|
+
* 封装 moduleMetadata 访问,避免直接访问内部属性
|
|
579
|
+
*/
|
|
580
|
+
hasCustom() {
|
|
581
|
+
return this.moduleMetadata.hasCustom;
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// src/components/PermissionDenied.tsx
|
|
588
|
+
var PermissionDenied_exports = {};
|
|
589
|
+
__export(PermissionDenied_exports, {
|
|
590
|
+
PermissionDeniedContent: () => PermissionDeniedContent,
|
|
591
|
+
PermissionDeniedPage: () => PermissionDeniedPage
|
|
592
|
+
});
|
|
593
|
+
function PermissionDeniedContent(adminContext, operationId, fromPath, registeredOperations) {
|
|
594
|
+
let operationInfo = null;
|
|
595
|
+
if (operationId && registeredOperations) {
|
|
596
|
+
operationInfo = registeredOperations.find(
|
|
597
|
+
(op) => op.operationId === operationId
|
|
598
|
+
) || null;
|
|
599
|
+
}
|
|
600
|
+
const parts = operationId?.split(".") || [];
|
|
601
|
+
const operationType = parts[parts.length - 1] || "";
|
|
602
|
+
const moduleName = parts.length >= 2 ? parts[parts.length - 2] : "";
|
|
603
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
604
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-6xl mb-4", children: "\u{1F512}" }),
|
|
605
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-bold text-gray-900 mb-2", children: "\u6743\u9650\u4E0D\u8DB3" }),
|
|
606
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-8", children: "\u60A8\u5F53\u524D\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u6B64\u8D44\u6E90\u3002\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u83B7\u53D6\u76F8\u5E94\u6743\u9650\u3002" }),
|
|
607
|
+
operationId && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-8 p-4 bg-red-50 border border-red-200 rounded-lg text-left", children: [
|
|
608
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-red-900 mb-2", children: "\u88AB\u62D2\u7EDD\u7684\u64CD\u4F5C" }),
|
|
609
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 text-sm", children: [
|
|
610
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
611
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-700", children: "\u64CD\u4F5CID:" }),
|
|
612
|
+
" ",
|
|
613
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { className: "px-2 py-1 bg-gray-100 rounded text-blue-600 font-mono", children: operationId })
|
|
614
|
+
] }),
|
|
615
|
+
operationInfo && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
616
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
617
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-700", children: "\u6A21\u5757:" }),
|
|
618
|
+
" ",
|
|
619
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-900", children: operationInfo.moduleTitle || moduleName })
|
|
620
|
+
] }),
|
|
621
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
622
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-700", children: "\u64CD\u4F5C\u7C7B\u578B:" }),
|
|
623
|
+
" ",
|
|
624
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-semibold", children: getOperationTypeLabel(operationType) })
|
|
625
|
+
] }),
|
|
626
|
+
operationInfo.moduleDescription && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
627
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-700", children: "\u6A21\u5757\u63CF\u8FF0:" }),
|
|
628
|
+
" ",
|
|
629
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-600", children: operationInfo.moduleDescription })
|
|
630
|
+
] }),
|
|
631
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
632
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-700", children: "\u8DEF\u7531\u8DEF\u5F84:" }),
|
|
633
|
+
" ",
|
|
634
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { className: "px-2 py-1 bg-gray-100 rounded text-gray-700 font-mono text-xs", children: operationInfo.routePath })
|
|
635
|
+
] })
|
|
636
|
+
] }),
|
|
637
|
+
fromPath && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
638
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-700", children: "\u8BF7\u6C42\u8DEF\u5F84:" }),
|
|
639
|
+
" ",
|
|
640
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { className: "px-2 py-1 bg-gray-100 rounded text-gray-700 font-mono text-xs", children: fromPath })
|
|
641
|
+
] })
|
|
642
|
+
] })
|
|
643
|
+
] }),
|
|
644
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
645
|
+
Button,
|
|
646
|
+
{
|
|
647
|
+
variant: "secondary",
|
|
648
|
+
_: "on click set #dialog-container's innerHTML to ''",
|
|
649
|
+
children: "\u5173\u95ED"
|
|
650
|
+
}
|
|
651
|
+
) })
|
|
652
|
+
] });
|
|
653
|
+
}
|
|
654
|
+
function PermissionDeniedPage(adminContext, operationId, fromPath, registeredOperations) {
|
|
655
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
656
|
+
BaseLayout,
|
|
657
|
+
{
|
|
658
|
+
title: `\u6743\u9650\u4E0D\u8DB3 - ${adminContext.pluginOptions.title}`,
|
|
659
|
+
description: "\u60A8\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u6B64\u8D44\u6E90",
|
|
660
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { id: "main-content", className: "min-h-screen flex items-center justify-center px-4", children: /* @__PURE__ */ jsxRuntime.jsx(Card, { className: "max-w-2xl w-full p-8", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
661
|
+
PermissionDeniedContent,
|
|
662
|
+
{
|
|
663
|
+
adminContext,
|
|
664
|
+
operationId,
|
|
665
|
+
fromPath,
|
|
666
|
+
registeredOperations
|
|
667
|
+
}
|
|
668
|
+
) }) })
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
function getOperationTypeLabel(operationType) {
|
|
673
|
+
const labels = {
|
|
674
|
+
read: "\u67E5\u770B",
|
|
675
|
+
create: "\u521B\u5EFA",
|
|
676
|
+
edit: "\u7F16\u8F91",
|
|
677
|
+
delete: "\u5220\u9664"
|
|
678
|
+
};
|
|
679
|
+
return labels[operationType] || operationType;
|
|
680
|
+
}
|
|
681
|
+
var init_PermissionDenied = __esm({
|
|
682
|
+
"src/components/PermissionDenied.tsx"() {
|
|
683
|
+
init_Card();
|
|
684
|
+
init_Button();
|
|
685
|
+
init_Layout();
|
|
686
|
+
}
|
|
687
|
+
});
|
|
11
688
|
function safeRender(value) {
|
|
12
689
|
if (value === null || value === void 0) {
|
|
13
690
|
return null;
|
|
@@ -23,88 +700,10 @@ function safeRender(value) {
|
|
|
23
700
|
}
|
|
24
701
|
return value;
|
|
25
702
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
size = "md",
|
|
31
|
-
disabled = false,
|
|
32
|
-
className = "",
|
|
33
|
-
hxGet,
|
|
34
|
-
hxPost,
|
|
35
|
-
hxPut,
|
|
36
|
-
hxDelete,
|
|
37
|
-
hxTarget,
|
|
38
|
-
hxSwap,
|
|
39
|
-
hxPushUrl,
|
|
40
|
-
hxIndicator,
|
|
41
|
-
hxConfirm,
|
|
42
|
-
hxHeaders,
|
|
43
|
-
...rest
|
|
44
|
-
} = props;
|
|
45
|
-
const baseClasses = "inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2";
|
|
46
|
-
const variantClasses = {
|
|
47
|
-
primary: "bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500",
|
|
48
|
-
secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500",
|
|
49
|
-
danger: "bg-red-600 text-white hover:bg-red-700 focus:ring-red-500",
|
|
50
|
-
ghost: "bg-transparent text-gray-700 hover:bg-gray-100 focus:ring-gray-500"
|
|
51
|
-
};
|
|
52
|
-
const sizeClasses = {
|
|
53
|
-
sm: "px-3 py-1.5 text-sm",
|
|
54
|
-
md: "px-4 py-2 text-sm",
|
|
55
|
-
lg: "px-6 py-3 text-base"
|
|
56
|
-
};
|
|
57
|
-
const classes = `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${disabled ? "opacity-50 cursor-not-allowed" : ""} ${className}`;
|
|
58
|
-
const htmxAttrs = {};
|
|
59
|
-
if (hxGet) htmxAttrs["hx-get"] = hxGet;
|
|
60
|
-
if (hxPost) htmxAttrs["hx-post"] = hxPost;
|
|
61
|
-
if (hxPut) htmxAttrs["hx-put"] = hxPut;
|
|
62
|
-
if (hxDelete) htmxAttrs["hx-delete"] = hxDelete;
|
|
63
|
-
if (hxTarget) htmxAttrs["hx-target"] = hxTarget;
|
|
64
|
-
if (hxSwap) htmxAttrs["hx-swap"] = hxSwap;
|
|
65
|
-
if (hxPushUrl !== void 0)
|
|
66
|
-
htmxAttrs["hx-push-url"] = hxPushUrl === true ? "true" : hxPushUrl;
|
|
67
|
-
if (hxIndicator) htmxAttrs["hx-indicator"] = hxIndicator;
|
|
68
|
-
if (hxConfirm) htmxAttrs["hx-confirm"] = hxConfirm;
|
|
69
|
-
if (hxHeaders) htmxAttrs["hx-headers"] = hxHeaders;
|
|
70
|
-
const Tag = hxGet || hxPost || hxPut || hxDelete ? "a" : "button";
|
|
71
|
-
const href = rest.href ?? hxGet ?? "#";
|
|
72
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
73
|
-
Tag,
|
|
74
|
-
{
|
|
75
|
-
className: classes,
|
|
76
|
-
disabled,
|
|
77
|
-
href,
|
|
78
|
-
...htmxAttrs,
|
|
79
|
-
...rest,
|
|
80
|
-
children
|
|
81
|
-
}
|
|
82
|
-
);
|
|
83
|
-
};
|
|
84
|
-
var Card = (props) => {
|
|
85
|
-
const {
|
|
86
|
-
children,
|
|
87
|
-
title,
|
|
88
|
-
className = "",
|
|
89
|
-
shadow = true,
|
|
90
|
-
bordered = false,
|
|
91
|
-
noPadding = false
|
|
92
|
-
} = props;
|
|
93
|
-
const baseClasses = "bg-white rounded-lg";
|
|
94
|
-
const shadowClass = shadow ? "shadow-sm hover:shadow-md transition-shadow" : "";
|
|
95
|
-
const borderClass = bordered ? "border border-gray-200" : "";
|
|
96
|
-
const paddingClass = noPadding ? "" : "p-6";
|
|
97
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
98
|
-
"div",
|
|
99
|
-
{
|
|
100
|
-
className: `${baseClasses} ${shadowClass} ${borderClass} ${className}`,
|
|
101
|
-
children: [
|
|
102
|
-
title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title }) }),
|
|
103
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: noPadding ? "" : paddingClass, children })
|
|
104
|
-
]
|
|
105
|
-
}
|
|
106
|
-
);
|
|
107
|
-
};
|
|
703
|
+
|
|
704
|
+
// src/components/Detail.tsx
|
|
705
|
+
init_Button();
|
|
706
|
+
init_Card();
|
|
108
707
|
var PageHeader = (props) => {
|
|
109
708
|
const { title, description, actions, className = "" } = props;
|
|
110
709
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `mb-6 ${className}`, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-start", children: [
|
|
@@ -232,6 +831,14 @@ function DetailContent(props) {
|
|
|
232
831
|
);
|
|
233
832
|
}
|
|
234
833
|
|
|
834
|
+
// src/utils/module-name.ts
|
|
835
|
+
function extractModuleNameFromPath(basePath, prefix) {
|
|
836
|
+
const pathWithoutPrefix = basePath.replace(prefix, "");
|
|
837
|
+
const pathSegments = pathWithoutPrefix.split("/").filter(Boolean);
|
|
838
|
+
const modulePath = pathSegments[0] || "";
|
|
839
|
+
return modulePath.toLowerCase();
|
|
840
|
+
}
|
|
841
|
+
|
|
235
842
|
// src/utils/path.ts
|
|
236
843
|
var PathHelper = class {
|
|
237
844
|
basePath;
|
|
@@ -335,6 +942,22 @@ var PageModule = class {
|
|
|
335
942
|
];
|
|
336
943
|
return breadcrumbs;
|
|
337
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* 获取所需权限
|
|
947
|
+
* 子类可以重写此方法来声明页面所需的权限
|
|
948
|
+
*
|
|
949
|
+
* 返回值说明:
|
|
950
|
+
* - 返回空字符串 "" 或 null 或 undefined:表示开放访问(无需权限)
|
|
951
|
+
* - 返回权限字符串:表示需要该权限,如 "users.read", "users.*"
|
|
952
|
+
*
|
|
953
|
+
* 默认实现:返回空字符串(开放访问)
|
|
954
|
+
*
|
|
955
|
+
* @param ctx Hono Context(可选,用于根据请求动态决定权限)
|
|
956
|
+
* @returns 所需权限字符串,或空字符串/null/undefined 表示开放
|
|
957
|
+
*/
|
|
958
|
+
getRequiredPermission(ctx) {
|
|
959
|
+
return "";
|
|
960
|
+
}
|
|
338
961
|
/**
|
|
339
962
|
* 处理请求的统一入口(由 RouteHandler 调用)
|
|
340
963
|
* 默认实现:直接调用 render 方法
|
|
@@ -350,6 +973,22 @@ var PageModule = class {
|
|
|
350
973
|
var DetailPageModule = class extends PageModule {
|
|
351
974
|
/** ID 字段名(默认 "id") */
|
|
352
975
|
idField = "id";
|
|
976
|
+
/**
|
|
977
|
+
* 获取所需权限
|
|
978
|
+
* 默认实现:返回 "{serviceName}.{moduleName}.read"
|
|
979
|
+
* 子类可以重写此方法来自定义权限要求
|
|
980
|
+
*
|
|
981
|
+
* @param ctx Hono Context(可选)
|
|
982
|
+
* @returns 所需权限字符串,或空字符串/null/undefined 表示开放
|
|
983
|
+
*/
|
|
984
|
+
getRequiredPermission(ctx) {
|
|
985
|
+
const moduleName = extractModuleNameFromPath(
|
|
986
|
+
this.context.moduleMetadata.basePath,
|
|
987
|
+
this.context.pluginOptions.prefix
|
|
988
|
+
);
|
|
989
|
+
const servicePrefix = this.context.serviceName ? `${this.context.serviceName}.` : "";
|
|
990
|
+
return `${servicePrefix}${moduleName}.read`;
|
|
991
|
+
}
|
|
353
992
|
/**
|
|
354
993
|
* 获取字段标签(可选)
|
|
355
994
|
* 子类可以重写此方法来自定义字段的中文标签
|
|
@@ -453,6 +1092,10 @@ var DetailPageModule = class extends PageModule {
|
|
|
453
1092
|
);
|
|
454
1093
|
}
|
|
455
1094
|
};
|
|
1095
|
+
|
|
1096
|
+
// src/components/Form.tsx
|
|
1097
|
+
init_Button();
|
|
1098
|
+
init_Card();
|
|
456
1099
|
var DateInput = (props) => {
|
|
457
1100
|
const {
|
|
458
1101
|
id,
|
|
@@ -954,6 +1597,33 @@ var FormPageModule = class extends PageModule {
|
|
|
954
1597
|
super();
|
|
955
1598
|
this.schema = schema;
|
|
956
1599
|
}
|
|
1600
|
+
/**
|
|
1601
|
+
* 获取所需权限
|
|
1602
|
+
* 根据是新建还是编辑返回不同的权限要求
|
|
1603
|
+
* - 新建:{serviceName}.{moduleName}.create
|
|
1604
|
+
* - 编辑:{serviceName}.{moduleName}.edit
|
|
1605
|
+
* 子类可以重写此方法来自定义权限要求
|
|
1606
|
+
*
|
|
1607
|
+
* @param ctx Hono Context(可选)
|
|
1608
|
+
* @returns 所需权限字符串,或空字符串/null/undefined 表示开放
|
|
1609
|
+
*/
|
|
1610
|
+
getRequiredPermission(ctx) {
|
|
1611
|
+
const moduleName = extractModuleNameFromPath(
|
|
1612
|
+
this.context.moduleMetadata.basePath,
|
|
1613
|
+
this.context.pluginOptions.prefix
|
|
1614
|
+
);
|
|
1615
|
+
const servicePrefix = this.context.serviceName ? `${this.context.serviceName}.` : "";
|
|
1616
|
+
if (ctx) {
|
|
1617
|
+
const url = new URL(ctx.req.url);
|
|
1618
|
+
const path = url.pathname;
|
|
1619
|
+
if (path.includes("/new")) {
|
|
1620
|
+
return `${servicePrefix}${moduleName}.create`;
|
|
1621
|
+
} else if (path.includes("/edit/")) {
|
|
1622
|
+
return `${servicePrefix}${moduleName}.edit`;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
return `${servicePrefix}${moduleName}.edit`;
|
|
1626
|
+
}
|
|
957
1627
|
/**
|
|
958
1628
|
* 获取单条数据(编辑时使用)
|
|
959
1629
|
*/
|
|
@@ -1218,6 +1888,13 @@ var FormPageModule = class extends PageModule {
|
|
|
1218
1888
|
return await this.render(formData);
|
|
1219
1889
|
}
|
|
1220
1890
|
};
|
|
1891
|
+
|
|
1892
|
+
// src/components/ListContent.tsx
|
|
1893
|
+
init_Button();
|
|
1894
|
+
|
|
1895
|
+
// src/components/FilterCard.tsx
|
|
1896
|
+
init_Button();
|
|
1897
|
+
init_Card();
|
|
1221
1898
|
var FilterCard = (props) => {
|
|
1222
1899
|
const {
|
|
1223
1900
|
title,
|
|
@@ -1362,6 +2039,9 @@ var StatCard = (props) => {
|
|
|
1362
2039
|
}
|
|
1363
2040
|
);
|
|
1364
2041
|
};
|
|
2042
|
+
|
|
2043
|
+
// src/components/ActionButton.tsx
|
|
2044
|
+
init_Button();
|
|
1365
2045
|
var ActionButton = (props) => {
|
|
1366
2046
|
const { label, href, method, className = "", item } = props;
|
|
1367
2047
|
const hrefValue = typeof href === "function" ? href(item || {}) : href;
|
|
@@ -1384,10 +2064,17 @@ var ActionButton = (props) => {
|
|
|
1384
2064
|
}
|
|
1385
2065
|
);
|
|
1386
2066
|
};
|
|
2067
|
+
|
|
2068
|
+
// src/components/Table.tsx
|
|
2069
|
+
init_Button();
|
|
2070
|
+
init_Card();
|
|
1387
2071
|
var EmptyState = (props) => {
|
|
1388
2072
|
const { message = "\u6682\u65E0\u6570\u636E", children } = props;
|
|
1389
2073
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-12", children: children || /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 text-sm", children: message }) });
|
|
1390
2074
|
};
|
|
2075
|
+
|
|
2076
|
+
// src/components/Pagination.tsx
|
|
2077
|
+
init_Button();
|
|
1391
2078
|
var Pagination = (props) => {
|
|
1392
2079
|
const {
|
|
1393
2080
|
page,
|
|
@@ -1718,6 +2405,22 @@ function parseListParams(ctx) {
|
|
|
1718
2405
|
var ListPageModule = class extends PageModule {
|
|
1719
2406
|
/** ID 字段名(默认 "id") */
|
|
1720
2407
|
idField = "id";
|
|
2408
|
+
/**
|
|
2409
|
+
* 获取所需权限
|
|
2410
|
+
* 默认实现:返回 "{serviceName}.{moduleName}.read"
|
|
2411
|
+
* 子类可以重写此方法来自定义权限要求
|
|
2412
|
+
*
|
|
2413
|
+
* @param ctx Hono Context(可选)
|
|
2414
|
+
* @returns 所需权限字符串,或空字符串/null/undefined 表示开放
|
|
2415
|
+
*/
|
|
2416
|
+
getRequiredPermission(ctx) {
|
|
2417
|
+
const moduleName = extractModuleNameFromPath(
|
|
2418
|
+
this.context.moduleMetadata.basePath,
|
|
2419
|
+
this.context.pluginOptions.prefix
|
|
2420
|
+
);
|
|
2421
|
+
const servicePrefix = this.context.serviceName ? `${this.context.serviceName}.` : "";
|
|
2422
|
+
return `${servicePrefix}${moduleName}.read`;
|
|
2423
|
+
}
|
|
1721
2424
|
/**
|
|
1722
2425
|
* 获取列表数据
|
|
1723
2426
|
*/
|
|
@@ -1903,137 +2606,69 @@ var MemoryListDatasource = class {
|
|
|
1903
2606
|
totalPages: Math.ceil(filtered.length / pageSize)
|
|
1904
2607
|
};
|
|
1905
2608
|
}
|
|
1906
|
-
async getItem(id) {
|
|
1907
|
-
return this.data.find((item) => {
|
|
1908
|
-
if (item.id === id) {
|
|
1909
|
-
return true;
|
|
1910
|
-
}
|
|
1911
|
-
if (typeof item.id === "number" && typeof id === "string") {
|
|
1912
|
-
return item.id === Number(id);
|
|
1913
|
-
}
|
|
1914
|
-
if (typeof item.id === "string" && typeof id === "number") {
|
|
1915
|
-
return Number(item.id) === id;
|
|
1916
|
-
}
|
|
1917
|
-
return false;
|
|
1918
|
-
}) || null;
|
|
1919
|
-
}
|
|
1920
|
-
async deleteItem(id) {
|
|
1921
|
-
const index = this.data.findIndex((item) => {
|
|
1922
|
-
if (item.id === id) {
|
|
1923
|
-
return true;
|
|
1924
|
-
}
|
|
1925
|
-
if (typeof item.id === "number" && typeof id === "string") {
|
|
1926
|
-
return item.id === Number(id);
|
|
1927
|
-
}
|
|
1928
|
-
if (typeof item.id === "string" && typeof id === "number") {
|
|
1929
|
-
return Number(item.id) === id;
|
|
1930
|
-
}
|
|
1931
|
-
return false;
|
|
1932
|
-
});
|
|
1933
|
-
if (index === -1) {
|
|
1934
|
-
return false;
|
|
1935
|
-
}
|
|
1936
|
-
this.data.splice(index, 1);
|
|
1937
|
-
return true;
|
|
1938
|
-
}
|
|
1939
|
-
async updateItem(id, data) {
|
|
1940
|
-
const index = this.data.findIndex((item) => {
|
|
1941
|
-
if (item.id === id) {
|
|
1942
|
-
return true;
|
|
1943
|
-
}
|
|
1944
|
-
if (typeof item.id === "number" && typeof id === "string") {
|
|
1945
|
-
return item.id === Number(id);
|
|
1946
|
-
}
|
|
1947
|
-
if (typeof item.id === "string" && typeof id === "number") {
|
|
1948
|
-
return Number(item.id) === id;
|
|
1949
|
-
}
|
|
1950
|
-
return false;
|
|
1951
|
-
});
|
|
1952
|
-
if (index === -1) {
|
|
1953
|
-
return null;
|
|
1954
|
-
}
|
|
1955
|
-
this.data[index] = { ...this.data[index], ...data };
|
|
1956
|
-
return this.data[index];
|
|
1957
|
-
}
|
|
1958
|
-
async createItem(data) {
|
|
1959
|
-
const maxId = this.data.length > 0 ? Math.max(
|
|
1960
|
-
...this.data.map(
|
|
1961
|
-
(item) => typeof item.id === "number" ? item.id : 0
|
|
1962
|
-
)
|
|
1963
|
-
) : 0;
|
|
1964
|
-
const newId = maxId + 1;
|
|
1965
|
-
const newItem = { ...data, id: newId };
|
|
1966
|
-
this.data.push(newItem);
|
|
1967
|
-
return newItem;
|
|
1968
|
-
}
|
|
1969
|
-
};
|
|
1970
|
-
var Dialog = (props) => {
|
|
1971
|
-
const {
|
|
1972
|
-
title,
|
|
1973
|
-
children,
|
|
1974
|
-
showClose = true,
|
|
1975
|
-
closeUrl,
|
|
1976
|
-
className = "",
|
|
1977
|
-
size = "lg"
|
|
1978
|
-
} = props;
|
|
1979
|
-
const sizeClasses = {
|
|
1980
|
-
sm: "max-w-md",
|
|
1981
|
-
md: "max-w-lg",
|
|
1982
|
-
lg: "max-w-2xl",
|
|
1983
|
-
xl: "max-w-4xl",
|
|
1984
|
-
full: "max-w-7xl"
|
|
1985
|
-
};
|
|
1986
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1987
|
-
"div",
|
|
1988
|
-
{
|
|
1989
|
-
className: "fixed inset-0 bg-black bg-opacity-50 z-[100] flex items-center justify-center p-4 dialog-backdrop",
|
|
1990
|
-
style: {
|
|
1991
|
-
animation: "fadeIn 0.2s ease-out"
|
|
1992
|
-
},
|
|
1993
|
-
_: "on click if event.target is me \r\n add .dialog-exit to me\r\n add .dialog-content-exit to .dialog-content\r\n wait 200ms\r\n set #dialog-container's innerHTML to '' end",
|
|
1994
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1995
|
-
"div",
|
|
1996
|
-
{
|
|
1997
|
-
className: `bg-gray-50 rounded-lg shadow-xl ${sizeClasses[size]} w-full max-h-[90vh] overflow-hidden flex flex-col dialog-content ${className}`,
|
|
1998
|
-
style: {
|
|
1999
|
-
animation: "slideIn 0.3s ease-out"
|
|
2000
|
-
},
|
|
2001
|
-
_: "on click call event.stopPropagation()",
|
|
2002
|
-
children: [
|
|
2003
|
-
(title || showClose) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 border-b border-gray-200 bg-white flex items-center justify-between", children: [
|
|
2004
|
-
title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title }),
|
|
2005
|
-
showClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2006
|
-
"button",
|
|
2007
|
-
{
|
|
2008
|
-
className: "text-gray-400 hover:text-gray-600 transition-colors",
|
|
2009
|
-
_: "on click \r\n add .dialog-exit to .dialog-backdrop\r\n add .dialog-content-exit to .dialog-content\r\n wait 200ms\r\n set #dialog-container's innerHTML to '' end",
|
|
2010
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2011
|
-
"svg",
|
|
2012
|
-
{
|
|
2013
|
-
className: "w-6 h-6",
|
|
2014
|
-
fill: "none",
|
|
2015
|
-
stroke: "currentColor",
|
|
2016
|
-
viewBox: "0 0 24 24",
|
|
2017
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2018
|
-
"path",
|
|
2019
|
-
{
|
|
2020
|
-
strokeLinecap: "round",
|
|
2021
|
-
strokeLinejoin: "round",
|
|
2022
|
-
strokeWidth: 2,
|
|
2023
|
-
d: "M6 18L18 6M6 6l12 12"
|
|
2024
|
-
}
|
|
2025
|
-
)
|
|
2026
|
-
}
|
|
2027
|
-
)
|
|
2028
|
-
}
|
|
2029
|
-
)
|
|
2030
|
-
] }),
|
|
2031
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50", children })
|
|
2032
|
-
]
|
|
2033
|
-
}
|
|
2034
|
-
)
|
|
2609
|
+
async getItem(id) {
|
|
2610
|
+
return this.data.find((item) => {
|
|
2611
|
+
if (item.id === id) {
|
|
2612
|
+
return true;
|
|
2613
|
+
}
|
|
2614
|
+
if (typeof item.id === "number" && typeof id === "string") {
|
|
2615
|
+
return item.id === Number(id);
|
|
2616
|
+
}
|
|
2617
|
+
if (typeof item.id === "string" && typeof id === "number") {
|
|
2618
|
+
return Number(item.id) === id;
|
|
2619
|
+
}
|
|
2620
|
+
return false;
|
|
2621
|
+
}) || null;
|
|
2622
|
+
}
|
|
2623
|
+
async deleteItem(id) {
|
|
2624
|
+
const index = this.data.findIndex((item) => {
|
|
2625
|
+
if (item.id === id) {
|
|
2626
|
+
return true;
|
|
2627
|
+
}
|
|
2628
|
+
if (typeof item.id === "number" && typeof id === "string") {
|
|
2629
|
+
return item.id === Number(id);
|
|
2630
|
+
}
|
|
2631
|
+
if (typeof item.id === "string" && typeof id === "number") {
|
|
2632
|
+
return Number(item.id) === id;
|
|
2633
|
+
}
|
|
2634
|
+
return false;
|
|
2635
|
+
});
|
|
2636
|
+
if (index === -1) {
|
|
2637
|
+
return false;
|
|
2035
2638
|
}
|
|
2036
|
-
|
|
2639
|
+
this.data.splice(index, 1);
|
|
2640
|
+
return true;
|
|
2641
|
+
}
|
|
2642
|
+
async updateItem(id, data) {
|
|
2643
|
+
const index = this.data.findIndex((item) => {
|
|
2644
|
+
if (item.id === id) {
|
|
2645
|
+
return true;
|
|
2646
|
+
}
|
|
2647
|
+
if (typeof item.id === "number" && typeof id === "string") {
|
|
2648
|
+
return item.id === Number(id);
|
|
2649
|
+
}
|
|
2650
|
+
if (typeof item.id === "string" && typeof id === "number") {
|
|
2651
|
+
return Number(item.id) === id;
|
|
2652
|
+
}
|
|
2653
|
+
return false;
|
|
2654
|
+
});
|
|
2655
|
+
if (index === -1) {
|
|
2656
|
+
return null;
|
|
2657
|
+
}
|
|
2658
|
+
this.data[index] = { ...this.data[index], ...data };
|
|
2659
|
+
return this.data[index];
|
|
2660
|
+
}
|
|
2661
|
+
async createItem(data) {
|
|
2662
|
+
const maxId = this.data.length > 0 ? Math.max(
|
|
2663
|
+
...this.data.map(
|
|
2664
|
+
(item) => typeof item.id === "number" ? item.id : 0
|
|
2665
|
+
)
|
|
2666
|
+
) : 0;
|
|
2667
|
+
const newId = maxId + 1;
|
|
2668
|
+
const newItem = { ...data, id: newId };
|
|
2669
|
+
this.data.push(newItem);
|
|
2670
|
+
return newItem;
|
|
2671
|
+
}
|
|
2037
2672
|
};
|
|
2038
2673
|
var ErrorAlert = (props) => {
|
|
2039
2674
|
const {
|
|
@@ -2114,473 +2749,338 @@ var ErrorAlert = (props) => {
|
|
|
2114
2749
|
{
|
|
2115
2750
|
className: "w-5 h-5",
|
|
2116
2751
|
fill: "none",
|
|
2117
|
-
stroke: "currentColor",
|
|
2118
|
-
viewBox: "0 0 24 24",
|
|
2119
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2120
|
-
"path",
|
|
2121
|
-
{
|
|
2122
|
-
strokeLinecap: "round",
|
|
2123
|
-
strokeLinejoin: "round",
|
|
2124
|
-
strokeWidth: 2,
|
|
2125
|
-
d: "M6 18L18 6M6 6l12 12"
|
|
2126
|
-
}
|
|
2127
|
-
)
|
|
2128
|
-
}
|
|
2129
|
-
)
|
|
2130
|
-
}
|
|
2131
|
-
)
|
|
2132
|
-
] })
|
|
2133
|
-
}
|
|
2134
|
-
);
|
|
2135
|
-
};
|
|
2136
|
-
var Breadcrumb = (props) => {
|
|
2137
|
-
const { items } = props;
|
|
2138
|
-
if (items.length === 0) {
|
|
2139
|
-
return null;
|
|
2140
|
-
}
|
|
2141
|
-
return /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "flex", "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "flex items-center space-x-2", children: items.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex items-center", children: [
|
|
2142
|
-
index > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2143
|
-
"svg",
|
|
2144
|
-
{
|
|
2145
|
-
className: "w-5 h-5 text-gray-400 mx-2",
|
|
2146
|
-
fill: "currentColor",
|
|
2147
|
-
viewBox: "0 0 20 20",
|
|
2148
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2149
|
-
"path",
|
|
2150
|
-
{
|
|
2151
|
-
fillRule: "evenodd",
|
|
2152
|
-
d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z",
|
|
2153
|
-
clipRule: "evenodd"
|
|
2154
|
-
}
|
|
2155
|
-
)
|
|
2156
|
-
}
|
|
2157
|
-
),
|
|
2158
|
-
item.href ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2159
|
-
"a",
|
|
2160
|
-
{
|
|
2161
|
-
href: item.href,
|
|
2162
|
-
className: "text-sm font-medium text-gray-500 hover:text-gray-700",
|
|
2163
|
-
"hx-get": item.href,
|
|
2164
|
-
"hx-push-url": "true",
|
|
2165
|
-
children: item.label
|
|
2166
|
-
}
|
|
2167
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-900", children: item.label })
|
|
2168
|
-
] }, index)) }) });
|
|
2169
|
-
};
|
|
2170
|
-
var Header = (props) => {
|
|
2171
|
-
const { breadcrumbs = [], userInfo, sidebarCollapsed = false } = props;
|
|
2172
|
-
return /* @__PURE__ */ jsxRuntime.jsx("header", { className: "bg-white border-b border-gray-200 shadow-sm sticky top-0 z-40", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2173
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4 flex-1 min-w-0", children: [
|
|
2174
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2175
|
-
"a",
|
|
2176
|
-
{
|
|
2177
|
-
className: "p-2 rounded-lg hover:bg-gray-100 transition-colors inline-block flex-shrink-0",
|
|
2178
|
-
title: sidebarCollapsed ? "\u5C55\u5F00\u4FA7\u8FB9\u680F" : "\u6298\u53E0\u4FA7\u8FB9\u680F",
|
|
2179
|
-
"hx-get": "",
|
|
2180
|
-
children: sidebarCollapsed ? (
|
|
2181
|
-
// 展开图标(向右箭头)
|
|
2182
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2183
|
-
"svg",
|
|
2184
|
-
{
|
|
2185
|
-
className: "w-5 h-5 text-gray-600",
|
|
2186
|
-
fill: "none",
|
|
2187
|
-
stroke: "currentColor",
|
|
2188
|
-
viewBox: "0 0 24 24",
|
|
2189
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2190
|
-
"path",
|
|
2191
|
-
{
|
|
2192
|
-
strokeLinecap: "round",
|
|
2193
|
-
strokeLinejoin: "round",
|
|
2194
|
-
strokeWidth: 2,
|
|
2195
|
-
d: "M9 5l7 7-7 7"
|
|
2196
|
-
}
|
|
2197
|
-
)
|
|
2198
|
-
}
|
|
2199
|
-
)
|
|
2200
|
-
) : (
|
|
2201
|
-
// 折叠图标(三条横线)
|
|
2202
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2203
|
-
"svg",
|
|
2204
|
-
{
|
|
2205
|
-
className: "w-5 h-5 text-gray-600",
|
|
2206
|
-
fill: "none",
|
|
2207
|
-
stroke: "currentColor",
|
|
2208
|
-
viewBox: "0 0 24 24",
|
|
2209
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2210
|
-
"path",
|
|
2211
|
-
{
|
|
2212
|
-
strokeLinecap: "round",
|
|
2213
|
-
strokeLinejoin: "round",
|
|
2214
|
-
strokeWidth: 2,
|
|
2215
|
-
d: "M4 6h16M4 12h16M4 18h16"
|
|
2216
|
-
}
|
|
2217
|
-
)
|
|
2218
|
-
}
|
|
2219
|
-
)
|
|
2220
|
-
)
|
|
2221
|
-
}
|
|
2222
|
-
),
|
|
2223
|
-
breadcrumbs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Breadcrumb, { items: breadcrumbs })
|
|
2224
|
-
] }),
|
|
2225
|
-
userInfo && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [
|
|
2226
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right hidden sm:block", children: [
|
|
2227
|
-
userInfo.name && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium text-gray-900", children: userInfo.name }),
|
|
2228
|
-
userInfo.email && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: userInfo.email })
|
|
2229
|
-
] }),
|
|
2230
|
-
userInfo.avatar ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2231
|
-
"img",
|
|
2232
|
-
{
|
|
2233
|
-
src: userInfo.avatar,
|
|
2234
|
-
alt: userInfo.name || "\u7528\u6237",
|
|
2235
|
-
className: "w-8 h-8 rounded-full"
|
|
2236
|
-
}
|
|
2237
|
-
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-sm font-medium", children: (userInfo.name || userInfo.email || "U").charAt(0).toUpperCase() })
|
|
2238
|
-
] })
|
|
2239
|
-
] }) }) });
|
|
2240
|
-
};
|
|
2241
|
-
var LoadingBar = () => {
|
|
2242
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2243
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2244
|
-
"div",
|
|
2245
|
-
{
|
|
2246
|
-
id: "loading-bar",
|
|
2247
|
-
className: "fixed top-0 left-0 right-0 h-1 bg-transparent z-50 opacity-0 transition-opacity duration-200",
|
|
2248
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full bg-gradient-to-r from-blue-500 via-blue-600 to-blue-500 loading-bar-progress" })
|
|
2249
|
-
}
|
|
2250
|
-
),
|
|
2251
|
-
/* @__PURE__ */ jsxRuntime.jsx("style", { children: `
|
|
2252
|
-
@keyframes loading-progress {
|
|
2253
|
-
0% { transform: translateX(-100%); width: 0%; }
|
|
2254
|
-
50% { width: 70%; }
|
|
2255
|
-
100% { transform: translateX(100%); width: 100%; }
|
|
2256
|
-
}
|
|
2257
|
-
#loading-bar.htmx-request {
|
|
2258
|
-
opacity: 1 !important;
|
|
2259
|
-
}
|
|
2260
|
-
#loading-bar.htmx-request .loading-bar-progress {
|
|
2261
|
-
animation: loading-progress 1.5s ease-in-out infinite;
|
|
2262
|
-
}
|
|
2263
|
-
` })
|
|
2264
|
-
] });
|
|
2265
|
-
};
|
|
2266
|
-
function renderNavItem(item, currentPath, collapsed = false, isChild = false) {
|
|
2267
|
-
const isActive = currentPath === item.href || currentPath && currentPath.startsWith(item.href + "/");
|
|
2268
|
-
const hasActiveChild = item.children?.some(
|
|
2269
|
-
(child) => currentPath === child.href || currentPath && currentPath.startsWith(child.href + "/")
|
|
2270
|
-
);
|
|
2271
|
-
const navItemId = `nav-item-${item.href.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
|
2272
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("li", { id: navItemId, className: "relative group", children: [
|
|
2273
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2274
|
-
"a",
|
|
2275
|
-
{
|
|
2276
|
-
href: item.href,
|
|
2277
|
-
className: `flex items-center ${collapsed ? "justify-center px-2" : "px-4"} py-2 rounded-lg transition-colors ${isActive || hasActiveChild ? "bg-blue-600 text-white shadow-md" : "text-gray-300 hover:bg-gray-700 hover:text-white"}`,
|
|
2278
|
-
"hx-get": item.href,
|
|
2279
|
-
"hx-push-url": "true",
|
|
2280
|
-
title: collapsed ? item.label : void 0,
|
|
2281
|
-
children: [
|
|
2282
|
-
item.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: collapsed ? "" : "mr-2", children: item.icon }),
|
|
2283
|
-
!collapsed && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-nowrap overflow-hidden text-ellipsis", children: item.label })
|
|
2284
|
-
]
|
|
2285
|
-
}
|
|
2286
|
-
),
|
|
2287
|
-
collapsed && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute left-full ml-2 px-3 py-2 bg-gray-900 text-white text-sm rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 pointer-events-none z-50 whitespace-nowrap", children: [
|
|
2288
|
-
item.label,
|
|
2289
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-full top-1/2 -translate-y-1/2 border-4 border-transparent border-r-gray-900" })
|
|
2290
|
-
] }),
|
|
2291
|
-
!collapsed && item.children && item.children.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "ml-4 mt-1 space-y-1", children: item.children.map((child) => {
|
|
2292
|
-
const isChildActive = currentPath === child.href || currentPath && currentPath.startsWith(child.href + "/");
|
|
2293
|
-
const childNavItemId = `nav-item-${child.href.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
|
2294
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2295
|
-
"li",
|
|
2296
|
-
{
|
|
2297
|
-
id: childNavItemId,
|
|
2298
|
-
className: "relative group",
|
|
2299
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2300
|
-
"a",
|
|
2301
|
-
{
|
|
2302
|
-
href: child.href,
|
|
2303
|
-
className: `flex items-center px-4 py-2 rounded-lg text-sm transition-colors ${isChildActive ? "bg-blue-500 text-white" : "text-gray-400 hover:bg-gray-700 hover:text-white"}`,
|
|
2304
|
-
"hx-get": child.href,
|
|
2305
|
-
"hx-push-url": "true",
|
|
2306
|
-
children: [
|
|
2307
|
-
child.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-2", children: child.icon }),
|
|
2308
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-nowrap overflow-hidden text-ellipsis", children: child.label })
|
|
2309
|
-
]
|
|
2310
|
-
}
|
|
2311
|
-
)
|
|
2312
|
-
},
|
|
2313
|
-
child.href
|
|
2314
|
-
);
|
|
2315
|
-
}) })
|
|
2316
|
-
] }, item.href);
|
|
2317
|
-
}
|
|
2318
|
-
var BaseLayout = (props) => {
|
|
2319
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("html", { children: [
|
|
2320
|
-
/* @__PURE__ */ jsxRuntime.jsxs("head", { children: [
|
|
2321
|
-
/* @__PURE__ */ jsxRuntime.jsx("meta", { charset: "UTF-8" }),
|
|
2322
|
-
/* @__PURE__ */ jsxRuntime.jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }),
|
|
2323
|
-
/* @__PURE__ */ jsxRuntime.jsx("title", { children: props.title }),
|
|
2324
|
-
props.description && /* @__PURE__ */ jsxRuntime.jsx("meta", { name: "description", content: props.description }),
|
|
2325
|
-
/* @__PURE__ */ jsxRuntime.jsx("script", { src: "https://unpkg.com/htmx.org@latest" }),
|
|
2326
|
-
/* @__PURE__ */ jsxRuntime.jsx("script", { src: "https://unpkg.com/hyperscript.org@latest" }),
|
|
2327
|
-
/* @__PURE__ */ jsxRuntime.jsx("script", { src: "https://cdn.tailwindcss.com" }),
|
|
2328
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2329
|
-
"style",
|
|
2330
|
-
{
|
|
2331
|
-
dangerouslySetInnerHTML: {
|
|
2332
|
-
__html: `
|
|
2333
|
-
@keyframes fadeIn {
|
|
2334
|
-
from { opacity: 0;}
|
|
2335
|
-
to { opacity: 1;}
|
|
2336
|
-
}
|
|
2337
|
-
|
|
2338
|
-
@keyframes slideIn {
|
|
2339
|
-
from { opacity: 0; transform: scale(0.95) translateY(-10px); }
|
|
2340
|
-
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
|
-
@keyframes slideInRight {
|
|
2344
|
-
from { opacity: 0; transform: translateX(100%); }
|
|
2345
|
-
to { opacity: 1; transform: translateX(0); }
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
@keyframes slideOutRight {
|
|
2349
|
-
from { opacity: 1; transform: translateX(0); }
|
|
2350
|
-
to { opacity: 0; transform: translateX(100%); }
|
|
2351
|
-
}
|
|
2352
|
-
|
|
2353
|
-
@keyframes fadeOut {
|
|
2354
|
-
from { opacity: 1; }
|
|
2355
|
-
to { opacity: 0; }
|
|
2356
|
-
}
|
|
2357
|
-
|
|
2358
|
-
@keyframes scaleOut {
|
|
2359
|
-
from { opacity: 1; transform: scale(1) translateY(0); }
|
|
2360
|
-
to { opacity: 0; transform: scale(0.95) translateY(-10px); }
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
/* Dialog \u9000\u51FA\u52A8\u753B */
|
|
2364
|
-
.dialog-exit {
|
|
2365
|
-
animation: fadeOut 0.2s ease-in forwards !important;
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
.dialog-content-exit {
|
|
2369
|
-
animation: scaleOut 0.2s ease-in forwards !important;
|
|
2370
|
-
}
|
|
2371
|
-
|
|
2372
|
-
/* ErrorAlert \u9000\u51FA\u52A8\u753B */
|
|
2373
|
-
.error-alert-exit {
|
|
2374
|
-
animation: slideOutRight 0.3s ease-in forwards, fadeOut 0.3s ease-in forwards;
|
|
2375
|
-
}
|
|
2376
|
-
`
|
|
2752
|
+
stroke: "currentColor",
|
|
2753
|
+
viewBox: "0 0 24 24",
|
|
2754
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2755
|
+
"path",
|
|
2756
|
+
{
|
|
2757
|
+
strokeLinecap: "round",
|
|
2758
|
+
strokeLinejoin: "round",
|
|
2759
|
+
strokeWidth: 2,
|
|
2760
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
2761
|
+
}
|
|
2762
|
+
)
|
|
2763
|
+
}
|
|
2764
|
+
)
|
|
2377
2765
|
}
|
|
2378
|
-
|
|
2379
|
-
)
|
|
2380
|
-
] }),
|
|
2381
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2382
|
-
"body",
|
|
2383
|
-
{
|
|
2384
|
-
className: "bg-gray-50",
|
|
2385
|
-
"hx-indicator": "#loading-bar",
|
|
2386
|
-
"hx-target": "#main-content",
|
|
2387
|
-
"hx-swap": "outerHTML",
|
|
2388
|
-
children: [
|
|
2389
|
-
/* @__PURE__ */ jsxRuntime.jsx(LoadingBar, {}),
|
|
2390
|
-
props.children,
|
|
2391
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2392
|
-
"div",
|
|
2393
|
-
{
|
|
2394
|
-
id: "error-container",
|
|
2395
|
-
className: "fixed top-4 right-4 z-[200] w-full max-w-2xl px-4"
|
|
2396
|
-
}
|
|
2397
|
-
),
|
|
2398
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { id: "dialog-container" })
|
|
2399
|
-
]
|
|
2400
|
-
}
|
|
2401
|
-
)
|
|
2402
|
-
] });
|
|
2403
|
-
};
|
|
2404
|
-
var AdminLayout = (props) => {
|
|
2405
|
-
const logo = props.adminContext.pluginOptions.logo;
|
|
2406
|
-
const navItems = props.adminContext.pluginOptions.navigation;
|
|
2407
|
-
const referer = props.adminContext.ctx.req.header("Referer");
|
|
2408
|
-
let currentPath = props.adminContext.ctx.req.path;
|
|
2409
|
-
if (referer) {
|
|
2410
|
-
try {
|
|
2411
|
-
const refererUrl = new URL(referer);
|
|
2412
|
-
const method = props.adminContext.ctx.req.method;
|
|
2413
|
-
if (["POST", "PUT", "DELETE"].includes(method)) {
|
|
2414
|
-
currentPath = refererUrl.pathname;
|
|
2415
|
-
}
|
|
2416
|
-
} catch (e) {
|
|
2766
|
+
)
|
|
2767
|
+
] })
|
|
2417
2768
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2769
|
+
);
|
|
2770
|
+
};
|
|
2771
|
+
|
|
2772
|
+
// src/handler.tsx
|
|
2773
|
+
init_Layout();
|
|
2774
|
+
var Dialog = (props) => {
|
|
2775
|
+
const {
|
|
2776
|
+
title,
|
|
2777
|
+
children,
|
|
2778
|
+
showClose = true,
|
|
2779
|
+
closeUrl,
|
|
2780
|
+
className = "",
|
|
2781
|
+
size = "lg"
|
|
2782
|
+
} = props;
|
|
2783
|
+
const sizeClasses = {
|
|
2784
|
+
sm: "max-w-md",
|
|
2785
|
+
md: "max-w-lg",
|
|
2786
|
+
lg: "max-w-2xl",
|
|
2787
|
+
xl: "max-w-4xl",
|
|
2788
|
+
full: "max-w-7xl"
|
|
2789
|
+
};
|
|
2790
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2791
|
+
"div",
|
|
2792
|
+
{
|
|
2793
|
+
className: "fixed inset-0 bg-black bg-opacity-50 z-[100] flex items-center justify-center p-4 dialog-backdrop",
|
|
2794
|
+
style: {
|
|
2795
|
+
animation: "fadeIn 0.2s ease-out"
|
|
2796
|
+
},
|
|
2797
|
+
_: "on click if event.target is me \r\n add .dialog-exit to me\r\n add .dialog-content-exit to .dialog-content\r\n wait 200ms\r\n set #dialog-container's innerHTML to '' end",
|
|
2798
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2799
|
+
"div",
|
|
2448
2800
|
{
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2801
|
+
className: `bg-gray-50 rounded-lg shadow-xl ${sizeClasses[size]} w-full max-h-[90vh] overflow-hidden flex flex-col dialog-content ${className}`,
|
|
2802
|
+
style: {
|
|
2803
|
+
animation: "slideIn 0.3s ease-out"
|
|
2804
|
+
},
|
|
2805
|
+
_: "on click call event.stopPropagation()",
|
|
2806
|
+
children: [
|
|
2807
|
+
(title || showClose) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4 border-b border-gray-200 bg-white flex items-center justify-between", children: [
|
|
2808
|
+
title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900", children: title }),
|
|
2809
|
+
showClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2810
|
+
"button",
|
|
2811
|
+
{
|
|
2812
|
+
className: "text-gray-400 hover:text-gray-600 transition-colors",
|
|
2813
|
+
_: "on click \r\n add .dialog-exit to .dialog-backdrop\r\n add .dialog-content-exit to .dialog-content\r\n wait 200ms\r\n set #dialog-container's innerHTML to '' end",
|
|
2814
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2815
|
+
"svg",
|
|
2816
|
+
{
|
|
2817
|
+
className: "w-6 h-6",
|
|
2818
|
+
fill: "none",
|
|
2819
|
+
stroke: "currentColor",
|
|
2820
|
+
viewBox: "0 0 24 24",
|
|
2821
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2822
|
+
"path",
|
|
2823
|
+
{
|
|
2824
|
+
strokeLinecap: "round",
|
|
2825
|
+
strokeLinejoin: "round",
|
|
2826
|
+
strokeWidth: 2,
|
|
2827
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
2828
|
+
}
|
|
2829
|
+
)
|
|
2830
|
+
}
|
|
2831
|
+
)
|
|
2832
|
+
}
|
|
2833
|
+
)
|
|
2834
|
+
] }),
|
|
2835
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto p-6 bg-gray-50", children })
|
|
2836
|
+
]
|
|
2452
2837
|
}
|
|
2453
|
-
)
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
] });
|
|
2838
|
+
)
|
|
2839
|
+
}
|
|
2840
|
+
);
|
|
2457
2841
|
};
|
|
2458
2842
|
|
|
2459
|
-
// src/
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
ctx;
|
|
2467
|
-
/** 之前的模块名 */
|
|
2468
|
-
previousModuleName;
|
|
2469
|
-
/** 是否是片段请求(HTMX 请求) */
|
|
2470
|
-
isFragment;
|
|
2471
|
-
/** 是否是对话框请求 */
|
|
2472
|
-
isDialog;
|
|
2473
|
-
/** 用户信息 */
|
|
2474
|
-
userInfo;
|
|
2475
|
-
/** 通知队列 */
|
|
2476
|
-
notifications = [];
|
|
2477
|
-
/** 页面标题(用于 HX-Title 和页面展示) */
|
|
2478
|
-
title = "";
|
|
2479
|
-
/** 页面描述(用于SEO和页面展示) */
|
|
2480
|
-
description = "";
|
|
2481
|
-
/** 面包屑项 */
|
|
2482
|
-
breadcrumbs = [];
|
|
2483
|
-
/** 主要内容 */
|
|
2484
|
-
content = null;
|
|
2485
|
-
/** 需要重定向的 URL */
|
|
2486
|
-
redirectUrl;
|
|
2487
|
-
/** 是否需要刷新页面(用于 HX-Refresh) */
|
|
2488
|
-
refresh = false;
|
|
2489
|
-
constructor(ctx, userInfo, moduleMetadata, pluginOptions) {
|
|
2490
|
-
this.ctx = ctx;
|
|
2491
|
-
this.userInfo = userInfo;
|
|
2492
|
-
this.moduleMetadata = moduleMetadata;
|
|
2493
|
-
this.pluginOptions = pluginOptions;
|
|
2494
|
-
const url = new URL(ctx.req.url);
|
|
2495
|
-
this.isFragment = ctx.req.header("HX-Request") === "true";
|
|
2496
|
-
this.isDialog = url.searchParams.get("dialog") === "true" || ctx.req.header("HX-Target") === "#dialog-container" || (ctx.req.header("Referer") || "").includes("dialog=true");
|
|
2497
|
-
const referer = ctx.req.header("Referer");
|
|
2498
|
-
this.previousModuleName = referer ? new URL(referer).pathname.replace(pluginOptions.prefix, "").split("/").pop() : void 0;
|
|
2843
|
+
// src/handler.tsx
|
|
2844
|
+
init_context();
|
|
2845
|
+
|
|
2846
|
+
// src/utils/permissions.ts
|
|
2847
|
+
function checkUserPermission(requiredPermission, userPermissions) {
|
|
2848
|
+
if (!requiredPermission) {
|
|
2849
|
+
return { allowed: true, reason: "\u65E0\u9700\u6743\u9650" };
|
|
2499
2850
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2851
|
+
if (!userPermissions || userPermissions.length === 0) {
|
|
2852
|
+
return {
|
|
2853
|
+
allowed: false,
|
|
2854
|
+
reason: "\u7528\u6237\u65E0\u4EFB\u4F55\u6743\u9650",
|
|
2855
|
+
matchedPermission: "none"
|
|
2856
|
+
};
|
|
2506
2857
|
}
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
sendError(title, message) {
|
|
2511
|
-
this.sendNotification("error", title, message);
|
|
2858
|
+
const denyResult = checkDenyPermissions(requiredPermission, userPermissions);
|
|
2859
|
+
if (!denyResult.allowed) {
|
|
2860
|
+
return denyResult;
|
|
2512
2861
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2862
|
+
const allowResult = checkAllowPermissions(
|
|
2863
|
+
requiredPermission,
|
|
2864
|
+
userPermissions
|
|
2865
|
+
);
|
|
2866
|
+
return allowResult;
|
|
2867
|
+
}
|
|
2868
|
+
function checkDenyPermissions(requiredPermission, userPermissions) {
|
|
2869
|
+
const denyPermissions = userPermissions.filter((p) => p.startsWith("deny:"));
|
|
2870
|
+
for (const denyPermission of denyPermissions) {
|
|
2871
|
+
const denyPattern = denyPermission.substring(5);
|
|
2872
|
+
if (matchPermission(requiredPermission, denyPattern)) {
|
|
2873
|
+
return {
|
|
2874
|
+
allowed: false,
|
|
2875
|
+
reason: `\u6743\u9650\u88AB\u7981\u6B62: ${denyPermission}`,
|
|
2876
|
+
matchedPermission: denyPermission
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
return { allowed: true };
|
|
2881
|
+
}
|
|
2882
|
+
function checkAllowPermissions(requiredPermission, userPermissions) {
|
|
2883
|
+
const allowPermissions = userPermissions.filter(
|
|
2884
|
+
(p) => !p.startsWith("deny:")
|
|
2885
|
+
);
|
|
2886
|
+
if (allowPermissions.length === 0) {
|
|
2887
|
+
return {
|
|
2888
|
+
allowed: false,
|
|
2889
|
+
reason: "\u7528\u6237\u65E0\u5141\u8BB8\u6743\u9650",
|
|
2890
|
+
matchedPermission: "none"
|
|
2891
|
+
};
|
|
2518
2892
|
}
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2893
|
+
for (const allowPermission of allowPermissions) {
|
|
2894
|
+
const allowPattern = allowPermission.startsWith("allow:") ? allowPermission.substring(6) : allowPermission;
|
|
2895
|
+
if (matchPermission(requiredPermission, allowPattern)) {
|
|
2896
|
+
return {
|
|
2897
|
+
allowed: true,
|
|
2898
|
+
reason: `\u6743\u9650\u5339\u914D: ${allowPermission}`,
|
|
2899
|
+
matchedPermission: allowPermission
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2524
2902
|
}
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2903
|
+
return {
|
|
2904
|
+
allowed: false,
|
|
2905
|
+
reason: "\u65E0\u5339\u914D\u7684\u5141\u8BB8\u6743\u9650",
|
|
2906
|
+
matchedPermission: "none"
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
function matchPermission(requiredPermission, pattern) {
|
|
2910
|
+
const required = requiredPermission.startsWith("allow:") ? requiredPermission.substring(6) : requiredPermission;
|
|
2911
|
+
if (pattern.includes("*")) {
|
|
2912
|
+
return matchWithWildcard(required, pattern);
|
|
2530
2913
|
}
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2914
|
+
return required === pattern;
|
|
2915
|
+
}
|
|
2916
|
+
function matchWithWildcard(required, pattern) {
|
|
2917
|
+
const requiredParts = required.split(".");
|
|
2918
|
+
const patternParts = pattern.split(".");
|
|
2919
|
+
if (requiredParts.length !== patternParts.length && !pattern.endsWith("*")) {
|
|
2920
|
+
return false;
|
|
2537
2921
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2922
|
+
const maxLength = Math.max(requiredParts.length, patternParts.length);
|
|
2923
|
+
for (let i = 0; i < maxLength; i++) {
|
|
2924
|
+
const requiredPart = requiredParts[i];
|
|
2925
|
+
const patternPart = patternParts[i];
|
|
2926
|
+
if (patternPart === "*") {
|
|
2927
|
+
continue;
|
|
2928
|
+
}
|
|
2929
|
+
if (!patternPart && pattern.endsWith("*")) {
|
|
2930
|
+
return true;
|
|
2931
|
+
}
|
|
2932
|
+
if (!requiredPart) {
|
|
2933
|
+
return false;
|
|
2934
|
+
}
|
|
2935
|
+
if (requiredPart !== patternPart) {
|
|
2936
|
+
return false;
|
|
2937
|
+
}
|
|
2544
2938
|
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2939
|
+
return true;
|
|
2940
|
+
}
|
|
2941
|
+
|
|
2942
|
+
// src/utils/auth.ts
|
|
2943
|
+
async function getUserInfo(ctx, authProvider) {
|
|
2944
|
+
if (!authProvider) {
|
|
2945
|
+
return null;
|
|
2551
2946
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
hasDetail() {
|
|
2557
|
-
return this.moduleMetadata.hasDetail;
|
|
2947
|
+
const cookieKey = authProvider.cookieKey || "auth_token";
|
|
2948
|
+
const token = cookie.getCookie(ctx, cookieKey);
|
|
2949
|
+
if (!token) {
|
|
2950
|
+
return null;
|
|
2558
2951
|
}
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
return this.moduleMetadata.hasForm;
|
|
2952
|
+
return await authProvider.tokenToUser(token, ctx);
|
|
2953
|
+
}
|
|
2954
|
+
function checkPermissionDefault(userInfo, operation) {
|
|
2955
|
+
if (!operation) {
|
|
2956
|
+
return { allowed: true };
|
|
2565
2957
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2958
|
+
if (!userInfo) {
|
|
2959
|
+
return {
|
|
2960
|
+
allowed: false,
|
|
2961
|
+
message: "\u672A\u767B\u5F55\uFF0C\u65E0\u6CD5\u8BBF\u95EE\u6B64\u8D44\u6E90",
|
|
2962
|
+
operationId: operation
|
|
2963
|
+
};
|
|
2572
2964
|
}
|
|
2573
|
-
|
|
2965
|
+
const userPermissions = userInfo.permissions || [];
|
|
2966
|
+
const result = checkUserPermission(operation, userPermissions);
|
|
2967
|
+
if (result.allowed) {
|
|
2968
|
+
return { allowed: true };
|
|
2969
|
+
} else {
|
|
2970
|
+
return {
|
|
2971
|
+
allowed: false,
|
|
2972
|
+
message: result.reason || "\u60A8\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u6B64\u8D44\u6E90",
|
|
2973
|
+
operationId: operation
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// src/utils/operation.ts
|
|
2979
|
+
function generateOperationId(ctx, moduleName, moduleOptions, serviceName) {
|
|
2980
|
+
const method = ctx.req.method.toLowerCase();
|
|
2981
|
+
const url = new URL(ctx.req.url);
|
|
2982
|
+
const path = url.pathname;
|
|
2983
|
+
const lowerModuleName = moduleName.toLowerCase();
|
|
2984
|
+
const servicePrefix = serviceName ? `${serviceName}.` : "";
|
|
2985
|
+
if (moduleOptions.type === "list") {
|
|
2986
|
+
return `${servicePrefix}${lowerModuleName}.read`;
|
|
2987
|
+
} else if (moduleOptions.type === "detail") {
|
|
2988
|
+
if (method === "delete") {
|
|
2989
|
+
return `${servicePrefix}${lowerModuleName}.delete`;
|
|
2990
|
+
}
|
|
2991
|
+
return `${servicePrefix}${lowerModuleName}.read`;
|
|
2992
|
+
} else if (moduleOptions.type === "form") {
|
|
2993
|
+
if (path.includes("/new")) {
|
|
2994
|
+
if (method === "get") {
|
|
2995
|
+
return `${servicePrefix}${lowerModuleName}.create`;
|
|
2996
|
+
}
|
|
2997
|
+
return `${servicePrefix}${lowerModuleName}.create`;
|
|
2998
|
+
} else if (path.includes("/edit/")) {
|
|
2999
|
+
if (method === "get") {
|
|
3000
|
+
return `${servicePrefix}${lowerModuleName}.edit`;
|
|
3001
|
+
}
|
|
3002
|
+
return `${servicePrefix}${lowerModuleName}.edit`;
|
|
3003
|
+
} else if (method === "post") {
|
|
3004
|
+
return `${servicePrefix}${lowerModuleName}.create`;
|
|
3005
|
+
} else if (method === "put") {
|
|
3006
|
+
return `${servicePrefix}${lowerModuleName}.edit`;
|
|
3007
|
+
} else if (method === "delete") {
|
|
3008
|
+
return `${servicePrefix}${lowerModuleName}.delete`;
|
|
3009
|
+
}
|
|
3010
|
+
return `${servicePrefix}${lowerModuleName}.read`;
|
|
3011
|
+
} else if (moduleOptions.type === "custom") {
|
|
3012
|
+
return `${servicePrefix}${lowerModuleName}.read`;
|
|
3013
|
+
}
|
|
3014
|
+
return `${servicePrefix}${lowerModuleName}.read`;
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
// src/utils/permission-handler.tsx
|
|
3018
|
+
init_PermissionDenied();
|
|
3019
|
+
async function handlePermissionDenied(ctx, adminContext, permissionResult, getOperationId, pluginPrefix, getRegisteredOperations) {
|
|
3020
|
+
const operationId = permissionResult.operationId || getOperationId();
|
|
3021
|
+
const fromPath = ctx.req.path;
|
|
3022
|
+
if (adminContext.isFragment) {
|
|
3023
|
+
return renderPermissionDeniedDialog(
|
|
3024
|
+
ctx,
|
|
3025
|
+
adminContext,
|
|
3026
|
+
operationId,
|
|
3027
|
+
fromPath,
|
|
3028
|
+
getRegisteredOperations
|
|
3029
|
+
);
|
|
3030
|
+
} else {
|
|
3031
|
+
return redirectToPermissionDeniedPage(
|
|
3032
|
+
ctx,
|
|
3033
|
+
operationId,
|
|
3034
|
+
fromPath,
|
|
3035
|
+
pluginPrefix,
|
|
3036
|
+
permissionResult.redirectUrl
|
|
3037
|
+
);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
function renderPermissionDeniedDialog(ctx, adminContext, operationId, fromPath, getRegisteredOperations) {
|
|
3041
|
+
const registeredOperations = getRegisteredOperations ? getRegisteredOperations() : [];
|
|
3042
|
+
const permissionContent = PermissionDeniedContent(
|
|
3043
|
+
adminContext,
|
|
3044
|
+
operationId,
|
|
3045
|
+
fromPath,
|
|
3046
|
+
registeredOperations
|
|
3047
|
+
);
|
|
3048
|
+
return ctx.html(
|
|
3049
|
+
/* @__PURE__ */ jsxRuntime.jsx(Dialog, { title: "\u6743\u9650\u4E0D\u8DB3", size: "lg", children: permissionContent }),
|
|
3050
|
+
200,
|
|
3051
|
+
{
|
|
3052
|
+
"HX-Retarget": "#dialog-container",
|
|
3053
|
+
"HX-Reswap": "innerHTML"
|
|
3054
|
+
}
|
|
3055
|
+
);
|
|
3056
|
+
}
|
|
3057
|
+
function redirectToPermissionDeniedPage(ctx, operationId, fromPath, pluginPrefix, customRedirectUrl) {
|
|
3058
|
+
const permissionDeniedUrl = `${pluginPrefix}/permission-denied`;
|
|
3059
|
+
const url = new URL(permissionDeniedUrl, ctx.req.url);
|
|
3060
|
+
url.searchParams.set("operation", operationId);
|
|
3061
|
+
url.searchParams.set("from", fromPath);
|
|
3062
|
+
const redirectUrl = url.pathname + url.search;
|
|
3063
|
+
const finalRedirectUrl = customRedirectUrl || redirectUrl;
|
|
3064
|
+
return ctx.redirect(finalRedirectUrl);
|
|
3065
|
+
}
|
|
2574
3066
|
var RouteHandler = class {
|
|
2575
3067
|
moduleClass;
|
|
2576
3068
|
pluginOptions;
|
|
2577
3069
|
moduleMetadata;
|
|
2578
3070
|
moduleOptions;
|
|
3071
|
+
moduleName;
|
|
3072
|
+
basePath;
|
|
3073
|
+
serviceName;
|
|
3074
|
+
getRegisteredOperations;
|
|
2579
3075
|
constructor(options) {
|
|
2580
3076
|
this.moduleClass = options.moduleClass;
|
|
2581
3077
|
this.pluginOptions = options.pluginOptions;
|
|
2582
3078
|
this.moduleMetadata = options.moduleMetadata;
|
|
2583
3079
|
this.moduleOptions = options.moduleOptions;
|
|
3080
|
+
this.moduleName = options.moduleName;
|
|
3081
|
+
this.basePath = options.basePath;
|
|
3082
|
+
this.serviceName = options.serviceName;
|
|
3083
|
+
this.getRegisteredOperations = options.getRegisteredOperations;
|
|
2584
3084
|
}
|
|
2585
3085
|
/**
|
|
2586
3086
|
* 更新模块元数据
|
|
@@ -2593,10 +3093,40 @@ var RouteHandler = class {
|
|
|
2593
3093
|
*/
|
|
2594
3094
|
async handle(ctx) {
|
|
2595
3095
|
try {
|
|
2596
|
-
const userInfo = await this.pluginOptions.
|
|
3096
|
+
const userInfo = await getUserInfo(ctx, this.pluginOptions.authProvider);
|
|
2597
3097
|
const adminContext = this.createAdminContext(ctx, userInfo);
|
|
2598
3098
|
const moduleInstance = new this.moduleClass();
|
|
2599
3099
|
moduleInstance.__init(adminContext);
|
|
3100
|
+
const requiredPermission = moduleInstance.getRequiredPermission?.(ctx);
|
|
3101
|
+
const operation = this.getOperationId(ctx);
|
|
3102
|
+
if (this.pluginOptions.authProvider && requiredPermission !== void 0 && requiredPermission !== null && requiredPermission !== "") {
|
|
3103
|
+
let permissionResult;
|
|
3104
|
+
if (this.pluginOptions.authProvider.checkPermission) {
|
|
3105
|
+
permissionResult = await this.pluginOptions.authProvider.checkPermission(
|
|
3106
|
+
userInfo,
|
|
3107
|
+
operation,
|
|
3108
|
+
adminContext
|
|
3109
|
+
);
|
|
3110
|
+
} else {
|
|
3111
|
+
permissionResult = checkPermissionDefault(
|
|
3112
|
+
userInfo,
|
|
3113
|
+
operation
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
if (!permissionResult.allowed) {
|
|
3117
|
+
if (!permissionResult.operationId) {
|
|
3118
|
+
permissionResult.operationId = operation;
|
|
3119
|
+
}
|
|
3120
|
+
return await handlePermissionDenied(
|
|
3121
|
+
ctx,
|
|
3122
|
+
adminContext,
|
|
3123
|
+
permissionResult,
|
|
3124
|
+
() => this.getOperationId(ctx),
|
|
3125
|
+
this.pluginOptions.prefix,
|
|
3126
|
+
this.getRegisteredOperations
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
2600
3130
|
try {
|
|
2601
3131
|
adminContext.content = await moduleInstance.__handle();
|
|
2602
3132
|
adminContext.title = moduleInstance.getTitle();
|
|
@@ -2620,6 +3150,17 @@ var RouteHandler = class {
|
|
|
2620
3150
|
);
|
|
2621
3151
|
}
|
|
2622
3152
|
}
|
|
3153
|
+
/**
|
|
3154
|
+
* 获取操作ID(内部方法,用于传递给工具函数)
|
|
3155
|
+
*/
|
|
3156
|
+
getOperationId(ctx) {
|
|
3157
|
+
return generateOperationId(
|
|
3158
|
+
ctx,
|
|
3159
|
+
this.moduleName,
|
|
3160
|
+
this.moduleOptions,
|
|
3161
|
+
this.serviceName
|
|
3162
|
+
);
|
|
3163
|
+
}
|
|
2623
3164
|
renderFullPage(ctx, adminContext) {
|
|
2624
3165
|
if (adminContext.redirectUrl) {
|
|
2625
3166
|
return ctx.redirect(adminContext.redirectUrl);
|
|
@@ -2725,7 +3266,8 @@ var RouteHandler = class {
|
|
|
2725
3266
|
ctx,
|
|
2726
3267
|
userInfo,
|
|
2727
3268
|
this.moduleMetadata,
|
|
2728
|
-
this.pluginOptions
|
|
3269
|
+
this.pluginOptions,
|
|
3270
|
+
this.serviceName
|
|
2729
3271
|
);
|
|
2730
3272
|
}
|
|
2731
3273
|
/**
|
|
@@ -2818,8 +3360,12 @@ var HtmxAdminPlugin = class {
|
|
|
2818
3360
|
engine;
|
|
2819
3361
|
hono;
|
|
2820
3362
|
options;
|
|
3363
|
+
// 服务名(从引擎中获取)
|
|
3364
|
+
serviceName = "";
|
|
2821
3365
|
// 模块类映射(记录每个模块的类)
|
|
2822
3366
|
moduleTypeMap = /* @__PURE__ */ new Map();
|
|
3367
|
+
// 注册的操作列表(权限列表)
|
|
3368
|
+
registeredOperations = [];
|
|
2823
3369
|
constructor(options) {
|
|
2824
3370
|
this.options = {
|
|
2825
3371
|
title: options?.title || "\u7BA1\u7406\u540E\u53F0",
|
|
@@ -2827,7 +3373,7 @@ var HtmxAdminPlugin = class {
|
|
|
2827
3373
|
prefix: options?.prefix || "/admin",
|
|
2828
3374
|
homePath: options?.homePath || "",
|
|
2829
3375
|
navigation: options?.navigation ?? [],
|
|
2830
|
-
|
|
3376
|
+
authProvider: options?.authProvider
|
|
2831
3377
|
};
|
|
2832
3378
|
}
|
|
2833
3379
|
/**
|
|
@@ -2850,7 +3396,8 @@ var HtmxAdminPlugin = class {
|
|
|
2850
3396
|
onInit(engine) {
|
|
2851
3397
|
this.engine = engine;
|
|
2852
3398
|
this.hono = engine.getHono();
|
|
2853
|
-
|
|
3399
|
+
this.serviceName = engine.options.name;
|
|
3400
|
+
imeanServiceEngine.logger.info(`HtmxAdminPlugin initialized${this.serviceName ? ` (service: ${this.serviceName})` : ""}`);
|
|
2854
3401
|
}
|
|
2855
3402
|
/**
|
|
2856
3403
|
* 检查并注册模块
|
|
@@ -2911,10 +3458,127 @@ var HtmxAdminPlugin = class {
|
|
|
2911
3458
|
}
|
|
2912
3459
|
}
|
|
2913
3460
|
}
|
|
3461
|
+
/**
|
|
3462
|
+
* 根据模块类型和路由信息生成操作ID
|
|
3463
|
+
*/
|
|
3464
|
+
generateOperationId(moduleName, moduleType, method, path) {
|
|
3465
|
+
const lowerModuleName = moduleName.toLowerCase();
|
|
3466
|
+
const lowerMethod = method.toLowerCase();
|
|
3467
|
+
if (moduleType === "list") {
|
|
3468
|
+
return {
|
|
3469
|
+
operationId: `${lowerModuleName}.read`,
|
|
3470
|
+
operationType: "read"
|
|
3471
|
+
};
|
|
3472
|
+
} else if (moduleType === "detail") {
|
|
3473
|
+
if (lowerMethod === "delete") {
|
|
3474
|
+
return {
|
|
3475
|
+
operationId: `${lowerModuleName}.delete`,
|
|
3476
|
+
operationType: "delete"
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3479
|
+
return {
|
|
3480
|
+
operationId: `${lowerModuleName}.read`,
|
|
3481
|
+
operationType: "read"
|
|
3482
|
+
};
|
|
3483
|
+
} else if (moduleType === "form") {
|
|
3484
|
+
if (path.includes("/new")) {
|
|
3485
|
+
if (lowerMethod === "get") {
|
|
3486
|
+
return {
|
|
3487
|
+
operationId: `${lowerModuleName}.create`,
|
|
3488
|
+
operationType: "create"
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3491
|
+
return {
|
|
3492
|
+
operationId: `${lowerModuleName}.create`,
|
|
3493
|
+
operationType: "create"
|
|
3494
|
+
};
|
|
3495
|
+
} else if (path.includes("/edit/")) {
|
|
3496
|
+
if (lowerMethod === "get") {
|
|
3497
|
+
return {
|
|
3498
|
+
operationId: `${lowerModuleName}.edit`,
|
|
3499
|
+
operationType: "edit"
|
|
3500
|
+
};
|
|
3501
|
+
}
|
|
3502
|
+
return {
|
|
3503
|
+
operationId: `${lowerModuleName}.edit`,
|
|
3504
|
+
operationType: "edit"
|
|
3505
|
+
};
|
|
3506
|
+
} else if (lowerMethod === "post") {
|
|
3507
|
+
return {
|
|
3508
|
+
operationId: `${lowerModuleName}.create`,
|
|
3509
|
+
operationType: "create"
|
|
3510
|
+
};
|
|
3511
|
+
} else if (lowerMethod === "put") {
|
|
3512
|
+
return {
|
|
3513
|
+
operationId: `${lowerModuleName}.edit`,
|
|
3514
|
+
operationType: "edit"
|
|
3515
|
+
};
|
|
3516
|
+
} else if (lowerMethod === "delete") {
|
|
3517
|
+
return {
|
|
3518
|
+
operationId: `${lowerModuleName}.delete`,
|
|
3519
|
+
operationType: "delete"
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3522
|
+
return {
|
|
3523
|
+
operationId: `${lowerModuleName}.read`,
|
|
3524
|
+
operationType: "read"
|
|
3525
|
+
};
|
|
3526
|
+
} else if (moduleType === "custom") {
|
|
3527
|
+
return {
|
|
3528
|
+
operationId: `${lowerModuleName}.read`,
|
|
3529
|
+
operationType: "read"
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
return {
|
|
3533
|
+
operationId: `${lowerModuleName}.read`,
|
|
3534
|
+
operationType: "read"
|
|
3535
|
+
};
|
|
3536
|
+
}
|
|
3537
|
+
/**
|
|
3538
|
+
* 注册内置路由(权限提示页面)
|
|
3539
|
+
*/
|
|
3540
|
+
registerBuiltinRoutes() {
|
|
3541
|
+
const permissionDeniedPath = `${this.options.prefix}/permission-denied`;
|
|
3542
|
+
this.hono.get(permissionDeniedPath, async (ctx) => {
|
|
3543
|
+
const url = new URL(ctx.req.url);
|
|
3544
|
+
const operationId = url.searchParams.get("operation") || "";
|
|
3545
|
+
const fromPath = url.searchParams.get("from") || "";
|
|
3546
|
+
const { PermissionDeniedPage: PermissionDeniedPage2 } = await Promise.resolve().then(() => (init_PermissionDenied(), PermissionDenied_exports));
|
|
3547
|
+
const { HtmxAdminContext: HtmxAdminContext2 } = await Promise.resolve().then(() => (init_context(), context_exports));
|
|
3548
|
+
const adminContext = new HtmxAdminContext2(
|
|
3549
|
+
ctx,
|
|
3550
|
+
null,
|
|
3551
|
+
// 不需要用户信息
|
|
3552
|
+
{
|
|
3553
|
+
hasList: false,
|
|
3554
|
+
hasDetail: false,
|
|
3555
|
+
hasForm: false,
|
|
3556
|
+
hasCustom: true,
|
|
3557
|
+
basePath: permissionDeniedPath,
|
|
3558
|
+
title: "\u6743\u9650\u4E0D\u8DB3",
|
|
3559
|
+
description: "\u60A8\u6CA1\u6709\u6743\u9650\u8BBF\u95EE\u6B64\u8D44\u6E90"
|
|
3560
|
+
},
|
|
3561
|
+
this.options,
|
|
3562
|
+
this.serviceName
|
|
3563
|
+
);
|
|
3564
|
+
return ctx.html(
|
|
3565
|
+
PermissionDeniedPage2(
|
|
3566
|
+
adminContext,
|
|
3567
|
+
operationId,
|
|
3568
|
+
fromPath,
|
|
3569
|
+
this.registeredOperations
|
|
3570
|
+
)
|
|
3571
|
+
);
|
|
3572
|
+
});
|
|
3573
|
+
imeanServiceEngine.logger.info(
|
|
3574
|
+
`[HtmxAdminPlugin] Registered builtin route: GET ${permissionDeniedPath}`
|
|
3575
|
+
);
|
|
3576
|
+
}
|
|
2914
3577
|
/**
|
|
2915
3578
|
* 注册路由
|
|
2916
3579
|
*/
|
|
2917
3580
|
registerRoutes() {
|
|
3581
|
+
this.registerBuiltinRoutes();
|
|
2918
3582
|
const moduleNames = Array.from(this.moduleTypeMap.keys());
|
|
2919
3583
|
for (const moduleName of moduleNames) {
|
|
2920
3584
|
const moduleTypes = this.moduleTypeMap.get(moduleName);
|
|
@@ -2927,6 +3591,8 @@ var HtmxAdminPlugin = class {
|
|
|
2927
3591
|
basePath,
|
|
2928
3592
|
moduleOptions: moduleInfo.options,
|
|
2929
3593
|
pluginOptions: this.options,
|
|
3594
|
+
serviceName: this.serviceName,
|
|
3595
|
+
// 传递服务名
|
|
2930
3596
|
moduleMetadata: {
|
|
2931
3597
|
hasList: !!moduleTypes["list"],
|
|
2932
3598
|
hasDetail: !!moduleTypes["detail"],
|
|
@@ -2935,7 +3601,9 @@ var HtmxAdminPlugin = class {
|
|
|
2935
3601
|
basePath,
|
|
2936
3602
|
title: moduleInfo.options.title ?? moduleName,
|
|
2937
3603
|
description: moduleInfo.options.description ?? ""
|
|
2938
|
-
}
|
|
3604
|
+
},
|
|
3605
|
+
getRegisteredOperations: () => this.registeredOperations
|
|
3606
|
+
// 传递获取操作列表的方法
|
|
2939
3607
|
});
|
|
2940
3608
|
const routeConfig = {
|
|
2941
3609
|
list: [{ method: "get", path: `${basePath}/list` }],
|
|
@@ -2954,17 +3622,74 @@ var HtmxAdminPlugin = class {
|
|
|
2954
3622
|
imeanServiceEngine.logger.info(
|
|
2955
3623
|
`[HtmxAdminPlugin] Registering ${type} route: ${route.method.toUpperCase()} ${route.path}`
|
|
2956
3624
|
);
|
|
2957
|
-
const
|
|
3625
|
+
const { operationId, operationType } = this.generateOperationId(
|
|
3626
|
+
moduleName,
|
|
3627
|
+
type,
|
|
3628
|
+
route.method,
|
|
3629
|
+
route.path
|
|
3630
|
+
);
|
|
3631
|
+
const existingOperation = this.registeredOperations.find(
|
|
3632
|
+
(op) => op.operationId === operationId && op.httpMethod === route.method.toUpperCase()
|
|
3633
|
+
);
|
|
3634
|
+
if (!existingOperation) {
|
|
3635
|
+
this.registeredOperations.push({
|
|
3636
|
+
operationId,
|
|
3637
|
+
moduleName: moduleName.toLowerCase(),
|
|
3638
|
+
operationType,
|
|
3639
|
+
moduleType: type,
|
|
3640
|
+
moduleTitle: moduleInfo.options.title,
|
|
3641
|
+
moduleDescription: moduleInfo.options.description,
|
|
3642
|
+
httpMethod: route.method.toUpperCase(),
|
|
3643
|
+
routePath: route.path
|
|
3644
|
+
});
|
|
3645
|
+
}
|
|
3646
|
+
const handler = async (ctx) => {
|
|
3647
|
+
return routeHandler.handle(ctx);
|
|
3648
|
+
};
|
|
2958
3649
|
this.hono[route.method](route.path, handler);
|
|
2959
3650
|
}
|
|
2960
3651
|
}
|
|
2961
3652
|
}
|
|
2962
3653
|
}
|
|
3654
|
+
/**
|
|
3655
|
+
* 获取所有注册的操作(权限)列表
|
|
3656
|
+
* 可用于权限管理模块作为数据源,或用于确定管理员的权限
|
|
3657
|
+
*
|
|
3658
|
+
* @returns 所有注册的操作信息列表
|
|
3659
|
+
*/
|
|
3660
|
+
getRegisteredOperations() {
|
|
3661
|
+
return [...this.registeredOperations];
|
|
3662
|
+
}
|
|
3663
|
+
/**
|
|
3664
|
+
* 获取指定模块的所有操作
|
|
3665
|
+
*
|
|
3666
|
+
* @param moduleName 模块名
|
|
3667
|
+
* @returns 该模块的所有操作信息列表
|
|
3668
|
+
*/
|
|
3669
|
+
getModuleOperations(moduleName) {
|
|
3670
|
+
const lowerModuleName = moduleName.toLowerCase();
|
|
3671
|
+
return this.registeredOperations.filter(
|
|
3672
|
+
(op) => op.moduleName === lowerModuleName
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3675
|
+
/**
|
|
3676
|
+
* 获取所有唯一的操作ID列表(去重)
|
|
3677
|
+
*
|
|
3678
|
+
* @returns 所有唯一的操作ID列表
|
|
3679
|
+
*/
|
|
3680
|
+
getOperationIds() {
|
|
3681
|
+
const uniqueIds = /* @__PURE__ */ new Set();
|
|
3682
|
+
for (const op of this.registeredOperations) {
|
|
3683
|
+
uniqueIds.add(op.operationId);
|
|
3684
|
+
}
|
|
3685
|
+
return Array.from(uniqueIds).sort();
|
|
3686
|
+
}
|
|
2963
3687
|
/**
|
|
2964
3688
|
* 模块加载钩子:检查模块类型约束并注册路由
|
|
2965
3689
|
*/
|
|
2966
3690
|
onModuleLoad(modules) {
|
|
2967
3691
|
this.checkAndRegisterModules(modules);
|
|
3692
|
+
this.registerBuiltinRoutes();
|
|
2968
3693
|
this.registerRoutes();
|
|
2969
3694
|
this.engine.getHono().get(this.options.prefix, async (ctx) => {
|
|
2970
3695
|
return ctx.redirect(this.options.homePath);
|
|
@@ -3049,6 +3774,10 @@ var Badge = (props) => {
|
|
|
3049
3774
|
}
|
|
3050
3775
|
);
|
|
3051
3776
|
};
|
|
3777
|
+
|
|
3778
|
+
// src/components/index.ts
|
|
3779
|
+
init_Button();
|
|
3780
|
+
init_Card();
|
|
3052
3781
|
var SystemStatusCard = (props) => {
|
|
3053
3782
|
const { title, statusItems, className = "" } = props;
|
|
3054
3783
|
const progressColorClasses = {
|