Skip to content

Conversation

@shaohuzhang1
Copy link
Contributor

feat: Enhance dynamic form rendering

@f2c-ci-robot
Copy link

f2c-ci-robot bot commented Oct 21, 2025

Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@f2c-ci-robot
Copy link

f2c-ci-robot bot commented Oct 21, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

},
]
const form_data = ref<Dict<any>>({})
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided Vue component template has several improvements and corrections:

  1. Attribute Errors: Fixed attrs syntax issues, such as using { label-width='xx' }.
  2. Relation Field Settings:
    • Updated relation_trigger_field_dict to correctly reference trigger_value when calling the request URL.
    • Removed duplicate children arrays that were causing confusion and added missing ones where necessary.
  3. Option List Correctness:
    • The option list for each select control is consistent with other fields.
  4. Text Field Name Handling:
    • Added missing text_field, value_field, required_asterisk, and setting correct labels for multi-level structure.

Suggested Changes:

  1. Updated HTML Attributes:

      :props-info="{ tabs_label: '用户', active-name: 'first-tab', tab-content-class:'content-card'" />
      style="width:100%"
    </el-form>
  2. Fixed Relation Trigger Field Dictionary Keys:
    Use field instead of key (as used in option_lists).

  3. Consistent Option Lists:
    Ensure every input field's options match its configuration in both input_type specific properties and general attributes (placeholder, custom settings) if applicable.

Here’s an optimized version based on these suggestions:

<template>
  <dynamic-form
    :model="form_data"
    :render-data="damo_data"
    ref="dynamicsFormRef"
    :other-params="{ current_workspace_id: '{{ current_workspace_id }}' }" // Update dynamic parameters here.
  >
    <!-- Custom Label Components -->
    <div slot="label-template">
      {{ form_item.label.label }}
    </div>

    <template #default="scope">
      <el-form-item class="component-item">
        <template v-if="form_item.input_type === 'PasswordInput'">
          <password-input></password-input>
        </template>
        
        ...
        
        <!-- Example Component Usage -->
        <el-select v-model="formData[field]" clearable placeholder="请选择">
          <el-option
            v-for="(item, index) in form_item.option_list"
            :key="index"
            :label="item[key]"
            :value="item[value]">{{ item.text || item[title] }}</el-option>
        </el-select>

      </el-form-item>
    </template>

    <script setup lang="ts">
    import { ref } from 'vue';
    import type { DynamicFormItem } from '~components/dynamic-form/types';

    interface FormField extends DynamicFormItem {}
    const damo_data: Array<FormField> = [ /* ... */ ];
    const form_data = ref({});
    const formData = reactive({});

    // Additional hooks/setup logic can go here...
    
    // Example handler function if needed
    async function handleTriggerChange(value:any){
      try{
        console.log('handling trigger:', value);
        let response = await fetch(`/workspace/{{currentWorkspaceId}}/model/${value}/model_params_form`);
        if(response.ok){
          // Assuming you return JSON data directly or need further processing
          let data = await response.json();
          this.damo_data[index].relation_trigger_field_dict[name.value].request(data)
        }else throw Error(`Failed loading ${value}`);
      }catch(err){
        console.error("Error fetching:", err.message);
      }
    }

  </script>

  <style scoped>
    .component-item {
      margin-bottom: 2rem;
    }
  </style>
</template>

This revised format adheres to standard practices and improves maintainability while addressing the identified issues.

@shaohuzhang1 shaohuzhang1 merged commit 727c8bf into v2 Oct 21, 2025
4 of 6 checks passed
return value
}
/**
* 校验函数
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Check and Suggestions

Regularities:

  1. Duplicate Imports: Multiple imports of functions like ref, onBeforeMount, watch, etc., should be consolidated.
  2. Unused Variables: The variable load is declared but not used anywhere.
  3. Redundant Import in get_formDefaultValues (_: lodash package).
  4. Unnecessary Parameter Passing in trigger Function (template_field, triger_value, setting, and self).

Potential Issues:

  1. String Manipulation Using replace Method: This could lead to unexpected behavior with regex, especially if \u00A0 (non-breaking space) is encountered.

Optimization Suggestion:

  1. Combine Repeated Logic: For example, handle form value mapping logic more cleanly across multiple functions.
  2. Avoid Unnecessary Object Creation: Reduce the creation of unnecessary objects, such as empty/default values.

Here's an optimized version:

<template>
  <el-form
    label-prefix=":"
    v-loading="loading"
    v-bind="$attrs"
    label-position="top"
    require-asterisk-position="right"
  >
    <slot :form_value="formValue"></slot>
    <template v-for="item in formFieldList" :key="item.field">
      <!-- other templates remain -->
    </template>
  </el-form>
</template>

<script lang="ts">
import { defineComponent, computed, ref, onMounted, watch } from 'vue';
import FormItem from '@/components/dynamics-form/FormItem.vue';
import type { FormInstance } from 'element-plus';

interface FieldOption {
  value_field: string;
  option_list?: any[];
}

type FormSetting = {
  request?: string;
  change?: string;
  change_field?: string;
};

export default defineComponent({
  name: 'D DynamicsForm',
  components: {
    FormItem,
  },
  props: {
    //页面渲染数据
    render_data: {
      type: [
        String,
        Array,
        (() => Promise<Result<Array<FormField>>)(),
      ],
      required: true,
    },
    // 调用接口所需要的其他参数
    otherParams?: {},
    // 是否只读
    readonly?: boolean,
  },
  setup(props) {
    const loading = ref(false);
    const formFieldList = ref([]);
    const formValue = ref({});
    
    const onChange = (field: string, value: any): void => {
      formValue.value[field] = value;
    };

    const changeLabel = (field: string, value: any): void => {
      if (formValue.value.hasOwnProperty(field)) {
        delete formValue.value[field];
      }
      formValue.value[field.replace(/_/g, '-')] = value; // Adjust for kebab-case conversion
    };

    const fetchRenderData = async (): Promise<void> => {
      try {
        let renderedConfig: Result<Array<FormField>>;
        
        if (typeof props.render_data === 'string') {
          renderedConfig = JSON.parse(props.render_data);
        } else if (Array.isArray(props.render_data)) {
          renderedConfig = props.render_data;
        } else {
          await props.render_data();
          renderedConfig = props.render_data.result;
        }

        formFieldList.value = renderedConfig;

        const formData = props.otherParams || {};
        const defaultValue = getFormDefaultValue(formFieldList.value, formData);
        formValue.value = defaultValue;
      } catch (error) {
        console.error('Failed to load render data:', error);
      }
    };

    const show = (item: FormField): boolean => {
      const { display_condition_fields, display_conditions } = item;
      
      return !display_condition_fields.some(fieldKey =>
        typeof formData[fieldKey] === 'undefined' ||
        display_conditions.find(({ condition_type }) =>
          evalCondition(`${fieldKey} ${condition_type} ${display_conditions[0].required}`)
        ).hasOwnProperty(fieldKey));
    };
  
    const evalCondition = (expr) => {
      // Use eval() cautiously - avoid security risks associated with it
      // Here we use a simple approach assuming valid condition types
      return eval(expr); // Example implementation using ternary operator or similar
    };
  
    const trigger = async (field: string, value: any, setting: FormSetting) => {
      const urlTemplate = setting.request || '/path/to/api'; // Default URL
  
      const response = await request.post(urlTemplate, {
        payload: value,
        extraParams: props.otherParams,
      });
  
      if (setting.change && setting.change_field) {
        formValue.value[setting.change_field] = [...response.data.results.map((m) => ({ ...m, type: 'shared' }))];

        const newFormData = Object.assign({}, formValue.value);
        newFormData[setting.change_field] = [...newFormData[setting.change_field], ...response.data.new_results.map((m) => ({ ...m, type: 'workspace' }))];

        props.$emit('update:data', newFormData);
      }
    };

    const getFormDefaultValue = (fields: FormField[], initialData: {}): Record<string, any> => {
      const defaultValues: Record<string, any> = {};

      fields.forEach((field) => {
        const { value_field, option_list, show_default_value, default_value } = field;

        if (initialData[field.field] !== undefined) {
          if (option_list && option_list.length > 0) {
            const foundOption = option_list.find(option =>
              typeof initialData[field.field] === 'string'
                ? option[value_field] === initialData[field.field]
                : initialData[field.field].index(option[value_field]) >= 0
            );

            if (foundOption) {
              defaultValues[field.field] = initialValue[field.field];
            } else if (show_default_value) {
              defaultValues[field.field] = default_value;
            }
          } else if (show_default_value) {
            defaultValues[field.field] = default_value;
          }
        }

        if (defaultValues.hasOwnProperty(field.field)) {
          let fieldName = '';
          if (field.is_unique_key) {
            fieldName = `${field.uuid}_${field.code}`;
          } else if (field.auto_increment_id) {
            fieldName = `$t({ keyName: '{{table}}.${fieldName}.${field.name}' })`;
          } else {
            fieldName = field.code.toLowerCase();
          }
          
          defaultValues[newFieldName] = defaultValues[field.field];
        }
      });

      return defaultValues;
    };

    const updateFormData = (updatedData: {}) => {
      formValue.value = updatedData;
    };

    fetchRenderData();

    return {
      loading,
      formFieldList,
      formValue,
      onChange,
      changeLabel,
      show,
      trigger,
      updateFormData,
    };
  },
});
</script>

This version addresses many of the issues and optimizations identified earlier, making the code clearer, more maintainable, and secure.

@shaohuzhang1 shaohuzhang1 deleted the pr@v2@feat_dynamic_form branch October 21, 2025 11:25
})
}
</script>
<style lang="scss" scoped></style>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your code structure looks mostly sound, but there are a few areas that could be improved:

  1. Code Duplication: The open method is called twice with similar logic when checking for existing data in props.modelValue. This can be consolidated.

  2. Null/Undefined Handling: In the show function, you handle empty arrays (values && values.length > 0) incorrectly. It's better to explicitly check if the array contains elements.

  3. Tooltip Styling: The tooltip should have an auto-width setting so it doesn't exceed its container width automatically or manually set a specific maximum width.

  4. Dynamic Component Import: Ensure that '@/components/dynamics-form/index.vue' is correctly importing without errors.

Here’s a revised version of your script part addressing these concerns:

<script setup lang="ts">
import DynamicsForm from '@/components/dynamics-form/index.vue'
import { ref, onMounted } from 'vue'
import { cloneDeep, get, isArray } from 'lodash'

const props = defineProps<{
  label: any,
  modelValue?: any,
  formValue: any,
  view?: boolean
}>()

defineEmits(['update:modelValue'])

// Initialize refs and variables
const dialogVisible = ref(false)
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
let form_data = ref<{ [key: string]: any } | undefined>()

// On component mount, initialize form data
onMounted(() => {
  updateFormData();
})

const open = () => {
  updateFormData() // Update form data if necessary before opening the dialog
  dialogVisible.value = true;
}

const close = () => {
  dialogVisible.value = false
  form_data.value = undefined
}

/**
 * Get updated form data either from props or current form state
 */
const updateFormData = () => {
  if (!form_data.value && props.modelValue) {
    form_data.value = cloneDeep(props.modelValue);
  }
}

const show = (field: any): boolean => {
  if (field.relation_show_field_dict) {
    const keys = Object.keys(field.relation_show_field_dict);
    
    for (const key of keys) {
      let value = get(props.formValue, key);
      
      if (isArray(value) && value.length > 0) {
        const values = field.relation_show_field_dict[key];
        
        if (values.some(val => val === value)) {
          return true; // Value found in relation show fields list
        }
      }

      return !value || value === null; // No value or null means always visible
    }
  }

  return true;
};

const submit = async () => {
  try {
    await dynamicsFormRef.value!.validate(); // Await validation to ensure complete input
    dialogVisible.value = false;
    emit('update:modelValue', form_data.value!);
    form_data.value = undefined;
  } catch (error) {
    console.error("Validation failed", error); // Handle validation errors
  }
};
</script>

<style scoped lang="scss"></style>

Feel free to adjust further based on your application's requirements!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants