perf: formdesign

pull/33/head
xingyu 2023-08-28 17:23:51 +08:00
parent 45a4ac80d9
commit f4ca72a7a2
27 changed files with 851 additions and 858 deletions

View File

@ -1,44 +1,40 @@
<script lang="ts"> <script lang="ts" setup>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { defineComponent } from 'vue'
import { Col, Row } from 'ant-design-vue' import { Col, Row } from 'ant-design-vue'
import type { IFormConfig, IVFormComponent } from '../../../typings/v-form-component' import type { IFormConfig, IVFormComponent } from '../../../typings/v-form-component'
import VFormItem from '../../VFormItem/index.vue' import VFormItem from '../../VFormItem/index.vue'
export default defineComponent({ defineProps({
name: 'FormRender', formData: {
components: { type: Object,
VFormItem, default: () => ({}),
Row,
Col,
}, },
props: { schema: {
formData: { type: Object as PropType<IVFormComponent>,
type: Object, default: () => ({}),
default: () => ({}), },
}, formConfig: {
schema: { type: Object as PropType<IFormConfig>,
type: Object as PropType<IVFormComponent>, default: () => [] as IFormConfig[],
default: () => ({}), },
}, setFormModel: {
formConfig: { type: Function as PropType<(key: string, value: any) => void>,
type: Object as PropType<IFormConfig>, default: null,
default: () => [] as IFormConfig[],
},
setFormModel: {
type: Function as PropType<(key: string, value: any) => void>,
default: null,
},
}, },
emits: ['change', 'submit', 'reset'],
setup(_props) {},
}) })
const emit = defineEmits(['change', 'submit', 'reset'])
</script> </script>
<template> <template>
<template v-if="['Grid'].includes(schema.component)"> <template v-if="['Grid'].includes(schema.component)">
<Row class="grid-row"> <Row class="grid-row">
<Col v-for="(colItem, index) in schema.columns" :key="index" class="grid-col" :span="colItem.span"> <Col
v-for="(colItem, index) in schema.columns"
:key="index"
class="grid-col"
:span="colItem.span"
>
<FormRender <FormRender
v-for="(item, k) in colItem.children" v-for="(item, k) in colItem.children"
:key="k" :key="k"
@ -56,11 +52,14 @@ export default defineComponent({
:schema="schema" :schema="schema"
:form-data="formData" :form-data="formData"
:set-form-model="setFormModel" :set-form-model="setFormModel"
@change="$emit('change', { schema, value: $event })" @change="emit('change', { schema, value: $event })"
@submit="$emit('submit', schema)" @submit="emit('submit', schema)"
@reset="$emit('reset')" @reset="emit('reset')"
> >
<template v-if="schema.componentProps && schema.componentProps.slotName" #[schema.componentProps!.slotName]> <template
v-if="schema.componentProps && schema.componentProps.slotName"
#[schema.componentProps!.slotName]
>
<slot :name="schema.componentProps!.slotName" /> <slot :name="schema.componentProps!.slotName" />
</template> </template>
</VFormItem> </VFormItem>

View File

@ -45,12 +45,16 @@ export default defineComponent({
}) })
const noHiddenList = computed(() => { const noHiddenList = computed(() => {
return props.formConfig.schemas && props.formConfig.schemas.filter(item => item.hidden !== true) return (
props.formConfig.schemas
&& props.formConfig.schemas.filter(item => item.hidden !== true)
)
}) })
const fApi = useVModel(props, 'fApi', emit) const fApi = useVModel(props, 'fApi', emit)
const { submit, validate, clearValidate, resetFields, validateField } = useFormInstanceMethods(props, formModelNew, context, eFormModel) const { submit, validate, clearValidate, resetFields, validateField }
= useFormInstanceMethods(props, formModelNew, context, eFormModel)
const { linkOn, ...methods } = useVFormMethods( const { linkOn, ...methods } = useVFormMethods(
{ formConfig: props.formConfig, formData: props.formModel } as unknown as IProps, { formConfig: props.formConfig, formData: props.formModel } as unknown as IProps,
@ -78,7 +82,9 @@ export default defineComponent({
/** /**
* 获取表单属性 * 获取表单属性
*/ */
const formModelProps = computed(() => omit(props.formConfig, ['disabled', 'labelWidth', 'schemas']) as Recordable) const formModelProps = computed(
() => omit(props.formConfig, ['disabled', 'labelWidth', 'schemas']) as Recordable,
)
const handleSubmit = () => { const handleSubmit = () => {
submit() submit()
@ -127,7 +133,10 @@ export default defineComponent({
@reset="resetFields" @reset="resetFields"
> >
<template v-if="schema && schema.componentProps" #[`schema.componentProps!.slotName`]> <template v-if="schema && schema.componentProps" #[`schema.componentProps!.slotName`]>
<slot :name="schema.componentProps!.slotName" v-bind="{ formModel, field: schema.field, schema }" /> <slot
:name="schema.componentProps!.slotName"
v-bind="{ formModel, field: schema.field, schema }"
/>
</template> </template>
</FormRender> </FormRender>
</Row> </Row>

View File

@ -1,8 +1,8 @@
<!-- <!--
* @Description: 渲染代码 * @Description: 渲染代码
--> -->
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, reactive, toRefs } from 'vue' import { computed, reactive } from 'vue'
import { Modal } from 'ant-design-vue' import { Modal } from 'ant-design-vue'
import { formatRules, removeAttrs } from '../../../utils' import { formatRules, removeAttrs } from '../../../utils'
import type { IFormConfig } from '../../../typings/v-form-component' import type { IFormConfig } from '../../../typings/v-form-component'
@ -38,37 +38,31 @@ let codeVueLast = `
} }
} }
} }
<\/script>` <\/script>`;
// //
export default defineComponent({ const state = reactive({
name: 'CodeModal', open: false,
components: { PreviewCode, Modal }, jsonData: {} as IFormConfig,
setup() { });
const state = reactive({
open: false,
jsonData: {} as IFormConfig
})
const showModal = (formConfig: IFormConfig) => { const showModal = (formConfig: IFormConfig) => {
formConfig.schemas && formatRules(formConfig.schemas) formConfig.schemas && formatRules(formConfig.schemas);
state.open = true state.open = true;
state.jsonData = formConfig state.jsonData = formConfig;
} };
const editorVueJson = computed(() => { const editorVueJson = computed(() => {
return codeVueFront + JSON.stringify(removeAttrs(state.jsonData), null, '\t') + codeVueLast return codeVueFront + JSON.stringify(removeAttrs(state.jsonData), null, '\t') + codeVueLast;
}) });
return { ...toRefs(state), editorVueJson, showModal } defineExpose({ showModal })
}
})
</script> </script>
<template> <template>
<Modal <Modal
title="代码" title="代码"
:footer="null" :footer="null"
:open="open" :open="state.open"
@cancel="open = false" @cancel="state.open = false"
wrapClassName="v-code-modal" wrapClassName="v-code-modal"
style="top: 20px" style="top: 20px"
width="850px" width="850px"

View File

@ -2,7 +2,20 @@
* @Description: 组件属性控件 * @Description: 组件属性控件
--> -->
<script lang="ts"> <script lang="ts">
import { Checkbox, Col, Empty, Form, FormItem, Input, InputNumber, RadioGroup, Row, Select, Switch } from 'ant-design-vue' import {
Checkbox,
Col,
Empty,
Form,
FormItem,
Input,
InputNumber,
RadioGroup,
Row,
Select,
Switch,
} from 'ant-design-vue'
import RadioButtonGroup from '/@/components/Form/src/components/RadioButtonGroup.vue'
import { computed, defineComponent, ref, watch } from 'vue' import { computed, defineComponent, ref, watch } from 'vue'
import { useFormDesignState } from '../../../hooks/useFormDesignState' import { useFormDesignState } from '../../../hooks/useFormDesignState'
import { import {
@ -14,7 +27,6 @@ import {
import { formItemsForEach, remove } from '../../../utils' import { formItemsForEach, remove } from '../../../utils'
import type { IBaseFormAttrs } from '../config/formItemPropsConfig' import type { IBaseFormAttrs } from '../config/formItemPropsConfig'
import FormOptions from './FormOptions.vue' import FormOptions from './FormOptions.vue'
import RadioButtonGroup from '@/components/Form/src/components/RadioButtonGroup.vue'
export default defineComponent({ export default defineComponent({
name: 'ComponentProps', name: 'ComponentProps',
@ -45,19 +57,21 @@ export default defineComponent({
const { formConfig } = useFormDesignState() const { formConfig } = useFormDesignState()
if (formConfig.value.currentItem) if (formConfig.value.currentItem) {
formConfig.value.currentItem.componentProps = formConfig.value.currentItem.componentProps || {} formConfig.value.currentItem.componentProps
= formConfig.value.currentItem.componentProps || {}
}
watch( watch(
() => formConfig.value.currentItem?.field, () => formConfig.value.currentItem?.field,
(_newValue, oldValue) => { (_newValue, oldValue) => {
formConfig.value.schemas formConfig.value.schemas
&& formItemsForEach(formConfig.value.schemas, (item) => { && formItemsForEach(formConfig.value.schemas, (item) => {
if (item.link) { if (item.link) {
const index = item.link.findIndex(linkItem => linkItem === oldValue) const index = item.link.findIndex(linkItem => linkItem === oldValue)
index !== -1 && remove(item.link, index) index !== -1 && remove(item.link, index)
} }
}) })
}, },
) )
@ -94,18 +108,18 @@ export default defineComponent({
}) })
baseComponentAttrs[formConfig.value.currentItem!.component] baseComponentAttrs[formConfig.value.currentItem!.component]
&& baseComponentAttrs[formConfig.value.currentItem!.component].forEach(async (item) => { && baseComponentAttrs[formConfig.value.currentItem!.component].forEach(async (item) => {
if (item.component) { if (item.component) {
if (['Switch', 'Checkbox', 'Radio'].includes(item.component)) { if (['Switch', 'Checkbox', 'Radio'].includes(item.component)) {
item.category = 'control' item.category = 'control'
allOptions.value.push(item) allOptions.value.push(item)
}
else {
item.category = 'input'
allOptions.value.push(item)
}
} }
else { })
item.category = 'input'
allOptions.value.push(item)
}
}
})
}, },
{ {
immediate: true, immediate: true,
@ -114,14 +128,14 @@ export default defineComponent({
// //
const controlOptions = computed(() => { const controlOptions = computed(() => {
return allOptions.value.filter((item) => { return allOptions.value.filter((item) => {
return item.category === 'control' return item.category == 'control'
}) })
}) })
// //
const inputOptions = computed(() => { const inputOptions = computed(() => {
return allOptions.value.filter((item) => { return allOptions.value.filter((item) => {
return item.category === 'input' return item.category == 'input'
}) })
}) })
@ -140,9 +154,9 @@ export default defineComponent({
const linkOptions = computed(() => { const linkOptions = computed(() => {
return ( return (
formConfig.value.schemas formConfig.value.schemas
&& formConfig.value.schemas && formConfig.value.schemas
.filter(item => item.key !== formConfig.value.currentItem!.key) .filter(item => item.key !== formConfig.value.currentItem!.key)
.map(({ label, field }) => ({ label: `${label}/${field}`, value: field })) .map(({ label, field }) => ({ label: `${label}/${field}`, value: field }))
) )
}) })
return { return {
@ -199,12 +213,23 @@ export default defineComponent({
</FormItem> </FormItem>
</div> </div>
<FormItem label="关联字段"> <FormItem label="关联字段">
<Select v-model:value="formConfig.currentItem.link" mode="multiple" :options="linkOptions" /> <Select
v-model:value="formConfig.currentItem.link"
mode="multiple"
:options="linkOptions"
/>
</FormItem> </FormItem>
<FormItem <FormItem
v-if=" v-if="
['Select', 'CheckboxGroup', 'RadioGroup', 'TreeSelect', 'Cascader', 'AutoComplete'].includes(formConfig.currentItem.component) [
'Select',
'CheckboxGroup',
'RadioGroup',
'TreeSelect',
'Cascader',
'AutoComplete',
].includes(formConfig.currentItem.component)
" "
label="选项" label="选项"
> >

View File

@ -3,7 +3,18 @@
--> -->
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, watch } from 'vue' import { computed, defineComponent, watch } from 'vue'
import { Checkbox, Col, Empty, Form, FormItem, Input, RadioGroup, Select, Slider, Switch } from 'ant-design-vue' import {
Checkbox,
Col,
Empty,
Form,
FormItem,
Input,
RadioGroup,
Select,
Slider,
Switch,
} from 'ant-design-vue'
import { isArray } from 'lodash-es' import { isArray } from 'lodash-es'
import { import {
advanceFormItemColProps, advanceFormItemColProps,
@ -40,8 +51,10 @@ export default defineComponent({
() => { () => {
if (formConfig.value.currentItem) { if (formConfig.value.currentItem) {
formConfig.value.currentItem.itemProps = formConfig.value.currentItem.itemProps || {} formConfig.value.currentItem.itemProps = formConfig.value.currentItem.itemProps || {}
formConfig.value.currentItem.itemProps.labelCol = formConfig.value.currentItem.itemProps.labelCol || {} formConfig.value.currentItem.itemProps.labelCol
formConfig.value.currentItem.itemProps.wrapperCol = formConfig.value.currentItem.itemProps.wrapperCol || {} = formConfig.value.currentItem.itemProps.labelCol || {}
formConfig.value.currentItem.itemProps.wrapperCol
= formConfig.value.currentItem.itemProps.wrapperCol || {}
} }
}, },
{ deep: true, immediate: true }, { deep: true, immediate: true },

View File

@ -40,13 +40,19 @@ export default defineComponent({
</script> </script>
<template> <template>
<div class="drag-move-box" :class="{ active: schema.key === formConfig.currentItem?.key }" @click.stop="handleSelectItem"> <div
class="drag-move-box"
:class="{ active: schema.key === formConfig.currentItem?.key }"
@click.stop="handleSelectItem"
>
<div class="form-item-box"> <div class="form-item-box">
<VFormItem :form-config="formConfig" :schema="schema" /> <VFormItem :form-config="formConfig" :schema="schema" />
</div> </div>
<div class="show-key-box"> <div class="show-key-box">
{{ schema.label + (schema.field ? `/${schema.field}` : '') }} {{ schema.label + (schema.field ? `/${schema.field}` : '') }}
</div> </div>
<FormNodeOperate :schema="schema" :current-item="formConfig.currentItem" /> <FormNodeOperate :schema="schema" :current-item="formConfig.currentItem" />
</div> </div>
</template> </template>

View File

@ -1,60 +1,57 @@
<!-- <!--
* @Description: 节点操作复制删除控件 * @Description: 节点操作复制删除控件
--> -->
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent } from 'vue' import { computed } from 'vue'
import type { IVFormComponent } from '../../../typings/v-form-component' import type { IVFormComponent } from '../../../typings/v-form-component'
import { remove } from '../../../utils' import { remove } from '../../../utils'
import { useFormDesignState } from '../../../hooks/useFormDesignState' import { useFormDesignState } from '../../../hooks/useFormDesignState'
import Icon from '@/components/Icon/index' import { Icon } from '@/components/Icon'
export default defineComponent({ const props = defineProps({
name: 'FormNodeOperate', schema: {
components: { type: Object,
Icon, default: () => ({}),
}, },
props: { currentItem: {
schema: { type: Object,
type: Object, default: () => ({}),
default: () => ({}),
},
currentItem: {
type: Object,
default: () => ({}),
},
},
setup(props) {
const { formConfig, formDesignMethods } = useFormDesignState()
const activeClass = computed(() => {
return props.schema.key === props.currentItem.key ? 'active' : 'unactivated'
})
/**
* 删除当前项
*/
const handleDelete = () => {
const traverse = (schemas: IVFormComponent[]) => {
schemas.some((formItem, index) => {
const { component, key } = formItem
//
;['Grid', 'Tabs'].includes(component) && formItem.columns?.forEach(item => traverse(item.children))
if (key === props.currentItem.key) {
const params: IVFormComponent
= schemas.length === 1 ? { component: '' } : schemas.length - 1 > index ? schemas[index + 1] : schemas[index - 1]
formDesignMethods.handleSetSelectItem(params)
remove(schemas, index)
return true
}
})
}
traverse(formConfig.value!.schemas)
}
const handleCopy = () => {
formDesignMethods.handleCopy()
}
return { activeClass, handleDelete, handleCopy }
}, },
}) })
const { formConfig, formDesignMethods } = useFormDesignState()
const activeClass = computed(() => {
return props.schema.key === props.currentItem.key ? 'active' : 'unactivated'
})
/**
* 删除当前项
*/
function handleDelete() {
const traverse = (schemas: IVFormComponent[]) => {
schemas.some((formItem, index) => {
const { component, key } = formItem;
//
['Grid', 'Tabs'].includes(component)
&& formItem.columns?.forEach(item => traverse(item.children))
if (key === props.currentItem.key) {
const params: IVFormComponent
= schemas.length === 1
? { component: '' }
: schemas.length - 1 > index
? schemas[index + 1]
: schemas[index - 1]
formDesignMethods.handleSetSelectItem(params)
remove(schemas, index)
return true
}
})
}
traverse(formConfig.value!.schemas)
}
function handleCopy() {
formDesignMethods.handleCopy()
}
</script> </script>
<template> <template>

View File

@ -1,55 +1,37 @@
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, reactive, toRefs } from 'vue'
import { Input } from 'ant-design-vue' import { Input } from 'ant-design-vue'
import { useFormDesignState } from '../../../hooks/useFormDesignState' import { useFormDesignState } from '../../../hooks/useFormDesignState'
import { remove } from '../../../utils' import { remove } from '../../../utils'
import message from '../../../utils/message' import message from '../../../utils/message'
import Icon from '@/components/Icon/index' import { Icon } from '@/components/Icon'
export default defineComponent({ const { formConfig } = useFormDesignState()
name: 'FormOptions', const key = formConfig.value.currentItem?.component === 'TreeSelect' ? 'treeData' : 'options'
components: { Input, Icon }, function addOptions() {
// props: {}, if (!formConfig.value.currentItem?.componentProps?.[key])
setup() { formConfig.value.currentItem!.componentProps![key] = []
const state = reactive({}) const len = formConfig.value.currentItem?.componentProps?.[key].length + 1
const { formConfig } = useFormDesignState() formConfig.value.currentItem!.componentProps![key].push({
const key = formConfig.value.currentItem?.component === 'TreeSelect' ? 'treeData' : 'options' label: `选项${len}`,
const addOptions = () => { value: `${len}`,
if (!formConfig.value.currentItem?.componentProps?.[key]) })
formConfig.value.currentItem!.componentProps![key] = [] }
const len = formConfig.value.currentItem?.componentProps?.[key].length + 1 function deleteOptions(index: number) {
formConfig.value.currentItem!.componentProps![key].push({ remove(formConfig.value.currentItem?.componentProps?.[key], index)
label: `选项${len}`, }
value: `${len}`,
})
}
const deleteOptions = (index: number) => {
remove(formConfig.value.currentItem?.componentProps?.[key], index)
}
const addGridOptions = () => { function addGridOptions() {
formConfig.value.currentItem?.columns?.push({ formConfig.value.currentItem?.columns?.push({
span: 12, span: 12,
children: [], children: [],
}) })
} }
const deleteGridOptions = (index: number) => { function deleteGridOptions(index: number) {
if (index === 0) if (index === 0)
return message.warning('请至少保留一个栅格') return message.warning('请至少保留一个栅格')
remove(formConfig.value.currentItem!.columns!, index) remove(formConfig.value.currentItem!.columns!, index)
} }
return {
...toRefs(state),
formConfig,
addOptions,
deleteOptions,
key,
deleteGridOptions,
addGridOptions,
}
},
})
</script> </script>
<template> <template>

View File

@ -1,43 +1,30 @@
<!-- <!--
* @Description: 右侧属性面板控件 表单属性面板 * @Description: 右侧属性面板控件 表单属性面板
--> -->
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'
import type { RadioChangeEvent } from 'ant-design-vue' import type { RadioChangeEvent } from 'ant-design-vue'
import { Checkbox, Col, InputNumber, Slider } from 'ant-design-vue' import {
Checkbox,
// import RadioButtonGroup from '/@/components/RadioButtonGroup.vue'; Col,
import { Form, FormItem, Radio } from 'ant-design-vue' Form,
FormItem,
InputNumber,
Radio,
Slider,
} from 'ant-design-vue'
import { useFormDesignState } from '../../../hooks/useFormDesignState' import { useFormDesignState } from '../../../hooks/useFormDesignState'
export default defineComponent({ const { formConfig } = useFormDesignState()
name: 'FormProps',
components: {
InputNumber,
Slider,
Checkbox,
RadioGroup: Radio.Group,
RadioButton: Radio.Button,
Form,
FormItem,
Col,
},
setup() {
const { formConfig } = useFormDesignState()
formConfig.value = formConfig.value || { formConfig.value = formConfig.value || {
labelCol: { span: 24 }, labelCol: { span: 24 },
wrapperCol: { span: 24 }, wrapperCol: { span: 24 },
} }
const lableLayoutChange = (e: RadioChangeEvent) => { function lableLayoutChange(e: RadioChangeEvent) {
if (e.target.value === 'Grid') if (e.target.value === 'Grid')
formConfig.value.layout = 'horizontal' formConfig.value.layout = 'horizontal'
} }
return { formConfig, lableLayoutChange }
},
})
</script> </script>
<template> <template>
@ -46,33 +33,42 @@ export default defineComponent({
<!-- <e-upload v-model="fileList"></e-upload> --> <!-- <e-upload v-model="fileList"></e-upload> -->
<FormItem label="表单布局"> <FormItem label="表单布局">
<RadioGroup v-model:value="formConfig.layout" button-style="solid"> <Radio.Group v-model:value="formConfig.layout" button-style="solid">
<RadioButton value="horizontal"> <Radio.Button value="horizontal">
水平 水平
</RadioButton> </Radio.Button>
<RadioButton value="vertical" :disabled="formConfig.labelLayout === 'Grid'"> <Radio.Button value="vertical" :disabled="formConfig.labelLayout === 'Grid'">
垂直 垂直
</RadioButton> </Radio.Button>
<RadioButton value="inline" :disabled="formConfig.labelLayout === 'Grid'"> <Radio.Button value="inline" :disabled="formConfig.labelLayout === 'Grid'">
行内 行内
</RadioButton> </Radio.Button>
</RadioGroup> </Radio.Group>
</FormItem> </FormItem>
<!-- <Row> --> <!-- <Row> -->
<FormItem label="标签布局"> <FormItem label="标签布局">
<RadioGroup v-model:value="formConfig.labelLayout" button-style="solid" @change="lableLayoutChange"> <Radio.Group
<RadioButton value="flex"> v-model:value="formConfig.labelLayout"
button-style="solid"
@change="lableLayoutChange"
>
<Radio.Button value="flex">
固定 固定
</RadioButton> </Radio.Button>
<RadioButton value="Grid" :disabled="formConfig.layout !== 'horizontal'"> <Radio.Button value="Grid" :disabled="formConfig.layout !== 'horizontal'">
栅格 栅格
</RadioButton> </Radio.Button>
</RadioGroup> </Radio.Group>
</FormItem> </FormItem>
<!-- </Row> --> <!-- </Row> -->
<FormItem v-show="formConfig.labelLayout === 'flex'" label="标签宽度px"> <FormItem v-show="formConfig.labelLayout === 'flex'" label="标签宽度px">
<InputNumber v-model:value="formConfig.labelWidth" :style="{ width: '100%' }" :min="0" :step="1" /> <InputNumber
v-model:value="formConfig.labelWidth"
:style="{ width: '100%' }"
:min="0"
:step="1"
/>
</FormItem> </FormItem>
<div v-if="formConfig.labelLayout === 'Grid'"> <div v-if="formConfig.labelLayout === 'Grid'">
<FormItem label="labelCol"> <FormItem label="labelCol">
@ -83,28 +79,28 @@ export default defineComponent({
</FormItem> </FormItem>
<FormItem label="标签对齐"> <FormItem label="标签对齐">
<RadioGroup v-model:value="formConfig.labelAlign" button-style="solid"> <Radio.Group v-model:value="formConfig.labelAlign" button-style="solid">
<RadioButton value="left"> <Radio.Button value="left">
靠左 靠左
</RadioButton> </Radio.Button>
<RadioButton value="right"> <Radio.Button value="right">
靠右 靠右
</RadioButton> </Radio.Button>
</RadioGroup> </Radio.Group>
</FormItem> </FormItem>
<FormItem label="控件大小"> <FormItem label="控件大小">
<RadioGroup v-model:value="formConfig.size" button-style="solid"> <Radio.Group v-model:value="formConfig.size" button-style="solid">
<RadioButton value="default"> <Radio.Button value="default">
默认 默认
</RadioButton> </Radio.Button>
<RadioButton value="small"> <Radio.Button value="small">
</RadioButton> </Radio.Button>
<RadioButton value="large"> <Radio.Button value="large">
</RadioButton> </Radio.Button>
</RadioGroup> </Radio.Group>
</FormItem> </FormItem>
</div> </div>
<FormItem label="表单属性"> <FormItem label="表单属性">

View File

@ -1,8 +1,8 @@
<!-- <!--
* @Description: 导入JSON模板 * @Description: 导入JSON模板
--> -->
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, reactive, ref, toRefs } from 'vue' import { reactive, ref } from 'vue'
// import message from '../../../utils/message'; // import message from '../../../utils/message';
import { Modal, Upload } from 'ant-design-vue' import { Modal, Upload } from 'ant-design-vue'
@ -14,21 +14,13 @@ import { formItemsForEach, generateKey } from '../../../utils'
import { CodeEditor, MODE } from '@/components/CodeEditor' import { CodeEditor, MODE } from '@/components/CodeEditor'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
export default defineComponent({ const { createMessage } = useMessage()
name: 'ImportJsonModal',
components: {
CodeEditor,
Upload,
Modal,
},
setup() {
const { createMessage } = useMessage()
const myEditor = ref(null) const myEditor = ref(null)
const state = reactive({ const state = reactive({
open: false, open: false,
json: `{ json: `{
"schemas": [ "schemas": [
{ {
"component": "input", "component": "input",
@ -46,67 +38,57 @@ export default defineComponent({
"labelCol": {}, "labelCol": {},
"wrapperCol": {} "wrapperCol": {}
}`, }`,
jsonData: { jsonData: {
schemas: {}, schemas: {},
config: {}, config: {},
},
handleSetSelectItem: null,
})
const { formDesignMethods } = useFormDesignState()
const handleCancel = () => {
state.open = false
}
const showModal = () => {
state.open = true
}
const handleImportJson = () => {
// JSON
try {
const editorJsonData = JSON.parse(state.json) as IFormConfig
editorJsonData.schemas
&& formItemsForEach(editorJsonData.schemas, (formItem) => {
generateKey(formItem)
})
formDesignMethods.setFormConfig({
...editorJsonData,
activeKey: 1,
currentItem: { component: '' },
})
handleCancel()
createMessage.success('导入成功')
}
catch {
createMessage.error('导入失败,数据格式不对')
}
}
const beforeUpload = (e: File) => {
// json
const reader = new FileReader()
reader.readAsText(e)
reader.onload = function () {
state.json = this.result as string
handleImportJson()
}
return false
}
return {
myEditor,
handleImportJson,
beforeUpload,
handleCancel,
showModal,
...toRefs(state),
MODE,
}
}, },
handleSetSelectItem: null,
}) })
const { formDesignMethods } = useFormDesignState()
function handleCancel() {
state.open = false
}
function showModal() {
state.open = true
}
function handleImportJson() {
// JSON
try {
const editorJsonData = JSON.parse(state.json) as IFormConfig
editorJsonData.schemas
&& formItemsForEach(editorJsonData.schemas, (formItem) => {
generateKey(formItem)
})
formDesignMethods.setFormConfig({
...editorJsonData,
activeKey: 1,
currentItem: { component: '' },
})
handleCancel()
createMessage.success('导入成功')
}
catch {
createMessage.error('导入失败,数据格式不对')
}
}
function beforeUpload(e: File) {
// json
const reader = new FileReader()
reader.readAsText(e)
reader.onload = function () {
state.json = this.result as string
handleImportJson()
}
return false
}
defineExpose({ showModal })
</script> </script>
<template> <template>
<Modal <Modal
title="JSON数据" title="JSON数据"
:open="open" :open="state.open"
cancel-text="关闭" cancel-text="关闭"
:destroy-on-close="true" :destroy-on-close="true"
wrap-class-name="v-code-modal" wrap-class-name="v-code-modal"
@ -126,7 +108,12 @@ export default defineComponent({
<a-button @click="handleCancel"> <a-button @click="handleCancel">
取消 取消
</a-button> </a-button>
<Upload class="upload-button" :before-upload="beforeUpload" :show-upload-list="false" accept="application/json"> <Upload
class="upload-button"
:before-upload="beforeUpload"
:show-upload-list="false"
accept="application/json"
>
<a-button type="primary"> <a-button type="primary">
导入json文件 导入json文件
</a-button> </a-button>

View File

@ -1,59 +1,51 @@
<!-- <!--
* @Description: 渲染JSON数据 * @Description: 渲染JSON数据
--> -->
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, reactive, toRefs } from 'vue' import { computed, reactive } from 'vue'
import { Modal } from 'ant-design-vue' import { Modal } from 'ant-design-vue'
import type { IFormConfig } from '../../../typings/v-form-component' import type { IFormConfig } from '../../../typings/v-form-component'
import { formatRules, removeAttrs } from '../../../utils' import { formatRules, removeAttrs } from '../../../utils'
import PreviewCode from './PreviewCode.vue' import PreviewCode from './PreviewCode.vue'
export default defineComponent({ const emit = defineEmits(['cancel'])
name: 'JsonModal',
components: { const state = reactive<{
PreviewCode, open: boolean
Modal, jsonData: IFormConfig
}, }>({
emits: ['cancel'], open: false, // json
setup(_props, { emit }) { jsonData: {} as IFormConfig, // json
const state = reactive<{ })
open: boolean /**
jsonData: IFormConfig
}>({
open: false, // json
jsonData: {} as IFormConfig, // json
})
/**
* 显示Json数据弹框 * 显示Json数据弹框
* @param jsonData * @param jsonData
*/ */
const showModal = (jsonData: IFormConfig) => { function showModal(jsonData: IFormConfig) {
formatRules(jsonData.schemas) formatRules(jsonData.schemas)
state.jsonData = jsonData state.jsonData = jsonData
state.open = true state.open = true
} }
// json // json
const editorJson = computed(() => { const editorJson = computed(() => {
return JSON.stringify(removeAttrs(state.jsonData), null, '\t') return JSON.stringify(removeAttrs(state.jsonData), null, '\t')
})
//
const handleCancel = () => {
state.open = false
emit('cancel')
}
return { ...toRefs(state), editorJson, handleCancel, showModal }
},
}) })
//
function handleCancel() {
state.open = false
emit('cancel')
}
defineExpose({ showModal })
</script> </script>
<template> <template>
<Modal <Modal
title="JSON数据" title="JSON数据"
:footer="null" :footer="null"
:open="open" :open="state.open"
:destroy-on-close="true" :destroy-on-close="true"
wrap-class-name="v-code-modal" wrap-class-name="v-code-modal"
style="top: 20px" style="top: 20px"

View File

@ -73,7 +73,13 @@ export default defineComponent({
<CodeEditor ref="myEditor" :value="editorJson" :mode="MODE.JSON" /> <CodeEditor ref="myEditor" :value="editorJson" :mode="MODE.JSON" />
</div> </div>
<div class="copy-btn-box"> <div class="copy-btn-box">
<a-button type="primary" class="copy-btn" data-clipboard-action="copy" :data-clipboard-text="editorJson" @click="handleCopyJson"> <a-button
type="primary"
class="copy-btn"
data-clipboard-action="copy"
:data-clipboard-text="editorJson"
@click="handleCopyJson"
>
复制数据 复制数据
</a-button> </a-button>
<a-button type="primary" @click="handleExportJson"> <a-button type="primary" @click="handleExportJson">

View File

@ -183,7 +183,9 @@ export const baseFormItemProps: IBaseFormAttrs[] = [
label: '控件-FormItem', label: '控件-FormItem',
component: 'Select', component: 'Select',
componentProps: { componentProps: {
options: baseComponents.concat(customComponents).map(item => ({ value: item.component, label: item.label })), options: baseComponents
.concat(customComponents)
.map(item => ({ value: item.component, label: item.label })),
}, },
}, },
{ {

View File

@ -1,9 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { type Ref, provide, ref } from 'vue' import type { Ref } from 'vue'
import { provide, ref } from 'vue'
import { Layout, LayoutContent, LayoutSider } from 'ant-design-vue' import { Layout, LayoutContent, LayoutSider } from 'ant-design-vue'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { type UseRefHistoryReturn, useRefHistory } from '@vueuse/core' import type { UseRefHistoryReturn } from '@vueuse/core'
import { useRefHistory } from '@vueuse/core'
import VFormPreview from '../VFormPreview/index.vue' import VFormPreview from '../VFormPreview/index.vue'
import VFormPreview2 from '../VFormPreview/useForm.vue' import VFormPreview2 from '../VFormPreview/useForm.vue'
import type { IFormConfig, IVFormComponent, PropsTabKey } from '../../typings/v-form-component' import type { IFormConfig, IVFormComponent, PropsTabKey } from '../../typings/v-form-component'
@ -13,18 +14,14 @@ import type { IFormDesignMethods, IPropsPanel, IToolbarMethods } from '../../typ
import CollapseItem from './modules/CollapseItem.vue' import CollapseItem from './modules/CollapseItem.vue'
import FormComponentPanel from './modules/FormComponentPanel.vue' import FormComponentPanel from './modules/FormComponentPanel.vue'
import JsonModal from './components/JsonModal.vue' import JsonModal from './components/JsonModal.vue'
import Toolbar from './modules/Toolbar.vue' import Toolbar from './modules/Toolbar.vue'
import PropsPanel from './modules/PropsPanel.vue' import PropsPanel from './modules/PropsPanel.vue'
import ImportJsonModal from './components/ImportJsonModal.vue' import ImportJsonModal from './components/ImportJsonModal.vue'
import CodeModal from './components/CodeModal.vue' import CodeModal from './components/CodeModal.vue'
import 'codemirror/mode/javascript/javascript'
import { globalConfigState } from './config/formItemPropsConfig' import { globalConfigState } from './config/formItemPropsConfig'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { CollapseContainer } from '@/components/Container/index' import { CollapseContainer } from '@/components/Container/index'
import 'codemirror/mode/javascript/javascript'
defineProps({ defineProps({
title: { title: {
@ -94,7 +91,9 @@ const historyReturn = useRefHistory(formConfig, {
*/ */
function handleSetSelectItem(schema: IVFormComponent) { function handleSetSelectItem(schema: IVFormComponent) {
formConfig.value.currentItem = schema formConfig.value.currentItem = schema
handleChangePropsTabs(schema.key ? (formConfig.value.activeKey! === 1 ? 2 : formConfig.value.activeKey!) : 1) handleChangePropsTabs(
schema.key ? (formConfig.value.activeKey! === 1 ? 2 : formConfig.value.activeKey!) : 1,
)
} }
function setGlobalConfigState(formItem: IVFormComponent) { function setGlobalConfigState(formItem: IVFormComponent) {
@ -154,7 +153,8 @@ function copyFormItem(formItem: IVFormComponent) {
* @param item {IVFormComponent} 当前点击的组件 * @param item {IVFormComponent} 当前点击的组件
* @param isCopy {boolean} 是否复制 * @param isCopy {boolean} 是否复制
*/ */
function handleCopy(item: IVFormComponent = formConfig.value.currentItem as IVFormComponent, isCopy = true) { function handleCopy(item: IVFormComponent = formConfig.value.currentItem as IVFormComponent,
isCopy = true) {
const key = formConfig.value.currentItem?.key const key = formConfig.value.currentItem?.key
/** /**
* 遍历当表单项配置如果是复制则复制一份表单项如果不是复制则直接添加到表单项中 * 遍历当表单项配置如果是复制则复制一份表单项如果不是复制则直接添加到表单项中
@ -165,7 +165,9 @@ function handleCopy(item: IVFormComponent = formConfig.value.currentItem as IVFo
schemas.some((formItem: IVFormComponent, index: number) => { schemas.some((formItem: IVFormComponent, index: number) => {
if (formItem.key === key) { if (formItem.key === key) {
// //
isCopy ? schemas.splice(index, 0, copyFormItem(formItem)) : schemas.splice(index + 1, 0, item) isCopy
? schemas.splice(index, 0, copyFormItem(formItem))
: schemas.splice(index + 1, 0, item)
const event = { const event = {
newIndex: index + 1, newIndex: index + 1,
} }
@ -254,7 +256,7 @@ provide<IFormDesignMethods>('formDesignMethods', {
collapsed-width="0" collapsed-width="0"
width="270" width="270"
:zero-width-trigger-style="{ :zero-width-trigger-style="{
'margin-top': '-70px', 'margin-top': '-60px',
'background-color': 'gray', 'background-color': 'gray',
}" }"
breakpoint="md" breakpoint="md"
@ -293,7 +295,11 @@ provide<IFormDesignMethods>('formDesignMethods', {
@handle-open-code-modal="handleOpenModal(codeModal!)" @handle-open-code-modal="handleOpenModal(codeModal!)"
@handle-clear-form-items="handleClearFormItems" @handle-clear-form-items="handleClearFormItems"
/> />
<FormComponentPanel :current-item="formConfig.currentItem" :data="formConfig" @handle-set-select-item="handleSetSelectItem" /> <FormComponentPanel
:current-item="formConfig.currentItem"
:data="formConfig"
@handle-set-select-item="handleSetSelectItem"
/>
</LayoutContent> </LayoutContent>
<LayoutSider <LayoutSider
:class="`right ${prefixCls}-sider`" :class="`right ${prefixCls}-sider`"
@ -301,12 +307,15 @@ provide<IFormDesignMethods>('formDesignMethods', {
:reverse-arrow="true" :reverse-arrow="true"
collapsed-width="0" collapsed-width="0"
width="270" width="270"
:zero-width-trigger-style="{ 'margin-top': '-70px', 'background-color': 'gray' }" :zero-width-trigger-style="{ 'margin-top': '-60px', 'background-color': 'gray' }"
breakpoint="lg" breakpoint="lg"
> >
<PropsPanel ref="propsPanel" :active-key="formConfig.activeKey"> <PropsPanel ref="propsPanel" :active-key="formConfig.activeKey">
<template v-for="item of formConfig.schemas" #[`${item.component}Props`]="data"> <template v-for="item of formConfig.schemas" #[`${item.component}Props`]="data">
<slot :name="`${item.component}Props`" v-bind="{ formItem: data, props: data.componentProps }" /> <slot
:name="`${item.component}Props`"
v-bind="{ formItem: data, props: data.componentProps }"
/>
</template> </template>
</PropsPanel> </PropsPanel>
</LayoutSider> </LayoutSider>

View File

@ -2,24 +2,25 @@
import { defineComponent, reactive } from 'vue' import { defineComponent, reactive } from 'vue'
import draggable from 'vuedraggable' import draggable from 'vuedraggable'
import type { IVFormComponent } from '../../../typings/v-form-component' import type { IVFormComponent } from '../../../typings/v-form-component'
// import { toRefs } from '@vueuse/core';
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import { useDesign } from '@/hooks/web/useDesign'
export default defineComponent({ export default defineComponent({
name: 'CollapseItem', name: 'CollapseItem',
components: { Draggable: draggable, Icon }, components: { Draggable: draggable, Icon },
props: { props: {
list: { list: {
type: [Array] as PropType<IVFormComponent[]>, type: [Array],
default: () => [], default: () => [],
}, },
handleListPush: { handleListPush: {
type: Function as PropType<(item: IVFormComponent) => void>, type: Function,
default: null, default: null,
}, },
}, },
setup(props, { emit }) { setup(props, { emit }) {
const { prefixCls } = useDesign('form-design-collapse-item')
const state = reactive({}) const state = reactive({})
const handleStart = (e: any, list1: IVFormComponent[]) => { const handleStart = (e: any, list1: IVFormComponent[]) => {
emit('start', list1[e.oldIndex].component) emit('start', list1[e.oldIndex].component)
@ -32,13 +33,13 @@ export default defineComponent({
const cloneItem = (one) => { const cloneItem = (one) => {
return props.handleListPush(one) return props.handleListPush(one)
} }
return { state, handleStart, handleAdd, cloneItem } return { prefixCls, state, handleStart, handleAdd, cloneItem }
}, },
}) })
</script> </script>
<template> <template>
<div> <div :class="prefixCls">
<Draggable <Draggable
tag="ul" tag="ul"
:model-value="list" :model-value="list"
@ -54,7 +55,11 @@ export default defineComponent({
@add="handleAdd" @add="handleAdd"
> >
<template #item="{ element, index }"> <template #item="{ element, index }">
<li class="bs-box text-ellipsis" @dragstart="$emit('add-attrs', list, index)" @click="$emit('handle-list-push', element)"> <li
class="bs-box text-ellipsis"
@dragstart="$emit('add-attrs', list, index)"
@click="$emit('handle-list-push', element)"
>
<!-- <svg v-if="element.icon.indexOf('icon-') > -1" class="icon" aria-hidden="true"> <!-- <svg v-if="element.icon.indexOf('icon-') > -1" class="icon" aria-hidden="true">
<use :xlink:href="`#${element.icon}`" /> <use :xlink:href="`#${element.icon}`" />
</svg> --> </svg> -->
@ -67,34 +72,42 @@ export default defineComponent({
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
@import url('../styles/variable.less'); @prefix-cls: ~'@{namespace}-form-design-collapse-item';
ul { @import url('../styles/variable.less');
display: flex;
flex-wrap: wrap;
padding: 5px;
margin-bottom: 0;
list-style: none;
// background: #efefef;
li { .@{prefix-cls} {
width: calc(50% - 6px); ul {
height: 36px; display: flex;
padding: 8px 12px; flex-wrap: wrap;
margin: 2.7px; padding: 5px;
line-height: 20px; margin-bottom: 0;
cursor: move; list-style: none;
border: 1px solid var(--border-color); // background: #efefef;
border-radius: 3px;
transition: all 0.3s;
&:hover { li {
position: relative; width: calc(50% - 6px);
height: 36px;
padding: 8px 12px;
margin: 2.7px;
line-height: 20px;
cursor: move;
border: 1px solid @border-color;
border-radius: 3px;
transition: all 0.3s;
&:hover {
position: relative;
color: @primary-color;
border: 1px solid @primary-color;
// z-index: 1;
box-shadow: 0 2px 6px @primary-color;
}
}
}
svg {
display: inline !important;
} }
} }
}
svg {
display: inline !important;
}
</style> </style>

View File

@ -7,8 +7,8 @@ import draggable from 'vuedraggable'
import { computed, defineComponent } from 'vue' import { computed, defineComponent } from 'vue'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { Empty, Form } from 'ant-design-vue' import { Empty, Form } from 'ant-design-vue'
import LayoutItem from '../components/LayoutItem.vue'
import { useFormDesignState } from '../../../hooks/useFormDesignState' import { useFormDesignState } from '../../../hooks/useFormDesignState'
import LayoutItem from '../components/LayoutItem.vue'
export default defineComponent({ export default defineComponent({
name: 'FormComponentPanel', name: 'FormComponentPanel',
@ -20,12 +20,12 @@ export default defineComponent({
}, },
emits: ['handleSetSelectItem'], emits: ['handleSetSelectItem'],
setup(_, { emit }) { setup(_, { emit }) {
const { formConfig } = useFormDesignState() as Recordable const { formConfig } = useFormDesignState()
/** /**
* 拖拽完成事件 * 拖拽完成事件
* @param newIndex * @param newIndex
*/ */
const addItem = ({ newIndex }: any) => { const addItem = ({ newIndex }: any) => {
formConfig.value.schemas = formConfig.value.schemas || [] formConfig.value.schemas = formConfig.value.schemas || []
@ -35,9 +35,9 @@ export default defineComponent({
} }
/** /**
* 拖拽开始事件 * 拖拽开始事件
* @param e {Object} 事件对象 * @param e {Object} 事件对象
*/ */
const handleDragStart = (e: any) => { const handleDragStart = (e: any) => {
emit('handleSetSelectItem', formConfig.value.schemas[e.oldIndex]) emit('handleSetSelectItem', formConfig.value.schemas[e.oldIndex])
} }
@ -61,7 +61,11 @@ export default defineComponent({
<template> <template>
<div class="form-panel v-form-container"> <div class="form-panel v-form-container">
<Empty v-show="formConfig.schemas.length === 0" class="empty-text" description="从左侧选择控件添加" /> <Empty
v-show="formConfig.schemas.length === 0"
class="empty-text"
description="从左侧选择控件添加"
/>
<Form v-bind="formConfig"> <Form v-bind="formConfig">
<div class="draggable-box"> <div class="draggable-box">
<Draggable <Draggable
@ -77,7 +81,12 @@ export default defineComponent({
@start="handleDragStart" @start="handleDragStart"
> >
<template #item="{ element }"> <template #item="{ element }">
<LayoutItem class="drag-move" :schema="element" :data="formConfig" :current-item="formConfig.currentItem || {}" /> <LayoutItem
class="drag-move"
:schema="element"
:data="formConfig"
:current-item="formConfig.currentItem || {}"
/>
</template> </template>
</Draggable> </Draggable>
</div> </div>
@ -86,73 +95,72 @@ export default defineComponent({
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
@import url('../styles/variable.less'); @import url('../styles/variable.less');
@import url('../styles/drag.less'); @import url('../styles/drag.less');
.v-form-container { .v-form-container {
// //
.ant-form-inline { .ant-form-inline {
.list-main { .list-main {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-content: flex-start; align-content: flex-start;
justify-content: flex-start; justify-content: flex-start;
.layout-width { .layout-width {
width: 100%; width: 100%;
} }
}
.ant-form-item-control-wrapper {
min-width: 175px !important;
}
}
}
.form-panel {
position: relative;
height: 100%;
.empty-text {
position: absolute;
inset: -10% 0 0;
z-index: 100;
height: 150px;
margin: auto;
color: #aaa;
}
.draggable-box {
height: calc(100vh - 200px);
overflow: auto;
.drag-move {
min-height: 62px;
cursor: move;
}
.list-main {
// height: 100%;
// overflow: auto;
//
.list-enter-active {
transition: all 0.5s;
} }
.list-leave-active { .ant-form-item-control-wrapper {
transition: all 0.3s; min-width: 175px !important;
} }
}
.list-enter, }
.list-leave-to {
opacity: 0; .form-panel {
transform: translateX(-100px); position: relative;
} height: 100%;
.list-enter { .empty-text {
height: 30px; position: absolute;
inset: -10% 0 0;
z-index: 100;
height: 150px;
margin: auto;
color: #aaa;
}
.draggable-box {
height: calc(100vh - 200px);
// width: 100%;
overflow: auto;
.drag-move {
min-height: 62px;
cursor: move;
}
.list-main {
//
.list-enter-active {
transition: all 0.5s;
}
.list-leave-active {
transition: all 0.3s;
}
.list-enter,
.list-leave-to {
opacity: 0;
transform: translateX(-100px);
}
.list-enter {
height: 30px;
}
} }
} }
} }
}
</style> </style>

View File

@ -28,7 +28,9 @@ export default defineComponent({
setup() { setup() {
const { formConfig } = useFormDesignState() const { formConfig } = useFormDesignState()
const slotProps = computed(() => { const slotProps = computed(() => {
return customComponents.find(item => item.component === formConfig.value.currentItem?.component) return customComponents.find(
item => item.component === formConfig.value.currentItem?.component,
)
}) })
return { formConfig, customComponents, slotProps } return { formConfig, customComponents, slotProps }
}, },
@ -56,43 +58,43 @@ export default defineComponent({
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
@import url('../styles/variable.less'); @import url('../styles/variable.less');
:deep(.ant-tabs) { :deep(.ant-tabs) {
box-sizing: border-box; box-sizing: border-box;
form { form {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: calc(100% - 50px); height: calc(100% - 50px);
margin-right: 10px; margin-right: 10px;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
} }
.hint-box { .hint-box {
margin-top: 200px; margin-top: 200px;
} }
.ant-form-item, .ant-form-item,
.ant-slider-with-marks { .ant-slider-with-marks {
margin-right: 20px; margin-right: 20px;
margin-bottom: 0; margin-bottom: 0;
margin-left: 10px; margin-left: 10px;
} }
.ant-form-item { .ant-form-item {
// width: 100%; // width: 100%;
margin-bottom: 0; margin-bottom: 0;
.ant-form-item-label { .ant-form-item-label {
line-height: 2; line-height: 2;
vertical-align: text-top; vertical-align: text-top;
}
}
.ant-input-number {
width: 100%;
} }
} }
.ant-input-number {
width: 100%;
}
}
</style> </style>

View File

@ -6,7 +6,7 @@ import { defineComponent, inject, reactive, toRefs } from 'vue'
import type { UseRefHistoryReturn } from '@vueuse/core' import type { UseRefHistoryReturn } from '@vueuse/core'
import { Divider, Tooltip } from 'ant-design-vue' import { Divider, Tooltip } from 'ant-design-vue'
import type { IFormConfig } from '../../../typings/v-form-component' import type { IFormConfig } from '../../../typings/v-form-component'
import Icon from '@/components/Icon/index' import { Icon } from '@/components/Icon'
interface IToolbarsConfig { interface IToolbarsConfig {
type: string type: string
@ -100,34 +100,38 @@ export default defineComponent({
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
//noinspection CssUnknownTarget //noinspection CssUnknownTarget
@import url('../styles/variable.less'); @import url('../styles/variable.less');
.operating-area { .operating-area {
display: flex; display: flex;
align-content: center; align-content: center;
justify-content: space-between; justify-content: space-between;
height: @operating-area-height; height: @operating-area-height;
padding: 0 12px; padding: 0 12px;
padding-left: 30px; padding-left: 40px;
font-size: 16px; font-size: 16px;
line-height: @operating-area-height; line-height: @operating-area-height;
text-align: left; text-align: left;
border-bottom: 2px solid var(--border-color); border-bottom: 2px solid @border-color;
a { a {
margin: 0 5px; margin: 0 5px;
color: #666; color: #666;
&.disabled, &.disabled,
&.disabled:hover { &.disabled:hover {
color: #ccc; color: #ccc;
} }
> span { &:hover {
padding-left: 2px; color: @primary-color;
font-size: 14px; }
> span {
padding-left: 2px;
font-size: 14px;
}
} }
} }
}
</style> </style>

View File

@ -2,7 +2,6 @@
height: 100%; height: 100%;
overflow: auto; overflow: auto;
/* stylelint-disable-next-line selector-pseudo-class-no-unknown */
:deep(.list-main) { :deep(.list-main) {
position: relative; position: relative;
padding: 5px; padding: 5px;
@ -17,12 +16,13 @@
overflow: hidden; overflow: hidden;
&::before { &::before {
content: '';
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
width: 100%; width: 100%;
height: 5px; height: 5px;
content: ""; background-color: @primary-color;
} }
} }
@ -34,19 +34,25 @@
overflow: hidden; overflow: hidden;
transition: all 0.3s; transition: all 0.3s;
&:hover {
background-color: @primary-hover-bg-color;
}
// 选择时 start // 选择时 start
&::before { &::before {
content: '';
position: absolute; position: absolute;
top: 0; top: 0;
right: -100%; right: -100%;
width: 100%; width: 100%;
height: 5px; height: 5px;
content: "";
transition: all 0.3s; transition: all 0.3s;
background-color: @primary-color;
} }
&.active { &.active {
outline-offset: 0; outline-offset: 0;
background-color: @primary-hover-bg-color;
&::before { &::before {
right: 0; right: 0;
@ -60,18 +66,18 @@
word-wrap: break-word; word-wrap: break-word;
&::before { &::before {
content: '';
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
content: "";
} }
.ant-form-item { .ant-form-item {
padding-bottom: 6px;
// 修改ant form-item的margin为padding // 修改ant form-item的margin为padding
margin: 0; margin: 0;
padding-bottom: 6px;
} }
} }
@ -80,8 +86,9 @@
position: absolute; position: absolute;
right: 5px; right: 5px;
bottom: 2px; bottom: 2px;
font-size: 14px;
// z-index: 999; // z-index: 999;
color: @primary-color;
font-size: 14px;
} }
.copy, .copy,
@ -90,15 +97,15 @@
top: 0; top: 0;
width: 30px; width: 30px;
height: 30px; height: 30px;
line-height: 30px;
color: #fff;
text-align: center;
// z-index: 989; // z-index: 989;
transition: all 0.3s; transition: all 0.3s;
color: #fff;
line-height: 30px;
text-align: center;
&.unactivated { &.unactivated {
pointer-events: none;
opacity: 0 !important; opacity: 0 !important;
pointer-events: none;
} }
&.active { &.active {
@ -109,10 +116,12 @@
.copy { .copy {
right: 30px; right: 30px;
border-radius: 0 0 0 8px; border-radius: 0 0 0 8px;
background-color: @primary-color;
} }
.delete { .delete {
right: 0; right: 0;
background-color: @primary-color;
} }
} }
@ -122,17 +131,17 @@
width: 100%; width: 100%;
padding: 5px; padding: 5px;
overflow: hidden; overflow: hidden;
background-color: @layout-background-color;
transition: all 0.3s; transition: all 0.3s;
background-color: @layout-background-color;
.form-item-box { .form-item-box {
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
.ant-form-item { .ant-form-item {
padding-bottom: 15px;
// 修改ant form-item的margin为padding // 修改ant form-item的margin为padding
margin: 0; margin: 0;
padding-bottom: 15px;
} }
} }
@ -157,19 +166,19 @@
// 选择时 start // 选择时 start
&::before { &::before {
content: '';
position: absolute; position: absolute;
top: 0; top: 0;
right: -100%; right: -100%;
width: 100%; width: 100%;
height: 5px; height: 5px;
content: "";
background: transparent;
transition: all 0.3s; transition: all 0.3s;
background: transparent;
} }
&.active { &.active {
background-color: @layout-hover-bg-color;
outline-offset: 0; outline-offset: 0;
background-color: @layout-hover-bg-color;
&::before { &::before {
right: 0; right: 0;
@ -184,15 +193,15 @@
top: 0; top: 0;
width: 30px; width: 30px;
height: 30px; height: 30px;
line-height: 30px;
color: #fff;
text-align: center;
// z-index: 989; // z-index: 989;
transition: all 0.3s; transition: all 0.3s;
color: #fff;
line-height: 30px;
text-align: center;
&.unactivated { &.unactivated {
pointer-events: none;
opacity: 0 !important; opacity: 0 !important;
pointer-events: none;
} }
&.active { &.active {
@ -202,8 +211,8 @@
> .copy { > .copy {
right: 30px; right: 30px;
background-color: @layout-color;
border-radius: 0 0 0 8px; border-radius: 0 0 0 8px;
background-color: @layout-color;
} }
> .delete { > .delete {

View File

@ -1,166 +1,143 @@
<!-- <!--
* @Description: * @Description:
--> -->
<script lang="ts"> <script lang="ts" setup>
import type { PropType } from 'vue' import type { PropType } from 'vue'
import { computed, defineComponent, reactive, toRefs, unref } from 'vue' import { computed, unref } from 'vue'
import { asyncComputed } from '@vueuse/core' import { asyncComputed } from '@vueuse/core'
import { omit } from 'lodash-es' import { omit } from 'lodash-es'
import { Col, Divider, FormItem, Tooltip } from 'ant-design-vue' import { Col, Divider, FormItem, Tooltip } from 'ant-design-vue'
import { componentMap } from '../../core/formItemConfig' import { componentMap } from '../../core/formItemConfig'
import type { IFormConfig, IVFormComponent } from '../../typings/v-form-component' import type { IFormConfig, IVFormComponent } from '../../typings/v-form-component'
import { handleAsyncOptions } from '../../utils' import { handleAsyncOptions } from '../../utils'
// import FormItem from '/@/components/Form/src/components/FormItem.vue';
import { useFormModelState } from '../../hooks/useFormDesignState' import { useFormModelState } from '../../hooks/useFormDesignState'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
export default defineComponent({ const props = defineProps({
name: 'VFormItem', formData: {
components: { type: Object,
Tooltip, default: () => ({}),
Icon,
FormItem,
Divider,
Col,
}, },
schema: {
props: { type: Object as PropType<IVFormComponent>,
formData: { required: true,
type: Object,
default: () => ({}),
},
schema: {
type: Object as PropType<IVFormComponent>,
required: true,
},
formConfig: {
type: Object as PropType<IFormConfig>,
required: true,
},
}, },
emits: ['update:form-data', 'change'], formConfig: {
setup(props, { emit }) { type: Object as PropType<IFormConfig>,
const state = reactive({ required: true,
componentMap, },
}) })
const { formModel: formData1, setFormModel } = useFormModelState() const emit = defineEmits(['update:form-data', 'change'])
const colPropsComputed = computed(() => {
const { colProps = {} } = props.schema
return colProps
})
const formItemProps = computed(() => {
const { formConfig } = unref(props)
let { field, required, rules, labelCol, wrapperCol } = unref(props.schema)
const { colon } = props.formConfig
const { itemProps } = unref(props.schema) const { formModel: formData1, setFormModel } = useFormModelState()
const colPropsComputed = computed(() => {
const { colProps = {} } = props.schema
return colProps
})
const formItemProps = computed(() => {
const { formConfig } = unref(props)
let { field, required, rules, labelCol, wrapperCol } = unref(props.schema)
const { colon } = props.formConfig
// <editor-fold desc=""> const { itemProps } = unref(props.schema)
labelCol = labelCol || (formConfig.layout === 'horizontal'
? formConfig.labelLayout === 'flex'
? { style: `width:${formConfig.labelWidth}px` }
: formConfig.labelCol
: {})
wrapperCol = wrapperCol || (formConfig.layout === 'horizontal' // <editor-fold desc="">
? formConfig.labelLayout === 'flex' labelCol = labelCol || (formConfig.layout === 'horizontal'
? { style: 'width:auto;flex:1' } ? formConfig.labelLayout === 'flex'
: formConfig.wrapperCol ? { style: `width:${formConfig.labelWidth}px` }
: {}) : formConfig.labelCol
: {})
const style = formConfig.layout === 'horizontal' && formConfig.labelLayout === 'flex' ? { display: 'flex' } : {} wrapperCol = wrapperCol || (formConfig.layout === 'horizontal'
? formConfig.labelLayout === 'flex'
? { style: 'width:auto;flex:1' }
: formConfig.wrapperCol
: {})
/** const style
= formConfig.layout === 'horizontal' && formConfig.labelLayout === 'flex'
? { display: 'flex' }
: {}
/**
* 将字符串正则格式化成正则表达式 * 将字符串正则格式化成正则表达式
*/ */
const newConfig = Object.assign( const newConfig = Object.assign(
{}, {},
{ {
name: field, name: field,
style: { ...style }, style: { ...style },
colon, colon,
required, required,
rules, rules,
labelCol, labelCol,
wrapperCol, wrapperCol,
}, },
itemProps, itemProps,
) )
if (!itemProps?.labelCol?.span) if (!itemProps?.labelCol?.span)
newConfig.labelCol = labelCol newConfig.labelCol = labelCol
if (!itemProps?.wrapperCol?.span) if (!itemProps?.wrapperCol?.span)
newConfig.wrapperCol = wrapperCol newConfig.wrapperCol = wrapperCol
if (!itemProps?.rules) if (!itemProps?.rules)
newConfig.rules = rules newConfig.rules = rules
return newConfig return newConfig
}) as Recordable }) as Recordable<any>
const componentItem = computed(() => componentMap.get(props.schema.component as string)) const componentItem = computed(() => componentMap.get(props.schema.component as string))
// console.log('component change:', props.schema.component, componentItem.value); // console.log('component change:', props.schema.component, componentItem.value);
const handleClick = (schema: IVFormComponent) => { function handleClick(schema: IVFormComponent) {
if (schema.component === 'Button' && schema.componentProps?.handle) if (schema.component === 'Button' && schema.componentProps?.handle)
emit(schema.componentProps?.handle) emit(schema.componentProps?.handle)
} }
/** /**
* 处理异步属性异步属性会导致一些属性渲染错误如defaultValue异步加载会导致渲染不出来故而此处只处理optionstreeData同步属性在cmpProps中处理 * 处理异步属性异步属性会导致一些属性渲染错误如defaultValue异步加载会导致渲染不出来故而此处只处理optionstreeData同步属性在cmpProps中处理
*/ */
const asyncProps = asyncComputed(async () => { const asyncProps = asyncComputed(async () => {
let { options, treeData } = props.schema.componentProps ?? {} let { options, treeData } = props.schema.componentProps ?? {}
if (options) if (options)
options = await handleAsyncOptions(options) options = await handleAsyncOptions(options)
if (treeData) if (treeData)
treeData = await handleAsyncOptions(treeData) treeData = await handleAsyncOptions(treeData)
return { return {
options, options,
treeData, treeData,
} }
}) })
/** /**
* 处理同步属性 * 处理同步属性
*/ */
const cmpProps = computed(() => { const cmpProps = computed(() => {
const isCheck = props.schema && ['Switch', 'Checkbox', 'Radio'].includes(props.schema.component) const isCheck
const { field } = props.schema = props.schema && ['Switch', 'Checkbox', 'Radio'].includes(props.schema.component)
const { field } = props.schema
let { disabled, ...attrs } = omit(props.schema.componentProps, ['options', 'treeData']) ?? {} let { disabled, ...attrs }
= omit(props.schema.componentProps, ['options', 'treeData']) ?? {}
disabled = props.formConfig.disabled || disabled disabled = props.formConfig.disabled || disabled
return { return {
...attrs, ...attrs,
disabled, disabled,
[isCheck ? 'checked' : 'value']: formData1.value[field!], [isCheck ? 'checked' : 'value']: formData1.value[field!],
} }
})
const handleChange = function (e) {
const isCheck = ['Switch', 'Checkbox', 'Radio'].includes(props.schema.component)
const target = e ? e.target : null
const value = target ? (isCheck ? target.checked : target.value) : e
setFormModel(props.schema.field!, value)
emit('change', value)
}
return {
...toRefs(state),
componentItem,
formItemProps,
handleClick,
asyncProps,
cmpProps,
handleChange,
colPropsComputed,
}
},
}) })
const handleChange = function (e) {
const isCheck = ['Switch', 'Checkbox', 'Radio'].includes(props.schema.component)
const target = e ? e.target : null
const value = target ? (isCheck ? target.checked : target.value) : e
setFormModel(props.schema.field!, value)
emit('change', value)
}
</script> </script>
<template> <template>
@ -176,8 +153,14 @@ export default defineComponent({
</Tooltip> </Tooltip>
</template> </template>
<slot v-if="schema.componentProps && schema.componentProps?.slotName" :name="schema.componentProps.slotName" v-bind="schema" /> <slot
<Divider v-else-if="schema.component === 'Divider' && schema.label && !formItemProps.hiddenLabel"> v-if="schema.componentProps && schema.componentProps?.slotName"
:name="schema.componentProps.slotName"
v-bind="schema"
/>
<Divider
v-else-if="schema.component === 'Divider' && schema.label && !formItemProps.hiddenLabel"
>
{{ schema.label }} {{ schema.label }}
</Divider> </Divider>
<!-- 部分控件需要一个空div --> <!-- 部分控件需要一个空div -->
@ -199,20 +182,20 @@ export default defineComponent({
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.ml-5 { .ml-5 {
margin-left: 5px; margin-left: 5px;
} }
// formant-col使width:100% // formant-col使width:100%
:deep(.ant-col) { :deep(.ant-col) {
width: auto; width: auto;
} }
.ant-form-item:not(.ant-form-item-with-help) { .ant-form-item:not(.ant-form-item-with-help) {
margin-bottom: 20px; margin-bottom: 20px;
} }
// .w-full { // .w-full {
// width: 100% !important; // width: 100% !important;
// } // }
</style> </style>

View File

@ -11,59 +11,46 @@
> >
--> -->
<script lang="ts"> <script lang="ts" setup>
import { computed, defineComponent, unref } from 'vue' import { computed, unref } from 'vue'
import type { IFormConfig, IVFormComponent } from '../../typings/v-form-component' import type { IFormConfig, IVFormComponent } from '../../typings/v-form-component'
import type { FormProps, FormSchema } from '@/components/Form' import type { FormProps, FormSchema } from '@/components/Form'
import FormItem from '/@/components/Form/src/components/FormItem.vue' import FormItem from '@/components/Form/src/components/FormItem.vue'
export default defineComponent({ const props = defineProps({
name: 'VFormItem', formData: {
components: { type: Object,
FormItem, default: () => ({}),
}, },
props: { schema: {
formData: { type: Object as PropType<IVFormComponent>,
type: Object, required: true,
default: () => ({}),
},
schema: {
type: Object as PropType<IVFormComponent>,
required: true,
},
formConfig: {
type: Object as PropType<IFormConfig>,
required: true,
},
}, },
setup(props) { formConfig: {
const schema = computed(() => { type: Object as PropType<IFormConfig>,
const schema: FormSchema = { required: true,
...unref(props.schema), },
} as FormSchema })
return schema const schema = computed(() => {
}) const schema: FormSchema = {
...unref(props.schema),
} as FormSchema
// Get the basic configuration of the form return schema
const getProps = computed((): FormProps => { })
return { ...unref(props.formConfig) } as FormProps
}) // Get the basic configuration of the form
return { const getProps = computed((): FormProps => {
schemaNew: schema, return { ...unref(props.formConfig) } as FormProps
getProps,
}
},
}) })
</script> </script>
<template> <template>
<FormItem :schema="schemaNew" :form-props="getProps"> <FormItem :schema="schema" :form-props="getProps">
<template v-for="item in Object.keys($slots)" #[item]="data"> <template v-for="item in Object.keys($slots)" #[item]="data">
<slot :name="item" v-bind="data || {}" /> <slot :name="item" v-bind="data || {}" />
</template> </template>
</FormItem> </FormItem>
</template> </template>
<style lang="less" scoped></style>

View File

@ -1,8 +1,8 @@
<!-- <!--
* @Description: 渲染组件无法使用Vben的组件 * @Description: 渲染组件无法使用Vben的组件
--> -->
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, reactive, ref, toRefs } from 'vue' import { reactive, ref } from 'vue'
import { Modal } from 'ant-design-vue' import { Modal } from 'ant-design-vue'
import type { IFormConfig } from '../../typings/v-form-component' import type { IFormConfig } from '../../typings/v-form-component'
import type { IAnyObject } from '../../typings/base-type' import type { IAnyObject } from '../../typings/base-type'
@ -12,74 +12,57 @@ import type { IVFormMethods } from '../../hooks/useVFormMethods'
import JsonModal from '../VFormDesign/components/JsonModal.vue' import JsonModal from '../VFormDesign/components/JsonModal.vue'
import type { IToolbarMethods } from '../../typings/form-type' import type { IToolbarMethods } from '../../typings/form-type'
export default defineComponent({ const jsonModal = ref<IToolbarMethods | null>(null)
name: 'VFormPreview', const state = reactive<{
components: { formModel: IAnyObject
JsonModal, open: boolean
VFormCreate, formConfig: IFormConfig
Modal, fApi: IVFormMethods
}, }>({
setup() { formModel: {},
const jsonModal = ref<IToolbarMethods | null>(null) formConfig: {} as IFormConfig,
const state = reactive<{ open: false,
formModel: IAnyObject fApi: {} as IVFormMethods,
open: boolean })
formConfig: IFormConfig
fApi: IVFormMethods
}>({
formModel: {},
formConfig: {} as IFormConfig,
open: false,
fApi: {} as IVFormMethods,
})
/** /**
* 显示Json数据弹框 * 显示Json数据弹框
* @param jsonData * @param jsonData
*/ */
const showModal = (jsonData: IFormConfig) => { function showModal(jsonData: IFormConfig) {
// console.log('showModal-', jsonData); // console.log('showModal-', jsonData);
formatRules(jsonData.schemas) formatRules(jsonData.schemas)
state.formConfig = jsonData state.formConfig = jsonData
state.open = true state.open = true
} }
/** /**
* 获取表单数据 * 获取表单数据
* @return {Promise<void>} * @return {Promise<void>}
*/ */
const handleCancel = () => { function handleCancel() {
state.open = false state.open = false
state.formModel = {} onCancel()
} }
const handleGetData = async () => { async function handleGetData() {
const _data = await state.fApi.submit?.() const _data = await state.fApi.submit?.()
jsonModal.value?.showModal?.(_data) jsonModal.value?.showModal?.(_data)
} }
const onSubmit = (_data: IAnyObject) => { function onSubmit(_data: IAnyObject) {
// //
} }
const onCancel = () => { function onCancel() {
state.formModel = {} state.formModel = {}
} }
return {
handleGetData, defineExpose({ showModal, onCancel })
handleCancel,
...toRefs(state),
showModal,
jsonModal,
onSubmit,
onCancel,
}
},
})
</script> </script>
<template> <template>
<Modal <Modal
title="预览(支持布局)" title="预览(支持布局)"
:open="open" :open="state.open"
ok-text="获取数据" ok-text="获取数据"
cancel-text="关闭" cancel-text="关闭"
style="top: 20px" style="top: 20px"
@ -88,7 +71,12 @@ export default defineComponent({
@ok="handleGetData" @ok="handleGetData"
@cancel="handleCancel" @cancel="handleCancel"
> >
<VFormCreate v-model:fApi="fApi" v-model:formModel="formModel" :form-config="formConfig" @submit="onSubmit"> <VFormCreate
v-model:fApi="state.fApi"
v-model:formModel="state.formModel"
:form-config="state.formConfig"
@submit="onSubmit"
>
<template #slotName="{ formModel, field }"> <template #slotName="{ formModel, field }">
<a-input v-model:value="formModel[field]" placeholder="我是插槽传递的输入框" /> <a-input v-model:value="formModel[field]" placeholder="我是插槽传递的输入框" />
</template> </template>

View File

@ -1,11 +1,16 @@
import { getCurrentInstance, toRaw } from 'vue'
import type { Ref, SetupContext } from 'vue' import type { Ref, SetupContext } from 'vue'
import { getCurrentInstance, toRaw } from 'vue'
import { cloneDeep, forOwn, isFunction } from 'lodash-es' import { cloneDeep, forOwn, isFunction } from 'lodash-es'
import { Form } from 'ant-design-vue' import { Form } from 'ant-design-vue'
import type { AForm, IVFormComponent } from '../typings/v-form-component' import type { AForm, IVFormComponent } from '../typings/v-form-component'
import type { IAnyObject } from '../typings/base-type' import type { IAnyObject } from '../typings/base-type'
export function useFormInstanceMethods(props: IAnyObject, formdata, context: Partial<SetupContext>, _formInstance: Ref<AForm | null>) { export function useFormInstanceMethods(
props: IAnyObject,
formdata,
context: Partial<SetupContext>,
_formInstance: Ref<AForm | null>,
) {
/** /**
* propsonparent * propsonparent
*/ */
@ -13,8 +18,9 @@ export function useFormInstanceMethods(props: IAnyObject, formdata, context: Par
const instance = getCurrentInstance() const instance = getCurrentInstance()
const vm = instance?.parent const vm = instance?.parent
if (!vm) if (!vm)
return return;
;(props.formConfig.schemas as IVFormComponent[]).forEach((item) => {
(props.formConfig.schemas as IVFormComponent[]).forEach((item) => {
// 绑定 props 中的上下文 // 绑定 props 中的上下文
forOwn(item.componentProps, (value: any, key) => { forOwn(item.componentProps, (value: any, key) => {
if (isFunction(value)) if (isFunction(value))
@ -35,7 +41,7 @@ export function useFormInstanceMethods(props: IAnyObject, formdata, context: Par
const { resetFields, validate, clearValidate, validateField } = useForm(formdata, []) const { resetFields, validate, clearValidate, validateField } = useForm(formdata, [])
const submit = () => { const submit = async () => {
// const _result = await validate(); // const _result = await validate();
const data = cloneDeep(toRaw(formdata.value)) const data = cloneDeep(toRaw(formdata.value))

View File

@ -13,7 +13,11 @@ export interface IProps {
formModel: IAnyObject formModel: IAnyObject
} }
type ISet = <T extends keyof IVFormComponent>(field: string, key: T, value: IVFormComponent[T]) => void type ISet = <T extends keyof IVFormComponent>(
field: string,
key: T,
value: IVFormComponent[T],
) => void
// 获取当前field绑定的表单项 // 获取当前field绑定的表单项
type IGet = (field: string) => IVFormComponent | undefined type IGet = (field: string) => IVFormComponent | undefined
// 获取field在formData中的值 // 获取field在formData中的值
@ -59,7 +63,8 @@ export function useVFormMethods(
* @param {string} field * @param {string} field
* @return {IVFormComponent | undefined} * @return {IVFormComponent | undefined}
*/ */
const get: IGet = field => findFormItem(props.formConfig.schemas, item => item.field === field) const get: IGet = field =>
findFormItem(props.formConfig.schemas, item => item.field === field)
/** /**
* field * field
@ -73,6 +78,20 @@ export function useVFormMethods(
formItem[key] = value formItem[key] = value
} }
/**
* props
* @param {string} field field
* @param {string} key key
* @param value
*/
const setProps: ISetProps = (field, key, value) => {
const formItem = get(field)
if (formItem?.componentProps) {
['options', 'treeData'].includes(key) && setValue(field, undefined)
formItem.componentProps[key] = value
}
}
/** /**
* *
* @param {string} field * @param {string} field
@ -92,20 +111,6 @@ export function useVFormMethods(
}) })
} }
} }
/**
* props
* @param {string} field field
* @param {string} key key
* @param value
*/
const setProps: ISetProps = (field, key, value) => {
const formItem = get(field)
if (formItem?.componentProps) {
;['options', 'treeData'].includes(key) && setValue(field, undefined)
formItem.componentProps[key] = value
}
}
/** /**
* *
* @param {string} key * @param {string} key
@ -143,7 +148,9 @@ export function useVFormMethods(
* @param {string | undefined} field * @param {string | undefined} field
*/ */
const disable: IDisable = (field) => { const disable: IDisable = (field) => {
typeof field === 'string' ? setProps(field, 'disabled', true) : setFormConfig('disabled', field !== false) typeof field === 'string'
? setProps(field, 'disabled', true)
: setFormConfig('disabled', field !== false)
} }
/** /**

View File

@ -44,5 +44,9 @@ export interface IFormDesignMethods {
handleAddAttrs(schemas: IVFormComponent[], index: number): void handleAddAttrs(schemas: IVFormComponent[], index: number): void
setFormConfig(config: IFormConfig): void setFormConfig(config: IFormConfig): void
// 添加到表单中之前触发 // 添加到表单中之前触发
handleBeforeColAdd(event: { newIndex: string }, schemas: IVFormComponent[], isCopy?: boolean): void handleBeforeColAdd(
event: { newIndex: string },
schemas: IVFormComponent[],
isCopy?: boolean,
): void
} }

View File

@ -177,7 +177,12 @@ export interface AForm {
* validate one or several form items * validate one or several form items
* @type Function * @type Function
*/ */
validateField: (name: string, value: any, rules: Record<string, unknown>[], option?: validateOptions) => Promise<RuleError[]> validateField: (
name: string,
value: any,
rules: Record<string, unknown>[],
option?: validateOptions,
) => Promise<RuleError[]>
/** /**
* reset all the fields and remove validation result * reset all the fields and remove validation result
*/ */

View File

@ -1,6 +1,5 @@
// import { VueConstructor } from 'vue'; // import { VueConstructor } from 'vue';
import { cloneDeep, isArray, isFunction, isNumber, uniqueId } from 'lodash-es' import { cloneDeep, isArray, isFunction, isNumber, uniqueId } from 'lodash-es'
import type { Ref } from 'vue'
import type { IFormConfig, IVFormComponent, IValidationRule } from '../typings/v-form-component' import type { IFormConfig, IVFormComponent, IValidationRule } from '../typings/v-form-component'
// import { del } from '@vue/composition-api'; // import { del } from '@vue/composition-api';
@ -40,7 +39,10 @@ export function generateKey(formItem?: IVFormComponent): string | boolean {
* @param value {number | ((item: T, index: number, array: Array<T>) => boolean} * @param value {number | ((item: T, index: number, array: Array<T>) => boolean}
* @returns {T} * @returns {T}
*/ */
export function remove<T>(array: Array<T>, value: number | ((item: T, index: number, array: Array<T>) => boolean)): T | undefined { export function remove<T>(
array: Array<T>,
value: number | ((item: T, index: number, array: Array<T>) => boolean),
): T | undefined {
let removeVal: Array<T | undefined> = [] let removeVal: Array<T | undefined> = []
if (!isArray(array)) if (!isArray(array))
return undefined return undefined
@ -107,10 +109,10 @@ export function formItemsForEach(array: IVFormComponent[], cb: (item: IVFormComp
/** /**
* *
*/ */
export const findFormItem: (schemas: IVFormComponent[], cb: (formItem: IVFormComponent) => boolean) => IVFormComponent | undefined = ( export const findFormItem: (
schemas, schemas: IVFormComponent[],
cb, cb: (formItem: IVFormComponent) => boolean,
) => { ) => IVFormComponent | undefined = (schemas, cb) => {
let res let res
const traverse = (schemas: IVFormComponent[]): boolean => { const traverse = (schemas: IVFormComponent[]): boolean => {
return schemas.some((formItem: IVFormComponent) => { return schemas.some((formItem: IVFormComponent) => {
@ -203,45 +205,3 @@ export function runCode<T>(code: any): T {
return code return code
} }
} }
/**
* https://github.com/xaboy/form-create-designer 封装的工具类
*/
// 编码表单 Conf
export function encodeConf(designerRef: Ref<Recordable>) {
return JSON.stringify(designerRef.value.getOption())
}
// 编码表单 Fields
export function encodeFields(designerRef: Ref<Recordable>) {
const rule = designerRef.value.getRule()
const fields: string[] = []
rule.forEach((item) => {
fields.push(JSON.stringify(item))
})
return fields
}
// 解码表单 Fields
export function decodeFields(fields: string[]) {
const rule: object[] = []
fields.forEach((item) => {
rule.push(JSON.parse(item))
})
return rule
}
// 设置表单的 Conf 和 Fields
export function setConfAndFields(designerRef: Ref<Recordable>, conf: string, fields: string[]) {
designerRef.value.setOption(JSON.parse(conf))
designerRef.value.setRule(decodeFields(fields))
}
// 设置表单的 Conf 和 Fields
export function setConfAndFields2(detailPreview: Ref<Recordable>, conf: string, fields: string[], value?: object) {
detailPreview.value.option = JSON.parse(conf)
detailPreview.value.rule = decodeFields(fields)
if (value)
detailPreview.value.value = value
}