vxe-pc-ui
Version:
A vue based PC component library
508 lines (464 loc) • 16.9 kB
text/typescript
import { defineComponent, ref, h, PropType, reactive, provide, watch, nextTick, ComponentOptions, createCommentVNode } from 'vue'
import { VxeUI, getConfig, getIcon, getI18n, renderer, useSize, createEvent, renderEmptyElement } from '../../ui'
import { toCssUnit } from '../../ui/src/dom'
import { FormDesignWidgetInfo, getWidgetConfig, getWidgetConfigCustomGroup, configToWidget } from './widget-info'
import XEUtils from 'xe-utils'
import VxeButtonComponent from '../../button/src/button'
import LayoutWidgetComponent from './layout-widget'
import LayoutPreviewComponent from './layout-preview'
import LayoutSettingComponent from './layout-setting'
import LayoutStyleComponent from './layout-style'
import { getDefaultSettingFormData } from './default-setting-data'
import type { VxeFormDesignDefines, VxeFormDesignPropTypes, VxeFormDesignEmits, FormDesignInternalData, FormDesignReactData, FormDesignPrivateRef, VxeFormDesignPrivateComputed, VxeFormDesignConstructor, VxeFormDesignPrivateMethods, FormDesignMethods, FormDesignPrivateMethods } from '../../../types'
export default defineComponent({
name: 'VxeFormDesign',
props: {
size: {
type: String as PropType<VxeFormDesignPropTypes.Size>,
default: () => getConfig().formDesign.size || getConfig().size
},
config: Object as PropType<VxeFormDesignPropTypes.Config>,
height: {
type: [String, Number] as PropType<VxeFormDesignPropTypes.Height>,
default: () => getConfig().formDesign.height
},
widgets: {
type: Array as PropType<VxeFormDesignPropTypes.Widgets>,
default: () => XEUtils.clone(getConfig().formDesign.widgets) || []
},
showHeader: {
type: Boolean as PropType<VxeFormDesignPropTypes.ShowHeader>,
default: () => getConfig().formDesign.showHeader
},
showPc: {
type: Boolean as PropType<VxeFormDesignPropTypes.ShowPc>,
default: () => getConfig().formDesign.showPc
},
showMobile: {
type: Boolean as PropType<VxeFormDesignPropTypes.ShowMobile>,
default: () => getConfig().formDesign.showMobile
},
formRender: Object as PropType<VxeFormDesignPropTypes.FormRender>
},
emits: [
'click-widget',
'add-widget',
'copy-widget',
'remove-widget',
'drag-widget'
] as VxeFormDesignEmits,
setup (props, context) {
const { emit, slots } = context
const xID = XEUtils.uniqueId()
const refElem = ref<HTMLDivElement>()
const refLayoutStyle = ref<any>()
const { computeSize } = useSize(props)
const reactData = reactive<FormDesignReactData>({
formData: {} as VxeFormDesignDefines.DefaultSettingFormDataObjVO,
widgetConfigs: [],
widgetObjList: [],
dragWidget: null,
sortWidget: null,
activeWidget: null
})
const internalData = reactive<FormDesignInternalData>({
})
const refMaps: FormDesignPrivateRef = {
refElem
}
const computeMaps: VxeFormDesignPrivateComputed = {
computeSize
}
const $xeFormDesign = {
xID,
props,
context,
reactData,
internalData,
getRefMaps: () => refMaps,
getComputeMaps: () => computeMaps
} as unknown as VxeFormDesignConstructor & VxeFormDesignPrivateMethods
const createWidget = (name: string) => {
return new FormDesignWidgetInfo($xeFormDesign, name, reactData.widgetObjList) as VxeFormDesignDefines.WidgetObjItem
}
const createEmptyWidget = () => {
return new FormDesignWidgetInfo($xeFormDesign, '', reactData.widgetObjList) as VxeFormDesignDefines.WidgetObjItem
}
const loadConfig = (config: Partial<VxeFormDesignDefines.FormDesignConfig>) => {
if (config) {
const { formConfig, widgetData } = config
if (formConfig) {
loadFormConfig(formConfig)
}
if (widgetData) {
loadWidgetData(widgetData)
}
}
const { activeWidget, widgetObjList } = reactData
if (activeWidget) {
const rest = XEUtils.findTree(widgetObjList, item => item.id === activeWidget.id, { children: 'children' })
if (rest) {
reactData.activeWidget = rest.item
} else {
reactData.activeWidget = widgetObjList[0] || null
}
} else {
reactData.activeWidget = widgetObjList[0] || null
}
return nextTick()
}
const reloadConfig = (config: Partial<VxeFormDesignDefines.FormDesignConfig>) => {
clearConfig()
return loadConfig(config)
}
const getFormConfig = (): VxeFormDesignPropTypes.FormData => {
return XEUtils.clone(reactData.formData, true)
}
const loadFormConfig = (data: VxeFormDesignPropTypes.FormData) => {
reactData.formData = Object.assign({}, createSettingForm(), data) as VxeFormDesignDefines.DefaultSettingFormDataObjVO
return nextTick()
}
const getWidgetById = (id: number | string | null | undefined) => {
const { widgetObjList } = reactData
if (id) {
const widgetId = XEUtils.toNumber(id)
const rest = XEUtils.findTree(widgetObjList, item => item && item.id === widgetId, { children: 'children' })
if (rest) {
return rest.item
}
}
return null
}
const getWidgetData = (): VxeFormDesignDefines.WidgetObjItem[] => {
const objList = XEUtils.clone(reactData.widgetObjList, true)
XEUtils.eachTree(objList, item => {
item.model.value = null
}, { children: 'children' })
return objList
}
const loadWidgetData = (widgetData: VxeFormDesignDefines.WidgetObjItem[]) => {
reactData.widgetObjList = (widgetData || []).map(item => configToWidget(item))
return nextTick()
}
const openStyleSetting = () => {
const $layoutStyle = refLayoutStyle.value
if ($layoutStyle) {
$layoutStyle.openStylePreview()
}
return nextTick()
}
const clearConfig = () => {
reactData.widgetObjList = []
initSettingForm()
return nextTick()
}
const formDesignMethods: FormDesignMethods = {
dispatchEvent (type, params, evnt) {
emit(type, createEvent(evnt, { $xeFormDesign }, params))
},
createWidget,
createEmptyWidget,
getConfig () {
return {
formConfig: getFormConfig(),
widgetData: getWidgetData()
}
},
clearConfig,
loadConfig,
reloadConfig,
getFormConfig,
loadFormConfig,
getWidgetById,
getFormData () {
const { widgetObjList } = reactData
const formData: Record<string, any> = {}
XEUtils.eachTree(widgetObjList, widget => {
formData[widget.field] = null
}, { children: 'children' })
return formData
},
getWidgetData,
loadWidgetData,
refreshPreviewView () {
const $layoutStyle = refLayoutStyle.value
if ($layoutStyle) {
$layoutStyle.updatePreviewView()
}
return nextTick()
},
openStyleSetting
}
const updateWidgetConfigs = () => {
const { widgets } = props
const widgetConfs: VxeFormDesignDefines.WidgetConfigGroup[] = []
const baseWidgets: VxeFormDesignDefines.WidgetObjItem[] = []
const layoutWidgets: VxeFormDesignDefines.WidgetObjItem[] = []
const advancedWidgets: VxeFormDesignDefines.WidgetObjItem[] = []
const customGroups: VxeFormDesignDefines.WidgetConfigGroup[] = []
renderer.forEach((item, name) => {
const { createFormDesignWidgetConfig } = item
if (createFormDesignWidgetConfig) {
const widthItem = createWidget(name)
const widgetConf = getWidgetConfig(name)
const widgetCustomGroup = getWidgetConfigCustomGroup(name, $xeFormDesign)
// 如果自定义组
if (widgetCustomGroup) {
const cusGroup = customGroups.find(item => item.title === widgetCustomGroup)
if (cusGroup) {
cusGroup.children.push(widthItem)
} else {
customGroups.push({
title: widgetCustomGroup,
children: [widthItem]
})
}
} else {
switch (widgetConf.group) {
case 'layout':
layoutWidgets.push(widthItem)
break
case 'advanced':
advancedWidgets.push(widthItem)
break
default:
// 已废弃 title
if (!['title'].includes(widthItem.name)) {
baseWidgets.push(widthItem)
}
break
}
}
}
})
if (baseWidgets.length) {
widgetConfs.push({
group: 'base',
children: baseWidgets
})
}
if (layoutWidgets.length) {
widgetConfs.push({
group: 'layout',
children: layoutWidgets
})
}
if (advancedWidgets.length) {
widgetConfs.push({
group: 'advanced',
children: advancedWidgets
})
}
if (customGroups.length) {
widgetConfs.push(...customGroups)
}
if (widgets && widgets.length) {
reactData.widgetConfigs = props.widgets.map(config => {
return {
title: config.customGroup,
group: config.group,
children: config.children
? config.children.map(name => {
const widthItem = createWidget(name)
return widthItem
})
: []
}
})
} else {
reactData.widgetConfigs = widgetConfs
}
}
const validWidgetUnique = (widgetName: string) => {
const { widgetObjList } = reactData
const widgetConf = getWidgetConfig(widgetName)
if (widgetConf.unique) {
const existWidgetList: VxeFormDesignDefines.WidgetObjItem[] = []
XEUtils.eachTree(widgetObjList, obj => {
if (obj.name === widgetName) {
existWidgetList.push(obj)
}
}, { children: 'children' })
const status = existWidgetList.length < 1
if (!status) {
if (VxeUI.modal) {
VxeUI.modal.message({
content: getI18n('vxe.formDesign.error.wdFormUni'),
status: 'error',
id: 'wdFormUni'
})
}
}
return status
}
return true
}
const formDesignPrivateMethods: FormDesignPrivateMethods = {
validWidgetUnique,
handleClickWidget (evnt: KeyboardEvent, item: VxeFormDesignDefines.WidgetObjItem) {
if (item && item.name) {
evnt.stopPropagation()
reactData.activeWidget = item
formDesignMethods.dispatchEvent('click-widget', { widget: item }, evnt)
}
},
handleCopyWidget (evnt: KeyboardEvent, widget: VxeFormDesignDefines.WidgetObjItem) {
const { widgetObjList } = reactData
const rest = XEUtils.findTree(widgetObjList, obj => obj.id === widget.id, { children: 'children' })
if (rest) {
evnt.stopPropagation()
if (validWidgetUnique(widget.name)) {
const { path } = rest
const rootIndex = Number(path[0])
const newWidget = createWidget(widget.name)
// 标题副本
if (newWidget.title) {
newWidget.title = getI18n('vxe.formDesign.widget.copyTitle', [`${widget.title}`.replace(getI18n('vxe.formDesign.widget.copyTitle', ['']), '')])
}
if (rootIndex >= widgetObjList.length - 1) {
widgetObjList.push(newWidget)
} else {
widgetObjList.splice(rootIndex + 1, 0, newWidget)
}
reactData.activeWidget = newWidget
reactData.widgetObjList = [...widgetObjList]
formDesignMethods.dispatchEvent('copy-widget', { widget, newWidget }, evnt)
}
}
},
handleRemoveWidget (evnt: KeyboardEvent, widget: VxeFormDesignDefines.WidgetObjItem) {
const { widgetObjList } = reactData
const rest = XEUtils.findTree(widgetObjList, obj => obj.id === widget.id, { children: 'children' })
if (rest) {
const { index, parent, items } = rest
evnt.stopPropagation()
if (index >= items.length - 1) {
reactData.activeWidget = items[index - 1]
} else {
reactData.activeWidget = items[index + 1] || null
}
// 如果是行控件,使用空的控件占位
if (parent && parent.name === 'row') {
items[index] = createEmptyWidget()
} else {
items.splice(index, 1)
}
reactData.widgetObjList = [...widgetObjList]
formDesignMethods.dispatchEvent('remove-widget', { widget }, evnt)
}
}
}
const createSettingForm = () => {
const { formRender, showPc, showMobile } = props
let conf: Record<string, any> = getDefaultSettingFormData({
pcVisible: showPc,
mobileVisible: showMobile
})
// 如果为自定义渲染
if (formRender) {
const compConf = renderer.get(formRender.name)
const createFormConfig = compConf ? compConf.createFormDesignSettingFormConfig : null
conf = (createFormConfig ? createFormConfig({}) : {}) || {}
}
return conf as VxeFormDesignDefines.DefaultSettingFormDataObjVO
}
const initSettingForm = () => {
reactData.formData = createSettingForm()
}
const openStylePreviewEvent = () => {
openStyleSetting()
}
Object.assign($xeFormDesign, formDesignMethods, formDesignPrivateMethods)
const renderLayoutHeader = () => {
const extraSlot = slots.extra
return h('div', {
class: 'vxe-form-design--header-wrapper'
}, [
h('div', {
class: 'vxe-form-design--header-left'
}),
h('div', {
class: 'vxe-form-design--header-middle'
}),
h('div', {
class: 'vxe-form-design--header-right'
}, [
extraSlot
? h('div', {
class: 'vxe-form-design--header-extra'
}, extraSlot({}))
: renderEmptyElement($xeFormDesign),
h('div', {
class: 'vxe-form-design--header-setting'
}, [
h(VxeButtonComponent, {
mode: 'text',
status: 'primary',
icon: getIcon().FORM_DESIGN_STYLE_SETTING,
content: getI18n('vxe.formDesign.styleSetting.btn'),
onClick: openStylePreviewEvent
})
])
])
])
}
const renderVN = () => {
const { height, showHeader } = props
const vSize = computeSize.value
const headerSlot = slots.header
const footerSlot = slots.footer
return h('div', {
ref: refElem,
class: ['vxe-form-design', {
[`size--${vSize}`]: vSize
}],
style: height
? {
height: toCssUnit(height)
}
: null
}, [
showHeader || headerSlot
? h('div', {
class: 'vxe-form-design--header'
}, headerSlot ? headerSlot({}) : renderLayoutHeader())
: createCommentVNode(),
h('div', {
class: 'vxe-form-design--body'
}, [
h(LayoutWidgetComponent),
h(LayoutPreviewComponent),
h(LayoutSettingComponent),
h(LayoutStyleComponent as ComponentOptions, {
ref: refLayoutStyle
})
]),
footerSlot
? h('div', {
class: 'vxe-form-design--footer'
}, footerSlot ? footerSlot({}) : [])
: createCommentVNode()
])
}
$xeFormDesign.renderVN = renderVN
watch(() => props.widgets, () => {
updateWidgetConfigs()
})
watch(() => props.widgets, () => {
updateWidgetConfigs()
})
watch(() => props.config, (value) => {
loadConfig(value || {})
})
initSettingForm()
updateWidgetConfigs()
if (props.config) {
loadConfig(props.config)
}
provide('$xeFormDesign', $xeFormDesign)
return $xeFormDesign
},
render () {
return this.renderVN()
}
})