This is an automated email from the ASF dual-hosted git repository. songjian pushed a commit to branch add_canvas_job_define in repository https://gitbox.apache.org/repos/asf/incubator-seatunnel-web.git
The following commit(s) were added to refs/heads/add_canvas_job_define by this push: new dfa56fde [Feat][UI] Add component of dynamic form in this project. dfa56fde is described below commit dfa56fde1e5040ab15465d58a0d7c42439c8fbf0 Author: songjianet <1778651...@qq.com> AuthorDate: Fri May 5 20:20:05 2023 +0900 [Feat][UI] Add component of dynamic form in this project. --- .../components/dynamic-form/dynamic-form-item.tsx | 165 +++++++++++++++++++++ .../src/components/dynamic-form/use-form-field.ts | 32 ++++ .../components/dynamic-form/use-form-locales.ts | 26 ++++ .../components/dynamic-form/use-form-request.ts | 39 +++++ .../components/dynamic-form/use-form-structure.ts | 25 ++++ .../components/dynamic-form/use-form-validate.ts | 69 +++++++++ 6 files changed, 356 insertions(+) diff --git a/seatunnel-ui/src/components/dynamic-form/dynamic-form-item.tsx b/seatunnel-ui/src/components/dynamic-form/dynamic-form-item.tsx new file mode 100644 index 00000000..e29727fb --- /dev/null +++ b/seatunnel-ui/src/components/dynamic-form/dynamic-form-item.tsx @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { defineComponent } from 'vue' +import { + NFormItemGi, + NGrid, + NInput, + NSelect, + NCheckboxGroup, + NSpace, + NCheckbox +} from 'naive-ui' +import { useI18n } from 'vue-i18n' +import type { PropType } from 'vue' +import type { SelectOption } from 'naive-ui' + +const props = { + formStructure: { + type: Object as PropType<Array<object>> + }, + model: { + type: Object as PropType<object> + }, + name: { + type: String as PropType<string>, + default: '' + }, + locales: { + type: Object as PropType<object> + } +} + +const DynamicFormItem = defineComponent({ + name: 'DynamicFormItem', + props, + setup(props) { + const { t } = useI18n() + + if (props.locales) { + useI18n().mergeLocaleMessage('zh_CN', { + i18n: (props.locales as any).zh_CN + }) + useI18n().mergeLocaleMessage('en_US', { + i18n: (props.locales as any).en_US + }) + } + + const formatClass = (name: string, modelField: string) => { + return name.indexOf('[') >= 0 + ? name.split('[')[0].toLowerCase() + name.split('[')[1].split(']')[0] + : name.toLowerCase() + '-' + modelField.toLowerCase() + } + + const formItemDisabled = (field: string, value: Array<any>) => { + return value.map((v) => field === v).indexOf(false) < 0 + } + + return { + t, + formatClass, + formItemDisabled + } + }, + render() { + return ( + <NGrid xGap={10}> + {(this.formStructure as Array<any>).map((f) => { + return ( + (f.show + ? this.formItemDisabled( + (this.model as any)[f.show.field], + f.show.value + ) + : true) && ( + <NFormItemGi + label={this.t(f.label)} + path={f.field} + span={f.span || 24} + > + {f.type === 'input' && ( + <NInput + class={`dynamic-form_${this.formatClass( + this.name, + f.field + )}`} + placeholder={f.placeholder ? this.t(f.placeholder) : ''} + v-model={[(this.model as any)[f.field], 'value']} + clearable={f.clearable} + type={f.inputType} + rows={f.row ? f.row : 4} + /> + )} + {f.type === 'select' && + (f.show + ? this.formItemDisabled( + (this.model as any)[f.show.field], + f.show.value + ) + : true) && ( + <NSelect + class={`dynamic-form_${this.formatClass( + this.name, + f.field + )}`} + placeholder={f.placeholder ? this.t(f.placeholder) : ''} + v-model={[(this.model as any)[f.field], 'value']} + options={ + f.options.map((o: SelectOption) => { + return { + label: this.t(o.label as string), + value: o.value + } + }) + } + /> + )} + {f.type === 'checkbox' && + (f.show + ? this.formItemDisabled( + (this.model as any)[f.show.field], + f.show.value + ) + : true) && ( + <NCheckboxGroup + class={`dynamic-form_${this.formatClass( + this.name, + f.field + )}`} + v-model={[(this.model as any)[f.field], 'value']} + > + <NSpace vertical={f.vertical}> + {f.options.map((o: any) => ( + <NCheckbox + label={this.t(o.label as string)} + value={o.value} + /> + ))} + </NSpace> + </NCheckboxGroup> + )} + </NFormItemGi> + ) + ) + })} + </NGrid> + ) + } +}) + +export { DynamicFormItem } diff --git a/seatunnel-ui/src/components/dynamic-form/use-form-field.ts b/seatunnel-ui/src/components/dynamic-form/use-form-field.ts new file mode 100644 index 00000000..3d6a0e97 --- /dev/null +++ b/seatunnel-ui/src/components/dynamic-form/use-form-field.ts @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function useFormField(forms: Array<any>) { + const model: any = {} + + const setField = (value: string, type: string): null | string => { + return value ? value : type === 'select' ? null : '' + } + + forms.forEach((f: any) => { + if (!f.field) return + + model[f.field] = setField(f.defaultValue, f.type) + }) + + return model +} diff --git a/seatunnel-ui/src/components/dynamic-form/use-form-locales.ts b/seatunnel-ui/src/components/dynamic-form/use-form-locales.ts new file mode 100644 index 00000000..90d8c7c0 --- /dev/null +++ b/seatunnel-ui/src/components/dynamic-form/use-form-locales.ts @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useI18n } from 'vue-i18n' + +export function useFormLocales(locales: { + zh_CN: object + en_US: object +}): void { + useI18n().mergeLocaleMessage('zh_CN', { i18n: locales.zh_CN }) + useI18n().mergeLocaleMessage('en_US', { i18n: locales.en_US }) +} diff --git a/seatunnel-ui/src/components/dynamic-form/use-form-request.ts b/seatunnel-ui/src/components/dynamic-form/use-form-request.ts new file mode 100644 index 00000000..6ba882e9 --- /dev/null +++ b/seatunnel-ui/src/components/dynamic-form/use-form-request.ts @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { axios } from '@/service/service' + +const reqFunction = (url: string, method: string) => { + return axios({ + url, + method + } as any) +} + +export function useFormRequest(apis: any, forms: Array<any>): Array<any> { + forms.map(f => { + if (f.api) { + reqFunction(apis[f.api].url, apis[f.api].method).then((res: any) => { + f.options = res.map((r: any) => { + return { label: r.label, value: r.value } + }) + }) + } + }) + + return forms +} \ No newline at end of file diff --git a/seatunnel-ui/src/components/dynamic-form/use-form-structure.ts b/seatunnel-ui/src/components/dynamic-form/use-form-structure.ts new file mode 100644 index 00000000..34166ac6 --- /dev/null +++ b/seatunnel-ui/src/components/dynamic-form/use-form-structure.ts @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function useFormStructure(forms: Array<any>) { + return forms.map((f: any) => { + delete f.validate + delete f.api + + return f + }) +} \ No newline at end of file diff --git a/seatunnel-ui/src/components/dynamic-form/use-form-validate.ts b/seatunnel-ui/src/components/dynamic-form/use-form-validate.ts new file mode 100644 index 00000000..86be15ae --- /dev/null +++ b/seatunnel-ui/src/components/dynamic-form/use-form-validate.ts @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { FormItemRule } from 'naive-ui' + +export function useFormValidate(forms: Array<any>, model: any, t: any) { + const validate: any = {} + + const setValidate = (validate: any, field: string): object => { + const data: any = { + required: validate.required, + trigger: validate.trigger + } + + if (validate.type === 'non-empty') { + data['validator'] = (rule: FormItemRule, value: string) => { + if (!model[field]) { + return Error(t(validate.message)) + } + } + } else if (validate.type === 'union-non-empty') { + const fields = validate.fields.splice( + validate.fields.findIndex((f: any) => f !== field), + 1 + ) + data['validator'] = (rule: FormItemRule, value: string) => { + const fieldsValidate = fields.map((f: string) => !!model[f]) + if (!value && fieldsValidate.includes(true)) { + return Error(t(validate.message)) + } + } + } else if (validate.type === 'mutually-exclusive') { + const fields = validate.fields.splice( + validate.fields.findIndex((f: any) => f !== field), + 1 + ) + data['validator'] = (rule: FormItemRule, value: string) => { + const fieldsValidate = fields.map((f: string) => !!model[f]) + if (value && fieldsValidate.indexOf(false) < 0) { + return Error(t(validate.message)) + } + } + } + + return data + } + + forms.forEach((f: any) => { + if (!f.validate || Object.keys(f.validate).length <= 0) return + + validate[f.field] = setValidate(f.validate, f.field) + }) + + return validate +}