fix: some bug

pull/4/head
bqy_fe 4 years ago
parent 25b3d69041
commit 65493e128a
  1. 21
      README.md
  2. 24
      package.json
  3. 5
      preview/main.ts
  4. 19
      preview/views/preview.vue
  5. 5
      src/main.ts
  6. 30
      src/packages/base-widgets/button/index.tsx
  7. 6
      src/packages/base-widgets/checkbox/createFieldProps.ts
  8. 10
      src/packages/base-widgets/checkbox/index.tsx
  9. 6
      src/packages/base-widgets/datetimePicker/createFieldProps.ts
  10. 40
      src/packages/base-widgets/datetimePicker/index.tsx
  11. 6
      src/packages/base-widgets/divider/index.tsx
  12. 24
      src/packages/base-widgets/image/index.tsx
  13. 39
      src/packages/base-widgets/input/createFieldProps.ts
  14. 6
      src/packages/base-widgets/picker/createFieldProps.ts
  15. 13
      src/packages/base-widgets/picker/index.tsx
  16. 6
      src/packages/base-widgets/radio/createFieldProps.ts
  17. 6
      src/packages/base-widgets/radio/index.tsx
  18. 6
      src/packages/base-widgets/rate/createFieldProps.ts
  19. 2
      src/packages/base-widgets/rate/index.tsx
  20. 6
      src/packages/base-widgets/slider/createFieldProps.ts
  21. 2
      src/packages/base-widgets/slider/index.tsx
  22. 6
      src/packages/base-widgets/stepper/createFieldProps.ts
  23. 2
      src/packages/base-widgets/stepper/index.tsx
  24. 6
      src/packages/base-widgets/switch/createFieldProps.ts
  25. 2
      src/packages/base-widgets/switch/index.tsx
  26. 6
      src/packages/base-widgets/text/index.tsx
  27. 76
      src/packages/container-component/form/compProps.ts
  28. 17
      src/packages/container-component/form/index.tsx
  29. 30
      src/packages/container-component/layout/index.tsx
  30. 7
      src/plugins/vant.ts
  31. 2
      src/visual-editor/components/left-aside/components/page-tree/index.vue
  32. 8
      src/visual-editor/components/right-attribute-panel/components/index.ts
  33. 13
      src/visual-editor/components/right-attribute-panel/components/table-prop-editor/table-prop-editor.tsx
  34. 58
      src/visual-editor/components/right-attribute-panel/index.tsx
  35. 17
      src/visual-editor/components/simulator-editor/simulator-editor.vue
  36. 54
      src/visual-editor/hooks/useVisualData.ts
  37. 11
      src/visual-editor/types/index.d.ts
  38. 17
      src/visual-editor/visual-editor.props.tsx
  39. 464
      src/visual-editor/visual.command.tsx
  40. 575
      yarn.lock

@ -32,6 +32,27 @@
目前在使用表单时,需要把相关的`表单控件`放到`表单容器`内部,并且需要将`按钮`放到`表单容器`内, 目前在使用表单时,需要把相关的`表单控件`放到`表单容器`内部,并且需要将`按钮`放到`表单容器`内,
然后再讲`按钮的type`设置为`表单提交按钮`这时候点击提交按钮才会自动收集表单容器内部的所有字段和值 然后再讲`按钮的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+` 浏览器 本地开发推荐使用`Chrome 80+` 浏览器

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

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

@ -44,26 +44,9 @@ export default defineComponent({
router.replace('/') 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 { return {
...toRefs(state), ...toRefs(state),
visualConfig, visualConfig
renderCom
} }
} }
}) })

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -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,详见下表'
})
}

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

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

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

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

@ -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'

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

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

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

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

@ -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
}[]

@ -18,22 +18,7 @@ export type VisualEditorProps = {
} & { } & {
table?: VisualEditorTableOption 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-------------------------------------------*/ /*---------------------------------------switch-------------------------------------------*/
interface EditorSwitchProp { interface EditorSwitchProp {
label: string label: string

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

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save