|
@@ -32,7 +32,7 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="right-canvas-box">
|
|
<div class="right-canvas-box">
|
|
|
- <canvas id="photo-canvas" @dragover.prevent @drop="onCanvasDrop" @click="onCanvasClick" @mousemove="onCanvasMouseMove" @mouseleave="onCanvasMouseLeave"></canvas>
|
|
|
|
|
|
|
+ <canvas id="photo-canvas" @dragover.prevent @drop="onCanvasDrop" @click="onCanvasClick"></canvas>
|
|
|
<!-- 悬浮删除按钮:选中图片后显示在其上方 -->
|
|
<!-- 悬浮删除按钮:选中图片后显示在其上方 -->
|
|
|
<div
|
|
<div
|
|
|
v-if="showDeleteBtn"
|
|
v-if="showDeleteBtn"
|
|
@@ -42,23 +42,7 @@
|
|
|
>
|
|
>
|
|
|
<el-icon><Delete /></el-icon>
|
|
<el-icon><Delete /></el-icon>
|
|
|
</div>
|
|
</div>
|
|
|
- <!-- 悬浮边缘线:鼠标靠近页间或两端边缘时显示 -->
|
|
|
|
|
- <div
|
|
|
|
|
- v-if="showInsertBtn"
|
|
|
|
|
- class="insert-edge-line"
|
|
|
|
|
- :style="{ left: insertEdgeLineStyle.left + 'px', top: insertEdgeLineStyle.top + 'px', height: insertEdgeLineStyle.height + 'px' }"
|
|
|
|
|
- ></div>
|
|
|
|
|
- <!-- 插入页加号:鼠标靠近页间或两端边缘时显示 -->
|
|
|
|
|
- <div
|
|
|
|
|
- v-if="showInsertBtn"
|
|
|
|
|
- class="canvas-insert-btn"
|
|
|
|
|
- :style="{ left: insertBtnPos.x + 'px', top: insertBtnPos.y + 'px' }"
|
|
|
|
|
- @click.stop="insertPageAtTarget"
|
|
|
|
|
- >
|
|
|
|
|
- <el-tooltip content="插入页" placement="top">
|
|
|
|
|
- <el-icon><Plus /></el-icon>
|
|
|
|
|
- </el-tooltip>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+
|
|
|
<!-- 页级操作工具条:选中某一页时显示布局选择和删除整页 -->
|
|
<!-- 页级操作工具条:选中某一页时显示布局选择和删除整页 -->
|
|
|
<div
|
|
<div
|
|
|
v-if="showPageToolbar"
|
|
v-if="showPageToolbar"
|
|
@@ -92,7 +76,7 @@ import { createPhotoControl } from "@/view/gaPhoto/photo/photoControl.js";
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
import { RouteName, router } from "@/router";
|
|
import { RouteName, router } from "@/router";
|
|
|
import { ElMessage } from "element-plus";
|
|
import { ElMessage } from "element-plus";
|
|
|
-import { Delete, ArrowLeft, Plus } from "@element-plus/icons-vue";
|
|
|
|
|
|
|
+import { Delete, ArrowLeft } from "@element-plus/icons-vue";
|
|
|
// 使用 gaPhoto 专用的 Scene(包含 #photo-canvas 尺寸适配等)
|
|
// 使用 gaPhoto 专用的 Scene(包含 #photo-canvas 尺寸适配等)
|
|
|
import Scene from "@/view/gaPhoto/photo/Scene.js";
|
|
import Scene from "@/view/gaPhoto/photo/Scene.js";
|
|
|
const props = defineProps<{
|
|
const props = defineProps<{
|
|
@@ -186,19 +170,22 @@ const createPlaceholder = (text: string) => {
|
|
|
|
|
|
|
|
// paperList:每页一个数组,数组内为图片项(四页,每页两个槽位,共8个)
|
|
// paperList:每页一个数组,数组内为图片项(四页,每页两个槽位,共8个)
|
|
|
// 默认使用占位图和默认说明文字,方便后续删除时进行跨页补位
|
|
// 默认使用占位图和默认说明文字,方便后续删除时进行跨页补位
|
|
|
-const paperList = Array.from({ length: 4 }, (_, pIdx) =>
|
|
|
|
|
- Array.from({ length: 2 }, (_, sIdx) => ({
|
|
|
|
|
|
|
+const paperList = Array.from({ length: 3 }, (_, pIdx) => {
|
|
|
|
|
+ // 默认第一页两张,后两页各一张(配合 pageTypes 演示)
|
|
|
|
|
+ const count = pIdx === 0 ? 2 : 1;
|
|
|
|
|
+ return Array.from({ length: count }, (_, sIdx) => ({
|
|
|
id: pIdx * 2 + sIdx + 1,
|
|
id: pIdx * 2 + sIdx + 1,
|
|
|
imgUrl: createPlaceholder("现场照片"),
|
|
imgUrl: createPlaceholder("现场照片"),
|
|
|
imgInfo: "说明文字",
|
|
imgInfo: "说明文字",
|
|
|
- }))
|
|
|
|
|
-);
|
|
|
|
|
|
|
+ }));
|
|
|
|
|
+});
|
|
|
const renderCanvas = () => {
|
|
const renderCanvas = () => {
|
|
|
const canvas = document.getElementById("photo-canvas");
|
|
const canvas = document.getElementById("photo-canvas");
|
|
|
scene = new Scene(canvas);
|
|
scene = new Scene(canvas);
|
|
|
scene.init();
|
|
scene.init();
|
|
|
window.scene = scene;
|
|
window.scene = scene;
|
|
|
- // 加载布局(type = 1:横排),将占位数据渲染到画布
|
|
|
|
|
|
|
+ // 加载布局:第一页两张(1),第二页横版(3),第三页竖版(2)
|
|
|
|
|
+ pageTypes.value = [1, 3, 2];
|
|
|
scene.load(paperList, pageTypes.value);
|
|
scene.load(paperList, pageTypes.value);
|
|
|
};
|
|
};
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
@@ -314,7 +301,6 @@ const clearSelection = () => {
|
|
|
selectedObj.value = null
|
|
selectedObj.value = null
|
|
|
selectedSlotId.value = null
|
|
selectedSlotId.value = null
|
|
|
showDeleteBtn.value = false
|
|
showDeleteBtn.value = false
|
|
|
- showInsertBtn.value = false
|
|
|
|
|
// 隐藏所有边框
|
|
// 隐藏所有边框
|
|
|
scene?.boxManager?.imgList?.forEach((img: any) => {
|
|
scene?.boxManager?.imgList?.forEach((img: any) => {
|
|
|
img.touchLines?.children?.forEach((line: any) => (line.visible = false))
|
|
img.touchLines?.children?.forEach((line: any) => (line.visible = false))
|
|
@@ -455,120 +441,7 @@ const deleteSlotContentById = (slotId: number) => {
|
|
|
scene.load(paperList, pageTypes.value)
|
|
scene.load(paperList, pageTypes.value)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// ===== 插入页(边缘/页间悬停出现加号) =====
|
|
|
|
|
-const showInsertBtn = ref(false)
|
|
|
|
|
-const insertBtnPos = ref({ x: 0, y: 0 })
|
|
|
|
|
-const insertTargetIndex = ref<number | null>(null)
|
|
|
|
|
-const insertEdgeLineStyle = ref({ left: 0, top: 0, height: 0 })
|
|
|
|
|
|
|
|
|
|
-const onCanvasMouseLeave = () => {
|
|
|
|
|
- showInsertBtn.value = false
|
|
|
|
|
- // 隐藏页边缘高亮
|
|
|
|
|
- scene?.boxManager?.pageEdgeList?.forEach((group: any) => {
|
|
|
|
|
- group?.children?.forEach((line: any) => (line.visible = false))
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const onCanvasMouseMove = (e: MouseEvent) => {
|
|
|
|
|
- if (!scene || !scene.orthCamera || !scene.boxManager?.model?.children?.length) return
|
|
|
|
|
-
|
|
|
|
|
- const canvas = scene.domElement
|
|
|
|
|
- const rect = canvas.getBoundingClientRect()
|
|
|
|
|
- const ndcX = ((e.clientX - rect.left) / canvas.clientWidth) * 2 - 1
|
|
|
|
|
-
|
|
|
|
|
- // 构建可插入的边界集合:左边缘、页间中点、右边缘
|
|
|
|
|
- const pages: any[] = scene.boxManager.model.children || []
|
|
|
|
|
- const edges: { x:number; insertIndex:number; neighborIdx:number }[] = []
|
|
|
|
|
- if (pages.length) {
|
|
|
|
|
- const first = pages[0]
|
|
|
|
|
- edges.push({ x: first.position.x - (first.width ?? 2) / 2, insertIndex: 0, neighborIdx: 0 })
|
|
|
|
|
- for (let i = 0; i < pages.length - 1; i++) {
|
|
|
|
|
- const mid = (pages[i].position.x + pages[i + 1].position.x) / 2
|
|
|
|
|
- edges.push({ x: mid, insertIndex: i + 1, neighborIdx: i })
|
|
|
|
|
- }
|
|
|
|
|
- const last = pages[pages.length - 1]
|
|
|
|
|
- edges.push({ x: last.position.x + (last.width ?? 2) / 2, insertIndex: pages.length, neighborIdx: pages.length - 1 })
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 将边界x投影到NDC,挑选与鼠标最近且在阈值内的一个
|
|
|
|
|
- const threshold = 0.03
|
|
|
|
|
- let bestEdge: { x:number; insertIndex:number; neighborIdx:number } | null = null
|
|
|
|
|
- let bestDx = Infinity
|
|
|
|
|
- for (const edge of edges) {
|
|
|
|
|
- const v = new THREE.Vector3(edge.x, 0, 0)
|
|
|
|
|
- v.project(scene.orthCamera)
|
|
|
|
|
- const dx = Math.abs(v.x - ndcX)
|
|
|
|
|
- if (dx < threshold && dx < bestDx) {
|
|
|
|
|
- bestEdge = edge
|
|
|
|
|
- bestDx = dx
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (!bestEdge) {
|
|
|
|
|
- showInsertBtn.value = false
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- // 进入插入态,恢复图片UI:隐藏删除按钮、页工具条
|
|
|
|
|
- showDeleteBtn.value = false
|
|
|
|
|
- showPageToolbar.value = false
|
|
|
|
|
-
|
|
|
|
|
- // 计算加号的屏幕位置:使用邻近页的顶部中心y/z,替换x为边界x
|
|
|
|
|
- const neighbor = pages[bestEdge.neighborIdx]
|
|
|
|
|
- const bbox = new THREE.Box3().setFromObject(neighbor)
|
|
|
|
|
- const center = bbox.getCenter(new THREE.Vector3())
|
|
|
|
|
- const size = bbox.getSize(new THREE.Vector3())
|
|
|
|
|
- const topCenter = center.clone()
|
|
|
|
|
- topCenter.z = center.z - size.z / 2 - 0.1
|
|
|
|
|
- topCenter.x = bestEdge.x
|
|
|
|
|
- topCenter.project(scene.orthCamera)
|
|
|
|
|
- const containerEl = document.querySelector('.right-canvas-box') as HTMLElement
|
|
|
|
|
- const canvasRect = scene.domElement.getBoundingClientRect()
|
|
|
|
|
- const containerRect = containerEl?.getBoundingClientRect() || { left: 0, top: 0, width: canvasRect.width, height: canvasRect.height }
|
|
|
|
|
- const px = ((topCenter.x + 1) / 2) * canvasRect.width + (canvasRect.left - containerRect.left)
|
|
|
|
|
- insertTargetIndex.value = bestEdge.insertIndex
|
|
|
|
|
- showInsertBtn.value = true
|
|
|
|
|
- // 计算垂直边缘线的屏幕位置与高度(对齐邻近页的顶部、底部)
|
|
|
|
|
- const bottomCenter = center.clone()
|
|
|
|
|
- bottomCenter.z = center.z + size.z / 2 + 0.1
|
|
|
|
|
- bottomCenter.x = bestEdge.x
|
|
|
|
|
- bottomCenter.project(scene.orthCamera)
|
|
|
|
|
- const topPx = ((topCenter.y + 1) / 2) * canvasRect.height + (canvasRect.top - containerRect.top)
|
|
|
|
|
- const bottomPx = ((bottomCenter.y + 1) / 2) * canvasRect.height + (canvasRect.top - containerRect.top)
|
|
|
|
|
- insertEdgeLineStyle.value = { left: Math.round(px), top: Math.round(topPx), height: Math.max(0, Math.round(bottomPx - topPx)) }
|
|
|
|
|
- const plusSize = 32
|
|
|
|
|
- const gap = 8
|
|
|
|
|
- const py = Math.round(topPx - plusSize - gap)
|
|
|
|
|
- insertBtnPos.value = { x: Math.round(px), y: py }
|
|
|
|
|
- // 边缘附近页的边框轻微高亮(左右各一页,首尾仅一页)
|
|
|
|
|
- const leftIdx = Math.max(0, Math.min(pages.length - 1, bestEdge.insertIndex - 1))
|
|
|
|
|
- const rightIdx = Math.max(0, Math.min(pages.length - 1, bestEdge.insertIndex))
|
|
|
|
|
- scene?.boxManager?.pageEdgeList?.forEach((group: any, i: number) => {
|
|
|
|
|
- const show = bestEdge.insertIndex === 0 ? i === rightIdx
|
|
|
|
|
- : bestEdge.insertIndex === pages.length ? i === leftIdx
|
|
|
|
|
- : (i === leftIdx || i === rightIdx)
|
|
|
|
|
- group?.children?.forEach((line: any) => (line.visible = !!show))
|
|
|
|
|
- })
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-const insertPageAtTarget = () => {
|
|
|
|
|
- if (insertTargetIndex.value == null) return
|
|
|
|
|
- const idx = insertTargetIndex.value
|
|
|
|
|
- const newPage = [
|
|
|
|
|
- { id: 0, imgUrl: createPlaceholder('现场照片'), imgInfo: '说明文字' },
|
|
|
|
|
- { id: 0, imgUrl: createPlaceholder('现场照片'), imgInfo: '说明文字' },
|
|
|
|
|
- ]
|
|
|
|
|
- paperList.splice(idx, 0, newPage)
|
|
|
|
|
- pageTypes.value.splice(idx, 0, 1)
|
|
|
|
|
- // 重新编排 id,保证唯一且顺序
|
|
|
|
|
- let idCounter = 1
|
|
|
|
|
- for (let pi = 0; pi < paperList.length; pi++) {
|
|
|
|
|
- for (let si = 0; si < paperList[pi].length; si++) {
|
|
|
|
|
- paperList[pi][si].id = idCounter++
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- scene.load(paperList, pageTypes.value)
|
|
|
|
|
- showInsertBtn.value = false
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
@@ -668,9 +541,8 @@ const insertPageAtTarget = () => {
|
|
|
.right-canvas-box{
|
|
.right-canvas-box{
|
|
|
position: relative;
|
|
position: relative;
|
|
|
width: calc(100% - 510px);
|
|
width: calc(100% - 510px);
|
|
|
- height: calc(100vh - 208px);
|
|
|
|
|
|
|
+ height: calc(100vh - 108px);
|
|
|
background: #f5f5f5;
|
|
background: #f5f5f5;
|
|
|
- padding: 64px 0 48px 64px;
|
|
|
|
|
// 让画布占满容器,便于 three 使用实际尺寸渲染
|
|
// 让画布占满容器,便于 three 使用实际尺寸渲染
|
|
|
#photo-canvas{
|
|
#photo-canvas{
|
|
|
width: 100%;
|
|
width: 100%;
|
|
@@ -703,28 +575,7 @@ const insertPageAtTarget = () => {
|
|
|
box-shadow: 0 6px 18px rgba(0,0,0,0.12);
|
|
box-shadow: 0 6px 18px rgba(0,0,0,0.12);
|
|
|
z-index: 12;
|
|
z-index: 12;
|
|
|
}
|
|
}
|
|
|
- .canvas-insert-btn{
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- width: 32px;
|
|
|
|
|
- height: 32px;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- background: #ffffff;
|
|
|
|
|
- box-shadow: 0 6px 18px rgba(0,0,0,0.12);
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- z-index: 12;
|
|
|
|
|
- color: #606266;
|
|
|
|
|
- border: 1px solid #e5e7eb;
|
|
|
|
|
- }
|
|
|
|
|
- .insert-edge-line{
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- width: 2px;
|
|
|
|
|
- background: var(--el-color-primary);
|
|
|
|
|
- box-shadow: 0 0 0 1px rgba(43,122,247,0.12);
|
|
|
|
|
- z-index: 11;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
.toolbar-divider{
|
|
.toolbar-divider{
|
|
|
width: 1px;
|
|
width: 1px;
|
|
|
height: 18px;
|
|
height: 18px;
|