属性设置面板数据结构设计和状态管理方案

160 阅读2分钟

整体思路:

  1. 分解-组合,设计数据结构
  2. 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