Browse Source

新增横版一张

wangfumin 1 month ago
parent
commit
69a3225c7f

+ 13 - 162
src/view/gaPhoto/index.vue

@@ -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;

+ 6 - 0
src/view/gaPhoto/photo/box/BoxManager.js

@@ -1,6 +1,7 @@
 import * as THREE from "three";
 import * as THREE from "three";
 import HorizontalBox from "./HorizontalBox";
 import HorizontalBox from "./HorizontalBox";
 import VerticalBox from "./VerticalBox";
 import VerticalBox from "./VerticalBox";
+import SingleHorizontalBox from "./SingleHorizontalBox";
 import SimpleLabel from "./object/SimpleLabel";
 import SimpleLabel from "./object/SimpleLabel";
 
 
 // import { Line2 } from "three/examples/jsm/lines/Line2.js";
 // import { Line2 } from "three/examples/jsm/lines/Line2.js";
@@ -43,6 +44,11 @@ export default class BoxManager {
         // console.log("竖排");
         // console.log("竖排");
         this.model.add(box);
         this.model.add(box);
       }
       }
+      if (pageType === 3) {
+        //单张横排
+        const box = new SingleHorizontalBox(this, item, index, total);
+        this.model.add(box);
+      }
     });
     });
     // this.model.position.y += 0.3;
     // this.model.position.y += 0.3;
     // this.model.visible =false;
     // this.model.visible =false;

+ 1 - 1
src/view/gaPhoto/photo/box/HorizontalBox.js

@@ -46,7 +46,7 @@ export default class HorizontalBox extends THREE.Group {
     this.manager.pageList.push(box);
     this.manager.pageList.push(box);
     // 页面之间贴合无间隔:按页面宽度等距排列,并以总宽居中
     // 页面之间贴合无间隔:按页面宽度等距排列,并以总宽居中
     // centerX = (index - (total - 1) / 2) * width
     // centerX = (index - (total - 1) / 2) * width
-    this.position.x = (index - (this.total - 2.2) / 2) * (this.width + 0.005 );
+    this.position.x = (index - (this.total - 1) / 2) * this.width;
 
 
     const matLine = new LineMaterial({
     const matLine = new LineMaterial({
       color: 0xe44d54,
       color: 0xe44d54,

+ 97 - 0
src/view/gaPhoto/photo/box/SingleHorizontalBox.js

@@ -0,0 +1,97 @@
+import * as THREE from "three";
+import TextLabel from "./object/TextLabel";
+import ImgLabelBox from "./object/ImgLabelBox";
+import SimpleLabel from "./object/SimpleLabel";
+import TouchEdge from "./object/TouchEdge";
+import { LineMaterial } from "three/examples/jsm/lines/LineMaterial.js";
+
+export default class SingleHorizontalBox extends THREE.Group {
+  constructor(manager, data, index, total) {
+    super();
+    this.manager = manager;
+    this.total = total;
+    this.name = "single_horizontal_box";
+    this.getStyle();
+    this.load(data, index);
+  }
+  getStyle() {
+    this.width = 2;
+    this.height = (2 * 710) / 500;
+    this.color = 0xffffff;
+  }
+
+  load(data, index) {
+    //box
+    const geometry = new THREE.PlaneGeometry(1, 1);
+    geometry.rotateX(-Math.PI / 2);
+
+    const bm = new THREE.MeshBasicMaterial({
+      color: this.color,
+    });
+
+    const box = new THREE.Mesh(geometry, bm);
+    box.scale.set(this.width, 1, this.height);
+
+    this.add(box);
+    // 记录页平面用于页级选择(raycast)
+    box.userData = { pageIndex: index };
+    this.manager.pageList.push(box);
+    // 页面之间贴合无间隔:按页面宽度等距排列,并以总宽居中
+    this.position.x = (index - (this.total - 1) / 2) * this.width;
+
+    const matLine = new LineMaterial({
+      color: 0xe44d54,
+      linewidth: 4, // in world units with size attenuation, pixels otherwise
+      dashed: false,
+      alphaToCoverage: true,
+    });
+    matLine.resolution = new THREE.Vector2(
+      this.manager.scene.width,
+      this.manager.scene.height
+    );
+    // 页级选中高亮边框
+    const halfW = this.width / 2;
+    const halfH = this.height / 2;
+    const p = [
+      [-halfW, 0, -halfH, halfW, 0, -halfH],
+      [-halfW, 0, -halfH, -halfW, 0, halfH],
+      [-halfW, 0, halfH, halfW, 0, halfH],
+      [halfW, 0, halfH, halfW, 0, -halfH],
+    ];
+    const pageLines = new TouchEdge(p, matLine);
+    this.add(pageLines);
+    this.manager.pageEdgeList.push(pageLines);
+    //content
+    data.forEach((i, j) => {
+      //img
+      let img;
+      this.manager.loader.load(i.imgUrl, (texture) => {
+        texture.matrixAutoUpdate = false;
+        texture.colorSpace = THREE.SRGBColorSpace;
+        // 使用自定义尺寸,宽度 1.5,高度 1.125 (4:3)
+        img = new ImgLabelBox(texture, matLine, { width: 1.5, height: 1.125 });
+
+        img.userData = i.id;
+        img.position.y += 1;
+        // 居中,微调 Z 轴
+        img.position.z -= 0.2;
+        this.add(img);
+        this.manager.imgList.push(img);
+        const textlabel = new TextLabel(i.imgInfo, true);
+        this.add(textlabel);
+        textlabel.position.copy(img.position);
+        // 文字在图片下方,根据高度调整
+        // 图片高度 1.125,半高 0.5625,文字下移一点
+        textlabel.position.z += textlabel.scale.z * 0.5 + 0.35; // 0.5625 + padding
+      });
+    });
+
+    //页脚
+    const f_txt_label = ` 第 ${index + 1} 页`;
+    const footlabel = new SimpleLabel(f_txt_label, true);
+    footlabel.renderOrder = 100;
+    footlabel.position.z += 1.26;
+
+    this.add(footlabel);
+  }
+}

+ 12 - 1
src/view/gaPhoto/photo/box/object/ImgLabelBox.js

@@ -46,7 +46,18 @@ function makeTriangleBlurShader(iterations = 10) {
 export default class ImgLabel extends THREE.Mesh {
 export default class ImgLabel extends THREE.Mesh {
   constructor(texture, matLine, isHorizontal = true) {
   constructor(texture, matLine, isHorizontal = true) {
     let width, height, p;
     let width, height, p;
-    if (isHorizontal) {
+    if (typeof isHorizontal === 'object') {
+      width = isHorizontal.width;
+      height = isHorizontal.height;
+      const halfW = width / 2;
+      const halfH = height / 2;
+      p = [
+        [-halfW, 0, -halfH, halfW, 0, -halfH],
+        [-halfW, 0, -halfH, -halfW, 0, halfH],
+        [-halfW, 0, halfH, halfW, 0, halfH],
+        [halfW, 0, halfH, halfW, 0, -halfH],
+      ];
+    } else if (isHorizontal) {
       width = 1.5;
       width = 1.5;
       height = 0.85;
       height = 0.85;
       p = [
       p = [