feat(components): ✨add tabbar component
This commit is contained in:
parent
32ccceb338
commit
7764fcafc9
18
README.EN.md
18
README.EN.md
|
@ -8,8 +8,6 @@
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone --single-branch https://github.com/buqiyuan/vite-vue3-lowcode.git
|
git clone --single-branch https://github.com/buqiyuan/vite-vue3-lowcode.git
|
||||||
# or
|
|
||||||
git clone --single-branch https://gitee.com/buqiyuan/vite-vue3-lowcode.git
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## technology stack
|
## technology stack
|
||||||
|
@ -67,6 +65,22 @@ JSON.stringify(
|
||||||
).replaceAll('"', '')
|
).replaceAll('"', '')
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 在vant文档中 chrome控制台输入以下代码,快速生成组件事件
|
||||||
|
JSON.stringify(
|
||||||
|
$$('#events + table tbody tr').reduce((prev, curr) => {
|
||||||
|
const children = curr.children
|
||||||
|
const event = {
|
||||||
|
label: children[1].textContent,
|
||||||
|
value: children[0].textContent
|
||||||
|
}
|
||||||
|
return prev.concat([event])
|
||||||
|
}, [])
|
||||||
|
)
|
||||||
|
.replaceAll(/(?<!:)\"(?!,|})/g, '')
|
||||||
|
.replace(/\"/g, "'")
|
||||||
|
```
|
||||||
|
|
||||||
## Browser support
|
## Browser support
|
||||||
|
|
||||||
The `Chrome 80+` browser is recommended for local development
|
The `Chrome 80+` browser is recommended for local development
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone --single-branch https://github.com/buqiyuan/vite-vue3-lowcode.git
|
git clone --single-branch https://github.com/buqiyuan/vite-vue3-lowcode.git
|
||||||
# or
|
|
||||||
git clone --single-branch https://gitee.com/buqiyuan/vite-vue3-lowcode.git
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vant/touch-emulator": "^1.3.1",
|
"@vant/touch-emulator": "^1.3.2",
|
||||||
"@vueuse/core": "^5.1.3",
|
"@vueuse/core": "^5.1.3",
|
||||||
"@vueuse/integrations": "^5.1.3",
|
"@vueuse/integrations": "^5.1.3",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
"nprogress": "^1.0.0-1",
|
"nprogress": "^1.0.0-1",
|
||||||
"qrcode": "^1.4.4",
|
"qrcode": "^1.4.4",
|
||||||
"qs": "^6.10.1",
|
"qs": "^6.10.1",
|
||||||
"vant": "3.1.2",
|
"vant": "3.1.3",
|
||||||
"vue": "3.1.4",
|
"vue": "3.1.4",
|
||||||
"vue-router": "^4.0.10",
|
"vue-router": "^4.0.10",
|
||||||
"vuedraggable": "^4.0.3",
|
"vuedraggable": "^4.0.3",
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^12.1.4",
|
"@commitlint/cli": "^12.1.4",
|
||||||
"@commitlint/config-conventional": "^12.1.4",
|
"@commitlint/config-conventional": "^12.1.4",
|
||||||
"@types/node": "^16.0.0",
|
"@types/node": "^16.3.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||||
"@typescript-eslint/parser": "^4.28.2",
|
"@typescript-eslint/parser": "^4.28.2",
|
||||||
"@vitejs/plugin-legacy": "^1.4.3",
|
"@vitejs/plugin-legacy": "^1.4.3",
|
||||||
|
@ -64,7 +64,7 @@
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"pretty-quick": "^3.1.1",
|
"pretty-quick": "^3.1.1",
|
||||||
"sass": "1.35.1",
|
"sass": "1.35.2",
|
||||||
"stylelint": "^13.13.1",
|
"stylelint": "^13.13.1",
|
||||||
"stylelint-config-prettier": "^8.0.2",
|
"stylelint-config-prettier": "^8.0.2",
|
||||||
"stylelint-config-standard": "^22.0.0",
|
"stylelint-config-standard": "^22.0.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!--
|
<!--
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-01 09:45:21
|
* @Date: 2021-06-01 09:45:21
|
||||||
* @LastEditTime: 2021-07-04 17:21:47
|
* @LastEditTime: 2021-07-12 10:22:26
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description:
|
* @Description:
|
||||||
* @FilePath: \vite-vue3-lowcode\preview\views\preview.vue
|
* @FilePath: \vite-vue3-lowcode\preview\views\preview.vue
|
||||||
|
@ -57,14 +57,16 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const { bgImage, bgColor } = currentPage.config
|
if (currentPage?.config) {
|
||||||
const bodyStyleStr = `
|
const { bgImage, bgColor } = currentPage.config
|
||||||
|
const bodyStyleStr = `
|
||||||
body {
|
body {
|
||||||
background-color: ${bgColor};
|
background-color: ${bgColor};
|
||||||
background-image: url(${bgImage});
|
background-image: url(${bgImage});
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
document.styleSheets[0].insertRule(bodyStyleStr)
|
document.styleSheets[0].insertRule(bodyStyleStr)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
createEditorSelectProp,
|
createEditorSelectProp,
|
||||||
createEditorSwitchProp
|
createEditorSwitchProp
|
||||||
} from '@/visual-editor/visual-editor.props'
|
} from '@/visual-editor/visual-editor.props'
|
||||||
import { getCurrentInstance, reactive } from 'vue'
|
import { useAttrs, reactive } from 'vue'
|
||||||
import { isDate } from '@/visual-editor/utils/is'
|
import { isDate } from '@/visual-editor/utils/is'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ export default {
|
||||||
render: ({ styles, block, props }) => {
|
render: ({ styles, block, props }) => {
|
||||||
const { registerRef } = useGlobalProperties()
|
const { registerRef } = useGlobalProperties()
|
||||||
|
|
||||||
const { attrs } = getCurrentInstance()!
|
const attrs = useAttrs()
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
showPicker: false,
|
showPicker: false,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-05-04 05:36:58
|
* @Date: 2021-05-04 05:36:58
|
||||||
* @LastEditTime: 2021-07-07 10:56:56
|
* @LastEditTime: 2021-07-11 16:36:05
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 导航栏
|
* @Description: 导航栏
|
||||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\nav-bar\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\nav-bar\index.tsx
|
||||||
|
@ -19,23 +19,37 @@ export default {
|
||||||
preview: () => (
|
preview: () => (
|
||||||
<NavBar title="标题" left-text="返回" right-text="按钮" left-arrow style={{ width: '100%' }} />
|
<NavBar title="标题" left-text="返回" right-text="按钮" left-arrow style={{ width: '100%' }} />
|
||||||
),
|
),
|
||||||
render: ({ props, styles, block, custom }) => {
|
render: ({ props, block }) => {
|
||||||
const { registerRef } = useGlobalProperties()
|
const { registerRef } = useGlobalProperties()
|
||||||
|
|
||||||
return (
|
setTimeout(() => {
|
||||||
<div style={styles}>
|
const compEl = window.$$refs[block._vid]?.$el
|
||||||
<NavBar ref={(el) => registerRef(el, block._vid)} placeholder {...custom} {...props} />
|
const draggableEl = compEl?.closest('div[data-draggable]')
|
||||||
</div>
|
const navbarEl = draggableEl?.querySelector('.van-nav-bar--fixed') as HTMLDivElement
|
||||||
)
|
if (draggableEl && navbarEl) {
|
||||||
|
navbarEl.style.position = 'unset'
|
||||||
|
draggableEl.style.top = '0'
|
||||||
|
draggableEl.style.left = '0'
|
||||||
|
draggableEl.style.width = '100%'
|
||||||
|
} else {
|
||||||
|
const slotEl = compEl?.closest('__slot-item')
|
||||||
|
if (slotEl) {
|
||||||
|
slotEl.style.position = 'fixed'
|
||||||
|
slotEl.style.bottom = '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <NavBar ref={(el) => registerRef(el, block._vid)} placeholder {...props} />
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
title: createEditorInputProp({ label: '标题', defaultValue: '标题' }),
|
title: createEditorInputProp({ label: '标题', defaultValue: '标题' }),
|
||||||
fixed: createEditorSwitchProp({ label: '是否固定', defaultValue: true }),
|
fixed: createEditorSwitchProp({ label: '是否固定', defaultValue: true }),
|
||||||
placeholder: createEditorSwitchProp({
|
// placeholder: createEditorSwitchProp({
|
||||||
label: '是否生成占位元素',
|
// label: '是否生成占位元素',
|
||||||
defaultValue: true,
|
// defaultValue: true,
|
||||||
tips: '固定在顶部时,是否在标签位置生成一个等高的占位元素'
|
// tips: '固定在顶部时,是否在标签位置生成一个等高的占位元素'
|
||||||
}),
|
// }),
|
||||||
zIndex: createEditorInputProp({ label: 'z-index' }),
|
zIndex: createEditorInputProp({ label: 'z-index' }),
|
||||||
border: createEditorSwitchProp({ label: '是否显示下边框', defaultValue: false }),
|
border: createEditorSwitchProp({ label: '是否显示下边框', defaultValue: false }),
|
||||||
leftText: createEditorInputProp({ label: '左侧文案', defaultValue: '返回' }),
|
leftText: createEditorInputProp({ label: '左侧文案', defaultValue: '返回' }),
|
||||||
|
@ -46,6 +60,8 @@ export default {
|
||||||
{ label: '点击左侧按钮时触发', value: 'click-left' },
|
{ label: '点击左侧按钮时触发', value: 'click-left' },
|
||||||
{ label: '点击右侧按钮时触发', value: 'click-right' }
|
{ label: '点击右侧按钮时触发', value: 'click-right' }
|
||||||
],
|
],
|
||||||
|
showStyleConfig: false,
|
||||||
|
draggable: false,
|
||||||
resize: {
|
resize: {
|
||||||
width: true
|
width: true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-01 09:45:21
|
* @Date: 2021-06-01 09:45:21
|
||||||
* @LastEditTime: 2021-07-07 10:57:41
|
* @LastEditTime: 2021-07-08 15:15:52
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 表单项类型 - 选择器
|
* @Description: 表单项类型 - 选择器
|
||||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\picker\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\picker\index.tsx
|
||||||
|
@ -15,7 +15,7 @@ import {
|
||||||
createEditorInputProp,
|
createEditorInputProp,
|
||||||
createEditorModelBindProp
|
createEditorModelBindProp
|
||||||
} from '@/visual-editor/visual-editor.props'
|
} from '@/visual-editor/visual-editor.props'
|
||||||
import { reactive, getCurrentInstance } from 'vue'
|
import { reactive, useAttrs } from 'vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
key: 'picker',
|
key: 'picker',
|
||||||
|
@ -25,7 +25,7 @@ export default {
|
||||||
render: ({ styles, block, props }) => {
|
render: ({ styles, block, props }) => {
|
||||||
const { registerRef } = useGlobalProperties()
|
const { registerRef } = useGlobalProperties()
|
||||||
|
|
||||||
const { attrs } = getCurrentInstance()!
|
const attrs = useAttrs()
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
showPicker: false,
|
showPicker: false,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-14 12:24:12
|
* @Date: 2021-06-14 12:24:12
|
||||||
* @LastEditTime: 2021-07-07 11:01:14
|
* @LastEditTime: 2021-07-11 16:43:31
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 轮播图组件
|
* @Description: 轮播图组件
|
||||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\swipe\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\swipe\index.tsx
|
||||||
|
@ -29,11 +29,11 @@ export default {
|
||||||
<SwipeItem style={swipeItemStyle}>4</SwipeItem>
|
<SwipeItem style={swipeItemStyle}>4</SwipeItem>
|
||||||
</Swipe>
|
</Swipe>
|
||||||
),
|
),
|
||||||
render: ({ block, props, styles }) => {
|
render: ({ block, props }) => {
|
||||||
const { registerRef } = useGlobalProperties()
|
const { registerRef } = useGlobalProperties()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles}>
|
<div>
|
||||||
<Swipe
|
<Swipe
|
||||||
ref={(el) => registerRef(el, block._vid)}
|
ref={(el) => registerRef(el, block._vid)}
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -52,6 +52,7 @@ export default {
|
||||||
},
|
},
|
||||||
props: createFieldProps(),
|
props: createFieldProps(),
|
||||||
events: [{ label: '每一页轮播结束后触发', value: 'change' }],
|
events: [{ label: '每一页轮播结束后触发', value: 'change' }],
|
||||||
|
showStyleConfig: false,
|
||||||
resize: {
|
resize: {
|
||||||
width: true
|
width: true
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
/*
|
||||||
|
* @Author: 卜启缘
|
||||||
|
* @Date: 2021-05-04 05:36:58
|
||||||
|
* @LastEditTime: 2021-07-11 22:38:54
|
||||||
|
* @LastEditors: 卜启缘
|
||||||
|
* @Description: 导航栏
|
||||||
|
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\tabbar\index.tsx
|
||||||
|
*/
|
||||||
|
import { Tabbar, TabbarItem } from 'vant'
|
||||||
|
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||||
|
import {
|
||||||
|
createEditorCrossSortableProp,
|
||||||
|
createEditorInputProp,
|
||||||
|
createEditorSwitchProp,
|
||||||
|
createEditorColorProp
|
||||||
|
} from '@/visual-editor/visual-editor.props'
|
||||||
|
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
|
||||||
|
import tabbarItem from './tabbar-item'
|
||||||
|
import { createNewBlock } from '@/visual-editor/visual-editor.utils'
|
||||||
|
import { BASE_URL } from '@/visual-editor/utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
key: 'tabbar',
|
||||||
|
moduleName: 'baseWidgets',
|
||||||
|
label: '底部标签栏',
|
||||||
|
preview: () => (
|
||||||
|
<Tabbar>
|
||||||
|
<TabbarItem icon="home-o">首页</TabbarItem>
|
||||||
|
<TabbarItem icon="apps-o">导航</TabbarItem>
|
||||||
|
<TabbarItem icon="user-o">我的</TabbarItem>
|
||||||
|
</Tabbar>
|
||||||
|
),
|
||||||
|
render: ({ props, block }) => {
|
||||||
|
const { registerRef } = useGlobalProperties()
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const compEl = window.$$refs[block._vid]?.$el
|
||||||
|
const draggableEl = compEl?.closest('div[data-draggable]')
|
||||||
|
const tabbarEl = draggableEl?.querySelector('.van-tabbar') as HTMLDivElement
|
||||||
|
if (draggableEl && tabbarEl) {
|
||||||
|
tabbarEl.style.position = 'unset'
|
||||||
|
draggableEl.style.position = 'fixed'
|
||||||
|
draggableEl.style.bottom = '0'
|
||||||
|
draggableEl.style.left = '0'
|
||||||
|
draggableEl.style.width = '100%'
|
||||||
|
draggableEl.style.zIndex = '1000'
|
||||||
|
} else {
|
||||||
|
document.body.style.paddingBottom = '50px'
|
||||||
|
const slotEl = compEl?.closest('__slot-item')
|
||||||
|
if (slotEl) {
|
||||||
|
slotEl.style.position = 'fixed'
|
||||||
|
slotEl.style.bottom = '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabbar ref={(el) => registerRef(el, block._vid)} v-model={props.modelValue} {...props}>
|
||||||
|
{props.tabs?.map((item) => {
|
||||||
|
const itemProps = item.block?.props
|
||||||
|
const url = `${BASE_URL}${props.baseUrl}${itemProps.url}`.replace(/\/{2,}/g, '/')
|
||||||
|
return (
|
||||||
|
<TabbarItem name={item.value} key={item.value} {...itemProps} url={url}>
|
||||||
|
{item.label}
|
||||||
|
</TabbarItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Tabbar>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: createEditorInputProp({
|
||||||
|
label: '当前选中标签的名称或索引值',
|
||||||
|
defaultValue: ''
|
||||||
|
}),
|
||||||
|
tabs: createEditorCrossSortableProp({
|
||||||
|
label: '默认选项',
|
||||||
|
labelPosition: 'top',
|
||||||
|
multiple: false,
|
||||||
|
showItemPropsConfig: true,
|
||||||
|
defaultValue: [
|
||||||
|
{ label: '首页', value: 'index', component: tabbarItem, block: createNewBlock(tabbarItem) },
|
||||||
|
{
|
||||||
|
label: '导航',
|
||||||
|
value: 'navigation',
|
||||||
|
component: tabbarItem,
|
||||||
|
block: createNewBlock(tabbarItem)
|
||||||
|
},
|
||||||
|
{ label: '我的', value: 'user', component: tabbarItem, block: createNewBlock(tabbarItem) }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
fixed: createEditorSwitchProp({ label: '是否固定在底部', defaultValue: true }),
|
||||||
|
border: createEditorSwitchProp({ label: '是否显示外边框', defaultValue: true }),
|
||||||
|
zIndex: createEditorInputProp({ label: '元素 z-index', defaultValue: '1' }),
|
||||||
|
baseUrl: createEditorInputProp({ label: '路由路径前缀', defaultValue: '/preview/#/' }),
|
||||||
|
activeColor: createEditorColorProp({ label: '选中标签的颜色', defaultValue: '#1989fa' }),
|
||||||
|
inactiveColor: createEditorColorProp({ label: '未选中标签的颜色', defaultValue: '#7d7e80' }),
|
||||||
|
route: createEditorSwitchProp({ label: '是否开启路由模式', defaultValue: false }),
|
||||||
|
// placeholder: createEditorSwitchProp({
|
||||||
|
// label: '固定在底部时,是否在标签位置生成一个等高的占位元素',
|
||||||
|
// defaultValue: true
|
||||||
|
// }),
|
||||||
|
safeAreaInsetBottom: createEditorSwitchProp({
|
||||||
|
label: '是否开启底部安全区适配,设置 fixed 时默认开启',
|
||||||
|
defaultValue: false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
events: [
|
||||||
|
{ label: '点击左侧按钮时触发', value: 'click-left' },
|
||||||
|
{ label: '点击右侧按钮时触发', value: 'click-right' }
|
||||||
|
],
|
||||||
|
draggable: false,
|
||||||
|
resize: {
|
||||||
|
width: true
|
||||||
|
}
|
||||||
|
} as VisualEditorComponent
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* @Author: 卜启缘
|
||||||
|
* @Date: 2021-05-04 05:36:58
|
||||||
|
* @LastEditTime: 2021-07-11 19:58:14
|
||||||
|
* @LastEditors: 卜启缘
|
||||||
|
* @Description: 导航栏项
|
||||||
|
* @FilePath: \vite-vue3-lowcode\src\packages\container-component\tabbar\tabbar-item.tsx
|
||||||
|
*/
|
||||||
|
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||||
|
import { createEditorInputProp, createEditorSwitchProp } from '@/visual-editor/visual-editor.props'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
key: 'tabbar-item',
|
||||||
|
moduleName: 'baseWidgets',
|
||||||
|
label: '底部标签栏',
|
||||||
|
preview: () => <></>,
|
||||||
|
render: () => <></>,
|
||||||
|
props: {
|
||||||
|
// name: createEditorInputProp({
|
||||||
|
// label: '标签名称,作为匹配的标识符',
|
||||||
|
// defaultValue: '当前标签的索引值'
|
||||||
|
// }),
|
||||||
|
icon: createEditorInputProp({ label: '图标名称或图片链接', defaultValue: 'home-o' }),
|
||||||
|
iconPrefix: createEditorInputProp({
|
||||||
|
label: '图标类名前缀',
|
||||||
|
tips: '图标类名前缀,同 Icon 组件的 class-prefix 属性',
|
||||||
|
defaultValue: 'van-icon'
|
||||||
|
}),
|
||||||
|
dot: createEditorSwitchProp({ label: '是否显示图标右上角小红点', defaultValue: false }),
|
||||||
|
badge: createEditorInputProp({ label: '图标右上角徽标的内容', defaultValue: '' }),
|
||||||
|
url: createEditorInputProp({ label: '点击后跳转的链接地址', defaultValue: '' }),
|
||||||
|
// to: createEditorInputProp({
|
||||||
|
// label: '点击后跳转的目标路由对象',
|
||||||
|
// tips: '点击后跳转的目标路由对象,同 vue-router 的 to 属性',
|
||||||
|
// defaultValue: ''
|
||||||
|
// }),
|
||||||
|
replace: createEditorSwitchProp({ label: '是否在跳转时替换当前页面历史', defaultValue: false })
|
||||||
|
},
|
||||||
|
events: [
|
||||||
|
{ label: '点击左侧按钮时触发', value: 'click-left' },
|
||||||
|
{ label: '点击右侧按钮时触发', value: 'click-right' }
|
||||||
|
],
|
||||||
|
draggable: false,
|
||||||
|
resize: {
|
||||||
|
width: true
|
||||||
|
}
|
||||||
|
} as VisualEditorComponent
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-01 09:45:21
|
* @Date: 2021-06-01 09:45:21
|
||||||
* @LastEditTime: 2021-07-07 21:23:23
|
* @LastEditTime: 2021-07-08 15:13:02
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description:
|
* @Description:
|
||||||
* @FilePath: \vite-vue3-lowcode\src\packages\container-component\form\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\packages\container-component\form\index.tsx
|
||||||
*/
|
*/
|
||||||
import { Form, Field, Button } from 'vant'
|
import { Form, Field, Button } from 'vant'
|
||||||
import { renderSlot, getCurrentInstance } from 'vue'
|
import { renderSlot, useSlots } from 'vue'
|
||||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
|
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
|
||||||
import { compProps } from './compProps'
|
import { compProps } from './compProps'
|
||||||
|
@ -28,7 +28,7 @@ export default {
|
||||||
</Form>
|
</Form>
|
||||||
),
|
),
|
||||||
render: function ({ props, styles, block }) {
|
render: function ({ props, styles, block }) {
|
||||||
const { slots } = getCurrentInstance()!
|
const slots = useSlots()
|
||||||
const { registerRef } = useGlobalProperties()
|
const { registerRef } = useGlobalProperties()
|
||||||
|
|
||||||
const onSubmit = (values) => {
|
const onSubmit = (values) => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Col, Row } from 'vant'
|
import { Col, Row } from 'vant'
|
||||||
import { renderSlot, getCurrentInstance } from 'vue'
|
import { renderSlot, useSlots } from 'vue'
|
||||||
import { createEditorInputProp, createEditorSelectProp } from '@/visual-editor/visual-editor.props'
|
import { createEditorInputProp, createEditorSelectProp } from '@/visual-editor/visual-editor.props'
|
||||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||||
import styleModule from './index.module.scss'
|
import styleModule from './index.module.scss'
|
||||||
|
@ -36,8 +36,8 @@ export default {
|
||||||
<Col span="8">span: 8</Col>
|
<Col span="8">span: 8</Col>
|
||||||
</Row>
|
</Row>
|
||||||
),
|
),
|
||||||
render: function ({ props, styles, block, custom }) {
|
render: ({ props, styles, block, custom }) => {
|
||||||
const { slots } = getCurrentInstance()!
|
const slots = useSlots()
|
||||||
const { registerRef } = useGlobalProperties()
|
const { registerRef } = useGlobalProperties()
|
||||||
|
|
||||||
slotsTemp[block._vid] ??= {}
|
slotsTemp[block._vid] ??= {}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
type RequestIdleCallbackHandle = any
|
|
||||||
type RequestIdleCallbackOptions = {
|
|
||||||
timeout: number
|
|
||||||
}
|
|
||||||
type RequestIdleCallbackDeadline = {
|
|
||||||
readonly didTimeout: boolean
|
|
||||||
timeRemaining: () => number
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface Window {
|
|
||||||
$$refs: any
|
|
||||||
requestIdleCallback: (
|
|
||||||
callback: (deadline: RequestIdleCallbackDeadline) => void,
|
|
||||||
opts?: RequestIdleCallbackOptions
|
|
||||||
) => RequestIdleCallbackHandle
|
|
||||||
cancelIdleCallback: (handle: RequestIdleCallbackHandle) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
// declare module '*.vue' {
|
|
||||||
// import { DefineComponent } from 'vue'
|
|
||||||
//
|
|
||||||
// const component: DefineComponent<{}, {}, any>
|
|
||||||
// export default component
|
|
||||||
// }
|
|
||||||
|
|
||||||
// declare module '*.module.scss'
|
|
||||||
|
|
||||||
declare module '*.vue' {
|
|
||||||
import { ComponentOptions } from 'vue'
|
|
||||||
const component: ComponentOptions
|
|
||||||
export default component
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@
|
||||||
<el-dialog v-model="dialogVisible" custom-class="h5-preview" :show-close="false" width="360px">
|
<el-dialog v-model="dialogVisible" custom-class="h5-preview" :show-close="false" width="360px">
|
||||||
<iframe
|
<iframe
|
||||||
v-if="dialogVisible"
|
v-if="dialogVisible"
|
||||||
:style="{ width: '360px', height: '640px' }"
|
:style="{ width: '100%', height: '100%' }"
|
||||||
:src="previewUrl"
|
:src="previewUrl"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
scrolling="auto"
|
scrolling="auto"
|
||||||
|
@ -48,6 +48,8 @@ export default defineComponent({
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.el-dialog__body {
|
.el-dialog__body {
|
||||||
|
width: 360px;
|
||||||
|
height: 640px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
border: solid 3px #ebeef5;
|
border: solid 3px #ebeef5;
|
||||||
|
transform: translate(0);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-01 13:22:14
|
* @Date: 2021-06-01 13:22:14
|
||||||
* @LastEditTime: 2021-07-06 20:32:39
|
* @LastEditTime: 2021-07-11 11:05:06
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 基础组件
|
* @Description: 基础组件
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\base-widgets\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\base-widgets\index.tsx
|
||||||
|
@ -28,7 +28,6 @@ export default defineComponent({
|
||||||
const cloneDog = (comp) => {
|
const cloneDog = (comp) => {
|
||||||
console.log('当前拖拽的组件:', comp)
|
console.log('当前拖拽的组件:', comp)
|
||||||
const newComp = cloneDeep(comp)
|
const newComp = cloneDeep(comp)
|
||||||
newComp._vid = Date.now()
|
|
||||||
return createNewBlock(newComp)
|
return createNewBlock(newComp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,45 @@
|
||||||
.list-group {
|
|
||||||
|
|
||||||
}
|
|
||||||
.list-group-item {
|
.list-group-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 20px);
|
||||||
|
min-height: 120px;
|
||||||
|
padding: 0 5px;
|
||||||
|
margin-top: 20px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
border: solid 3px #ebeef5;
|
border: solid 3px #ebeef5;
|
||||||
margin-top: 20px;
|
transform: translate(0);
|
||||||
min-height: 120px;
|
box-sizing: border-box;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0px 5px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #409EFF;
|
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
border-color: #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: attr(data-label);
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -3px;
|
top: -3px;
|
||||||
left: -3px;
|
left: -3px;
|
||||||
background-color: #409EFF;
|
z-index: 1;
|
||||||
color: white;
|
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
z-index: 1;
|
color: white;
|
||||||
|
background-color: #409eff;
|
||||||
|
content: attr(data-label);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
content: '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-01 13:22:14
|
* @Date: 2021-06-01 13:22:14
|
||||||
* @LastEditTime: 2021-07-04 21:36:46
|
* @LastEditTime: 2021-07-11 11:04:06
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description:
|
* @Description:
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\container-component\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\container-component\index.tsx
|
||||||
|
@ -27,7 +27,6 @@ export default defineComponent({
|
||||||
const cloneDog = (comp) => {
|
const cloneDog = (comp) => {
|
||||||
console.log('当前拖拽的组件:', comp)
|
console.log('当前拖拽的组件:', comp)
|
||||||
const newComp = cloneDeep(comp)
|
const newComp = cloneDeep(comp)
|
||||||
newComp._vid = Date.now()
|
|
||||||
return createNewBlock(newComp)
|
return createNewBlock(newComp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!--
|
<!--
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-24 18:36:03
|
* @Date: 2021-06-24 18:36:03
|
||||||
* @LastEditTime: 2021-07-07 21:48:03
|
* @LastEditTime: 2021-07-09 20:00:22
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 接口请求
|
* @Description: 接口请求
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\data-source\data-fetch.vue
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\data-source\data-fetch.vue
|
||||||
|
@ -167,6 +167,8 @@ const showModelMoal = () => {
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="请求数据" prop={'data.bind'}>
|
<ElFormItem label="请求数据" prop={'data.bind'}>
|
||||||
<ElCascader
|
<ElCascader
|
||||||
|
v-model={state.ruleForm.data.bind}
|
||||||
|
options={models.value}
|
||||||
clearable={true}
|
clearable={true}
|
||||||
props={{
|
props={{
|
||||||
checkStrictly: true,
|
checkStrictly: true,
|
||||||
|
@ -177,8 +179,6 @@ const showModelMoal = () => {
|
||||||
}}
|
}}
|
||||||
placeholder="请选择绑定的请求数据"
|
placeholder="请选择绑定的请求数据"
|
||||||
onChange={handleBindChange}
|
onChange={handleBindChange}
|
||||||
v-model={state.ruleForm.data.bind}
|
|
||||||
options={models.value}
|
|
||||||
></ElCascader>
|
></ElCascader>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="响应数据" prop={'data.recv'}>
|
<ElFormItem label="响应数据" prop={'data.recv'}>
|
||||||
|
|
|
@ -1,238 +0,0 @@
|
||||||
/*
|
|
||||||
* @Author: 卜启缘
|
|
||||||
* @Date: 2021-06-10 16:23:06
|
|
||||||
* @LastEditTime: 2021-07-07 19:36:45
|
|
||||||
* @LastEditors: 卜启缘
|
|
||||||
* @Description: 组件属性编辑器
|
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\AttrEditor.tsx
|
|
||||||
*/
|
|
||||||
import { defineComponent, computed, watch } from 'vue'
|
|
||||||
import {
|
|
||||||
ElColorPicker,
|
|
||||||
ElForm,
|
|
||||||
ElFormItem,
|
|
||||||
ElInput,
|
|
||||||
ElOption,
|
|
||||||
ElSelect,
|
|
||||||
ElSwitch,
|
|
||||||
ElPopover,
|
|
||||||
ElCascader,
|
|
||||||
ElInputNumber,
|
|
||||||
ElRadioGroup,
|
|
||||||
ElRadioButton
|
|
||||||
} from 'element-plus'
|
|
||||||
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props'
|
|
||||||
import { TablePropEditor, CrossSortableOptionsEditor } from './components'
|
|
||||||
import { useDotProp } from '@/visual-editor/hooks/useDotProp'
|
|
||||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
|
||||||
import { cloneDeep } from 'lodash'
|
|
||||||
import { FormatInputNumber } from '@/visual-editor/components/common/format-input-number'
|
|
||||||
|
|
||||||
export const AttrEditor = defineComponent({
|
|
||||||
setup() {
|
|
||||||
const { visualConfig, currentBlock, jsonData } = useVisualData()
|
|
||||||
/**
|
|
||||||
* @description 模型集合
|
|
||||||
*/
|
|
||||||
const models = computed(() => cloneDeep(jsonData.models))
|
|
||||||
|
|
||||||
const compPaddingAttrs = ['paddingTop', 'paddingLeft', 'paddingRight', 'paddingBottom']
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 监听组件padding值的变化
|
|
||||||
*/
|
|
||||||
watch(
|
|
||||||
compPaddingAttrs.map((item) => () => currentBlock.value.styles?.[item]),
|
|
||||||
(val: string[]) => {
|
|
||||||
const isSame = val.every((item) => currentBlock.value.styles?.tempPadding == item)
|
|
||||||
if (isSame || new Set(val).size === 1) {
|
|
||||||
if (Reflect.has(currentBlock.value, 'styles')) {
|
|
||||||
currentBlock.value.styles.tempPadding = val[0]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentBlock.value.styles.tempPadding = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 总的组件padding变化时进行的操作
|
|
||||||
*/
|
|
||||||
const compPadding = computed({
|
|
||||||
get: () => currentBlock.value.styles?.tempPadding,
|
|
||||||
set(val) {
|
|
||||||
compPaddingAttrs.forEach((item) => (currentBlock.value.styles[item] = val))
|
|
||||||
currentBlock.value.styles.tempPadding = val
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const renderEditor = (propName: string, propConfig: VisualEditorProps) => {
|
|
||||||
const { propObj, prop } = useDotProp(currentBlock.value.props, propName)
|
|
||||||
|
|
||||||
propObj[prop] ??= propConfig.defaultValue
|
|
||||||
|
|
||||||
return {
|
|
||||||
[VisualEditorPropsType.input]: () => {
|
|
||||||
if (!Object.is(propObj[prop], undefined) && !Object.is(propObj[prop], null)) {
|
|
||||||
propObj[prop] = `${propObj[prop]}`
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ElInput v-model={propObj[prop]} placeholder={propConfig.tips || propConfig.label} />
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[VisualEditorPropsType.inputNumber]: () => <ElInputNumber v-model={propObj[prop]} />,
|
|
||||||
[VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />,
|
|
||||||
[VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />,
|
|
||||||
[VisualEditorPropsType.crossSortable]: () => (
|
|
||||||
<CrossSortableOptionsEditor v-model={propObj[prop]} multiple={propConfig.multiple} />
|
|
||||||
),
|
|
||||||
[VisualEditorPropsType.select]: () => (
|
|
||||||
<ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}>
|
|
||||||
{propConfig.options?.map((opt) => (
|
|
||||||
<ElOption label={opt.label} style={{ fontFamily: opt.value }} value={opt.value} />
|
|
||||||
))}
|
|
||||||
</ElSelect>
|
|
||||||
),
|
|
||||||
[VisualEditorPropsType.table]: () => (
|
|
||||||
<TablePropEditor v-model={propObj[prop]} propConfig={propConfig} />
|
|
||||||
),
|
|
||||||
[VisualEditorPropsType.modelBind]: () => (
|
|
||||||
<ElCascader
|
|
||||||
clearable={true}
|
|
||||||
props={{
|
|
||||||
checkStrictly: true,
|
|
||||||
children: 'entitys',
|
|
||||||
label: 'name',
|
|
||||||
value: 'key',
|
|
||||||
expandTrigger: 'hover'
|
|
||||||
}}
|
|
||||||
placeholder="请选择绑定的请求数据"
|
|
||||||
v-model={propObj[prop]}
|
|
||||||
options={models.value}
|
|
||||||
></ElCascader>
|
|
||||||
)
|
|
||||||
}[propConfig.type]()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表单项
|
|
||||||
const FormEditor = () => {
|
|
||||||
const content: JSX.Element[] = []
|
|
||||||
if (currentBlock.value) {
|
|
||||||
const { componentKey } = currentBlock.value
|
|
||||||
const component = visualConfig.componentMap[componentKey]
|
|
||||||
console.log('props.block:', currentBlock.value)
|
|
||||||
content.push(
|
|
||||||
<>
|
|
||||||
<ElFormItem label="组件ID" labelWidth={'76px'}>
|
|
||||||
{currentBlock.value._vid}
|
|
||||||
<ElPopover
|
|
||||||
width={200}
|
|
||||||
trigger="hover"
|
|
||||||
content={`你可以利用该组件ID。对该组件进行获取和设置其属性,组件可用属性可在控制台输入:$$refs.${currentBlock.value._vid} 进行查看`}
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
reference: () => <i class={'el-icon-warning-outline ml-6px'}></i>
|
|
||||||
}}
|
|
||||||
</ElPopover>
|
|
||||||
</ElFormItem>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
if (!!component) {
|
|
||||||
if (!!component.props) {
|
|
||||||
content.push(
|
|
||||||
...Object.entries(component.props || {}).map(([propName, propConfig]) => (
|
|
||||||
<>
|
|
||||||
<ElFormItem
|
|
||||||
key={currentBlock.value._vid + propName}
|
|
||||||
style={
|
|
||||||
propConfig.labelPosition == 'top'
|
|
||||||
? {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-start'
|
|
||||||
}
|
|
||||||
: {}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
label: () => (
|
|
||||||
<>
|
|
||||||
{propConfig.tips && (
|
|
||||||
<ElPopover width={200} trigger={'hover'} content={propConfig.tips}>
|
|
||||||
{{
|
|
||||||
reference: () => <i class={'el-icon-warning-outline'}></i>
|
|
||||||
}}
|
|
||||||
</ElPopover>
|
|
||||||
)}
|
|
||||||
{propConfig.label}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
default: () => renderEditor(propName, propConfig)
|
|
||||||
}}
|
|
||||||
</ElFormItem>
|
|
||||||
</>
|
|
||||||
))
|
|
||||||
)
|
|
||||||
content.push(
|
|
||||||
<ElFormItem label={'组件对齐方式'} labelWidth={'90px'}>
|
|
||||||
<ElRadioGroup v-model={currentBlock.value.styles.justifyContent} size="mini">
|
|
||||||
<ElRadioButton label="flex-start"></ElRadioButton>
|
|
||||||
<ElRadioButton label="center"></ElRadioButton>
|
|
||||||
<ElRadioButton label="flex-end"></ElRadioButton>
|
|
||||||
</ElRadioGroup>
|
|
||||||
</ElFormItem>
|
|
||||||
)
|
|
||||||
content.push(
|
|
||||||
<>
|
|
||||||
<ElFormItem class={'flex flex-col justify-start'}>
|
|
||||||
{{
|
|
||||||
label: () => (
|
|
||||||
<div class={'flex justify-between mb-2'}>
|
|
||||||
<div>组件内边距</div>
|
|
||||||
<FormatInputNumber v-model={compPadding.value} class={'!w-100px'} />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
default: () => (
|
|
||||||
<div class={'grid grid-cols-3 gap-2 w-full bg-gray-100 p-20px items-center'}>
|
|
||||||
<FormatInputNumber
|
|
||||||
v-model={currentBlock.value.styles.paddingTop}
|
|
||||||
class={'!w-100px col-span-full col-start-2'}
|
|
||||||
/>
|
|
||||||
<FormatInputNumber
|
|
||||||
v-model={currentBlock.value.styles.paddingLeft}
|
|
||||||
class={'!w-100px col-span-1'}
|
|
||||||
/>
|
|
||||||
<div class={'bg-white col-span-1 h-40px'}></div>
|
|
||||||
<FormatInputNumber
|
|
||||||
v-model={currentBlock.value.styles.paddingRight}
|
|
||||||
class={'!w-100px col-span-1'}
|
|
||||||
/>
|
|
||||||
<FormatInputNumber
|
|
||||||
v-model={currentBlock.value.styles.paddingBottom}
|
|
||||||
class={'!w-100px col-span-full col-start-2'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</ElFormItem>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ElForm size="mini" labelPosition={'left'}>
|
|
||||||
{content}
|
|
||||||
</ElForm>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => (
|
|
||||||
<>
|
|
||||||
<FormEditor />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-14 15:00:45
|
* @Date: 2021-06-14 15:00:45
|
||||||
* @LastEditTime: 2021-07-03 10:00:59
|
* @LastEditTime: 2021-07-12 10:15:21
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 可以拖拽排序的选项列表
|
* @Description: 可以拖拽排序的选项列表
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\components\cross-sortable-options-editor\cross-sortable-options-editor.tsx
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\components\cross-sortable-options-editor\cross-sortable-options-editor.tsx
|
||||||
|
@ -9,18 +9,36 @@
|
||||||
|
|
||||||
import { defineComponent, reactive, computed, PropType } from 'vue'
|
import { defineComponent, reactive, computed, PropType } from 'vue'
|
||||||
import Draggable from 'vuedraggable'
|
import Draggable from 'vuedraggable'
|
||||||
import { ElInput, ElCheckboxGroup, ElCheckbox } from 'element-plus'
|
import {
|
||||||
|
ElInput,
|
||||||
|
ElCheckboxGroup,
|
||||||
|
ElCheckbox,
|
||||||
|
ElCollapse,
|
||||||
|
ElCollapseItem,
|
||||||
|
ElTabs,
|
||||||
|
ElTabPane,
|
||||||
|
ElForm
|
||||||
|
} from 'element-plus'
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import { isObject } from '@/visual-editor/utils/is'
|
import { isObject } from '@/visual-editor/utils/is'
|
||||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||||
|
import { PropConfig } from '../prop-config'
|
||||||
|
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
|
||||||
|
interface OptionItem extends LabelValue {
|
||||||
|
component?: VisualEditorComponent
|
||||||
|
block?: VisualEditorBlockData
|
||||||
|
}
|
||||||
|
|
||||||
export const CrossSortableOptionsEditor = defineComponent({
|
export const CrossSortableOptionsEditor = defineComponent({
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array as PropType<(string | LabelValue)[]>,
|
type: Array as PropType<(string | OptionItem)[]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
multiple: Boolean // 是否多选
|
multiple: Boolean, // 是否多选
|
||||||
|
showItemPropsConfig: Boolean // 是否多选
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const { currentBlock } = useVisualData()
|
const { currentBlock } = useVisualData()
|
||||||
|
@ -58,80 +76,98 @@ export const CrossSortableOptionsEditor = defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const incrementOption = (index) => {
|
const incrementOption = (index) => {
|
||||||
|
const length = state.list.length + 1
|
||||||
const newItem = state.list.some((item) => isObject(item))
|
const newItem = state.list.some((item) => isObject(item))
|
||||||
? {
|
? Object.assign(cloneDeep(state.list[0]), {
|
||||||
label: '',
|
label: `选项${length}`,
|
||||||
value: ''
|
value: `选项${length}`
|
||||||
}
|
})
|
||||||
: ''
|
: ''
|
||||||
state.list.splice(index + 1, 0, newItem)
|
state.list.splice(index + 1, 0, newItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<ElCheckboxGroup
|
<div>
|
||||||
modelValue={checkList.value}
|
<ElCheckboxGroup
|
||||||
style={{ fontSize: 'inherit' }}
|
modelValue={checkList.value}
|
||||||
onChange={onChange}
|
style={{ fontSize: 'inherit' }}
|
||||||
>
|
onChange={onChange}
|
||||||
<Draggable
|
|
||||||
tag="ul"
|
|
||||||
list={state.list}
|
|
||||||
class="list-group"
|
|
||||||
component-data={{
|
|
||||||
tag: 'ul',
|
|
||||||
type: 'transition-group',
|
|
||||||
name: !state.drag ? 'flip-list' : null
|
|
||||||
}}
|
|
||||||
handle=".handle"
|
|
||||||
{...dragOptions.value}
|
|
||||||
itemKey={''}
|
|
||||||
onStart={() => (state.drag = true)}
|
|
||||||
onEnd={() => (state.drag = false)}
|
|
||||||
>
|
>
|
||||||
{{
|
<Draggable
|
||||||
item: ({ element, index }) => (
|
tag="ul"
|
||||||
<div class={'flex items-center justify-between'}>
|
list={state.list}
|
||||||
<i class={'el-icon-rank handle cursor-move'}></i>
|
class="list-group"
|
||||||
{isObject(element) ? (
|
component-data={{
|
||||||
<>
|
tag: 'ul',
|
||||||
<ElCheckbox label={element.value} class={'ml-5px'}>
|
type: 'transition-group',
|
||||||
{''}
|
name: !state.drag ? 'flip-list' : null
|
||||||
</ElCheckbox>
|
}}
|
||||||
label:
|
handle=".handle"
|
||||||
|
{...dragOptions.value}
|
||||||
|
itemKey={''}
|
||||||
|
onStart={() => (state.drag = true)}
|
||||||
|
onEnd={() => (state.drag = false)}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
item: ({ element, index }) => (
|
||||||
|
<div class={'flex items-center justify-between'}>
|
||||||
|
<i class={'el-icon-rank handle cursor-move'}></i>
|
||||||
|
{isObject(element) ? (
|
||||||
|
<>
|
||||||
|
<ElCheckbox label={element.value} class={'ml-5px'}>
|
||||||
|
{''}
|
||||||
|
</ElCheckbox>
|
||||||
|
label:
|
||||||
|
<ElInput
|
||||||
|
v-model={element.label}
|
||||||
|
class={'my-12px mx-3px'}
|
||||||
|
style={{ width: '108px' }}
|
||||||
|
></ElInput>
|
||||||
|
value:
|
||||||
|
<ElInput
|
||||||
|
v-model={element.value}
|
||||||
|
class={'my-12px mx-3px'}
|
||||||
|
style={{ width: '106px' }}
|
||||||
|
></ElInput>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model={element.label}
|
v-model={state.list[index]}
|
||||||
class={'my-12px mx-3px'}
|
class={'m-12px'}
|
||||||
style={{ width: '108px' }}
|
style={{ width: '270px' }}
|
||||||
></ElInput>
|
></ElInput>
|
||||||
value:
|
)}
|
||||||
<ElInput
|
<div class={'flex flex-col'}>
|
||||||
v-model={element.value}
|
<i
|
||||||
class={'my-12px mx-3px'}
|
class={'el-icon-circle-plus-outline hover:text-blue-400 cursor-pointer'}
|
||||||
style={{ width: '106px' }}
|
onClick={() => incrementOption(index)}
|
||||||
></ElInput>
|
></i>
|
||||||
</>
|
<i
|
||||||
) : (
|
class={'el-icon-remove-outline hover:text-red-500 cursor-pointer'}
|
||||||
<ElInput
|
onClick={() => state.list.splice(index, 1)}
|
||||||
v-model={state.list[index]}
|
></i>
|
||||||
class={'m-12px'}
|
</div>
|
||||||
style={{ width: '270px' }}
|
|
||||||
></ElInput>
|
|
||||||
)}
|
|
||||||
<div class={'flex flex-col'}>
|
|
||||||
<i
|
|
||||||
class={'el-icon-circle-plus-outline'}
|
|
||||||
onClick={() => incrementOption(index)}
|
|
||||||
></i>
|
|
||||||
<i
|
|
||||||
class={'el-icon-remove-outline'}
|
|
||||||
onClick={() => state.list.splice(index, 1)}
|
|
||||||
></i>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
)
|
}}
|
||||||
}}
|
</Draggable>
|
||||||
</Draggable>
|
</ElCheckboxGroup>
|
||||||
</ElCheckboxGroup>
|
{props.showItemPropsConfig && (
|
||||||
|
<ElCollapse>
|
||||||
|
<ElCollapseItem title={'选项配置'}>
|
||||||
|
<ElTabs type={'border-card'}>
|
||||||
|
{state.list.map((item: OptionItem) => (
|
||||||
|
<ElTabPane label={item.label} key={item.label}>
|
||||||
|
<ElForm size="mini" labelPosition={'left'}>
|
||||||
|
<PropConfig component={item.component} block={item.block} />
|
||||||
|
</ElForm>
|
||||||
|
</ElTabPane>
|
||||||
|
))}
|
||||||
|
</ElTabs>
|
||||||
|
</ElCollapseItem>
|
||||||
|
</ElCollapse>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* @Author: 卜启缘
|
||||||
|
* @Date: 2021-07-11 17:53:54
|
||||||
|
* @LastEditTime: 2021-07-11 18:36:17
|
||||||
|
* @LastEditors: 卜启缘
|
||||||
|
* @Description: 组件属性配置
|
||||||
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\components\prop-config\index.tsx
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { computed, defineComponent, PropType } from 'vue'
|
||||||
|
import {
|
||||||
|
ElColorPicker,
|
||||||
|
ElInput,
|
||||||
|
ElOption,
|
||||||
|
ElSelect,
|
||||||
|
ElSwitch,
|
||||||
|
ElCascader,
|
||||||
|
ElInputNumber,
|
||||||
|
ElFormItem,
|
||||||
|
ElPopover
|
||||||
|
} from 'element-plus'
|
||||||
|
import { useDotProp } from '@/visual-editor/hooks/useDotProp'
|
||||||
|
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props'
|
||||||
|
import { TablePropEditor, CrossSortableOptionsEditor } from '../../components'
|
||||||
|
import { cloneDeep } from 'lodash'
|
||||||
|
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||||
|
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||||
|
|
||||||
|
export const PropConfig = defineComponent({
|
||||||
|
props: {
|
||||||
|
component: {
|
||||||
|
type: Object as PropType<VisualEditorComponent>,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
block: {
|
||||||
|
type: Object as PropType<VisualEditorBlockData>,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const { jsonData } = useVisualData()
|
||||||
|
/**
|
||||||
|
* @description 模型集合
|
||||||
|
*/
|
||||||
|
const models = computed(() => cloneDeep(jsonData.models))
|
||||||
|
|
||||||
|
const renderPropItem = (propName: string, propConfig: VisualEditorProps) => {
|
||||||
|
const { propObj, prop } = useDotProp(props.block.props, propName)
|
||||||
|
|
||||||
|
propObj[prop] ??= propConfig.defaultValue
|
||||||
|
|
||||||
|
return {
|
||||||
|
[VisualEditorPropsType.input]: () => {
|
||||||
|
if (!Object.is(propObj[prop], undefined) && !Object.is(propObj[prop], null)) {
|
||||||
|
propObj[prop] = `${propObj[prop]}`
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ElInput v-model={propObj[prop]} placeholder={propConfig.tips || propConfig.label} />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[VisualEditorPropsType.inputNumber]: () => <ElInputNumber v-model={propObj[prop]} />,
|
||||||
|
[VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />,
|
||||||
|
[VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />,
|
||||||
|
[VisualEditorPropsType.crossSortable]: () => (
|
||||||
|
<CrossSortableOptionsEditor
|
||||||
|
v-model={propObj[prop]}
|
||||||
|
multiple={propConfig.multiple}
|
||||||
|
showItemPropsConfig={propConfig.showItemPropsConfig}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
[VisualEditorPropsType.select]: () => (
|
||||||
|
<ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}>
|
||||||
|
{propConfig.options?.map((opt) => (
|
||||||
|
<ElOption label={opt.label} style={{ fontFamily: opt.value }} value={opt.value} />
|
||||||
|
))}
|
||||||
|
</ElSelect>
|
||||||
|
),
|
||||||
|
[VisualEditorPropsType.table]: () => (
|
||||||
|
<TablePropEditor v-model={propObj[prop]} propConfig={propConfig} />
|
||||||
|
),
|
||||||
|
[VisualEditorPropsType.modelBind]: () => (
|
||||||
|
<ElCascader
|
||||||
|
clearable={true}
|
||||||
|
props={{
|
||||||
|
checkStrictly: true,
|
||||||
|
children: 'entitys',
|
||||||
|
label: 'name',
|
||||||
|
value: 'key',
|
||||||
|
expandTrigger: 'hover'
|
||||||
|
}}
|
||||||
|
placeholder="请选择绑定的请求数据"
|
||||||
|
v-model={propObj[prop]}
|
||||||
|
options={models.value}
|
||||||
|
></ElCascader>
|
||||||
|
)
|
||||||
|
}[propConfig.type]()
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
return Object.entries(props.component.props ?? {}).map(([propName, propConfig]) => (
|
||||||
|
<>
|
||||||
|
<ElFormItem
|
||||||
|
key={props.block._vid + propName}
|
||||||
|
style={
|
||||||
|
propConfig.labelPosition == 'top'
|
||||||
|
? {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-start'
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
label: () => (
|
||||||
|
<>
|
||||||
|
{propConfig.tips && (
|
||||||
|
<ElPopover width={200} trigger={'hover'} content={propConfig.tips}>
|
||||||
|
{{
|
||||||
|
reference: () => <i class={'el-icon-warning-outline'}></i>
|
||||||
|
}}
|
||||||
|
</ElPopover>
|
||||||
|
)}
|
||||||
|
{propConfig.label}
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
default: () => renderPropItem(propName, propConfig)
|
||||||
|
}}
|
||||||
|
</ElFormItem>
|
||||||
|
</>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* @Author: 卜启缘
|
||||||
|
* @Date: 2021-06-10 16:23:06
|
||||||
|
* @LastEditTime: 2021-07-11 18:36:24
|
||||||
|
* @LastEditors: 卜启缘
|
||||||
|
* @Description: 组件属性编辑器
|
||||||
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\index.tsx
|
||||||
|
*/
|
||||||
|
import { defineComponent, computed, watch } from 'vue'
|
||||||
|
import { ElForm, ElFormItem, ElPopover, ElRadioGroup, ElRadioButton } from 'element-plus'
|
||||||
|
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||||
|
import { FormatInputNumber } from '@/visual-editor/components/common/format-input-number'
|
||||||
|
import { PropConfig } from './components/prop-config'
|
||||||
|
|
||||||
|
export const AttrEditor = defineComponent({
|
||||||
|
setup() {
|
||||||
|
const { visualConfig, currentBlock } = useVisualData()
|
||||||
|
|
||||||
|
const compPaddingAttrs = ['paddingTop', 'paddingLeft', 'paddingRight', 'paddingBottom']
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 监听组件padding值的变化
|
||||||
|
*/
|
||||||
|
watch(
|
||||||
|
compPaddingAttrs.map((item) => () => currentBlock.value.styles?.[item]),
|
||||||
|
(val: string[]) => {
|
||||||
|
const isSame = val.every((item) => currentBlock.value.styles?.tempPadding == item)
|
||||||
|
if (isSame || new Set(val).size === 1) {
|
||||||
|
if (Reflect.has(currentBlock.value, 'styles')) {
|
||||||
|
currentBlock.value.styles.tempPadding = val[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentBlock.value.styles.tempPadding = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 总的组件padding变化时进行的操作
|
||||||
|
*/
|
||||||
|
const compPadding = computed({
|
||||||
|
get: () => currentBlock.value.styles?.tempPadding,
|
||||||
|
set(val) {
|
||||||
|
compPaddingAttrs.forEach((item) => (currentBlock.value.styles[item] = val))
|
||||||
|
currentBlock.value.styles.tempPadding = val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单项
|
||||||
|
const FormEditor = () => {
|
||||||
|
const content: JSX.Element[] = []
|
||||||
|
if (currentBlock.value) {
|
||||||
|
const { componentKey } = currentBlock.value
|
||||||
|
const component = visualConfig.componentMap[componentKey]
|
||||||
|
console.log('props.block:', currentBlock.value)
|
||||||
|
content.push(
|
||||||
|
<>
|
||||||
|
<ElFormItem label="组件ID" labelWidth={'76px'}>
|
||||||
|
{currentBlock.value._vid}
|
||||||
|
<ElPopover
|
||||||
|
width={200}
|
||||||
|
trigger="hover"
|
||||||
|
content={`你可以利用该组件ID。对该组件进行获取和设置其属性,组件可用属性可在控制台输入:$$refs.${currentBlock.value._vid} 进行查看`}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
reference: () => <i class={'el-icon-warning-outline ml-6px'}></i>
|
||||||
|
}}
|
||||||
|
</ElPopover>
|
||||||
|
</ElFormItem>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
if (!!component) {
|
||||||
|
if (!!component.props) {
|
||||||
|
content.push(<PropConfig component={component} block={currentBlock.value} />)
|
||||||
|
{
|
||||||
|
currentBlock.value.showStyleConfig &&
|
||||||
|
content.push(
|
||||||
|
<ElFormItem label={'组件对齐方式'} labelWidth={'90px'}>
|
||||||
|
<ElRadioGroup v-model={currentBlock.value.styles.justifyContent} size="mini">
|
||||||
|
<ElRadioButton label="flex-start">{'左对齐'}</ElRadioButton>
|
||||||
|
<ElRadioButton label="center">{'居中'}</ElRadioButton>
|
||||||
|
<ElRadioButton label="flex-end">{'右对齐'}</ElRadioButton>
|
||||||
|
</ElRadioGroup>
|
||||||
|
</ElFormItem>,
|
||||||
|
<ElFormItem class={'flex flex-col justify-start'}>
|
||||||
|
{{
|
||||||
|
label: () => (
|
||||||
|
<div class={'flex justify-between mb-2'}>
|
||||||
|
<div>组件内边距</div>
|
||||||
|
<FormatInputNumber v-model={compPadding.value} class={'!w-100px'} />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
default: () => (
|
||||||
|
<div
|
||||||
|
class={'grid grid-cols-3 gap-2 w-full bg-gray-100 p-20px items-center'}
|
||||||
|
>
|
||||||
|
<FormatInputNumber
|
||||||
|
v-model={currentBlock.value.styles.paddingTop}
|
||||||
|
class={'!w-100px col-span-full col-start-2'}
|
||||||
|
/>
|
||||||
|
<FormatInputNumber
|
||||||
|
v-model={currentBlock.value.styles.paddingLeft}
|
||||||
|
class={'!w-100px col-span-1'}
|
||||||
|
/>
|
||||||
|
<div class={'bg-white col-span-1 h-40px'}></div>
|
||||||
|
<FormatInputNumber
|
||||||
|
v-model={currentBlock.value.styles.paddingRight}
|
||||||
|
class={'!w-100px col-span-1'}
|
||||||
|
/>
|
||||||
|
<FormatInputNumber
|
||||||
|
v-model={currentBlock.value.styles.paddingBottom}
|
||||||
|
class={'!w-100px col-span-full col-start-2'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</ElFormItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ElForm size="mini" labelPosition={'left'}>
|
||||||
|
{content}
|
||||||
|
</ElForm>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<>
|
||||||
|
<FormEditor />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-24 11:01:45
|
* @Date: 2021-06-24 11:01:45
|
||||||
* @LastEditTime: 2021-07-07 21:07:07
|
* @LastEditTime: 2021-07-08 09:53:27
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 事件-动作
|
* @Description: 事件-动作
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\event-action\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\event-action\index.tsx
|
||||||
*/
|
*/
|
||||||
import { computed, defineComponent, reactive } from 'vue'
|
import { computed, ref, defineComponent, reactive } from 'vue'
|
||||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||||
import {
|
import {
|
||||||
ElForm,
|
ElForm,
|
||||||
|
@ -27,7 +27,6 @@ import { useModal } from '@/visual-editor/hooks/useModal'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
ruleFormRef: NonNullable<any>
|
|
||||||
activeNames: string[]
|
activeNames: string[]
|
||||||
ruleForm: Action
|
ruleForm: Action
|
||||||
}
|
}
|
||||||
|
@ -59,9 +58,9 @@ export const EventAction = defineComponent({
|
||||||
const isEdit = computed(() =>
|
const isEdit = computed(() =>
|
||||||
currentBlock.value.actions?.some((item) => item.key === state.ruleForm.key)
|
currentBlock.value.actions?.some((item) => item.key === state.ruleForm.key)
|
||||||
)
|
)
|
||||||
|
const ruleFormRef = ref<InstanceType<typeof ElForm>>()
|
||||||
|
|
||||||
const state = reactive<IState>({
|
const state = reactive<IState>({
|
||||||
ruleFormRef: null,
|
|
||||||
activeNames: [],
|
activeNames: [],
|
||||||
ruleForm: createEmptyAction()
|
ruleForm: createEmptyAction()
|
||||||
})
|
})
|
||||||
|
@ -162,12 +161,7 @@ export const EventAction = defineComponent({
|
||||||
title: `${operateType}动作`,
|
title: `${operateType}动作`,
|
||||||
props: { width: 600 },
|
props: { width: 600 },
|
||||||
content: () => (
|
content: () => (
|
||||||
<ElForm
|
<ElForm model={state.ruleForm} ref={ruleFormRef} label-width="100px" size={'mini'}>
|
||||||
model={state.ruleForm}
|
|
||||||
ref={(el) => el && (state.ruleFormRef = el)}
|
|
||||||
label-width="100px"
|
|
||||||
size={'mini'}
|
|
||||||
>
|
|
||||||
<ElFormItem
|
<ElFormItem
|
||||||
label="事件"
|
label="事件"
|
||||||
prop={'event'}
|
prop={'event'}
|
||||||
|
@ -247,7 +241,7 @@ export const EventAction = defineComponent({
|
||||||
),
|
),
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
state.ruleFormRef.validate((valid) => {
|
ruleFormRef.value?.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const index = currentBlock.value.actions.findIndex(
|
const index = currentBlock.value.actions.findIndex(
|
||||||
(item) => item.key == state.ruleForm.key
|
(item) => item.key == state.ruleForm.key
|
||||||
|
|
|
@ -1,15 +1,48 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-07-05 10:51:09
|
* @Date: 2021-07-05 10:51:09
|
||||||
* @LastEditTime: 2021-07-05 10:52:26
|
* @LastEditTime: 2021-07-08 23:20:17
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 表单规则
|
* @Description: 表单规则
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\form-rule\index.tsx
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\form-rule\index.tsx
|
||||||
*/
|
*/
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
|
import { ElCard, ElTooltip } from 'element-plus'
|
||||||
|
|
||||||
export const FormRule = defineComponent({
|
export const FormRule = defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
return () => <>表单规则</>
|
return () => (
|
||||||
|
<>
|
||||||
|
<ElCard shadow={'always'} class={'mb-20px'}>
|
||||||
|
{{
|
||||||
|
header: () => (
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>设置关联规则</span>
|
||||||
|
<ElTooltip content="当前面题目选中某些选项时才出现此题" placement="bottom-end">
|
||||||
|
<i class={'el-icon-question'}></i>
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
default: () => <div>暂无规则</div>
|
||||||
|
}}
|
||||||
|
</ElCard>
|
||||||
|
<ElCard shadow={'always'} bodyStyle={{ padding: 1 ? '0' : '20px' }} class={'mb-20px'}>
|
||||||
|
{{
|
||||||
|
header: () => (
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span>设置选项关联规则</span>
|
||||||
|
<ElTooltip
|
||||||
|
content="当前面题目选择某些选项时才出现此题的某些选项 "
|
||||||
|
placement="bottom-end"
|
||||||
|
>
|
||||||
|
<i class={'el-icon-question'}></i>
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
default: () => null
|
||||||
|
}}
|
||||||
|
</ElCard>
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-12 22:18:48
|
* @Date: 2021-06-12 22:18:48
|
||||||
* @LastEditTime: 2021-07-05 10:53:16
|
* @LastEditTime: 2021-07-11 18:05:22
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 统一导出组件
|
* @Description: 统一导出组件
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\index.ts
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\index.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { AttrEditor } from './attr-editor/AttrEditor'
|
export { AttrEditor } from './attr-editor'
|
||||||
export { Animate } from './animate/Animate'
|
export { Animate } from './animate/Animate'
|
||||||
export { PageSetting } from './page-setting/pageSetting'
|
export { PageSetting } from './page-setting/pageSetting'
|
||||||
export { EventAction } from './event-action/'
|
export { EventAction } from './event-action/'
|
||||||
|
|
|
@ -10,13 +10,13 @@
|
||||||
name: !isDrag ? 'flip-list' : null
|
name: !isDrag ? 'flip-list' : null
|
||||||
}"
|
}"
|
||||||
:group="group"
|
:group="group"
|
||||||
v-bind="{ ...dragOptions, $attrs }"
|
v-bind="{ ...dragOptions, ...$attrs }"
|
||||||
:item-key="itemKey"
|
:item-key="itemKey"
|
||||||
@start="isDrag = true"
|
@start="isDrag = true"
|
||||||
@end="isDrag = false"
|
@end="isDrag = false"
|
||||||
>
|
>
|
||||||
<template #item="item">
|
<template #item="item">
|
||||||
<div>
|
<div :class="{ 'item-drag': item.element.draggable }" :data-el="item.element.draggable">
|
||||||
<slot name="item" v-bind="item"> </slot>
|
<slot name="item" v-bind="item"> </slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -67,6 +67,7 @@ export default defineComponent({
|
||||||
const dragOptions = computed(() => ({
|
const dragOptions = computed(() => ({
|
||||||
animation: 200,
|
animation: 200,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
scroll: true,
|
||||||
ghostClass: 'ghost'
|
ghostClass: 'ghost'
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="simulator-container">
|
<div class="simulator-container">
|
||||||
<div class="simulator-editor">
|
<div class="simulator-editor">
|
||||||
<div class="simulator-editor-content" :style="pageStyle">
|
<div class="simulator-editor-content">
|
||||||
<DraggableTransitionGroup
|
<DraggableTransitionGroup
|
||||||
v-model:drag="drag"
|
v-model:drag="drag"
|
||||||
v-model="currentPage.blocks"
|
v-model="currentPage.blocks"
|
||||||
style="min-height: 500px"
|
class="!min-h-680px"
|
||||||
|
draggable=".item-drag"
|
||||||
>
|
>
|
||||||
<template #item="{ element: outElement }">
|
<template #item="{ element: outElement }">
|
||||||
<div
|
<div
|
||||||
|
@ -51,7 +52,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { defineComponent, reactive, computed, toRefs } from 'vue'
|
import { defineComponent, reactive, watchEffect, toRefs } from 'vue'
|
||||||
import type { VisualEditorBlockData } from '@/visual-editor/visual-editor.utils'
|
import type { VisualEditorBlockData } from '@/visual-editor/visual-editor.utils'
|
||||||
import DraggableTransitionGroup from './draggable-transition-group.vue'
|
import DraggableTransitionGroup from './draggable-transition-group.vue'
|
||||||
import { $$dropdown, DropdownOption } from '@/visual-editor/utils/dropdown-service'
|
import { $$dropdown, DropdownOption } from '@/visual-editor/utils/dropdown-service'
|
||||||
|
@ -80,12 +81,23 @@ export default defineComponent({
|
||||||
drag: false
|
drag: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const pageStyle = computed(() => {
|
/**
|
||||||
|
* @description 操作当前页面样式表
|
||||||
|
*/
|
||||||
|
watchEffect(() => {
|
||||||
const { bgImage, bgColor } = currentPage.value.config
|
const { bgImage, bgColor } = currentPage.value.config
|
||||||
return {
|
const bodyStyleStr = `
|
||||||
backgroundImage: `url(${bgImage})`,
|
.simulator-editor-content {
|
||||||
backgroundColor: bgColor
|
background-color: ${bgColor};
|
||||||
|
background-image: url(${bgImage});
|
||||||
|
}`
|
||||||
|
const styleSheets = document.styleSheets[0]
|
||||||
|
const firstCssRule = document.styleSheets[0].cssRules[0]
|
||||||
|
const isExistContent = firstCssRule.cssText.includes('.simulator-editor-content')
|
||||||
|
if (isExistContent) {
|
||||||
|
styleSheets.deleteRule(0)
|
||||||
}
|
}
|
||||||
|
styleSheets.insertRule(bodyStyleStr)
|
||||||
})
|
})
|
||||||
|
|
||||||
//递归实现
|
//递归实现
|
||||||
|
@ -118,7 +130,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// 给当前点击的组件设置聚焦
|
// 给当前点击的组件设置聚焦
|
||||||
const handleSlotsFocus = (block, _vid) => {
|
const handleSlotsFocus = (block: VisualEditorBlockData, _vid: string) => {
|
||||||
const slots = block.props?.slots || {}
|
const slots = block.props?.slots || {}
|
||||||
if (Object.keys(slots).length > 0) {
|
if (Object.keys(slots).length > 0) {
|
||||||
Object.keys(slots).forEach((key) => {
|
Object.keys(slots).forEach((key) => {
|
||||||
|
@ -138,7 +150,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择要操作的组件
|
// 选择要操作的组件
|
||||||
const selectComp = (element) => {
|
const selectComp = (element: VisualEditorBlockData) => {
|
||||||
setCurrentBlock(element)
|
setCurrentBlock(element)
|
||||||
currentPage.value.blocks.forEach((block) => {
|
currentPage.value.blocks.forEach((block) => {
|
||||||
block.focus = element._vid == block._vid
|
block.focus = element._vid == block._vid
|
||||||
|
@ -234,7 +246,6 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
currentPage,
|
currentPage,
|
||||||
pageStyle,
|
|
||||||
deleteComp,
|
deleteComp,
|
||||||
selectComp,
|
selectComp,
|
||||||
onContextmenuBlock
|
onContextmenuBlock
|
||||||
|
@ -266,7 +277,6 @@ export default defineComponent({
|
||||||
overflow: hidden auto;
|
overflow: hidden auto;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
transform: translate(0);
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-clip: content-box;
|
background-clip: content-box;
|
||||||
contain: layout;
|
contain: layout;
|
||||||
|
@ -277,6 +287,7 @@ export default defineComponent({
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
transform: translate(0);
|
||||||
box-shadow: 0 8px 12px #ebedf0;
|
box-shadow: 0 8px 12px #ebedf0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -285,7 +296,6 @@ export default defineComponent({
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
transform: translate(0);
|
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<draggable-transition-group
|
<draggable-transition-group
|
||||||
:key="slotKey"
|
|
||||||
v-model="slotChildren"
|
v-model="slotChildren"
|
||||||
v-model:drag="isDrag"
|
v-model:drag="isDrag"
|
||||||
class="inner-draggable"
|
class="inner-draggable"
|
||||||
:class="{ slot: !slotChildren?.length }"
|
:class="{ slot: !slotChildren?.length }"
|
||||||
|
draggable=".item-drag"
|
||||||
:data-slot="`插槽(${slotKey})\n 拖拽组件到此处`"
|
:data-slot="`插槽(${slotKey})\n 拖拽组件到此处`"
|
||||||
>
|
>
|
||||||
<template #item="{ element: innerElement }">
|
<template #item="{ element: innerElement }">
|
||||||
<div
|
<div
|
||||||
class="list-group-item inner"
|
class="list-group-item inner"
|
||||||
:data-label="innerElement.label"
|
:data-label="innerElement.label"
|
||||||
:class="{ focus: innerElement.focus, focusWithChild: innerElement.focusWithChild }"
|
:class="{
|
||||||
|
focus: innerElement.focus,
|
||||||
|
focusWithChild: innerElement.focusWithChild
|
||||||
|
}"
|
||||||
@contextmenu.stop.prevent="onContextmenuBlock($event, innerElement, slotChildren)"
|
@contextmenu.stop.prevent="onContextmenuBlock($event, innerElement, slotChildren)"
|
||||||
@mousedown.stop="selectComp(innerElement)"
|
@mousedown.stop="selectComp(innerElement)"
|
||||||
>
|
>
|
||||||
|
@ -45,35 +48,48 @@
|
||||||
* @update: 2021/5/2 22:36
|
* @update: 2021/5/2 22:36
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent, PropType } from 'vue'
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
import DraggableTransitionGroup from './draggable-transition-group.vue'
|
import DraggableTransitionGroup from './draggable-transition-group.vue'
|
||||||
import CompRender from './comp-render'
|
import CompRender from './comp-render'
|
||||||
|
import type { VisualEditorBlockData } from '@/visual-editor/visual-editor.utils'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SlotItem',
|
name: 'SlotItem',
|
||||||
components: { CompRender, DraggableTransitionGroup },
|
components: { CompRender, DraggableTransitionGroup },
|
||||||
props: {
|
props: {
|
||||||
slotKey: String,
|
slotKey: {
|
||||||
|
type: String as PropType<string>,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
drag: {
|
drag: {
|
||||||
type: Boolean,
|
type: Boolean as PropType<boolean>,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
children: {
|
children: {
|
||||||
type: Array,
|
type: Array as PropType<VisualEditorBlockData[]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
selectComp: {
|
selectComp: {
|
||||||
type: Function,
|
type: Function as PropType<(comp: VisualEditorBlockData) => void>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
onContextmenuBlock: {
|
onContextmenuBlock: {
|
||||||
type: Function,
|
type: Function as PropType<
|
||||||
|
(
|
||||||
|
e: MouseEvent,
|
||||||
|
block: VisualEditorBlockData,
|
||||||
|
parentBlocks?: VisualEditorBlockData[]
|
||||||
|
) => void
|
||||||
|
>,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['update:children', 'on-selected', 'update:drag'],
|
emits: ['update:children', 'on-selected', 'update:drag'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
|
// 初始化时设置上次选中的组件
|
||||||
|
props.children.some((item) => item.focus && !void props.selectComp(item))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isDrag: useVModel(props, 'drag', emit),
|
isDrag: useVModel(props, 'drag', emit),
|
||||||
slotChildren: useVModel(props, 'children', emit)
|
slotChildren: useVModel(props, 'children', emit)
|
||||||
|
@ -113,7 +129,6 @@ export default defineComponent({
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
transform: translate(0);
|
|
||||||
|
|
||||||
&.focusWithChild {
|
&.focusWithChild {
|
||||||
@include showContainerBorder;
|
@include showContainerBorder;
|
||||||
|
|
|
@ -1,18 +1,40 @@
|
||||||
/*
|
/*
|
||||||
* @Author: 卜启缘
|
* @Author: 卜启缘
|
||||||
* @Date: 2021-06-01 09:45:21
|
* @Date: 2021-06-01 09:45:21
|
||||||
* @LastEditTime: 2021-07-07 21:45:41
|
* @LastEditTime: 2021-07-08 16:47:34
|
||||||
* @LastEditors: 卜启缘
|
* @LastEditors: 卜启缘
|
||||||
* @Description: 全局声明文件
|
* @Description: 全局声明文件
|
||||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\types\index.d.ts
|
* @FilePath: \vite-vue3-lowcode\src\visual-editor\types\index.d.ts
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare type LabelValue = {
|
declare global {
|
||||||
label: string
|
/** label-value一般用做选项 */
|
||||||
value: any
|
type LabelValue = {
|
||||||
}
|
label: string
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
/** label-value 数组 一般用做选项 */
|
||||||
|
type LabelValueOptions = OptionItem[]
|
||||||
|
|
||||||
declare type LabelValueOptions = OptionItem[]
|
type RequestIdleCallbackHandle = any
|
||||||
|
|
||||||
|
type RequestIdleCallbackDeadline = {
|
||||||
|
readonly didTimeout: boolean
|
||||||
|
timeRemaining: () => number
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestIdleCallbackOptions = {
|
||||||
|
timeout: number
|
||||||
|
}
|
||||||
|
interface Window {
|
||||||
|
$$refs: any
|
||||||
|
requestIdleCallback: (
|
||||||
|
callback: (deadline: RequestIdleCallbackDeadline) => void,
|
||||||
|
opts?: RequestIdleCallbackOptions
|
||||||
|
) => RequestIdleCallbackHandle
|
||||||
|
cancelIdleCallback: (handle: RequestIdleCallbackHandle) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
export interface ComponentCustomProperties {
|
export interface ComponentCustomProperties {
|
||||||
|
|
|
@ -1,23 +1,39 @@
|
||||||
export enum VisualEditorPropsType {
|
export enum VisualEditorPropsType {
|
||||||
|
/** 输入框 */
|
||||||
input = 'input',
|
input = 'input',
|
||||||
|
/** 数字输入框 */
|
||||||
inputNumber = 'InputNumber',
|
inputNumber = 'InputNumber',
|
||||||
|
/** 颜色选择器 */
|
||||||
color = 'color',
|
color = 'color',
|
||||||
|
/** 下拉选择器 */
|
||||||
select = 'select',
|
select = 'select',
|
||||||
|
/** 表格 */
|
||||||
table = 'table',
|
table = 'table',
|
||||||
|
/** 开关 */
|
||||||
switch = 'switch',
|
switch = 'switch',
|
||||||
|
/** 模型绑定选择器 */
|
||||||
modelBind = 'ModelBind',
|
modelBind = 'ModelBind',
|
||||||
|
/** 可拖拽项 */
|
||||||
crossSortable = 'CrossSortable'
|
crossSortable = 'CrossSortable'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VisualEditorProps = {
|
export type VisualEditorProps = {
|
||||||
type: VisualEditorPropsType
|
type: VisualEditorPropsType
|
||||||
|
/** 表单项标签名称 */
|
||||||
label: string
|
label: string
|
||||||
tips?: string // 表单项提示
|
/** 表单项提示说明 */
|
||||||
labelPosition?: string // 表单域标签的位置
|
tips?: string
|
||||||
multiple?: boolean
|
/** 表单域标签的位置 */
|
||||||
|
labelPosition?: string
|
||||||
|
/** 表单项默认值 */
|
||||||
defaultValue?: any
|
defaultValue?: any
|
||||||
} & {
|
} & {
|
||||||
|
/** 可选项 */
|
||||||
options?: VisualEditorSelectOptions
|
options?: VisualEditorSelectOptions
|
||||||
|
/** 是否可以多选 */
|
||||||
|
multiple?: boolean
|
||||||
|
/** 项属性配置 */
|
||||||
|
showItemPropsConfig?: boolean
|
||||||
} & {
|
} & {
|
||||||
table?: VisualEditorTableOption
|
table?: VisualEditorTableOption
|
||||||
}
|
}
|
||||||
|
@ -123,6 +139,7 @@ export function createEditorColorProp({ label, defaultValue }: EditorColorProp):
|
||||||
export type VisualEditorSelectOptions = {
|
export type VisualEditorSelectOptions = {
|
||||||
label: string
|
label: string
|
||||||
value: string | number | boolean | object
|
value: string | number | boolean | object
|
||||||
|
[prop: string]: any
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
interface EditorSelectProp {
|
interface EditorSelectProp {
|
||||||
|
@ -185,6 +202,7 @@ interface EditorCrossSortableProp {
|
||||||
label: string
|
label: string
|
||||||
labelPosition: 'top' | ''
|
labelPosition: 'top' | ''
|
||||||
multiple?: boolean
|
multiple?: boolean
|
||||||
|
showItemPropsConfig?: boolean
|
||||||
defaultValue?: string[] | VisualEditorSelectOptions
|
defaultValue?: string[] | VisualEditorSelectOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,12 +210,14 @@ export function createEditorCrossSortableProp({
|
||||||
label,
|
label,
|
||||||
labelPosition,
|
labelPosition,
|
||||||
multiple,
|
multiple,
|
||||||
|
showItemPropsConfig,
|
||||||
defaultValue
|
defaultValue
|
||||||
}: EditorCrossSortableProp): VisualEditorProps {
|
}: EditorCrossSortableProp): VisualEditorProps {
|
||||||
return {
|
return {
|
||||||
type: VisualEditorPropsType.crossSortable,
|
type: VisualEditorPropsType.crossSortable,
|
||||||
label,
|
label,
|
||||||
multiple,
|
multiple,
|
||||||
|
showItemPropsConfig,
|
||||||
labelPosition,
|
labelPosition,
|
||||||
defaultValue
|
defaultValue
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,10 @@ export interface VisualEditorBlockData {
|
||||||
props: Record<string, any>
|
props: Record<string, any>
|
||||||
/** 绑定的字段 */
|
/** 绑定的字段 */
|
||||||
model: Record<string, string>
|
model: Record<string, string>
|
||||||
|
/** 组件是否可以被拖拽 */
|
||||||
|
draggable: boolean
|
||||||
|
/** 是否显示组件样式配置项 */
|
||||||
|
showStyleConfig?: boolean
|
||||||
/** 动画集 */
|
/** 动画集 */
|
||||||
animations?: Animation[]
|
animations?: Animation[]
|
||||||
/** 组件动作集合 */
|
/** 组件动作集合 */
|
||||||
|
@ -182,11 +186,17 @@ export interface Animation {
|
||||||
* @description 单个组件注册规则
|
* @description 单个组件注册规则
|
||||||
*/
|
*/
|
||||||
export interface VisualEditorComponent {
|
export interface VisualEditorComponent {
|
||||||
key: string // 组件名称
|
/** 组件name */
|
||||||
moduleName: keyof ComponentModules // 模块名称
|
key: string
|
||||||
_vid?: string // 组件id 时间戳
|
/** 组件所属模块名称 */
|
||||||
|
moduleName: keyof ComponentModules
|
||||||
|
/** 组件唯一id */
|
||||||
|
_vid?: string
|
||||||
|
/** 组件中文名称 */
|
||||||
label: string
|
label: string
|
||||||
|
/** 组件预览函数 */
|
||||||
preview: () => JSX.Element
|
preview: () => JSX.Element
|
||||||
|
/** 组件渲染函数 */
|
||||||
render: (data: {
|
render: (data: {
|
||||||
props: any
|
props: any
|
||||||
model: any
|
model: any
|
||||||
|
@ -194,9 +204,17 @@ export interface VisualEditorComponent {
|
||||||
block: VisualEditorBlockData
|
block: VisualEditorBlockData
|
||||||
custom: Record<string, any>
|
custom: Record<string, any>
|
||||||
}) => JSX.Element
|
}) => JSX.Element
|
||||||
|
/** 组件是否可以被拖拽 */
|
||||||
|
draggable?: boolean
|
||||||
|
/** 是否显示组件的样式配置项 */
|
||||||
|
showStyleConfig?: boolean
|
||||||
|
/** 组件属性 */
|
||||||
props?: Record<string, VisualEditorProps>
|
props?: Record<string, VisualEditorProps>
|
||||||
animations?: Animation[] // 动画集
|
/** 动画集 */
|
||||||
events?: { label: string; value: string }[] // 组件事件集合
|
animations?: Animation[]
|
||||||
|
/** 组件事件集合 */
|
||||||
|
events?: { label: string; value: string }[]
|
||||||
|
/** 组件样式 */
|
||||||
styles?: CSSProperties
|
styles?: CSSProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,10 +224,9 @@ export interface VisualEditorMarkLines {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createNewBlock(component: VisualEditorComponent): VisualEditorBlockData {
|
export function createNewBlock(component: VisualEditorComponent): VisualEditorBlockData {
|
||||||
component._vid = `${component._vid}`.startsWith('vid_') ? component._vid : `vid_${component._vid}`
|
const cid = parseInt(`${Date.now() * Math.random()}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_vid: component._vid!,
|
_vid: `vid_${cid}`,
|
||||||
moduleName: component.moduleName,
|
moduleName: component.moduleName,
|
||||||
componentKey: component!.key,
|
componentKey: component!.key,
|
||||||
label: component!.label,
|
label: component!.label,
|
||||||
|
@ -232,6 +249,8 @@ export function createNewBlock(component: VisualEditorComponent): VisualEditorBl
|
||||||
}
|
}
|
||||||
return prev
|
return prev
|
||||||
}, {}),
|
}, {}),
|
||||||
|
draggable: component.draggable ?? true, // 是否可以拖拽
|
||||||
|
showStyleConfig: component.showStyleConfig ?? true, // 是否显示组件样式配置
|
||||||
animations: [], // 动画集
|
animations: [], // 动画集
|
||||||
actions: [], // 动作集合
|
actions: [], // 动作集合
|
||||||
events: component.events || [], // 事件集合
|
events: component.events || [], // 事件集合
|
||||||
|
|
|
@ -84,7 +84,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
||||||
'element-plus',
|
'element-plus',
|
||||||
'vant',
|
'vant',
|
||||||
'lodash',
|
'lodash',
|
||||||
'vuedraggable/src/vuedraggable'
|
'vuedraggable'
|
||||||
],
|
],
|
||||||
exclude: ['vue-demi']
|
exclude: ['vue-demi']
|
||||||
},
|
},
|
||||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -681,11 +681,16 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
|
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
|
||||||
integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==
|
integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==
|
||||||
|
|
||||||
"@types/node@*", "@types/node@^16.0.0":
|
"@types/node@*":
|
||||||
version "16.0.0"
|
version "16.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.0.tgz#067a6c49dc7a5c2412a505628e26902ae967bf6f"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.0.tgz#067a6c49dc7a5c2412a505628e26902ae967bf6f"
|
||||||
integrity sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg==
|
integrity sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg==
|
||||||
|
|
||||||
|
"@types/node@^16.3.1":
|
||||||
|
version "16.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.3.1.tgz#24691fa2b0c3ec8c0d34bfcfd495edac5593ebb4"
|
||||||
|
integrity sha512-N87VuQi7HEeRJkhzovao/JviiqKjDKMVKxKMfUvSKw+MbkbW8R0nA3fi/MQhhlxV2fQ+2ReM+/Nt4efdrJx3zA==
|
||||||
|
|
||||||
"@types/normalize-package-data@^2.4.0":
|
"@types/normalize-package-data@^2.4.0":
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||||
|
@ -808,10 +813,10 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@popperjs/core" "^2.9.2"
|
"@popperjs/core" "^2.9.2"
|
||||||
|
|
||||||
"@vant/touch-emulator@^1.3.1":
|
"@vant/touch-emulator@^1.3.2":
|
||||||
version "1.3.1"
|
version "1.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@vant/touch-emulator/-/touch-emulator-1.3.1.tgz#93a0c8824d47482c660b982045378d45245e07a0"
|
resolved "https://registry.yarnpkg.com/@vant/touch-emulator/-/touch-emulator-1.3.2.tgz#9392c3971dd7247c2cf8e5d5aabc951e33cd5e6f"
|
||||||
integrity sha512-7qid+kyhvFdW7qMhxn0M9ClivvZY1sf2ph5Lu7cSVlA3s+tdtnySwOiAdZc4BSVJQUejeE63XxZIn/p6e45Uvw==
|
integrity sha512-Om6e8kCAnmk/q8byngKreff7Hyn6XxwOGr8yedP3y3LEVoE+iyj8/+Mn+AYvGEQ00GK0MlgAfyaV4emXAYj1Hw==
|
||||||
|
|
||||||
"@vant/use@^1.1.2":
|
"@vant/use@^1.1.2":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
|
@ -5315,10 +5320,10 @@ safe-buffer@~5.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
sass@1.35.1:
|
sass@1.35.2:
|
||||||
version "1.35.1"
|
version "1.35.2"
|
||||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.35.1.tgz#90ecf774dfe68f07b6193077e3b42fb154b9e1cd"
|
resolved "https://registry.yarnpkg.com/sass/-/sass-1.35.2.tgz#b732314fcdaf7ef8d0f1698698adc378043cb821"
|
||||||
integrity sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ==
|
integrity sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw==
|
||||||
dependencies:
|
dependencies:
|
||||||
chokidar ">=3.0.0 <4.0.0"
|
chokidar ">=3.0.0 <4.0.0"
|
||||||
|
|
||||||
|
@ -6254,10 +6259,10 @@ validate-npm-package-license@^3.0.1:
|
||||||
spdx-correct "^3.0.0"
|
spdx-correct "^3.0.0"
|
||||||
spdx-expression-parse "^3.0.0"
|
spdx-expression-parse "^3.0.0"
|
||||||
|
|
||||||
vant@3.1.2:
|
vant@3.1.3:
|
||||||
version "3.1.2"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/vant/-/vant-3.1.2.tgz#8777290f38c4b042d22612343e72dcab95b044ed"
|
resolved "https://registry.yarnpkg.com/vant/-/vant-3.1.3.tgz#c87a1b86bbf0e3c139a519d89e310427fa36d8b3"
|
||||||
integrity sha512-uw+ZKZTD44L2YojenZjRuF/rETR28rnuI7cQ4tFtEJBLKc1TNxsxl3PYmoGbYt7jd5rMrHYST8SkBsbB5i4kNA==
|
integrity sha512-TihKiAUE+Kf2Cx0/sokMtk6/WTZWTwHygBwOgWKB2eJ43piMogcRYpgK66UK7EATfn00565Rp+trIkAwE63+zQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vant/icons" "^1.6.0"
|
"@vant/icons" "^1.6.0"
|
||||||
"@vant/lazyload" "^1.2.0"
|
"@vant/lazyload" "^1.2.0"
|
||||||
|
|
Loading…
Reference in New Issue