perf: dragable components

This commit is contained in:
bqy_fe 2021-06-14 18:50:33 +08:00
parent 43b6361515
commit 240a0bd779
35 changed files with 513 additions and 109 deletions

View File

@ -52,8 +52,13 @@ let propObj = {
$$('#props + table tr').reduce((prev, curr) => { $$('#props + table tr').reduce((prev, curr) => {
const children = curr.children const children = curr.children
const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase()) const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase())
const child3Text = children[3].textContent
const defaultValue = ['true', 'false'].includes(child3Text)
? child3Text
: `'${child3Text == '-' ? '' : child3Text}'`
const value = (propObj[children[2].textContent] ?? propObj['string'])({ const value = (propObj[children[2].textContent] ?? propObj['string'])({
label: `'${children[1].textContent}'` label: `'${children[1].textContent}'`,
defaultValue
}).replaceAll('"', '') }).replaceAll('"', '')
prev[key] = value prev[key] = value
return prev return prev

View File

@ -57,8 +57,13 @@ let propObj = {
$$('#props + table tr').reduce((prev, curr) => { $$('#props + table tr').reduce((prev, curr) => {
const children = curr.children const children = curr.children
const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase()) const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase())
const child3Text = children[3].textContent
const defaultValue = ['true', 'false'].includes(child3Text)
? child3Text
: `'${child3Text == '-' ? '' : child3Text}'`
const value = (propObj[children[2].textContent] ?? propObj['string'])({ const value = (propObj[children[2].textContent] ?? propObj['string'])({
label: `'${children[1].textContent}'` label: `'${children[1].textContent}'`,
defaultValue
}).replaceAll('"', '') }).replaceAll('"', '')
prev[key] = value prev[key] = value
return prev return prev

View File

@ -21,8 +21,8 @@
}, },
"dependencies": { "dependencies": {
"@vant/touch-emulator": "^1.3.0", "@vant/touch-emulator": "^1.3.0",
"@vueuse/core": "^5.0.2", "@vueuse/core": "^5.0.3",
"@vueuse/integrations": "^5.0.2", "@vueuse/integrations": "^5.0.3",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"dayjs": "^1.10.5", "dayjs": "^1.10.5",
@ -56,7 +56,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.23.4", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-prettier": "^3.4.0", "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.11.0", "eslint-plugin-vue": "^7.11.1",
"gh-pages": "^3.2.0", "gh-pages": "^3.2.0",
"husky": "^6.0.0", "husky": "^6.0.0",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",

View File

@ -1,14 +1,23 @@
import { defineComponent } from 'vue' /*
* @Author:
* @Date: 2021-05-04 05:36:58
* @LastEditTime: 2021-06-14 10:03:06
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\preview\views\comp-render.tsx
*/
import { defineComponent, PropType } from 'vue'
import { VisualEditorBlockData, VisualEditorConfig } from '@/visual-editor/visual-editor.utils'
export default defineComponent({ export default defineComponent({
name: 'CompRender', name: 'CompRender',
props: { props: {
element: { element: {
type: Object, type: Object as PropType<VisualEditorBlockData>,
default: () => ({}) default: () => ({})
}, },
config: { config: {
type: Object, type: Object as PropType<VisualEditorConfig>,
default: () => ({}) default: () => ({})
} }
}, },

View File

@ -1,7 +1,7 @@
/* /*
* @Author: * @Author:
* @Date: 2021-06-12 21:29:32 * @Date: 2021-06-12 21:29:32
* @LastEditTime: 2021-06-12 22:03:43 * @LastEditTime: 2021-06-13 19:27:04
* @LastEditors: * @LastEditors:
* @Description: * @Description:
* @FilePath: \vite-vue3-lowcode\src\hooks\useAnimate.ts * @FilePath: \vite-vue3-lowcode\src\hooks\useAnimate.ts
@ -9,30 +9,47 @@
import { Animation } from '@/visual-editor/visual-editor.utils' import { Animation } from '@/visual-editor/visual-editor.utils'
export const useAnimate = async (animateEl: HTMLElement, animations: Animation | Animation[]) => { export const useAnimate = async (
animateEl: HTMLElement,
animations: Animation | Animation[],
prefixCls = 'animate__'
) => {
animations = Array.isArray(animations) ? animations : [animations] animations = Array.isArray(animations) ? animations : [animations]
const play = (animate: Animation) => const play = (animate: Animation) =>
new Promise((resolve) => { new Promise((resolve) => {
if (animateEl) { if (animateEl) {
const animationName = `animate__${animate.value}` const animationName = `${prefixCls}${animate.value}`
// 过滤可能残留的animate.css动画类名
animateEl.classList.value = animateEl.classList.value
.split(' ')
.filter((item) => !item.includes(prefixCls))
.join(' ')
// 设置动画属性
const setAnimate = () => {
animateEl.style.setProperty('--animate-duration', `${animate.duration}s`) animateEl.style.setProperty('--animate-duration', `${animate.duration}s`)
animateEl.style.setProperty('--animate-delay', `${animate.delay}s`) animateEl.style.setProperty('animation-delay', `${animate.delay}s`)
animateEl.style.setProperty( animateEl.style.setProperty(
'animation-iteration-count', 'animation-iteration-count',
`${animate.infinite ? 'infinite' : animate.count}` `${animate.infinite ? 'infinite' : animate.count}`
) )
animateEl?.classList.add(`${prefixCls}animated`, animationName)
}
// 动画结束时,删除类名 // 动画结束时,删除类名
const handleAnimationEnd = (event?: AnimationEvent) => { const handleAnimationEnd = (event?: AnimationEvent) => {
event?.stopPropagation() event?.stopPropagation()
animateEl.classList.remove(`animate__animated`, animationName) animateEl.classList.remove(`${prefixCls}animated`, animationName)
animateEl.removeEventListener('animationend', handleAnimationEnd) animateEl.removeEventListener('animationend', handleAnimationEnd)
resolve('animation end') resolve('animation end')
} }
animateEl?.classList.add(`animate__animated`, animationName) setAnimate()
animateEl?.addEventListener('animationend', handleAnimationEnd, { once: true }) animateEl?.addEventListener('animationend', handleAnimationEnd, { once: true })
// animateEl?.addEventListener('animationcancel', handleAnimationEnd, { once: true })
} else { } else {
resolve('动画执行失败!执行动画元素不存在!') resolve('动画执行失败!执行动画元素不存在!')
} }

View File

@ -6,16 +6,23 @@ import {
createEditorSelectProp createEditorSelectProp
} from '@/visual-editor/visual-editor.props' } from '@/visual-editor/visual-editor.props'
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils' import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
export default { export default {
key: 'divider', key: 'divider',
moduleName: 'baseWidgets', moduleName: 'baseWidgets',
label: '分割线', label: '分割线',
preview: () => <Divider style="width:190px"></Divider>, preview: () => <Divider style="width:190px"></Divider>,
render: ({ props }) => { render: ({ props, block }) => {
const style = `color:${props['text-color']};borderColor:${props['divider-color']}` const { registerRef } = useGlobalProperties()
const style = {
color: props['text-color'],
borderColor: props['divider-color']
}
return ( return (
<Divider {...props} style={style}> <Divider ref={(el) => registerRef(el, block._vid)} {...props} style={style}>
{{ {{
default: () => props.text default: () => props.text
}} }}

View File

@ -1,7 +1,7 @@
/* /*
* @Author: * @Author:
* @Date: 2021-06-01 09:45:21 * @Date: 2021-06-01 09:45:21
* @LastEditTime: 2021-06-12 09:55:10 * @LastEditTime: 2021-06-14 10:31:27
* @LastEditors: * @LastEditors:
* @Description: * @Description:
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\image\index.tsx * @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\image\index.tsx
@ -32,8 +32,8 @@ export default {
), ),
render: ({ props, block }) => { render: ({ props, block }) => {
const { registerRef } = useGlobalProperties() const { registerRef } = useGlobalProperties()
const ImageComp = () => <Image ref={(el) => registerRef(el, block._vid)} {...props} />
return <ImageComp></ImageComp> return <Image ref={(el) => registerRef(el, block._vid)} {...props} />
}, },
props: { props: {
src: createEditorInputProp({ src: createEditorInputProp({

View File

@ -13,6 +13,11 @@ export default {
render: ({ model, size, block, props, custom }) => { render: ({ model, size, block, props, custom }) => {
const { registerRef } = useGlobalProperties() const { registerRef } = useGlobalProperties()
let rules = []
try {
rules = JSON.parse(props.rules)
} catch (e) {}
return ( return (
<Field <Field
ref={(el) => registerRef(el, block._vid)} ref={(el) => registerRef(el, block._vid)}
@ -20,6 +25,7 @@ export default {
{...props} {...props}
{...model.default} {...model.default}
v-model={props.modelValue} v-model={props.modelValue}
rules={rules}
style={{ style={{
width: size.width ? `${size.width}px` : null width: size.width ? `${size.width}px` : null
}} }}

View File

@ -0,0 +1,44 @@
/*
* @Author:
* @Date: 2021-06-14 12:24:12
* @LastEditTime: 2021-06-14 12:38:02
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\notice-bar\createFieldProps.ts
*/
import {
createEditorInputProp,
createEditorSelectProp,
createEditorSwitchProp
} from '@/visual-editor/visual-editor.props'
export const createFieldProps = () => ({
background: createEditorInputProp({ label: '滚动条背景' }),
color: createEditorInputProp({ label: '通知文本颜色' }),
delay: createEditorInputProp({ label: '动画延迟时间 (s)' }),
leftIcon: createEditorInputProp({ label: '左侧图标名称或图片链接', defaultValue: 'volume-o' }),
mode: createEditorSelectProp({
label: '通知栏模式',
options: [
{
label: '默认',
value: ''
},
{
label: '可关闭',
value: 'closeable'
},
{
label: '链接',
value: 'link'
}
]
}),
scrollable: createEditorSwitchProp({ label: '是否开启滚动播放,内容长度溢出时默认开启' }),
speed: createEditorInputProp({ label: '滚动速率 (px/s)' }),
text: createEditorInputProp({
label: '通知文本内容',
defaultValue: '在代码阅读过程中人们说脏话的频率是衡量代码质量的唯一标准。'
}),
wrapable: createEditorSwitchProp({ label: '是否开启文本换行,只在禁用滚动时生效' })
})

View File

@ -0,0 +1,37 @@
/*
* @Author:
* @Date: 2021-06-14 12:24:12
* @LastEditTime: 2021-06-14 12:56:23
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\notice-bar\index.tsx
*/
import { NoticeBar } from 'vant'
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
import { createFieldProps } from './createFieldProps'
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
export default {
key: 'NoticeBar',
moduleName: 'baseWidgets',
label: '通知栏',
preview: () => (
<NoticeBar
style={{ width: '180px' }}
leftIcon={'volume-o'}
text={'在代码阅读过程中人们说脏话的频率是衡量代码质量的唯一标准。'}
/>
),
render: ({ block, props }) => {
const { registerRef } = useGlobalProperties()
return <NoticeBar ref={(el) => registerRef(el, block._vid)} {...props} />
},
props: createFieldProps(),
resize: {
width: true
},
model: {
default: '绑定字段'
}
} as VisualEditorComponent

View File

@ -49,7 +49,7 @@ export default {
) )
}} }}
</Field> </Field>
<Popup v-model={[state.showPicker, 'show', ['modifier']]} position={'bottom'}> <Popup v-model={[state.showPicker, 'show']} position={'bottom'}>
<Picker <Picker
ref={(el) => registerRef(el, block._vid)} ref={(el) => registerRef(el, block._vid)}
{...props} {...props}

View File

@ -11,9 +11,11 @@ export default {
key: 'process', key: 'process',
moduleName: 'baseWidgets', moduleName: 'baseWidgets',
label: '进度条', label: '进度条',
preview: () => <Progress style="width:190px" percentage={0} />, preview: () => <Progress style="width:190px" percentage={50} />,
render: ({ props }) => { render: ({ props }) => {
return <Progress {...props} pivotText={props.pivotText || undefined} /> const RenderProgress = () => <Progress {...props} pivotText={props.pivotText || undefined} />
return <RenderProgress />
}, },
props: { props: {
percentage: createEditorInputNumberProp({ label: '进度百分比', defaultValue: 50 }), percentage: createEditorInputNumberProp({ label: '进度百分比', defaultValue: 50 }),

View File

@ -11,7 +11,7 @@ import {
export default { export default {
key: 'rate', key: 'rate',
moduleName: 'baseWidgets', moduleName: 'baseWidgets',
label: '表单项类型 - 单选框', label: '表单项类型 - 评分',
preview: () => ( preview: () => (
<Field <Field
name="rate" name="rate"

View File

@ -10,7 +10,7 @@ import {
export default { export default {
key: 'stepper', key: 'stepper',
moduleName: 'baseWidgets', moduleName: 'baseWidgets',
label: '表单项类型 - 单选框', label: '表单项类型 - 步进器',
preview: () => ( preview: () => (
<Field <Field
name="stepper" name="stepper"

View File

@ -0,0 +1,33 @@
/*
* @Author:
* @Date: 2021-06-14 12:24:12
* @LastEditTime: 2021-06-14 18:43:21
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\swipe\createFieldProps.ts
*/
import {
createEditorInputProp,
createEditorSwitchProp,
createEditorCrossSortableProp
} from '@/visual-editor/visual-editor.props'
export const createFieldProps = () => ({
images: createEditorCrossSortableProp({
label: '图片列表',
labelPosition: 'top',
defaultValue: ['https://img.yzcdn.cn/vant/apple-1.jpg', 'https://img.yzcdn.cn/vant/apple-2.jpg']
}),
width: createEditorInputProp({ label: '滑块宽度,单位为 px', defaultValue: 'auto' }),
height: createEditorInputProp({ label: '滑块高度,单位为 px', defaultValue: '200' }),
autoplay: createEditorInputProp({ label: '自动轮播间隔,单位为 ms', defaultValue: '' }),
duration: createEditorInputProp({ label: '动画时长,单位为 ms', defaultValue: '500' }),
indicatorColor: createEditorInputProp({ label: '指示器颜色', defaultValue: '#1989fa' }),
initialSwipe: createEditorInputProp({ label: '初始位置索引值', defaultValue: '0' }),
lazyRender: createEditorSwitchProp({ label: '是否延迟渲染未展示的轮播', defaultValue: false }),
loop: createEditorSwitchProp({ label: '是否开启循环播放', defaultValue: true }),
showIndicators: createEditorSwitchProp({ label: '是否显示指示器', defaultValue: true }),
stopPropagation: createEditorSwitchProp({ label: '是否阻止滑动事件冒泡', defaultValue: true }),
touchable: createEditorSwitchProp({ label: '是否可以通过手势滑动', defaultValue: true }),
vertical: createEditorSwitchProp({ label: '是否为纵向滚动', defaultValue: false })
})

View File

@ -0,0 +1,54 @@
/*
* @Author:
* @Date: 2021-06-14 12:24:12
* @LastEditTime: 2021-06-14 18:48:44
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\swipe\index.tsx
*/
import { Swipe, SwipeItem } from 'vant'
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
import { createFieldProps } from './createFieldProps'
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
const swipeItemStyle = `color: #fff;
font-size: 20px;
line-height: 150px;
text-align: center;
background-color: #39a9ed;`
export default {
key: 'swipe',
moduleName: 'baseWidgets',
label: '轮播图',
preview: () => (
<Swipe style={{ width: '180px', height: '80%' }} indicatorColor={'white'}>
<SwipeItem style={swipeItemStyle}>1</SwipeItem>
<SwipeItem style={swipeItemStyle}>2</SwipeItem>
<SwipeItem style={swipeItemStyle}>3</SwipeItem>
<SwipeItem style={swipeItemStyle}>4</SwipeItem>
</Swipe>
),
render: ({ block, props }) => {
const { registerRef } = useGlobalProperties()
return (
<Swipe ref={(el) => registerRef(el, block._vid)} {...props}>
{props.images?.map((item) => (
<>
<SwipeItem key={item}>
<img src={item} />
</SwipeItem>
</>
))}
</Swipe>
)
},
props: createFieldProps(),
resize: {
width: true
},
model: {
default: '绑定字段'
}
} as VisualEditorComponent

View File

@ -0,0 +1,54 @@
/*
* @Author:
* @Date: 2021-06-14 00:53:21
* @LastEditTime: 2021-06-14 00:55:55
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\text\fontArr.ts
*/
export const fontArr = [
{ label: '宋体', value: 'SimSun' },
{ label: '黑体', value: 'SimHei' },
{ label: '微软雅黑', value: 'Microsoft Yahei' },
{ label: '微软正黑体', value: 'Microsoft JhengHei' },
{ label: '楷体', value: 'KaiTi' },
{ label: '新宋体', value: 'NSimSun' },
{ label: '仿宋', value: 'FangSong' },
{ label: '苹方', value: 'PingFang SC' },
{ label: '华文黑体', value: 'STHeiti' },
{ label: '华文楷体', value: 'STKaiti' },
{ label: '华文宋体', value: 'STSong' },
{ label: '华文仿宋', value: 'STFangsong' },
{ label: '华文中宋', value: 'STZhongsong' },
{ label: '华文琥珀', value: 'STHupo' },
{ label: '华文新魏', value: 'STXinwei' },
{ label: '华文隶书', value: 'STLiti' },
{ label: '华文行楷', value: 'STXingkai' },
{ label: '冬青黑体简', value: 'Hiragino Sans GB' },
{ label: '兰亭黑-简', value: 'Lantinghei SC' },
{ label: '翩翩体-简', value: 'Hanzipen SC' },
{ label: '手札体-简', value: 'Hannotate SC' },
{ label: '宋体-简', value: 'Songti SC' },
{ label: '娃娃体-简', value: 'Wawati SC' },
{ label: '魏碑-简', value: 'Weibei SC' },
{ label: '行楷-简', value: 'Xingkai SC' },
{ label: '雅痞-简', value: 'Yapi SC' },
{ label: '圆体-简', value: 'Yuanti SC' },
{ label: '幼圆', value: 'YouYuan' },
{ label: '隶书', value: 'LiSu' },
{ label: '华文细黑', value: 'STXihei' },
{ label: '华文楷体', value: 'STKaiti' },
{ label: '华文宋体', value: 'STSong' },
{ label: '华文仿宋', value: 'STFangsong' },
{ label: '华文中宋', value: 'STZhongsong' },
{ label: '华文彩云', value: 'STCaiyun' },
{ label: '华文琥珀', value: 'STHupo' },
{ label: '华文新魏', value: 'STXinwei' },
{ label: '华文隶书', value: 'STLiti' },
{ label: '华文行楷', value: 'STXingkai' },
{ label: '方正舒体', value: 'FZShuTi' },
{ label: '方正姚体', value: 'FZYaoti' },
{ label: '思源黑体', value: 'Source Han Sans CN' },
{ label: '思源宋体', value: 'Source Han Serif SC' },
{ label: '文泉驿微米黑', value: 'WenQuanYi Micro Hei' }
]

View File

@ -1,7 +1,7 @@
/* /*
* @Author: * @Author:
* @Date: 2021-06-01 09:45:21 * @Date: 2021-06-01 09:45:21
* @LastEditTime: 2021-06-12 10:06:33 * @LastEditTime: 2021-06-14 10:17:54
* @LastEditors: * @LastEditors:
* @Description: * @Description:
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\text\index.tsx * @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\text\index.tsx
@ -13,6 +13,7 @@ import {
createEditorSelectProp createEditorSelectProp
} from '@/visual-editor/visual-editor.props' } from '@/visual-editor/visual-editor.props'
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils' import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
import { fontArr } from './fontArr'
export default { export default {
key: 'text', key: 'text',
@ -21,18 +22,19 @@ export default {
preview: () => <span></span>, preview: () => <span></span>,
render: ({ props, block }) => { render: ({ props, block }) => {
const { registerRef } = useGlobalProperties() const { registerRef } = useGlobalProperties()
const Text = () => (
return (
<div <div
ref={(el) => registerRef(el, block._vid)} ref={(el) => registerRef(el, block._vid)}
style={{ color: props.color, fontSize: props.size }} style={{ color: props.color, fontSize: props.size, fontFamily: props.font }}
> >
{props.text || '默认文本'} {props.text || '默认文本'}
</div> </div>
) )
return <Text></Text>
}, },
props: { props: {
text: createEditorInputProp({ label: '显示文本' }), text: createEditorInputProp({ label: '显示文本' }),
font: createEditorSelectProp({ label: '字体设置', options: fontArr }),
color: createEditorColorProp('字体颜色'), color: createEditorColorProp('字体颜色'),
size: createEditorSelectProp({ size: createEditorSelectProp({
label: '字体大小', label: '字体大小',

View File

@ -1,7 +1,7 @@
<!-- <!--
* @Author: 卜启缘 * @Author: 卜启缘
* @Date: 2021-06-01 13:30:22 * @Date: 2021-06-01 13:30:22
* @LastEditTime: 2021-06-12 18:29:28 * @LastEditTime: 2021-06-14 00:21:31
* @LastEditors: 卜启缘 * @LastEditors: 卜启缘
* @Description: 手机模拟器 * @Description: 手机模拟器
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\common\simulator.vue * @FilePath: \vite-vue3-lowcode\src\visual-editor\components\common\simulator.vue

View File

@ -151,7 +151,7 @@ export const useTools = () => {
icon: 'el-icon-position', icon: 'el-icon-position',
onClick: () => { onClick: () => {
localStorage.setItem(localKey, JSON.stringify(jsonData)) localStorage.setItem(localKey, JSON.stringify(jsonData))
window.open(location.href.replace('/#/', '/preview/')) window.open(location.href.replace('/#/', '/preview/#/'))
} }
}, },
{ {

View File

@ -1,7 +1,7 @@
/* /*
* @Author: * @Author:
* @Date: 2021-06-11 18:08:01 * @Date: 2021-06-11 18:08:01
* @LastEditTime: 2021-06-12 22:07:05 * @LastEditTime: 2021-06-13 18:32:53
* @LastEditors: * @LastEditors:
* @Description: * @Description:
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\animate\Animate.tsx * @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\animate\Animate.tsx
@ -92,10 +92,10 @@ export const Animate = defineComponent({
<> <>
{currentBlock.value.animations?.map((item, index) => ( {currentBlock.value.animations?.map((item, index) => (
<ElAlert <ElAlert
onClose={() => delAnimate(index)} key={item.value}
type={'info'} type={'info'}
style={{ marginTop: '12px' }} style={{ marginTop: '12px' }}
key={item.value} onClose={() => delAnimate(index)}
> >
{{ {{
title: () => ( title: () => (
@ -164,9 +164,10 @@ export const Animate = defineComponent({
<div v-show={!state.isAddAnimates}> <div v-show={!state.isAddAnimates}>
<ElButton <ElButton
type={'primary'} type={'primary'}
onClick={() => (state.isAddAnimates = true)} disabled={!currentBlock.value.animations}
plain plain
icon={'el-icon-plus'} icon={'el-icon-plus'}
onClick={() => (state.isAddAnimates = true)}
> >
</ElButton> </ElButton>

View File

@ -1,10 +1,10 @@
/* /*
* @Author: * @Author:
* @Date: 2021-06-10 16:23:06 * @Date: 2021-06-10 16:23:06
* @LastEditTime: 2021-06-10 16:46:36 * @LastEditTime: 2021-06-14 17:22:11
* @LastEditors: * @LastEditors:
* @Description: * @Description:
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\AttrEditor.tsx * @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\AttrEditor.tsx
*/ */
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import { import {
@ -19,7 +19,7 @@ import {
ElPopover ElPopover
} from 'element-plus' } from 'element-plus'
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props' import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props'
import { TablePropEditor } from '../' import { TablePropEditor, CrossSortableOptionsEditor } from '../'
import { useDotProp } from '@/visual-editor/hooks/useDotProp' import { useDotProp } from '@/visual-editor/hooks/useDotProp'
import { useVisualData } from '@/visual-editor/hooks/useVisualData' import { useVisualData } from '@/visual-editor/hooks/useVisualData'
@ -37,6 +37,9 @@ export const AttrEditor = defineComponent({
[VisualEditorPropsType.inputNumber]: () => <ElInputNumber v-model={propObj[prop]} />, [VisualEditorPropsType.inputNumber]: () => <ElInputNumber v-model={propObj[prop]} />,
[VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />, [VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />,
[VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />, [VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />,
[VisualEditorPropsType.crossSortable]: () => (
<CrossSortableOptionsEditor v-model={propObj[prop]} />
),
[VisualEditorPropsType.select]: () => ( [VisualEditorPropsType.select]: () => (
<ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}> <ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}>
{propConfig.options?.map((opt) => ( {propConfig.options?.map((opt) => (
@ -91,20 +94,30 @@ export const AttrEditor = defineComponent({
content.push( content.push(
...Object.entries(component.props || {}).map(([propName, propConfig]) => ( ...Object.entries(component.props || {}).map(([propName, propConfig]) => (
<> <>
<ElFormItem key={currentBlock.value._vid + propName}> <ElFormItem
key={currentBlock.value._vid + propName}
style={
propConfig.labelPosition == 'top'
? {
display: 'flex',
'flex-direction': 'column',
'align-items': 'flex-start'
}
: {}
}
>
{{ {{
label: () => label: () => (
propConfig.tips ? (
<> <>
{propConfig.tips && (
<ElPopover width={200} trigger={'hover'} content={propConfig.tips}> <ElPopover width={200} trigger={'hover'} content={propConfig.tips}>
{{ {{
reference: () => <i class={'el-icon-warning-outline'}></i> reference: () => <i class={'el-icon-warning-outline'}></i>
}} }}
</ElPopover> </ElPopover>
)}
{propConfig.label} {propConfig.label}
</> </>
) : (
propConfig.label
), ),
default: () => renderEditor(propName, propConfig) default: () => renderEditor(propName, propConfig)
}} }}
@ -117,7 +130,7 @@ export const AttrEditor = defineComponent({
} }
return ( return (
<> <>
<ElForm size="mini" label-position="left"> <ElForm size="mini" labelPosition={'left'}>
{content} {content}
</ElForm> </ElForm>
</> </>

View File

@ -0,0 +1,72 @@
/*
* @Author:
* @Date: 2021-06-14 15:00:45
* @LastEditTime: 2021-06-14 17:41:14
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\cross-sortable-options\cross-sortable-options.tsx
*/
import { defineComponent, reactive, computed, PropType } from 'vue'
import Draggable from 'vuedraggable'
import { ElInput } from 'element-plus'
import { useVModel } from '@vueuse/core'
export const CrossSortableOptionsEditor = defineComponent({
props: {
modelValue: {
type: Array as PropType<string[]>,
default: () => []
}
},
setup(props, { emit }) {
const state = reactive({
list: useVModel(props, 'modelValue', emit),
drag: false
})
const dragOptions = computed(() => {
return {
animation: 200,
group: 'description',
disabled: false,
ghostClass: 'ghost'
}
})
return () => (
<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)}
>
{{
item: ({ element, index }) => (
<div class={'flex items-center justify-between'}>
<i class={'el-icon-s-grid handle'}></i>
<ElInput
v-model={state.list[index]}
class={'m-12px'}
style={{ width: '270px' }}
></ElInput>
<div class={'flex flex-col'}>
<i class={'el-icon-circle-plus-outline'} onClick={() => state.list.push('')}></i>
<i class={'el-icon-remove-outline'} onClick={() => state.list.splice(index, 1)}></i>
</div>
</div>
)
}}
</Draggable>
)
}
})

View File

@ -1,10 +1,13 @@
/** /*
* @name: index * @Author:
* @author: * @Date: 2021-06-12 22:18:48
* @date: 2021/5/30 10:57 * @LastEditTime: 2021-06-14 16:58:34
* @descriptionindex * @LastEditors:
* @update: 2021/5/30 10:57 * @Description:
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\index.ts
*/ */
export { TablePropEditor } from './table-prop-editor/table-prop-editor' export { TablePropEditor } from './table-prop-editor/table-prop-editor'
export { AttrEditor } from './attr-editor/AttrEditor' export { AttrEditor } from './attr-editor/AttrEditor'
export { Animate } from './animate/Animate' export { Animate } from './animate/Animate'
export { CrossSortableOptionsEditor } from './cross-sortable-options/cross-sortable-options'

View File

@ -0,0 +1,8 @@
/*
* @Author:
* @Date: 2021-06-13 22:07:29
* @LastEditTime: 2021-06-14 18:18:51
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\page-setting\pageSetting.tsx
*/

View File

@ -1,17 +0,0 @@
/**
* @name: index.common
* @author: 卜启缘
* @date: 2021/5/3 16:05
* @descriptionindex.common
* @update: 2021/5/3 16:05
*/
body {
.el-form-item__label {
font-size: 12px;
}
.el-form-item--mini .el-form-item__content {
display: flex;
justify-content: flex-end;
align-items: center;
}
}

View File

@ -67,6 +67,16 @@ $boxShadow: -2px 0 4px 0 rgb(0 0 0 / 10%);
padding-bottom: 50px; padding-bottom: 50px;
overflow-y: auto; overflow-y: auto;
} }
.el-form-item__label {
font-size: 12px;
}
.el-form-item--mini .el-form-item__content {
display: flex;
justify-content: flex-end;
align-items: center;
}
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* @Author: * @Author:
* @Date: 2021-06-01 13:22:14 * @Date: 2021-06-01 13:22:14
* @LastEditTime: 2021-06-12 19:25:26 * @LastEditTime: 2021-06-13 21:26:49
* @LastEditors: * @LastEditors:
* @Description: * @Description:
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\index.tsx * @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\index.tsx
@ -10,11 +10,7 @@
import { defineComponent, reactive } from 'vue' import { defineComponent, reactive } from 'vue'
import styles from './index.module.scss' import styles from './index.module.scss'
import './index.common.scss'
import { ElTabPane, ElTabs } from 'element-plus' import { ElTabPane, ElTabs } from 'element-plus'
console.log(styles, 'styles')
import MonacoEditor from '../common/monaco-editor/MonacoEditor' import MonacoEditor from '../common/monaco-editor/MonacoEditor'
import { useVisualData } from '@/visual-editor/hooks/useVisualData' import { useVisualData } from '@/visual-editor/hooks/useVisualData'
import { AttrEditor, Animate } from './components' import { AttrEditor, Animate } from './components'

View File

@ -1,5 +1,13 @@
/*
* @Author:
* @Date: 2021-05-04 05:36:58
* @LastEditTime: 2021-06-14 10:02:47
* @LastEditors:
* @Description:
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\simulator-editor\comp-render.tsx
*/
import { defineComponent, PropType } from 'vue' import { defineComponent, PropType } from 'vue'
import { VisualEditorBlockData } from '@/visual-editor/visual-editor.utils' import { VisualEditorBlockData, VisualEditorConfig } from '@/visual-editor/visual-editor.utils'
export default defineComponent({ export default defineComponent({
name: 'CompRender', name: 'CompRender',
@ -9,7 +17,7 @@ export default defineComponent({
default: () => ({}) default: () => ({})
}, },
config: { config: {
type: Object, type: Object as PropType<VisualEditorConfig>,
default: () => ({}) default: () => ({})
} }
}, },

View File

@ -94,9 +94,9 @@ export default defineComponent({
height: 100%; height: 100%;
min-height: 40px; min-height: 40px;
&.isDrag div[data-draggable='true'] { // &.isDrag div[data-draggable='true'] {
padding: 8px 0; // padding: 2px 0;
} // }
&.isDrag:not(.no-child) :deep(.list-group-item.has-slot) { &.isDrag:not(.no-child) :deep(.list-group-item.has-slot) {
@include showContainerBorder; @include showContainerBorder;

View File

@ -1,5 +1,9 @@
<template> <template>
<DraggableTransitionGroup v-model:drag="drag" v-model="currentPage.blocks"> <DraggableTransitionGroup
v-model:drag="drag"
v-model="currentPage.blocks"
style="min-height: 500px"
>
<template #item="{ element: outElement }"> <template #item="{ element: outElement }">
<div <div
class="list-group-item" class="list-group-item"
@ -14,6 +18,7 @@
@mousedown="selectComp(outElement)" @mousedown="selectComp(outElement)"
> >
<comp-render <comp-render
:key="outElement._vid"
:config="visualConfig" :config="visualConfig"
:element="outElement" :element="outElement"
:style="{ :style="{
@ -93,6 +98,7 @@ export default defineComponent({
} }
} }
//
const handleSlotsFocus = (block, _vid) => { const handleSlotsFocus = (block, _vid) => {
const slots = block.props?.slots || {} const slots = block.props?.slots || {}
if (Object.keys(slots).length > 0) { if (Object.keys(slots).length > 0) {
@ -112,6 +118,7 @@ export default defineComponent({
} }
} }
//
const selectComp = (element) => { const selectComp = (element) => {
setCurrentBlock(element) setCurrentBlock(element)
currentPage.value.blocks.forEach((block) => { currentPage.value.blocks.forEach((block) => {
@ -165,7 +172,10 @@ export default defineComponent({
const index = parentBlocks.findIndex((item) => item._vid == block._vid) const index = parentBlocks.findIndex((item) => item._vid == block._vid)
if (index != -1) { if (index != -1) {
delete globalProperties.$$refs[parentBlocks[index]._vid] delete globalProperties.$$refs[parentBlocks[index]._vid]
parentBlocks.splice(index, 1) const delTarget = parentBlocks.splice(index, 1)[0]
if (delTarget.focus) {
setCurrentBlock({} as VisualEditorBlockData)
}
} }
} }
}} }}

View File

@ -5,7 +5,7 @@
v-model:drag="isDrag" v-model:drag="isDrag"
class="inner-draggable" class="inner-draggable"
:class="{ slot: !slotChildren?.length }" :class="{ slot: !slotChildren?.length }"
:data-slot="`插槽(${slotKey}\n 拖拽组件到此处${drag}`" :data-slot="`插槽(${slotKey}\n 拖拽组件到此处`"
> >
<template #item="{ element: innerElement }"> <template #item="{ element: innerElement }">
<div <div

View File

@ -122,6 +122,8 @@ export const initVisualData = (): VisualData => {
state.currentPage = jsonData.pages['/'] state.currentPage = jsonData.pages['/']
router.replace('/') router.replace('/')
} }
const currentFocusBlock = state.currentPage.blocks.find((item) => item.focus)
setCurrentBlock(currentFocusBlock ?? ({} as VisualEditorBlockData))
} }
// 设置当前被操作的组件 // 设置当前被操作的组件

View File

@ -4,13 +4,15 @@ export enum VisualEditorPropsType {
color = 'color', color = 'color',
select = 'select', select = 'select',
table = 'table', table = 'table',
switch = 'switch' switch = 'switch',
crossSortable = 'crossSortable'
} }
export type VisualEditorProps = { export type VisualEditorProps = {
type: VisualEditorPropsType type: VisualEditorPropsType
label: string label: string
tips?: string tips?: string // 表单项提示
labelPosition?: string // 表单域标签的位置
multiple?: boolean multiple?: boolean
defaultValue?: any defaultValue?: any
} & { } & {
@ -150,3 +152,24 @@ export function createEditorTableProp({
defaultValue defaultValue
} }
} }
/*---------------------------------------CrossSortableOptions-------------------------------------------*/
interface EditorCrossSortableProp {
label: string
labelPosition: string
defaultValue?: string[]
}
export function createEditorCrossSortableProp({
label,
labelPosition,
defaultValue
}: EditorCrossSortableProp): VisualEditorProps {
return {
type: VisualEditorPropsType.crossSortable,
label,
labelPosition,
defaultValue
}
}

View File

@ -964,20 +964,20 @@
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.1.1.tgz#2287cfc3dc20e5b20aeb65c2c3a56533bdca801c" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.1.1.tgz#2287cfc3dc20e5b20aeb65c2c3a56533bdca801c"
integrity sha512-g+4pzAw7PYSjARtLBoDq6DmcblX8i9KJHSCnyM5VDDFFifUaUT9iHbFpOF/KOizQ9f7QAqU2JH3Y6aXjzUMhVA== integrity sha512-g+4pzAw7PYSjARtLBoDq6DmcblX8i9KJHSCnyM5VDDFFifUaUT9iHbFpOF/KOizQ9f7QAqU2JH3Y6aXjzUMhVA==
"@vueuse/core@^5.0.2": "@vueuse/core@^5.0.3":
version "5.0.2" version "5.0.3"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-5.0.2.tgz#302389f620c0d4b51fdf157012d9b5b522b605e7" resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-5.0.3.tgz#8f3170e2a51ae62fb1725c84d4cc02a7552aad0b"
integrity sha512-Sp9+7AL4Cg3Tx6I55WoH7zICGRlp6ZUF9NW3EU8SZTkryHm0afAjFfASMwlfV030JFeh45BdqafDOrenVmM9Cw== integrity sha512-TMCL11EVMaj2Y5qdYosvuwA+i1aKrerFXs7fhNZiQiLCWxF8XsrNdxzoiaI2n12UcmSOXvd1xdyWs7Nss+p/Hg==
dependencies: dependencies:
"@vueuse/shared" "5.0.2" "@vueuse/shared" "5.0.3"
vue-demi "*" vue-demi "*"
"@vueuse/integrations@^5.0.2": "@vueuse/integrations@^5.0.3":
version "5.0.2" version "5.0.3"
resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-5.0.2.tgz#95b9f5bea831430f747d7311d9497ec9f52db3bd" resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-5.0.3.tgz#64820965d068b356b4df50ed47a87adf0141f68e"
integrity sha512-jh9Ywz8zu2YmsSd1xeyXkuoA8fhNRkoUkzX4y/hSm2IQC2ydkD8Rk/aOvPAew5RiwndtWhXQYu5XrSMQRsYDYQ== integrity sha512-c7dy7u4XTJeSfq/NnFotoUPrbkPJv/DbWwEYVW9YXXMdDvz1IxDdXUFrOu8ElM0qzGn4CuYg6Yr37uYR13MgXg==
dependencies: dependencies:
"@vueuse/shared" "5.0.2" "@vueuse/shared" "5.0.3"
vue-demi "*" vue-demi "*"
optionalDependencies: optionalDependencies:
axios "^0.21.1" axios "^0.21.1"
@ -987,10 +987,10 @@
qrcode "^1.4.4" qrcode "^1.4.4"
universal-cookie "^4.0.4" universal-cookie "^4.0.4"
"@vueuse/shared@5.0.2": "@vueuse/shared@5.0.3":
version "5.0.2" version "5.0.3"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-5.0.2.tgz#274c2bf163d25eb7fd2fc51f23088a2b7f060594" resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-5.0.3.tgz#31613951d5036459650ad8d47a9185e8950ea3c9"
integrity sha512-S1hRRmEdipjTD4DbXgPdw4ZZYebU/nDi75vNP3Ibpa1irW3NUNUKOT/TWnwRHLQvXquUtdvalhI8D9Db+czZJg== integrity sha512-aY93WPygr8H/4RB8YuOmAD83Y+faq7zwW10Kd9i0kD9zf5ysVP+32j09rF/mZVtGCa0CSM8ambPZMsEhCkRbwQ==
dependencies: dependencies:
vue-demi "*" vue-demi "*"
@ -2324,10 +2324,10 @@ eslint-plugin-prettier@^3.4.0:
dependencies: dependencies:
prettier-linter-helpers "^1.0.0" prettier-linter-helpers "^1.0.0"
eslint-plugin-vue@^7.11.0: eslint-plugin-vue@^7.11.1:
version "7.11.0" version "7.11.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-7.11.0.tgz#c19b098899b7e3cd692beffbbe73611064ef1ea6" resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-7.11.1.tgz#77eb4b44032d5cca79f9af21d06991d8694a314a"
integrity sha512-Qwo8wilqnOXnG9B5auEiTstyaHefyhHd5lEhhxemwXoWsAxIW2yppzuVudowC5n+qn1nMLNV9TANkTthBK7Waw== integrity sha512-lbw3vkEAGqYjqd1HpPFWHXtYaS8mILTJ5KOpJfRxO3Fo7o0wCf1zD7vSOasbm6nTA9xIgvZQ4VcyGIzQXxznHw==
dependencies: dependencies:
eslint-utils "^2.1.0" eslint-utils "^2.1.0"
natural-compare "^1.4.0" natural-compare "^1.4.0"