feat: Enhance dynamic form rendering (#4223)

v3.2
shaohuzhang1 2025-10-21 19:25:22 +08:00 committed by GitHub
parent da7f0edecd
commit 727c8bfa98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 471 additions and 169 deletions

View File

@ -5,6 +5,7 @@
:model="form_data" :model="form_data"
:render_data="damo_data" :render_data="damo_data"
ref="dynamicsFormRef" ref="dynamicsFormRef"
:other-params="{ current_workspace_id: 'default' }"
> >
<template #default="scope"> <template #default="scope">
<el-form-item label="其他字段"> <el-form-item label="其他字段">
@ -21,7 +22,30 @@ import { ref } from 'vue'
import type { Dict } from '@/api/type/common' import type { Dict } from '@/api/type/common'
const damo_data: Array<FormField> = [ const damo_data: Array<FormField> = [
{ field: 'name', input_type: 'PasswordInput', label: '用戶名', required: false }, {
field: 'name',
input_type: 'PasswordInput',
label: {
label: '用戶名',
input_type: 'SettingLabel',
field: 'name_setting',
relation_show_field_dict: {
name: {
values: ['01993837-5b09-7f20-9360-801d11d43d28'],
},
},
relation_trigger_field_dict: {
name: {
values: ['01993837-5b09-7f20-9360-801d11d43d28'],
request:
'self.children=()=>request.get(extra.renderTemplate(trigger_setting.url)).then(ok=>{return ok})',
url: '/workspace/${current_workspace_id}/model/${trigger_value}/model_params_form',
},
},
children: [],
},
required: false,
},
{ field: 'json_text', input_type: 'JsonInput', label: 'aa', required: false }, { field: 'json_text', input_type: 'JsonInput', label: 'aa', required: false },
{ {
field: 'array_object_card_field', field: 'array_object_card_field',
@ -33,8 +57,8 @@ const damo_data: Array<FormField> = [
children: [ children: [
{ field: 'name1', input_type: 'TextInput', label: '用戶名1' }, { field: 'name1', input_type: 'TextInput', label: '用戶名1' },
{ field: 'name2', input_type: 'TextInput', label: '用戶名2' }, { field: 'name2', input_type: 'TextInput', label: '用戶名2' },
{ field: 'name3', input_type: 'TextInput', label: '用戶名3' } { field: 'name3', input_type: 'TextInput', label: '用戶名3' },
] ],
}, },
{ {
field: 'maxkb_tokens', field: 'maxkb_tokens',
@ -46,9 +70,9 @@ const damo_data: Array<FormField> = [
step: 1, step: 1,
precision: 1, precision: 1,
'show-input-controls': false, 'show-input-controls': false,
'show-input': true 'show-input': true,
}, },
label: { label: '温度', attrs: { tooltip: 'sss' }, input_type: 'TooltipLabel' } label: { label: '温度', attrs: { tooltip: 'sss' }, input_type: 'TooltipLabel' },
}, },
{ {
field: 'object_card_field', field: 'object_card_field',
@ -60,8 +84,8 @@ const damo_data: Array<FormField> = [
children: [ children: [
{ field: 'name1', input_type: 'TextInput', label: '用戶名1' }, { field: 'name1', input_type: 'TextInput', label: '用戶名1' },
{ field: 'name2', input_type: 'TextInput', label: '用戶名2' }, { field: 'name2', input_type: 'TextInput', label: '用戶名2' },
{ field: 'name3', input_type: 'TextInput', label: '用戶名3' } { field: 'name3', input_type: 'TextInput', label: '用戶名3' },
] ],
}, },
{ {
field: 'tab_card_field', field: 'tab_card_field',
@ -70,39 +94,54 @@ const damo_data: Array<FormField> = [
trigger_type: 'CHILD_FORMS', trigger_type: 'CHILD_FORMS',
attrs: { 'label-width': '120px', 'label-suffix': ':ssss', 'label-position': 'left' }, attrs: { 'label-width': '120px', 'label-suffix': ':ssss', 'label-position': 'left' },
required: false, required: false,
relation_trigger_field_dict: {
'array_object_card_field.0.name1': ['111']
},
props_info: { tabs_label: '用户' }, props_info: { tabs_label: '用户' },
children: [ children: [
{ field: 'name1', input_type: 'TextInput', label: '用戶名1' }, { field: 'name1', input_type: 'TextInput', label: '用戶名1' },
{ field: 'name2', input_type: 'TextInput', label: '用戶名2' }, { field: 'name2', input_type: 'TextInput', label: '用戶名2' },
{ field: 'name3', input_type: 'TextInput', label: '用戶名3' } { field: 'name3', input_type: 'TextInput', label: '用戶名3' },
] ],
}, },
{ {
field: 'single_select_field', field: 'single_select_field',
input_type: 'SingleSelect', input_type: 'SingleSelect',
label: '测试单选', text_field: 'name',
value_field: 'id',
required: true, required: true,
attrs: { placeholder: '请选择' }, attrs: { placeholder: '请选择' },
option_list: [ required_asterisk: true,
{ label: {
key: '测试', label: '测试单选',
value: 'test' input_type: 'SettingLabel',
field: 'name_setting',
relation_show_field_dict: {
single_select_field: {
values: [],
},
}, },
{ relation_trigger_field_dict: {
key: '测试1', single_select_field: {
value: 'test1' values: [],
} request:
] 'self.children=()=>request.get(extra.renderTemplate(trigger_setting.url)).then(ok=>{return ok})',
url: '/workspace/${current_workspace_id}/model/${trigger_value}/model_params_form',
},
},
children: [],
},
relation_trigger_field_dict: {
name: {
values: [],
url: '/workspace/${current_workspace_id}/model_list?model_type=LLM',
change_field: 'option_list',
},
},
}, },
{ {
field: 'multi_select_field', field: 'multi_select_field',
input_type: 'MultiSelect', input_type: 'MultiSelect',
default_value: ['test1'], default_value: ['test1'],
relation_show_field_dict: { relation_show_field_dict: {
'object_card_field.name1': [] 'object_card_field.name1': [],
}, },
label: '测试多选下拉', label: '测试多选下拉',
required: true, required: true,
@ -110,13 +149,13 @@ const damo_data: Array<FormField> = [
option_list: [ option_list: [
{ {
key: '测试', key: '测试',
value: 'test' value: 'test',
}, },
{ {
key: '测试1', key: '测试1',
value: 'test1' value: 'test1',
} },
] ],
}, },
{ {
field: 'radio_field', field: 'radio_field',
@ -127,13 +166,13 @@ const damo_data: Array<FormField> = [
option_list: [ option_list: [
{ {
key: '测试', key: '测试',
value: 'test' value: 'test',
}, },
{ {
key: '测试1', key: '测试1',
value: 'test1' value: 'test1',
} },
] ],
}, },
{ {
field: 'radio_button_field', field: 'radio_button_field',
@ -144,13 +183,13 @@ const damo_data: Array<FormField> = [
option_list: [ option_list: [
{ {
key: '测试', key: '测试',
value: 'test' value: 'test',
}, },
{ {
key: '测试1', key: '测试1',
value: 'test1' value: 'test1',
} },
] ],
}, },
{ {
field: 'radio_card_field', field: 'radio_card_field',
@ -161,13 +200,13 @@ const damo_data: Array<FormField> = [
option_list: [ option_list: [
{ {
key: '测试', key: '测试',
value: 'test' value: 'test',
}, },
{ {
key: '测试111111', key: '测试111111',
value: 'test1' value: 'test1',
} },
] ],
}, },
{ {
field: 'table_radio_field', field: 'table_radio_field',
@ -181,7 +220,7 @@ const damo_data: Array<FormField> = [
{ {
property: '`${row.key}${row.number}`', property: '`${row.key}${row.number}`',
label: '名称', label: '名称',
type: 'eval' type: 'eval',
}, },
{ {
property: 'ProgressTableItem', property: 'ProgressTableItem',
@ -194,8 +233,8 @@ const damo_data: Array<FormField> = [
{ color: '#e6a23c', percentage: 40 }, { color: '#e6a23c', percentage: 40 },
{ color: '#5cb87a', percentage: 60 }, { color: '#5cb87a', percentage: 60 },
{ color: '#1989fa', percentage: 80 }, { color: '#1989fa', percentage: 80 },
{ color: '#6f7ad3', percentage: 100 } { color: '#6f7ad3', percentage: 100 },
] ],
}, },
props_info: { props_info: {
view_card: [ view_card: [
@ -203,31 +242,31 @@ const damo_data: Array<FormField> = [
type: 'eval', type: 'eval',
title: '测试', title: '测试',
value_field: value_field:
'`${parseFloat(row.number).toLocaleString("zh-CN",{style: "decimal",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`' '`${parseFloat(row.number).toLocaleString("zh-CN",{style: "decimal",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`',
}, },
{ {
type: 'eval', type: 'eval',
title: '名称', title: '名称',
value_field: '`${row.key}&nbsp;&nbsp;&nbsp;`' value_field: '`${row.key}&nbsp;&nbsp;&nbsp;`',
} },
] ],
} },
} },
], ],
style: { width: '500px' } style: { width: '500px' },
}, },
option_list: [ option_list: [
{ {
key: '测试', key: '测试',
value: 'test', value: 'test',
number: 10 number: 10,
}, },
{ {
key: '测试111111', key: '测试111111',
value: 'test1', value: 'test1',
number: 100 number: 100,
} },
] ],
}, },
{ {
field: 'table_checkbox_field', field: 'table_checkbox_field',
@ -241,7 +280,7 @@ const damo_data: Array<FormField> = [
{ {
property: '`${row.key}${row.number}`', property: '`${row.key}${row.number}`',
label: '名称', label: '名称',
type: 'eval' type: 'eval',
}, },
{ {
property: 'ProgressTableItem', property: 'ProgressTableItem',
@ -254,8 +293,8 @@ const damo_data: Array<FormField> = [
{ color: '#e6a23c', percentage: 40 }, { color: '#e6a23c', percentage: 40 },
{ color: '#5cb87a', percentage: 60 }, { color: '#5cb87a', percentage: 60 },
{ color: '#1989fa', percentage: 80 }, { color: '#1989fa', percentage: 80 },
{ color: '#6f7ad3', percentage: 100 } { color: '#6f7ad3', percentage: 100 },
] ],
}, },
props_info: { props_info: {
view_card: [ view_card: [
@ -263,32 +302,32 @@ const damo_data: Array<FormField> = [
type: 'eval', type: 'eval',
title: '测试', title: '测试',
value_field: value_field:
'`${parseFloat(row.number).toLocaleString("zh-CN",{style: "decimal",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`' '`${parseFloat(row.number).toLocaleString("zh-CN",{style: "decimal",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`',
}, },
{ {
type: 'eval', type: 'eval',
title: '名称', title: '名称',
value_field: '`${row.key}&nbsp;&nbsp;&nbsp;`' value_field: '`${row.key}&nbsp;&nbsp;&nbsp;`',
} },
] ],
} },
} },
], ],
style: { width: '500px' } style: { width: '500px' },
}, },
option_list: [ option_list: [
{ {
key: '测试', key: '测试',
value: 'test', value: 'test',
number: 10 number: 10,
}, },
{ {
key: '测试111111', key: '测试111111',
value: 'test1', value: 'test1',
number: 100 number: 100,
} },
] ],
} },
] ]
const form_data = ref<Dict<any>>({}) const form_data = ref<Dict<any>>({})
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>() const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()

View File

@ -5,13 +5,16 @@
:prop="formfield.field" :prop="formfield.field"
:key="formfield.field" :key="formfield.field"
:rules="rules" :rules="rules"
:class="formfield.required_asterisk ? 'hide-asterisk' : ''"
> >
<template #label v-if="formfield.label"> <template #label v-if="formfield.label">
<FormItemLabel v-if="isString(formfield.label)" :form-field="formfield"></FormItemLabel> <FormItemLabel v-if="isString(formfield.label)" :form-field="formfield"></FormItemLabel>
<component <component
v-else v-else
:is="formfield.label.input_type" :is="formfield.label.input_type"
:label="formfield.label.label" :label="formfield.label"
v-model="labelValue"
:form-value="formValue"
v-bind="label_attrs" v-bind="label_attrs"
></component> ></component>
</template> </template>
@ -36,6 +39,7 @@ import FormItemLabel from './FormItemLabel.vue'
import type { Dict } from '@/api/type/common' import type { Dict } from '@/api/type/common'
import bus from '@/utils/bus' import bus from '@/utils/bus'
import { t } from '@/locales' import { t } from '@/locales'
import { get } from 'lodash'
const props = defineProps<{ const props = defineProps<{
// //
modelValue: any modelValue: any
@ -47,7 +51,13 @@ const props = defineProps<{
// //
otherParams: any otherParams: any
// Options // Options
trigger: (formItem: FormField, loading: Ref<boolean>) => Promise<any> trigger: (
trigger_field: string,
trigger_value: any,
trigger_setting: any,
self: any,
loading: Ref<boolean>,
) => void
// //
initDefaultData: (formItem: FormField) => void initDefaultData: (formItem: FormField) => void
// //
@ -60,13 +70,22 @@ const props = defineProps<{
parent_field?: string parent_field?: string
}>() }>()
const emit = defineEmits(['change']) const emit = defineEmits(['change', 'changeLabel'])
const loading = ref<boolean>(false) const loading = ref<boolean>(false)
const isString = (value: any) => { const isString = (value: any) => {
return typeof value === 'string' return typeof value === 'string'
} }
const labelValue = computed({
get: () => {
return props.formValue[props.formfield.label.field]
},
set: (value: any) => {
emit('changeLabel', value)
bus.emit(props.formfield.label.field, value)
},
})
const itemValue = computed({ const itemValue = computed({
get: () => { get: () => {
return props.modelValue return props.modelValue
@ -147,32 +166,50 @@ const componentStyle = computed(() => {
const attrs = computed(() => { const attrs = computed(() => {
return props.formfield.attrs ? props.formfield.attrs : {} return props.formfield.attrs ? props.formfield.attrs : {}
}) })
const initTrigger = (self: any, trigger_field_dict?: Dict<any>) => {
if (trigger_field_dict) {
Object.keys(trigger_field_dict).forEach((key) => {
const setting = trigger_field_dict[key]
const triggerValues = setting['values']
const value = get(props.formValue, key)
if (triggerValues && triggerValues.length > 0) {
if (triggerValues.includes(value)) {
props.trigger(key, value, setting, self, loading)
}
} else {
props.trigger(key, value, setting, self, loading)
}
})
}
}
onMounted(() => { onMounted(() => {
props.initDefaultData(props.formfield) props.initDefaultData(props.formfield)
if (props.formfield.provider && props.formfield.method) { initTrigger(props.formfield, props.formfield.relation_trigger_field_dict)
props.trigger(props.formfield, loading) initTrigger(props.formfield.label, props.formfield.label?.relation_trigger_field_dict)
} isString(props.formfield.label)
// ? undefined
const trigger_field_dict = props.formfield.relation_trigger_field_dict : onTrigger(props.formfield.label, props.formfield.label.relation_trigger_field_dict)
onTrigger(props.formfield, props.formfield.relation_trigger_field_dict)
})
const onTrigger = (self: any, trigger_field_dict?: Dict<any>) => {
if (trigger_field_dict) { if (trigger_field_dict) {
const keys = Object.keys(trigger_field_dict) const keys = Object.keys(trigger_field_dict)
keys.forEach((key) => { keys.forEach((key) => {
const value = trigger_field_dict[key] const setting = trigger_field_dict[key]
const values: Array<any> = setting.values
// //
bus.on(key, (v: any) => { bus.on(key, (v: any) => {
if (value && value.length > 0) { if (values && values.length > 0) {
if (value.includes(v)) { if (values.includes(v)) {
props.trigger(props.formfield, loading) props.trigger(key, v, setting, self, loading)
} }
} else { } else {
props.trigger(props.formfield, loading) props.trigger(key, v, setting, self, loading)
} }
}) })
}) })
} }
}) }
const validate = () => { const validate = () => {
if (props.formfield.trigger_type === 'CHILD_FORMS' && componentFormRef.value) { if (props.formfield.trigger_type === 'CHILD_FORMS' && componentFormRef.value) {
return componentFormRef.value.validate() return componentFormRef.value.validate()
@ -181,4 +218,10 @@ const validate = () => {
} }
defineExpose({ validate }) defineExpose({ validate })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.hide-asterisk {
::after {
display: none;
}
}
</style>

View File

@ -2,13 +2,13 @@ import type { App } from 'vue'
import type { Dict } from '@/api/type/common' import type { Dict } from '@/api/type/common'
import DynamicsForm from '@/components/dynamics-form/index.vue' import DynamicsForm from '@/components/dynamics-form/index.vue'
let components: Dict<any> = import.meta.glob('@/components/dynamics-form/**/**.vue', { let components: Dict<any> = import.meta.glob('@/components/dynamics-form/**/**.vue', {
eager: true eager: true,
}) })
components = { components = {
...components, ...components,
...import.meta.glob('@/components/dynamics-form/**/**/**.vue', { ...import.meta.glob('@/components/dynamics-form/**/**/**.vue', {
eager: true eager: true,
}) }),
} }
const install = (app: App) => { const install = (app: App) => {

View File

@ -6,6 +6,8 @@
label-suffix=":" label-suffix=":"
v-loading="loading" v-loading="loading"
v-bind="$attrs" v-bind="$attrs"
label-position="top"
require-asterisk-position="right"
> >
<slot :form_value="formValue"></slot> <slot :form_value="formValue"></slot>
<template v-for="item in formFieldList" :key="item.field"> <template v-for="item in formFieldList" :key="item.field">
@ -14,6 +16,7 @@
:key="item.field" :key="item.field"
v-if="show(item)" v-if="show(item)"
@change="change(item, $event)" @change="change(item, $event)"
@changeLabel="changeLabel(item, $event)"
v-bind:modelValue="formValue[item.field]" v-bind:modelValue="formValue[item.field]"
:formfield="item" :formfield="item"
:trigger="trigger" :trigger="trigger"
@ -33,18 +36,27 @@
import type { Dict } from '@/api/type/common' import type { Dict } from '@/api/type/common'
import FormItem from '@/components/dynamics-form/FormItem.vue' import FormItem from '@/components/dynamics-form/FormItem.vue'
import type { FormField } from '@/components/dynamics-form/type' import type { FormField } from '@/components/dynamics-form/type'
import { ref, onBeforeMount, watch, type Ref } from 'vue' import { ref, onBeforeMount, watch, type Ref, computed } from 'vue'
import type { FormInstance } from 'element-plus' import type { FormInstance } from 'element-plus'
import { get } from '@/request/index'
import type Result from '@/request/Result' import type Result from '@/request/Result'
import _ from 'lodash' import _ from 'lodash'
import { get, post, put, del } from '@/request/index'
const request = {
get,
post,
put,
del,
}
defineOptions({ name: 'dynamicsForm' }) defineOptions({ name: 'dynamicsForm' })
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
// //
render_data: Promise<Result<Array<FormField>>> | string | Array<FormField> render_data:
| Promise<Result<Array<FormField>>>
| string
| Array<FormField>
| (() => Promise<Result<Array<FormField>>>)
// //
otherParams?: any otherParams?: any
// //
@ -103,6 +115,15 @@ const change = (field: FormField, value: any) => {
formValue.value[field.field] = value formValue.value[field.field] = value
} }
/**
* 表单字段修改
* @param field
* @param value
*/
const changeLabel = (field: FormField, value: any) => {
formValue.value[field.label.field] = value
}
watch( watch(
formValue, formValue,
() => { () => {
@ -110,14 +131,60 @@ watch(
}, },
{ deep: true }, { deep: true },
) )
function renderTemplate(template: string, data: any) {
return template.replace(/\$\{(\w+)\}/g, (match, key) => {
return data[key] !== undefined ? data[key] : match
})
}
/** /**
* 触发器,用户获取子表单 或者 下拉选项 * 触发器,用户获取子表单 或者 下拉选项
* @param field * @param field
* @param loading * @param loading
*/ */
const trigger = (field: FormField, loading: Ref<boolean>) => { const trigger = (
return Promise.resolve([]) trigger_field: string,
trigger_value: any,
trigger_setting: any,
self: any,
loading: Ref<boolean>,
) => {
const request_call = new Function(
'self',
'trigger_setting',
'request',
'extra',
trigger_setting.request
? trigger_setting.request
: 'return request.get(extra.renderTemplate(trigger_setting.url));',
)(self, trigger_setting, request, {
renderTemplate: (url: string) =>
renderTemplate(url, {
trigger_value: trigger_value,
...props.otherParams,
}),
})
if (!trigger_setting.change && !trigger_setting.change_field) {
return
}
request_call.then((ok: any) => {
new Function(
'self',
'trigger_setting',
'response',
'extra',
trigger_setting.change
? trigger_setting.change
: `self[trigger_setting.change_field]=[
...response.data.shared_model.map((m) => {
return { ...m, type: 'share' }
}),
...response.data.model.map((m) => {
return { ...m, type: 'workspace' }
})
];`,
)(self, trigger_setting, ok, { form_data: formValue, getDefault: getFormDefaultValue })
})
} }
/** /**
* 初始化默认数据 * 初始化默认数据
@ -141,7 +208,11 @@ onBeforeMount(() => {
}) })
const render = ( const render = (
render_data: string | Array<FormField> | Promise<Result<Array<FormField>>>, render_data:
| string
| Array<FormField>
| Promise<Result<Array<FormField>>>
| (() => Promise<Result<Array<FormField>>>),
data?: Dict<any>, data?: Dict<any>,
) => { ) => {
if (typeof render_data == 'string') { if (typeof render_data == 'string') {
@ -150,6 +221,15 @@ const render = (
}) })
} else if (render_data instanceof Array) { } else if (render_data instanceof Array) {
formFieldList.value = render_data formFieldList.value = render_data
} else if (typeof render_data === 'function') {
render_data().then((ok: any) => {
formFieldList.value = ok.data
const form_data = data ? data : {}
if (form_data) {
const value = getFormDefaultValue(formFieldList.value, form_data)
formValue.value = _.cloneDeep(value)
}
})
} else { } else {
render_data.then((ok) => { render_data.then((ok) => {
formFieldList.value = ok.data formFieldList.value = ok.data
@ -157,37 +237,42 @@ const render = (
} }
const form_data = data ? data : {} const form_data = data ? data : {}
if (form_data) { if (form_data) {
const value = formFieldList.value const value = getFormDefaultValue(formFieldList.value, form_data)
.map((item) => {
if (form_data[item.field] !== undefined) {
if (item.value_field && item.option_list && item.option_list.length > 0) {
const value_field = item.value_field
const find = item.option_list?.find((i) => {
if (typeof form_data[item.field] === 'string') {
return i[value_field] === form_data[item.field]
} else {
return form_data[item.field].indexOf([value_field]) === -1
}
})
if (find) {
return { [item.field]: form_data[item.field] }
}
if (item.show_default_value === true || item.show_default_value === undefined) {
return { [item.field]: item.default_value }
}
} else {
return { [item.field]: form_data[item.field] }
}
}
if (item.show_default_value === true || item.show_default_value === undefined) {
return { [item.field]: item.default_value }
}
return {}
})
.reduce((x, y) => ({ ...x, ...y }), {})
formValue.value = _.cloneDeep(value) formValue.value = _.cloneDeep(value)
} }
} }
const getFormDefaultValue = (fieldList: Array<any>, form_data?: any) => {
form_data = form_data ? form_data : {}
const value = fieldList
.map((item) => {
if (form_data[item.field] !== undefined) {
if (item.value_field && item.option_list && item.option_list.length > 0) {
const value_field = item.value_field
const find = item.option_list?.find((i: any) => {
if (typeof form_data[item.field] === 'string') {
return i[value_field] === form_data[item.field]
} else {
return form_data[item.field].indexOf([value_field]) === -1
}
})
if (find) {
return { [item.field]: form_data[item.field] }
}
if (item.show_default_value === true || item.show_default_value === undefined) {
return { [item.field]: item.default_value }
}
} else {
return { [item.field]: form_data[item.field] }
}
}
if (item.show_default_value === true || item.show_default_value === undefined) {
return { [item.field]: item.default_value }
}
return {}
})
.reduce((x, y) => ({ ...x, ...y }), {})
return value
}
/** /**
* 校验函数 * 校验函数
*/ */

View File

@ -4,8 +4,6 @@
<DynamicsForm <DynamicsForm
:style="formStyle" :style="formStyle"
:view="view" :view="view"
label-position="top"
require-asterisk-position="right"
ref="ceFormRef" ref="ceFormRef"
v-model="_data[index]" v-model="_data[index]"
:model="_data[index]" :model="_data[index]"
@ -13,6 +11,8 @@
:render_data="render_data()" :render_data="render_data()"
v-bind="attr" v-bind="attr"
:parent_field="formField.field + '.' + index" :parent_field="formField.field + '.' + index"
label-position="top"
require-asterisk-position="right"
></DynamicsForm> ></DynamicsForm>
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top"> <el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
<el-button text @click.stop="deleteKnowledge(item)" class="delete-button"> <el-button text @click.stop="deleteKnowledge(item)" class="delete-button">

View File

@ -3,14 +3,14 @@
<DynamicsForm <DynamicsForm
:read-only="view" :read-only="view"
:style="formStyle" :style="formStyle"
label-position="top"
require-asterisk-position="right"
ref="dynamicsFormRef" ref="dynamicsFormRef"
v-model="data" v-model="data"
:other-params="other" :other-params="other"
:render_data="formField.children ? formField.children : []" :render_data="formField.children ? formField.children : []"
v-bind="$attrs" v-bind="$attrs"
:parent_field="formField.field" :parent_field="formField.field"
label-position="top"
require-asterisk-position="right"
></DynamicsForm> ></DynamicsForm>
</el-card> </el-card>
</template> </template>
@ -38,7 +38,7 @@ const data = computed({
}, },
set: ($event) => { set: ($event) => {
emit('update:modelValue', $event) emit('update:modelValue', $event)
} },
}) })
const other = computed(() => { const other = computed(() => {
@ -69,7 +69,7 @@ function validate() {
return Promise.resolve() return Promise.resolve()
} }
defineExpose({ defineExpose({
validate validate,
}) })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -12,8 +12,6 @@
<DynamicsForm <DynamicsForm
:style="formStyle" :style="formStyle"
:view="view" :view="view"
label-position="top"
require-asterisk-position="right"
ref="ceFormRef" ref="ceFormRef"
v-model="_data[index]" v-model="_data[index]"
:model="_data[index]" :model="_data[index]"
@ -21,6 +19,8 @@
:render_data="render_data()" :render_data="render_data()"
v-bind="attr" v-bind="attr"
:parent_field="formField.field + '.' + index" :parent_field="formField.field + '.' + index"
label-position="top"
require-asterisk-position="right"
></DynamicsForm> ></DynamicsForm>
</el-card> </el-card>
</template> </template>
@ -66,7 +66,7 @@ const _data = computed<Array<any>>({
}, },
set(value) { set(value) {
emit('update:modelValue', value) emit('update:modelValue', value)
} },
}) })
const props_info = computed(() => { const props_info = computed(() => {
@ -117,7 +117,7 @@ const handleTabsEdit = (targetName: TabPaneName | undefined, action: 'remove' |
defineExpose({ defineExpose({
validate, validate,
field: props.field field: props.field,
}) })
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -0,0 +1,97 @@
<template>
<div class="flex-between w-full my-required">
<div>
<span> {{ label.label }}<span class="color-danger">*</span></span>
</div>
<el-tooltip v-if="label.attrs?.tooltip" effect="dark" placement="right">
<template #content
><div style="max-width: 200px">{{ label.attrs.tooltip }}</div></template
>
<AppIcon iconName="app-warning" class="app-warning-icon" style="flex-shrink: 0"></AppIcon>
</el-tooltip>
<el-button v-if="show(label)" type="primary" link @click="open()">
<AppIcon iconName="app-setting"></AppIcon>
</el-button>
<el-dialog
destroy-on-close
v-model="dialogVisible"
title="Tips"
width="500"
:before-close="close"
>
<DynamicsForm
:read-only="view"
ref="dynamicsFormRef"
:render_data="label.children ? label.children : []"
label-position="top"
v-model="form_data"
require-asterisk-position="right"
:model="form_data"
></DynamicsForm>
<template #footer>
<div class="dialog-footer">
<el-button @click="close"></el-button>
<el-button type="primary" @click="submit"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import DynamicsForm from '@/components/dynamics-form/index.vue'
import { ref } from 'vue'
import { cloneDeep, get } from 'lodash'
const props = defineProps<{
label: any
modelValue?: any
formValue: any
view?: boolean
}>()
const emit = defineEmits(['update:modelValue'])
const dialogVisible = ref<boolean>(false)
const dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()
const form_data = ref<any>(undefined)
const open = () => {
if (props.modelValue) {
form_data.value = cloneDeep(props.modelValue)
}
dialogVisible.value = true
}
const close = () => {
dialogVisible.value = false
form_data.value = undefined
}
/**
* 当前 field是否展示
* @param field
*/
const show = (field: any) => {
if (field.relation_show_field_dict) {
const keys = Object.keys(field.relation_show_field_dict)
for (const index in keys) {
const key = keys[index]
const v = get(props.formValue, key)
if (v && v !== undefined && v !== null) {
const values = field.relation_show_field_dict[key]
if (values && values.length > 0) {
return values.includes(v)
} else {
return true
}
} else {
return false
}
}
}
return true
}
const submit = () => {
dynamicsFormRef.value?.validate().then(() => {
dialogVisible.value = false
emit('update:modelValue', form_data.value)
form_data.value = undefined
})
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="flex align-center" style="display: inline-flex"> <div class="flex align-center" style="display: inline-flex">
<div class="flex-between mr-4"> <div class="flex-between mr-4">
<span>{{ label }}</span> <span>{{ label.label }}</span>
</div> </div>
<el-tooltip effect="dark" placement="right"> <el-tooltip v-if="label.attrs.tooltip" effect="dark" placement="right">
<template #content <template #content
><div style="max-width: 200px">{{ tooltip }}</div></template ><div style="max-width: 200px">{{ label.attrs.tooltip }}</div></template
> >
<AppIcon iconName="app-warning" class="app-warning-icon" style="flex-shrink: 0"></AppIcon> <AppIcon iconName="app-warning" class="app-warning-icon" style="flex-shrink: 0"></AppIcon>
</el-tooltip> </el-tooltip>
@ -13,8 +13,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ defineProps<{
label: string label: any
tooltip: string
}>() }>()
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -0,0 +1,18 @@
<template>
<el-row> </el-row>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { FormField } from '@/components/dynamics-form/type'
import _ from 'lodash'
const props = defineProps<{
formValue?: any
formfieldList?: Array<FormField>
field: string
otherParams: any
formField: FormField
view?: boolean
}>()
</script>
<style lang="scss" scoped></style>

View File

@ -42,7 +42,7 @@ const _modelValue = computed({
set(value) { set(value) {
emit('update:modelValue', value) emit('update:modelValue', value)
emit('change', props.formField) emit('change', props.formField)
} },
}) })
const textField = computed(() => { const textField = computed(() => {
return props.formField.text_field ? props.formField.text_field : 'key' return props.formField.text_field ? props.formField.text_field : 'key'

View File

@ -137,7 +137,7 @@ interface FormField {
/** /**
* {field:field_value_list} field ,field_value_list * {field:field_value_list} field ,field_value_list
*/ */
relation_trigger_field_dict?: Dict<Array<any>> relation_trigger_field_dict?: Dict<any>
/** /**
* OPTION_LISTOption_list CHILD_FORMS * OPTION_LISTOption_list CHILD_FORMS
*/ */
@ -172,5 +172,6 @@ interface FormField {
method?: string method?: string
children?: Array<FormField> children?: Array<FormField>
required_asterisk?: boolean
} }
export type { FormField } export type { FormField }

View File

@ -23,6 +23,9 @@ instance.interceptors.request.use(
if (config.headers === undefined) { if (config.headers === undefined) {
config.headers = new AxiosHeaders() config.headers = new AxiosHeaders()
} }
if (config.url && config.url.startsWith('http')) {
return config
}
const { user, login } = useStore() const { user, login } = useStore()
const token = login.getToken() const token = login.getToken()
const language = user.getLanguage() const language = user.getLanguage()
@ -250,32 +253,33 @@ export const exportExcel: (
} }
function extractFilename(contentDisposition: string) { function extractFilename(contentDisposition: string) {
if (!contentDisposition) return null; if (!contentDisposition) return null
// 处理 URL 编码的文件名 // 处理 URL 编码的文件名
const urlEncodedMatch = contentDisposition.match(/filename=([^;]*)/i) || const urlEncodedMatch =
contentDisposition.match(/filename\*=UTF-8''([^;]*)/i); contentDisposition.match(/filename=([^;]*)/i) ||
contentDisposition.match(/filename\*=UTF-8''([^;]*)/i)
if (urlEncodedMatch && urlEncodedMatch[1]) { if (urlEncodedMatch && urlEncodedMatch[1]) {
try { try {
return decodeURIComponent(urlEncodedMatch[1].replace(/"/g, '')); return decodeURIComponent(urlEncodedMatch[1].replace(/"/g, ''))
} catch (e) { } catch (e) {
console.error("解码URL编码文件名失败:", e); console.error('解码URL编码文件名失败:', e)
} }
} }
// 处理 Base64 编码的文件名 // 处理 Base64 编码的文件名
const base64Part = contentDisposition.match(/=\?utf-8\?b\?(.*?)\?=/i)?.[1]; const base64Part = contentDisposition.match(/=\?utf-8\?b\?(.*?)\?=/i)?.[1]
if (base64Part) { if (base64Part) {
try { try {
const decoded = decodeURIComponent(escape(atob(base64Part))); const decoded = decodeURIComponent(escape(atob(base64Part)))
const filenameMatch = decoded.match(/filename="(.*?)"/i); const filenameMatch = decoded.match(/filename="(.*?)"/i)
return filenameMatch ? filenameMatch[1] : null; return filenameMatch ? filenameMatch[1] : null
} catch (e) { } catch (e) {
console.error("解码Base64文件名失败:", e); console.error('解码Base64文件名失败:', e)
} }
} }
return null; return null
} }
export const exportFile: ( export const exportFile: (
@ -320,20 +324,22 @@ export const exportFile: (
], ],
}), }),
loading, loading,
).then((res: any) => { )
if (res) { .then((res: any) => {
const blob = new Blob([res], { if (res) {
type: 'application/octet-stream', const blob = new Blob([res], {
}) type: 'application/octet-stream',
const link = document.createElement('a') })
link.href = window.URL.createObjectURL(blob) const link = document.createElement('a')
link.download = fileName link.href = window.URL.createObjectURL(blob)
link.click() link.download = fileName
//释放内存 link.click()
window.URL.revokeObjectURL(link.href) //释放内存
} window.URL.revokeObjectURL(link.href)
return true }
}).catch(()=>{}) return true
})
.catch(() => {})
} }
export const exportExcelPost: ( export const exportExcelPost: (

View File

@ -1,6 +1,6 @@
import type {RouteRecordRaw} from 'vue-router' import type { RouteRecordRaw } from 'vue-router'
const modules: any = import.meta.glob('./modules/*.ts', {eager: true}) const modules: any = import.meta.glob('./modules/*.ts', { eager: true })
const rolesRoutes: RouteRecordRaw[] = [...Object.keys(modules).map((key) => modules[key].default)] const rolesRoutes: RouteRecordRaw[] = [...Object.keys(modules).map((key) => modules[key].default)]
@ -33,7 +33,7 @@ export const routes: Array<RouteRecordRaw> = [
{ {
path: '/application/:from/:id/workflow', path: '/application/:from/:id/workflow',
name: 'ApplicationWorkflow', name: 'ApplicationWorkflow',
meta: {activeMenu: '/application'}, meta: { activeMenu: '/application' },
component: () => import('@/views/application-workflow/index.vue'), component: () => import('@/views/application-workflow/index.vue'),
}, },
// 对话 // 对话
@ -42,6 +42,11 @@ export const routes: Array<RouteRecordRaw> = [
name: 'Chat', name: 'Chat',
component: () => import('@/views/chat/index.vue'), component: () => import('@/views/chat/index.vue'),
}, },
{
path: '/demo',
name: 'demo',
component: () => import('@/views/demo/index.vue'),
},
// 对话用户登录 // 对话用户登录
{ {

View File

@ -0,0 +1,9 @@
<template>
<div style="height: 500px">
<DemoVue />
</div>
</template>
<script setup lang="ts">
import DemoVue from '@/components/dynamics-form/Demo.vue'
</script>
<style lang="scss" scoped></style>