fix: some bug

This commit is contained in:
bqy_fe 2021-05-30 14:31:04 +08:00
parent 25b3d69041
commit 65493e128a
40 changed files with 846 additions and 805 deletions

View File

@ -32,6 +32,27 @@
目前在使用表单时,需要把相关的`表单控件`放到`表单容器`内部,并且需要将`按钮`放到`表单容器`内,
然后再讲`按钮的type`设置为`表单提交按钮`这时候点击提交按钮才会自动收集表单容器内部的所有字段和值
### 快速生成组件属性
```javascript
// 在vant文档中 chrome控制台输入以下代码快速生成组件属性
let propObj = {
string: (config) => `createEditorInputProp(${JSON.stringify(config)})`,
number: (config) => `createEditorInputNumberProp(${JSON.stringify(config)})`,
boolean: (config) => `createEditorSwitchProp(${JSON.stringify(config)})`
}
$$('#props + table tr').reduce((prev, curr) => {
const children = curr.children
const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase())
const value = (propObj[children[2].textContent] ?? propObj['string'])({
label: `'${children[1].textContent}'`
}).replaceAll('"', '')
prev[key] = value
return prev
}, {})
```
## 浏览器支持
本地开发推荐使用`Chrome 80+` 浏览器

View File

@ -20,20 +20,20 @@
"prepare": "husky install"
},
"dependencies": {
"@vant/touch-emulator": "^1.2.0",
"@vant/touch-emulator": "^1.3.0",
"@vueuse/core": "^4.11.1",
"@vueuse/integrations": "^4.11.1",
"axios": "^0.21.1",
"dayjs": "^1.10.4",
"dexie": "^3.0.3",
"element-plus": "^1.0.2-beta.44",
"element-plus": "^1.0.2-beta.45",
"lodash": "^4.17.21",
"monaco-editor": "^0.24.0",
"normalize.css": "^8.0.1",
"nprogress": "^1.0.0-1",
"qrcode": "^1.4.4",
"vant": "^3.0.17",
"vue": "^3.1.0-beta.4",
"vue": "3.0.11",
"vue-router": "^4.0.8",
"vuedraggable": "^4.0.1",
"vuex": "^4.0.1"
@ -41,13 +41,13 @@
"devDependencies": {
"@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4",
"@types/node": "^14.17.1",
"@types/node": "^15.6.1",
"@typescript-eslint/eslint-plugin": "^4.25.0",
"@typescript-eslint/parser": "^4.25.0",
"@vitejs/plugin-legacy": "^1.4.0",
"@vitejs/plugin-vue": "^1.2.2",
"@vitejs/plugin-vue-jsx": "^1.1.4",
"@vue/compiler-sfc": "^3.1.0-beta.4",
"@vue/compiler-sfc": "3.0.11",
"commitizen": "^4.2.4",
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^6.3.0",
@ -56,24 +56,24 @@
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.9.0",
"gh-pages": "^3.1.0",
"gh-pages": "^3.2.0",
"husky": "^6.0.0",
"lint-staged": "^10.5.4",
"lint-staged": "^11.0.0",
"prettier": "^2.3.0",
"pretty-quick": "^3.1.0",
"sass": "1.32.13",
"sass": "1.34.0",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0",
"typescript": "^4.2.4",
"typescript": "^4.3.2",
"vite": "2.3.4",
"vite-plugin-components": "^0.10.2",
"vite-plugin-components": "^0.10.3",
"vite-plugin-style-import": "^0.10.1",
"vite-plugin-windicss": "^0.16.0",
"vite-plugin-windicss": "^0.16.7",
"vue-eslint-parser": "^7.6.0",
"vue-tsc": "^0.1.6",
"windicss": "^2.5.14"
"windicss": "^3.0.12"
},
"repository": {
"type": "git",

View File

@ -3,10 +3,13 @@ import App from './App.vue'
import router from './router'
import '@/plugins/vant'
import { setupVant } from '@/plugins/vant'
const app = createApp(App)
// 安装vant插件
setupVant(app)
app.config.globalProperties.$$refs = {}
// if (import.meta.env.DEV) {

View File

@ -44,26 +44,9 @@ export default defineComponent({
router.replace('/')
}
//
const renderCom = (element) => {
if (Array.isArray(element)) {
return element.map((item) => renderCom(item))
}
const component = visualConfig.componentMap[element.componentKey]
return component.render({
size: {},
props: element.props || {},
block: element,
model: {},
custom: {}
})
}
return {
...toRefs(state),
visualConfig,
renderCom
visualConfig
}
}
})

View File

@ -2,7 +2,7 @@ import { createApp } from 'vue'
import App from './App.vue'
import './plugins/element-plus'
import './plugins/vant'
import { setupVant } from './plugins/vant'
import 'normalize.css'
import 'virtual:windi.css'
@ -13,6 +13,9 @@ import store from './store/'
const app = createApp(App)
// 使用vant插件
setupVant(app)
app.config.globalProperties.$$refs = {}
// if (import.meta.env.DEV) {

View File

@ -37,23 +37,23 @@ export default {
options: [
{
label: '主要按钮',
val: 'primary'
value: 'primary'
},
{
label: '成功按钮',
val: 'success'
value: 'success'
},
{
label: '默认按钮',
val: 'default'
value: 'default'
},
{
label: '警告按钮',
val: 'warning'
value: 'warning'
},
{
label: '危险按钮',
val: 'danger'
value: 'danger'
}
],
defaultValue: 'default'
@ -63,19 +63,19 @@ export default {
options: [
{
label: '大型',
val: 'large'
value: 'large'
},
{
label: '普通',
val: 'normal'
value: 'normal'
},
{
label: '小型',
val: 'small'
value: 'small'
},
{
label: '迷你',
val: 'mini'
value: 'mini'
}
],
defaultValue: 'normal'
@ -83,10 +83,10 @@ export default {
'native-type': createEditorSelectProp({
label: '原生button的type属性',
options: [
{ label: '普通button', val: 'button' },
{ label: '普通button', value: 'button' },
{
label: '表单提交按钮',
val: 'submit'
value: 'submit'
}
],
defaultValue: 'button'
@ -110,11 +110,11 @@ export default {
options: [
{
label: '左侧',
val: 'left'
value: 'left'
},
{
label: '右侧',
val: 'right'
value: 'right'
}
]
}),
@ -128,8 +128,8 @@ export default {
'loading-type': createEditorSelectProp({
label: '加载图标类型',
options: [
{ label: 'circular', val: 'circular' },
{ label: 'spinner', val: 'spinner' }
{ label: 'circular', value: 'circular' },
{ label: 'spinner', value: 'spinner' }
],
defaultValue: 'circular'
})

View File

@ -19,15 +19,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -54,13 +54,13 @@ export default {
modelValue: createEditorSelectProp({
label: '默认值',
options: [
{ label: '萝卜', val: 'radish' },
{ label: '青菜', val: 'greens' }
{ label: '萝卜', value: 'radish' },
{ label: '青菜', value: 'greens' }
],
multiple: true,
defaultValue: []
}),
name: createEditorInputProp({ label: '称,提交表单的标识符', defaultValue: 'checkbox' }),
name: createEditorInputProp({ label: '字段名', defaultValue: 'checkbox' }),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '复选框' }),
options: createEditorTableProp({
label: '默认选项',
@ -82,11 +82,11 @@ export default {
options: [
{
label: '水平',
val: 'horizontal'
value: 'horizontal'
},
{
label: '垂直',
val: 'vertical'
value: 'vertical'
}
],
defaultValue: 'horizontal'

View File

@ -19,15 +19,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -12,6 +12,14 @@ import { reactive } from 'vue'
import { isDate } from '@/visual-editor/utils/is'
import dayjs from 'dayjs'
const dateType = {
'month-day': 'MM-DD',
'year-month': 'YYYY-MM',
date: 'YYYY-MM-DD',
datehour: 'YYYY-MM-DD HH',
datetime: 'YYYY-MM-DD HH:mm:ss'
}
export default {
key: 'datetimePicker',
moduleName: 'baseWidgets',
@ -21,11 +29,12 @@ export default {
const { registerRef } = useGlobalProperties()
const state = reactive({
showPicker: false,
text: ''
text: '',
currentDate: new Date()
})
const onConfirm = (value) => {
const date = isDate(value) ? dayjs(value).format(props.format) : value
const date = isDate(value) ? dayjs(value).format(props.format || dateType[props.type]) : value
props.modelValue = date
state.text = date
state.showPicker = false
@ -56,6 +65,7 @@ export default {
<DatetimePicker
ref={(el) => registerRef(el, block._vid)}
{...props}
v-model={state.currentDate}
onConfirm={onConfirm}
onCancel={() => (state.showPicker = false)}
/>
@ -63,16 +73,12 @@ export default {
</>
)
return (
<>
<PopupPicker />
</>
)
return <PopupPicker />
},
props: {
modelValue: createEditorInputProp({ label: '默认值' }),
name: createEditorInputProp({
label: '称,提交表单的标识符',
label: '字段名',
defaultValue: 'datetimePicker'
}),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '时间选择器' }),
@ -82,31 +88,31 @@ export default {
options: [
{
label: 'date',
val: 'date'
value: 'date'
},
{
label: 'time',
val: 'time'
label: 'datetime',
value: 'datetime'
},
{
label: 'year-month',
val: 'year-month'
value: 'year-month'
},
{
label: 'month-day',
val: 'month-day'
value: 'month-day'
},
{
label: 'datehour',
val: 'datehour'
value: 'datehour'
}
],
defaultValue: 'time'
defaultValue: 'datetime'
}),
format: createEditorInputProp({
label: '选择时间后格式化值',
label: '自定义日期格式化值',
tips: 'YYYY-MM-DD HH:mm:ss',
defaultValue: 'YYYY-MM-DD HH:mm:ss'
defaultValue: ''
}),
cancelButtonText: createEditorInputProp({ label: '取消按钮文字' }),
columnsOrder: createEditorInputProp({

View File

@ -27,9 +27,9 @@ export default {
'content-position': createEditorSelectProp({
label: '文本位置',
options: [
{ label: '左边', val: 'left' },
{ label: '中间', val: 'center' },
{ label: '右边', val: 'right' }
{ label: '左边', value: 'left' },
{ label: '中间', value: 'center' },
{ label: '右边', value: 'right' }
],
defaultValue: 'center'
}),

View File

@ -29,45 +29,45 @@ export default {
label: '图片链接',
defaultValue: 'https://img.yzcdn.cn/vant/cat.jpeg'
}),
width: createEditorInputProp({ label: '宽度,默认单位为 px', defaultValue: 100 }),
height: createEditorInputProp({ label: '高度,默认单位为 px', defaultValue: 100 }),
'error-icon': createEditorInputProp({ label: '失败时提示的图标名称或图片链接' }),
width: createEditorInputProp({ label: '宽度', defaultValue: 100 }),
height: createEditorInputProp({ label: '高度', defaultValue: 100 }),
errorIcon: createEditorInputProp({ label: '失败时提示的图标名称或图片链接' }),
fit: createEditorSelectProp({
label: '图片填充模式',
options: [
{
label: '保持宽高缩放图片,使图片的长边能完全显示出来',
val: 'contain'
value: 'contain'
},
{
label: '保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边',
val: 'cover'
value: 'cover'
},
{
label: '拉伸图片,使图片填满元素',
val: 'fill'
value: 'fill'
},
{
label: '保持图片原有尺寸',
val: 'none'
value: 'none'
},
{
label: '取 none 或 contain 中较小的一个',
val: 'scale-down'
value: 'scale-down'
}
],
defaultValue: 'fill'
}),
'icon-prefix': createEditorInputProp({
iconPrefix: createEditorInputProp({
label: '图标类名前缀',
tips: '图标类名前缀,同 Icon 组件的 class-prefix 属性'
}),
'icon-size': createEditorInputProp({ label: '加载图标和失败图标的大小' }),
'lazy-load': createEditorSwitchProp({
iconSize: createEditorInputProp({ label: '加载图标和失败图标的大小' }),
lazyLoad: createEditorSwitchProp({
label: '是否开启图片懒加载',
tips: '须配合 Lazyload 组件使用'
}),
'loading-icon': createEditorInputProp({ label: '加载时提示的图标名称或图片链接' }),
loadingIcon: createEditorInputProp({ label: '加载时提示的图标名称或图片链接' }),
radius: createEditorInputProp({ label: '圆角大小', tips: '默认单位为 px' }),
round: createEditorSwitchProp({ label: '是否显示为圆形' }),
'show-error': createEditorSwitchProp({ label: '是否展示图片加载失败提示' }),

View File

@ -16,17 +16,17 @@ export const createFieldProps = () => ({
label: '默认值',
defaultValue: ''
}),
name: createEditorInputProp({ label: '称,提交表单的标识符', defaultValue: 'input' }),
name: createEditorInputProp({ label: '字段名', defaultValue: 'input' }),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '输入框' }),
type: createEditorSelectProp({
label: '输入框类型',
options: [
{ label: '文本', val: 'text' },
{ label: '数字', val: 'number' },
{ label: '文本域', val: 'textarea' },
{ label: '密码', val: 'password' },
{ label: '电话', val: 'tel' },
{ label: '小数点', val: 'digit' }
{ label: '文本', value: 'text' },
{ label: '数字', value: 'number' },
{ label: '文本域', value: 'textarea' },
{ label: '密码', value: 'password' },
{ label: '电话', value: 'tel' },
{ label: '小数点', value: 'digit' }
],
defaultValue: 'text'
}),
@ -40,15 +40,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'
@ -66,8 +66,7 @@ export const createFieldProps = () => ({
autosize: createEditorSwitchProp({
label: '自适应内容高度',
defaultValue: false,
tips:
'是否自适应内容高度,只对 textarea 有效,可传入对象,如 { maxHeight: 100, minHeight: 50 }单位为px'
tips: '是否自适应内容高度,只对 textarea 有效,可传入对象,如 { maxHeight: 100, minHeight: 50 }单位为px'
}),
border: createEditorSwitchProp({ label: '是否显示内边框', defaultValue: true }),
center: createEditorSwitchProp({ label: '内容垂直居中' }),
@ -78,8 +77,8 @@ export const createFieldProps = () => ({
'clear-trigger': createEditorSelectProp({
label: '清除图标显示时机',
options: [
{ label: '输入框不为空时展示', val: 'always' },
{ label: '输入框聚焦且不为空时展示', val: 'focus' }
{ label: '输入框不为空时展示', value: 'always' },
{ label: '输入框聚焦且不为空时展示', value: 'focus' }
],
defaultValue: 'always',
tips: '显示清除图标的时机always 表示输入框不为空时展示focus 表示输入框聚焦且不为空时展示'
@ -101,15 +100,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'
@ -119,15 +118,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -19,15 +19,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -39,7 +39,8 @@ export default {
style={{
width: size.width ? `${size.width}px` : null
}}
v-slots={{
>
{{
input: () =>
state.text?.trim() == '' ? (
<span class={'placeholder'}>{props.placeholder}</span>
@ -47,7 +48,7 @@ export default {
state.text
)
}}
/>
</Field>
<Popup v-model={[state.showPicker, 'show', ['modifier']]} position={'bottom'}>
<Picker
ref={(el) => registerRef(el, block._vid)}
@ -60,15 +61,11 @@ export default {
</>
)
return (
<>
<PopupPicker />
</>
)
return <PopupPicker />
},
props: {
modelValue: createEditorInputProp({ label: '默认值' }),
name: createEditorInputProp({ label: '称,提交表单的标识符', defaultValue: 'picker' }),
name: createEditorInputProp({ label: '字段名', defaultValue: 'picker' }),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '选择器' }),
columns: createEditorTableProp({
label: '数据项',

View File

@ -19,15 +19,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -48,7 +48,7 @@ export default {
},
props: {
modelValue: createEditorInputProp({ label: '默认值', defaultValue: '' }),
name: createEditorInputProp({ label: '称,提交表单的标识符', defaultValue: 'radio' }),
name: createEditorInputProp({ label: '字段名', defaultValue: 'radio' }),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '单选框' }),
options: createEditorTableProp({
label: '默认选项',
@ -70,11 +70,11 @@ export default {
options: [
{
label: '水平',
val: 'horizontal'
value: 'horizontal'
},
{
label: '垂直',
val: 'vertical'
value: 'vertical'
}
],
defaultValue: 'horizontal'

View File

@ -19,15 +19,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -44,7 +44,7 @@ export default {
},
props: {
modelValue: createEditorInputNumberProp({ label: '默认值', defaultValue: 0 }),
name: createEditorInputProp({ label: '称,提交表单的标识符', defaultValue: 'rate' }),
name: createEditorInputProp({ label: '字段名', defaultValue: 'rate' }),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '评分' }),
count: createEditorInputNumberProp({ label: '图标总数' }),
size: createEditorInputProp({ label: '图标大小' }),

View File

@ -19,15 +19,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -44,7 +44,7 @@ export default {
},
props: {
modelValue: createEditorInputNumberProp({ label: '默认值', defaultValue: 0 }),
name: createEditorInputProp({ label: '称,提交表单的标识符', defaultValue: 'slider' }),
name: createEditorInputProp({ label: '字段名', defaultValue: 'slider' }),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '滑块' }),
min: createEditorInputNumberProp({ label: '最小值' }),
max: createEditorInputNumberProp({ label: '最大值' }),

View File

@ -19,15 +19,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -43,7 +43,7 @@ export default {
},
props: {
modelValue: createEditorInputNumberProp({ label: '默认值', defaultValue: 0 }),
name: createEditorInputProp({ label: '称,提交表单的标识符', defaultValue: 'stepper' }),
name: createEditorInputProp({ label: '字段名', defaultValue: 'stepper' }),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '步进器' }),
min: createEditorInputNumberProp({ label: '最小值' }),
max: createEditorInputNumberProp({ label: '最大值' }),

View File

@ -19,15 +19,15 @@ export const createFieldProps = () => ({
options: [
{
label: '左对齐',
val: 'left'
value: 'left'
},
{
label: '居中',
val: 'center'
value: 'center'
},
{
label: '右对齐',
val: 'right'
value: 'right'
}
],
defaultValue: 'left'

View File

@ -35,7 +35,7 @@ export default {
},
props: {
modelValue: createEditorInputProp({ label: '默认值', defaultValue: 'false' }),
name: createEditorInputProp({ label: '称,提交表单的标识符', defaultValue: 'switch' }),
name: createEditorInputProp({ label: '字段名', defaultValue: 'switch' }),
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '开关' }),
'active-color': createEditorInputProp({ label: '打开时的背景色' }),
'active-value': createEditorInputProp({ label: '打开时对应的值' }),

View File

@ -19,9 +19,9 @@ export default {
size: createEditorSelectProp({
label: '字体大小',
options: [
{ label: '14px', val: '14px' },
{ label: '18px', val: '18px' },
{ label: '24px', val: '24px' }
{ label: '14px', value: '14px' },
{ label: '18px', value: '18px' },
{ label: '24px', value: '24px' }
]
})
}

View File

@ -0,0 +1,76 @@
/**
* @name: createProps
* @author:
* @date: 2021/5/30 10:50
* @descriptioncreateProps
* @update: 2021/5/30 10:50
*/
import {
createEditorInputProp,
createEditorSelectProp,
createEditorSwitchProp,
createEditorTableProp
} from '@/visual-editor/visual-editor.props'
// 对齐方式
const alignOptions = [
{
label: '左对齐',
value: 'left'
},
{
label: '右对齐',
value: 'right'
},
{
label: '居中对齐',
value: 'center'
}
]
export const compProps = {
'slots.default.children': createEditorTableProp({
label: '表单项',
option: {
options: [
{ label: '显示值', field: 'label' },
{ label: '绑定值', field: 'value' },
{ label: '备注', field: 'comments' }
],
showKey: 'label'
},
defaultValue: []
}),
colon: createEditorSwitchProp({ label: '是否在 label 后面添加冒号' }),
disabled: createEditorSwitchProp({ label: '是否禁用表单中的所有输入框' }),
errorMessageAlign: createEditorSelectProp({
label: '错误提示文案对齐方式',
defaultValue: 'left',
options: alignOptions
}),
inputAlign: createEditorSelectProp({
label: '输入框对齐方式',
defaultValue: 'left',
options: alignOptions
}),
labelAlign: createEditorSelectProp({
label: '表单项 label 对齐方式',
defaultValue: 'left',
options: alignOptions
}),
labelWidth: createEditorInputProp({ label: '表单项 label 宽度默认单位为px' }),
readonly: createEditorSwitchProp({ label: '是否将表单中的所有输入框设置为只读状态' }),
scrollToError: createEditorSwitchProp({
label: '在提交表单且校验不通过时滚动至错误的表单项'
}),
showError: createEditorSwitchProp({ label: '是否在校验不通过时标红输入框' }),
showErrorMessage: createEditorSwitchProp({
label: '是否在校验不通过时在输入框下方展示错误提示'
}),
submitOnEnter: createEditorSwitchProp({ label: '是否在按下回车键时提交表单' }),
validateFirst: createEditorSwitchProp({ label: '是否在某一项校验不通过时停止校验' }),
validateTrigger: createEditorInputProp({
label: '表单校验触发时机,可选值为 onChange、onSubmit详见下表'
})
}

View File

@ -1,8 +1,8 @@
import { Form, Field, Button } from 'vant'
import { renderSlot, getCurrentInstance } from 'vue'
import { createEditorTableProp } from '@/visual-editor/visual-editor.props'
import { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
import { compProps } from './compProps'
export default {
key: 'form',
@ -37,18 +37,5 @@ export default {
height: true,
width: true
},
props: {
'slots.default.children': createEditorTableProp({
label: '表单项',
option: {
options: [
{ label: '显示值', field: 'label' },
{ label: '绑定值', field: 'value' },
{ label: '备注', field: 'comments' }
],
showKey: 'label'
},
defaultValue: []
})
}
props: compProps
} as VisualEditorComponent

View File

@ -85,32 +85,32 @@ export default {
slots: createEditorSelectProp({
label: '列比例',
options: [
{ label: '24', val: createSlots('24') },
{ label: '12:12', val: createSlots('12:12') },
{ label: '6:18', val: createSlots('6:18') },
{ label: '18:6', val: createSlots('18:6') },
{ label: '8:8:8', val: createSlots('8:8:8') },
{ label: '6:12:6', val: createSlots('6:12:6') },
{ label: '6:6:6:6', val: createSlots('6:6:6:6') }
{ label: '24', value: createSlots('24') },
{ label: '12:12', value: createSlots('12:12') },
{ label: '6:18', value: createSlots('6:18') },
{ label: '18:6', value: createSlots('18:6') },
{ label: '8:8:8', value: createSlots('8:8:8') },
{ label: '6:12:6', value: createSlots('6:12:6') },
{ label: '6:6:6:6', value: createSlots('6:6:6:6') }
],
defaultValue: createSlots('12:12')
}),
justify: createEditorSelectProp({
label: '主轴对齐方式',
options: [
{ label: '左对齐', val: 'start' },
{ label: '居中排列', val: 'center' },
{ label: '均匀对齐', val: 'space-around' },
{ label: '两端对齐', val: 'space-between' },
{ label: '右对齐', val: 'end' }
{ label: '左对齐', value: 'start' },
{ label: '居中排列', value: 'center' },
{ label: '均匀对齐', value: 'space-around' },
{ label: '两端对齐', value: 'space-between' },
{ label: '右对齐', value: 'end' }
]
}),
align: createEditorSelectProp({
label: '交叉轴对齐方式',
options: [
{ label: '顶部对齐', val: 'top' },
{ label: '垂直居中', val: 'center' },
{ label: '底部对齐', val: 'bottom' }
{ label: '顶部对齐', value: 'top' },
{ label: '垂直居中', value: 'center' },
{ label: '底部对齐', value: 'bottom' }
]
})
}

View File

@ -1,2 +1,9 @@
import { App } from 'vue'
import '@vant/touch-emulator'
import 'vant/lib/index.css'
import { Lazyload } from 'vant'
export const setupVant = (app: App) => {
app.use(Lazyload)
}

View File

@ -33,7 +33,7 @@
>删除</el-dropdown-item
>
<el-dropdown-item icon="el-icon-link" @click="setDefaultPage(data)"
>设为默认</el-dropdown-item
>设为首页</el-dropdown-item
>
</el-dropdown-menu>
</template>

View File

@ -0,0 +1,8 @@
/**
* @name: index
* @author:
* @date: 2021/5/30 10:57
* @descriptionindex
* @update: 2021/5/30 10:57
*/
export { TablePropEditor } from './table-prop-editor/table-prop-editor'

View File

@ -1,8 +1,8 @@
import { defineComponent, PropType } from 'vue'
import { VisualEditorProps } from '../../../../visual-editor.props'
import { useModel } from '../../../../hooks/useModel'
import { defineComponent, PropType, SetupContext } from 'vue'
import { VisualEditorProps } from '@/visual-editor/visual-editor.props'
import { ElButton, ElTag } from 'element-plus'
import { $$tablePropEditor } from './table-prop-edit.service'
import { useVModel } from '@vueuse/core'
export const TablePropEditor = defineComponent({
props: {
@ -10,11 +10,8 @@ export const TablePropEditor = defineComponent({
propConfig: { type: Object as PropType<VisualEditorProps>, required: true }
},
emits: ['update:modelValue'],
setup(props, ctx) {
const model = useModel(
() => props.modelValue,
(val) => ctx.emit('update:modelValue', val)
)
setup(props, { emit }: SetupContext) {
const model = useVModel(props, 'modelValue', emit)
const onClick = async () => {
const data = await $$tablePropEditor({

View File

@ -22,7 +22,7 @@ import {
ElPopover
} from 'element-plus'
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props'
import { TablePropEditor } from '@/visual-editor/components/right-attribute-panel/components/table-prop-editor/table-prop-editor'
import { TablePropEditor } from './components/'
import { VisualEditorBlockData } from '@/visual-editor/visual-editor.utils'
import MonacoEditor from '../common/monaco-editor/MonacoEditor'
import { useVModel } from '@vueuse/core'
@ -57,7 +57,7 @@ export default defineComponent({
<ElSelect v-model={propObj[prop]} valueKey={'value'} multiple={propConfig.multiple}>
{(() => {
return propConfig.options!.map((opt) => (
<ElOption label={opt.label} value={opt.val} />
<ElOption label={opt.label} value={opt.value} />
))
})()}
</ElSelect>
@ -93,44 +93,40 @@ export default defineComponent({
width={200}
trigger="hover"
content={`你可以利用该组件ID。对该组件进行获取和设置其属性组件可用属性可在控制台输入$$refs.${props.block._vid} 进行查看`}
v-slots={{
>
{{
reference: () => (
<i style={{ marginLeft: '6px' }} class={'el-icon-warning-outline'}></i>
)
}}
></ElPopover>
</ElPopover>
</ElFormItem>
)
if (!!component) {
if (!!component.props) {
content.push(
<>
{Object.entries(component.props || {}).map(([propName, propConfig]) => (
<ElFormItem
key={propName}
v-slots={{
label: () =>
propConfig.tips ? (
<>
<ElPopover
width={200}
trigger={'hover'}
content={propConfig.tips}
v-slots={{
reference: () => <i class={'el-icon-warning-outline'}></i>
}}
></ElPopover>
{propConfig.label}
</>
) : (
propConfig.label
)
}}
>
{renderEditor(propName, propConfig)}
</ElFormItem>
))}
</>
Object.entries(component.props || {}).map(([propName, propConfig]) => (
<ElFormItem
key={props.block._vid + propName}
v-slots={{
label: () =>
propConfig.tips ? (
<>
<ElPopover width={200} trigger={'hover'} content={propConfig.tips}>
{{
reference: () => <i class={'el-icon-warning-outline'}></i>
}}
</ElPopover>
{propConfig.label}
</>
) : (
propConfig.label
)
}}
>
{renderEditor(propName, propConfig)}
</ElFormItem>
))
)
}
}

View File

@ -60,7 +60,6 @@ export default defineComponent({
const { currentPage, visualConfig } = useVisualData()
const state = reactive({
compRefs: [],
drag: false
})
@ -188,37 +187,41 @@ export default defineComponent({
<style lang="scss" scoped>
.list-group-item {
position: relative;
padding: 3px;
cursor: move;
transform: translate(0);
padding: 3px;
&.focus {
content: '';
outline: 2px solid #006eff;
outline-offset: -2px;
}
&.drag::after {
display: none;
}
&.no-child {
content: '';
}
&.focusWithChild {
outline: 2px dashed #b0c1d7;
outline-offset: -2px;
}
&.focusWithChild::before {
content: attr(data-label);
position: absolute;
left: -3px;
top: 0;
transform: translate(-100%, 0);
background-color: #006eff;
color: white;
left: -3px;
padding: 3px;
font-size: 12px;
font-weight: 700;
color: white;
background-color: #006eff;
border-radius: 3px;
content: attr(data-label);
transform: translate(-100%, 0);
}
i {

View File

@ -6,7 +6,7 @@
* @update: 2021/5/6 11:59
*/
import { reactive, inject, readonly, computed, watch, ComputedRef, DeepReadonly } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import {
VisualEditorModelValue,
VisualEditorBlockData,
@ -40,24 +40,28 @@ export interface VisualData {
setCurrentPage: (path: string) => void // 设置当前正在操作的页面
}
export const initVisualData = (): VisualData => {
const jsonData: VisualEditorModelValue = JSON.parse(
sessionStorage.getItem(localKey) as string
) || {
container: {
width: 360,
height: 960
},
pages: {
'/': {
title: '首页',
path: '/',
blocks: []
}
const defaultValue: VisualEditorModelValue = {
container: {
width: 360,
height: 960
},
pages: {
'/': {
title: '首页',
path: '/',
blocks: []
}
}
}
export const initVisualData = (): VisualData => {
const localData = JSON.parse(sessionStorage.getItem(localKey) as string)
const jsonData: VisualEditorModelValue = Object.keys(localData?.pages || {}).length
? localData
: defaultValue
const route = useRoute()
const router = useRouter()
console.log('jsonData', jsonData)
// 所有页面的path都必须以 / 开发
@ -65,8 +69,20 @@ export const initVisualData = (): VisualData => {
const state: IState = reactive({
jsonData,
currentPage: jsonData.pages[route.path] ?? jsonData.pages['/']
currentPage: jsonData.pages[route.path]
})
const paths = Object.keys(jsonData.pages)
const isExistPath = paths.some((path) => route.path == path)
// 当前页面是否存在
if (!isExistPath) {
router.replace(paths[0] || '/')
state.currentPage = jsonData.pages[paths[0]] ?? defaultValue.pages['/']
}
console.log(jsonData.pages, 'jsonData.pages')
console.log(route.path, 'route.path')
console.log(state.currentPage, '哈哈哈')
// 路由变化时更新当前操作的页面
watch(
@ -88,7 +104,7 @@ export const initVisualData = (): VisualData => {
}
// 添加page
const incrementPage = (path = '', page: VisualEditorPage) => {
state.jsonData.pages[getPrefixPath(path)] = page ?? { title: '新页面', path, blocks: [] }
state.jsonData.pages[getPrefixPath(path)] ??= page ?? { title: '新页面', path, blocks: [] }
}
// 删除page
const deletePage = (path = '', redirectPath = '') => {
@ -100,6 +116,10 @@ export const initVisualData = (): VisualData => {
// 设置当前页面
const setCurrentPage = (path = '/') => {
state.currentPage = jsonData.pages[path]
if (!state.currentPage) {
state.currentPage = jsonData.pages['/']
router.replace('/')
}
}
// 更新pages下面的blocks

11
src/visual-editor/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
/**
* @name: index.d
* @author:
* @date: 2021/5/30 10:40
* @descriptionindex.d
* @update: 2021/5/30 10:40
*/
declare type LabelValueOptions = {
label: string
value: any
}[]

View File

@ -18,22 +18,7 @@ export type VisualEditorProps = {
} & {
table?: VisualEditorTableOption
}
// 控制台输入以下代码,快速生成组件属性
// let propObj = {
// string: (config) => `createEditorInputProp(${JSON.stringify(config)})`,
// number: (config) => `createEditorInputNumberProp(${JSON.stringify(config)})`,
// boolean: (config) => `createEditorSwitchProp(${JSON.stringify(config)})`
// }
//
// $$('#props + table tr').reduce((prev, curr) => {
// const children = curr.children
// const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase())
// const value = (propObj[children[2].textContent ?? propObj['string'])({
// label: children[1].textContent
// }).replaceAll('"', '')
// prev[key] = value
// return prev
// }, {})
/*---------------------------------------switch-------------------------------------------*/
interface EditorSwitchProp {
label: string

View File

@ -1,232 +1,232 @@
import { useCommander } from './plugins/command.plugin'
import { VisualEditorBlockData, VisualEditorModelValue } from './visual-editor.utils'
import { cloneDeep } from 'lodash'
export function useVisualCommand({
focusData,
updateBlocks,
dataModel,
dragstart,
dragend
}: {
focusData: { value: { focus: VisualEditorBlockData[]; unFocus: VisualEditorBlockData[] } }
updateBlocks: (blocks?: VisualEditorBlockData[]) => void
dataModel: { value: VisualEditorModelValue }
dragstart: { on: (cb: () => void) => void; off: (cb: () => void) => void }
dragend: { on: (cb: () => void) => void; off: (cb: () => void) => void }
}) {
const commander = useCommander()
/**
*
* @author
* @date 2021/4/22 11:37
*/
commander.registry({
name: 'delete',
keyboard: ['backspace', 'delete', 'ctrl+d'],
execute: () => {
// console.log('执行删除命令')
const data = {
before: dataModel.value.blocks,
after: focusData.value.unFocus
}
return {
redo: () => {
// console.log('重做删除命令')
updateBlocks(cloneDeep(data.after))
},
undo: () => {
// console.log('撤回删除命令')
updateBlocks(cloneDeep(data.before))
}
}
}
})
/**
*
* -
* -
* -
* @author
* @date 2021/4/22 11:38
*/
commander.registry({
name: 'drag',
init() {
this.data = { before: null as null | VisualEditorBlockData[] }
const handler = {
dragstart: () => (this.data.before = cloneDeep(dataModel.value.blocks)),
dragend: () => commander.state.commands.drag()
}
dragstart.on(handler.dragstart)
dragend.on(handler.dragend)
return () => {
dragstart.off(handler.dragstart)
dragend.off(handler.dragend)
}
},
execute() {
const before = cloneDeep(this.data.before)
const after = cloneDeep(dataModel.value.blocks)
return {
redo: () => {
updateBlocks(cloneDeep(after))
},
undo: () => {
updateBlocks(cloneDeep(before))
}
}
}
})
commander.registry({
name: 'clear',
execute: () => {
const data = {
before: cloneDeep(dataModel.value.blocks),
after: cloneDeep([])
}
return {
redo: () => {
updateBlocks(cloneDeep(data.after))
},
undo: () => {
updateBlocks(cloneDeep(data.before))
}
}
}
})
commander.registry({
name: 'placeTop',
keyboard: 'ctrl+up',
execute: () => {
const data = {
before: cloneDeep(dataModel.value.blocks),
after: cloneDeep(
(() => {
const { focus, unFocus } = focusData.value
const maxZIndex =
unFocus.reduce((prev, block) => Math.max(prev, block.zIndex), -Infinity) + 1
focus.forEach((block) => (block.zIndex = maxZIndex))
return cloneDeep(dataModel.value.blocks)
})()
)
}
return {
redo: () => {
updateBlocks(cloneDeep(data.after))
},
undo: () => {
updateBlocks(cloneDeep(data.before))
}
}
}
})
commander.registry({
name: 'placeBottom',
keyboard: 'ctrl+down',
execute: () => {
const data = {
before: cloneDeep(dataModel.value.blocks),
after: cloneDeep(
(() => {
const { focus, unFocus } = focusData.value
let minZIndex =
unFocus.reduce((prev, block) => Math.min(prev, block.zIndex), Infinity) - 1
if (minZIndex < 0) {
const dur = Math.abs(minZIndex)
unFocus.forEach((block) => (block.zIndex += dur))
minZIndex = 0
}
focus.forEach((block) => (block.zIndex = minZIndex))
return cloneDeep(dataModel.value.blocks)
})()
)
}
return {
redo: () => {
updateBlocks(cloneDeep(data.after))
},
undo: () => {
updateBlocks(cloneDeep(data.before))
}
}
}
})
commander.registry({
name: 'updateBlock',
execute: (newBlock: VisualEditorBlockData, oldBlock: VisualEditorBlockData) => {
let blocks = cloneDeep(dataModel.value.blocks || [])
const data = {
before: blocks,
after: (() => {
blocks = [...blocks]
const index = dataModel.value.blocks!.indexOf(oldBlock)
if (index > -1) {
blocks.splice(index, 1, newBlock)
}
return cloneDeep(blocks)
})()
}
return {
redo: () => {
updateBlocks(cloneDeep(data.after))
},
undo: () => {
updateBlocks(cloneDeep(data.before))
}
}
}
})
commander.registry({
name: 'updateModelValue',
execute: (val: VisualEditorModelValue) => {
const data = {
before: cloneDeep(dataModel.value),
after: cloneDeep(val)
}
return {
redo: () => {
dataModel.value = data.after
},
undo: () => {
dataModel.value = data.before
}
}
}
})
commander.registry({
name: 'selectAll',
followQueue: false,
keyboard: 'ctrl+a',
execute: () => {
return {
redo: () => {
;(dataModel.value.blocks || []).forEach((block) => (block.focus = true))
}
}
}
})
commander.init()
return {
undo: () => commander.state.commands.undo(),
redo: () => commander.state.commands.redo(),
delete: () => commander.state.commands.delete(),
clear: () => commander.state.commands.clear(),
placeTop: () => commander.state.commands.placeTop(),
placeBottom: () => commander.state.commands.placeBottom(),
updateBlock: (newBlock: VisualEditorBlockData, oldBlock: VisualEditorBlockData) =>
commander.state.commands.updateBlock(newBlock, oldBlock),
updateModelValue: (val: VisualEditorModelValue) =>
commander.state.commands.updateModelValue(val)
}
}
// import { useCommander } from './plugins/command.plugin'
// import { VisualEditorBlockData, VisualEditorModelValue } from './visual-editor.utils'
// import { cloneDeep } from 'lodash'
//
// export function useVisualCommand({
// focusData,
// updateBlocks,
// dataModel,
// dragstart,
// dragend
// }: {
// focusData: { value: { focus: VisualEditorBlockData[]; unFocus: VisualEditorBlockData[] } }
// updateBlocks: (blocks?: VisualEditorBlockData[]) => void
// dataModel: { value: VisualEditorModelValue }
// dragstart: { on: (cb: () => void) => void; off: (cb: () => void) => void }
// dragend: { on: (cb: () => void) => void; off: (cb: () => void) => void }
// }) {
// const commander = useCommander()
//
// /**
// * 删除命令
// * @author 卜启缘
// * @date 2021/4/22 11:37 下午
// */
// commander.registry({
// name: 'delete',
// keyboard: ['backspace', 'delete', 'ctrl+d'],
// execute: () => {
// // console.log('执行删除命令')
// const data = {
// before: dataModel.value.blocks,
// after: focusData.value.unFocus
// }
// return {
// redo: () => {
// // console.log('重做删除命令')
// updateBlocks(cloneDeep(data.after))
// },
// undo: () => {
// // console.log('撤回删除命令')
// updateBlocks(cloneDeep(data.before))
// }
// }
// }
// })
//
// /**
// * 拖拽命令,适用于三种情况:
// * - 从菜单拖拽组件到容器画布;
// * - 在容器中拖拽组件调整位置
// * - 拖拽调整组件的宽度和高度;
// * @author 卜启缘
// * @date 2021/4/22 11:38 下午
// */
// commander.registry({
// name: 'drag',
// init() {
// this.data = { before: null as null | VisualEditorBlockData[] }
// const handler = {
// dragstart: () => (this.data.before = cloneDeep(dataModel.value.blocks)),
// dragend: () => commander.state.commands.drag()
// }
// dragstart.on(handler.dragstart)
// dragend.on(handler.dragend)
// return () => {
// dragstart.off(handler.dragstart)
// dragend.off(handler.dragend)
// }
// },
// execute() {
// const before = cloneDeep(this.data.before)
// const after = cloneDeep(dataModel.value.blocks)
// return {
// redo: () => {
// updateBlocks(cloneDeep(after))
// },
// undo: () => {
// updateBlocks(cloneDeep(before))
// }
// }
// }
// })
//
// commander.registry({
// name: 'clear',
// execute: () => {
// const data = {
// before: cloneDeep(dataModel.value.blocks),
// after: cloneDeep([])
// }
// return {
// redo: () => {
// updateBlocks(cloneDeep(data.after))
// },
// undo: () => {
// updateBlocks(cloneDeep(data.before))
// }
// }
// }
// })
//
// commander.registry({
// name: 'placeTop',
// keyboard: 'ctrl+up',
// execute: () => {
// const data = {
// before: cloneDeep(dataModel.value.blocks),
// after: cloneDeep(
// (() => {
// const { focus, unFocus } = focusData.value
// const maxZIndex =
// unFocus.reduce((prev, block) => Math.max(prev, block.zIndex), -Infinity) + 1
// focus.forEach((block) => (block.zIndex = maxZIndex))
// return cloneDeep(dataModel.value.blocks)
// })()
// )
// }
// return {
// redo: () => {
// updateBlocks(cloneDeep(data.after))
// },
// undo: () => {
// updateBlocks(cloneDeep(data.before))
// }
// }
// }
// })
//
// commander.registry({
// name: 'placeBottom',
// keyboard: 'ctrl+down',
// execute: () => {
// const data = {
// before: cloneDeep(dataModel.value.blocks),
// after: cloneDeep(
// (() => {
// const { focus, unFocus } = focusData.value
// let minZIndex =
// unFocus.reduce((prev, block) => Math.min(prev, block.zIndex), Infinity) - 1
// if (minZIndex < 0) {
// const dur = Math.abs(minZIndex)
// unFocus.forEach((block) => (block.zIndex += dur))
// minZIndex = 0
// }
// focus.forEach((block) => (block.zIndex = minZIndex))
// return cloneDeep(dataModel.value.blocks)
// })()
// )
// }
// return {
// redo: () => {
// updateBlocks(cloneDeep(data.after))
// },
// undo: () => {
// updateBlocks(cloneDeep(data.before))
// }
// }
// }
// })
//
// commander.registry({
// name: 'updateBlock',
// execute: (newBlock: VisualEditorBlockData, oldBlock: VisualEditorBlockData) => {
// let blocks = cloneDeep(dataModel.value.blocks || [])
// const data = {
// before: blocks,
// after: (() => {
// blocks = [...blocks]
// const index = dataModel.value.blocks!.indexOf(oldBlock)
// if (index > -1) {
// blocks.splice(index, 1, newBlock)
// }
// return cloneDeep(blocks)
// })()
// }
// return {
// redo: () => {
// updateBlocks(cloneDeep(data.after))
// },
// undo: () => {
// updateBlocks(cloneDeep(data.before))
// }
// }
// }
// })
//
// commander.registry({
// name: 'updateModelValue',
// execute: (val: VisualEditorModelValue) => {
// const data = {
// before: cloneDeep(dataModel.value),
// after: cloneDeep(val)
// }
// return {
// redo: () => {
// dataModel.value = data.after
// },
// undo: () => {
// dataModel.value = data.before
// }
// }
// }
// })
//
// commander.registry({
// name: 'selectAll',
// followQueue: false,
// keyboard: 'ctrl+a',
// execute: () => {
// return {
// redo: () => {
// ;(dataModel.value.blocks || []).forEach((block) => (block.focus = true))
// }
// }
// }
// })
//
// commander.init()
//
// return {
// undo: () => commander.state.commands.undo(),
// redo: () => commander.state.commands.redo(),
// delete: () => commander.state.commands.delete(),
// clear: () => commander.state.commands.clear(),
// placeTop: () => commander.state.commands.placeTop(),
// placeBottom: () => commander.state.commands.placeBottom(),
// updateBlock: (newBlock: VisualEditorBlockData, oldBlock: VisualEditorBlockData) =>
// commander.state.commands.updateBlock(newBlock, oldBlock),
// updateModelValue: (val: VisualEditorModelValue) =>
// commander.state.commands.updateModelValue(val)
// }
// }

573
yarn.lock

File diff suppressed because it is too large Load Diff