-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: Enhance dynamic form rendering #4223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
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. DetailsInstructions 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. |
|
[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. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
| }, | ||
| ] | ||
| const form_data = ref<Dict<any>>({}) | ||
| const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>() |
There was a problem hiding this comment.
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:
- Attribute Errors: Fixed
attrssyntax issues, such as using{ label-width='xx' }. - Relation Field Settings:
- Updated
relation_trigger_field_dictto correctly referencetrigger_valuewhen calling the request URL. - Removed duplicate
childrenarrays that were causing confusion and added missing ones where necessary.
- Updated
- Option List Correctness:
- The option list for each select control is consistent with other fields.
- Text Field Name Handling:
- Added missing
text_field,value_field,required_asterisk, and setting correct labels for multi-level structure.
- Added missing
Suggested Changes:
-
Updated HTML Attributes:
:props-info="{ tabs_label: '用户', active-name: 'first-tab', tab-content-class:'content-card'" /> style="width:100%" </el-form> -
Fixed Relation Trigger Field Dictionary Keys:
Usefieldinstead ofkey(as used in option_lists). -
Consistent Option Lists:
Ensure every input field's options match its configuration in bothinput_typespecific 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.
| return value | ||
| } | ||
| /** | ||
| * 校验函数 |
There was a problem hiding this comment.
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:
- Duplicate Imports: Multiple imports of functions like
ref,onBeforeMount,watch, etc., should be consolidated. - Unused Variables: The variable
loadis declared but not used anywhere. - Redundant Import in
get_formDefaultValues(_: lodash package). - Unnecessary Parameter Passing in
triggerFunction (template_field,triger_value,setting, andself).
Potential Issues:
- String Manipulation Using
replaceMethod: This could lead to unexpected behavior with regex, especially if\u00A0(non-breaking space) is encountered.
Optimization Suggestion:
- Combine Repeated Logic: For example, handle form value mapping logic more cleanly across multiple functions.
- 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.
| }) | ||
| } | ||
| </script> | ||
| <style lang="scss" scoped></style> |
There was a problem hiding this comment.
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:
-
Code Duplication: The
openmethod is called twice with similar logic when checking for existing data inprops.modelValue. This can be consolidated. -
Null/Undefined Handling: In the
showfunction, you handle empty arrays (values && values.length > 0) incorrectly. It's better to explicitly check if the array contains elements. -
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.
-
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!
feat: Enhance dynamic form rendering