imean-service-engine-htmx-plugin 2.10.1 → 2.11.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/dist/index.d.mts +47 -16
- package/dist/index.d.ts +47 -16
- package/dist/index.js +374 -166
- package/dist/index.mjs +374 -166
- package/docs/filter-schema-type-conversion.md +405 -0
- package/package.json +14 -15
package/dist/index.js
CHANGED
|
@@ -878,6 +878,22 @@ function convertBoolean(value) {
|
|
|
878
878
|
}
|
|
879
879
|
return value;
|
|
880
880
|
}
|
|
881
|
+
function convertDate(value) {
|
|
882
|
+
if (typeof value === "string") {
|
|
883
|
+
const trimmed = value.trim();
|
|
884
|
+
if (trimmed !== "") {
|
|
885
|
+
const dateValue = new Date(trimmed);
|
|
886
|
+
if (!isNaN(dateValue.getTime())) {
|
|
887
|
+
return dateValue;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return value;
|
|
891
|
+
}
|
|
892
|
+
if (value instanceof Date) {
|
|
893
|
+
return value;
|
|
894
|
+
}
|
|
895
|
+
return value;
|
|
896
|
+
}
|
|
881
897
|
function processArrayElement(item, elementType) {
|
|
882
898
|
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
883
899
|
const { typeName } = resolveZodType(elementType);
|
|
@@ -997,6 +1013,9 @@ function preprocessFormData(data, zodSchema) {
|
|
|
997
1013
|
case "boolean":
|
|
998
1014
|
processed[fieldName] = convertBoolean(value);
|
|
999
1015
|
break;
|
|
1016
|
+
case "date":
|
|
1017
|
+
processed[fieldName] = convertDate(value);
|
|
1018
|
+
break;
|
|
1000
1019
|
case "array":
|
|
1001
1020
|
processed[fieldName] = processArrayType(value, actualSchema);
|
|
1002
1021
|
break;
|
|
@@ -1019,6 +1038,8 @@ function convertValueByType(value, schema) {
|
|
|
1019
1038
|
return convertNumber(value);
|
|
1020
1039
|
case "boolean":
|
|
1021
1040
|
return convertBoolean(value);
|
|
1041
|
+
case "date":
|
|
1042
|
+
return convertDate(value);
|
|
1022
1043
|
default:
|
|
1023
1044
|
return value;
|
|
1024
1045
|
}
|
|
@@ -1244,6 +1265,11 @@ var BaseModelFeature = class extends BaseFeature {
|
|
|
1244
1265
|
};
|
|
1245
1266
|
var BaseFormFeature = class extends BaseModelFeature {
|
|
1246
1267
|
groups;
|
|
1268
|
+
options;
|
|
1269
|
+
constructor(options) {
|
|
1270
|
+
super(options);
|
|
1271
|
+
this.options = options;
|
|
1272
|
+
}
|
|
1247
1273
|
/**
|
|
1248
1274
|
* 设置标题和描述到 context
|
|
1249
1275
|
*/
|
|
@@ -1316,13 +1342,13 @@ var BaseFormFeature = class extends BaseModelFeature {
|
|
|
1316
1342
|
* 处理请求
|
|
1317
1343
|
*/
|
|
1318
1344
|
async handler(context) {
|
|
1319
|
-
context.actions =
|
|
1345
|
+
context.actions = [
|
|
1346
|
+
...this.options.actions ?? [],
|
|
1347
|
+
...this.getDefaultActions(context)
|
|
1348
|
+
];
|
|
1320
1349
|
if (context.ctx.req.method === "GET") {
|
|
1321
1350
|
const initialData = await this.getInitialData(context);
|
|
1322
|
-
await this.setTitleAndDescription(
|
|
1323
|
-
context,
|
|
1324
|
-
initialData
|
|
1325
|
-
);
|
|
1351
|
+
await this.setTitleAndDescription(context, initialData);
|
|
1326
1352
|
return this.renderForm(context, initialData);
|
|
1327
1353
|
}
|
|
1328
1354
|
const formData = preprocessFormData(context.body, this.schema);
|
|
@@ -1424,22 +1450,21 @@ var BaseFormFeature = class extends BaseModelFeature {
|
|
|
1424
1450
|
|
|
1425
1451
|
// src/features/default-create-feature.tsx
|
|
1426
1452
|
var DefaultCreateFeature = class extends BaseFormFeature {
|
|
1427
|
-
|
|
1453
|
+
createItem;
|
|
1454
|
+
constructor(createOptions) {
|
|
1428
1455
|
super({
|
|
1429
|
-
...
|
|
1456
|
+
...createOptions,
|
|
1430
1457
|
name: "create",
|
|
1431
1458
|
type: "create",
|
|
1432
1459
|
routes: [
|
|
1433
1460
|
{ method: "get", path: "/new" },
|
|
1434
1461
|
{ method: "post", path: "" }
|
|
1435
1462
|
],
|
|
1436
|
-
permission:
|
|
1463
|
+
permission: createOptions.permission || "create"
|
|
1437
1464
|
});
|
|
1438
|
-
this.
|
|
1439
|
-
this.
|
|
1440
|
-
this.groups = options.groups;
|
|
1465
|
+
this.createItem = createOptions.createItem;
|
|
1466
|
+
this.groups = createOptions.groups;
|
|
1441
1467
|
}
|
|
1442
|
-
createItem;
|
|
1443
1468
|
getFormAction() {
|
|
1444
1469
|
return "create";
|
|
1445
1470
|
}
|
|
@@ -1964,24 +1989,23 @@ var DefaultDetailFeature = class extends BaseModelFeature {
|
|
|
1964
1989
|
|
|
1965
1990
|
// src/features/default-edit-feature.tsx
|
|
1966
1991
|
var DefaultEditFeature = class extends BaseFormFeature {
|
|
1967
|
-
|
|
1992
|
+
getItem;
|
|
1993
|
+
updateItem;
|
|
1994
|
+
constructor(editOptions) {
|
|
1968
1995
|
super({
|
|
1969
|
-
...
|
|
1996
|
+
...editOptions,
|
|
1970
1997
|
name: "edit",
|
|
1971
1998
|
type: "edit",
|
|
1972
1999
|
routes: [
|
|
1973
2000
|
{ method: "get", path: "/edit/:id" },
|
|
1974
2001
|
{ method: "put", path: "/:id" }
|
|
1975
2002
|
],
|
|
1976
|
-
permission:
|
|
2003
|
+
permission: editOptions.permission || "edit"
|
|
1977
2004
|
});
|
|
1978
|
-
this.
|
|
1979
|
-
this.
|
|
1980
|
-
this.
|
|
1981
|
-
this.groups = options.groups;
|
|
2005
|
+
this.getItem = editOptions.getItem;
|
|
2006
|
+
this.updateItem = editOptions.updateItem;
|
|
2007
|
+
this.groups = editOptions.groups;
|
|
1982
2008
|
}
|
|
1983
|
-
getItem;
|
|
1984
|
-
updateItem;
|
|
1985
2009
|
getFormAction() {
|
|
1986
2010
|
return "edit";
|
|
1987
2011
|
}
|
|
@@ -2023,154 +2047,315 @@ var DefaultEditFeature = class extends BaseFormFeature {
|
|
|
2023
2047
|
return void 0;
|
|
2024
2048
|
}
|
|
2025
2049
|
};
|
|
2026
|
-
function
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
const value = currentFilters[field.name];
|
|
2034
|
-
if (value === null || value === void 0 || value === "") {
|
|
2035
|
-
return "";
|
|
2050
|
+
function SubmitButton() {
|
|
2051
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2052
|
+
"button",
|
|
2053
|
+
{
|
|
2054
|
+
type: "submit",
|
|
2055
|
+
className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium text-sm",
|
|
2056
|
+
children: "\u7B5B\u9009"
|
|
2036
2057
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
function ResetButton({ listPath }) {
|
|
2061
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2062
|
+
"a",
|
|
2063
|
+
{
|
|
2064
|
+
href: listPath,
|
|
2065
|
+
"hx-get": listPath,
|
|
2066
|
+
className: "px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors font-medium text-sm",
|
|
2067
|
+
children: "\u91CD\u7F6E"
|
|
2043
2068
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
function AddConditionButton() {
|
|
2072
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2073
|
+
"button",
|
|
2074
|
+
{
|
|
2075
|
+
type: "button",
|
|
2076
|
+
"x-show": "getAvailableFields().length > 0",
|
|
2077
|
+
className: "px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors font-medium text-sm flex items-center gap-2",
|
|
2078
|
+
"x-on:click": "showFieldSelector=!showFieldSelector",
|
|
2079
|
+
children: [
|
|
2080
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2081
|
+
"svg",
|
|
2082
|
+
{
|
|
2083
|
+
className: "w-4 h-4",
|
|
2084
|
+
fill: "none",
|
|
2085
|
+
stroke: "currentColor",
|
|
2086
|
+
viewBox: "0 0 24 24",
|
|
2087
|
+
"aria-hidden": "true",
|
|
2088
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2089
|
+
"path",
|
|
2090
|
+
{
|
|
2091
|
+
strokeLinecap: "round",
|
|
2092
|
+
strokeLinejoin: "round",
|
|
2093
|
+
strokeWidth: 2,
|
|
2094
|
+
d: "M12 4v16m8-8H4"
|
|
2095
|
+
}
|
|
2096
|
+
)
|
|
2097
|
+
}
|
|
2098
|
+
),
|
|
2099
|
+
"\u6DFB\u52A0\u7B5B\u9009\u6761\u4EF6"
|
|
2100
|
+
]
|
|
2101
|
+
}
|
|
2102
|
+
);
|
|
2103
|
+
}
|
|
2104
|
+
function FieldInput() {
|
|
2105
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2106
|
+
/* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'checkbox'", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2107
|
+
"select",
|
|
2108
|
+
{
|
|
2109
|
+
className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm appearance-none",
|
|
2110
|
+
"x-bind:name": "condition.fieldName",
|
|
2111
|
+
"x-model": "condition.value",
|
|
2112
|
+
autocomplete: "off",
|
|
2113
|
+
children: [
|
|
2114
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "true", children: "\u662F" }),
|
|
2115
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "false", children: "\u5426" })
|
|
2116
|
+
]
|
|
2117
|
+
}
|
|
2118
|
+
) }),
|
|
2119
|
+
/* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'select' && getField(condition.fieldName)?.options", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2120
|
+
"select",
|
|
2121
|
+
{
|
|
2122
|
+
className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm appearance-none",
|
|
2123
|
+
"x-bind:name": "condition.fieldName",
|
|
2124
|
+
"x-model": "condition.value",
|
|
2125
|
+
autocomplete: "off",
|
|
2126
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2127
|
+
"template",
|
|
2128
|
+
{
|
|
2129
|
+
"x-for": "option in getField(condition.fieldName)?.options",
|
|
2130
|
+
"x-bind:key": "option.value",
|
|
2131
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2132
|
+
"option",
|
|
2133
|
+
{
|
|
2134
|
+
"x-bind:value": "String(option.value)",
|
|
2135
|
+
"x-text": "option.label"
|
|
2136
|
+
}
|
|
2137
|
+
)
|
|
2138
|
+
}
|
|
2139
|
+
)
|
|
2140
|
+
}
|
|
2141
|
+
) }),
|
|
2142
|
+
/* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'date'", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2143
|
+
"input",
|
|
2144
|
+
{
|
|
2145
|
+
type: "date",
|
|
2146
|
+
className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm",
|
|
2147
|
+
"x-bind:name": "condition.fieldName",
|
|
2148
|
+
"x-model": "condition.value",
|
|
2149
|
+
autocomplete: "off"
|
|
2150
|
+
}
|
|
2151
|
+
) }),
|
|
2152
|
+
/* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'number'", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2153
|
+
"input",
|
|
2154
|
+
{
|
|
2155
|
+
type: "number",
|
|
2156
|
+
className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm",
|
|
2157
|
+
"x-bind:name": "condition.fieldName",
|
|
2158
|
+
"x-bind:placeholder": "getField(condition.fieldName)?.placeholder || '\u8BF7\u8F93\u5165' + getField(condition.fieldName)?.label",
|
|
2159
|
+
"x-bind:step": "getField(condition.fieldName)?.step",
|
|
2160
|
+
"x-model": "condition.value",
|
|
2161
|
+
autocomplete: "off"
|
|
2162
|
+
}
|
|
2163
|
+
) }),
|
|
2164
|
+
/* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "getField(condition.fieldName)?.type === 'email'", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2165
|
+
"input",
|
|
2166
|
+
{
|
|
2167
|
+
type: "email",
|
|
2168
|
+
className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm",
|
|
2169
|
+
"x-bind:name": "condition.fieldName",
|
|
2170
|
+
"x-bind:placeholder": "getField(condition.fieldName)?.placeholder || '\u8BF7\u8F93\u5165' + getField(condition.fieldName)?.label",
|
|
2171
|
+
"x-model": "condition.value",
|
|
2172
|
+
autocomplete: "off"
|
|
2173
|
+
}
|
|
2174
|
+
) }),
|
|
2175
|
+
/* @__PURE__ */ jsxRuntime.jsx("template", { "x-if": "!['checkbox', 'select', 'date', 'number', 'email'].includes(getField(condition.fieldName)?.type || '')", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2176
|
+
"input",
|
|
2177
|
+
{
|
|
2178
|
+
type: "text",
|
|
2179
|
+
className: "w-full h-full px-3 py-2 border-0 rounded-none focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white text-sm",
|
|
2180
|
+
"x-bind:name": "condition.fieldName",
|
|
2181
|
+
"x-bind:placeholder": "getField(condition.fieldName)?.placeholder || '\u8BF7\u8F93\u5165' + getField(condition.fieldName)?.label",
|
|
2182
|
+
"x-model": "condition.value",
|
|
2183
|
+
autocomplete: "off"
|
|
2184
|
+
}
|
|
2185
|
+
) })
|
|
2186
|
+
] });
|
|
2187
|
+
}
|
|
2188
|
+
function FieldSelectorDropdown({ fields }) {
|
|
2189
|
+
const transitionProps = {
|
|
2190
|
+
"x-show": "showFieldSelector",
|
|
2191
|
+
"x-transition:enter": "transition ease-out duration-200",
|
|
2192
|
+
"x-transition:enter-start": "opacity-0 scale-95",
|
|
2193
|
+
"x-transition:enter-end": "opacity-100 scale-100",
|
|
2194
|
+
"x-transition:leave": "transition ease-in duration-150",
|
|
2195
|
+
"x-transition:leave-start": "opacity-100 scale-100",
|
|
2196
|
+
"x-transition:leave-end": "opacity-0 scale-95"
|
|
2197
|
+
};
|
|
2198
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2199
|
+
"div",
|
|
2200
|
+
{
|
|
2201
|
+
className: "absolute top-full left-0 mt-2 w-64 bg-white border border-gray-200 rounded-lg shadow-lg z-10",
|
|
2202
|
+
...transitionProps,
|
|
2203
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3", children: [
|
|
2204
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-xs font-semibold text-gray-600 mb-2", children: "\u9009\u62E9\u7B5B\u9009\u5B57\u6BB5" }),
|
|
2205
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 max-h-64 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2206
|
+
"template",
|
|
2207
|
+
{
|
|
2208
|
+
"x-for": "field in getAvailableFields()",
|
|
2209
|
+
"x-bind:key": "field.name",
|
|
2210
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2211
|
+
"button",
|
|
2212
|
+
{
|
|
2213
|
+
type: "button",
|
|
2214
|
+
className: "w-full text-left px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded-lg transition-colors",
|
|
2215
|
+
"x-on:click": "onFieldSelected(field.name)",
|
|
2216
|
+
"x-text": "field.label"
|
|
2217
|
+
}
|
|
2218
|
+
)
|
|
2219
|
+
}
|
|
2220
|
+
) })
|
|
2221
|
+
] })
|
|
2222
|
+
}
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
function FilterConditionItem({ children }) {
|
|
2226
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-[auto_1fr_auto] items-stretch h-10 bg-gray-50 rounded-lg border border-gray-200 overflow-hidden", children: [
|
|
2227
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2228
|
+
"label",
|
|
2229
|
+
{
|
|
2230
|
+
className: "px-3 flex items-center text-xs font-semibold text-gray-600 whitespace-nowrap",
|
|
2231
|
+
"x-text": "getFieldLabel(condition.fieldName)"
|
|
2232
|
+
}
|
|
2233
|
+
),
|
|
2234
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full border-l border-r border-gray-300", children }),
|
|
2235
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2236
|
+
"button",
|
|
2237
|
+
{
|
|
2238
|
+
type: "button",
|
|
2239
|
+
className: "px-3 flex items-center justify-center text-gray-400 hover:text-red-600 hover:bg-red-50 transition-colors",
|
|
2240
|
+
"x-on:click": "removeCondition(condition.id)",
|
|
2241
|
+
title: "\u5220\u9664\u6B64\u7B5B\u9009\u6761\u4EF6",
|
|
2242
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2243
|
+
"svg",
|
|
2244
|
+
{
|
|
2245
|
+
className: "w-4 h-4",
|
|
2246
|
+
fill: "none",
|
|
2247
|
+
stroke: "currentColor",
|
|
2248
|
+
viewBox: "0 0 24 24",
|
|
2249
|
+
"aria-hidden": "true",
|
|
2250
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2251
|
+
"path",
|
|
2252
|
+
{
|
|
2253
|
+
strokeLinecap: "round",
|
|
2254
|
+
strokeLinejoin: "round",
|
|
2255
|
+
strokeWidth: 2,
|
|
2256
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
2257
|
+
}
|
|
2258
|
+
)
|
|
2259
|
+
}
|
|
2260
|
+
)
|
|
2261
|
+
}
|
|
2262
|
+
)
|
|
2263
|
+
] });
|
|
2264
|
+
}
|
|
2265
|
+
function DynamicFilters(props) {
|
|
2266
|
+
const { fields, listPath, currentFilters = {} } = props;
|
|
2046
2267
|
if (fields.length === 0) {
|
|
2047
2268
|
return null;
|
|
2048
2269
|
}
|
|
2049
|
-
|
|
2270
|
+
const systemParams = /* @__PURE__ */ new Set(["page", "pageSize", "sortBy", "sortOrder"]);
|
|
2271
|
+
const initialConditions = [];
|
|
2272
|
+
for (const field of fields) {
|
|
2273
|
+
if (currentFilters.hasOwnProperty(field.name) && !systemParams.has(field.name)) {
|
|
2274
|
+
const value = currentFilters[field.name];
|
|
2275
|
+
initialConditions.push({
|
|
2276
|
+
id: `condition-${Date.now()}-${Math.random()}`,
|
|
2277
|
+
fieldName: field.name,
|
|
2278
|
+
value: value === null || value === void 0 ? "" : String(value)
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
const availableFields = fields.map((field) => ({
|
|
2283
|
+
name: field.name,
|
|
2284
|
+
label: field.label,
|
|
2285
|
+
type: field.type,
|
|
2286
|
+
options: field.options,
|
|
2287
|
+
step: field.step,
|
|
2288
|
+
placeholder: field.placeholder
|
|
2289
|
+
}));
|
|
2290
|
+
const escapedListPath = listPath.replace(/'/g, "\\'");
|
|
2291
|
+
const alpineData = `{
|
|
2292
|
+
conditions: ${JSON.stringify(initialConditions)},
|
|
2293
|
+
availableFields: ${JSON.stringify(availableFields)},
|
|
2294
|
+
showFieldSelector: false,
|
|
2295
|
+
listPath: '${escapedListPath}',
|
|
2296
|
+
|
|
2297
|
+
|
|
2298
|
+
// \u83B7\u53D6\u5B57\u6BB5\u5B9A\u4E49
|
|
2299
|
+
getField(fieldName) {
|
|
2300
|
+
return this.availableFields.find(f => f.name === fieldName);
|
|
2301
|
+
},
|
|
2302
|
+
|
|
2303
|
+
// \u83B7\u53D6\u5B57\u6BB5\u6807\u7B7E
|
|
2304
|
+
getFieldLabel(fieldName) {
|
|
2305
|
+
const field = this.getField(fieldName);
|
|
2306
|
+
return field ? field.label : fieldName;
|
|
2307
|
+
},
|
|
2308
|
+
|
|
2309
|
+
// \u68C0\u67E5\u5B57\u6BB5\u662F\u5426\u5DF2\u88AB\u4F7F\u7528
|
|
2310
|
+
isFieldUsed(fieldName) {
|
|
2311
|
+
return this.conditions.some(c => c.fieldName === fieldName);
|
|
2312
|
+
},
|
|
2313
|
+
|
|
2314
|
+
// \u83B7\u53D6\u6240\u6709\u53EF\u7528\u5B57\u6BB5\uFF08\u672A\u4F7F\u7528\u7684\u5B57\u6BB5\uFF09
|
|
2315
|
+
getAvailableFields() {
|
|
2316
|
+
return this.availableFields.filter(f => !this.isFieldUsed(f.name));
|
|
2317
|
+
},
|
|
2318
|
+
|
|
2319
|
+
// \u5220\u9664\u6761\u4EF6
|
|
2320
|
+
removeCondition(id) {
|
|
2321
|
+
this.conditions = this.conditions.filter(c => c.id !== id);
|
|
2322
|
+
},
|
|
2323
|
+
|
|
2324
|
+
// \u5B57\u6BB5\u9009\u62E9\u56DE\u8C03\uFF08\u4ECE\u6D6E\u5C42\u9009\u62E9\u5B57\u6BB5\u65F6\u8C03\u7528\uFF09
|
|
2325
|
+
onFieldSelected(fieldName) {
|
|
2326
|
+
// \u68C0\u67E5\u5B57\u6BB5\u662F\u5426\u53EF\u7528
|
|
2327
|
+
if (!this.isFieldUsed(fieldName)) {
|
|
2328
|
+
const newCondition = {
|
|
2329
|
+
id: 'condition-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9),
|
|
2330
|
+
fieldName: fieldName,
|
|
2331
|
+
value: ''
|
|
2332
|
+
};
|
|
2333
|
+
setTimeout(() => {
|
|
2334
|
+
this.conditions.push(newCondition);
|
|
2335
|
+
}, 200);
|
|
2336
|
+
}
|
|
2337
|
+
this.showFieldSelector = false;
|
|
2338
|
+
}
|
|
2339
|
+
}`;
|
|
2340
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white border border-gray-200 rounded-lg shadow-sm p-4 mb-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2050
2341
|
"form",
|
|
2051
2342
|
{
|
|
2052
2343
|
method: "get",
|
|
2053
2344
|
action: listPath,
|
|
2054
2345
|
"hx-get": listPath,
|
|
2055
|
-
className: "space-y-4",
|
|
2056
|
-
"data
|
|
2057
|
-
children: [
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
{
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
checked: getCheckboxValue(field),
|
|
2067
|
-
className: "w-4 h-4 text-blue-600 bg-white border-gray-300 rounded focus:ring-blue-500 focus:ring-2",
|
|
2068
|
-
"data-testid": `filter-checkbox-${field.name}`
|
|
2069
|
-
}
|
|
2070
|
-
),
|
|
2071
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2072
|
-
"label",
|
|
2073
|
-
{
|
|
2074
|
-
htmlFor: `filter-${field.name}`,
|
|
2075
|
-
className: "text-sm font-semibold text-gray-700 cursor-pointer",
|
|
2076
|
-
"data-testid": `filter-label-${field.name}`,
|
|
2077
|
-
children: field.label
|
|
2078
|
-
}
|
|
2079
|
-
)
|
|
2080
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2081
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2082
|
-
"label",
|
|
2083
|
-
{
|
|
2084
|
-
htmlFor: `filter-${field.name}`,
|
|
2085
|
-
className: "block text-sm font-semibold text-gray-700",
|
|
2086
|
-
"data-testid": `filter-label-${field.name}`,
|
|
2087
|
-
children: field.label
|
|
2088
|
-
}
|
|
2089
|
-
),
|
|
2090
|
-
field.type === "select" ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2091
|
-
"select",
|
|
2092
|
-
{
|
|
2093
|
-
id: `filter-${field.name}`,
|
|
2094
|
-
name: field.name,
|
|
2095
|
-
className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
|
|
2096
|
-
"data-testid": `filter-select-${field.name}`,
|
|
2097
|
-
children: [
|
|
2098
|
-
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", selected: getFieldValue(field) === "", children: "\u5168\u90E8" }),
|
|
2099
|
-
field.options?.map((option) => {
|
|
2100
|
-
const optionValue = String(option.value);
|
|
2101
|
-
const isSelected = getFieldValue(field) === optionValue;
|
|
2102
|
-
return /* @__PURE__ */ jsxRuntime.jsx("option", { value: optionValue, selected: isSelected, children: option.label }, optionValue);
|
|
2103
|
-
})
|
|
2104
|
-
]
|
|
2105
|
-
}
|
|
2106
|
-
) : field.type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2107
|
-
"input",
|
|
2108
|
-
{
|
|
2109
|
-
id: `filter-${field.name}`,
|
|
2110
|
-
name: field.name,
|
|
2111
|
-
type: "date",
|
|
2112
|
-
value: getFieldValue(field),
|
|
2113
|
-
className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
|
|
2114
|
-
"data-testid": `filter-input-${field.name}`
|
|
2115
|
-
}
|
|
2116
|
-
) : field.type === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2117
|
-
"input",
|
|
2118
|
-
{
|
|
2119
|
-
id: `filter-${field.name}`,
|
|
2120
|
-
name: field.name,
|
|
2121
|
-
type: "number",
|
|
2122
|
-
value: getFieldValue(field),
|
|
2123
|
-
placeholder: field.placeholder || `\u8BF7\u8F93\u5165${field.label}`,
|
|
2124
|
-
step: field.step,
|
|
2125
|
-
className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
|
|
2126
|
-
"data-testid": `filter-input-${field.name}`
|
|
2127
|
-
}
|
|
2128
|
-
) : field.type === "email" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2129
|
-
"input",
|
|
2130
|
-
{
|
|
2131
|
-
id: `filter-${field.name}`,
|
|
2132
|
-
name: field.name,
|
|
2133
|
-
type: "email",
|
|
2134
|
-
value: getFieldValue(field),
|
|
2135
|
-
placeholder: field.placeholder || `\u8BF7\u8F93\u5165${field.label}`,
|
|
2136
|
-
className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
|
|
2137
|
-
"data-testid": `filter-input-${field.name}`
|
|
2138
|
-
}
|
|
2139
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2140
|
-
"input",
|
|
2141
|
-
{
|
|
2142
|
-
id: `filter-${field.name}`,
|
|
2143
|
-
name: field.name,
|
|
2144
|
-
type: "text",
|
|
2145
|
-
value: getFieldValue(field),
|
|
2146
|
-
placeholder: field.placeholder || `\u8BF7\u8F93\u5165${field.label}`,
|
|
2147
|
-
className: "w-full px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 bg-white",
|
|
2148
|
-
"data-testid": `filter-input-${field.name}`
|
|
2149
|
-
}
|
|
2150
|
-
)
|
|
2151
|
-
] }) }, field.name)) }),
|
|
2152
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 pt-2", children: [
|
|
2153
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2154
|
-
"button",
|
|
2155
|
-
{
|
|
2156
|
-
type: "submit",
|
|
2157
|
-
className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium",
|
|
2158
|
-
"data-testid": "filter-submit-button",
|
|
2159
|
-
children: "\u7B5B\u9009"
|
|
2160
|
-
}
|
|
2161
|
-
),
|
|
2162
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2163
|
-
"a",
|
|
2164
|
-
{
|
|
2165
|
-
href: listPath,
|
|
2166
|
-
"hx-get": listPath,
|
|
2167
|
-
className: "px-4 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition-colors font-medium",
|
|
2168
|
-
"data-testid": "filter-reset-button",
|
|
2169
|
-
children: "\u91CD\u7F6E"
|
|
2170
|
-
}
|
|
2171
|
-
)
|
|
2346
|
+
className: "space-y-4 mb-0",
|
|
2347
|
+
"x-data": alpineData,
|
|
2348
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
2349
|
+
/* @__PURE__ */ jsxRuntime.jsx("template", { "x-for": "condition in conditions", "x-bind:key": "condition.id", children: /* @__PURE__ */ jsxRuntime.jsx(FilterConditionItem, { children: /* @__PURE__ */ jsxRuntime.jsx(FieldInput, {}) }) }),
|
|
2350
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2351
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
2352
|
+
/* @__PURE__ */ jsxRuntime.jsx(AddConditionButton, {}),
|
|
2353
|
+
/* @__PURE__ */ jsxRuntime.jsx(FieldSelectorDropdown, { fields })
|
|
2354
|
+
] }),
|
|
2355
|
+
/* @__PURE__ */ jsxRuntime.jsx(SubmitButton, {}),
|
|
2356
|
+
/* @__PURE__ */ jsxRuntime.jsx(ResetButton, { listPath })
|
|
2172
2357
|
] })
|
|
2173
|
-
]
|
|
2358
|
+
] })
|
|
2174
2359
|
}
|
|
2175
2360
|
) });
|
|
2176
2361
|
}
|
|
@@ -2596,7 +2781,7 @@ function ListPage(props) {
|
|
|
2596
2781
|
];
|
|
2597
2782
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
|
|
2598
2783
|
filterFields && filterFields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2599
|
-
|
|
2784
|
+
DynamicFilters,
|
|
2600
2785
|
{
|
|
2601
2786
|
fields: filterFields,
|
|
2602
2787
|
listPath,
|
|
@@ -2634,21 +2819,42 @@ function ListPage(props) {
|
|
|
2634
2819
|
}
|
|
2635
2820
|
|
|
2636
2821
|
// src/utils/params.ts
|
|
2637
|
-
function parseListParams(ctx) {
|
|
2822
|
+
function parseListParams(ctx, filterSchema) {
|
|
2638
2823
|
const url = new URL(ctx.req.url);
|
|
2639
2824
|
const systemParams = /* @__PURE__ */ new Set(["page", "pageSize", "sortBy", "sortOrder"]);
|
|
2640
2825
|
const filters = {};
|
|
2641
2826
|
for (const [key, value] of url.searchParams.entries()) {
|
|
2642
|
-
if (!systemParams.has(key)
|
|
2827
|
+
if (!systemParams.has(key)) {
|
|
2643
2828
|
filters[key] = value;
|
|
2644
2829
|
}
|
|
2645
2830
|
}
|
|
2831
|
+
let processedFilters = void 0;
|
|
2832
|
+
if (filterSchema && Object.keys(filters).length > 0) {
|
|
2833
|
+
const emptyStringFields = /* @__PURE__ */ new Set();
|
|
2834
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
2835
|
+
if (value === "") {
|
|
2836
|
+
emptyStringFields.add(key);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
const preprocessed = preprocessFormData(filters, filterSchema);
|
|
2840
|
+
const cleanedFilters = {};
|
|
2841
|
+
for (const [key, value] of Object.entries(preprocessed)) {
|
|
2842
|
+
if (emptyStringFields.has(key)) {
|
|
2843
|
+
cleanedFilters[key] = "";
|
|
2844
|
+
} else if (value !== void 0 && value !== null) {
|
|
2845
|
+
cleanedFilters[key] = value;
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
processedFilters = Object.keys(cleanedFilters).length > 0 ? cleanedFilters : void 0;
|
|
2849
|
+
} else if (Object.keys(filters).length > 0) {
|
|
2850
|
+
processedFilters = filters;
|
|
2851
|
+
}
|
|
2646
2852
|
return {
|
|
2647
2853
|
page: parseInt(url.searchParams.get("page") || "1", 10),
|
|
2648
2854
|
pageSize: parseInt(url.searchParams.get("pageSize") || "10", 10),
|
|
2649
2855
|
sortBy: url.searchParams.get("sortBy") || void 0,
|
|
2650
2856
|
sortOrder: url.searchParams.get("sortOrder") || void 0,
|
|
2651
|
-
filters:
|
|
2857
|
+
filters: processedFilters
|
|
2652
2858
|
};
|
|
2653
2859
|
}
|
|
2654
2860
|
var DefaultListFeature = class extends BaseModelFeature {
|
|
@@ -2677,7 +2883,7 @@ var DefaultListFeature = class extends BaseModelFeature {
|
|
|
2677
2883
|
const prefix = context.prefix || "";
|
|
2678
2884
|
const basePath = `${prefix}/${model.modelName}`;
|
|
2679
2885
|
const hasCreate = model.features.get("create") !== void 0;
|
|
2680
|
-
const actions = [];
|
|
2886
|
+
const actions = [...this.options.actions ?? []];
|
|
2681
2887
|
if (hasCreate) {
|
|
2682
2888
|
const createMode = this.openMode?.create || "dialog";
|
|
2683
2889
|
const createUrl = createMode === "dialog" ? `${basePath}/new?dialog=true` : `${basePath}/new`;
|
|
@@ -2699,8 +2905,10 @@ var DefaultListFeature = class extends BaseModelFeature {
|
|
|
2699
2905
|
return actions;
|
|
2700
2906
|
}
|
|
2701
2907
|
async handler(context) {
|
|
2702
|
-
const params = parseListParams(context.ctx);
|
|
2703
|
-
const result = await this.getList(
|
|
2908
|
+
const params = this.filterSchema ? parseListParams(context.ctx, this.filterSchema) : parseListParams(context.ctx);
|
|
2909
|
+
const result = await this.getList(
|
|
2910
|
+
params
|
|
2911
|
+
);
|
|
2704
2912
|
context.actions = this.getDefaultActions(context);
|
|
2705
2913
|
const title = this.options?.title;
|
|
2706
2914
|
if (typeof title === "function") {
|
|
@@ -5431,9 +5639,9 @@ exports.DefaultEditFeature = DefaultEditFeature;
|
|
|
5431
5639
|
exports.DefaultListFeature = DefaultListFeature;
|
|
5432
5640
|
exports.DetailFieldRenderer = DetailFieldRenderer;
|
|
5433
5641
|
exports.Dialog = Dialog;
|
|
5642
|
+
exports.DynamicFilters = DynamicFilters;
|
|
5434
5643
|
exports.EmptyState = EmptyState;
|
|
5435
5644
|
exports.ErrorAlert = ErrorAlert;
|
|
5436
|
-
exports.FilterForm = FilterForm;
|
|
5437
5645
|
exports.FragmentFeature = FragmentFeature;
|
|
5438
5646
|
exports.Header = Header;
|
|
5439
5647
|
exports.HtmxAdminPlugin = HtmxAdminPlugin;
|