@ftjs/core 0.0.2
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/LICENSE +21 -0
- package/README.md +16 -0
- package/dist/form/columns.d.ts +94 -0
- package/dist/form/define-component.d.ts +110 -0
- package/dist/form/index.d.ts +5 -0
- package/dist/form/types.d.ts +78 -0
- package/dist/form/use-form-item.d.ts +22 -0
- package/dist/form/use-form.d.ts +20 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +570 -0
- package/dist/table/columns.d.ts +16 -0
- package/dist/table/define-components.d.ts +89 -0
- package/dist/table/index.d.ts +3 -0
- package/dist/table/use-table.d.ts +10 -0
- package/dist/type-helper.d.ts +67 -0
- package/dist/utils.d.ts +40 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 yuhengshen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @ftjs/antd
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
核心模块
|
|
9
|
+
|
|
10
|
+
## 文档
|
|
11
|
+
|
|
12
|
+
请查看 [@ftjs/core 文档](https://tf-docs.yhs.ink/@ftjs/core/introduction.html)。
|
|
13
|
+
|
|
14
|
+
## License
|
|
15
|
+
|
|
16
|
+
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import { RecordPath } from '../type-helper';
|
|
3
|
+
type WatchHandler<FormData extends Record<string, any>> = (params: {
|
|
4
|
+
val: any;
|
|
5
|
+
oldVal: any;
|
|
6
|
+
form: FormData;
|
|
7
|
+
}) => void;
|
|
8
|
+
type Watch<FormData extends Record<string, any>> = WatchHandler<FormData> | {
|
|
9
|
+
handler: WatchHandler<FormData>;
|
|
10
|
+
deep?: boolean;
|
|
11
|
+
immediate?: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* 实现方需要继承这个interface
|
|
15
|
+
*/
|
|
16
|
+
export interface TfFormColumnBase<FormData extends Record<string, any>> {
|
|
17
|
+
/**
|
|
18
|
+
* 字段名 `fields` 和 `field` 至少有一个存在
|
|
19
|
+
*
|
|
20
|
+
* `field` 优先级高于 `fields`
|
|
21
|
+
*
|
|
22
|
+
* 如果是在 TableColumns 中,则默认继承其中的 field
|
|
23
|
+
*/
|
|
24
|
+
field?: RecordPath<FormData>;
|
|
25
|
+
/**
|
|
26
|
+
* 字段名数组,当表单需要返回多个值时,使用这个字段
|
|
27
|
+
*
|
|
28
|
+
* 如: [startTime, endTime]
|
|
29
|
+
*
|
|
30
|
+
* 注意: 第一个字段需要尽量是基础类型的值(这个值会用于watch, expect等操作),后面可以包含详情
|
|
31
|
+
*
|
|
32
|
+
* 如人员信息: [staffId, staffInfoObj, deptInfoObj, ...]
|
|
33
|
+
*/
|
|
34
|
+
fields?: string[];
|
|
35
|
+
/**
|
|
36
|
+
* 字段标题
|
|
37
|
+
*
|
|
38
|
+
* 如果是在 TableColumns 中,则默认继承其中的 title
|
|
39
|
+
*/
|
|
40
|
+
title?: MaybeRefOrGetter<string>;
|
|
41
|
+
/**
|
|
42
|
+
* 是否隐藏
|
|
43
|
+
*/
|
|
44
|
+
hide?: MaybeRefOrGetter<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* 监听字段值变化,如果是 `fields` ,则只会监听第一个字段的值变化
|
|
47
|
+
*/
|
|
48
|
+
watch?: Watch<FormData>;
|
|
49
|
+
/**
|
|
50
|
+
* 字段默认值
|
|
51
|
+
*/
|
|
52
|
+
value?: any;
|
|
53
|
+
/**
|
|
54
|
+
* 控制其他字段基于此值的显示规则
|
|
55
|
+
*
|
|
56
|
+
* 当其他字段值符合`value`时,控制字段显示,否则隐藏
|
|
57
|
+
*/
|
|
58
|
+
control?: {
|
|
59
|
+
/**
|
|
60
|
+
* 控制字段
|
|
61
|
+
*/
|
|
62
|
+
field: string;
|
|
63
|
+
/**
|
|
64
|
+
* 条件,可以是一个值,也可以是一个函数
|
|
65
|
+
*
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
68
|
+
value: boolean | string | number | boolean[] | string[] | number[] | /** 返回值表示这个字段是否显示 */ (({ formData, val, }: {
|
|
69
|
+
formData: FormData;
|
|
70
|
+
val: any;
|
|
71
|
+
}) => boolean);
|
|
72
|
+
}[];
|
|
73
|
+
valueGetter?: (val: any) => any;
|
|
74
|
+
valueSetter?: (val: any) => any;
|
|
75
|
+
/**
|
|
76
|
+
* props 配置,子类定义
|
|
77
|
+
*/
|
|
78
|
+
props?: any;
|
|
79
|
+
/**
|
|
80
|
+
* slots 配置,子类定义
|
|
81
|
+
*/
|
|
82
|
+
slots?: {};
|
|
83
|
+
/**
|
|
84
|
+
* 排序
|
|
85
|
+
*
|
|
86
|
+
* @default index
|
|
87
|
+
*/
|
|
88
|
+
sort?: number;
|
|
89
|
+
/**
|
|
90
|
+
* 是否查看模式
|
|
91
|
+
*/
|
|
92
|
+
isView?: MaybeRefOrGetter<boolean>;
|
|
93
|
+
}
|
|
94
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Component, EmitsOptions, SetupContext, SlotsType, VNode } from 'vue';
|
|
2
|
+
import { TfFormColumnBase } from './columns';
|
|
3
|
+
import { RuntimeProps } from '../utils';
|
|
4
|
+
import { TupleKeys } from '../type-helper';
|
|
5
|
+
export interface FormTypeMap<_FormData extends Record<string, any>> {
|
|
6
|
+
default: {
|
|
7
|
+
formSlots: {};
|
|
8
|
+
extendedProps: {};
|
|
9
|
+
internalFormProps: {};
|
|
10
|
+
columns: (TfFormColumnBase<any> & {
|
|
11
|
+
type: "custom";
|
|
12
|
+
}) | (TfFormColumnBase<any> & {
|
|
13
|
+
type: "custom2";
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface TfFormIntrinsicProps<FormData extends Record<string, any>, Type extends keyof FormTypeMap<FormData>> {
|
|
18
|
+
/**
|
|
19
|
+
* 用于缓存配置,不填则不缓存
|
|
20
|
+
*/
|
|
21
|
+
cache?: string;
|
|
22
|
+
/**
|
|
23
|
+
* v-model:formData 的值
|
|
24
|
+
*
|
|
25
|
+
* 如果`formData`不为`undefined`或者`null`,则双向绑定这个值,否则 TfForm内部会生成一个内部值
|
|
26
|
+
*/
|
|
27
|
+
formData?: FormData;
|
|
28
|
+
/**
|
|
29
|
+
* 内部 form 组件 props
|
|
30
|
+
*/
|
|
31
|
+
internalFormProps?: FormTypeMap<FormData>[Type]["internalFormProps"];
|
|
32
|
+
/**
|
|
33
|
+
* v-model:formData 的更新函数
|
|
34
|
+
*
|
|
35
|
+
* 需要`formData`不为空
|
|
36
|
+
*/
|
|
37
|
+
"onUpdate:formData"?: (value: FormData) => void;
|
|
38
|
+
/**
|
|
39
|
+
* 提交函数
|
|
40
|
+
* @param formData 当先的有效表单值
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
43
|
+
onSubmit?: (formData: FormData) => Promise<void> | void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 每一个表单组件的 props
|
|
47
|
+
*/
|
|
48
|
+
export interface CommonFormItemProps<T extends TfFormColumnBase<any>> {
|
|
49
|
+
/** column 定义 */
|
|
50
|
+
column: T;
|
|
51
|
+
/** 是否查看模式 */
|
|
52
|
+
isView: boolean;
|
|
53
|
+
}
|
|
54
|
+
export type TfFormPropsMap<FormData extends Record<string, any>, Type extends keyof FormTypeMap<FormData>> = TfFormIntrinsicProps<FormData, Type> & FormTypeMap<FormData>[Type]["extendedProps"] & {
|
|
55
|
+
columns: FormTypeMap<FormData>[Type]["columns"][];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* 定义表单容器组件
|
|
59
|
+
*/
|
|
60
|
+
export declare const defineTfForm: <Type extends keyof FormTypeMap<any>>(setup: (props: {}, ctx: SetupContext<EmitsOptions, SlotsType<FormTypeMap<any>[Type]["formSlots"] & {
|
|
61
|
+
/**
|
|
62
|
+
* 表单内容
|
|
63
|
+
*/
|
|
64
|
+
formContent: () => any;
|
|
65
|
+
}>>) => any, renderMap: Map<string, Component>, _runtimeProps: RuntimeProps<TupleKeys<FormTypeMap<any>[Type]["extendedProps"]>>[] & {
|
|
66
|
+
length: TupleKeys<FormTypeMap<any>[Type]["extendedProps"]>["length"];
|
|
67
|
+
}) => new <FormData extends Record<string, any>>(props: (TfFormIntrinsicProps<FormData, Type> & FormTypeMap<FormData>[Type]["extendedProps"] & {
|
|
68
|
+
columns: FormTypeMap<FormData>[Type]["columns"][];
|
|
69
|
+
} & ({
|
|
70
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
71
|
+
} | {
|
|
72
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
73
|
+
})) & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps) => import('vue').CreateComponentPublicInstanceWithMixins<TfFormIntrinsicProps<FormData, Type> & FormTypeMap<FormData>[Type]["extendedProps"] & {
|
|
74
|
+
columns: FormTypeMap<FormData>[Type]["columns"][];
|
|
75
|
+
} & ({
|
|
76
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
77
|
+
} | {
|
|
78
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
79
|
+
}), {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, EmitsOptions, import('vue').PublicProps, {}, false, {}, SlotsType<FormTypeMap<FormData>[Type]["formSlots"]>, {}, {}, string, {}, any, import('vue').ComponentProvideOptions, {
|
|
80
|
+
P: {};
|
|
81
|
+
B: {};
|
|
82
|
+
D: {};
|
|
83
|
+
C: {};
|
|
84
|
+
M: {};
|
|
85
|
+
Defaults: {};
|
|
86
|
+
}, {} & ((TfFormIntrinsicProps<FormData, Type> & FormTypeMap<FormData>[Type]["extendedProps"] & {
|
|
87
|
+
columns: FormTypeMap<FormData>[Type]["columns"][];
|
|
88
|
+
} & {
|
|
89
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
90
|
+
} extends infer T ? T extends TfFormIntrinsicProps<FormData, Type> & FormTypeMap<FormData>[Type]["extendedProps"] & {
|
|
91
|
+
columns: FormTypeMap<FormData>[Type]["columns"][];
|
|
92
|
+
} & {
|
|
93
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
94
|
+
} ? T extends void ? {} : T : never : never) | (TfFormIntrinsicProps<FormData, Type> & FormTypeMap<FormData>[Type]["extendedProps"] & {
|
|
95
|
+
columns: FormTypeMap<FormData>[Type]["columns"][];
|
|
96
|
+
} & {
|
|
97
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
98
|
+
} extends infer T_1 ? T_1 extends TfFormIntrinsicProps<FormData, Type> & FormTypeMap<FormData>[Type]["extendedProps"] & {
|
|
99
|
+
columns: FormTypeMap<FormData>[Type]["columns"][];
|
|
100
|
+
} & {
|
|
101
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
102
|
+
} ? T_1 extends void ? {} : T_1 : never : never)), {}, {}, {}, {}, {}>;
|
|
103
|
+
/**
|
|
104
|
+
* 定义表单组件
|
|
105
|
+
*/
|
|
106
|
+
export declare function defineFormComponent<T extends TfFormColumnBase<any>>(setup: (props: CommonFormItemProps<T>, ctx: SetupContext) => () => VNode): import('vue').DefineSetupFnComponent<CommonFormItemProps<T>, EmitsOptions, {}, CommonFormItemProps<T> & ({
|
|
107
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
108
|
+
} | {
|
|
109
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
110
|
+
}), import('vue').PublicProps>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Ref, ComputedRef } from 'vue';
|
|
2
|
+
import { RecordPath } from '../type-helper';
|
|
3
|
+
import { FormTypeMap } from './define-component';
|
|
4
|
+
import { GetFormData, ResetToDefault, SetAsDefault } from './use-form';
|
|
5
|
+
/**
|
|
6
|
+
* 对于需要暴露给外部使用的方法,其类型从这里Pick,这样会有统一的类型提示
|
|
7
|
+
*/
|
|
8
|
+
export interface ExposeWithComment<FormData extends Record<string, any>, Type extends keyof FormTypeMap<FormData>> {
|
|
9
|
+
/**
|
|
10
|
+
* 获取表单当前展示出的项目的表单值
|
|
11
|
+
*/
|
|
12
|
+
getFormData: GetFormData<FormData>;
|
|
13
|
+
/**
|
|
14
|
+
* 重置表单为默认值
|
|
15
|
+
*
|
|
16
|
+
* `sync false` 由于这个方法很可能在`watchEffect`中调用
|
|
17
|
+
*
|
|
18
|
+
* 以非同步的方式调用,其内部属性不会放到`watchEffect`的依赖中
|
|
19
|
+
* @param sync 是否同步更新,默认为 false
|
|
20
|
+
*/
|
|
21
|
+
resetToDefault: ResetToDefault;
|
|
22
|
+
/**
|
|
23
|
+
* 设置当前表单的默认值,如果参数为空,则将`当前表单值`设置为默认值
|
|
24
|
+
*/
|
|
25
|
+
setAsDefault: SetAsDefault<FormData>;
|
|
26
|
+
/**
|
|
27
|
+
* 表单值,包含已经隐藏的值
|
|
28
|
+
*/
|
|
29
|
+
form: Ref<FormData>;
|
|
30
|
+
/**
|
|
31
|
+
* 配置显示的项目
|
|
32
|
+
*/
|
|
33
|
+
columnsChecked: Ref<RecordPath<FormData>[]>;
|
|
34
|
+
/**
|
|
35
|
+
* 重置配置显示项目
|
|
36
|
+
*/
|
|
37
|
+
resetColumnsChecked: () => void;
|
|
38
|
+
/**
|
|
39
|
+
* 配置排序的项目
|
|
40
|
+
*/
|
|
41
|
+
columnsSort: Ref<Partial<Record<RecordPath<FormData>, number>>>;
|
|
42
|
+
/**
|
|
43
|
+
* 重置排序
|
|
44
|
+
*/
|
|
45
|
+
resetColumnsSort: () => void;
|
|
46
|
+
/**
|
|
47
|
+
* 所有表单项目
|
|
48
|
+
*/
|
|
49
|
+
columns: ComputedRef<FormTypeMap<FormData>[Type]["columns"]>;
|
|
50
|
+
/**
|
|
51
|
+
* 当前显示的表单项目
|
|
52
|
+
*/
|
|
53
|
+
visibleColumns: ComputedRef<FormTypeMap<FormData>[Type]["columns"]>;
|
|
54
|
+
/**
|
|
55
|
+
* 表单容器组件 props, 由表单容器组件决定
|
|
56
|
+
*
|
|
57
|
+
* 定义方式:
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
*
|
|
62
|
+
* declare module "@ftjs/core" {
|
|
63
|
+
* interface FormContainerProps {
|
|
64
|
+
* ...
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
internalFormProps: ComputedRef<FormTypeMap<FormData>[Type]["internalFormProps"] | undefined>;
|
|
70
|
+
/**
|
|
71
|
+
* 表单提交事件
|
|
72
|
+
*/
|
|
73
|
+
onSubmit?: (formData: FormData) => Promise<void> | void;
|
|
74
|
+
/**
|
|
75
|
+
* 缓存
|
|
76
|
+
*/
|
|
77
|
+
cache: ComputedRef<string | undefined>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TfFormColumnBase } from './columns';
|
|
2
|
+
import { CommonFormItemProps, FormTypeMap } from './define-component';
|
|
3
|
+
interface UseFormItemOptions<FormData extends Record<string, any>, Type extends keyof FormTypeMap<FormData>> {
|
|
4
|
+
/** 通用 props */
|
|
5
|
+
props: CommonFormItemProps<FormTypeMap<FormData>[Type]["columns"]>;
|
|
6
|
+
/**
|
|
7
|
+
* set 转换
|
|
8
|
+
*/
|
|
9
|
+
valueSetter?: (val: any) => any;
|
|
10
|
+
/**
|
|
11
|
+
* get 转换
|
|
12
|
+
*/
|
|
13
|
+
valueGetter?: (val: any) => any;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 通用的 form item 组件处理,处理 form 中的值
|
|
17
|
+
*/
|
|
18
|
+
export declare const useFormItem: <FromData extends Record<string, any>, Type extends keyof FormTypeMap<FromData>>(options: UseFormItemOptions<TfFormColumnBase<FormData>, Type>) => {
|
|
19
|
+
valueComputed: import('vue').WritableComputedRef<any, any>;
|
|
20
|
+
isView: import('vue').ComputedRef<boolean>;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Ref, ComputedRef } from 'vue';
|
|
2
|
+
import { FormTypeMap, TfFormIntrinsicProps } from './define-component';
|
|
3
|
+
import { SplitEventKeys } from '../type-helper';
|
|
4
|
+
import { ExposeWithComment } from './types';
|
|
5
|
+
export type FormInject<FormData extends Record<string, any>, Type extends keyof FormTypeMap<FormData>> = Pick<ExposeWithComment<FormData, Type>, "form" | "columnsChecked" | "columnsSort" | "visibleColumns" | "internalFormProps" | "onSubmit" | "getFormData" | "resetToDefault" | "setAsDefault" | "resetColumnsSort" | "resetColumnsChecked" | "cache"> & SplitEventKeys<FormTypeMap<FormData>[Type]["extendedProps"]> & {
|
|
6
|
+
columns: ComputedRef<FormTypeMap<FormData>[Type]["columns"][]>;
|
|
7
|
+
};
|
|
8
|
+
export declare const useForm: <FormData extends Record<string, any>, Type extends keyof FormTypeMap<FormData>>(props: TfFormIntrinsicProps<FormData, Type> & FormTypeMap<FormData>[Type]["extendedProps"] & {
|
|
9
|
+
columns: FormTypeMap<FormData>[Type]["columns"][];
|
|
10
|
+
}, formData: Ref<FormData | undefined>, runtimePropsKeys: string[]) => {
|
|
11
|
+
form: Ref<FormData, FormData>;
|
|
12
|
+
visibleColumns: ComputedRef<FormTypeMap<FormData>[Type]["columns"][]>;
|
|
13
|
+
resetToDefault: ResetToDefault;
|
|
14
|
+
getFormData: GetFormData<FormData>;
|
|
15
|
+
setAsDefault: SetAsDefault<FormData>;
|
|
16
|
+
};
|
|
17
|
+
export declare const useFormInject: <FormData extends Record<string, any>, Type extends keyof FormTypeMap<FormData>>() => FormInject<FormData, Type> | undefined;
|
|
18
|
+
export type GetFormData<FormData extends Record<string, any>> = () => FormData;
|
|
19
|
+
export type ResetToDefault = (sync?: boolean) => Promise<void> | void;
|
|
20
|
+
export type SetAsDefault<FormData extends Record<string, any>> = (v?: FormData) => void;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import { unref, computed, toValue, ref, watch, onUnmounted, provide, inject, nextTick, defineComponent, h } from "vue";
|
|
2
|
+
const isBrowser = typeof window !== "undefined";
|
|
3
|
+
const getField = (column) => {
|
|
4
|
+
var _a;
|
|
5
|
+
return column.field ?? ((_a = column.fields) == null ? void 0 : _a[0]);
|
|
6
|
+
};
|
|
7
|
+
const isEmptyStrOrNull = (val) => {
|
|
8
|
+
return val === "" || val == null;
|
|
9
|
+
};
|
|
10
|
+
const cloneDeep = (obj) => {
|
|
11
|
+
if (typeof obj === "object" && obj !== null) {
|
|
12
|
+
return JSON.parse(JSON.stringify(obj));
|
|
13
|
+
}
|
|
14
|
+
return obj;
|
|
15
|
+
};
|
|
16
|
+
const get = (obj, path) => {
|
|
17
|
+
const keys = path.split(".");
|
|
18
|
+
let result = obj;
|
|
19
|
+
for (const key of keys) {
|
|
20
|
+
if (result == null || typeof result !== "object") {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
if (Reflect.has(result, key)) {
|
|
24
|
+
result = Reflect.get(result, key);
|
|
25
|
+
} else {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
31
|
+
const set = (obj, path, value) => {
|
|
32
|
+
const keys = path.split(".");
|
|
33
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
34
|
+
const key = keys[i];
|
|
35
|
+
if (!Reflect.has(obj, key)) {
|
|
36
|
+
Reflect.set(obj, key, {});
|
|
37
|
+
}
|
|
38
|
+
obj = Reflect.get(obj, key);
|
|
39
|
+
}
|
|
40
|
+
Reflect.set(obj, keys[keys.length - 1], value);
|
|
41
|
+
};
|
|
42
|
+
const has = (obj, path) => {
|
|
43
|
+
const keys = path.split(".");
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
if (!Reflect.has(obj, key)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
obj = Reflect.get(obj, key);
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
const unrefs = (obj) => {
|
|
53
|
+
if (!obj || typeof obj !== "object") {
|
|
54
|
+
return obj;
|
|
55
|
+
}
|
|
56
|
+
return Object.fromEntries(
|
|
57
|
+
Object.entries(obj).map(([key, value]) => [key, unref(value)])
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
const getStorage = (key, cache) => {
|
|
61
|
+
let value = {};
|
|
62
|
+
if (cache && isBrowser) {
|
|
63
|
+
try {
|
|
64
|
+
const storageStr = localStorage.getItem(key);
|
|
65
|
+
if (storageStr) {
|
|
66
|
+
const storageV = JSON.parse(storageStr);
|
|
67
|
+
if (storageV[cache]) {
|
|
68
|
+
value = storageV[cache];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(error);
|
|
73
|
+
value = {};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
};
|
|
78
|
+
const setStorage = (key, value, cache) => {
|
|
79
|
+
if (cache && isBrowser) {
|
|
80
|
+
let obj = {};
|
|
81
|
+
const storageStr = localStorage.getItem(key);
|
|
82
|
+
if (storageStr) {
|
|
83
|
+
try {
|
|
84
|
+
obj = JSON.parse(storageStr) ?? {};
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
obj[cache] = value;
|
|
90
|
+
localStorage.setItem(key, JSON.stringify(obj));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const transferVueArrayPropsToObject = (arr) => {
|
|
94
|
+
const props = {};
|
|
95
|
+
arr.forEach((item) => {
|
|
96
|
+
if (typeof item === "string") {
|
|
97
|
+
props[item] = {};
|
|
98
|
+
} else {
|
|
99
|
+
props[item[0]] = item[1] || {};
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return props;
|
|
103
|
+
};
|
|
104
|
+
const getPropsKeys = (arr) => {
|
|
105
|
+
return arr.map((item) => {
|
|
106
|
+
if (typeof item === "string") {
|
|
107
|
+
return item;
|
|
108
|
+
} else {
|
|
109
|
+
return item[0];
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const provideFormKey = Symbol("@ftjs/core-form-provide");
|
|
114
|
+
const useColumnsChecked = (columns, cache) => {
|
|
115
|
+
const storageKey = `tf-form-columns-checked-obj`;
|
|
116
|
+
const columnsV = computed(() => {
|
|
117
|
+
const entries = columns.value.map((e) => {
|
|
118
|
+
var _a;
|
|
119
|
+
const field = e.field ?? ((_a = e.fields) == null ? void 0 : _a[0]);
|
|
120
|
+
return [field, !toValue(e.hide)];
|
|
121
|
+
});
|
|
122
|
+
return Object.fromEntries(entries);
|
|
123
|
+
});
|
|
124
|
+
const storageV = getStorage(storageKey, cache);
|
|
125
|
+
const vRef = ref(
|
|
126
|
+
Object.assign({}, columnsV.value, storageV)
|
|
127
|
+
);
|
|
128
|
+
const columnsChecked = computed({
|
|
129
|
+
get() {
|
|
130
|
+
return Object.entries(vRef.value).filter(([_, show]) => show).map(([field]) => field);
|
|
131
|
+
},
|
|
132
|
+
set(v) {
|
|
133
|
+
const storageV2 = {};
|
|
134
|
+
const entries = columns.value.map((e) => {
|
|
135
|
+
const field = getField(e);
|
|
136
|
+
const show = v.includes(field);
|
|
137
|
+
if (show !== !toValue(e.hide)) storageV2[field] = show;
|
|
138
|
+
return [field, show];
|
|
139
|
+
});
|
|
140
|
+
vRef.value = Object.fromEntries(entries);
|
|
141
|
+
setStorage(storageKey, storageV2, cache);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
const resetColumnsChecked = () => {
|
|
145
|
+
columnsChecked.value = columns.value.filter((e) => !toValue(e.hide)).map((e) => getField(e));
|
|
146
|
+
};
|
|
147
|
+
return {
|
|
148
|
+
columnsChecked,
|
|
149
|
+
resetColumnsChecked
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const useColumnsSorted = (columns, cache) => {
|
|
153
|
+
const storageKey = `tf-form-columns-sorted-obj`;
|
|
154
|
+
const columnsV = computed(() => {
|
|
155
|
+
const entries = columns.value.map((e, idx) => {
|
|
156
|
+
var _a;
|
|
157
|
+
const field = e.field ?? ((_a = e.fields) == null ? void 0 : _a[0]);
|
|
158
|
+
return [field, e.sort ?? idx];
|
|
159
|
+
});
|
|
160
|
+
return Object.fromEntries(entries);
|
|
161
|
+
});
|
|
162
|
+
const storageV = getStorage(storageKey, cache);
|
|
163
|
+
const vRef = ref(
|
|
164
|
+
Object.assign({}, columnsV.value, storageV)
|
|
165
|
+
);
|
|
166
|
+
const columnsSort = computed({
|
|
167
|
+
get() {
|
|
168
|
+
return vRef.value;
|
|
169
|
+
},
|
|
170
|
+
set(v) {
|
|
171
|
+
const storageV2 = {};
|
|
172
|
+
const entries = columns.value.map((e, idx) => {
|
|
173
|
+
const field = getField(e);
|
|
174
|
+
if (v[field] !== (e.sort ?? idx)) {
|
|
175
|
+
storageV2[field] = v[field];
|
|
176
|
+
}
|
|
177
|
+
return [field, v[field]];
|
|
178
|
+
});
|
|
179
|
+
vRef.value = Object.fromEntries(entries);
|
|
180
|
+
setStorage(storageKey, storageV2, cache);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
const resetColumnsSort = () => {
|
|
184
|
+
const resetV = {};
|
|
185
|
+
columns.value.forEach((e, idx) => {
|
|
186
|
+
const field = getField(e);
|
|
187
|
+
resetV[field] = e.sort ?? idx;
|
|
188
|
+
});
|
|
189
|
+
columnsSort.value = resetV;
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
columnsSort,
|
|
193
|
+
resetColumnsSort
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
const getFieldsAndValues = (column) => {
|
|
197
|
+
const fields = column.fields || [column.field];
|
|
198
|
+
const values = column.fields ? column.value || [] : [column.value];
|
|
199
|
+
return { fields, values };
|
|
200
|
+
};
|
|
201
|
+
const useForm = (props, formData, runtimePropsKeys) => {
|
|
202
|
+
const columns = computed(() => toValue(props.columns));
|
|
203
|
+
const formLocal = ref({});
|
|
204
|
+
const form = unref(formData) != null ? formData : formLocal;
|
|
205
|
+
const { columnsChecked, resetColumnsChecked } = useColumnsChecked(
|
|
206
|
+
columns,
|
|
207
|
+
props.cache
|
|
208
|
+
);
|
|
209
|
+
const { columnsSort, resetColumnsSort } = useColumnsSorted(
|
|
210
|
+
columns,
|
|
211
|
+
props.cache
|
|
212
|
+
);
|
|
213
|
+
let tmpDefaultForm;
|
|
214
|
+
const watchMap = /* @__PURE__ */ new Map();
|
|
215
|
+
const hideFieldSet = ref(/* @__PURE__ */ new Set());
|
|
216
|
+
const visibleColumns = computed(() => {
|
|
217
|
+
return columns.value.filter((column) => {
|
|
218
|
+
var _a;
|
|
219
|
+
const key = getField(column);
|
|
220
|
+
return !hideFieldSet.value.has(key) && (((_a = columnsChecked.value) == null ? void 0 : _a.includes(key)) ?? true);
|
|
221
|
+
}).sort((a, b) => {
|
|
222
|
+
const keyA = getField(a);
|
|
223
|
+
const keyB = getField(b);
|
|
224
|
+
const aSort = columnsSort.value[keyA] ?? 0;
|
|
225
|
+
const bSort = columnsSort.value[keyB] ?? 0;
|
|
226
|
+
return aSort - bSort;
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
const customProps = runtimePropsKeys.reduce((acc, event) => {
|
|
230
|
+
if (event.startsWith("on")) {
|
|
231
|
+
acc[event] = props[event];
|
|
232
|
+
} else {
|
|
233
|
+
acc[event] = computed(() => props[event]);
|
|
234
|
+
}
|
|
235
|
+
return acc;
|
|
236
|
+
}, {});
|
|
237
|
+
watch(
|
|
238
|
+
columns,
|
|
239
|
+
(_v, _o, onCleanup) => {
|
|
240
|
+
onCleanup(() => {
|
|
241
|
+
watchMap.forEach((cancel) => cancel());
|
|
242
|
+
watchMap.clear();
|
|
243
|
+
hideFieldSet.value.clear();
|
|
244
|
+
console.warn(`[@ftjs/core] 检测到columns在改变,应该尽量避免变动`);
|
|
245
|
+
});
|
|
246
|
+
addFormDefaultValue();
|
|
247
|
+
runFieldWatch();
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
immediate: true
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
onUnmounted(() => {
|
|
254
|
+
watchMap.forEach((cancel) => cancel());
|
|
255
|
+
});
|
|
256
|
+
function runFieldWatch() {
|
|
257
|
+
columns.value.forEach((column) => {
|
|
258
|
+
let watchObj = column.watch;
|
|
259
|
+
const control = column.control;
|
|
260
|
+
if (!watchObj && !control) return;
|
|
261
|
+
const field = getField(column);
|
|
262
|
+
const cancel = [];
|
|
263
|
+
if (typeof watchObj === "function") {
|
|
264
|
+
watchObj = {
|
|
265
|
+
handler: watchObj
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (watchObj) {
|
|
269
|
+
cancel.push(
|
|
270
|
+
watch(
|
|
271
|
+
() => {
|
|
272
|
+
return get(form.value, field);
|
|
273
|
+
},
|
|
274
|
+
(val, oldVal) => {
|
|
275
|
+
watchObj.handler({ val, oldVal, form: form.value });
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
immediate: watchObj.immediate,
|
|
279
|
+
deep: watchObj.deep
|
|
280
|
+
}
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
if (control) {
|
|
285
|
+
cancel.push(
|
|
286
|
+
watch(
|
|
287
|
+
() => get(form.value, field),
|
|
288
|
+
(val) => {
|
|
289
|
+
control.forEach(({ field: field2, value }) => {
|
|
290
|
+
let show = true;
|
|
291
|
+
if (typeof value === "function") {
|
|
292
|
+
show = value({
|
|
293
|
+
formData: form.value,
|
|
294
|
+
val
|
|
295
|
+
});
|
|
296
|
+
} else {
|
|
297
|
+
show = Array.isArray(value) ? value.includes(val) : val === value;
|
|
298
|
+
}
|
|
299
|
+
if (show) {
|
|
300
|
+
hideFieldSet.value.delete(field2);
|
|
301
|
+
} else {
|
|
302
|
+
hideFieldSet.value.add(field2);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
immediate: true
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
watchMap.set(field, () => {
|
|
313
|
+
cancel.forEach((c) => c());
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
const resetToDefault = async (sync = false) => {
|
|
318
|
+
if (!sync) await nextTick();
|
|
319
|
+
if (tmpDefaultForm) {
|
|
320
|
+
form.value = cloneDeep(tmpDefaultForm);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
form.value = columns.value.reduce((prev, column) => {
|
|
324
|
+
const { fields, values } = getFieldsAndValues(column);
|
|
325
|
+
fields.forEach((field, idx) => {
|
|
326
|
+
set(prev, field, cloneDeep(values[idx]));
|
|
327
|
+
});
|
|
328
|
+
return prev;
|
|
329
|
+
}, {});
|
|
330
|
+
};
|
|
331
|
+
const setAsDefault = (v) => {
|
|
332
|
+
tmpDefaultForm = cloneDeep(v ?? form.value);
|
|
333
|
+
};
|
|
334
|
+
function addFormDefaultValue() {
|
|
335
|
+
columns.value.forEach((column) => {
|
|
336
|
+
const { fields, values } = getFieldsAndValues(column);
|
|
337
|
+
fields.forEach((field, idx) => {
|
|
338
|
+
if (values[idx] != null && !has(form.value, field)) {
|
|
339
|
+
set(form.value, field, cloneDeep(values[idx]));
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const getFormData = () => {
|
|
345
|
+
const formData2 = {};
|
|
346
|
+
visibleColumns.value.forEach((usefulColumn) => {
|
|
347
|
+
const { fields } = getFieldsAndValues(usefulColumn);
|
|
348
|
+
fields.forEach((field) => {
|
|
349
|
+
set(formData2, field, get(form.value, field));
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
return formData2;
|
|
353
|
+
};
|
|
354
|
+
const internalFormProps = computed(() => props.internalFormProps);
|
|
355
|
+
const cache = computed(() => props.cache);
|
|
356
|
+
provide(provideFormKey, {
|
|
357
|
+
form,
|
|
358
|
+
columnsChecked,
|
|
359
|
+
columnsSort,
|
|
360
|
+
columns,
|
|
361
|
+
visibleColumns,
|
|
362
|
+
internalFormProps,
|
|
363
|
+
cache,
|
|
364
|
+
getFormData,
|
|
365
|
+
resetToDefault,
|
|
366
|
+
setAsDefault,
|
|
367
|
+
onSubmit: props.onSubmit,
|
|
368
|
+
resetColumnsChecked,
|
|
369
|
+
resetColumnsSort,
|
|
370
|
+
...customProps
|
|
371
|
+
});
|
|
372
|
+
return {
|
|
373
|
+
form,
|
|
374
|
+
visibleColumns,
|
|
375
|
+
resetToDefault,
|
|
376
|
+
getFormData,
|
|
377
|
+
setAsDefault
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
const useFormInject = () => {
|
|
381
|
+
return inject(provideFormKey);
|
|
382
|
+
};
|
|
383
|
+
const useFormItem = (options) => {
|
|
384
|
+
let { props, valueGetter, valueSetter } = options;
|
|
385
|
+
if (props.column.valueGetter) {
|
|
386
|
+
valueGetter = props.column.valueGetter;
|
|
387
|
+
}
|
|
388
|
+
if (props.column.valueSetter) {
|
|
389
|
+
valueSetter = props.column.valueSetter;
|
|
390
|
+
}
|
|
391
|
+
const { form } = useFormInject();
|
|
392
|
+
const valueComputed = computed({
|
|
393
|
+
get() {
|
|
394
|
+
let val;
|
|
395
|
+
if (props.column.fields) {
|
|
396
|
+
val = props.column.fields.map((field) => {
|
|
397
|
+
return get(form.value, field);
|
|
398
|
+
}).filter((e) => !isEmptyStrOrNull(e));
|
|
399
|
+
} else if (props.column.field) {
|
|
400
|
+
val = get(form.value, props.column.field);
|
|
401
|
+
} else {
|
|
402
|
+
console.warn(`column 没有设置 field 或者 fields`, props.column);
|
|
403
|
+
}
|
|
404
|
+
if (valueGetter) val = valueGetter(val);
|
|
405
|
+
return val;
|
|
406
|
+
},
|
|
407
|
+
set(val) {
|
|
408
|
+
if (valueSetter) val = valueSetter(val);
|
|
409
|
+
if (props.column.fields) {
|
|
410
|
+
props.column.fields.forEach((field, index) => {
|
|
411
|
+
set(form.value, field, val == null ? void 0 : val[index]);
|
|
412
|
+
});
|
|
413
|
+
} else if (props.column.field) {
|
|
414
|
+
set(form.value, props.column.field, val);
|
|
415
|
+
} else {
|
|
416
|
+
console.warn(`column 没有设置 field 或者 fields`, props.column);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
const isView = computed(() => {
|
|
421
|
+
return toValue(props.column.isView) ?? props.isView;
|
|
422
|
+
});
|
|
423
|
+
return {
|
|
424
|
+
valueComputed,
|
|
425
|
+
isView
|
|
426
|
+
};
|
|
427
|
+
};
|
|
428
|
+
const defineTfForm = (setup, renderMap, _runtimeProps) => {
|
|
429
|
+
const FormComponent = defineComponent(setup, {
|
|
430
|
+
inheritAttrs: false,
|
|
431
|
+
name: "TfFormContainer"
|
|
432
|
+
});
|
|
433
|
+
const runtimeProps = [
|
|
434
|
+
"cache",
|
|
435
|
+
"columns",
|
|
436
|
+
"formData",
|
|
437
|
+
"internalFormProps",
|
|
438
|
+
"onSubmit",
|
|
439
|
+
"onUpdate:formData",
|
|
440
|
+
..._runtimeProps
|
|
441
|
+
];
|
|
442
|
+
return defineComponent(
|
|
443
|
+
(props, ctx) => {
|
|
444
|
+
const formData = computed({
|
|
445
|
+
get: () => props.formData,
|
|
446
|
+
set(v) {
|
|
447
|
+
var _a;
|
|
448
|
+
(_a = props["onUpdate:formData"]) == null ? void 0 : _a.call(props, v);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
const { visibleColumns } = useForm(
|
|
452
|
+
props,
|
|
453
|
+
formData,
|
|
454
|
+
getPropsKeys(_runtimeProps)
|
|
455
|
+
);
|
|
456
|
+
return () => h(FormComponent, null, {
|
|
457
|
+
...ctx.slots,
|
|
458
|
+
formContent: () => visibleColumns.value.map((column) => {
|
|
459
|
+
var _a;
|
|
460
|
+
if (!renderMap.has(column.type)) {
|
|
461
|
+
console.warn(
|
|
462
|
+
`[@ftjs/core]: 没有配置 column.type ${column.type}, 请检查该组件是否注册`
|
|
463
|
+
);
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
const component = renderMap.get(column.type);
|
|
467
|
+
return h(component, {
|
|
468
|
+
column,
|
|
469
|
+
// 是否为查看模式
|
|
470
|
+
isView: column.isView,
|
|
471
|
+
key: column.field ?? ((_a = column.fields) == null ? void 0 : _a[0])
|
|
472
|
+
});
|
|
473
|
+
})
|
|
474
|
+
});
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
props: transferVueArrayPropsToObject(runtimeProps),
|
|
478
|
+
name: "TfForm"
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
};
|
|
482
|
+
function defineFormComponent(setup) {
|
|
483
|
+
return defineComponent(setup, {
|
|
484
|
+
props: ["column", "isView"],
|
|
485
|
+
inheritAttrs: false
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const provideTableKey = Symbol("@ftjs/core-table-provide");
|
|
489
|
+
const useTable = (props, runtimePropsKeys) => {
|
|
490
|
+
const formColumns = computed(() => {
|
|
491
|
+
const fromTable = props.columns.filter((e) => e.search).map((e) => ({
|
|
492
|
+
field: e.field,
|
|
493
|
+
title: e.title,
|
|
494
|
+
...e.search
|
|
495
|
+
}));
|
|
496
|
+
return [...fromTable, ...props.searchColumns ?? []];
|
|
497
|
+
});
|
|
498
|
+
const tableColumns = computed(() => {
|
|
499
|
+
return props.columns;
|
|
500
|
+
});
|
|
501
|
+
const injectProps = runtimePropsKeys.reduce(
|
|
502
|
+
(acc, key) => {
|
|
503
|
+
if (key.startsWith("on")) {
|
|
504
|
+
acc[key] = props[key];
|
|
505
|
+
} else {
|
|
506
|
+
acc[key] = computed(() => props[key]);
|
|
507
|
+
}
|
|
508
|
+
return acc;
|
|
509
|
+
},
|
|
510
|
+
{}
|
|
511
|
+
);
|
|
512
|
+
provide(provideTableKey, {
|
|
513
|
+
formColumns,
|
|
514
|
+
tableColumns,
|
|
515
|
+
...injectProps
|
|
516
|
+
});
|
|
517
|
+
};
|
|
518
|
+
const useTableInject = () => {
|
|
519
|
+
return inject(provideTableKey);
|
|
520
|
+
};
|
|
521
|
+
function defineTfTable(setup, _runtimeProps) {
|
|
522
|
+
const TableComponent = defineComponent(setup, {
|
|
523
|
+
inheritAttrs: false,
|
|
524
|
+
name: "TfTableContainer"
|
|
525
|
+
});
|
|
526
|
+
const runtimeProps = [
|
|
527
|
+
"cache",
|
|
528
|
+
"columns",
|
|
529
|
+
"searchColumns",
|
|
530
|
+
"total",
|
|
531
|
+
"defaultPageSize",
|
|
532
|
+
["loading", { type: Boolean }],
|
|
533
|
+
"internalTableProps",
|
|
534
|
+
"internalFormProps",
|
|
535
|
+
"tableData",
|
|
536
|
+
"keyField",
|
|
537
|
+
..._runtimeProps
|
|
538
|
+
];
|
|
539
|
+
return defineComponent(
|
|
540
|
+
(props, ctx) => {
|
|
541
|
+
useTable(props, getPropsKeys(runtimeProps));
|
|
542
|
+
return () => h(TableComponent, null, ctx.slots);
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
props: transferVueArrayPropsToObject(runtimeProps),
|
|
546
|
+
name: "TfTable"
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
export {
|
|
551
|
+
cloneDeep,
|
|
552
|
+
defineFormComponent,
|
|
553
|
+
defineTfForm,
|
|
554
|
+
defineTfTable,
|
|
555
|
+
get,
|
|
556
|
+
getField,
|
|
557
|
+
getPropsKeys,
|
|
558
|
+
getStorage,
|
|
559
|
+
has,
|
|
560
|
+
isBrowser,
|
|
561
|
+
isEmptyStrOrNull,
|
|
562
|
+
set,
|
|
563
|
+
setStorage,
|
|
564
|
+
transferVueArrayPropsToObject,
|
|
565
|
+
unrefs,
|
|
566
|
+
useForm,
|
|
567
|
+
useFormInject,
|
|
568
|
+
useFormItem,
|
|
569
|
+
useTableInject
|
|
570
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RecordPath } from '../type-helper';
|
|
2
|
+
/**
|
|
3
|
+
* 表格列定义
|
|
4
|
+
*
|
|
5
|
+
* @public
|
|
6
|
+
*/
|
|
7
|
+
export interface TfTableColumn<TableData extends Record<string, any>> {
|
|
8
|
+
/**
|
|
9
|
+
* 列标题
|
|
10
|
+
*/
|
|
11
|
+
title?: string;
|
|
12
|
+
/**
|
|
13
|
+
* 列字段
|
|
14
|
+
*/
|
|
15
|
+
field: RecordPath<TableData> | `_${string}`;
|
|
16
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { EmitsOptions, SetupContext, SlotsType } from 'vue';
|
|
2
|
+
import { TfTableColumn } from './columns';
|
|
3
|
+
import { RuntimeProps, TfFormColumnBase } from '../form';
|
|
4
|
+
import { TupleKeys } from '../type-helper';
|
|
5
|
+
export interface TableTypeMap<TableData extends Record<string, any>, SearchData extends Record<string, any>> {
|
|
6
|
+
default: {
|
|
7
|
+
tableSlots: {};
|
|
8
|
+
tableColumn: TfTableColumn<TableData>;
|
|
9
|
+
formColumn: TfFormColumnBase<SearchData>;
|
|
10
|
+
extendedProps: {};
|
|
11
|
+
internalFormProps: {};
|
|
12
|
+
internalTableProps: {};
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export type TableColumn<TableData extends Record<string, any>, SearchData extends Record<string, any>, Type extends keyof TableTypeMap<TableData, SearchData>> = TableTypeMap<TableData, SearchData>[Type]["tableColumn"] & {
|
|
16
|
+
/**
|
|
17
|
+
* 搜索表单配置
|
|
18
|
+
*/
|
|
19
|
+
search?: TableTypeMap<TableData, SearchData>[Type]["formColumn"];
|
|
20
|
+
};
|
|
21
|
+
export interface TfTableIntrinsicProps<TableData extends Record<string, any>, SearchData extends Record<string, any>, type extends keyof TableTypeMap<TableData, SearchData>> {
|
|
22
|
+
/**
|
|
23
|
+
* 用于缓存配置,不填则不缓存
|
|
24
|
+
*/
|
|
25
|
+
cache?: string;
|
|
26
|
+
/**
|
|
27
|
+
* 列定义
|
|
28
|
+
*/
|
|
29
|
+
columns: TableColumn<TableData, SearchData, type>[];
|
|
30
|
+
/**
|
|
31
|
+
* 列定义外的搜索条件
|
|
32
|
+
*/
|
|
33
|
+
searchColumns?: TableTypeMap<TableData, SearchData>[type]["formColumn"][];
|
|
34
|
+
/**
|
|
35
|
+
* 表格总条数
|
|
36
|
+
*/
|
|
37
|
+
total?: number;
|
|
38
|
+
/**
|
|
39
|
+
* 默认每页条数
|
|
40
|
+
*/
|
|
41
|
+
defaultPageSize?: number;
|
|
42
|
+
/**
|
|
43
|
+
* 是否显示 loading
|
|
44
|
+
*/
|
|
45
|
+
loading?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* 内部表单组件的 props
|
|
48
|
+
*/
|
|
49
|
+
internalFormProps?: TableTypeMap<TableData, SearchData>[type]["internalFormProps"];
|
|
50
|
+
/**
|
|
51
|
+
* 内部表格组件的 props
|
|
52
|
+
*/
|
|
53
|
+
internalTableProps?: TableTypeMap<TableData, SearchData>[type]["internalTableProps"];
|
|
54
|
+
/**
|
|
55
|
+
* 表格数据
|
|
56
|
+
*/
|
|
57
|
+
tableData?: TableData[];
|
|
58
|
+
/**
|
|
59
|
+
* 表格 key 字段
|
|
60
|
+
*/
|
|
61
|
+
keyField?: string;
|
|
62
|
+
}
|
|
63
|
+
export type TfTablePropsMap<TableData extends Record<string, any>, SearchData extends Record<string, any>, Type extends keyof TableTypeMap<TableData, SearchData>> = TfTableIntrinsicProps<TableData, SearchData, Type> & TableTypeMap<TableData, SearchData>[Type]["extendedProps"];
|
|
64
|
+
export declare function defineTfTable<Type extends keyof TableTypeMap<any, any>>(setup: (props: {}, ctx: SetupContext<EmitsOptions, SlotsType<TableTypeMap<any, any>[Type]["tableSlots"]>>) => any, _runtimeProps: RuntimeProps<TupleKeys<TableTypeMap<any, any>[Type]["extendedProps"]>>[] & {
|
|
65
|
+
length: TupleKeys<TableTypeMap<any, any>[Type]["extendedProps"]>["length"];
|
|
66
|
+
}): new <TableData extends Record<string, any>, SearchData extends Record<string, any> = TableData>(props: (TfTableIntrinsicProps<TableData, SearchData, Type> & TableTypeMap<TableData, SearchData>[Type]["extendedProps"] & ({
|
|
67
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
68
|
+
} | {
|
|
69
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
70
|
+
})) & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps) => import('vue').CreateComponentPublicInstanceWithMixins<TfTableIntrinsicProps<TableData, SearchData, Type> & TableTypeMap<TableData, SearchData>[Type]["extendedProps"] & ({
|
|
71
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
72
|
+
} | {
|
|
73
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
74
|
+
}), {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, EmitsOptions, import('vue').PublicProps, {}, false, {}, SlotsType<TableTypeMap<TableData, SearchData>[Type]["tableSlots"]>, {}, {}, string, {}, any, import('vue').ComponentProvideOptions, {
|
|
75
|
+
P: {};
|
|
76
|
+
B: {};
|
|
77
|
+
D: {};
|
|
78
|
+
C: {};
|
|
79
|
+
M: {};
|
|
80
|
+
Defaults: {};
|
|
81
|
+
}, {} & ((TfTableIntrinsicProps<TableData, SearchData, Type> & TableTypeMap<TableData, SearchData>[Type]["extendedProps"] & {
|
|
82
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
83
|
+
} extends infer T ? T extends TfTableIntrinsicProps<TableData, SearchData, Type> & TableTypeMap<TableData, SearchData>[Type]["extendedProps"] & {
|
|
84
|
+
[x: `on${Capitalize<string>}`]: ((...args: any[]) => any) | undefined;
|
|
85
|
+
} ? T extends void ? {} : T : never : never) | (TfTableIntrinsicProps<TableData, SearchData, Type> & TableTypeMap<TableData, SearchData>[Type]["extendedProps"] & {
|
|
86
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
87
|
+
} extends infer T_1 ? T_1 extends TfTableIntrinsicProps<TableData, SearchData, Type> & TableTypeMap<TableData, SearchData>[Type]["extendedProps"] & {
|
|
88
|
+
[x: `on${Capitalize<string>}`]: ((...args: never) => any) | undefined;
|
|
89
|
+
} ? T_1 extends void ? {} : T_1 : never : never)), {}, {}, {}, {}, {}>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ComputedRef } from 'vue';
|
|
2
|
+
import { TableTypeMap, TfTableIntrinsicProps } from './define-components';
|
|
3
|
+
import { SplitEventKeys } from '../type-helper';
|
|
4
|
+
type TableInject<TableData extends Record<string, any>, FormData extends Record<string, any> = TableData, Type extends keyof TableTypeMap<TableData, FormData> = "default"> = SplitEventKeys<TfTableIntrinsicProps<TableData, FormData, Type> & TableTypeMap<TableData, FormData>[Type]["extendedProps"]> & {
|
|
5
|
+
formColumns: ComputedRef<TableTypeMap<TableData, FormData>[Type]["formColumn"][]>;
|
|
6
|
+
tableColumns: ComputedRef<TableTypeMap<TableData, FormData>[Type]["tableColumn"][]>;
|
|
7
|
+
};
|
|
8
|
+
export declare const useTable: <TableData extends Record<string, any>, FormData extends Record<string, any>, Type extends keyof TableTypeMap<TableData, FormData>>(props: TfTableIntrinsicProps<TableData, FormData, Type>, runtimePropsKeys: string[]) => void;
|
|
9
|
+
export declare const useTableInject: <TableData extends Record<string, any>, FormData extends Record<string, any> = TableData, Type extends keyof TableTypeMap<TableData, FormData> = "default">() => TableInject<TableData, FormData, Type> | undefined;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ComputedRef, MaybeRef } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* 临时工具类型减1
|
|
4
|
+
*/
|
|
5
|
+
type Sub1 = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
6
|
+
/**
|
|
7
|
+
* 工具类型:获取对象的路径
|
|
8
|
+
*
|
|
9
|
+
* T: 传入的对象类型
|
|
10
|
+
* Depth: 有一些对象属性是循环引用的,需要限制下递归深度,避免超出栈的最大深度
|
|
11
|
+
*/
|
|
12
|
+
export type RecordPath<T, Depth extends number = 5> = Depth extends -1 ? string : T extends any[] ? `${number}` | `${number}.${RecordPath<T[number], Sub1[Depth]>}` : T extends Record<string, any> ? ValueOf<{
|
|
13
|
+
[K in keyof T]-?: K extends string ? `${K}` | `${K}.${RecordPath<T[K], Sub1[Depth]>}` : never;
|
|
14
|
+
}> : never;
|
|
15
|
+
export type ValueOf<T> = T[keyof T];
|
|
16
|
+
/**
|
|
17
|
+
* 工具类型:恢复{@link MaybeRef}值为普通值
|
|
18
|
+
*/
|
|
19
|
+
export type Unref<T> = T extends MaybeRef<infer U> ? U : T;
|
|
20
|
+
/**
|
|
21
|
+
* 工具类型:将对象的属性值转换为 {@link Unref}
|
|
22
|
+
*/
|
|
23
|
+
export type Unrefs<T> = {
|
|
24
|
+
[K in keyof T]: Unref<T[K]>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* 工具类型:将对象的属性值转换为 {@link MaybeRef}
|
|
28
|
+
*/
|
|
29
|
+
export type Refs<T> = {
|
|
30
|
+
[K in keyof T]: MaybeRef<T[K]>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* 工具类型:将对象的属性转换为元组
|
|
34
|
+
*
|
|
35
|
+
* @deprecated 元祖推算的元素顺序不确定,所以实际上无法使用,替换为 {@link WithLengthKeys}
|
|
36
|
+
*/
|
|
37
|
+
export type TupleKeys<T> = UnionToTuple<keyof T>;
|
|
38
|
+
/**
|
|
39
|
+
* 工具类型:将联合类型转换为元组
|
|
40
|
+
*
|
|
41
|
+
* @deprecated 元祖推算的元素顺序不确定,所以实际上无法使用
|
|
42
|
+
*/
|
|
43
|
+
type UnionToTuple<T> = ((T extends any ? (t: T) => T : never) extends infer U ? (U extends any ? (u: U) => any : never) extends (v: infer V) => any ? V : never : never) extends (_: any) => infer W ? [...UnionToTuple<Exclude<T, W>>, W] : [];
|
|
44
|
+
/**
|
|
45
|
+
* 工具类型:得到一个具有指定长度的数组,且每个元素唯一
|
|
46
|
+
*/
|
|
47
|
+
export type WithLengthKeys<T> = (keyof T)[] & {
|
|
48
|
+
length: TupleKeys<T>["length"];
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* 工具类型:将对象中所有属性值转为 {@link ComputedRef}
|
|
52
|
+
*/
|
|
53
|
+
export type ComputedRefKeys<T> = {
|
|
54
|
+
[K in keyof T]-?: T[K] extends ComputedRef<any> ? T[K] : ComputedRef<T[K]>;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* 工具类型:将对象中所有属性值分流,分为事件和非事件
|
|
58
|
+
*
|
|
59
|
+
* 事件可为空。
|
|
60
|
+
* 属性为可为空的计算属性,本身一定存在。
|
|
61
|
+
*/
|
|
62
|
+
export type SplitEventKeys<T> = {
|
|
63
|
+
[K in keyof T as K extends `on${string}` ? K : never]?: T[K];
|
|
64
|
+
} & {
|
|
65
|
+
[K in keyof T as K extends `on${string}` ? never : K]-?: ComputedRef<T[K]>;
|
|
66
|
+
};
|
|
67
|
+
export {};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { PropType } from 'vue';
|
|
2
|
+
import { RecordPath, Unrefs } from './type-helper';
|
|
3
|
+
import { TfFormColumnBase } from './form/columns';
|
|
4
|
+
export declare const isBrowser: boolean;
|
|
5
|
+
export declare const getField: <T extends Record<string, any>>(column: TfFormColumnBase<T>) => RecordPath<T>;
|
|
6
|
+
export declare const isEmptyStrOrNull: (val: any) => boolean;
|
|
7
|
+
export declare const cloneDeep: <T>(obj: T) => T;
|
|
8
|
+
export declare const get: (obj: any, path: string) => any;
|
|
9
|
+
export declare const set: (obj: any, path: string, value: any) => void;
|
|
10
|
+
export declare const has: (obj: any, path: string) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 浅层的将对象的属性值(可能是响应式)转换为普通值,不转化getter
|
|
13
|
+
* @param obj
|
|
14
|
+
* @returns
|
|
15
|
+
*/
|
|
16
|
+
export declare const unrefs: <T>(obj: T) => Unrefs<T>;
|
|
17
|
+
/**
|
|
18
|
+
* 获取缓存
|
|
19
|
+
* @param key 缓存 key
|
|
20
|
+
* @param cache 缓存名称
|
|
21
|
+
* @returns
|
|
22
|
+
*/
|
|
23
|
+
export declare const getStorage: (key: string, cache?: string) => {};
|
|
24
|
+
/**
|
|
25
|
+
* 设置缓存
|
|
26
|
+
* @param key 缓存 key
|
|
27
|
+
* @param cache 缓存名称
|
|
28
|
+
*/
|
|
29
|
+
export declare const setStorage: (key: string, value: any, cache?: string) => void;
|
|
30
|
+
export type RuntimeProps<T extends readonly any[] = []> = T[number] | [
|
|
31
|
+
T[number],
|
|
32
|
+
{
|
|
33
|
+
type?: PropType<any> | true | null;
|
|
34
|
+
required?: boolean;
|
|
35
|
+
default?: any;
|
|
36
|
+
validator?(value: unknown, props: any): boolean;
|
|
37
|
+
}
|
|
38
|
+
];
|
|
39
|
+
export declare const transferVueArrayPropsToObject: (arr: RuntimeProps[]) => any;
|
|
40
|
+
export declare const getPropsKeys: (arr: RuntimeProps<any>[]) => any[];
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ftjs/core",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"keywords": [],
|
|
5
|
+
"author": "",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^22.10.9",
|
|
22
|
+
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
|
23
|
+
"@vitest/coverage-v8": "^3.0.5",
|
|
24
|
+
"@vitest/ui": "^3.0.5",
|
|
25
|
+
"@vue/test-utils": "^2.4.6",
|
|
26
|
+
"happy-dom": "^17.0.2",
|
|
27
|
+
"typescript": "^5.7.3",
|
|
28
|
+
"vite": "^6.1.0",
|
|
29
|
+
"vite-plugin-dts": "^4.5.0",
|
|
30
|
+
"vitest": "^3.0.5",
|
|
31
|
+
"vue-tsc": "2.2.0",
|
|
32
|
+
"vue": "^3.5.13"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"vue": ">=3.3.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "vite build",
|
|
39
|
+
"minify": "pnpm dlx esbuild ./dist/index.js --minify --outfile=./dist/index.min.js",
|
|
40
|
+
"test": "vitest",
|
|
41
|
+
"test:coverage": "vitest run --coverage",
|
|
42
|
+
"pub": "tsx ../../scripts/publish.ts"
|
|
43
|
+
}
|
|
44
|
+
}
|