(null);
166 |
167 | //Context for parent
168 | const formItemInternalContext = {
169 | getValidateTrigger: () => validateTrigger.value || formContextProps?.validateTrigger.value || 'submit',
170 | getFieldName: () => name.value,
171 | setErrorState(errorMessage) { error.value = errorMessage; },
172 | getUniqueId,
173 | } as FormItemInternalContext;
174 |
175 | //Context for custom children
176 | provide(FormItemContextContextKey, {
177 | getFieldName: () => name.value,
178 | onFieldBlur: () => { formContextProps?.onFieldBlur(formItemInternalContext); },
179 | onFieldChange: (newValue: unknown) => { formContextProps?.onFieldChange(formItemInternalContext, newValue); },
180 | clearValidate: () => { formContextProps?.clearValidate(formItemInternalContext); },
181 | });
182 |
183 | //Add ref in form
184 | const addNumber = formContextProps?.addFormItemField(formItemInternalContext);
185 | const uniqueId = (formContextProps?.name || 'form') + 'Item' + (name.value || `unknowProperity${addNumber}`);
186 |
187 | onBeforeUnmount(() => {
188 | formContextProps?.removeFormItemField(formItemInternalContext);
189 | })
190 |
191 | ctx.expose({
192 | error,
193 | });
194 |
195 | function getUniqueId() {
196 | return uniqueId;
197 | }
198 |
199 | return () => {
200 | const labelColValue = labelCol.value?.span ?? formContextProps?.labelCol.value?.span;
201 | return (
202 |
206 | {
207 | (labelColValue > 0 && showLabel.value && formContextProps?.showLabel.value && label) ?
208 |
214 |
229 | : ''
230 | }
231 |
236 |
237 | { renderSlot(ctx.slots, 'default') }
238 | {
239 | error.value ?
240 |
{ error.value }
241 | : ''
242 | }
243 |
244 |
245 |
246 | );
247 | };
248 | },
249 | });
--------------------------------------------------------------------------------
/library/DynamicFormBasicControls/Layout/Col.tsx:
--------------------------------------------------------------------------------
1 | import { computed, defineComponent, inject, renderSlot, toRefs } from 'vue';
2 |
3 | export interface ColProps {
4 | /**
5 | * 列元素偏移距离
6 | */
7 | offset?: number;
8 | /**
9 | * 列元素宽度
10 | */
11 | span?: number;
12 |
13 | style?: object;
14 | class?: string,
15 | }
16 |
17 | /**
18 | * 24列栅格列组件。
19 | *
20 | * 提供了 24列栅格,通过在 Col 上添加 span 属性设置列所占的宽度百分比。
21 | *
22 | * 此外,添加 offset 属性可以设置列的偏移宽度,计算方式与 span 相同。
23 | */
24 | export default defineComponent({
25 | name: 'Col',
26 | props: {
27 | /**
28 | * 列元素偏移距离
29 | */
30 | offset: {
31 | type: Number,
32 | default: 0
33 | },
34 | /**
35 | * 列元素宽度
36 | */
37 | span: {
38 | type: Number,
39 | default: 0
40 | },
41 | class: {
42 | type: String,
43 | default: undefined,
44 | },
45 | style: {
46 | type: Object,
47 | default: undefined,
48 | }
49 | },
50 | setup(props, ctx) {
51 | const { span, offset } = toRefs(props);
52 |
53 | const GRID_SIZE = inject('DynamicFormLayoyGridSize', 24);
54 |
55 | const pec = computed(() => {
56 | return ((span.value || 0) / GRID_SIZE) * 100;
57 | });
58 |
59 | return () => (
60 | 0 ? `${pec.value}%` : undefined,
64 | marginLeft: offset.value ? `${(offset.value / GRID_SIZE) * 100}%` : undefined,
65 | maxWidth: pec.value > 0 ? `${pec.value}%` : undefined,
66 | ...props.style,
67 | }}
68 | >
69 | { renderSlot(ctx.slots, 'default') }
70 |
71 | )
72 | },
73 | });
74 |
--------------------------------------------------------------------------------
/library/DynamicFormBasicControls/Layout/Row.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, toRefs, type VNode } from 'vue';
2 |
3 | export interface RowProps {
4 | /**
5 | * 列元素之间的间距(单位为 px)
6 | */
7 | gutter?: number;
8 | /**
9 | * 主轴对齐方式,可选值为
10 | */
11 | justify?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' | undefined;
12 | /**
13 | * 交叉轴对齐方式
14 | */
15 | align?: "center" | "flex-start" | "flex-end" | "stretch" | "baseline" | undefined;
16 | /**
17 | * 是否自动换行,默认 true
18 | */
19 | wrap?: boolean;
20 |
21 | style?: object;
22 | class?: string,
23 | }
24 |
25 | /**
26 | * 24列栅格行组件。
27 | */
28 | export default defineComponent({
29 | name: 'Row',
30 | props: {
31 | /**
32 | * 列元素之间的间距(单位为px)
33 | */
34 | gutter: {
35 | type: Number,
36 | default: 0
37 | },
38 | /**
39 | * 主轴对齐方式,可选值为 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'
40 | */
41 | justify: {
42 | type: String,
43 | default: undefined,
44 | },
45 | /**
46 | * 交叉轴对齐方式,可选值为 "center" | "flex-start" | "flex-end" | "stretch" | "baseline"
47 | */
48 | align: {
49 | type: String,
50 | default: undefined,
51 | },
52 | /**
53 | * 是否自动换行,默认 true
54 | */
55 | wrap: {
56 | type: Boolean,
57 | default: true
58 | },
59 |
60 | class: {
61 | type: String,
62 | default: undefined,
63 | },
64 | style: {
65 | type: Object,
66 | default: undefined,
67 | }
68 | },
69 | setup(props, ctx) {
70 | const { gutter, wrap, align, justify } = toRefs(props);
71 |
72 | function setVnodeStyle(node: VNode, style: Record) {
73 | if (!node.props) node.props = {};
74 | if (!node.props.style) node.props.style = {};
75 |
76 | node.props.style = {
77 | ...node.props.style,
78 | ...style,
79 | }
80 | }
81 |
82 | return () => {
83 |
84 | const children = ctx.slots.default ? ctx.slots.default() : [];
85 | //处理子级元素,为其增加边距
86 | const count = children.length;
87 | if (count > 0 && gutter.value > 0) {
88 | if (count > 1) {
89 | children.forEach((k, index) => setVnodeStyle(children[0], {
90 | paddingLeft: index === 0 ? 0 : (index === count - 1 ? gutter.value : gutter.value / 2),
91 | paddingRight: index === count - 1 ? 0 : (index === 0 ? gutter.value : gutter.value / 2),
92 | }));
93 | } else {
94 | setVnodeStyle(children[0], { paddingLeft: gutter.value / 2, paddingRight: gutter.value / 2 });
95 | }
96 | }
97 |
98 | return (
99 |
108 | { children }
109 |
);
110 | };
111 | },
112 | });
113 |
--------------------------------------------------------------------------------
/library/DynamicFormBasicControls/Utils/ArrayUtils.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 交换数组两个元素
4 | * @param {Array} arr 数组
5 | * @param {Number} index1 索引1
6 | * @param {Number} index2 索引2
7 | */
8 | function swapItems(arr: Array, index1: number, index2: number) {
9 | arr[index1] = arr.splice(index2, 1, arr[index1])[0]
10 | /*
11 | let x = arr[index1];
12 | arr[index1] = arr[index2];
13 | arr[index2] = x;
14 | */
15 | return arr
16 | }
17 | /**
18 | * 指定数组索引位置元素向上移
19 | * @param {Array} arr 数组
20 | * @param {Number} index 索引
21 | */
22 | function upData(arr: Array, index: number) {
23 | if (arr.length > 1 && index !== 0)
24 | return swapItems(arr, index, index - 1)
25 | }
26 | /**
27 | * 指定数组索引位置元素向下移
28 | * @param {Array} arr 数组
29 | * @param {Number} index 索引
30 | */
31 | function downData(arr: Array, index: number) {
32 | if (arr.length > 1 && index !== (arr.length - 1))
33 | return swapItems(arr, index, index + 1)
34 | }
35 |
36 | export default {
37 | swapItems,
38 | upData,
39 | downData,
40 | };
41 |
--------------------------------------------------------------------------------
/library/DynamicFormBasicControls/Utils/ObjectUtils.ts:
--------------------------------------------------------------------------------
1 | export type CKeyValueTypeAll = Record|unknown|undefined|string|bigint|number|boolean;
2 |
3 | export interface CKeyValue {
4 | [index: string]: CKeyValueTypeAll;
5 | }
6 |
7 | /**
8 | * 深克隆对象,数组
9 | * @param obj 要克隆的对象
10 | * @param deepArray 是否要深度克隆数组里的每个对象
11 | */
12 | function clone(obj: CKeyValueTypeAll, deepArray = false): CKeyValue|Array|null {
13 | let temp: CKeyValue|Array|null = null;
14 | if (obj instanceof Array) {
15 | if (deepArray) {
16 | temp = (obj as CKeyValue[]).map((item) => clone(item, deepArray) as CKeyValue);
17 | }
18 | else {
19 | temp = obj.concat();
20 | }
21 | }
22 | else if (typeof obj === 'object') {
23 | temp = {} as CKeyValue;
24 | for (const item in obj) {
25 | const val = (obj as CKeyValue)[item];
26 | if (val === null) { temp[item] = null; }
27 | else { (temp as CKeyValue)[item] = clone(val, deepArray) as CKeyValueTypeAll; }
28 | }
29 | } else {
30 | temp = obj as CKeyValue;
31 | }
32 | return temp;
33 | }
34 |
35 | export default {
36 | clone,
37 | };
38 |
--------------------------------------------------------------------------------
/library/DynamicFormBasicControls/index.ts:
--------------------------------------------------------------------------------
1 | import Form from './Form'
2 | import FormItem from './FormItem'
3 | import Col from './Layout/Col'
4 | import Row from './Layout/Row'
5 |
6 | export { Form, FormItem, Col, Row }
7 | export * from './Form'
8 | export * from './FormItem'
9 | export * from './Layout/Col'
10 | export * from './Layout/Row'
11 | export * from './FormContext'
12 |
--------------------------------------------------------------------------------
/library/DynamicFormInner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ options.emptyText }}
6 |
7 |
8 | DynamicForm Warn: model is not a object!
9 |
10 |
11 |
12 | model[item.name] = v"
21 | :disabled="options.disabled"
22 | >
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
57 |
--------------------------------------------------------------------------------
/library/DynamicFormInternal.ts:
--------------------------------------------------------------------------------
1 | export type IDynamicFormMessageCenterCallback = (messageName: string, data: unknown) => void;
2 |
3 | export interface IDynamicFormMessageCenter {
4 | addInstance: (name: string, fn: IDynamicFormMessageCenterCallback) => void,
5 | removeInstance: (name: string) => void,
6 | }
--------------------------------------------------------------------------------
/library/DynamicFormItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 | [object] Warn: Input field {{ name }} is undefined
18 |
19 |
20 |
21 |
30 |
31 | {{ evaluateCallback(item.label) }}
32 |
33 |
34 |
35 | (model as IDynamicFormObject)[child.name] = v"
45 | :disabled="disabled || evaluateCallback(item.disabled)"
46 | />
47 |
48 |
49 |
50 |
51 |
52 | [group-object] Warn: Input field {{ name }} is undefined
53 |
54 |
55 |
56 | (model as IDynamicFormObject)[child.name] = v"
67 | :disabled="disabled || evaluateCallback(item.disabled)"
68 | />
69 |
70 |
71 |
72 |
73 |
74 |
75 | (parentModel as IDynamicFormObject)[child.name] = v"
86 | :disabled="disabled || evaluateCallback(item.disabled)"
87 | >
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | (parentModel as IDynamicFormObject)[child.name] = v"
108 | :disabled="disabled || evaluateCallback(item.disabled)"
109 | >
110 |
111 |
112 |
113 |
114 |
115 |
116 |
120 |
124 |
129 |
130 | (parentModel as IDynamicFormObject)[formRow.name] = v"
141 | :disabled="disabled || evaluateCallback(formRow.disabled)"
142 | >
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
160 |
161 |
162 |
163 | (parentModel as IDynamicFormObject)[child.name] = v"
174 | :disabled="disabled || evaluateCallback(item.disabled)"
175 | >
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
193 |
194 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | onUpdateValue(v)"
220 | />
221 |
222 |
223 |
224 |
225 |
226 |
235 |
236 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 | onUpdateValue(v)"
262 | />
263 |
264 |
265 |
266 |
267 |
268 | $emit('update:model', v)"
277 | >
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseCheck.ts:
--------------------------------------------------------------------------------
1 | export interface BaseCheckProps {
2 | text: string;
3 | }
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseCheck.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | onValueUpdate((e.target as HTMLInputElement).checked)"
10 | @blur="onBlur"
11 | />
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseDivider.ts:
--------------------------------------------------------------------------------
1 | import { StyleHTMLAttributes } from "vue";
2 |
3 | export interface BaseDividerProps {
4 | style?: StyleHTMLAttributes;
5 | }
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseDivider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseInput.ts:
--------------------------------------------------------------------------------
1 | export interface BaseInputProps {
2 | placeholder?: string;
3 | password?:boolean;
4 | }
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseInput.vue:
--------------------------------------------------------------------------------
1 |
2 | onValueUpdate(e)"
9 | @change="(e: Event) => onValueUpdate((e.target as HTMLInputElement).value)"
10 | />
11 |
12 |
13 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseRadio.ts:
--------------------------------------------------------------------------------
1 | export interface BaseRadioProps {
2 | /**
3 | * 是否禁用
4 | */
5 | disabled: boolean;
6 | /**
7 | * 选项数据
8 | */
9 | items: {
10 | label: string,
11 | value: string|number,
12 | }[];
13 | /**
14 | * 选择值
15 | */
16 | value: unknown;
17 | }
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseRadio.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseSelect.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface IDynamicFormItemSelectOption {
3 | text: string,
4 | value: string|number,
5 | }
6 |
7 | export interface BaseSelectProps {
8 | /**
9 | * 是否禁用
10 | */
11 | disabled: boolean;
12 | /**
13 | * 选项数据
14 | */
15 | options: IDynamicFormItemSelectOption[];
16 | /**
17 | * 选择值
18 | */
19 | value: unknown;
20 | }
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseSelect.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseTextArea.ts:
--------------------------------------------------------------------------------
1 | export interface BaseTextAreaProps {
2 | cols?: number,
3 | rows?: number,
4 | maxlength?: number,
5 | }
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/BaseTextArea.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/FormArrayGroup.ts:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 动态表单类型是 arrag-object 时的 additionalProps
4 | */
5 | export interface FormArrayGroupProps {
6 | /**
7 | * 是否显示添加按钮,默认是
8 | */
9 | showAddButton?: boolean,
10 | /**
11 | * 是否显示删除按钮,默认是
12 | */
13 | showDeleteButton?: boolean,
14 | /**
15 | * 是否显示上移下移按钮,默认是
16 | */
17 | showUpDownButton?: boolean,
18 | /**
19 | * 删除按钮回调,可选,不提供时默认操作为将 item 从 array 中移除。
20 | */
21 | deleteCallback?: (array: unknown[], item: unknown) => void,
22 | /**
23 | * 添加按钮回调,必填,否则用户无法添加数据
24 | */
25 | addCallback: (array: unknown[]) => void,
26 | }
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/FormArrayGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
72 |
73 |
74 |
156 |
157 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/FormArrayGroupItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
15 |
16 |
23 |
24 |
25 |
30 |
33 |
36 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/FormCustomLayout.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/FormGroup.ts:
--------------------------------------------------------------------------------
1 | export interface FormGroupProps {
2 | /**
3 | * 标题
4 | */
5 | title: string
6 | /**
7 | * 栅格间隔,可以写成像素值或支持响应式的对象写法来设置水平间隔 { xs: 8, sm: 16, md: 24}。或者使用数组形式同时设置 [水平间距, 垂直间距]
8 | */
9 | gutter: unknown,
10 | /**
11 | * flex 布局下的水平排列方式:
12 | */
13 | justify: 'start'|'end'|'center'|'space-around'|'space-between';
14 | }
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/FormGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
41 |
42 |
--------------------------------------------------------------------------------
/library/DynamicFormItemControls/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BaseRadio';
2 | export * from './BaseSelect';
3 | export * from './BaseCheck';
4 | export * from './BaseTextArea';
5 | export * from './BaseInput';
6 | export * from './BaseDivider';
7 | export * from './FormArrayGroup';
8 | export * from './FormGroup';
--------------------------------------------------------------------------------
/library/DynamicFormItemNormal.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/DynamicFormItemRenderer/DynamicFormItemRegistry.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface DynamicFormItemRegistryItem {
3 | componentInstance: unknown;
4 | valueName: string;
5 | additionalProps: Record;
6 | }
7 |
8 | export function makeWidget(componentInstance: unknown, additionalProps?: Record, valueName?: string) : DynamicFormItemRegistryItem {
9 | return { componentInstance, additionalProps: additionalProps ?? {}, valueName: valueName || 'valueName' };
10 | }
11 |
12 | const DynamicFormItemRegistryData = new Map();
13 |
14 | /**
15 | * 动态表单组件注册器。
16 | *
17 | * 您可以在这里调用 register 注册自定义表单控件
18 | */
19 | export const DynamicFormItemRegistry = {
20 | /**
21 | * 查找已注册的表单组件,如果未找到,则返回 null
22 | * @param type 唯一类型名称
23 | */
24 | findDynamicFormItemByType(type: string) : DynamicFormItemRegistryItem|null {
25 | return DynamicFormItemRegistryData.get(type) || null;
26 | },
27 | /**
28 | * 注册自定义表单控件
29 | * @param type 唯一类型名称
30 | * @param componentInstance 组件类
31 | * @param additionalProps 组件的附加属性,将会设置到渲染函数上
32 | * @param valueName 用于指定表单子组件的双向绑定值属性名称,默认是 value
33 | */
34 | register(type: string, componentInstance: unknown, additionalProps: Record = {}, valueName = 'value') {
35 | if (DynamicFormItemRegistryData.has(type)) {
36 | console.warn('[DynamicFormItemRegistry] Type ' + type + ' already exists and cannot be registered twice.');
37 | return this;
38 | }
39 | DynamicFormItemRegistryData.set(type, { componentInstance, additionalProps, valueName });
40 | return this;
41 | },
42 | /**
43 | * 取消注册自定义表单控件
44 | * @param type 唯一类型名称
45 | */
46 | unregister(type: string) {
47 | if (!DynamicFormItemRegistryData.has(type)) {
48 | console.warn('[DynamicFormItemRegistry] Can not unregister nonexistent type ' + type + ' .');
49 | return this;
50 | }
51 | DynamicFormItemRegistryData.delete(type);
52 | return this;
53 | },
54 | /**
55 | * 清空所有已注册的自定义表单控件
56 | */
57 | clearAll() {
58 | DynamicFormItemRegistryData.clear();
59 | return this;
60 | }
61 | };
--------------------------------------------------------------------------------
/library/DynamicFormItemRenderer/DynamicFormItemRenderer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/library/DynamicFormTab/DynamicFormTab.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/DynamicFormTab/DynamicFormTabPage.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/Scss/BaseControl.scss:
--------------------------------------------------------------------------------
1 | input[type="text"].dynamic-form-base-control,
2 | input[type="password"].dynamic-form-base-control,
3 | textarea.dynamic-form-base-control,
4 | select.dynamic-form-base-control {
5 | max-width: 100%;
6 | padding: 1px 11px;
7 | background-color: var(--dynamic-form-base-color);
8 | background-image: none;
9 | border-radius: 5px;
10 | border: 1px solid var(--dynamic-form-border-color);
11 | font-size: var(--dynamic-form-font-size);
12 | line-height: 30px;
13 | height: 30px;
14 | outline: none;
15 |
16 | &::-webkit-input-placeholder {
17 | color: var(--dynamic-form-placeholder-color);
18 | }
19 |
20 | &:disabled {
21 | opacity: 0.8;
22 | color: var(--dynamic-form-secondary-color);
23 | }
24 |
25 | &:hover, &:active, &:focus {
26 | border-color: var(--dynamic-form-primary-color);
27 | }
28 | }
29 |
30 | .dynamic-form-base-control.radio {
31 | display: flex;
32 | flex-direction: row;
33 |
34 | .item {
35 | display: flex;
36 | flex-direction: row;
37 | align-items: center;
38 | }
39 |
40 | input {
41 | position: relative;
42 | appearance: none;
43 | width: 22px;
44 | height: 22px;
45 | border-radius: 22px;
46 | background-color: var(--dynamic-form-base-color);
47 | border: 1px solid var(--dynamic-form-border-color);
48 | margin: 0;
49 | margin-right: 5px;
50 |
51 | &:after {
52 | position: absolute;
53 | display: inline-block;
54 | top: 3px;
55 | left: 3px;
56 | width: 14px;
57 | height: 14px;
58 | content: "";
59 | border: none;
60 | text-align: center;
61 | border-radius: 14px;
62 | }
63 |
64 | &:checked {
65 | &:after {
66 | background-color: var(--dynamic-form-primary-color);
67 | }
68 | }
69 |
70 | }
71 | }
72 |
73 | textarea.dynamic-form-base-control {
74 | height: unset;
75 | }
76 |
77 | .dynamic-form-base-control.base-button {
78 | position: relative;
79 | display: inline-flex;
80 | justify-content: center;
81 | align-items: center;
82 | text-align: center;
83 | position: relative;
84 | flex-shrink: 0;
85 | user-select: none;
86 | width: auto;
87 | white-space: nowrap;
88 | transition: background-color .1s,transform .1s,color .35s cubic-bezier(.65,0,.25,1);
89 | border: none;
90 | cursor: pointer;
91 | border-radius: 5px;
92 | box-sizing: border-box;
93 | overflow: hidden;
94 | min-width: 60px;
95 | height: 30px;
96 | padding: 0 14px;
97 | font-size: var(--dynamic-form-font-size);
98 |
99 | background: #f0f0f0;
100 | color: #333;
101 |
102 | &:hover {
103 | background-color: #ebebeb;
104 | }
105 | &:active {
106 | background-color: #d8d8d8;
107 | transform: scale(0.9);
108 | }
109 | }
110 | .dynamic-form-base-control.base-checkbox {
111 | vertical-align: middle;
112 | display: inline-flex;
113 | align-items: center;
114 | justify-content: center;
115 | position: relative;
116 | background: transparent;
117 | appearance: none;
118 | height: 22px;
119 | width: 22px;
120 | overflow: hidden;
121 | background-color: var(--dynamic-form-base-color);
122 | background-image: none;
123 | border-radius: 5px;
124 | border: 1px solid var(--dynamic-form-border-color);
125 | color: var(--dynamic-form-primary-color);
126 | margin: 0;
127 | margin-right: 10px;
128 |
129 | &:after {
130 | display: inline-block;
131 | width: 22px;
132 | line-height: 22px;
133 | top: 0px;
134 | content: "";
135 | color: var(--dynamic-form-primary-color);
136 | border: none;
137 | text-align: center;
138 | font-size: 12px;
139 | }
140 |
141 | &:checked:after {
142 | content: "✔️";
143 | color: var(--dynamic-form-primary-color);
144 | }
145 | }
146 |
147 | .dynamic-form-row {
148 | display: flex;
149 | flex-direction: row;
150 | flex: 1;
151 | }
152 | .dynamic-form-col {
153 | display: flex;
154 | justify-content: flex-start;
155 | flex-direction: column;
156 | flex-grow: 0;
157 | flex-shrink: 0;
158 | }
--------------------------------------------------------------------------------
/library/Scss/Color.scss:
--------------------------------------------------------------------------------
1 | :root {
2 | --dynamic-form-font-size: 14px;
3 | --dynamic-form-height: 32px;
4 | --dynamic-form-space: 18px;
5 | --dynamic-form-primary-color: #1a91f3;
6 | --dynamic-form-error-color: #f55;
7 | --dynamic-form-secondary-color: #666;
8 | --dynamic-form-placeholder-color: #bbbbbb;
9 | --dynamic-form-border-color: #dcdfe6;
10 | --dynamic-form-base-color: #fff;
11 | }
--------------------------------------------------------------------------------
/library/Scss/Form.scss:
--------------------------------------------------------------------------------
1 |
2 | .dynamic-form-control {
3 | position: relative;
4 | }
5 | .dynamic-form-error-alert {
6 | color: var(--dynamic-form-error-color);
7 | }
8 | .dynamic-form-object-title {
9 | color: var(--dynamic-form-secondary-color);
10 | height: var(--dynamic-form-height);
11 | line-height: var(--dynamic-form-height);
12 | }
13 | .dynamic-form-control-item {
14 | padding: 2px 6px;
15 | margin-bottom: var(--dynamic-form-space);
16 |
17 | .dynamic-form-control-item {
18 | padding-left: 0;
19 | padding-right: 0;
20 | }
21 |
22 | .error-message {
23 | color: var(--dynamic-form-error-color);
24 | }
25 |
26 | &.no-bottom-margin {
27 | margin-bottom: 0;
28 | }
29 |
30 | label {
31 | color: var(--dynamic-form-secondary-color);
32 | height: var(--dynamic-form-height);
33 | line-height: var(--dynamic-form-height);
34 | padding: 0 12px 0 0;
35 |
36 | .colon {
37 | color: var(--dynamic-form-secondary-color);
38 | margin-left: 3px;
39 | margin-right: 5px;
40 | }
41 | .required {
42 | color: var(--dynamic-form-error-color);
43 | margin-right: 10px;
44 | }
45 | }
46 | .label-container {
47 | flex-shrink: 0;
48 | }
49 | .wrapper-container {
50 | flex-grow: 1;
51 | }
52 | }
53 | .dynamic-form-wrapper {
54 | position: relative;
55 | }
56 | .dynamic-form-item-wrapper {
57 | &.nest-with-margin > .dynamic-form-item-wrapper {
58 | margin-left: 20px;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/library/main.ts:
--------------------------------------------------------------------------------
1 | export * from './DynamicForm'
2 | export * from './DynamicFormBasicControls'
3 | export * from './DynamicFormItemControls'
4 | export * from './DynamicFormItemRenderer/DynamicFormItemRegistry'
5 | import DynamicForm from './DynamicForm.vue'
6 |
7 | export { DynamicForm };
--------------------------------------------------------------------------------
/library/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "env.d.ts",
4 | "./**/*",
5 | "./**/*.tsx",
6 | ],
7 | "exclude": ["./**/__tests__/*"],
8 | "compilerOptions": {
9 | "target": "esnext",
10 | "module": "esnext",
11 | "jsx": "preserve",
12 | "importHelpers": true,
13 | "strict": true,
14 | "moduleResolution": "node",
15 | "skipLibCheck": true,
16 | "esModuleInterop": true,
17 | "allowSyntheticDefaultImports": true,
18 | "sourceMap": true,
19 | "allowJs": false,
20 | "noImplicitAny": true,
21 | "noImplicitThis": true,
22 | "strictFunctionTypes": false,
23 | "composite": true,
24 | "baseUrl": ".",
25 | "isolatedModules": false,
26 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/library/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 | import { defineConfig } from 'vite'
3 | import vue from '@vitejs/plugin-vue'
4 | import vueJsx from '@vitejs/plugin-vue-jsx'
5 | import dts from 'vite-plugin-dts'
6 |
7 | export default defineConfig({
8 | plugins: [
9 | vue(),
10 | vueJsx(),
11 | dts(),
12 | ],
13 | resolve: {
14 | alias: {
15 | '@': fileURLToPath(new URL('./', import.meta.url))
16 | }
17 | },
18 | build: {
19 | lib: {
20 | entry: 'main.ts',
21 | name: 'vue-dynamic-form',
22 | fileName: (format) => `vue-dynamic-form.${format}.js`,
23 | },
24 | outDir: '../dist',
25 | sourcemap: true,
26 | minify: false,
27 | rollupOptions: {
28 | external: ['vue'],
29 | output: {
30 | globals: {
31 | vue: 'Vue',
32 | },
33 | },
34 | },
35 | },
36 | })
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "deprecated": false,
3 | "license": "MIT",
4 | "main": "dist/vue-dynamic-form.umd.js",
5 | "types": "dist/main.d.ts",
6 | "name": "@imengyu/vue-dynamic-form",
7 | "version": "0.1.2",
8 | "description": "A data driven form component for vue3",
9 | "files": [
10 | "dist"
11 | ],
12 | "publishConfig": {
13 | "access": "public",
14 | "registry": "https://registry.npmjs.org"
15 | },
16 | "keywords": [
17 | "vue",
18 | "form",
19 | "dynamic-form"
20 | ],
21 | "homepage": "https://github.com/imengyu/vue-dynamic-form",
22 | "author": "imengyu",
23 | "scripts": {
24 | "dev": "vite",
25 | "test:unit": "vitest --environment jsdom --root src/",
26 | "build": "vite build library",
27 | "docs:dev": "vitepress dev docs",
28 | "docs:build": "vitepress build docs",
29 | "docs:serve": "vitepress serve docs"
30 | },
31 | "dependencies": {
32 | "async-validator": "^4.2.5",
33 | "scroll-into-view-if-needed": "^3.0.3",
34 | "vue": "^3.2.45"
35 | },
36 | "devDependencies": {
37 | "@arco-design/web-vue": "^2.41.0",
38 | "@codemirror/lang-json": "^6.0.1",
39 | "@rushstack/eslint-patch": "^1.1.4",
40 | "@types/jsdom": "^20.0.1",
41 | "@types/node": "^18.11.12",
42 | "@vitejs/plugin-vue": "^4.0.0",
43 | "@vitejs/plugin-vue-jsx": "^3.0.0",
44 | "@vue/eslint-config-typescript": "^11.0.0",
45 | "@vue/test-utils": "^2.2.6",
46 | "@vue/tsconfig": "^0.1.3",
47 | "ant-design-vue": "^4.2.1",
48 | "codemirror": "^6.0.1",
49 | "element-plus": "^2.7.2",
50 | "eslint": "^8.22.0",
51 | "eslint-plugin-vue": "^9.3.0",
52 | "highlight.js": "^11.9.0",
53 | "jsdom": "^20.0.3",
54 | "npm-run-all": "^4.1.5",
55 | "sass": "^1.57.1",
56 | "typescript": "~4.7.4",
57 | "vite": "^4.0.0",
58 | "vite-plugin-dts": "^3.9.0",
59 | "vite-plugin-markdown-preview": "^1.1.1",
60 | "vitepress": "^1.1.4",
61 | "vitest": "^0.25.6",
62 | "vue-codemirror": "^6.1.1",
63 | "vue-router": "^4.1.6",
64 | "vue-tsc": "^1.0.12"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imengyu/vue-dynamic-form/67c458aaab05acf2e99d19512893f92b1d373148/public/favicon.ico
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "library/env.d.ts",
4 | "library/**/*",
5 | "library/**/*.tsx",
6 | "docs/**/*.vue",
7 | "docs/**/*.tsx",
8 | "docs/**/*"
9 | ],
10 | "exclude": ["library/**/__tests__/*"],
11 | "compilerOptions": {
12 | "target": "esnext",
13 | "module": "esnext",
14 | "jsx": "preserve",
15 | "importHelpers": true,
16 | "strict": true,
17 | "moduleResolution": "node",
18 | "skipLibCheck": true,
19 | "esModuleInterop": true,
20 | "allowSyntheticDefaultImports": true,
21 | "sourceMap": true,
22 | "allowJs": false,
23 | "noImplicitAny": true,
24 | "noImplicitThis": true,
25 | "strictFunctionTypes": false,
26 | "composite": true,
27 | "baseUrl": ".",
28 | "isolatedModules": false,
29 | "outDir": "./temp",
30 | "paths": {
31 | "@/*": ["./library/*"],
32 | "vue-dynamic-form": [ "./library/main" ],
33 | "@imengyu/vue-dynamic-form": [ "./library/main" ]
34 | },
35 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
36 | }
37 | }
38 |
--------------------------------------------------------------------------------