Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions examples/views/form/FormTest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@

<p class="tip">配置式表单</p>

<vxe-form :data="demo4.formData4" :items="demo4.formItems4">
<vxe-form :data="demo4.formData4" :items="demo4.formItems4" :useDynamicSpan="true" :dynamicSpan="demo4.dynamicSpan">
<template #myaddress="{ data }">
<vxe-input v-model="data.address" placeholder="自定义插槽模板"></vxe-input>
</template>
Expand Down Expand Up @@ -376,31 +376,31 @@ const demo4 = reactive({
flag: false,
address: ''
},
formItems4: [
{
title: '左侧',
span: 12,
children: [
{ field: 'name', title: '名称', span: 8, itemRender: { name: '$input', props: { placeholder: '请输入名称' } } },
{ field: 'sex', title: '性别', span: 8, itemRender: { name: '$select', options: [{ value: '0', label: '女' }, { value: '1', label: '男' }], props: { placeholder: '请选择性别' } } },
{ field: 'role', title: '角色', span: 8, itemRender: { name: '$input', props: { placeholder: '请输入角色' } } },
{ field: 'age', title: '年龄', span: 24, itemRender: { name: '$input', props: { type: 'number', placeholder: '请输入年龄' } } },
{ field: 'region', title: '地区选择', span: 24, itemRender: { name: 'VxeTreeSelect', options: regionOptions.value } },
{ field: 'val1', title: '复选框-组', span: 12, itemRender: { name: '$checkbox', options: [{ label: '爬山', value: '11' }, { label: '健身', value: '22' }] } },
{ field: 'val2', title: '复选框', span: 12, itemRender: { name: '$checkbox' } },
{ field: 'val3', title: '单选框', span: 12, itemRender: { name: '$radio', options: [{ label: '是', value: 'Y' }, { label: '否', value: 'N' }] } },
{ field: 'flag', title: '开关', span: 24, itemRender: { name: '$switch', props: { openLabel: '是', closeLabel: '否' } } },
{ field: 'address', title: '地区', span: 24, slots: { default: 'myaddress' } }
]
dynamicSpan: {
colProps: {
md: 8,
lg: 6,
xl: 6
},
{
title: '右侧',
span: 12,
children: [
{ field: 'nickname', title: '昵称', span: 24, itemRender: { name: '$input', props: { placeholder: '请输入昵称' } } }
]
actionColProps: {
span: 24
},
{ align: 'center', span: 24, itemRender: { name: '$buttons', children: [{ props: { type: 'submit', content: '配置式表单', status: 'primary' } }, { props: { type: 'reset', content: '重置' } }] } }
autoAction: true,
alwaysShowLines: 2
},
formItems4: [
{ field: 'name', title: '名称', span: 8, itemRender: { name: '$input', props: { placeholder: '请输入名称' } } },
{ field: 'sex', title: '性别', span: 8, itemRender: { name: '$select', options: [{ value: '0', label: '女' }, { value: '1', label: '男' }], props: { placeholder: '请选择性别' } } },
{ field: 'role', title: '角色', span: 8, itemRender: { name: '$input', props: { placeholder: '请输入角色' } } },
{ field: 'address', title: '地区', folding: true, span: 8, slots: { default: 'myaddress' }, visibleMethod: ({data}) => data.name === '1111' },
{ field: 'age', title: '年龄', folding: true, span: 8, itemRender: { name: '$input', props: { type: 'number', placeholder: '请输入年龄' } } },
{ field: 'region', title: '地区选择', folding: true, span: 8, itemRender: { name: 'VxeTreeSelect', options: regionOptions.value } },
{ field: 'val1', title: '复选框-组', folding: true, span: 8, itemRender: { name: '$checkbox', options: [{ label: '爬山', value: '11' }, { label: '健身', value: '22' }] } },
{ field: 'val2', title: '复选框', folding: true, span: 8, itemRender: { name: '$checkbox' } },
{ field: 'val3', title: '单选框', folding: true, span: 8, itemRender: { name: '$radio', options: [{ label: '是', value: 'Y' }, { label: '否', value: 'N' }] } },
{ field: 'flag', title: '开关', folding: true, span: 8, itemRender: { name: '$switch', props: { openLabel: '是', closeLabel: '否' } } },
{ field: 'nickname', title: '昵称', folding: true, span: 8, itemRender: { name: '$input', props: { placeholder: '请输入昵称' } } },
{ align: 'center', span: 8, collapseNode: true, itemRender: { name: '$buttons', children: [{ props: { type: 'submit', content: '配置式表单', status: 'primary' } }, { props: { type: 'reset', content: '重置' } }] } }
]
})

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"style": "lib/style.css",
"typings": "types/index.d.ts",
"dependencies": {
"@vxe-ui/core": "^1.0.12"
"@vxe-ui/core": "^1.0.12",
"dom-zindex": "^1.0.4",
"xe-utils": "^3.5.28"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.21.0",
Expand All @@ -38,6 +40,7 @@
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-standard": "^6.1.0",
"@vue/eslint-config-typescript": "^9.1.0",
"@vueuse/core": "^10.11.0",
"core-js": "^3.8.3",
"del": "^6.1.1",
"eslint": "^7.32.0",
Expand Down
152 changes: 152 additions & 0 deletions packages/form/src/form-calc-span.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import type { ComputedRef, Ref } from 'vue'
import type { VxeFormProps, VxeFormItemProps, VxeFormConstructor, VxeFormPrivateMethods, VxeFormPropTypes } from '../../../types'
import { useWindowSize, useDebounceFn } from '@vueuse/core'
import { watch, unref, computed } from 'vue'
import XEUtils from 'xe-utils'

interface UseFormCalcContext {
isCollapse: ComputedRef<boolean>;
getProps: ComputedRef<VxeFormProps>;
schemas: Ref<VxeFormItemProps[]>
$xeForm: VxeFormConstructor & VxeFormPrivateMethods
updateSchemas: (items: VxeFormItemProps[]) => void
}

const BASIC_COL_LEN = 24
const DEFAULTSPAN: VxeFormPropTypes.DynamicSpanConfig = {
colProps: {
span: BASIC_COL_LEN,
xs: 0,
sm: 0,
md: 0,
lg: 0,
xl: 0
},
actionColProps: {
span: BASIC_COL_LEN,
xs: 0,
sm: 0,
md: 0,
lg: 0,
xl: 0
}
}

export default function ({ isCollapse, getProps, schemas, $xeForm, updateSchemas }: UseFormCalcContext) {
const { useDynamicSpan, dynamicSpan, isMicroApp } = getProps.value

if (!useDynamicSpan) return false
const formItems = unref(schemas.value)
const { width } = useWindowSize()
const showAdvancedButton = computed(() => {
const actionItem = formItems.find(schema => schema.hasOwnProperty('collapseNode'))
if (actionItem) {
const { visible, visibleMethod, field } = actionItem
let isShow = true
if (visible) {
isShow = true
}

if (visibleMethod && typeof visibleMethod === 'function') {
const { data } = $xeForm.props
isShow = visibleMethod({ data, field: `${field}`, property: `${field}`, item: actionItem as any, $form: $xeForm, $grid: $xeForm.xegrid })
}
return isShow
}
return false
})
const { alwaysShowLines, colProps, actionColProps, autoAction } = XEUtils.merge(DEFAULTSPAN, dynamicSpan || {})
let rowSpan = BASIC_COL_LEN
let lastSpan = BASIC_COL_LEN

function updateAdvanced () {
let itemColSum = 0
const windowW = isMicroApp ? window.parent.innerWidth : width.value
const xsWidth = parseInt(colProps.xs as string) || (colProps.span as number) || BASIC_COL_LEN
const smWidth = parseInt(colProps.sm as string) || xsWidth
const mdWidth = parseInt(colProps.md as string) || smWidth
const lgWidth = parseInt(colProps.lg as string) || mdWidth
const xlWidth = parseInt(colProps.xl as string) || lgWidth

const xsAcWidth = parseInt(actionColProps.xs as string) || (actionColProps.span as number) || BASIC_COL_LEN
const smAcWidth = parseInt(actionColProps.sm as string) || xsAcWidth
const mdAcWidth = parseInt(actionColProps.md as string) || smAcWidth
const lgAcWidth = parseInt(actionColProps.lg as string) || mdAcWidth
const xlAcWidth = parseInt(actionColProps.xl as string) || lgAcWidth

if (windowW < 768) {
rowSpan = xsWidth
if (!autoAction) {
lastSpan = xsAcWidth
}
} else if (windowW >= 768 && windowW < 992) {
rowSpan = smWidth
if (!autoAction) {
lastSpan = smAcWidth
}
} else if (windowW >= 992 && windowW < 1200) {
rowSpan = mdWidth
if (!autoAction) {
lastSpan = mdAcWidth
}
} else if (windowW >= 1200 && windowW < 1920) {
rowSpan = lgWidth
if (!autoAction) {
lastSpan = lgAcWidth
}
} else {
rowSpan = xlWidth
if (!autoAction) {
lastSpan = xlAcWidth
}
}

formItems.forEach(schema => {
const { visible, visibleMethod, field } = schema
let isShow = true
if (visible) {
isShow = true
}

if (visibleMethod && typeof visibleMethod === 'function') {
const { data } = $xeForm.props
isShow = visibleMethod({ data, field: `${field}`, property: `${field}`, item: schema as any, $form: $xeForm, $grid: $xeForm.xegrid })
}
if (schema.hasOwnProperty('collapseNode')) {
if (autoAction) {
lastSpan = isCollapse.value ? rowSpan : BASIC_COL_LEN - (itemColSum % BASIC_COL_LEN)
}
schema.span = lastSpan
itemColSum += isShow ? schema.span : 0
schema.collapseNode = itemColSum > (BASIC_COL_LEN * (alwaysShowLines || 1))
} else {
schema.span = rowSpan
itemColSum += isShow ? schema.span : 0
if (isCollapse.value) {
schema.folding = itemColSum > (BASIC_COL_LEN * (alwaysShowLines || 1) - (autoAction ? rowSpan : 0))
} else {
schema.folding = false
}
}
})
updateSchemas(formItems)
}

const debounceUpdateAdvanced = useDebounceFn(updateAdvanced, 50)

function handlerAdvanced () {
if (showAdvancedButton.value) {
debounceUpdateAdvanced()
}
}

watch(
[() => isCollapse.value, () => schemas.value, () => width.value],
() => {
handlerAdvanced()
},
{ immediate: true }
)

return { handlerAdvanced }
}
66 changes: 57 additions & 9 deletions packages/form/src/form.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineComponent, h, ref, Ref, provide, computed, inject, reactive, watch, nextTick, PropType, onMounted } from 'vue'
import { defineComponent, h, ref, Ref, provide, computed, inject, reactive, watch, nextTick, PropType, onMounted, onBeforeUnmount } from 'vue'
import XEUtils from 'xe-utils'
import { getConfig, validators, renderer, createEvent, useSize } from '../../ui'
import { getFuncText, isEnableConf, eqEmptyValue } from '../../ui/src/utils'
Expand All @@ -9,8 +9,9 @@ import VxeFormConfigItem from './form-config-item'
import VxeLoadingComponent from '../../loading/src/loading'
import { getSlotVNs } from '../../ui/src/vn'
import { warnLog, errLog } from '../../ui/src/log'

import type { VxeFormConstructor, VxeFormPropTypes, VxeFormEmits, FormReactData, FormMethods, FormPrivateRef, VxeFormPrivateMethods, VxeFormDefines, VxeFormItemPropTypes, VxeTooltipInstance, FormInternalData, VxeFormPrivateComputed } from '../../../types'
import useAdvance from './form-calc-span'
import type { VxeFormConstructor, VxeFormPropTypes, VxeFormEmits, FormReactData, FormMethods, FormPrivateRef, VxeFormPrivateMethods, VxeFormDefines, VxeFormItemPropTypes, VxeTooltipInstance, FormInternalData, VxeFormPrivateComputed, VxeFormItemProps } from '../../../types'
import mitt from './mitt'

class Rule {
constructor (rule: any) {
Expand Down Expand Up @@ -128,6 +129,17 @@ export default defineComponent({
customLayout: {
type: Boolean as PropType<VxeFormPropTypes.CustomLayout>,
default: () => getConfig().form.customLayout
},
isMicroApp: {
type: Boolean,
default: false
},
useDynamicSpan: {
type: Boolean,
default: false
},
dynamicSpan: {
type: Object as PropType<VxeFormPropTypes.DynamicSpanConfig>
}
},
emits: [
Expand Down Expand Up @@ -197,6 +209,14 @@ export default defineComponent({
getComputeMaps: () => computeMaps
} as unknown as VxeFormConstructor & VxeFormPrivateMethods

const isCollapse = computed(() => reactData.collapseAll)
const getProps = computed(() => props)
const schemas = ref<VxeFormItemProps[]>([])
schemas.value = (props.items || [])
const updateSchemas = (formItems:VxeFormItemProps[] = []) => {
schemas.value = formItems
}

const callSlot = (slotFunc: ((params: any) => any) | string | null, params: any) => {
if (slotFunc) {
if (XEUtils.isString(slotFunc)) {
Expand Down Expand Up @@ -237,6 +257,8 @@ export default defineComponent({
return itemList
}

const handlerAdvancedCallback = useAdvance({ isCollapse, getProps, schemas, $xeForm, updateSchemas })

const getItemByField = (field: string) => {
const rest = XEUtils.findTree(reactData.formItems, item => item.field === field, { children: 'children' })
return rest ? rest.item : null
Expand Down Expand Up @@ -714,6 +736,13 @@ export default defineComponent({

$xeForm.renderVN = renderVN

mitt.on('updateAdvanced', () => {
if (XEUtils.isObject(handlerAdvancedCallback)) {
const { handlerAdvanced } = handlerAdvancedCallback
handlerAdvanced && handlerAdvanced()
}
})

const staticItemFlag = ref(0)
watch(() => reactData.staticItems.length, () => {
staticItemFlag.value++
Expand All @@ -726,14 +755,16 @@ export default defineComponent({
})

const itemFlag = ref(0)
watch(() => props.items ? props.items.length : -1, () => {
watch(() => schemas.value ? schemas.value.length : -1, () => {
itemFlag.value++
})
watch(() => props.items, () => {
watch(() => schemas.value, () => {
itemFlag.value++
}, {
deep: true
})
watch(itemFlag, () => {
loadItem(props.items || [])
loadItem(schemas.value || [])
})

watch(() => props.collapseStatus, (value) => {
Expand All @@ -748,18 +779,35 @@ export default defineComponent({
clearValidate()
})

watch(() => props.items, (value: VxeFormItemProps<VxeFormItemProps>[] | undefined) => {
const normalItem = (value || []).filter(el => !el.hasOwnProperty('collapseNode'))
const actionItem = (value || []).filter(el => el.hasOwnProperty('collapseNode'))
schemas.value = [...normalItem, ...actionItem]
}, {
deep: true
})

onMounted(() => {
nextTick(() => {
if (process.env.VUE_APP_VXE_ENV === 'development') {
if (props.customLayout && props.items) {
if (props.customLayout && schemas.value) {
errLog('vxe.error.errConflicts', ['custom-layout', 'items'])
}
}
})
})

if (props.items) {
loadItem(props.items)
onBeforeUnmount(() => {
mitt.off('updateAdvanced', () => {
if (XEUtils.isObject(handlerAdvancedCallback)) {
const { handlerAdvanced } = handlerAdvancedCallback
handlerAdvanced && handlerAdvanced()
}
})
})

if (schemas.value) {
loadItem(schemas.value)
}

provide('$xeForm', $xeForm)
Expand Down
5 changes: 4 additions & 1 deletion packages/form/src/itemInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export class ItemInfo {
showError: false,
errRule: null,
slots: item.slots,
children: []
children: [],
isMicroApp: false,
useDynamicSpan: false,
dynamicSpan: null
})
}

Expand Down
Loading