我们这一部分主要是对最右侧图层面板功能进行剖析,完成对应的功能的开发:
每个图层都对应编辑器上面的元素,有多少个元素就对应多少个图层,主要的功能如下:
- 锁定功能:点击锁定,在编辑器中没法编辑对应的组件属性,再次点击是取消锁定,恢复到可编辑的模式
- 可见化:点击隐藏,在编辑器中消失,再次点击,进行展示
- 最外层图层也是可以进行点击,单击图层就是选中的效果。在编辑器上就是自动选中的效果。
- 图层的文字也可以进行修改,单击图层的文字,会切换到编辑模式,展示成input输入框,可以进行文字的修改。
回车确认,点击esc退出,点击外部区域确定。
- 比较复杂的功能:拖动排序,按住这个按钮拖动以后,可以改变图层的顺序。
图层属性需求分析
图层锁定和隐藏/显示以及选中
图层和编辑器中的元素都是一一对应的,
// editor.ts
export interface EditorProps {// 供中间编辑器渲染的数组components: ComponentData[];// 当前编辑的是哪个元素,uuidcurrentElement: string
}export interface ComponentData {// 这个元素的 属性,属性请详见下面props: Partial<AllComponentProps>;// id,uuid v4 生成id: string;// 业务组件库名称 l-text,l-image 等等name: 'l-text' | 'l-image' | 'l-shape';
}
在editor.ts中,components
其实就是对应的图层,有对应的一些属性ComponentData
,对于不同的状态,我们来添加对应的标识符来添加特定的标识符来表示他的状态即可。
- 在editor.ts的store中的components添加更多的标识符
{
…
isLocked: boolean;
isHidden: boolean;
}
- 点击按钮切换为不同的值,使用这个值在页面上做判断
- 点击选中,设置 currentElement的值
图层名称编辑
- 添加更多属性 - layerName
- 点击图层名称的时候,在input和普通标签之间切换
- 添加按钮响应 - 对于 esc 和 enter 键的响应
- 可能抽象一个通用的 hooks函数 - useKeyPress,可以处理与键盘相关的事件
- 点击到input外部区域的响应
- 可能抽象一个通用的 hooks函数 - useClickOutside
拖动改变顺序
- 最有难度的一个需求,涉及到一个较复杂的交互
- 最终目的其实就是改变store中components数组的顺序
代码实现
// LayerList.vue
<ul :list="list" class="ant-list-items ant-list-border"><li class="ant-list-item" v-for="item in list" :key="item.id"><a-tooltip :title="item.isHidden ? '显示' : '隐藏'"><a-button shape="circle"><template v-slot:icon v-if="item.isHidden"><EyeInvisibleOutlined /></template><template v-slot:icon v-else><EyeOutlined /> </template></a-button></a-tooltip><a-tooltip :title="item.isLocked ? '解锁' : '锁定'"><a-button shape="circle"><template v-slot:icon v-if="item.isLocked"><LockOutlined /></template><template v-slot:icon v-else><UnlockOutlined /> </template></a-button></a-tooltip><span>{{ item.layerName }}</span></li>
</ul>// list的数据来源:在点击左侧组件模板库的时候,会在store中发射一个事件:
// Editor.vue
// 右侧图层设置组件(其中components就是store中的components)
// const components = computed(() => store.state.editor.components);
<layer-list:list="components":selectedId="currentElement && currentElement.id"@change="handleChange"@select="setActive">
</layer-list>
// 点击左侧模板库某个组件触发的事件
const addItem = (component: any) => {store.commit('addComponent', component);
};
// editor.ts
addComponent: setDirtyWrapper((state, component: ComponentData) => {component.layerName = '图层' + (state.components.length + 1);state.components.push(component);}),// 比如点击大标题,在addItem中对应的参数如下:component: {// 通过pageUUid生成的唯一主键id: '3c78b476-7a8d-4ad1-b944-9b163993595d',// 动态需要渲染的组件name: "l-text",props: {actionType: "";backgroundColor: "";borderColor: "#000";borderRadius: "0";borderStyle: "none";borderWidth: "0";boxShadow: "0 0 0 #000000";color: "#000000";fontFamily: "";fontSize: "30px";fontStyle: "normal";fontWeight: "bold";height: "";left: "0";lineHeight: "1";opacity: "1";paddingBottom: "0px";paddingLeft: "0px";paddingRight: "0px";paddingTop: "0px";position: "absolute";right: "0";tag: "h2";text: "大标题";textAlign: "left";textDecoration: "none";top: "0";url: "";width: "100px";}
最开始的样子
进行锁定隐藏操作
// 隐藏
<a-tooltip :title="item.isHidden ? '显示' : '隐藏'"><a-buttonshape="circle"@click.stop="handleChange(item.id, 'isHidden', !item.isHidden)"><template v-slot:icon v-if="item.isHidden"><EyeInvisibleOutlined /></template><template v-slot:icon v-else><EyeOutlined /> </template></a-button>
</a-tooltip>
// 锁定
<a-tooltip :title="item.isLocked ? '解锁' : '锁定'"><a-buttonshape="circle"@click.stop="handleChange(item.id, 'isLocked', !item.isLocked)"><template v-slot:icon v-if="item.isLocked"><LockOutlined /></template><template v-slot:icon v-else><UnlockOutlined /> </template></a-button>
</a-tooltip>const handleChange = (id: string, key: string, value: boolean) => {const data = {id,key,value,isRoot: true,};context.emit("change", data);
};// 最终在子组件中emit chang事件,父组件中触发该方法,
const handleChange = (e: any) => {console.log('event', e);store.commit('updateComponent', e);
};// 对store中的updateComponent进行稍微的改造
// 原来的updateComponent
// 这个主要针对于最右侧面板设置区域中的属性设置进行更新的,改变的是props的值。
updateComponent(state, { key, value }) {const updatedComponent = state.components.find((component) => component.id === state.currentElement); if(updatedComponent) {updatedComponent.props[key as keyof TextComponentProps] = value;}
}
// 现在的
updateComponent(state, { key, value, id, isRoot }) {const updatedComponent = state.components.find((component) => component.id === (id || state.currentElement)); if(updatedComponent) {if(isRoot) {(updatedComponent as any)[key as string] = value;}updatedComponent.props[key as keyof TextComponentProps] = value;}
}
// 增加isRoot主要用来判断改变的是否是props中的某一项的值,我们进行的是展示隐藏,锁定不锁定的功能,所以直接改变key值就行:
export interface ComponentData {// 这个元素的 属性,属性请详见下面props: Partial<AllComponentProps>;// id,uuid v4 生成id: string;// 业务组件库名称 l-text,l-image 等等name: 'l-text' | 'l-image' | 'l-shape';// 图层是否隐藏isHidden?: boolean;// 图层是否锁定isLocked?: boolean;// 图层名称layerName?: string;
}// Editor.vue
// 根据isLocked来判断右侧面板设置区域属性设置是否可以进行编辑
<a-tab-pane key="component" tab="属性设置" class="no-top-radius"><div v-if="currentElement"><edit-groupv-if="!currentElement.isLocked":props="currentElement.props"@change="handleChange"></edit-group><div v-else><a-empty><template #description>该元素已被锁定,无法被编辑</template></a-empty></div></div><pre>{{ currentElement && currentElement.props }}</pre>
</a-tab-pane>// 根据hidden属性来控制中间画布区域是否可以进行显示与隐藏
// EditorWrapper.vue
:class="{ active: active, hidden: hidden }"