diff --git a/examples/main.js b/examples/main.js index 88079c9..a1250f5 100644 --- a/examples/main.js +++ b/examples/main.js @@ -1,6 +1,5 @@ import { createApp } from 'vue' import ElementPlus from 'element-plus' -import * as ElementPlusIconsVue from '@element-plus/icons-vue' import Vant, { Locale } from 'vant' import enUS from 'vant/es/locale/lang/en-US' // import '@vant/touch-emulator' @@ -14,6 +13,3 @@ app.use(router) app.use(ElementPlus) app.use(Vant) app.mount('#app') -for (const [key, component] of Object.entries(ElementPlusIconsVue)) { - app.component(key, component) -} diff --git a/examples/views/formEditor.vue b/examples/views/formEditor.vue index 0e47131..99b9ee9 100644 --- a/examples/views/formEditor.vue +++ b/examples/views/formEditor.vue @@ -1,9 +1,27 @@ <script setup> -import { ref, onMounted, getCurrentInstance } from 'vue' +import { ref, onMounted, getCurrentInstance, inject } from 'vue' import { erFormEditor } from '@ER/formEditor' +const { + lang +} = inject('globalConfig') const EReditorRef = ref(null) +const handleListener = async ({ type, data }) => { + // console.log(data) + switch (type) { + case 'lang': + lang.value = data + localStorage.setItem('er-lang', data) + break + } +} +const checkFieldsForNewBadge = (field) => { + return field.type === 'subform' +} </script> <template> <er-form-editor + :checkFieldsForNewBadge="checkFieldsForNewBadge" + :lang="lang" + @listener="handleListener" ref="EReditorRef"/> </template> diff --git a/examples/views/formEditor/objEdit.vue b/examples/views/formEditor/objEdit.vue index 6fe3183..eb30dfa 100644 --- a/examples/views/formEditor/objEdit.vue +++ b/examples/views/formEditor/objEdit.vue @@ -41,7 +41,6 @@ const getObjData = async () => { } } const handleListener = async ({ type, data }) => { - console.log(type) switch (type) { case 'lang': lang.value = data @@ -53,6 +52,8 @@ const handleListener = async ({ type, data }) => { } loading.value = true try { + // console.log(data) + // data.fields[0].options.defaultValue = [] const postData = { name: state.name, content: Object.assign({ @@ -89,6 +90,9 @@ const quickImages = ref([ '/public/Everright-logo.svg', '/public/Everright-logo.svg' ]) +const checkFieldsForNewBadge = (field) => { + return field.type === 'subform' +} </script> <template> <div @@ -96,6 +100,7 @@ const quickImages = ref([ > <er-form-editor :checkPropsBySelected="checkPropsBySelected" + :checkFieldsForNewBadge="checkFieldsForNewBadge" v-if="isRender" :quickImages="quickImages" :layoutType="layoutType" diff --git a/examples/views/formEditorConfig.vue b/examples/views/formEditorConfig.vue index 29507e0..82d93a1 100644 --- a/examples/views/formEditorConfig.vue +++ b/examples/views/formEditorConfig.vue @@ -41,6 +41,11 @@ watch(lang, (newLang) => { node.columns[0].rows[0].columns[0].label = `${node.columns[0].label} > ${node.columns[0].rows[0].columns[0].type}` store.layouts.push(node.columns[0].rows[0].columns[0]) break + case 'subform': + node.columns[0].list[0].push(erGeneratorData(erComponentsConfig.fieldsConfig[1].list[0], true, 'en')) + // node.columns[0].rows[0].columns[0].label = `${node.columns[0].label} > ${node.columns[0].rows[0].columns[0].type}` + // store.layouts.push(node.columns[0].rows[0].columns[0]) + break } }) all.value = [...store.fields, ...store.layouts] diff --git a/package.json b/package.json index f17cff0..5fa79a4 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "canvas": "^2.11.2", "dayjs": "^1.11.7", "element-plus": "^2.2.28", - "everright-filter": "^0.0.22", + "everright-filter": "^1.1.1", "jss": "^10.9.2", "jss-preset-default": "^10.9.2", "lodash-es": "^4.17.21", @@ -79,7 +79,6 @@ "@ckeditor/ckeditor5-vue": "^4.0.1", "@commitlint/cli": "^17.4.4", "@commitlint/config-conventional": "^17.4.4", - "@element-plus/icons-vue": "^2.0.10", "@vitejs/plugin-vue": "^3.2.0", "@vitejs/plugin-vue-jsx": "^2.1.1", "@vue/compiler-sfc": "^3.2.47", diff --git a/packages/formEditor/components/FormTypes/Uploadfile/pc.vue b/packages/formEditor/components/FormTypes/Uploadfile/pc.vue index fc4ae7d..5cc0800 100644 --- a/packages/formEditor/components/FormTypes/Uploadfile/pc.vue +++ b/packages/formEditor/components/FormTypes/Uploadfile/pc.vue @@ -80,7 +80,9 @@ const handleError = (error) => { :before-upload="beforeAvatarUpload" :on-error="handleError" > - <el-icon><Plus /></el-icon> + <el-icon> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64h352z"></path></svg> + </el-icon> </el-upload> <el-image-viewer diff --git a/packages/formEditor/components/Layout/ControlInsertionPlugin.js b/packages/formEditor/components/Layout/ControlInsertionPlugin.js index 85ddd72..27536bc 100644 --- a/packages/formEditor/components/Layout/ControlInsertionPlugin.js +++ b/packages/formEditor/components/Layout/ControlInsertionPlugin.js @@ -163,7 +163,7 @@ const setBorder = (el, className) => { el.classList.add(className) } const getDragElement = (node) => { - return node.__draggable_context.element + return node.__draggable_context?.element } const setStates = (newTarget, ev, ER) => { @@ -313,14 +313,6 @@ function ControlInsertionPlugin (ER) { } ControlInsertion.prototype = { dragStart (e) { - // const { - // rootEl, - // target - // } = e - // const isBlock = _.get(e, 'sortable.options.dataSource', false) === 'block' - // if (isBlock) return false - // const targetContainer = rootEl.parentNode - // targetContainer.style.zIndex = 2 }, drop (e) { if (!prevEl || !e.activeSortable) { @@ -344,7 +336,12 @@ function ControlInsertionPlugin (ER) { } } if (inserRowIndex !== '') { - const store = Array.isArray(prevSortable.options.parent) ? prevSortable.options.parent : prevSortable.options.parent.list + let store = [] + if (prevSortable.options.parent.type === 'subform') { + store = prevSortable.options.parent.list[0] + } else { + store = Array.isArray(prevSortable.options.parent) ? prevSortable.options.parent : prevSortable.options.parent.list + } store.splice(inserRowIndex, 0, newElement) utils.addContext(store[inserRowIndex], prevSortable.options.parent) } @@ -382,7 +379,7 @@ function ControlInsertionPlugin (ER) { const { activeSortable: { constructor: { - utils + utils: SortableUtils }, options: { dataSource @@ -413,15 +410,26 @@ function ControlInsertionPlugin (ER) { if (target.dataset.layoutType === 'grid') { return false } + const dragNode = getDragElement(dragEl) + const targetNode = getDragElement(target) + if ((!utils.checkIsField(dragNode) || dragNode.type === 'subform') && utils.checkIsInSubform(targetNode)) { + return false + } + if (target.dataset.layoutType === 'subform') { + // console.log(utils) + if (!utils.checkIsField(dragNode) || dragNode.type === 'subform') { + return false + } + } originalEvent.stopPropagation && originalEvent.stopPropagation() const direction = '' const targetContainer = el.parentNode const targetOnlyOne = targetList.length === 1 - let newTarget = utils.closest(target, this.options.draggable, sortable.el) + let newTarget = SortableUtils.closest(target, this.options.draggable, sortable.el) if (dragEl.contains(newTarget)) { return false } - if (/^(grid-col|tabs-col|td|collapse-col|root|inline)$/.test(target.dataset.layoutType)) { + if (/^(grid-col|tabs-col|td|collapse-col|root|inline|subform)$/.test(target.dataset.layoutType)) { newTarget = target const state = (newTarget.__draggable_component__ || newTarget.children[0].__draggable_component__) if (!state.list.length) { @@ -433,9 +441,6 @@ function ControlInsertionPlugin (ER) { if (/^(root|grid-col)$/.test(target.dataset.layoutType)) { const rows = el.children prevEl = lastChild(el) - // if (prevEl.contains(dragEl) && list.length === 1) { - // console.log(prevEl) - // console.log(dragEl) if (prevEl === dragEl.parentNode.parentNode && list.length === 1) { prevEl = '' return false diff --git a/packages/formEditor/components/Layout/DragGable.jsx b/packages/formEditor/components/Layout/DragGable.jsx index d21dd44..09c820d 100644 --- a/packages/formEditor/components/Layout/DragGable.jsx +++ b/packages/formEditor/components/Layout/DragGable.jsx @@ -21,6 +21,7 @@ import LayoutTabsLayout from './TabsLayout' import LayoutCollapseLayout from './CollapseLayout' import LayoutTableLayout from './TableLayout' import LayoutInlineLayout from './InlineLayout' +import LayoutSubformLayout from './SubformLayout' import Selection from '@ER/formEditor/components/Selection/selectElement.jsx' import ControlInsertionPlugin from './ControlInsertionPlugin' const dragGableWrap = defineComponent({ @@ -143,6 +144,11 @@ export default defineComponent({ case 'inline': node = (<LayoutInlineLayout key={element.id} data={element} parent={props.data}></LayoutInlineLayout>) break + case 'subform': + if (unref(isEditModel) || _.get(state.fieldsLogicState.get(element), 'visible', undefined) !== 0) { + node = (<LayoutSubformLayout key={element.id} data={element} parent={props.data}></LayoutSubformLayout>) + } + break default: let TypeComponent = '' if (unref(isEditModel) || _.get(state.fieldsLogicState.get(element), 'visible', undefined) !== 0) { diff --git a/packages/formEditor/components/Layout/SubformLayout.jsx b/packages/formEditor/components/Layout/SubformLayout.jsx new file mode 100644 index 0000000..d7741f2 --- /dev/null +++ b/packages/formEditor/components/Layout/SubformLayout.jsx @@ -0,0 +1,164 @@ +import { + defineComponent, + useAttrs, + unref, + inject, + onBeforeUnmount +} from 'vue' +import Selection from '@ER/formEditor/components/Selection/selectElement.jsx' +import LayoutDragGable from './DragGable.jsx' +import hooks from '@ER/hooks' +import _ from 'lodash-es' +import utils from '@ER/utils' +import Icon from '@ER/icon' +export default defineComponent({ + name: 'SubformLayout', + inheritAttrs: false, + customOptions: {}, + props: { + data: Object, + parent: Array + }, + setup (props) { + const ER = inject('Everright') + const ExtraParams = inject('EverrightExtraParams', {}) + const ns = hooks.useNamespace('SubformLayout') + const { + state, + isEditModel, + isPc, + setSelection + } = hooks.useTarget() + const typeProps = hooks.useProps(state, props.data, unref(isPc)) + const addData = unref(isEditModel) ? [] : _.cloneDeep(props.data.list[0]) + const handleAdd = () => { + props.data.list.splice(props.data.list.length, 0, _.cloneDeep(addData)) + // console.log(props.data.list) + // console.log(props.data.list[props.data.list.length - 1]) + props.data.list[props.data.list.length - 1].forEach(e => { + utils.addContext(e, props.data) + }) + } + const clearList = () => { + props.data.list.splice(0, props.data.list.length) + } + const setList = (length, values) => { + clearList() + for (let i = 0; i < length; i++) { + handleAdd() + } + props.data.list.forEach((e, index) => { + e.forEach(e => { + e.columns.forEach(e => { + if (props.data.options.defaultValue) { + try { + ER.setValue(e, values[index][e.key]) + } catch (e) { + } + } + }) + }) + }) + } + if (!unref(isEditModel)) { + onBeforeUnmount(() => { + props.data.list[0] = addData + }) + clearList() + if (ER.state.remoteValues.has(props.data.key)) { + const values = ER.state.remoteValues.get(props.data.key) + setList(values.length, values) + } else { + if (props.data.options.defaultValue.length) { + setList(props.data.options.defaultValue.length, props.data.options.defaultValue) + } + } + } + if (ExtraParams.inSubformDefaultValueComponent) { + ExtraParams.handle.handleAdd = handleAdd + } + const params = { + hasCopy: true, + hasDel: true, + hasDrag: true, + hasWidthScale: true, + data: props.data, + parent: props.parent + } + if (process.env.NODE_ENV === 'test') { + params['data-field-id'] = `${props.data.id}` + } + return () => { + return ( + <Selection + {...useAttrs()} + { + ...params + } + > + <div class={ns.b()}> + <el-form-item + {...typeProps.value} + > + <div + class={[ns.e('content')]} + > + { + props.data.list.map((node, index) => + ( + <div + class={[ + ns.e('item'), + !unref(isEditModel) && !typeProps.value.disabled && ns.e('edit') + ]} + {...utils.addTestId('SubformLayout:item')} + > + <div + class={[ns.e('button')]}> + <el-button + size="large" + circle + > + {index + 1} + </el-button> + <el-button + size="large" + circle + type="danger" + onClick={() => props.data.list.splice(index, 1)} + icon={<Icon class={[ns.e('icon')]} icon="delete"></Icon>} + > + </el-button> + </div> + <LayoutDragGable + data-layout-type={'subform'} + data={node} + ControlInsertion={true} + parent={props.data}/> + </div> + )) + } + { + (!typeProps.value.disabled && !ExtraParams.inSubformDefaultValueComponent && addData.length) + ? (<div + class={[ns.e('addButton')]} + {...utils.addTestId('SubformLayout:addButton')} + > + <el-button + link + type="primary" + onClick={!unref(isEditModel) && handleAdd} + > + Add new + </el-button> + </div>) + : '' + } + </div> + </el-form-item> + </div> + </Selection> + ) + } + } +}) diff --git a/packages/formEditor/components/Panels/Config/components/BackgroundComponent.vue b/packages/formEditor/components/Panels/Config/components/BackgroundComponent.vue index 0c0ba3a..daa2c82 100644 --- a/packages/formEditor/components/Panels/Config/components/BackgroundComponent.vue +++ b/packages/formEditor/components/Panels/Config/components/BackgroundComponent.vue @@ -227,7 +227,9 @@ const handleSuccess = (response, uploadFile) => { :on-success="handleSuccess" :on-error="handleError" > - <el-icon><Plus /></el-icon> + <el-icon> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64h352z"></path></svg> + </el-icon> </el-upload> </li> <li diff --git a/packages/formEditor/components/Panels/Config/components/DataComponent2.jsx b/packages/formEditor/components/Panels/Config/components/DataComponent2.jsx index 9d65308..f7499ed 100644 --- a/packages/formEditor/components/Panels/Config/components/DataComponent2.jsx +++ b/packages/formEditor/components/Panels/Config/components/DataComponent2.jsx @@ -1,4 +1,4 @@ -import { computed, defineComponent, resolveComponent, unref, ref, watch, reactive, defineExpose, nextTick } from 'vue' +import { computed, defineComponent, resolveComponent, unref, ref, watch, reactive, defineExpose, nextTick, h } from 'vue' import utils from '@ER/utils' import hooks from '@ER/hooks' import Icon from '@ER/icon' @@ -141,11 +141,11 @@ export default defineComponent({ onClick={() => handleAction(2, index, e)} vModel={e.label}></el-input> <el-icon onClick={() => items.splice(i, 1)} color="#fff" size={10} class={ns.e('del')}> - <Minus/> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M128 544h768a32 32 0 1 0 0-64H128a32 32 0 0 0 0 64z"></path></svg> </el-icon> <el-icon onClick={() => handleAction(3, index, e)} color="#409eff" size={20} class={[ns.e('hide'), e.disabled && ns.e('show')]}> { - e.disabled ? <Hide/> : <View/> + e.disabled ? <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path d="M876.8 156.8c0-9.6-3.2-16-9.6-22.4-6.4-6.4-12.8-9.6-22.4-9.6-9.6 0-16 3.2-22.4 9.6L736 220.8c-64-32-137.6-51.2-224-60.8-160 16-288 73.6-377.6 176C44.8 438.4 0 496 0 512s48 73.6 134.4 176c22.4 25.6 44.8 48 73.6 67.2l-86.4 89.6c-6.4 6.4-9.6 12.8-9.6 22.4 0 9.6 3.2 16 9.6 22.4 6.4 6.4 12.8 9.6 22.4 9.6 9.6 0 16-3.2 22.4-9.6l704-710.4c3.2-6.4 6.4-12.8 6.4-22.4Zm-646.4 528c-76.8-70.4-128-128-153.6-172.8 28.8-48 80-105.6 153.6-172.8C304 272 400 230.4 512 224c64 3.2 124.8 19.2 176 44.8l-54.4 54.4C598.4 300.8 560 288 512 288c-64 0-115.2 22.4-160 64s-64 96-64 160c0 48 12.8 89.6 35.2 124.8L256 707.2c-9.6-6.4-19.2-16-25.6-22.4Zm140.8-96c-12.8-22.4-19.2-48-19.2-76.8 0-44.8 16-83.2 48-112 32-28.8 67.2-48 112-48 28.8 0 54.4 6.4 73.6 19.2L371.2 588.8ZM889.599 336c-12.8-16-28.8-28.8-41.6-41.6l-48 48c73.6 67.2 124.8 124.8 150.4 169.6-28.8 48-80 105.6-153.6 172.8-73.6 67.2-172.8 108.8-284.8 115.2-51.2-3.2-99.2-12.8-140.8-28.8l-48 48c57.6 22.4 118.4 38.4 188.8 44.8 160-16 288-73.6 377.6-176C979.199 585.6 1024 528 1024 512s-48.001-73.6-134.401-176Z" fill="currentColor"></path><path d="M511.998 672c-12.8 0-25.6-3.2-38.4-6.4l-51.2 51.2c28.8 12.8 57.6 19.2 89.6 19.2 64 0 115.2-22.4 160-64 41.6-41.6 64-96 64-160 0-32-6.4-64-19.2-89.6l-51.2 51.2c3.2 12.8 6.4 25.6 6.4 38.4 0 44.8-16 83.2-48 112-32 28.8-67.2 48-112 48Z" fill="currentColor"></path></svg> : <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352zm0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288zm0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448zm0 64a160.192 160.192 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160z"></path></svg> } </el-icon> </div> @@ -158,7 +158,23 @@ export default defineComponent({ {this.shows[index] && ( <div class={[ns.e('control')]}> <el-button - icon={'CirclePlus'} + icon={h('svg', { + viewBox: '0 0 1024 1024', + xmlns: 'http://www.w3.org/2000/svg' + }, + h('path', { + fill: 'currentColor', + d: 'M352 480h320a32 32 0 1 1 0 64H352a32 32 0 0 1 0-64z' + }), + h('path', { + fill: 'currentColor', + d: 'M480 672V352a32 32 0 1 1 64 0v320a32 32 0 0 1-64 0z' + }), + h('path', { + fill: 'currentColor', + d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768zm0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896z' + }) + )} onClick={() => handleAction(1, index, items)} text> {t('er.config.dataComponent2.add')} diff --git a/packages/formEditor/components/Panels/Config/components/LogicComponent.vue b/packages/formEditor/components/Panels/Config/components/LogicComponent.vue index 1c3f89b..645a93e 100644 --- a/packages/formEditor/components/Panels/Config/components/LogicComponent.vue +++ b/packages/formEditor/components/Panels/Config/components/LogicComponent.vue @@ -5,7 +5,8 @@ import { generateIfFilterOptionsData, generateIfFilterConditionsData, generateThenFilterOptionsData, - generateThenFilterConditionsData + generateThenFilterConditionsData, + findValidFields } from './generateFilterdata.js' import _ from 'lodash-es' import { EverrightFilter } from 'everright-filter' @@ -58,7 +59,7 @@ const { const getIfOptions = (type) => async () => { return new Promise((resolve, reject) => { resolve({ - data: generateIfFilterOptionsData(type, state.fields) + data: generateIfFilterOptionsData(type, findValidFields(state.fields)) }) }) } @@ -72,14 +73,14 @@ const getIfConditions = (type) => async ({ property }) => { const getThenOptions = (type) => async () => { return new Promise((resolve, reject) => { resolve({ - data: generateThenFilterOptionsData(type, state.fields) + data: generateThenFilterOptionsData(type, findValidFields(state.fields)) }) }) } const getThenConditions = (type) => async ({ property }) => { return new Promise((resolve, reject) => { resolve({ - data: generateThenFilterConditionsData(type, state.fields) + data: generateThenFilterConditionsData(type, findValidFields(state.fields)) }) }) } diff --git a/packages/formEditor/components/Panels/Config/components/PropsPanel.vue b/packages/formEditor/components/Panels/Config/components/PropsPanel.vue index 2ecca37..c36c7b2 100644 --- a/packages/formEditor/components/Panels/Config/components/PropsPanel.vue +++ b/packages/formEditor/components/Panels/Config/components/PropsPanel.vue @@ -13,6 +13,7 @@ import PanelsConfigComponentsBackgroundComponent from './BackgroundComponent.vue import PanelsConfigComponentsDataComponent1 from './DataComponent1.jsx' import PanelsConfigComponentsDataComponent2 from './DataComponent2.jsx' import PanelsConfigComponentsDataComponent3 from './DataComponent3.vue' +import PanelsConfigComponentsSubformDefaultValue from './SubformDefaultValue.vue' import Icon from '@ER/icon' import _ from 'lodash-es' export default { @@ -37,7 +38,8 @@ const { isSelectTabs, isSelectCollapse, isSelectTable, - isPc + isPc, + isSelectSubform } = hooks.useTarget() defineEmits(['changePanel']) const bgStatus = ref(false) @@ -415,7 +417,7 @@ onMounted(() => { <template v-slot:content> <div :class="[ns.e('collapseWrap'), ns.e('collapseWrap-left')]"> <el-row justify="space-between" align="middle"> - <el-col :span="isPc ? 11 : 24"> + <el-col :span="isSelectSubform ? 24 : (isPc ? 11 : 24)"> <el-form-item v-bind="utils.addTestId('configPanel:title')"> <template v-slot:label> <Icon icon="title"/> @@ -427,7 +429,7 @@ onMounted(() => { /> </el-form-item> </el-col> - <el-col :span="12" v-if="isPc"> + <el-col :span="12" v-if="isPc && !isSelectSubform"> <el-form-item v-bind="utils.addTestId('configPanel:titleWidth')"> <template v-slot:label> <Icon icon="dragWidth"/> @@ -445,7 +447,7 @@ onMounted(() => { <PanelsConfigComponentsTypeComponent :label="t('er.config.propsPanel.defaultContent')" :layoutType="0" - v-if="checkTypeBySelected([ + v-if="checkTypeBySelected(['subform'], 'defaultValue') ? target.list[0].length : checkTypeBySelected([ 'input', 'textarea', 'time', @@ -528,6 +530,9 @@ onMounted(() => { style="padding: 0 14px;" /> </template> + <template v-else-if="checkTypeBySelected(['subform'], 'defaultValue')"> + <PanelsConfigComponentsSubformDefaultValue/> + </template> </PanelsConfigComponentsTypeComponent> <PanelsConfigComponentsTypeComponent :label="t('er.public.Data')" diff --git a/packages/formEditor/components/Panels/Config/components/SubformDefaultValue.vue b/packages/formEditor/components/Panels/Config/components/SubformDefaultValue.vue new file mode 100644 index 0000000..19753e5 --- /dev/null +++ b/packages/formEditor/components/Panels/Config/components/SubformDefaultValue.vue @@ -0,0 +1,122 @@ +<script> +import _ from 'lodash-es' +import hooks from '@ER/hooks' +import { ref, inject, nextTick, reactive, computed, watch, onMounted, provide, onBeforeUnmount } from 'vue' +import utils from '@ER/utils' +import erFormPreview from '@ER/formEditor/preview.vue' +export default { + name: 'ConfigSubformDefaultValueComponent', + inheritAttrs: false, + customOptions: {} +} +</script> +<script setup> +const handle = {} +const ExtraParams = provide('EverrightExtraParams', { + inSubformDefaultValueComponent: true, + handle +}) +const ER = inject('Everright') +const EReditorPreviewRef = ref('') +const scrollbarRef = ref() +const { + t, + lang +} = hooks.useI18n() +const { + target, + state +} = hooks.useTarget() +const ns = hooks.useNamespace('ConfigSubformDefaultValueComponent') +const dialogVisible = ref(false) +const handleClosed = () => { + // tabs.value.forEach(tab => { + // tab.rules = [] + // }) +} +const openDialog = async () => { + dialogVisible.value = true + let rawData = {} + if (state.mode === 'config') { + rawData = utils.generateData() + rawData.list = [target.value] + rawData = utils.disassemblyData1(_.cloneDeep(rawData)) + } else { + rawData = ER.getData() + rawData.logic = {} + rawData.list = [{ + type: 'inline', + columns: [ + target.value.id + ] + }] + } + await nextTick() + const value = {} + value[target.value.key] = target.value.options.defaultValue + EReditorPreviewRef.value.setData(rawData, value) +} +const closeDialog = () => { + dialogVisible.value = false +} +const handleAction = (type) => { + switch (type) { + case 0: + closeDialog() + break + case 1: + handle.handleAdd() + break + case 2: + target.value.options.defaultValue = EReditorPreviewRef.value.getData()[target.value.key] + closeDialog() + break + } +} +</script> +<template> + <el-drawer + destroy-on-close + size="60%" + :modal="false" + append-to-body + :close-on-press-escape="false" + :with-header="false" + @closed="handleClosed" + :class="[ns.b()]" + v-model="dialogVisible"> + <div>{{ t('er.config.propsPanel.defaultContent') }}</div> + <div> + <el-scrollbar ref="scrollbarRef" max-height="calc(100vh - 180px)"> + <er-form-preview + ref="EReditorPreviewRef" + :isShowCompleteButton="false" + /> + </el-scrollbar> + <el-button + :class="[ns.e('button')]" + @click="handleAction(1)" + v-bind="utils.addTestId('configPanel:defaultValue:addButton')" + > + {{ t('er.public.add')}} + </el-button> + </div> + <template #footer> + <span class="dialog-footer"> + <el-button @click="handleAction(0)"> + {{ t('er.public.cancel')}} + </el-button> + <el-button type="primary" @click="handleAction(2)"> + {{ t('er.public.confirm')}} + </el-button> + </span> + </template> + </el-drawer> + <el-button + v-bind="utils.addTestId('configPanel:defaultValue:button')" + style="width: 100%;" + type="primary" + @click="openDialog"> + {{ t('er.config.propsPanel.setDefaultContent') }} + </el-button> +</template> diff --git a/packages/formEditor/components/Panels/Config/components/generateFilterdata.js b/packages/formEditor/components/Panels/Config/components/generateFilterdata.js index 7bcbdd5..00037f6 100644 --- a/packages/formEditor/components/Panels/Config/components/generateFilterdata.js +++ b/packages/formEditor/components/Panels/Config/components/generateFilterdata.js @@ -138,6 +138,7 @@ const generateIfFilterOptionsData = (activeTab, fields) => { break case 'signature': case 'uploadfile': + case 'subform': filterNode.renderType = 'NONE' filterNode.operatorKey = 'Text' filterNode.includeOperator = { @@ -411,9 +412,15 @@ const generateThenFilterConditionsData = (activeTab, fields) => { } }) } +const findValidFields = (fields) => { + return fields.filter(field => { + return field.type === 'subform' || !utils.checkIsInSubform(field) + }) +} export { generateIfFilterOptionsData, generateIfFilterConditionsData, generateThenFilterOptionsData, - generateThenFilterConditionsData + generateThenFilterConditionsData, + findValidFields } diff --git a/packages/formEditor/components/Panels/Config/index.vue b/packages/formEditor/components/Panels/Config/index.vue index 9ee3114..3209315 100644 --- a/packages/formEditor/components/Panels/Config/index.vue +++ b/packages/formEditor/components/Panels/Config/index.vue @@ -1,7 +1,7 @@ <script> import utils from '@ER/utils' import hooks from '@ER/hooks' -import { ref, computed, reactive, watch, onMounted, inject } from 'vue' +import { ref, computed, reactive, watch, onMounted, inject, h } from 'vue' import _ from 'lodash-es' import Icon from '@ER/icon' import PanelsConfigComponentsPropsPanel from '@ER/formEditor/components/Panels/Config/components/PropsPanel.vue' @@ -155,10 +155,21 @@ watch(target, () => { <el-aside :class="[ns.b()]" :width="ER.props.configPanelWidth"> <el-breadcrumb :class="[ns.e('breadcrumb')]" - separator-icon="ArrowRight" + :separator-icon="() => ( + h('svg', { + viewBox: '0 0 1024 1024', + xmlns: 'http://www.w3.org/2000/svg' + }, + h('path', { + fill: 'currentColor', + d: 'M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z' + }) + ) + )" v-bind="utils.addTestId('configPanel:breadcrumb')" > - <el-breadcrumb-item @click="(index !== bars.length - 1 && item.node.value !== 'placeholder') && handleBreadcrumbClick(item.node)" v-for="(item, index) in bars" :key="index"> + <el-breadcrumb-item + @click="(index !== bars.length - 1 && item.node.value !== 'placeholder') && handleBreadcrumbClick(item.node)" v-for="(item, index) in bars" :key="index"> {{item.node.value === 'placeholder' ? '...' : item.label}} </el-breadcrumb-item> </el-breadcrumb> diff --git a/packages/formEditor/components/Panels/Fields/index.jsx b/packages/formEditor/components/Panels/Fields/index.jsx index eb01668..3a0312d 100644 --- a/packages/formEditor/components/Panels/Fields/index.jsx +++ b/packages/formEditor/components/Panels/Fields/index.jsx @@ -23,7 +23,6 @@ export default { const { t } = hooks.useI18n() - // console.log(t('lhf')) const { state, setSelection @@ -42,7 +41,7 @@ export default { const slots = { item: ({ element }) => { return ( - <li onClick={() => addStore(element)}> + <li class={[ER.props.checkFieldsForNewBadge(element) ? ns.is('new') : '']} onClick={() => addStore(element)}> <Icon class={[ns.e('icon')]} icon={element.icon}></Icon> <span>{utils.fieldLabel(t, element)}</span> </li> diff --git a/packages/formEditor/componentsConfig.js b/packages/formEditor/componentsConfig.js index 8422e46..7fea22d 100644 --- a/packages/formEditor/componentsConfig.js +++ b/packages/formEditor/componentsConfig.js @@ -563,6 +563,21 @@ export const fieldsConfig = [ labelHidden: true, required: false } + }, + { + type: 'subform', + label: '子表单', + icon: 'subform', + id: '', + list: [ + [] + ], + options: { + defaultValue: [], + isShowLabel: true, + required: false, + disabled: false + } } ] } diff --git a/packages/formEditor/index.vue b/packages/formEditor/index.vue index 6a25991..43554d7 100644 --- a/packages/formEditor/index.vue +++ b/packages/formEditor/index.vue @@ -51,6 +51,10 @@ const props = defineProps(_.merge({ type: String, default: 'icon', validator: (value) => ['full', 'icon'].includes(value) + }, + checkFieldsForNewBadge: { + type: Function, + default: () => {} } }, defaultProps)) const layout = { @@ -229,7 +233,7 @@ const syncLayout = (platform, fn) => { layout[isPc ? 'mobile' : 'pc'] = original if (_.isEmpty(isPc ? layout.pc : layout.mobile)) { // const newData = _.cloneDeep(state.fields.map(e => wrapElement(e, true, false))) - const newData = state.fields.map(e => wrapElement(e, true, false, false, false)) + const newData = state.fields.filter(field => !utils.checkIsInSubform(field)).map(e => wrapElement(e, true, false, false, false)) fn && fn(newData) } else { // debugger @@ -239,7 +243,7 @@ const syncLayout = (platform, fn) => { } }) const copyData = _.cloneDeep(isPc ? layout.pc : layout.mobile) - const addFields = _.differenceBy(state.fields, layoutFields, 'id') + const addFields = _.differenceBy(state.fields.filter(field => !utils.checkIsInSubform(field)), layoutFields, 'id') const delFields = _.differenceBy(layoutFields, state.fields, 'id') utils.repairLayout(copyData, delFields) // console.log(JSON.stringify(copyData, '', 2)) @@ -258,7 +262,7 @@ const getLayoutDataByplatform = (platform) => { utils.disassemblyData2(original) return original } else { - const newData = _.cloneDeep(state.fields.map(e => wrapElement(e, true, false, false, false))) + const newData = _.cloneDeep(state.fields.filter(field => !utils.checkIsInSubform(field)).map(e => wrapElement(e, true, false, false, false))) utils.disassemblyData2(newData) return newData } @@ -274,7 +278,7 @@ const getLayoutDataByplatform = (platform) => { } }) const copyData = _.cloneDeep(isPc ? layout.pc : layout.mobile) - const addFields = _.cloneDeep(_.differenceBy(state.fields, layoutFields, 'id').map(e => wrapElement(e, true, false, false, false))) + const addFields = _.cloneDeep(_.differenceBy(state.fields.filter(field => !utils.checkIsInSubform(field)), layoutFields, 'id').map(e => wrapElement(e, true, false, false, false))) const delFields = _.differenceBy(layoutFields, state.fields, 'id') utils.repairLayout(copyData, delFields) utils.disassemblyData2(addFields) @@ -304,36 +308,24 @@ const fireEvent = (type, data) => { data }) } -provide('Everright', { - state, - setSelection, - props, - wrapElement, - delField, - addField, - switchPlatform, - addFieldData, - canvesScrollRef, - fireEvent -}) const ns = hooks.useNamespace('Main', state.Namespace) const getData1 = () => { - return Object.assign(utils.disassemblyData1(_.cloneDeep({ + return utils.disassemblyData1(_.cloneDeep({ list: state.store, config: state.config, - data: state.data - })), { + data: state.data, logic: state.logic - }) + })) } const getData2 = () => { + const fields = utils.processField(_.cloneDeep(state.store)) layout.pc = getLayoutDataByplatform('pc') layout.mobile = getLayoutDataByplatform('mobile') return _.cloneDeep({ layout, data: state.data, config: state.config, - fields: state.fields, + fields: fields, logic: state.logic }) } @@ -341,6 +333,7 @@ const setData1 = (data) => { if (_.isEmpty(data)) return false // stop() const newData = utils.combinationData1(_.cloneDeep(data)) + // console.log(newData.list[0].columns[0].list[0][0].columns[0]) isShow.value = false // console.log(data.list.slice(data.list.length - 1)) state.store = newData.list @@ -355,6 +348,7 @@ const setData1 = (data) => { }) nextTick(() => { isShow.value = true + // setSelection(state.store[0]) // restart() }) } @@ -372,6 +366,7 @@ const setData2 = (data) => { state.store = curLayout state.config = newData.config state.data = newData.data + state.logic = newData.logic setSelection(state.config) state.store.forEach((e) => { utils.addContext(e, state.store) @@ -455,6 +450,19 @@ watch(() => state.selected, (newVal) => { }) const onClickOutside = () => { } +provide('Everright', { + state, + setSelection, + props, + wrapElement, + delField, + addField, + switchPlatform, + addFieldData, + canvesScrollRef, + fireEvent, + getData +}) </script> <template> <el-dialog diff --git a/packages/formEditor/locale/en.js b/packages/formEditor/locale/en.js index cdd52e0..04f9905 100644 --- a/packages/formEditor/locale/en.js +++ b/packages/formEditor/locale/en.js @@ -33,7 +33,8 @@ export default { divider: 'Divider', container: 'Container', field: 'Basic field', - defaultField: 'Default field' + defaultField: 'Default field', + subform: 'Sub-form' }, layout: { tabsCol: 'Tab panel', @@ -172,7 +173,8 @@ export default { alpha: 'Alpha', anyNode: 'Select any node', clearable: 'clearable', - star: 'Star' + star: 'Star', + setDefaultContent: 'Set default content' } }, public: { diff --git a/packages/formEditor/locale/zh-cn.js b/packages/formEditor/locale/zh-cn.js index 02d9df0..1a09423 100644 --- a/packages/formEditor/locale/zh-cn.js +++ b/packages/formEditor/locale/zh-cn.js @@ -33,7 +33,8 @@ export default { divider: '分割线', container: '容器', field: '基础字段', - defaultField: '预设字段' + defaultField: '预设字段', + subform: '子表单' }, layout: { tabsCol: '标签面板', @@ -172,7 +173,8 @@ export default { alpha: '透明度', anyNode: '选择任意节点', clearable: '一键清除按钮', - star: '星星数' + star: '星星数', + setDefaultContent: '设置默认内容' } }, public: { diff --git a/packages/formEditor/preview.vue b/packages/formEditor/preview.vue index a3e0c68..fc38f95 100644 --- a/packages/formEditor/preview.vue +++ b/packages/formEditor/preview.vue @@ -29,31 +29,24 @@ const state = reactive({ data: {}, fields: [], logic: {}, - fieldsLogicState: new Map() + fieldsLogicState: new Map(), + remoteValues: new Map() }) const ns = hooks.useNamespace('Main', state.Namespace) hooks.useLogic(state) -// const checkFieldsValidation = async () => { -// for (const [key, value] of state.fieldsValidation) { -// if (value) { -// if (utils.isPc()) { -// ElMessage({ -// message: key.value, -// type: 'warning' -// }) -// } else { -// showNotify({ type: 'warning', message: key.value }) -// } -// return Promise.reject(key) -// } -// } -// return Promise.resolve() -// } -// window.checkFieldsValidation = checkFieldsValidation const getData = () => { const result = {} state.fields.forEach(e => { - result[e.key] = e.options.defaultValue + if (e.type === 'subform') { + result[e.key] = utils.getSubFormValues(e) + } else { + try { + if (!utils.checkIsInSubform(e)) { + result[e.key] = e.options.defaultValue + } + } catch (e) { + } + } }) return _.cloneDeep(result) } @@ -63,12 +56,18 @@ const fireEvent = (type, data) => { data }) } +const setValue = (field, value) => { + if (field.type === 'time' && !field.options.valueFormat) { + field.options.valueFormat = 'HH:mm:ss' + } + field.options.defaultValue = value +} provide('Everright', { state, getData, props, - fireEvent - // checkFieldsValidation + fireEvent, + setValue }) const setData2 = (data, value) => { const newData = _.cloneDeep(data) @@ -96,7 +95,7 @@ const setData2 = (data, value) => { }) } } -const setData1 = (data, value) => { +const setData1 = async (data, value) => { if (_.isEmpty(data)) return false const newData = utils.combinationData1(_.cloneDeep(data)) state.store = newData.list @@ -107,13 +106,21 @@ const setData1 = (data, value) => { state.store.forEach((e) => { utils.addContext(e, state.store) }) + const subforms = _.cloneDeep(state.fields.filter(e => e.type === 'subform')) + // For SubformLayout.jsx to get the first data + await nextTick() if (!_.isEmpty(value)) { - state.fields.forEach((e) => { - if (e.type === 'time' && !e.options.valueFormat) { - e.options.valueFormat = 'HH:mm:ss' - } - if (value[e.key]) { - e.options.defaultValue = value[e.key] + for (const key in value) { + state.remoteValues.set(key, value[key]) + } + state.fields.forEach((field) => { + if (field.type !== 'subform') { + try { + if (!utils.checkIsInSubform(field)) { + setValue(field, value[field.key]) + } + } catch (e) { + } } }) } @@ -126,6 +133,7 @@ defineExpose({ setData, getData }) +window.state = state </script> <template> <CanvesPanel v-if="state.store.length"></CanvesPanel> diff --git a/packages/hooks/use-logic/index.js b/packages/hooks/use-logic/index.js index 490342d..2efd72b 100644 --- a/packages/hooks/use-logic/index.js +++ b/packages/hooks/use-logic/index.js @@ -312,7 +312,16 @@ const listenEvent = (state) => { rules[type].forEach(rule => { const targetFields = findFieldsByid(rule.if.conditions.map(e => e.property), state.fields) const operator = (v) => rule.if.logicalOperator === 'and' ? v.every(v => v) : v.some(v => v) - // console.log(rule) + const subforms = targetFields.filter(field => field.type === 'subform') + subforms.forEach(subform => { + watch(() => utils.findSubFormAllFields(subform).map(e => e.options.defaultValue), () => { + subform.options.defaultValue = utils.getSubFormValues(subform) + }, { + immediate: true, + deep: true, + flush: 'sync' + }) + }) watch(() => targetFields.map(e => e.options.defaultValue), (values) => { // console.log(operator(values.map((value, index) => validator(rule.if.conditions[index], value, targetFields[index])))) switch (type) { diff --git a/packages/hooks/use-props/index.js b/packages/hooks/use-props/index.js index 316796d..1765230 100644 --- a/packages/hooks/use-props/index.js +++ b/packages/hooks/use-props/index.js @@ -1,10 +1,22 @@ -import { computed, isRef } from 'vue' +import { computed, inject, isRef } from 'vue' import { showToast } from 'vant' import dayjs from 'dayjs' import _ from 'lodash-es' import Region from '@ER/region/Region' import { areaList } from '@vant/area-data' import { useI18n } from '../use-i18n' +import utils from '@ER/utils' +const findPosition = (node, parent) => { + for (let y = 0; y < parent.list.length; y++) { + const row = parent.list[y] + const x = row.indexOf(node) + if (x !== -1) { + return { x, y } + } + } + + return { x: -1, y: -1 } +} const addValidate = (result, node, isPc, t) => { const { options @@ -17,17 +29,25 @@ const addValidate = (result, node, isPc, t) => { } else { const parent = e.context.parent let nodes = [] - if (parent.columns) { - nodes = parent.columns - result += 'columns.' - } else if (parent.list) { - nodes = parent.list - result += 'list.' - } else if (parent.rows) { - nodes = parent.rows - result += 'rows.' + if (parent.type === 'subform') { + const { + x, + y + } = findPosition(e, parent) + result += `list.${y}.${x}` + } else { + if (parent.columns) { + nodes = parent.columns + result += 'columns.' + } else if (parent.list) { + nodes = parent.list + result += 'list.' + } else if (parent.rows) { + nodes = parent.rows + result += 'rows.' + } + result += nodes.indexOf(e) } - result += nodes.indexOf(e) } return result }).join('.') + '.options.defaultValue' @@ -35,31 +55,48 @@ const addValidate = (result, node, isPc, t) => { const obj = { } - // if (node.type === 'select') { - // // obj.type = 'array' - // } const validator = (...arg0) => new Promise((...arg1) => { - const resolve = arg1[0] + const resolve = () => { + arg1[0]() + } const reject = isPc ? arg1[1] : (message) => { obj.message = message arg1[0](false) } - // const value = options.isShowTrim ? (isPc ? arg0[1] : arg0[0]).trim() : (isPc ? arg0[1] : arg0[0]) - // let message - // let result = true - // let msg = '' let value = isPc ? arg0[1] : arg0[0] // only for mobile if (/^(signature|radio|checkbox|select|html)$/.test(node.type)) { value = options.defaultValue } const newValue = options.isShowTrim ? value.trim() : value - // if (options.required && (!newValue || newValue === null || newValue === undefined || (Array.isArray(newValue) && !newValue.length))) { - if (result.required && (newValue === '' || newValue === null || newValue === undefined || (Array.isArray(newValue) && !newValue.length))) { - reject(t('er.validateMsg.required')) - return + if (node.type === 'subform') { + const allFields = utils.findSubFormAllFields(node) + if (result.required) { + if (allFields.length) { + if (allFields.some(e => utils.isEmpty(e.options.isShowTrim ? e.options.defaultValue.trim() : e.options.defaultValue))) { + reject(t('er.validateMsg.required')) + } + } else { + reject(t('er.validateMsg.required')) + } + } else { + resolve() + } + } else { + let isRequired = result.required + if (utils.checkIsInSubform(node)) { + const parent = node?.context?.parent?.context?.parent + if (parent) { + const parentProps = useProps(state, parent, isPc).value + isRequired = parentProps.required + } + } + if (isRequired && node.type !== 'subform' && utils.isEmpty(newValue)) { + reject(t('er.validateMsg.required')) + return + } } switch (node.type) { case 'input': @@ -126,6 +163,7 @@ export const useProps = (state, data, isPc = true, isRoot = false, specialHandli t } = useI18n() return computed(() => { + const ExtraParams = inject('EverrightExtraParams', {}) let node = isRoot ? data.config : data let result = {} const platform = isPc ? 'pc' : 'mobile' @@ -167,6 +205,20 @@ export const useProps = (state, data, isPc = true, isRoot = false, specialHandli result.required = result.disabled ? false : required === 1 } } + if (utils.checkIsInSubform(node)) { + const parent = node?.context?.parent?.context?.parent + if (parent) { + const parentProps = useProps(state, parent, isPc).value + result.disabled = parentProps.disabled + result.required = parentProps.required + } + } + try { + if (ExtraParams.inSubformDefaultValueComponent) { + result.disabled = result.required = false + } + } catch (e) { + } addValidate(result, node, isPc, t) if (isPc) { result.labelWidth = options.isShowLabel ? options.labelWidth + 'px' : 'auto' diff --git a/packages/hooks/use-target/index.js b/packages/hooks/use-target/index.js index 21f3eb3..c3e58cb 100644 --- a/packages/hooks/use-target/index.js +++ b/packages/hooks/use-target/index.js @@ -97,6 +97,11 @@ export const useTarget = () => { return /^(edit|config)$/.test(state.mode) } }) + const isSelectSubform = computed({ + get () { + return checkTypeBySelected(['subform']) + } + }) return { state, setSelection, @@ -113,6 +118,7 @@ export const useTarget = () => { isSelectTable, isSelectRoot, isPc, - isEditModel + isEditModel, + isSelectSubform } } diff --git a/packages/icon/svg/subform.svg b/packages/icon/svg/subform.svg new file mode 100644 index 0000000..7d8dffe --- /dev/null +++ b/packages/icon/svg/subform.svg @@ -0,0 +1 @@ +<svg fill="currentColor" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path d="M10 28V35H18" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M18 28H42V42H18V35V28Z" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="6" y1="13.5" x2="6" y2="12.5" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="6" y1="20" x2="6" y2="19" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="6" y1="7" x2="6" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="13.5" x2="32" y2="12.5" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="20" x2="32" y2="19" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="7" x2="32" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="20" x2="31" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="7" y1="20" x2="6" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="7" y1="6" x2="6" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="13" y1="6" x2="12" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="19.5" y1="6" x2="18.5" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="19.5" y1="20" x2="18.5" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="26" y1="6" x2="25" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="13" y1="20" x2="12" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="26" y1="20" x2="25" y2="20" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><line x1="32" y1="6" x2="31" y2="6" stroke="#333" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg> diff --git a/packages/region/index.vue b/packages/region/index.vue index 0af98ec..c1f4baf 100644 --- a/packages/region/index.vue +++ b/packages/region/index.vue @@ -301,7 +301,7 @@ onMounted(() => { :class="[ns.e('icon'), 'icon-circle-close']" @click.stop="handleClear" > - <circle-close /> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="m466.752 512-90.496-90.496a32 32 0 0 1 45.248-45.248L512 466.752l90.496-90.496a32 32 0 1 1 45.248 45.248L557.248 512l90.496 90.496a32 32 0 1 1-45.248 45.248L512 557.248l-90.496 90.496a32 32 0 0 1-45.248-45.248L466.752 512z"></path><path fill="currentColor" d="M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768zm0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896z"></path></svg> </el-icon> <el-icon v-else @@ -313,7 +313,7 @@ onMounted(() => { ]" @click.stop="togglePopperVisible()" > - <arrow-down /> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z"></path></svg> </el-icon> </template> </el-input> @@ -397,7 +397,7 @@ onMounted(() => { <span :class="[ns.e('label')]">{{node.label}}</span> <template v-if="!node.isLeaf"> <el-icon :class="['arrow-right', ns.e('postfix')]"> - <arrow-right /> + <svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-ea893728=""><path fill="currentColor" d="M340.864 149.312a30.592 30.592 0 0 0 0 42.752L652.736 512 340.864 831.872a30.592 30.592 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z"></path></svg> </el-icon> </template> </li> diff --git a/packages/theme/formEditor/ConfigSubformDefaultValueComponent.scss b/packages/theme/formEditor/ConfigSubformDefaultValueComponent.scss new file mode 100644 index 0000000..06b7132 --- /dev/null +++ b/packages/theme/formEditor/ConfigSubformDefaultValueComponent.scss @@ -0,0 +1,14 @@ +@include b(ConfigSubformDefaultValueComponent) { + @include e(button) { + width: calc(100% - 20px); + border: none; + margin: 10px; + box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.1) !important; + &:focus { + background-color: transparent; + } + &:hover { + background: var(--el-button-hover-bg-color); + } + } +} diff --git a/packages/theme/formEditor/Fields.scss b/packages/theme/formEditor/Fields.scss index c50c058..831fa22 100644 --- a/packages/theme/formEditor/Fields.scss +++ b/packages/theme/formEditor/Fields.scss @@ -21,7 +21,20 @@ justify-content: space-between; flex-wrap: wrap; li { - overflow: hidden; + @include when(new) { + &::after { + content: 'new'; + position: absolute; + top: -6px; + right: 0; + color: #fff; + background-color: #f00; + font-size: 12px; + padding: 0 6px; + border-radius: 10px; + } + } + position: relative; border-radius: 4px; margin: 4px; background: #F9F9F9; diff --git a/packages/theme/formEditor/SubformLayout.scss b/packages/theme/formEditor/SubformLayout.scss new file mode 100644 index 0000000..cb12dc9 --- /dev/null +++ b/packages/theme/formEditor/SubformLayout.scss @@ -0,0 +1,58 @@ +@include b(SubformLayout) { + @include e(content) { + width: 100%; + } + @include e(item) { + position: relative; + [data-layout-type="subform"] { + padding-left: 50px; + } + } + .el-form-item { + display: block; + .el-form-item__label { + display: block; + height: auto; + text-align: left; + margin-bottom: 8px; + line-height: 22px; + } + } + @include e(button) { + width: 50px; + top: 10px; + left: 10px; + position: absolute; + button { + margin: 0 !important; + } + button:last-child { + display: none; + svg { + color: #ffffff; + } + } + } + @include e(edit) { + &:hover { + background: #F9F9F9; + .#{$namespace}-SubformLayout__button { + button:first-child { + display: none; + } + button:last-child { + display: inline-flex; + } + } + } + } + @include e(addButton) { + padding-top: 20px; + //width: 100%; + //button { + // //border: 1px dashed #409EFF; + // width: 100%; + // box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1); + //} + } +} diff --git a/packages/theme/formEditor/index.scss b/packages/theme/formEditor/index.scss index 2222462..c400a9d 100644 --- a/packages/theme/formEditor/index.scss +++ b/packages/theme/formEditor/index.scss @@ -15,6 +15,7 @@ $namespace: 'Everright-formEditor'; @import './TabsLayout'; @import './CollapseLayout'; @import './InlineLayout'; +@import './SubformLayout'; @import './DragGableLayout'; @import './SelectElement'; @import './FormTypes/Signature'; @@ -37,4 +38,5 @@ $namespace: 'Everright-formEditor'; @import './ConfigCollapseComponent'; @import './ConfigTypeComponent'; @import './ConfigLogicComponent'; +@import './ConfigSubformDefaultValueComponent'; diff --git a/packages/utils/addContext.js b/packages/utils/addContext.js index c4232ce..baebc9d 100644 --- a/packages/utils/addContext.js +++ b/packages/utils/addContext.js @@ -3,6 +3,7 @@ import dayjs from 'dayjs' import _ from 'lodash-es' import { nanoid } from './nanoid' import { wrapElement } from './field' +import utils from '@ER/utils/index.js' const getNodes = (node, key) => { const { @@ -595,11 +596,11 @@ export const addContext = (node, parent, fn) => { arr.splice(index + 1, 0, newNode) }, delete () { - // console.log(123123) - arr.splice(arr.indexOf(node), 1) - // if (node.context.parent.type === 'inline' && !arr.length) { - // node.context.parent.context.delete() - // } + if (utils.checkIsInSubform(node) && node.type === 'inline') { + arr[0].splice(arr[0].indexOf(node), 1) + } else { + arr.splice(arr.indexOf(node), 1) + } }, appendCol () { const newNode = wrapElement({ @@ -874,7 +875,12 @@ export const addContext = (node, parent, fn) => { enumerable: false, configurable: true }) - const nodes = node.columns || node.list || node.rows || [] + let nodes = [] + if (node.type === 'subform') { + nodes = node.list[0] + } else { + nodes = node.columns || node.list || node.rows || [] + } nodes.forEach(e => { addContext(e, node, fn) }) diff --git a/packages/utils/field.js b/packages/utils/field.js index 2e057ef..277d54e 100644 --- a/packages/utils/field.js +++ b/packages/utils/field.js @@ -1,9 +1,9 @@ import _ from 'lodash-es' import { nanoid } from './nanoid' -const fieldsRe = /^(input|textarea|number|radio|checkbox|select|time|date|rate|switch|slider|html|cascader|uploadfile|signature|region)$/ +const fieldsRe = /^(input|textarea|number|radio|checkbox|select|time|date|rate|switch|slider|html|cascader|uploadfile|signature|region|subform)$/ const deepTraversal = (node, fn) => { fn(node) - const nodes = node.list || node.rows || node.columns || node.children || [] + const nodes = node.type === 'subform' ? node.list[0] : (node.list || node.rows || node.columns || node.children || []) nodes.forEach(e => { deepTraversal(e, fn) }) @@ -11,6 +11,7 @@ const deepTraversal = (node, fn) => { const wrapElement = (element, fn) => { const result = element deepTraversal(result, (node) => { + if (Array.isArray(node)) return false if (!node.style) { node.style = {} } @@ -72,7 +73,7 @@ const flatNodes = (nodes, excludes, fn, excludesFn) => { } else { excludesFn && excludesFn(nodes, node, currentIndex) } - const children = node.list || node.rows || node.columns || node.children || [] + const children = node.type === 'subform' ? node.list[0] : (node.list || node.rows || node.columns || node.children || []) res = res.concat(flatNodes(children, excludes, fn, excludesFn)) return res }, []) @@ -81,14 +82,16 @@ const getAllFields = (store) => flatNodes(store, excludes) const pickfields = (list) => { return flatNodes(list, excludes) } +const processField = (list) => flatNodes(list, excludes, (nodes, node, currentIndex) => { + nodes[currentIndex] = node.id +}) const disassemblyData1 = (data) => { const result = { list: data.list, config: data.config, - fields: flatNodes(data.list, excludes, (nodes, node, currentIndex) => { - nodes[currentIndex] = node.id - }), - data: data.data + fields: processField(data.list), + data: data.data, + logic: data.logic } return result } @@ -100,21 +103,29 @@ const combinationData1 = (data) => { fields: data.fields, logic: data.logic } - flatNodes(data.list, excludes, (nodes, node, currentIndex) => { + const fn = (nodes, node, currentIndex) => { const cur = _.find(data.fields, { id: node }) if (!_.isEmpty(cur)) { + if (cur.type === 'subform') { + flatNodes(cur.list[0], excludes, fn) + } nodes[currentIndex] = cur } - }) + } + flatNodes(data.list, excludes, fn) return result } const combinationData2 = (list, fields) => { - flatNodes(list, excludes, (nodes, node, currentIndex) => { + const fn = (nodes, node, currentIndex) => { const cur = _.find(fields, { id: node }) if (!_.isEmpty(cur)) { + if (cur.type === 'subform') { + flatNodes(cur.list[0], excludes, fn) + } nodes[currentIndex] = cur } - }) + } + flatNodes(list, excludes, fn) } const repairLayout = (layout, fields) => { flatNodes(layout, excludes, (nodes, node, currentIndex) => { @@ -196,6 +207,40 @@ const transferData = (lang, path, locale, options = {}) => { return result } const isNull = (e) => e === '' || e === null || e === undefined +const checkIsInSubform = (node) => { + if (!node) return false + let result = false + let parent = node.context.parent + while (parent && !result) { + if (parent.type === 'subform') { + result = true + } + parent = parent.context?.parent + } + return result +} +const getSubFormValues = (subform) => subform.list.map(e => { + const cur = {} + const children = [] + e.forEach(e => { + e.columns.forEach(e => { + children.push(e) + }) + }) + children.forEach(e => { + cur[e.key] = e.options.defaultValue + }) + return cur +}) +const findSubFormAllFields = (subform) => { + const result = [] + subform.list.forEach(e => { + e.forEach(e => { + result.push(...e.columns) + }) + }) + return result +} export { syncWidthByPlatform, wrapElement, @@ -213,5 +258,9 @@ export { transferData, transferLabelPath, isNull, - repairLayout + repairLayout, + checkIsInSubform, + getSubFormValues, + findSubFormAllFields, + processField } diff --git a/packages/utils/generateOptions.js b/packages/utils/generateOptions.js index 79c39a3..bd82d6b 100644 --- a/packages/utils/generateOptions.js +++ b/packages/utils/generateOptions.js @@ -1,4 +1,6 @@ import { nanoid } from './nanoid' +import { globalConfig } from '@ER/formEditor/componentsConfig.js' +import _ from 'lodash-es' export const generateOptions = (len) => { const result = [] while (len--) { @@ -9,3 +11,16 @@ export const generateOptions = (len) => { } return result } +export const generateData = (layoutType = 1) => { + const result = { + config: _.cloneDeep(globalConfig) + } + result.logic = result.data = {} + if (layoutType === 1) { + result.list = [] + } + if (layoutType === 2) { + result.layout = [] + } + return result +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index feba837..ecf65f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,6 @@ specifiers: '@ckeditor/ckeditor5-vue': ^4.0.1 '@commitlint/cli': ^17.4.4 '@commitlint/config-conventional': ^17.4.4 - '@element-plus/icons-vue': ^2.0.10 '@vant/area-data': ^1.4.0 '@vant/touch-emulator': ^1.4.0 '@vitejs/plugin-vue': ^3.2.0 @@ -34,7 +33,7 @@ specifiers: eslint-plugin-n: ^15.6.1 eslint-plugin-promise: ^6.1.1 eslint-plugin-vue: ^9.9.0 - everright-filter: ^0.0.22 + everright-filter: ^1.1.1 express: ^4.18.2 husky: ^8.0.3 jsdom: ^22.0.0 @@ -68,7 +67,7 @@ dependencies: canvas: 2.11.2 dayjs: registry.npmmirror.com/dayjs/1.11.7 element-plus: registry.npmmirror.com/element-plus/2.3.1_vue@3.2.47 - everright-filter: 0.0.22_ap7lbsygcc6j7vt74w6nzwfhdi + everright-filter: 1.1.1_ap7lbsygcc6j7vt74w6nzwfhdi jss: registry.npmmirror.com/jss/10.10.0 jss-preset-default: registry.npmmirror.com/jss-preset-default/10.10.0 lodash-es: registry.npmmirror.com/lodash-es/4.17.21 @@ -90,7 +89,6 @@ devDependencies: '@ckeditor/ckeditor5-vue': registry.npmmirror.com/@ckeditor/ckeditor5-vue/4.0.1 '@commitlint/cli': registry.npmmirror.com/@commitlint/cli/17.4.4 '@commitlint/config-conventional': registry.npmmirror.com/@commitlint/config-conventional/17.4.4 - '@element-plus/icons-vue': registry.npmmirror.com/@element-plus/icons-vue/2.1.0_vue@3.2.47 '@vitejs/plugin-vue': registry.npmmirror.com/@vitejs/plugin-vue/3.2.0_vite@3.2.5+vue@3.2.47 '@vitejs/plugin-vue-jsx': registry.npmmirror.com/@vitejs/plugin-vue-jsx/2.1.1_vite@3.2.5+vue@3.2.47 '@vue/compiler-sfc': registry.npmmirror.com/@vue/compiler-sfc/3.2.47 @@ -280,10 +278,6 @@ packages: dev: true optional: true - /@vue/devtools-api/6.5.0: - resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} - dev: false - /@vue/server-renderer/3.2.47_vue@3.2.47: resolution: {integrity: sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==} requiresBuild: true @@ -823,8 +817,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /everright-filter/0.0.22_ap7lbsygcc6j7vt74w6nzwfhdi: - resolution: {integrity: sha512-wVKk4FCgB1G6owZwByz/wFqeQ3Gx2UzIVqd2OwElizxaT7fX2n/KpghE5gs6YiyenupQZRfsxCv2Ou77UFNJCw==} + /everright-filter/1.1.1_ap7lbsygcc6j7vt74w6nzwfhdi: + resolution: {integrity: sha512-xQGZ15FcLVw8ja54JiL6nLlvMdviGXketkNwUtfzV+Hvxt0cgr10bKZcaEES1aGzfw+qo5W6c5s1bVebdCL66g==} peerDependencies: element-plus: ^2.2.28 vue: ^3.2.45 @@ -839,8 +833,6 @@ packages: lodash-es: 4.17.21 nzh: 1.0.8 vue: registry.npmmirror.com/vue/3.2.47 - vue-router: 4.1.6_vue@3.2.47 - vuedraggable: 4.1.0_vue@3.2.47 written-number: 0.11.1 transitivePeerDependencies: - '@vue/composition-api' @@ -1090,7 +1082,6 @@ packages: /lodash-es/4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - dev: false /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1441,10 +1432,6 @@ packages: once: 1.4.0 simple-concat: 1.0.1 - /sortablejs/1.14.0: - resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} - dev: false - /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -1735,24 +1722,6 @@ packages: vue: registry.npmmirror.com/vue/3.2.47 dev: false - /vue-router/4.1.6_vue@3.2.47: - resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==} - peerDependencies: - vue: ^3.2.0 - dependencies: - '@vue/devtools-api': 6.5.0 - vue: registry.npmmirror.com/vue/3.2.47 - dev: false - - /vuedraggable/4.1.0_vue@3.2.47: - resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} - peerDependencies: - vue: ^3.0.1 - dependencies: - sortablejs: 1.14.0 - vue: registry.npmmirror.com/vue/3.2.47 - dev: false - /w3c-xmlserializer/4.0.0: resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} engines: {node: '>=14'} @@ -2408,7 +2377,7 @@ packages: '@ckeditor/ckeditor5-engine': registry.npmmirror.com/@ckeditor/ckeditor5-engine/35.4.0 '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0 '@ckeditor/ckeditor5-widget': registry.npmmirror.com/@ckeditor/ckeditor5-widget/35.4.0 - lodash-es: registry.npmmirror.com/lodash-es/4.17.21 + lodash-es: 4.17.21 dev: true registry.npmmirror.com/@ckeditor/ckeditor5-cloud-services/35.4.0: @@ -2429,7 +2398,7 @@ packages: '@ckeditor/ckeditor5-engine': registry.npmmirror.com/@ckeditor/ckeditor5-engine/35.4.0 '@ckeditor/ckeditor5-ui': registry.npmmirror.com/@ckeditor/ckeditor5-ui/35.4.0 '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0 - lodash-es: registry.npmmirror.com/lodash-es/4.17.21 + lodash-es: 4.17.21 dev: true registry.npmmirror.com/@ckeditor/ckeditor5-easy-image/35.4.0: @@ -2478,7 +2447,7 @@ packages: engines: {node: '>=14.0.0', npm: '>=5.7.1'} dependencies: '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0 - lodash-es: registry.npmmirror.com/lodash-es/4.17.21 + lodash-es: 4.17.21 dev: true registry.npmmirror.com/@ckeditor/ckeditor5-enter/35.4.0: @@ -2630,7 +2599,7 @@ packages: dependencies: '@ckeditor/ckeditor5-core': registry.npmmirror.com/@ckeditor/ckeditor5-core/35.4.0 '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0 - lodash-es: registry.npmmirror.com/lodash-es/4.17.21 + lodash-es: 4.17.21 dev: true registry.npmmirror.com/@ckeditor/ckeditor5-undo/35.4.0: @@ -2661,7 +2630,7 @@ packages: version: 35.4.0 engines: {node: '>=14.0.0', npm: '>=5.7.1'} dependencies: - lodash-es: registry.npmmirror.com/lodash-es/4.17.21 + lodash-es: 4.17.21 dev: true registry.npmmirror.com/@ckeditor/ckeditor5-vue/4.0.1: @@ -2683,7 +2652,7 @@ packages: '@ckeditor/ckeditor5-typing': registry.npmmirror.com/@ckeditor/ckeditor5-typing/35.4.0 '@ckeditor/ckeditor5-ui': registry.npmmirror.com/@ckeditor/ckeditor5-ui/35.4.0 '@ckeditor/ckeditor5-utils': registry.npmmirror.com/@ckeditor/ckeditor5-utils/35.4.0 - lodash-es: registry.npmmirror.com/lodash-es/4.17.21 + lodash-es: 4.17.21 dev: true registry.npmmirror.com/@commitlint/cli/17.4.4: @@ -2913,6 +2882,7 @@ packages: vue: ^3.2.0 dependencies: vue: registry.npmmirror.com/vue/3.2.47 + dev: false registry.npmmirror.com/@eslint-community/eslint-utils/4.3.0_eslint@8.36.0: resolution: {integrity: sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.3.0.tgz} @@ -3447,7 +3417,7 @@ packages: name: '@vueuse/shared' version: 9.13.0 dependencies: - vue-demi: registry.npmmirror.com/vue-demi/0.13.11_vue@3.2.47 + vue-demi: 0.13.11_vue@3.2.47 transitivePeerDependencies: - '@vue/composition-api' - vue diff --git a/test/__snapshots__/erGeneratorData.test.js.snap b/test/__snapshots__/erGeneratorData.test.js.snap index 7b8d706..8e467b0 100644 --- a/test/__snapshots__/erGeneratorData.test.js.snap +++ b/test/__snapshots__/erGeneratorData.test.js.snap @@ -767,6 +767,39 @@ exports[`Generate field data: > Slider 1`] = ` } `; +exports[`Generate field data: > Subform 1`] = ` +{ + "columns": [ + { + "icon": "subform", + "id": "{{test-id-nanoid}}", + "key": "subform_{{test-id-nanoid}}", + "label": "Sub-form", + "list": [ + [], + ], + "options": { + "defaultValue": [], + "disabled": false, + "isShowLabel": true, + "required": false, + }, + "style": { + "width": { + "mobile": "100%", + "pc": "100%", + }, + }, + "type": "subform", + }, + ], + "id": "{{test-id-nanoid}}", + "key": "inline_{{test-id-nanoid}}", + "style": {}, + "type": "inline", +} +`; + exports[`Generate field data: > Switch 1`] = ` { "columns": [ diff --git a/test/disassemblyData/layoutType1.test.js b/test/disassemblyData/layoutType1.test.js new file mode 100644 index 0000000..1828b8b --- /dev/null +++ b/test/disassemblyData/layoutType1.test.js @@ -0,0 +1,77 @@ +import { describe, assert, expect, test, beforeEach, beforeAll, vi } from 'vitest' +import erGeneratorData from '@ER/formEditor/generatorData.js' +import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js' +import _ from 'lodash-es' +import { nextTick } from 'vue' +import { _mount, wrapLayoutDataByLayoutType } from '@ER-test/utils.js' +describe('Fields and layout not separated', () => { + let wrapper = {} + const handleListener = vi.fn() + let field = {} + beforeAll(() => { + vi.stubEnv('TESTIDTYPE', 'nanoid') + field = erGeneratorData(erComponentsConfig.fieldsConfig[0].list[0], true, 'en') + wrapper = _mount(` + <er-form-editor + @listener="handleListener" + :layoutType="1" + ref="EReditorRef"/> + `, () => ({ + handleListener + }) + ) + return () => { + vi.stubEnv('TESTIDTYPE', '') + } + }) + test('Only fields without layout', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const data = wrapLayoutDataByLayoutType([newField], [field.columns[0]]) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('Grid', async () => { + const data = wrapLayoutDataByLayoutType([erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en')], [field.columns[0]]) + data.list[0].columns[0].columns[0].list.push(field.columns[0].id) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('Table', async () => { + const data = wrapLayoutDataByLayoutType([erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[1]), true, 'en')], [field.columns[0]]) + data.list[0].columns[0].rows[0].columns[0].list.push(field.columns[0].id) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('Tabs', async () => { + const data = wrapLayoutDataByLayoutType([erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[2]), true, 'en')], [field.columns[0]]) + data.list[0].columns[0].columns[0].list.push(field.columns[0].id) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('nested layout:Grid > Grid > field', async () => { + const grid0 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en') + const grid1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en') + grid0.columns[0].columns[0].list.push(grid1) + grid1.columns[0].columns[0].list.push(field.columns[0].id) + const data = wrapLayoutDataByLayoutType([grid0], [field.columns[0]]) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('Sub-form', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + subForm.columns[0].list[0].push(newField) + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]]) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) +}) diff --git a/test/disassemblyData/layoutType2.test.js b/test/disassemblyData/layoutType2.test.js new file mode 100644 index 0000000..96a3293 --- /dev/null +++ b/test/disassemblyData/layoutType2.test.js @@ -0,0 +1,112 @@ +import { describe, assert, expect, test, beforeEach, beforeAll, vi } from 'vitest' +import erGeneratorData from '@ER/formEditor/generatorData.js' +import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js' +import _ from 'lodash-es' +import { nextTick } from 'vue' +import { _mount, wrapLayoutDataByLayoutType } from '@ER-test/utils.js' +describe('Fields and layout separated', () => { + let wrapper = {} + const handleListener = vi.fn() + let field = {} + beforeAll(() => { + vi.stubEnv('TESTIDTYPE', 'nanoid') + field = erGeneratorData(erComponentsConfig.fieldsConfig[0].list[0], true, 'en') + wrapper = _mount(` + <er-form-editor + @listener="handleListener" + :layoutType="2" + ref="EReditorRef"/> + `, () => ({ + handleListener + }) + ) + return () => { + vi.stubEnv('TESTIDTYPE', '') + } + }) + test('Only fields without layout', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const data = wrapLayoutDataByLayoutType({ + pc: [ + newField + ], + mobile: [ + newField + ] + }, [field.columns[0]], 2) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('Grid', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const layout = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en') + layout.columns[0].columns[0].list.push(newField) + const data = wrapLayoutDataByLayoutType({ + pc: [ + layout + ], + mobile: [ + layout + ] + }, [field.columns[0]], 2) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('Table', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const layout = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[1]), true, 'en') + layout.columns[0].rows[0].columns[0].list.push(newField) + const data = wrapLayoutDataByLayoutType({ + pc: [ + layout + ], + mobile: [ + layout + ] + }, [field.columns[0]], 2) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('Tabs', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const layout = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[2]), true, 'en') + layout.columns[0].columns[0].list.push(newField) + const data = wrapLayoutDataByLayoutType({ + pc: [ + layout + ], + mobile: [ + layout + ] + }, [field.columns[0]], 2) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) + test('nested layout:Grid > Grid > field', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const grid0 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en') + const grid1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[0]), true, 'en') + grid0.columns[0].columns[0].list.push(grid1) + grid1.columns[0].columns[0].list.push(newField) + const data = wrapLayoutDataByLayoutType({ + pc: [ + grid0 + ], + mobile: [ + grid0 + ] + }, [field.columns[0]], 2) + wrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + await nextTick() + expect(wrapper.findComponent({ ref: 'EReditorRef' }).vm.getData()).toEqual(data) + }) +}) diff --git a/test/erFormConfig.test.js b/test/erFormConfig.test.js index b5f3ce3..5b006ba 100644 --- a/test/erFormConfig.test.js +++ b/test/erFormConfig.test.js @@ -1,7 +1,6 @@ import { describe, assert, expect, test, beforeEach, vi, beforeAll, afterEach } from 'vitest' import { erComponentsConfig, erFormConfig, erGeneratorData, utils } from '@ER/formEditor/index.js' import { mount } from '@vue/test-utils' -import { Plus } from '@element-plus/icons-vue' import ElementPlus from 'element-plus' import Vant from 'vant' import { computed, nextTick, reactive, ref } from 'vue' @@ -18,9 +17,6 @@ const _mount = (template, data, otherObj) => mount( { attachTo: 'body', global: { - components: { - Plus - }, plugins: [ ElementPlus, Vant @@ -2659,4 +2655,98 @@ describe('Configuration options owned by the config panel', () => { expect(wrapper.find(utils.getTestId('configPanel:accordion')).exists()).toBe(false) expect(wrapper.find(utils.getTestId('configPanel:contentPosition')).exists()).toBe(true) }) + test('Subform:pc', async () => { + value0.value = store.layouts[9].id + await nextTick() + expect(wrapper.find(utils.getTestId('configPanel:breadcrumb')).findAll('.el-breadcrumb__item').map(e => e.text())).toEqual(['Form Attribute', 'Sub-form']) + expect(wrapper.find(utils.getTestId('configPanel:id')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:title')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:titleWidth')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:defaultValue')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:placeholder')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:isShowTrim')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:required')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:disabled')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:clearable')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:width')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:prepend')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:append')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:textareaHeight')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:wordLimit')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:controls')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:dataEntry')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:displayStyle')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:multiple')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:format')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:dateType')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:dateRange')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:star')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:allowHalf')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:sliderCount')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:step')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:precision')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:filterable')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:anyNode')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:accept')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:uploadLimit')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:fileSize')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:brushColor')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:justify')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:margin')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:padding')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:background')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:borderLine')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:tabsType')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:tabPosition')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:dataEntry3')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:accordion')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:contentPosition')).exists()).toBe(false) + }) + test('Subform:mobile', async () => { + value0.value = store.layouts[9].id + await nextTick() + expect(wrapper.find(utils.getTestId('configPanel:breadcrumb')).findAll('.el-breadcrumb__item').map(e => e.text())).toEqual(['Form Attribute', 'Sub-form']) + expect(wrapper.find(utils.getTestId('configPanel:id')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:title')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:titleWidth')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:defaultValue')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:placeholder')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:isShowTrim')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:required')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:disabled')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:clearable')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:width')).exists()).toBe(true) + expect(wrapper.find(utils.getTestId('configPanel:prepend')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:append')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:textareaHeight')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:wordLimit')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:controls')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:dataEntry')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:displayStyle')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:multiple')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:format')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:dateType')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:dateRange')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:star')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:allowHalf')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:sliderCount')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:step')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:precision')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:filterable')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:anyNode')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:accept')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:uploadLimit')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:fileSize')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:brushColor')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:justify')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:margin')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:padding')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:background')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:borderLine')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:tabsType')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:tabPosition')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:dataEntry3')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:accordion')).exists()).toBe(false) + expect(wrapper.find(utils.getTestId('configPanel:contentPosition')).exists()).toBe(false) + }) }) diff --git a/test/erGeneratorData.test.js b/test/erGeneratorData.test.js index 95bece9..4e840c9 100644 --- a/test/erGeneratorData.test.js +++ b/test/erGeneratorData.test.js @@ -96,4 +96,7 @@ describe('Generate field data:', () => { test('Divider', () => { expect(erGeneratorData(erComponentsConfig.fieldsConfig[2].list[4], true, 'en')).toMatchSnapshot() }) + test('Subform', () => { + expect(erGeneratorData(erComponentsConfig.fieldsConfig[2].list[5], true, 'en')).toMatchSnapshot() + }) }) diff --git a/test/formTypes/subform.test.js b/test/formTypes/subform.test.js new file mode 100644 index 0000000..d12a524 --- /dev/null +++ b/test/formTypes/subform.test.js @@ -0,0 +1,277 @@ +import { describe, assert, expect, test, beforeEach, beforeAll, vi } from 'vitest' +import { mount, flushPromises, enableAutoUnmount, config, DOMWrapper } from '@vue/test-utils' +import erGeneratorData from '@ER/formEditor/generatorData.js' +import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js' +import _ from 'lodash-es' +import { nextTick, reactive } from 'vue' +import { _mount, wrapLayoutDataByLayoutType } from '@ER-test/utils.js' +import { utils } from '@ER/formEditor/index.js' +describe('Field: subform', () => { + let previewWrapper = {} + let configWrapper = {} + const handleListener = vi.fn() + let field = {} + const layoutType = 1 + const store = reactive({ + fields: [], + sector: 'root' + }) + beforeAll(() => { + vi.stubEnv('TESTIDTYPE', 'nanoid') + previewWrapper = _mount(` + <er-form-preview + @listener="handleListener" + :layoutType="layoutType" + ref="EReditorRef"/> + `, () => ({ + handleListener, + layoutType + }) + ) + configWrapper = _mount(` + <er-form-config + @listener="handleListener" + :layoutType="layoutType" + :field="store.sector" + :fields="store.fields" + ref="EReditorRef"/> + `, () => ({ + handleListener, + layoutType, + store + }) + ) + return () => { + vi.stubEnv('TESTIDTYPE', '') + } + }) + beforeEach(() => { + field = erGeneratorData(erComponentsConfig.fieldsConfig[1].list[0], true, 'en') + }) + test('No child', async () => { + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0]], layoutType) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(false) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0) + previewWrapper.find('[data-test="er-complete-button"] button').trigger('click') + await flushPromises() + expect([list.columns[0]]).toStrictEqual(previewWrapper.findAll('[data-field-id]').map(element => element.element.dataset.fieldId)) + expect(previewWrapper.find(`[data-field-id="${list.columns[0]}"] .el-form-item`).classes()).not.toContain('is-required') + store.fields.push(subForm.columns[0]) + store.sector = subForm.columns[0] + await flushPromises() + expect(configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).exists()).toBe(false) + }) + test('No child: Required', async () => { + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + subForm.columns[0].options.required = true + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0]], layoutType) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(false) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0) + previewWrapper.find('[data-test="er-complete-button"] button').trigger('click') + await flushPromises() + expect([list.columns[0]]).toStrictEqual(previewWrapper.findAll('[data-field-id]').map(element => element.element.dataset.fieldId)) + expect(previewWrapper.find(`[data-field-id="${list.columns[0]}"] .el-form-item`).classes()).toContain('is-required') + }) + test('Only one child', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + subForm.columns[0].list[0].push(newField) + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]]) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0) + await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click') + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(1) + const subForm1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + subForm1.columns[0].list[0].push(_.cloneDeep(field)) + store.fields.push(subForm1.columns[0]) + store.sector = subForm1.columns[0] + await flushPromises() + expect(configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).exists()).toBe(true) + await configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).trigger('click') + await flushPromises() + const setDefaultEl = new DOMWrapper(document.querySelector('.Everright-formEditor-ConfigSubformDefaultValueComponent')) + expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0) + }) + test('Only one child: field Required', async () => { + const newField = _.cloneDeep(field) + field.columns[0].options.required = true + newField.columns[0] = newField.columns[0].id + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + subForm.columns[0].list[0].push(newField) + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]]) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0) + await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click') + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(1) + previewWrapper.find('[data-test="er-complete-button"] button').trigger('click') + await flushPromises() + expect([list.columns[0], newField.columns[0]]).toStrictEqual(previewWrapper.findAll('[data-field-id]').map(element => element.element.dataset.fieldId)) + expect(previewWrapper.find(`[data-field-id="${list.columns[0]}"] .el-form-item`).classes()).not.toContain('is-required') + expect(previewWrapper.find(`[data-field-id="${newField.columns[0]}"] .el-form-item`).classes()).toContain('is-required') + }) + test('Only one child: field Disabled', async () => { + const newField = _.cloneDeep(field) + field.columns[0].options.disabled = true + newField.columns[0] = newField.columns[0].id + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + subForm.columns[0].list[0].push(newField) + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]]) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(0) + await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click') + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(1) + previewWrapper.find('[data-test="er-complete-button"] button').trigger('click') + await new Promise(resolve => setTimeout(resolve, 1000)) + expect([list.columns[0], newField.columns[0]]).toStrictEqual(previewWrapper.findAll('[data-field-id]').map(element => element.element.dataset.fieldId)) + expect(previewWrapper.find(`[data-field-id="${newField.columns[0]}"] .el-input`).classes()).toContain('is-disabled') + }) + test('Only one child: Disabled', async () => { + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + subForm.columns[0].options.disabled = true + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + subForm.columns[0].list[0].push(newField) + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]]) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(false) + }) + test('Only one child: has 2 default contents', async () => { + const values = ['1', '2'] + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + subForm.columns[0].list[0].push(newField) + subForm.columns[0].options.defaultValue = values.map(e => { + const result = {} + result[field.columns[0].key] = e + return result + }) + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]]) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2) + await new Promise(resolve => setTimeout(resolve, 1000)) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item')).map(e => e.find('input').element.value)).toEqual(values) + await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click') + await flushPromises() + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))[2].find('input').element.value).toEqual('') + const subForm1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + subForm1.columns[0].list[0].push(_.cloneDeep(field)) + subForm1.columns[0].options.defaultValue = values.map(e => { + const result = {} + result[field.columns[0].key] = e + return result + }) + store.fields.push(subForm1.columns[0]) + store.sector = subForm1.columns[0] + await flushPromises() + expect(configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).exists()).toBe(true) + await configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).trigger('click') + await new Promise(resolve => setTimeout(resolve, 1000)) + const setDefaultEl = new DOMWrapper(document.querySelector('.Everright-formEditor-ConfigSubformDefaultValueComponent')) + expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2) + expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item')).map(e => e.find('input').element.value)).toEqual(values) + await setDefaultEl.find(utils.getTestId('configPanel:defaultValue:addButton')).trigger('click') + await flushPromises() + expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))[2].find('input').element.value).toEqual('') + }) + test('Only one child: has 2 default contents & Disabled', async () => { + const values = ['1', '2'] + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + subForm.columns[0].list[0].push(newField) + subForm.columns[0].options.defaultValue = values.map(e => { + const result = {} + result[field.columns[0].key] = e + return result + }) + subForm.columns[0].options.disabled = true + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]]) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(false) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2) + await new Promise(resolve => setTimeout(resolve, 1000)) + expect(previewWrapper.findAll(`[data-field-id="${newField.columns[0]}"]`).map(e => e.find('.el-input').classes())).toStrictEqual([ + [ + 'el-input', + 'el-input--default', + 'is-disabled', + 'el-input--suffix' + ], + [ + 'el-input', + 'el-input--default', + 'is-disabled', + 'el-input--suffix' + ] + ]) + }) + test('Only one child: has 2 default contents && field has default', async () => { + const values = ['1', '2'] + const addValue = 'everright-formeditor' + const newField = _.cloneDeep(field) + newField.columns[0] = newField.columns[0].id + const subForm = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + const list = _.cloneDeep(subForm) + list.columns[0] = subForm.columns[0].id + subForm.columns[0].list[0].push(newField) + subForm.columns[0].options.defaultValue = values.map(e => { + const result = {} + result[field.columns[0].key] = e + return result + }) + field.columns[0].options.defaultValue = addValue + const data = wrapLayoutDataByLayoutType([list], [subForm.columns[0], field.columns[0]]) + await previewWrapper.findComponent({ ref: 'EReditorRef' }).vm.setData(data) + expect(previewWrapper.find(utils.getTestId('SubformLayout:addButton')).exists()).toBe(true) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2) + await new Promise(resolve => setTimeout(resolve, 1000)) + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item')).map(e => e.find('input').element.value)).toEqual(values) + await previewWrapper.find(utils.getTestId('SubformLayout:addButton')).find('button').trigger('click') + await flushPromises() + expect(previewWrapper.findAll(utils.getTestId('SubformLayout:item'))[2].find('input').element.value).toEqual(addValue) + const subForm1 = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), true, 'en') + subForm1.columns[0].list[0].push(_.cloneDeep(field)) + subForm1.columns[0].options.defaultValue = values.map(e => { + const result = {} + result[field.columns[0].key] = e + return result + }) + store.fields.push(subForm1.columns[0]) + store.sector = subForm1.columns[0] + await flushPromises() + expect(configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).exists()).toBe(true) + await configWrapper.find(utils.getTestId('configPanel:defaultValue:button')).trigger('click') + await new Promise(resolve => setTimeout(resolve, 1000)) + const setDefaultEl = new DOMWrapper(document.querySelector('.Everright-formEditor-ConfigSubformDefaultValueComponent')) + expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))).toHaveLength(2) + expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item')).map(e => e.find('input').element.value)).toEqual(values) + await setDefaultEl.find(utils.getTestId('configPanel:defaultValue:addButton')).trigger('click') + await flushPromises() + expect(setDefaultEl.findAll(utils.getTestId('SubformLayout:item'))[2].find('input').element.value).toEqual(addValue) + }) +}) diff --git a/test/logic/er-form-preview/er-form-preview.test.js b/test/logic/er-form-preview/er-form-preview.test.js index ca90d32..b2dd6a9 100644 --- a/test/logic/er-form-preview/er-form-preview.test.js +++ b/test/logic/er-form-preview/er-form-preview.test.js @@ -3,7 +3,6 @@ import { mount, flushPromises, enableAutoUnmount } from '@vue/test-utils' import { nextTick, reactive } from 'vue' import _ from 'lodash-es' import ElementPlus from 'element-plus' -import { Plus } from '@element-plus/icons-vue' import Vant from 'vant' import { erFormPreview } from '@ER/formEditor' import previewData from './data/preview.json' @@ -20,9 +19,6 @@ const _mount = (template, data, otherObj) => mount( { attachTo: 'body', global: { - components: { - Plus - }, plugins: [ ElementPlus, Vant diff --git a/test/logic/generateFilterdata/Subform.test.js b/test/logic/generateFilterdata/Subform.test.js new file mode 100644 index 0000000..1f2e086 --- /dev/null +++ b/test/logic/generateFilterdata/Subform.test.js @@ -0,0 +1,14 @@ +import { describe, assert, expect, test, beforeEach, beforeAll } from 'vitest' +import { generateIfFilterOptionsData } from '@ER/formEditor/components/Panels/Config/components/generateFilterdata.js' +import _ from 'lodash-es' +import erGeneratorData from '@ER/formEditor/generatorData.js' +import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js' +describe('Generate filter data: Subform', () => { + let testData = {} + beforeAll(() => { + testData = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), false, 'en') + }) + test('default', () => { + expect(generateIfFilterOptionsData('', [testData])).toMatchSnapshot() + }) +}) diff --git a/test/logic/generateFilterdata/__snapshots__/Subform.test.js.snap b/test/logic/generateFilterdata/__snapshots__/Subform.test.js.snap new file mode 100644 index 0000000..70b10e5 --- /dev/null +++ b/test/logic/generateFilterdata/__snapshots__/Subform.test.js.snap @@ -0,0 +1,60 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Generate filter data: Subform > default 1`] = ` +{ + "operators": { + "Text": [ + { + "en_label": "Equal", + "label": "等于", + "style": "noop", + "value": "equal", + }, + { + "en_label": "Not equal", + "label": "不等于", + "style": "noop", + "value": "not_equal", + }, + { + "en_label": "Contains", + "label": "包含", + "style": "tags", + "value": "contains", + }, + { + "en_label": "Not contain", + "label": "不包含", + "style": "tags", + "value": "not_contain", + }, + { + "en_label": "Empty", + "label": "为空", + "style": "none", + "value": "empty", + }, + { + "en_label": "Not empty", + "label": "不为空", + "style": "none", + "value": "not_empty", + }, + ], + }, + "options": [ + { + "includeOperator": { + "operator": [ + "empty", + "not_empty", + ], + }, + "label": "Sub-form", + "operatorKey": "Text", + "renderType": "NONE", + "value": "{{test-id-nanoid}}", + }, + ], +} +`; diff --git a/test/logic/validator/Subform.test.js b/test/logic/validator/Subform.test.js new file mode 100644 index 0000000..c8d7478 --- /dev/null +++ b/test/logic/validator/Subform.test.js @@ -0,0 +1,69 @@ +import { describe, assert, expect, test, beforeAll } from 'vitest' +import { validator } from '@ER/hooks/use-logic' +import _ from 'lodash-es' +import erGeneratorData from '@ER/formEditor/generatorData.js' +import * as erComponentsConfig from '@ER/formEditor/componentsConfig.js' +describe('Subform', () => { + let field = {} + beforeAll(() => { + field = erGeneratorData(_.cloneDeep(erComponentsConfig.fieldsConfig[2].list[5]), false, 'en') + }) + test('empty', () => { + expect( + validator({ + operator: 'empty' + }, + '', + field)).toBeTruthy() + expect( + validator({ + operator: 'empty' + }, + undefined, + field)).toBeTruthy() + expect( + validator({ + operator: 'empty' + }, + null, + field)).toBeTruthy() + expect( + validator({ + operator: 'empty' + }, + [], + field)).toBeTruthy() + expect( + validator({ + operator: 'empty' + }, + [{}], + field)).toBeFalsy() + }) + test('not_empty', () => { + expect( + validator({ + operator: 'not_empty' + }, + '', + field)).toBeFalsy() + expect( + validator({ + operator: 'not_empty' + }, + undefined, + field)).toBeFalsy() + expect( + validator({ + operator: 'not_empty' + }, + null, + field)).toBeFalsy() + expect( + validator({ + operator: 'not_empty' + }, + [{}], + field)).toBeTruthy() + }) +}) diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 0000000..a985b05 --- /dev/null +++ b/test/utils.js @@ -0,0 +1,38 @@ +import { mount } from '@vue/test-utils' +import { erFormEditor, erFormPreview, erFormConfig, utils } from '@ER/formEditor/index.js' +import ElementPlus from 'element-plus' +import Vant from 'vant' +import { generateData } from '@ER/utils/generateOptions.js' + +export const _mount = (template, data, otherObj) => mount( + { + components: { + erFormEditor, + erFormPreview, + erFormConfig + }, + template, + data, + ...otherObj + }, + { + attachTo: 'body', + global: { + plugins: [ + ElementPlus, + Vant + ] + } + } +) +export const wrapLayoutDataByLayoutType = (layout, fields = [], layoutType = 1) => { + const result = utils.generateData(layoutType) + result.fields = fields + if (layoutType === 1) { + result.list = layout + } + if (layoutType === 2) { + result.layout = layout + } + return result +}