@svadmin/core 0.52.0 → 0.54.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/package.json +31 -1
- package/src/approval-contract.ts +131 -0
- package/src/dashboard-contract.ts +72 -0
- package/src/date-time.ts +55 -0
- package/src/enterprise-maturity.ts +54 -0
- package/src/export-format.ts +44 -0
- package/src/export-hooks.svelte.ts +169 -8
- package/src/filter-values.ts +42 -0
- package/src/gantt.ts +84 -0
- package/src/i18n.svelte.ts +314 -2
- package/src/import-contract.ts +94 -6
- package/src/import-hooks.svelte.ts +13 -5
- package/src/index.ts +36 -1
- package/src/inferencer.ts +9 -0
- package/src/pivot.ts +367 -0
- package/src/query-session.svelte.ts +15 -6
- package/src/schema-form.ts +204 -0
- package/src/schema.ts +7 -0
- package/src/session-query.svelte.ts +26 -11
- package/src/spreadsheet-clipboard.ts +45 -0
- package/src/spreadsheet-workbook.ts +64 -0
- package/src/spreadsheet-xlsx.ts +181 -0
- package/src/task-hooks.svelte.ts +148 -113
- package/src/types.ts +31 -2
package/src/task-hooks.svelte.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { definedOptions } from './defined-options';
|
|
2
|
-
import {
|
|
2
|
+
import { onDestroy } from 'svelte';
|
|
3
|
+
import { createMutation,useQueryClient } from '@tanstack/svelte-query';
|
|
4
|
+
import { createSessionQuery } from './session-query.svelte';
|
|
5
|
+
import { supersededQuerySession } from './query-session.svelte';
|
|
6
|
+
import { captureAuthLiveScope } from './auth-hooks.svelte';
|
|
3
7
|
import { getAdminOptions } from './options.svelte';
|
|
4
8
|
import { captureAdminContext } from './context.svelte';
|
|
5
9
|
import type { AdminContextAccessor } from './context.svelte';
|
|
@@ -9,7 +13,7 @@ import {
|
|
|
9
13
|
import { queryKeyMatches, type QueryKey } from './query-keys';
|
|
10
14
|
import { replaceReactiveMembers } from './reactive-projection';
|
|
11
15
|
import { taskQueryParams, withValidatedTaskProvider } from './task-provider';
|
|
12
|
-
import
|
|
16
|
+
import { TaskError, decodeTaskRecord, decodeTaskList } from './task-contract';
|
|
13
17
|
import { checkError,createOvertimeTracker,fireErrorNotification,fireSuccessNotification } from './hook-utils.svelte';
|
|
14
18
|
import type { NotificationConfig,OvertimeOptions } from './hook-utils.svelte';
|
|
15
19
|
import { useTranslation } from './i18n.svelte';
|
|
@@ -48,6 +52,19 @@ function scopedTaskKey(key: QueryKey, provider: TaskProvider | undefined): Query
|
|
|
48
52
|
return [{ ...key[0], params: definedOptions({ taskProvider: providerKey, query: key[0].params }) }];
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
function taskReadError(error: unknown): Error {
|
|
56
|
+
try {
|
|
57
|
+
if (typeof error === 'object' && error !== null) {
|
|
58
|
+
const code: unknown = Object.getOwnPropertyDescriptor(error, 'code')?.value;
|
|
59
|
+
if (code === 'QUERY_SESSION_SUPERSEDED') return supersededQuerySession();
|
|
60
|
+
if (code === 'INVALID_TASK_INPUT' || code === 'INVALID_TASK_RESPONSE' || code === 'TASK_PROVIDER_FAILED') {
|
|
61
|
+
return new TaskError(code);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch { /* 不执行错误对象中的 getter,也不回显上游诊断。 */ }
|
|
65
|
+
return new TaskError('TASK_PROVIDER_FAILED');
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
export interface UseSubmitTaskOptions {
|
|
52
69
|
mutationOptions?: {
|
|
53
70
|
onSuccess?: (data: unknown,variables: unknown,context: unknown) => void;
|
|
@@ -70,29 +87,74 @@ export function useSubmitTask(options: UseSubmitTaskOptions={}) {
|
|
|
70
87
|
const adminOptions=getAdminOptions();
|
|
71
88
|
const queryClient=useQueryClient();
|
|
72
89
|
const overtime=createOvertimeTracker(() => mutation.isPending,options.overtimeOptions??adminOptions.overtime);
|
|
73
|
-
|
|
90
|
+
let disposed = false;
|
|
91
|
+
onDestroy(() => { disposed = true; });
|
|
92
|
+
interface SubmissionScope {
|
|
93
|
+
queryScope: ReturnType<AdminContextAccessor['queryKeyMatcher']>;
|
|
94
|
+
auth: ReturnType<typeof captureAuthLiveScope>;
|
|
95
|
+
notification: AdminContextAccessor['notificationProvider'];
|
|
96
|
+
current: () => boolean;
|
|
97
|
+
}
|
|
98
|
+
const submissionScopes = new WeakMap<TaskHandle, SubmissionScope>();
|
|
99
|
+
const failureScopes = new WeakMap<Error, SubmissionScope>();
|
|
74
100
|
|
|
75
101
|
const mutation=createMutation<TaskHandle,Error,UseSubmitTaskMutateParams>(() => ({
|
|
76
102
|
mutationFn: async (params) => {
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
const authProvider = adminContext.authProvider;
|
|
104
|
+
const auth = captureAuthLiveScope(authProvider);
|
|
105
|
+
const source = params.taskProvider ?? adminContext.taskProvider;
|
|
106
|
+
const tenant = adminContext.tenantCacheKey?.__svadminTenant;
|
|
107
|
+
const router = adminContext.routerProvider;
|
|
108
|
+
const access = adminContext.accessControlProvider;
|
|
109
|
+
const notification = adminContext.notificationProvider;
|
|
110
|
+
const scope: SubmissionScope = {
|
|
111
|
+
queryScope: adminContext.queryKeyMatcher(),
|
|
112
|
+
auth,
|
|
113
|
+
notification,
|
|
114
|
+
current: () => !disposed && auth.isCurrent() && adminContext.authProvider === authProvider
|
|
115
|
+
&& adminContext.tenantCacheKey?.__svadminTenant === tenant
|
|
116
|
+
&& adminContext.routerProvider === router && adminContext.accessControlProvider === access
|
|
117
|
+
&& adminContext.notificationProvider === notification
|
|
118
|
+
&& (params.taskProvider ?? adminContext.taskProvider) === source,
|
|
119
|
+
};
|
|
120
|
+
if (!scope.current()) throw supersededQuerySession();
|
|
121
|
+
try {
|
|
122
|
+
const provider=normalizeTaskProvider(source,adminContext);
|
|
123
|
+
const providerMeta=adminContext.tenant
|
|
124
|
+
? {
|
|
125
|
+
...(params.options?.meta??{}),
|
|
126
|
+
...resolveTenantProviderMeta(adminContext.tenant,adminContext.tenantAdapter),
|
|
127
|
+
}
|
|
128
|
+
:params.options?.meta;
|
|
129
|
+
const taskOptions=providerMeta===params.options?.meta
|
|
130
|
+
? params.options
|
|
131
|
+
:definedOptions({
|
|
132
|
+
...params.options,
|
|
133
|
+
meta: providerMeta,
|
|
134
|
+
});
|
|
135
|
+
const handle = await provider.submit(params.taskName,taskOptions);
|
|
136
|
+
submissionScopes.set(handle, scope);
|
|
137
|
+
return handle;
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const failure = error instanceof TaskError ? error : new TaskError('TASK_PROVIDER_FAILED');
|
|
140
|
+
failureScopes.set(failure, scope);
|
|
141
|
+
throw failure;
|
|
142
|
+
}
|
|
94
143
|
},
|
|
95
144
|
onSuccess: (data,params,context) => {
|
|
145
|
+
const submission = submissionScopes.get(data);
|
|
146
|
+
if (submission) submissionScopes.delete(data);
|
|
147
|
+
if (!submission) return;
|
|
148
|
+
if (submission.auth.isCurrent()) {
|
|
149
|
+
void queryClient.invalidateQueries({
|
|
150
|
+
predicate: (query) => queryKeyMatches(query.queryKey,{
|
|
151
|
+
...submission.queryScope,
|
|
152
|
+
kind: 'task',
|
|
153
|
+
action: 'list',
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (!submission.current()) return;
|
|
96
158
|
fireSuccessNotification({
|
|
97
159
|
config: params.successNotification,
|
|
98
160
|
defaultMessage: i18n.t('task.submitSuccess'),
|
|
@@ -100,34 +162,27 @@ export function useSubmitTask(options: UseSubmitTaskOptions={}) {
|
|
|
100
162
|
values: params.options,
|
|
101
163
|
resource: params.taskName,
|
|
102
164
|
...definedOptions({
|
|
103
|
-
provider:
|
|
165
|
+
provider: submission.notification
|
|
104
166
|
})
|
|
105
167
|
});
|
|
106
|
-
|
|
107
|
-
if (scope) {
|
|
108
|
-
submissionScopes.delete(data);
|
|
109
|
-
void queryClient.invalidateQueries({
|
|
110
|
-
predicate: (query) => queryKeyMatches(query.queryKey,{
|
|
111
|
-
...scope,
|
|
112
|
-
kind: 'task',
|
|
113
|
-
action: 'list',
|
|
114
|
-
}),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
options.mutationOptions?.onSuccess?.(data,params,context);
|
|
168
|
+
if (submission.current()) options.mutationOptions?.onSuccess?.(data,params,context);
|
|
118
169
|
},
|
|
119
170
|
onError: (error,params,context) => {
|
|
171
|
+
const submission = failureScopes.get(error);
|
|
172
|
+
failureScopes.delete(error);
|
|
173
|
+
if (!submission?.current()) return;
|
|
120
174
|
checkError(error,adminContext);
|
|
175
|
+
if (!submission.current()) return;
|
|
121
176
|
fireErrorNotification({
|
|
122
177
|
config: params.errorNotification,
|
|
123
178
|
defaultMessage: i18n.t('task.submitFailed'),
|
|
124
179
|
error,
|
|
125
180
|
resource: params.taskName,
|
|
126
181
|
...definedOptions({
|
|
127
|
-
provider:
|
|
182
|
+
provider: submission.notification
|
|
128
183
|
})
|
|
129
184
|
});
|
|
130
|
-
options.mutationOptions?.onError?.(error,params,context);
|
|
185
|
+
if (submission.current()) options.mutationOptions?.onError?.(error,params,context);
|
|
131
186
|
},
|
|
132
187
|
}));
|
|
133
188
|
|
|
@@ -157,12 +212,17 @@ export function useTask(
|
|
|
157
212
|
const i18n=useTranslation();
|
|
158
213
|
const adminOptions=getAdminOptions();
|
|
159
214
|
|
|
160
|
-
const query=
|
|
215
|
+
const query=createSessionQuery<TaskRecord>(adminContext, () => {
|
|
161
216
|
const activeProvider=options.taskProvider??adminContext.taskProvider;
|
|
162
217
|
const provider=activeProvider? withValidatedTaskProvider(activeProvider):undefined;
|
|
163
218
|
const taskId=options.taskId;
|
|
164
219
|
const queryOptions=options.queryOptions;
|
|
165
220
|
return {
|
|
221
|
+
resource: taskId ?? '',
|
|
222
|
+
successNotification: options.successNotification,
|
|
223
|
+
errorNotification: options.errorNotification ?? i18n.t('task.fetchFailed'),
|
|
224
|
+
normalizeError: taskReadError,
|
|
225
|
+
decode: value => decodeTaskRecord(value, taskId),
|
|
166
226
|
queryKey: scopedTaskKey(adminContext.queryKeys().task.one(taskId??''),provider),
|
|
167
227
|
queryFn: async () => {
|
|
168
228
|
if(!provider) throw new Error('TaskProvider not found.');
|
|
@@ -184,37 +244,6 @@ export function useTask(
|
|
|
184
244
|
|
|
185
245
|
const overtime=createOvertimeTracker(() => query.isLoading,options.overtimeOptions??adminOptions.overtime);
|
|
186
246
|
|
|
187
|
-
let lastSuccessAt=0;
|
|
188
|
-
let lastErrorAt=0;
|
|
189
|
-
$effect(() => {
|
|
190
|
-
if(query.isSuccess&&query.dataUpdatedAt>lastSuccessAt) {
|
|
191
|
-
lastSuccessAt=query.dataUpdatedAt;
|
|
192
|
-
if(options.successNotification) {
|
|
193
|
-
fireSuccessNotification({
|
|
194
|
-
config: options.successNotification,
|
|
195
|
-
defaultMessage: '',
|
|
196
|
-
data: query.data,
|
|
197
|
-
...definedOptions({ resource: options.taskId }),
|
|
198
|
-
...definedOptions({
|
|
199
|
-
provider: adminContext.notificationProvider
|
|
200
|
-
})
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
} else if(query.isError&&query.errorUpdatedAt>lastErrorAt) {
|
|
204
|
-
lastErrorAt=query.errorUpdatedAt;
|
|
205
|
-
checkError(query.error,adminContext);
|
|
206
|
-
fireErrorNotification({
|
|
207
|
-
config: options.errorNotification,
|
|
208
|
-
defaultMessage: i18n.t('task.fetchFailed'),
|
|
209
|
-
error: query.error,
|
|
210
|
-
...definedOptions({ resource: options.taskId }),
|
|
211
|
-
...definedOptions({
|
|
212
|
-
provider: adminContext.notificationProvider
|
|
213
|
-
})
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
|
|
218
247
|
return replaceReactiveMembers(query,{ get overtime() { return overtime; } });
|
|
219
248
|
}
|
|
220
249
|
|
|
@@ -242,25 +271,46 @@ export function useTaskList(
|
|
|
242
271
|
const i18n=useTranslation();
|
|
243
272
|
const adminOptions=getAdminOptions();
|
|
244
273
|
|
|
245
|
-
const query=
|
|
274
|
+
const query=createSessionQuery<TaskListResult>(adminContext, () => {
|
|
246
275
|
const activeProvider=options.taskProvider??adminContext.taskProvider;
|
|
247
276
|
const provider=activeProvider? withValidatedTaskProvider(activeProvider):undefined;
|
|
248
277
|
const params=options.params===undefined? undefined:taskQueryParams(options.params);
|
|
278
|
+
const callerMeta = params?.['meta'];
|
|
279
|
+
if (callerMeta !== undefined
|
|
280
|
+
&& (typeof callerMeta !== 'object' || callerMeta === null || Array.isArray(callerMeta))) {
|
|
281
|
+
throw new TaskError('INVALID_TASK_INPUT');
|
|
282
|
+
}
|
|
283
|
+
const tenantMeta = adminContext.tenant
|
|
284
|
+
? resolveTenantProviderMeta(adminContext.tenant, adminContext.tenantAdapter)
|
|
285
|
+
: undefined;
|
|
286
|
+
const providerParams = params === undefined && tenantMeta === undefined ? undefined : {
|
|
287
|
+
...(params ?? {}),
|
|
288
|
+
...(tenantMeta ? { meta: { ...(callerMeta ?? {}), ...tenantMeta } } : {}),
|
|
289
|
+
};
|
|
249
290
|
const dlq=options.dlq??false;
|
|
250
291
|
const queryOptions=options.queryOptions;
|
|
292
|
+
const resource = dlq ? 'taskDlq' : 'tasks';
|
|
293
|
+
const successNotification = options.successNotification;
|
|
251
294
|
return {
|
|
295
|
+
resource,
|
|
296
|
+
successNotification: typeof successNotification === 'function'
|
|
297
|
+
? (data: unknown) => successNotification(data, params, resource)
|
|
298
|
+
: successNotification,
|
|
299
|
+
errorNotification: options.errorNotification ?? i18n.t('task.fetchListFailed'),
|
|
300
|
+
normalizeError: taskReadError,
|
|
301
|
+
decode: decodeTaskList,
|
|
252
302
|
queryKey: scopedTaskKey(adminContext.queryKeys().task.list(definedOptions({
|
|
253
303
|
list: dlq? 'dlq':'default',
|
|
254
|
-
params,
|
|
304
|
+
params: providerParams,
|
|
255
305
|
})),provider),
|
|
256
306
|
queryFn: async () => {
|
|
257
307
|
if(!provider) throw new Error('TaskProvider not found.');
|
|
258
308
|
if(dlq) {
|
|
259
309
|
if(!provider.listDlq) throw new Error('TaskProvider does not implement listDlq');
|
|
260
|
-
return provider.listDlq(
|
|
310
|
+
return provider.listDlq(providerParams);
|
|
261
311
|
}
|
|
262
312
|
if(!provider.list) throw new Error('TaskProvider does not implement list');
|
|
263
|
-
return provider.list(
|
|
313
|
+
return provider.list(providerParams);
|
|
264
314
|
},
|
|
265
315
|
enabled: queryOptions?.enabled??true,
|
|
266
316
|
...definedOptions({
|
|
@@ -277,38 +327,6 @@ export function useTaskList(
|
|
|
277
327
|
|
|
278
328
|
const overtime=createOvertimeTracker(() => query.isLoading,options.overtimeOptions??adminOptions.overtime);
|
|
279
329
|
|
|
280
|
-
let lastSuccessAt=0;
|
|
281
|
-
let lastErrorAt=0;
|
|
282
|
-
$effect(() => {
|
|
283
|
-
if(query.isSuccess&&query.dataUpdatedAt>lastSuccessAt) {
|
|
284
|
-
lastSuccessAt=query.dataUpdatedAt;
|
|
285
|
-
if(options.successNotification) {
|
|
286
|
-
fireSuccessNotification({
|
|
287
|
-
config: options.successNotification,
|
|
288
|
-
defaultMessage: '',
|
|
289
|
-
data: query.data,
|
|
290
|
-
values: options.params,
|
|
291
|
-
resource: options.dlq? 'taskDlq':'tasks',
|
|
292
|
-
...definedOptions({
|
|
293
|
-
provider: adminContext.notificationProvider
|
|
294
|
-
})
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
} else if(query.isError&&query.errorUpdatedAt>lastErrorAt) {
|
|
298
|
-
lastErrorAt=query.errorUpdatedAt;
|
|
299
|
-
checkError(query.error,adminContext);
|
|
300
|
-
fireErrorNotification({
|
|
301
|
-
config: options.errorNotification,
|
|
302
|
-
defaultMessage: i18n.t('task.fetchListFailed'),
|
|
303
|
-
error: query.error,
|
|
304
|
-
resource: options.dlq? 'taskDlq':'tasks',
|
|
305
|
-
...definedOptions({
|
|
306
|
-
provider: adminContext.notificationProvider
|
|
307
|
-
})
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
|
|
312
330
|
return replaceReactiveMembers(query,{ get overtime() { return overtime; } });
|
|
313
331
|
}
|
|
314
332
|
|
|
@@ -328,19 +346,36 @@ export function useTaskSubscription(
|
|
|
328
346
|
const enabled=options.enabled??true;
|
|
329
347
|
if(!enabled) return;
|
|
330
348
|
|
|
331
|
-
const
|
|
349
|
+
const authProvider = adminContext.authProvider;
|
|
350
|
+
const auth = captureAuthLiveScope(authProvider);
|
|
351
|
+
if (!auth.available) return;
|
|
352
|
+
const source = options.taskProvider ?? adminContext.taskProvider;
|
|
353
|
+
const tenant = adminContext.tenantCacheKey?.__svadminTenant;
|
|
354
|
+
const router = adminContext.routerProvider;
|
|
355
|
+
const access = adminContext.accessControlProvider;
|
|
356
|
+
const taskId = options.taskId;
|
|
357
|
+
const onTask = options.onTask;
|
|
358
|
+
const onError = options.onError;
|
|
359
|
+
let active = true;
|
|
360
|
+
const current = () => active && (options.enabled ?? true) && options.taskId === taskId
|
|
361
|
+
&& (options.taskProvider ?? adminContext.taskProvider) === source
|
|
362
|
+
&& adminContext.authProvider === authProvider && auth.isCurrent()
|
|
363
|
+
&& adminContext.tenantCacheKey?.__svadminTenant === tenant
|
|
364
|
+
&& adminContext.routerProvider === router && adminContext.accessControlProvider === access;
|
|
365
|
+
const provider=normalizeTaskProvider(source,adminContext);
|
|
332
366
|
if(!provider.subscribe) {
|
|
333
367
|
throw new Error('TaskProvider does not implement subscribe');
|
|
334
368
|
}
|
|
335
369
|
|
|
336
|
-
const subscription=provider.subscribe(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
370
|
+
const subscription=provider.subscribe(taskId,
|
|
371
|
+
task => { if (current()) onTask(task); },
|
|
372
|
+
error => { if (current()) onError?.(error); },
|
|
373
|
+
);
|
|
374
|
+
return () => {
|
|
375
|
+
// 先失效,再退订;上游退订时同步发出的最后一条事件也不能进入新会话。
|
|
376
|
+
active = false;
|
|
377
|
+
if (typeof subscription === 'function') subscription();
|
|
378
|
+
else subscription.unsubscribe();
|
|
379
|
+
};
|
|
345
380
|
});
|
|
346
381
|
}
|
package/src/types.ts
CHANGED
|
@@ -405,12 +405,28 @@ export interface MenuItem {
|
|
|
405
405
|
children?: MenuItem[];
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
export interface DateInputOptions {
|
|
409
|
+
min?: string;
|
|
410
|
+
max?: string;
|
|
411
|
+
step?: number;
|
|
412
|
+
/** 仅用于 daterange;其他日期字段由 field.type 决定输入模式。 */
|
|
413
|
+
rangeMode?: 'date' | 'time' | 'datetime';
|
|
414
|
+
disabledDate?: (date: string) => boolean;
|
|
415
|
+
presets?: { label: string; value: { start: string; end: string } }[];
|
|
416
|
+
/** datetime 字段的输出语义;默认保留本地 datetime-local 字符串。 */
|
|
417
|
+
valueMode?: 'civil' | 'instant';
|
|
418
|
+
/** valueMode=instant 时必填的 IANA 时区。 */
|
|
419
|
+
timeZone?: string;
|
|
420
|
+
/** 仅用于 datetime,重复本地时刻默认拒绝,允许明确选择较早或较晚的一次。 */
|
|
421
|
+
disambiguation?: 'reject' | 'earlier' | 'later';
|
|
422
|
+
}
|
|
423
|
+
|
|
408
424
|
export interface FieldDefinition {
|
|
409
425
|
key: string;
|
|
410
426
|
label: string;
|
|
411
|
-
type: 'text' | 'number' | 'boolean' | 'date' | 'select' | 'multiselect' | 'tags'
|
|
427
|
+
type: 'text' | 'number' | 'boolean' | 'date' | 'time' | 'datetime' | 'daterange' | 'select' | 'multiselect' | 'tags'
|
|
412
428
|
| 'textarea' | 'richtext' | 'image' | 'images' | 'json' | 'relation' | 'color'
|
|
413
|
-
| 'url' | 'email' | 'phone' | 'currency' | 'file' | 'markdown' | 'password' | 'array'
|
|
429
|
+
| 'url' | 'email' | 'phone' | 'currency' | 'percent' | 'file' | 'markdown' | 'password' | 'array'
|
|
414
430
|
| 'tree-select' | 'treeselect' | 'cascader' | 'transfer' | 'rate' | 'rating' | 'avatar' | 'copy' | 'code';
|
|
415
431
|
required?: boolean;
|
|
416
432
|
searchable?: boolean;
|
|
@@ -430,6 +446,19 @@ export interface FieldDefinition {
|
|
|
430
446
|
changeOnSelect?: boolean;
|
|
431
447
|
separator?: string;
|
|
432
448
|
defaultValue?: unknown;
|
|
449
|
+
// Numeric input contract
|
|
450
|
+
min?: number;
|
|
451
|
+
max?: number;
|
|
452
|
+
step?: number;
|
|
453
|
+
precision?: number;
|
|
454
|
+
currency?: string;
|
|
455
|
+
locale?: string;
|
|
456
|
+
scale?: '100' | '1';
|
|
457
|
+
// 日期输入策略与数值上下限分离,不隐式转换时区。
|
|
458
|
+
dateInput?: DateInputOptions;
|
|
459
|
+
// File upload contract
|
|
460
|
+
accept?: string;
|
|
461
|
+
maxSize?: number;
|
|
433
462
|
// Relation support
|
|
434
463
|
resource?: string; // related resource name
|
|
435
464
|
optionLabel?: string; // field to use as label
|