@kine-design/crud 0.0.1-beta.2 → 0.0.1-beta.21
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/.vlaude/last-session-id +1 -0
- package/components/crudPage/KCrudPage.tsx +178 -0
- package/components/crudPage/crudPage.css +64 -0
- package/components/crudPage/index.ts +10 -0
- package/components/editableTable/KEditableTable.tsx +281 -0
- package/components/editableTable/editableTable.css +268 -0
- package/components/editableTable/index.ts +10 -0
- package/components/formPage/KApprovalDialog.tsx +142 -0
- package/components/formPage/KFormCard.tsx +65 -0
- package/components/formPage/KFormPage.tsx +128 -0
- package/components/formPage/KMasterDetailPage.tsx +205 -0
- package/components/formPage/KStickyActionBar.tsx +33 -0
- package/components/formPage/formPage.css +629 -0
- package/components/formPage/index.ts +14 -0
- package/components/layout/KContent.tsx +20 -0
- package/components/layout/KHeader.tsx +37 -0
- package/components/layout/KLayout.tsx +82 -0
- package/components/layout/KSider.tsx +80 -0
- package/components/layout/index.ts +18 -0
- package/components/layout/layout.css +262 -0
- package/components/login/KLoginPage.tsx +129 -0
- package/components/login/index.ts +10 -0
- package/components/login/login.css +118 -0
- package/components/navMenu/KNavMenu.tsx +175 -0
- package/components/navMenu/index.ts +2 -0
- package/components/navMenu/navMenu.css +197 -0
- package/components/pageHeader/KPageHeader.tsx +85 -0
- package/components/pageHeader/index.ts +9 -0
- package/components/pageHeader/pageHeader.css +93 -0
- package/components/searchTable/KSearchTable.tsx +138 -0
- package/components/searchTable/index.ts +10 -0
- package/components/searchTable/searchTable.css +121 -0
- package/components/upload/KFileList.tsx +95 -0
- package/components/upload/KImageUpload.tsx +286 -0
- package/components/upload/KUpload.tsx +206 -0
- package/components/upload/index.ts +13 -0
- package/components/upload/types.ts +26 -0
- package/components/upload/upload.css +345 -0
- package/composables/auth/authGuard.ts +128 -0
- package/composables/auth/index.ts +23 -0
- package/composables/auth/types.ts +109 -0
- package/composables/auth/useAuth.ts +278 -0
- package/composables/auth/vCan.ts +95 -0
- package/composables/defineRepository.ts +224 -0
- package/composables/error/createErrorHandler.ts +46 -0
- package/composables/error/defaultFeedbackHandler.ts +76 -0
- package/composables/error/dispatchError.ts +70 -0
- package/composables/error/index.ts +32 -0
- package/composables/error/types.ts +57 -0
- package/composables/error/useErrorHandler.ts +41 -0
- package/composables/form/index.ts +18 -0
- package/composables/form/renderFormField.tsx +119 -0
- package/composables/form/types.ts +129 -0
- package/composables/form/useFormPage.ts +183 -0
- package/composables/index.ts +62 -0
- package/composables/page/index.ts +11 -0
- package/composables/page/types.ts +62 -0
- package/composables/page/useCrudPage.ts +88 -0
- package/composables/request/composables.ts +206 -0
- package/composables/request/controlGate.ts +143 -0
- package/composables/request/createRequest.ts +173 -0
- package/composables/request/index.ts +71 -0
- package/composables/request/orchestrator.ts +145 -0
- package/composables/request/requestBuilder.ts +418 -0
- package/composables/request/transport/fetchTransport.ts +79 -0
- package/composables/request/transport/xhrTransport.ts +100 -0
- package/composables/request/types.ts +226 -0
- package/composables/request/upload.ts +146 -0
- package/composables/router/createRouterGuard.ts +134 -0
- package/composables/router/defineCrudRoutes.ts +116 -0
- package/composables/router/index.ts +22 -0
- package/composables/router/types.ts +128 -0
- package/composables/router/useMenuFromRoutes.ts +109 -0
- package/composables/router/useTabStore.ts +183 -0
- package/composables/search/index.ts +11 -0
- package/composables/search/useAutoCompleteSearch.ts +161 -0
- package/composables/setupCrud.ts +43 -0
- package/composables/storage/createStorageAdapter.ts +72 -0
- package/composables/storage/index.ts +13 -0
- package/composables/storage/types.ts +30 -0
- package/composables/storage/useStorage.ts +108 -0
- package/composables/store/defineUserStore.ts +122 -0
- package/composables/store/index.ts +11 -0
- package/composables/types.ts +118 -0
- package/dist/components/crudPage/KCrudPage.d.ts +14 -0
- package/dist/components/crudPage/index.d.ts +9 -0
- package/dist/components/editableTable/KEditableTable.d.ts +146 -0
- package/dist/components/editableTable/index.d.ts +10 -0
- package/dist/components/formPage/KApprovalDialog.d.ts +99 -0
- package/dist/components/formPage/KFormCard.d.ts +49 -0
- package/dist/components/formPage/KFormPage.d.ts +14 -0
- package/dist/components/formPage/KMasterDetailPage.d.ts +14 -0
- package/dist/components/formPage/KStickyActionBar.d.ts +16 -0
- package/dist/components/formPage/index.d.ts +14 -0
- package/dist/components/layout/KLayout.d.ts +7 -4
- package/dist/composables/auth/useAuth.d.ts +5 -5
- package/dist/composables/error/types.d.ts +2 -1
- package/dist/composables/form/index.d.ts +12 -0
- package/dist/composables/form/renderFormField.d.ts +11 -0
- package/dist/composables/form/types.d.ts +104 -0
- package/dist/composables/form/useFormPage.d.ts +38 -0
- package/dist/composables/index.d.ts +2 -0
- package/dist/composables/page/index.d.ts +10 -0
- package/dist/composables/page/types.d.ts +61 -0
- package/dist/composables/page/useCrudPage.d.ts +14 -0
- package/dist/composables/request/createRequest.d.ts +2 -0
- package/dist/composables/request/requestBuilder.d.ts +2 -0
- package/dist/composables/search/index.d.ts +10 -0
- package/dist/composables/search/useAutoCompleteSearch.d.ts +50 -0
- package/dist/crud.css +2499 -663
- package/dist/crud.js +11512 -2910
- package/dist/index.d.ts +11 -0
- package/dist/setup.d.ts +2 -2
- package/index.ts +144 -0
- package/package.json +20 -19
- package/setup.ts +288 -0
- package/tsconfig.json +12 -0
- package/vite.config.build.ts +52 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 默认用户反馈处理器,基于 KMessage 显示 toast
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/15
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { UserFeedbackHandler } from './types';
|
|
11
|
+
|
|
12
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// 尝试获取 KMessage
|
|
14
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 懒加载 useMessage,避免循环依赖和 SSR 问题。
|
|
18
|
+
* 如果 kine-ui 不可用则降级到 console。
|
|
19
|
+
*/
|
|
20
|
+
function tryGetMessageApi() {
|
|
21
|
+
try {
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
+
const { useMessage } = require('@kine-design/ui');
|
|
24
|
+
return useMessage();
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 缓存 message 实例 */
|
|
31
|
+
let messageApi: ReturnType<typeof tryGetMessageApi> = undefined;
|
|
32
|
+
|
|
33
|
+
function getMessageApi() {
|
|
34
|
+
if (messageApi === undefined) {
|
|
35
|
+
messageApi = tryGetMessageApi();
|
|
36
|
+
}
|
|
37
|
+
return messageApi;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
// 默认反馈处理器
|
|
42
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/** 默认的 UserFeedbackHandler 实现 */
|
|
45
|
+
export const defaultFeedbackHandler: UserFeedbackHandler = {
|
|
46
|
+
showSuccess(message: string) {
|
|
47
|
+
const api = getMessageApi();
|
|
48
|
+
if (api) {
|
|
49
|
+
api.success(message);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(`[success] ${message}`);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
showError(message: string) {
|
|
56
|
+
const api = getMessageApi();
|
|
57
|
+
if (api) {
|
|
58
|
+
api.error(message);
|
|
59
|
+
} else {
|
|
60
|
+
console.error(`[error] ${message}`);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
showWarning(message: string) {
|
|
65
|
+
const api = getMessageApi();
|
|
66
|
+
if (api) {
|
|
67
|
+
api.warning(message);
|
|
68
|
+
} else {
|
|
69
|
+
console.warn(`[warning] ${message}`);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
handleAuthenticationFailure() {
|
|
74
|
+
console.warn('[auth] 认证失败,请重新登录');
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 错误分发逻辑,根据错误类型选择对应的处理方式
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/15
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { BusinessError, NetworkRequestError } from '../request/types';
|
|
11
|
+
import type { ErrorHandlerOptions } from './types';
|
|
12
|
+
import { defaultFeedbackHandler } from './defaultFeedbackHandler';
|
|
13
|
+
|
|
14
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
// 根据错误类型分发处理
|
|
16
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 统一的错误分发逻辑。
|
|
20
|
+
* createErrorHandler 和 useErrorHandler 共用此函数。
|
|
21
|
+
*/
|
|
22
|
+
export function dispatchError(error: unknown, options: ErrorHandlerOptions): void {
|
|
23
|
+
const feedback = options.feedbackHandler ?? defaultFeedbackHandler;
|
|
24
|
+
|
|
25
|
+
// 业务错误:后端返回 success=false
|
|
26
|
+
if (error instanceof BusinessError) {
|
|
27
|
+
options.onBusinessError?.(error);
|
|
28
|
+
feedback.showError(error.message);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 网络错误:HTTP 异常、超时、取消等
|
|
33
|
+
if (error instanceof NetworkRequestError) {
|
|
34
|
+
const detail = error.detail;
|
|
35
|
+
options.onNetworkError?.(detail);
|
|
36
|
+
|
|
37
|
+
switch (detail.type) {
|
|
38
|
+
case 'httpError':
|
|
39
|
+
if (detail.status === 401) {
|
|
40
|
+
feedback.handleAuthenticationFailure();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (detail.status === 403) {
|
|
44
|
+
feedback.showWarning('权限不足,无法执行此操作');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
feedback.showError(`请求失败 (${detail.status})`);
|
|
48
|
+
return;
|
|
49
|
+
case 'timeout':
|
|
50
|
+
feedback.showWarning('请求超时,请稍后重试');
|
|
51
|
+
return;
|
|
52
|
+
case 'aborted':
|
|
53
|
+
// 用户主动取消,静默处理
|
|
54
|
+
return;
|
|
55
|
+
case 'invalidURL':
|
|
56
|
+
feedback.showError(`无效的请求地址: ${detail.url}`);
|
|
57
|
+
return;
|
|
58
|
+
case 'decodingFailed':
|
|
59
|
+
feedback.showError('数据解析失败');
|
|
60
|
+
return;
|
|
61
|
+
case 'unknown':
|
|
62
|
+
feedback.showError('网络异常,请检查网络连接');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 其他未知错误
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
feedback.showError(message || '发生了未知错误');
|
|
70
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 全局错误处理模块统一导出
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/15
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ── 类型导出 ──────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
ErrorLevel,
|
|
14
|
+
UnauthorizedStrategy,
|
|
15
|
+
ErrorHandlerOptions,
|
|
16
|
+
UserFeedbackHandler,
|
|
17
|
+
NetworkError,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
export { BusinessError, NetworkRequestError } from './types';
|
|
21
|
+
|
|
22
|
+
// ── 默认反馈处理器 ────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export { defaultFeedbackHandler } from './defaultFeedbackHandler';
|
|
25
|
+
|
|
26
|
+
// ── 全局错误处理 ──────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
export { createErrorHandler } from './createErrorHandler';
|
|
29
|
+
|
|
30
|
+
// ── 组件级错误处理 ────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
export { useErrorHandler } from './useErrorHandler';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 全局错误处理模块类型定义
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/15
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// 复用请求模块已有的错误类型和用户反馈接口
|
|
11
|
+
export type {
|
|
12
|
+
NetworkError,
|
|
13
|
+
UserFeedbackHandler,
|
|
14
|
+
} from '../request/types';
|
|
15
|
+
|
|
16
|
+
export { BusinessError, NetworkRequestError } from '../request/types';
|
|
17
|
+
|
|
18
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
// 错误等级
|
|
20
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** 错误反馈等级,对应不同的 UI 展示样式 */
|
|
23
|
+
export type ErrorLevel = 'info' | 'warning' | 'error' | 'success';
|
|
24
|
+
|
|
25
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
// 未授权处理策略
|
|
27
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
/** 401 未授权时的处理方式 */
|
|
30
|
+
export type UnauthorizedStrategy = 'redirect' | 'dialog';
|
|
31
|
+
|
|
32
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
// 错误处理配置
|
|
34
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
import type { InjectionKey } from 'vue';
|
|
37
|
+
import type { UserFeedbackHandler, NetworkError } from '../request/types';
|
|
38
|
+
import { BusinessError } from '../request/types';
|
|
39
|
+
|
|
40
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
// Inject key
|
|
42
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/** provide/inject 使用的唯一 symbol key,避免命名冲突 */
|
|
45
|
+
export const ERROR_HANDLER_INJECT_KEY: InjectionKey<ErrorHandlerOptions> = Symbol('kine-design:error-handler');
|
|
46
|
+
|
|
47
|
+
/** createErrorHandler 配置项 */
|
|
48
|
+
export interface ErrorHandlerOptions {
|
|
49
|
+
/** 用户反馈处理器,默认使用 defaultFeedbackHandler */
|
|
50
|
+
feedbackHandler?: UserFeedbackHandler;
|
|
51
|
+
/** 未授权处理策略,默认 'redirect' */
|
|
52
|
+
unauthorizedStrategy?: UnauthorizedStrategy;
|
|
53
|
+
/** 业务错误自定义处理,返回 true 表示已处理(跳过默认行为) */
|
|
54
|
+
onBusinessError?: (error: BusinessError) => void;
|
|
55
|
+
/** 网络错误自定义处理 */
|
|
56
|
+
onNetworkError?: (error: NetworkError) => void;
|
|
57
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 组件级错误处理 composable,基于 provide/inject 获取全局配置
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/15
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { inject } from 'vue';
|
|
11
|
+
import { ERROR_HANDLER_INJECT_KEY } from './types';
|
|
12
|
+
import { dispatchError } from './dispatchError';
|
|
13
|
+
|
|
14
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
// useErrorHandler — 组件中手动调用
|
|
16
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 在组件中获取错误处理能力。
|
|
20
|
+
* 如果 createErrorHandler() 未安装,使用默认配置降级处理。
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const { handleError } = useErrorHandler();
|
|
25
|
+
*
|
|
26
|
+
* try {
|
|
27
|
+
* await fetchSomething();
|
|
28
|
+
* } catch (e) {
|
|
29
|
+
* handleError(e);
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function useErrorHandler(): { handleError: (error: unknown) => void } {
|
|
34
|
+
const options = inject(ERROR_HANDLER_INJECT_KEY, {});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
handleError(error: unknown) {
|
|
38
|
+
dispatchError(error, options);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description form composables barrel export
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/22
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
export { useFormPage } from './useFormPage';
|
|
10
|
+
export type { UseFormPageOptions, UseFormPageReturn } from './useFormPage';
|
|
11
|
+
export { renderFormField } from './renderFormField';
|
|
12
|
+
export type {
|
|
13
|
+
FormFieldType,
|
|
14
|
+
FormFieldConfig,
|
|
15
|
+
FormPageConfig,
|
|
16
|
+
DetailTableConfig,
|
|
17
|
+
MasterDetailPageConfig,
|
|
18
|
+
} from './types';
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description renderFormField — 表单字段渲染共享函数
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/22
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*
|
|
9
|
+
* 抽取自 KFormPage / KMasterDetailPage 的 renderField,消除重复。
|
|
10
|
+
* KFormPage 和 KMasterDetailPage 均通过此函数渲染主表单字段。
|
|
11
|
+
*/
|
|
12
|
+
import type { FormFieldConfig } from './types';
|
|
13
|
+
import KInput from 'kine-ui/components/input/KInput.tsx';
|
|
14
|
+
import KSelect from 'kine-ui/components/select/KSelect.tsx';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 渲染单个表单字段控件。
|
|
18
|
+
*
|
|
19
|
+
* @param field 字段配置
|
|
20
|
+
* @param formData 响应式表单数据对象(reactive)
|
|
21
|
+
* @param errors 当前校验错误集合(已解包,非 Ref)
|
|
22
|
+
* @param validateField 触发单字段校验的函数
|
|
23
|
+
* @param slots 组件 slots,用于支持 `field-{param}` 自定义插槽
|
|
24
|
+
*/
|
|
25
|
+
export function renderFormField(
|
|
26
|
+
field: FormFieldConfig,
|
|
27
|
+
formData: Record<string, unknown>,
|
|
28
|
+
errors: Record<string, string>,
|
|
29
|
+
validateField: (param: string) => void,
|
|
30
|
+
slots: Record<string, ((...args: unknown[]) => unknown) | undefined>,
|
|
31
|
+
) {
|
|
32
|
+
// 优先使用自定义插槽
|
|
33
|
+
const customSlot = slots[`field-${field.param}`];
|
|
34
|
+
if (customSlot) {
|
|
35
|
+
return customSlot({ formData, field, error: errors[field.param] });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const error = errors[field.param];
|
|
39
|
+
const val = formData[field.param];
|
|
40
|
+
const onBlur = () => validateField(field.param);
|
|
41
|
+
|
|
42
|
+
switch (field.type) {
|
|
43
|
+
case 'textarea':
|
|
44
|
+
return (
|
|
45
|
+
<KInput
|
|
46
|
+
type="textarea"
|
|
47
|
+
class={['k-fp-input--error-wrap', error && 'k-fp-input--error', field.disabled && 'k-fp-input--disabled'].filter(Boolean)}
|
|
48
|
+
modelValue={val as string}
|
|
49
|
+
placeholder={field.placeholder ?? `请输入${field.label}`}
|
|
50
|
+
disabled={field.disabled}
|
|
51
|
+
readonly={field.readonly}
|
|
52
|
+
onUpdate:modelValue={(v: string) => { formData[field.param] = v; }}
|
|
53
|
+
onBlur={onBlur}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
case 'number':
|
|
58
|
+
return (
|
|
59
|
+
<div class="k-fp-input-wrapper">
|
|
60
|
+
{field.prefix && <span class="k-fp-input-prefix">{field.prefix}</span>}
|
|
61
|
+
<KInput
|
|
62
|
+
type="number"
|
|
63
|
+
class={['k-fp-input', error && 'k-fp-input--error', field.disabled && 'k-fp-input--disabled'].filter(Boolean)}
|
|
64
|
+
modelValue={val as string | number}
|
|
65
|
+
placeholder={field.placeholder ?? `请输入${field.label}`}
|
|
66
|
+
disabled={field.disabled}
|
|
67
|
+
readonly={field.readonly}
|
|
68
|
+
onUpdate:modelValue={(v: string) => { formData[field.param] = v; }}
|
|
69
|
+
onBlur={onBlur}
|
|
70
|
+
/>
|
|
71
|
+
{field.suffix && <span class="k-fp-input-suffix">{field.suffix}</span>}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
case 'select':
|
|
76
|
+
return (
|
|
77
|
+
<KSelect
|
|
78
|
+
class={['k-fp-select-wrap', error && 'k-fp-input--error'].filter(Boolean)}
|
|
79
|
+
modelValue={val}
|
|
80
|
+
options={field.options ?? []}
|
|
81
|
+
optionParam="label"
|
|
82
|
+
valueParam="value"
|
|
83
|
+
placeholder={field.placeholder ?? `请选择${field.label}`}
|
|
84
|
+
disabled={field.disabled}
|
|
85
|
+
readonly={field.readonly}
|
|
86
|
+
onUpdate:modelValue={(v: unknown) => {
|
|
87
|
+
formData[field.param] = v;
|
|
88
|
+
validateField(field.param);
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
case 'switch':
|
|
94
|
+
return (
|
|
95
|
+
<label class="k-fp-switch">
|
|
96
|
+
<input
|
|
97
|
+
type="checkbox"
|
|
98
|
+
checked={!!val}
|
|
99
|
+
disabled={field.disabled}
|
|
100
|
+
onChange={(e: Event) => { formData[field.param] = (e.target as HTMLInputElement).checked; }}
|
|
101
|
+
/>
|
|
102
|
+
<span class="k-fp-switch-track" />
|
|
103
|
+
</label>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
default:
|
|
107
|
+
return (
|
|
108
|
+
<KInput
|
|
109
|
+
class={['k-fp-input--error-wrap', error && 'k-fp-input--error', field.disabled && 'k-fp-input--disabled'].filter(Boolean)}
|
|
110
|
+
modelValue={val as string}
|
|
111
|
+
placeholder={field.placeholder ?? `请输入${field.label}`}
|
|
112
|
+
disabled={field.disabled}
|
|
113
|
+
readonly={field.readonly}
|
|
114
|
+
onUpdate:modelValue={(v: string) => { formData[field.param] = v; }}
|
|
115
|
+
onBlur={onBlur}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description FormPage / MasterDetailPage 配置类型定义
|
|
3
|
+
* @author 阿怪
|
|
4
|
+
* @date 2026/3/22
|
|
5
|
+
* @version v0.0.1
|
|
6
|
+
*
|
|
7
|
+
* 江湖的业务千篇一律,复杂的代码好几百行。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { EditableColumn } from '../../components/editableTable';
|
|
11
|
+
|
|
12
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
// 表单字段配置
|
|
14
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/** 表单字段类型 */
|
|
17
|
+
export type FormFieldType =
|
|
18
|
+
| 'text'
|
|
19
|
+
| 'number'
|
|
20
|
+
| 'select'
|
|
21
|
+
| 'date'
|
|
22
|
+
| 'textarea'
|
|
23
|
+
| 'switch'
|
|
24
|
+
| 'autocomplete'
|
|
25
|
+
| 'custom';
|
|
26
|
+
|
|
27
|
+
/** 表单字段配置 */
|
|
28
|
+
export interface FormFieldConfig {
|
|
29
|
+
/** 字段名,对应数据对象的 key */
|
|
30
|
+
param: string;
|
|
31
|
+
/** 标签文本 */
|
|
32
|
+
label: string;
|
|
33
|
+
/** 字段类型 */
|
|
34
|
+
type?: FormFieldType;
|
|
35
|
+
/** 占位文本 */
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
/** 是否必填 */
|
|
38
|
+
required?: boolean;
|
|
39
|
+
/** 是否禁用 */
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
/** 是否只读 */
|
|
42
|
+
readonly?: boolean;
|
|
43
|
+
/** 跨列数(默认 1,textarea 常用 'full' 表示占满整行) */
|
|
44
|
+
span?: number | 'full';
|
|
45
|
+
/** select 类型的选项 */
|
|
46
|
+
options?: Array<{ label: string; value: string | number }>;
|
|
47
|
+
/** autocomplete 的 fetchSuggestions */
|
|
48
|
+
fetchSuggestions?: (query: string) => Promise<Array<{ label: string; value: string | number }>>;
|
|
49
|
+
/** number 类型的前缀(如 ¥) */
|
|
50
|
+
prefix?: string;
|
|
51
|
+
/** number 类型的后缀(如 米、件) */
|
|
52
|
+
suffix?: string;
|
|
53
|
+
/** textarea 的行数 */
|
|
54
|
+
rows?: number;
|
|
55
|
+
/** textarea 的最大字数 */
|
|
56
|
+
maxLength?: number;
|
|
57
|
+
/** 自定义校验函数 */
|
|
58
|
+
validator?: (value: unknown, formData: Record<string, unknown>) => string | undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
// FormPage 配置
|
|
63
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
/** SimpleFormPage 完整配置 */
|
|
66
|
+
export interface FormPageConfig {
|
|
67
|
+
/** 页面标题 */
|
|
68
|
+
title: string;
|
|
69
|
+
/** API 路径 */
|
|
70
|
+
api: string;
|
|
71
|
+
/** 表单字段定义 */
|
|
72
|
+
fields: FormFieldConfig[];
|
|
73
|
+
/** 列数布局(默认 2) */
|
|
74
|
+
columns?: 1 | 2 | 3;
|
|
75
|
+
/** 主键字段(编辑模式下从路由取 id,默认 'id') */
|
|
76
|
+
rowKey?: string;
|
|
77
|
+
/** 保存成功后跳转路径 */
|
|
78
|
+
redirectPath?: string;
|
|
79
|
+
/** 是否显示"保存草稿"按钮 */
|
|
80
|
+
showDraft?: boolean;
|
|
81
|
+
/** 提交按钮文本 */
|
|
82
|
+
submitText?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
86
|
+
// MasterDetailPage 配置
|
|
87
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
/** 子表配置 */
|
|
90
|
+
export interface DetailTableConfig {
|
|
91
|
+
/** 子表标题 */
|
|
92
|
+
title: string;
|
|
93
|
+
/** 子表字段名(对应主表数据中的数组字段) */
|
|
94
|
+
param: string;
|
|
95
|
+
/** 列定义(复用 EditableColumn) */
|
|
96
|
+
columns: EditableColumn[];
|
|
97
|
+
/** 是否显示汇总行 */
|
|
98
|
+
showSummary?: boolean;
|
|
99
|
+
/** 汇总行标签 */
|
|
100
|
+
summaryLabel?: string;
|
|
101
|
+
/** 币种前缀 */
|
|
102
|
+
currencyPrefix?: string;
|
|
103
|
+
/** 新增行按钮文本 */
|
|
104
|
+
addText?: string;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** MasterDetailPage 完整配置 */
|
|
108
|
+
export interface MasterDetailPageConfig {
|
|
109
|
+
/** 页面标题 */
|
|
110
|
+
title: string;
|
|
111
|
+
/** API 路径 */
|
|
112
|
+
api: string;
|
|
113
|
+
/** 面包屑路径 */
|
|
114
|
+
breadcrumb?: Array<{ label: string; path?: string }>;
|
|
115
|
+
/** 主表字段定义 */
|
|
116
|
+
fields: FormFieldConfig[];
|
|
117
|
+
/** 主表列数布局(默认 3) */
|
|
118
|
+
columns?: 1 | 2 | 3;
|
|
119
|
+
/** 子表配置(支持多个子表) */
|
|
120
|
+
detailTables: DetailTableConfig[];
|
|
121
|
+
/** 主键字段 */
|
|
122
|
+
rowKey?: string;
|
|
123
|
+
/** 保存成功后跳转路径 */
|
|
124
|
+
redirectPath?: string;
|
|
125
|
+
/** 是否显示"保存草稿"按钮 */
|
|
126
|
+
showDraft?: boolean;
|
|
127
|
+
/** 提交按钮文本 */
|
|
128
|
+
submitText?: string;
|
|
129
|
+
}
|