0
点赞
收藏
分享

微信扫一扫

【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染

属性更新

  • 属性编辑通过​​store​​ 获取属性值
  • 通过发射事件触发​​commit​​ 修改属性值
  • 支持属性值的转换

【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染_架构

propsMap.ts

import type { TextComponentProps } from './defaultProps';

// 属性转化成表单 哪个属性使用哪个类型的组件去编辑
export interface PropsToForm {
component: string;
subComponent?: string;
extraProps?: { [key: string]: any };
text?: string;
options?: {
text: string;
value: any;
}[];
initalTransform?: (v: any) => any;
afterTransform?: (v: any) => any;
valueProp?: string;
eventName?: string;
}

// 属性列表转化成表单列表
export type PropsToForms = {
[p in keyof TextComponentProps]?: PropsToForm;
};

// 属性转化成表单的映射表 key:属性 value:使用的组件
export const mapPropsToForms: PropsToForms = {
// 比如: text 属性,使用 a-input 这个组件去编辑
text: {
component: 'a-textarea',
extraProps: {
rows: 3,
},
text: '文本',
afterTransform: (e: any) => e.target.value,
},
fontSize: {
text: '字号',
component: 'a-input-number',
initalTransform: (v: string) => parseInt(v),
afterTransform: (e: any) => (e ? `${e}px` : ''),
},
lineHeight: {
text: '行高',
component: 'a-slider',
extraProps: {
min: 0,
max: 3,
step: 0.1,
},
initalTransform: (v: string) => parseFloat(v),
afterTransform: (e: number) => e.toString(),
},
textAlign: {
component: 'a-radio-group',
subComponent: 'a-radio-button',
text: '对齐',
options: [
{
value: 'left',
text: '左',
},
{
value: 'center',
text: '中',
},
{
value: 'right',
text: '右',
},
],
afterTransform: (e: any) => e.target.value,
},
fontFamily: {
component: 'a-select',
subComponent: 'a-select-option',
text: '字体',
options: [
{
value: '',
text: '无',
},
{
value: '"SimSun","STSong',
text: '宋体',
},
{
value: '"SimHei","STHeiti',
text: '黑体',
},
],
afterTransform: (e: any) => e,
},
};

PropsTable.vue

<template>
<div class="props-table">
<div v-for="(item, index) in finalProps"
class="prop-item"
:key="index">
<span class="label">{{ item.text }}</span>
<div class="prop-component">
<!-- 使用 antd 组件库中的组件 -->
<component v-if="item.valueProp"
:[item.valueProp]="item?.value"
v-bind="item?.extraProps"
v-on="item.events"
:is="item?.component">
<template v-if="item.options">
<component :is="item.subComponent"
v-for="(option, key) in item.options"
:key="key"
:value="option.value">
{{ option.text }}
</component>
</template>
</component>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { defineProps, computed, defineEmits } from 'vue';
import { mapPropsToForms } from '../propsMap'
import { reduce } from 'lodash-es'
import type { PartialTextComponentProps } from '../defaultProps'
export interface Props {
props: PartialTextComponentProps;
}
export interface FormProps {
component: string;
subComponent?: string;
value: string;
extraProps?: { [key: string]: any };
text?: string;
options?: {
text: string;
value: any;
}[];
initalTransform?: (v: any) => any;
valueProp: string;
eventName: string;
events: { [key: string]: (e: any) => void };
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'change', data: any): void;
}>()

// 获取属性表单映射列表
const finalProps = computed(() => {
return reduce(props.props, (result, value, key) => {
const newKey = key as keyof PartialTextComponentProps
const item = mapPropsToForms[newKey]
if (item) {
const { valueProp = 'value', eventName = 'change', initalTransform, afterTransform } = item
const newItem: FormProps = {
...item,
value: initalTransform ? initalTransform(value) : value,
valueProp,
eventName,
events: {
[eventName]: (e: any) => {
emit('change', { key, value: afterTransform ? afterTransform(e) : e })
}
}
}
result[newKey] = newItem
}
return result
}, {} as { [key: string]: FormProps })
})
</script>

Editor.vue

// template
<!-- 右侧组件属性编辑 -->
<a-layout-sider width="300"
style="background:#fff"
class="setting-container">
组件属性
<props-table v-if="currentElement"
@change="handleChange"
:props="currentElement?.props"></props-table>
</a-layout-sider>

// script
// 更新组件的属性值事件
const handleChange = (e: any) => {
store.commit('updateComponent', e)
}

editor.ts

mutations: {
// 更新组件属性
updateComponent(state, { key, value }) {
const updatedComponent = state.components.find(
(c) => c.id === state.currentElement,
);
if (updatedComponent) {
updatedComponent.props[key as keyof TextComponentProps] = value;
}
},
},

最终实现如下

【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染_前端_02

优化需求

选择字体的下拉框可以直接显示当前的字体样式

【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染_表单_03

h 函数

​h​​ 函数接收三个参数

  • type: 元素的类型
  • props: 数据对象
  • children: 子节点

使用 ​​h​​ 函数改写

import { h, VNode } from 'vue';
const fontFamilyArr = [
{
value: '"SimSun","STSong',
text: '宋体',
},
{
value: '"SimHei","STHeiti',
text: '黑体',
},
];
const fontFamilyOptions = fontFamilyArr.map((font) => {
return {
value: font.value,
text: h('span', { style: { fontFamily: font.value } }, font.text),
};
});
// 属性转化成表单的映射表 key:属性 value:使用的组件
export const mapPropsToForms: PropsToForms = {
fontFamily: {
component: 'a-select',
subComponent: 'a-select-option',
text: '字体',
options: [
{
value: '',
text: '无',
},
...fontFamilyOptions,
],
afterTransform: (e: any) => e,
},
};

tsx

使用 ​​tsx​​ 改写

const fontFamilyOptions = fontFamilyArr.map((font) => {
return {
value: font.value,
text: (<span style={{ fontFamily: font.text }}>{font.text}</span>) as VNode,
};
});

使用 render 函数实现桥梁

// \src\components\RenderVnode.ts

import { defineComponent } from 'vue';

const RenderVnode = defineComponent({
props: {
vNode: {
type: [Object, String],
required: true,
},
},
render() {
return this.vNode;
},
});
export default RenderVnode;
// \src\components\PropsTable.vue

<!-- 使用 antd 组件库中的组件 -->
<component v-if="item.valueProp"
:[item.valueProp]="item?.value"
v-bind="item?.extraProps"
v-on="item.events"
:is="item?.component">
<template v-if="item.options">
<component :is="item.subComponent"
v-for="(option, key) in item.options"
:key="key"
:value="option.value">
<render-vnode :vNode="option.text"></render-vnode>
</component>
</template>
</component>

最终实现如下

【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染_属性值_04

阶段总结

业务组件

  • 创建编辑器​​vuex store​​ 结构,画布循环展示组件
  • 组件初步实现,使用​​lodash​​ 分离样式属性
  • 添加通用和特殊属性,转换为​​props​​ 类型
  • 抽取重用逻辑,​​style​​ 抽取和点击跳转
  • 左侧组件库点击添加到画布的逻辑

组件属性对应表单组件的展示和更新

  • 获得正在被编辑的元素,通过​​vuex getters​
  • 创建属性和表单组件的对应关系
  • 使用​​propsTable​​ 将传入的属性渲染为对应的表单组件
  • 丰富对应关系字段支持更多自定义配置
  • 使用标准流程更新表单并实时同步单项数据流
  • 使用​​h​​​ 函数以及​​vnode​​ 实现字体下拉框实时显示
举报

相关推荐

Linux 第二十六章

0 条评论