feat(manager): 画布楼层加名称标签与悬浮操作条

- 每楼层左上角显示组件名标签
- 选中楼层右侧竖排操作条:上移/下移/复制/删除/隐藏,替代原右上角关闭图标
- 隐藏楼层变暗并显示已隐藏蒙层,hidden 随 pageData 保存
This commit is contained in:
pikachu1995@126.com
2026-10-12 15:59:58 +08:00
parent 654839b247
commit 2f097ace49
2 changed files with 104 additions and 15 deletions

View File

@@ -45,6 +45,7 @@
>
<template #item="{ element, index }">
<div class="list">
<div class="floor-name">{{ element.name || element.type }}</div>
<component
v-if="templates[element.type]"
class="component"
@@ -60,15 +61,24 @@
>
未知组件「{{ element.type }}」已下线,点击删除
</div>
<el-icon
v-if="selected == index"
@click="closeComponent(index)"
class="close"
:size="25"
color="#e1251b"
>
<CircleClose />
</el-icon>
<div v-if="element.hidden" class="floor-hidden-mask">已隐藏</div>
<div v-if="selected == index" class="floor-tools">
<el-icon
:class="{ disabled: index === 0 }"
@click="moveComponent(index, -1)"
><Top
/></el-icon>
<el-icon
:class="{ disabled: index === contentData.list.length - 1 }"
@click="moveComponent(index, 1)"
><Bottom
/></el-icon>
<el-icon @click="copyComponent(index)"><CopyDocument /></el-icon>
<el-icon @click="closeComponent(index)"><Delete /></el-icon>
<el-icon @click="toggleHide(index)"
><View v-if="!element.hidden" /><Hide v-else
/></el-icon>
</div>
</div>
</template>
</draggable>
@@ -88,7 +98,6 @@
</template>
<script>
import {
CircleClose,
Picture,
PictureFilled,
Document,
@@ -110,6 +119,12 @@ import {
Crop,
Minus,
EditPen,
Top,
Bottom,
CopyDocument,
Delete,
View,
Hide,
} from "@element-plus/icons-vue";
import templates from "./template/index";
import Draggable from "vuedraggable";
@@ -121,7 +136,6 @@ export default {
components: {
Draggable,
decorate,
CircleClose,
Picture,
PictureFilled,
Document,
@@ -143,6 +157,12 @@ export default {
Crop,
Minus,
EditPen,
Top,
Bottom,
CopyDocument,
Delete,
View,
Hide,
},
data() {
return {
@@ -268,6 +288,29 @@ export default {
console.warn(`[page-decoration] 未知组件类型: ${element.type}`);
this.closeComponent(index);
},
// 上移/下移楼层
moveComponent(index, dir) {
const target = index + dir;
if (target < 0 || target >= this.contentData.list.length) return;
const list = this.contentData.list;
[list[index], list[target]] = [list[target], list[index]];
this.selected = target;
this.handleComponent(this.contentData.list[target], target);
},
// 复制楼层
copyComponent(index) {
const cloned = JSON.parse(JSON.stringify(this.contentData.list[index]));
const key = Date.now() + "_" + Math.ceil(Math.random() * 99999);
cloned.key = key;
cloned.model = cloned.type + "_" + key;
this.contentData.list.splice(index + 1, 0, cloned);
this.handleComponent(this.contentData.list[index + 1], index + 1);
},
// 隐藏/显示楼层(渲染端跳过 hidden 楼层)
toggleHide(index) {
const item = this.contentData.list[index];
item.hidden = !item.hidden;
},
// 右侧栏回调
handleDrawer(val) {
let newIndex = this.selected;

View File

@@ -124,11 +124,57 @@
.list {
position: relative;
}
.close {
.floor-name {
position: absolute;
right: 5px;
top: 10px;
z-index: 99;
left: 4px;
top: 4px;
z-index: 60;
background: #ffffff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 2px 8px;
font-size: 12px;
color: #606266;
white-space: nowrap;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
pointer-events: none;
}
.floor-tools {
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
z-index: 100;
display: flex;
flex-direction: column;
background: #ffffff;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
padding: 4px 0;
.el-icon {
padding: 6px 8px;
cursor: pointer;
color: #606266;
&:hover {
color: #409eff;
}
&.disabled {
color: #dcdfe6;
cursor: not-allowed;
}
}
}
.floor-hidden-mask {
position: absolute;
inset: 0;
z-index: 50;
background: rgba(0, 0, 0, 0.45);
color: #ffffff;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
}
.unknown-component {
padding: 16px 10px;