整体思路:
- 分解-组合,设计数据结构
- vuex 全局状态管理,完成数据流转
设计数据结构
第一步:分解每一个属性,设计数据结构
如上所示:每一个属性和属性值都对应一个键值对,由此设计数据结构 currentElement,在 vuex 中存储 currentElement,其数据结构示例如下:
{
"id": "",
"props": {
"text": "标题",
"fontSize": "30px",
"fontFamily": "",
"fontWeight": "bold",
"fontStyle": "normal",
"textAlign": "center",
"color": "#000000",
"height": "36px",
"width": "125px",
"paddingLeft": "0px",
"paddingRight": "0px",
"paddingTop": "0px",
"paddingBottom": "0px",
"opacity": 0.51,
"left": "97.5px",
"top": "232px",
"right": "0"
},
// 其他需要用到的属性
}
每次选中画布中的某个组件,就更改这个值,面板右侧的属性设置折叠面板根据 currentElement.props 动态渲染,更改属性值就是更新 vuex 中的 currentElement
第二步:组合起来设计数据结构,渲染折叠面板
vuex 中的 currentElement 和 响应式变量 editGroups 生成计算属性 editGroupsNew,被用于渲染折叠面板
<template>
<el-collapse v-model="currentKey" accordion>
<el-collapse-item
v-for="item in editGroupsNew"
:key="item.text"
:name="item.text"
>
<template slot="title">
<!-- 渲染自定义 title -->
</template>
<prop-list :props="item.props" />
</el-collapse-item>
</el-collapse>
</template>
<script>
import PropList from './PropList.vue'
export default {
name: 'Attribute',
components: {
PropList
},
data() {
return {
currentKey: '主体设置',
editGroups: [
{
text: '主体设置',
icon: 'setting-main',
items: ['left', 'top', 'width', 'height', 'borderRadius']
},
{
text: '标题设置',
icon: 'setting-heading',
items: ['title']
}
],
// 响应式变量模拟 vuex 中的数据
currentElement: {
id: '',
props: {
left: '20px',
top: '30px',
width: '200px',
height: '300px',
borderRadius: '4px',
title: '标题'
}
}
}
},
computed: {
// 此计算属性是最终用于渲染的数据
editGroupsNew() {
return this.editGroups.map((item) => {
const props = {}
item.items.forEach((key) => {
props[key] = this.currentElement.props[key]
})
this.$set(item, 'props', props)
return item
})
}
}
}
</script>
第三步:由 propsMap.js 统一管理属性的配置
每一种属性渲染什么样的组件,显示的 lable,以及数据初始化和输出的转换等,由 propsMap.js 配置文件统一管理,示例如下:
// 1 component 确定对应是哪个 component
// 2 更改 value 的 事件名称
// 3 intialTransform 初始值的变换,有些初始值需要处理以后在传递给组件
// 4 afterTransform 触发更改以后,不同类型需要不同处理
// 5 text 属性对应的中文名称
// 6 给组件赋值的时候 属性的名称,一般是 value,也有可能是另外的
const defaultMap = {
component: 'el-input',
eventName: 'input',
valueProp: 'value',
intialTransform: (v) => v,
afterTransform: (e) => e
}
const mapPropsToComponents = {
position: {
...defaultMap,
eventName: 'change',
component: 'numbers-adjuster',
intialTransform: (v) => (v ? v.split(',').map((i) => parseInt(i)) : [0]),
afterTransform: (e) => (e ? e.map((i) => `${i}px`).join(',') : '0px'),
text: '位置',
isInline: false
},
size: {
...defaultMap,
eventName: 'change',
component: 'numbers-adjuster',
intialTransform: (v) => (v ? v.split(',').map((i) => parseInt(i)) : [0]),
afterTransform: (e) => (e ? e.map((i) => `${i}px`).join(',') : '0px'),
text: '尺寸',
isInline: false
},
borderRadius: {
...defaultMap,
eventName: 'change',
component: 'numbers-adjuster',
intialTransform: (v) => (v ? v.split(',').map((i) => parseInt(i)) : [0]),
afterTransform: (e) => (e ? e.map((i) => `${i}px`).join(',') : '0px'),
text: '圆角',
isInline: true
},
title: {
...defaultMap,
isInline: true,
extraProps: {
style: 'width: 150px'
},
text: '标题'
},
visible: {
...defaultMap,
isInline: true,
component: 'el-checkbox',
intialTransform: (v) => v,
afterTransform: (e) => e,
text: '显隐'
},
theme: {
...defaultMap,
isInline: true,
eventName: 'change',
component: 'theme-change',
intialTransform: (v) => v,
afterTransform: (e) => e,
text: '风格'
}
}
export default mapPropsToComponents