Browse Source

add: Sortable.js拖拽页面移动

wzl 1 year ago
parent
commit
8b587f53b1

+ 44 - 33
packages/core/src/index.js

@@ -2775,48 +2775,48 @@ class ComPDFKitViewer {
       const doc = await this.messageHandler.sendWithPromise('CopyDocument', {
       const doc = await this.messageHandler.sendWithPromise('CopyDocument', {
         doc: this.doc,
         doc: this.doc,
         oldPassword: this.#oldPwd
         oldPassword: this.#oldPwd
-      })
-      const pages = await this.getPages(doc)
+      });
+      const pages = await this.getPages(doc);
   
   
-      this.docEditorCopy = { doc, pages }
+      this.docEditorCopy = { doc, pages };
     }
     }
-    const pages = this.docEditorCopy.pages
-    const radio = window.devicePixelRatio || 1
-
+  
+    const pages = this.docEditorCopy.pages;
+    const radio = window.devicePixelRatio || 1;
+  
+    const canvasPool = [];
+    const contextPool = [];
+  
     for (let i = 0; i < (num ?? pages.length); i++) {
     for (let i = 0; i < (num ?? pages.length); i++) {
-      const page = pages[i + index]
       if (this.toolMode !== 'document') {
       if (this.toolMode !== 'document') {
-        break
+        break;
       }
       }
-
-      // const rotation = await this.messageHandler.sendWithPromise('GetPageRotation', page.pagePtr)
-
-      const canvasWidth = Math.round(page.width * radio)
-      const canvasHeight = Math.round(page.height * radio)
-
+  
+      const page = pages[i + index];
+      const canvas = canvasPool.pop() || document.createElement('canvas');
+      const context = contextPool.pop() || canvas.getContext('2d', { alpha: false });
+  
+      const canvasWidth = Math.round(page.width * radio);
+      const canvasHeight = Math.round(page.height * radio);
+  
       const { imageArray } = await this.messageHandler.sendWithPromise('RenderPageBitmap', {
       const { imageArray } = await this.messageHandler.sendWithPromise('RenderPageBitmap', {
         pagePtr: page.pagePtr,
         pagePtr: page.pagePtr,
         x: 0,
         x: 0,
         y: 0,
         y: 0,
         width: canvasWidth,
         width: canvasWidth,
         height: canvasHeight
         height: canvasHeight
-      })
-
-      const canvas = document.createElement('canvas')
-      const context = canvas.getContext('2d', { alpha: false })
-
-      canvas.width = canvasWidth
-      canvas.height = canvasHeight
-
-      const imageData = context.createImageData(canvas.width, canvas.height)
-      const uint8Array = imageArray
-
-      for (let i = 0; i < imageData.data.length; i++) {
-        imageData.data[i] = uint8Array[i]
-      }
-      context.putImageData(imageData, 0, 0)
-      const imageURL = canvas.toDataURL('image/png')
-
+      });
+  
+      canvas.width = canvasWidth;
+      canvas.height = canvasHeight;
+  
+      const imageData = context.createImageData(canvas.width, canvas.height);
+      const imageDataArray = new Uint8ClampedArray(imageArray.buffer);
+  
+      imageData.data.set(imageDataArray);
+      context.putImageData(imageData, 0, 0);
+      const imageURL = canvas.toDataURL('image/jpeg', 0.8);
+  
       const pageData = {
       const pageData = {
         type: 'page',
         type: 'page',
         img: imageURL,
         img: imageURL,
@@ -2825,12 +2825,23 @@ class ComPDFKitViewer {
           height: page.height
           height: page.height
         },
         },
         rotation: 0
         rotation: 0
-      }
+      };
+  
       this.eventBus.dispatch('getDocEditorPages', {
       this.eventBus.dispatch('getDocEditorPages', {
         index: i + index,
         index: i + index,
         pageData
         pageData
-      })
+      });
+  
+      canvas.width = 0;
+      canvas.height = 0;
+      context.clearRect(0, 0, canvasWidth, canvasHeight);
+      canvasPool.push(canvas);
+      contextPool.push(context);
     }
     }
+  
+    canvasPool.forEach(canvas => URL.revokeObjectURL(canvas.toDataURL()));
+    canvasPool.length = 0;
+    contextPool.length = 0;
   }
   }
 
 
   // 页面编辑 - 保存
   // 页面编辑 - 保存

+ 1 - 0
packages/webview/package.json

@@ -17,6 +17,7 @@
     "file-saver": "^2.0.5",
     "file-saver": "^2.0.5",
     "lodash.debounce": "^4.0.8",
     "lodash.debounce": "^4.0.8",
     "pinia": "^2.0.36",
     "pinia": "^2.0.36",
+    "sortablejs": "^1.15.2",
     "vue": "^3.2.41",
     "vue": "^3.2.41",
     "vue-i18n": "9"
     "vue-i18n": "9"
   },
   },

+ 4 - 1
packages/webview/src/components/App/index.vue

@@ -5,7 +5,7 @@
       <LeftPanel />
       <LeftPanel />
       <DocumentContainer />
       <DocumentContainer />
       <CompareDocumentContainer />
       <CompareDocumentContainer />
-      <DocumentEditorContainer v-if="toolMode === 'document'" />
+      <DocumentEditorContainer v-if="toolMode === 'document'" :Sortable="Sortable" />
       <RightPanel />
       <RightPanel />
       <RightPanelPageMode />
       <RightPanelPageMode />
       <StampPanel />
       <StampPanel />
@@ -34,6 +34,9 @@
   import initDocument from '@/helpers/initDocument'
   import initDocument from '@/helpers/initDocument'
   import { ref, computed, provide, getCurrentInstance } from 'vue'
   import { ref, computed, provide, getCurrentInstance } from 'vue'
   import { useViewerStore } from '@/stores/modules/viewer'
   import { useViewerStore } from '@/stores/modules/viewer'
+  import { Sortable, MultiDrag } from 'sortablejs'
+  
+  Sortable.mount(new MultiDrag())
 
 
   const useViewer = useViewerStore()
   const useViewer = useViewerStore()
   const themeMode = computed(() => useViewer.getThemeMode)
   const themeMode = computed(() => useViewer.getThemeMode)

+ 1 - 1
packages/webview/src/components/DocumentContainer/DocumentContainer.vue

@@ -192,7 +192,7 @@ onMounted(async () => {
     toggleButton: document.querySelector('.toggle-button')
     toggleButton: document.querySelector('.toggle-button')
   })
   })
   let initialDoc = getHashParameters('d', '')
   let initialDoc = getHashParameters('d', '')
-  initialDoc = initialDoc ? JSON.parse(initialDoc) : ''
+  initialDoc = initialDoc ? JSON.parse(initialDoc) : './example/Number Pages.pdf'
   initialDoc = Array.isArray(initialDoc) ? initialDoc : [initialDoc]
   initialDoc = Array.isArray(initialDoc) ? initialDoc : [initialDoc]
   const activeTab = useViewer.activeTab || 0
   const activeTab = useViewer.activeTab || 0
   initialDoc = initialDoc[activeTab]
   initialDoc = initialDoc[activeTab]

+ 127 - 18
packages/webview/src/components/DocumentEditorContainer/DocumentEditorContainer.vue

@@ -38,10 +38,19 @@
     </div>
     </div>
 
 
     <!-- 页面展示 -->
     <!-- 页面展示 -->
-    <div class="page-container">
-      <div v-for="(item, index) in pageList" :key="`${item.type} - ${index}`" class="page" :class="{ 'selected': selectedPageList.includes(index) }">
+    <div class="page-container" ref="dragContainer">
+      <div v-for="(item, index) in pageList" :key="`${item.type} - ${index}`" class="page"
+        :class="{
+          'selected': selectedPageList.includes(index),
+          // 'drag-indicate-right': dragToIndex === index && !isDragToLeft,
+          // 'drag-indicate-left': dragToIndex + 1 === index && isDragToLeft,
+          'drag-indicate-right': dragToIndex === index && !isDragToLeft,
+          'drag-indicate-left': dragToIndex + 1 === index && isDragToLeft,
+          'drag-indicate-disable': dragIndicateDisable(index)
+        }"
+      >
         <div class="img-box">
         <div class="img-box">
-          <img v-if="item.img" :src="item.img" draggable="false" @click="selectPage(index)" :style="`transform: rotate(${rotationToAngle(item.rotation)}deg);`" />
+          <img v-if="item.img" :src="item.img" @click="selectPage(index)" :style="`transform: rotate(${rotationToAngle(item.rotation)}deg);`" />
           <img v-else-if="item.type === 'blank'"
           <img v-else-if="item.type === 'blank'"
             :class="{ 'blank-page': item.type === 'blank', 'w-auto': item.size.width <= item.size.height, 'h-auto': item.size.width > item.size.height }"
             :class="{ 'blank-page': item.type === 'blank', 'w-auto': item.size.width <= item.size.height, 'h-auto': item.size.width > item.size.height }"
             :width="item.size.width / ratio"
             :width="item.size.width / ratio"
@@ -67,6 +76,9 @@ import { computed, ref, watch, onMounted, reactive, onUnmounted } from 'vue'
 import { useViewerStore } from '@/stores/modules/viewer'
 import { useViewerStore } from '@/stores/modules/viewer'
 import { useDocumentStore } from '@/stores/modules/document'
 import { useDocumentStore } from '@/stores/modules/document'
 import core from '@/core'
 import core from '@/core'
+import { useMouseInElement } from '@vueuse/core'
+
+const { Sortable } = defineProps(['Sortable'])
 
 
 const useViewer = useViewerStore()
 const useViewer = useViewerStore()
 const useDocument = useDocumentStore()
 const useDocument = useDocumentStore()
@@ -77,8 +89,33 @@ const data = reactive({
 })
 })
 let { pageList, selectedPageList } = data
 let { pageList, selectedPageList } = data
 
 
+const dragContainer = ref(null)
+const dragFromIndex = ref(-2)
+const dragToIndex = ref(-2)
+const isDragToLeft = ref(false)
+
 const ratio = window.devicePixelRatio || 1
 const ratio = window.devicePixelRatio || 1
 
 
+onMounted(() => {
+  Sortable.create(dragContainer.value, {
+    multiDrag: true,
+    selectedClass: 'sortable-selected',
+    handle: '.img-box',
+    dragClass: "sortable-drag",
+    sort: false,
+    forceFallback: true,
+    fallbackTolerance: 20,
+    onStart: dragStart,
+    onEnd: dragEnd
+  })
+
+  core.getDocEditorPages()
+})
+
+onUnmounted(() => {
+  core.removeEvent('getDocEditorPages', getPages)
+})
+
 // 初始化 获取所有页面数量和序号
 // 初始化 获取所有页面数量和序号
 const totalPages = core.getPagesCount()
 const totalPages = core.getPagesCount()
 for (let i = 0; i < totalPages; i++) {
 for (let i = 0; i < totalPages; i++) {
@@ -87,10 +124,6 @@ for (let i = 0; i < totalPages; i++) {
   })
   })
 }
 }
 
 
-onMounted(() => {
-  core.getDocEditorPages()
-})
-
 // 获取单个页面图片
 // 获取单个页面图片
 const getPages = (data) => {
 const getPages = (data) => {
   const { index, pageData } = data
   const { index, pageData } = data
@@ -98,10 +131,6 @@ const getPages = (data) => {
 }
 }
 core.addEvent('getDocEditorPages', getPages)
 core.addEvent('getDocEditorPages', getPages)
 
 
-onUnmounted(() => {
-  core.removeEvent('getDocEditorPages', getPages)
-})
-
 // 打开插入页面选项弹窗
 // 打开插入页面选项弹窗
 const openDeletePageDialog = () => {
 const openDeletePageDialog = () => {
   useViewer.openElement('deletePageDialog')
   useViewer.openElement('deletePageDialog')
@@ -305,9 +334,15 @@ const openMovePageDialog = () => {
 
 
 // 移动页面
 // 移动页面
 const handleMovePage = (targetIndex) => {
 const handleMovePage = (targetIndex) => {
-  if (selectedPageList.length === 1) { // 移动一页
-    const index = selectedPageList[0]
+  if (selectedPageList.length === 1 ||
+    (selectedPageList.length < 2 && dragFromIndex.value >= 0) ||
+    (selectedPageList.length > 1 && dragFromIndex.value >= 0 && !selectedPageList.includes(dragFromIndex.value))
+  ) { // 移动一页
+    const dragSelectedOrNoDrag = (selectedPageList.length === 1 && selectedPageList.includes(dragFromIndex.value)) || (selectedPageList.length === 1 && dragFromIndex.value < 0)
+    const index = dragSelectedOrNoDrag ? selectedPageList[0] : [dragFromIndex.value]
     const tIndex = index > targetIndex ? targetIndex : targetIndex - 1
     const tIndex = index > targetIndex ? targetIndex : targetIndex - 1
+    if (index === tIndex) return
+    
     const [page] = pageList.splice(index, 1)
     const [page] = pageList.splice(index, 1)
     pageList.splice(tIndex, 0, page)
     pageList.splice(tIndex, 0, page)
 
 
@@ -316,13 +351,14 @@ const handleMovePage = (targetIndex) => {
       index,
       index,
       targetIndex: tIndex
       targetIndex: tIndex
     }
     }
+    // console.log(operation)
     core.saveDocumentEdit(operation)
     core.saveDocumentEdit(operation)
     useDocument.setDocEditorOperationList(operation)
     useDocument.setDocEditorOperationList(operation)
-    selectedPageList[0] = tIndex
+    if (dragSelectedOrNoDrag) selectedPageList[0] = tIndex
 
 
   } else { // 移动多页
   } else { // 移动多页
     selectedPageList.sort((a, b) => a - b)
     selectedPageList.sort((a, b) => a - b)
-    
+
     for (let i = 0; i < selectedPageList.length; i++) {
     for (let i = 0; i < selectedPageList.length; i++) {
       let index, tIndex
       let index, tIndex
       if (selectedPageList[i] === targetIndex) {
       if (selectedPageList[i] === targetIndex) {
@@ -353,12 +389,62 @@ const handleMovePage = (targetIndex) => {
         index,
         index,
         targetIndex: tIndex
         targetIndex: tIndex
       }
       }
+      // console.log(operation)
       core.saveDocumentEdit(operation)
       core.saveDocumentEdit(operation)
       useDocument.setDocEditorOperationList(operation)
       useDocument.setDocEditorOperationList(operation)
       selectedPageList[i] = selectedPageList[i] > targetIndex ? tIndex : tIndex - i
       selectedPageList[i] = selectedPageList[i] > targetIndex ? tIndex : tIndex - i
     }
     }
   }
   }
 }
 }
+
+// 拖拽开始
+const dragStart = (e) => {
+  dragFromIndex.value = e.oldIndex
+  dragContainer.value.addEventListener('mousemove', dragging)
+}
+
+// 拖拽中
+const dragging = (e) => {
+  if (selectedPageList.length > 1 && selectedPageList.includes(dragFromIndex.value)) {
+    dragContainer.value.querySelector('.sortable-drag').style.setProperty("--after-content", "'" + selectedPageList.length + "'")
+  }
+
+  let target = e.target
+  const pageDomList = dragContainer.value.children
+
+  for (let i = 0; i < pageDomList.length; i++) {
+    const element = pageDomList[i]
+    if (element.contains(target)) {
+      const domRect = element.getBoundingClientRect().width
+      isDragToLeft.value = e.offsetX < domRect / 2
+      if (isDragToLeft.value) {
+        dragToIndex.value = i - 1
+      } else {
+        dragToIndex.value = i
+      }
+    }
+  }
+}
+
+// 拖拽结束
+const dragEnd = (e) => {
+  dragContainer.value.removeEventListener('mousemove', dragging)
+  if (dragToIndex.value >= -1 && ![dragToIndex.value, dragToIndex.value + 1].includes(dragFromIndex.value) && !(selectedPageList.includes(dragFromIndex.value) && selectedPageList.includes(dragToIndex.value + 1))) {
+    handleMovePage(dragToIndex.value + 1, dragFromIndex.value)
+  }
+  dragFromIndex.value = -2
+  dragToIndex.value = -2
+}
+
+// 拖拽计算是否置灰显示位置样式
+const dragIndicateDisable = (index) => {
+  if (![index - 1, index, index + 1].includes(dragToIndex.value + 1)) return false
+
+  if ([dragToIndex.value, dragToIndex.value + 1].includes(dragFromIndex.value)) return true
+  if (selectedPageList.includes(dragFromIndex.value) && selectedPageList.includes(dragToIndex.value + 1)) return true
+
+  return false
+}
 </script>
 </script>
 
 
 <style lang="scss">
 <style lang="scss">
@@ -462,6 +548,29 @@ const handleMovePage = (targetIndex) => {
       &.selected {
       &.selected {
         background-color: #DDE9FF;
         background-color: #DDE9FF;
       }
       }
+
+      &.drag-indicate-right {
+        border-right: 3px solid var(--c-popup-bg-active);
+      }
+
+      &.drag-indicate-left {
+        border-left: 3px solid var(--c-popup-bg-active);
+      }
+
+      &.drag-indicate-disable {
+        border-color: var(--c-divider);
+      }
+
+      &.sortable-drag::after {
+        content: var(--after-content, '1');
+        position: absolute;
+        top: 10px;
+        left: 10px;
+        padding: 5px 20px;
+        border-radius: 50px;
+        background-color: var(--c-popup-bg-active);
+        color: white;
+      }
     }
     }
   }
   }
 }
 }
@@ -484,9 +593,9 @@ const handleMovePage = (targetIndex) => {
 
 
 @media screen and (min-width: 578px) {
 @media screen and (min-width: 578px) {
   .document-editor-container .tools-container .button {
   .document-editor-container .tools-container .button {
-      &:not(.disabled):hover {
-        background-color: var(--c-header-button-active);
-      }
+    &:not(.disabled):hover {
+      background-color: var(--c-header-button-active);
     }
     }
   }
   }
+}
 </style>
 </style>