|
@@ -0,0 +1,761 @@
|
|
|
+<template>
|
|
|
+ <div class="document-editor-container" ref="docEditorContainer">
|
|
|
+ <!-- 编辑工具 -->
|
|
|
+ <div class="tools-container">
|
|
|
+ <Button class="with-text" @click="openInsertPageDialog">
|
|
|
+ <InsertPage />
|
|
|
+ <span>{{ $t('documentEditor.insert') }}</span>
|
|
|
+ </Button>
|
|
|
+ <Button class="with-text" :class="{ disabled: !selectedPageList.length || selectedPageList.length == pageList.length }" @click="openDeletePageDialog">
|
|
|
+ <DeletePage />
|
|
|
+ <span>{{ $t('documentEditor.delete') }}</span>
|
|
|
+ </Button>
|
|
|
+ <Button class="with-text" :class="{ disabled: !selectedPageList.length }" @click="rotatePage(1)">
|
|
|
+ <RotatePageRight />
|
|
|
+ <span>{{ $t('documentEditor.rotateRight') }}</span>
|
|
|
+ </Button>
|
|
|
+ <Button class="with-text" :class="{ disabled: !selectedPageList.length }" @click="rotatePage(-1)">
|
|
|
+ <RotatePageLeft />
|
|
|
+ <span>{{ $t('documentEditor.rotateLeft') }}</span>
|
|
|
+ </Button>
|
|
|
+ <Button class="with-text" :class="{ disabled: !selectedPageList.length }" @click="copyPage">
|
|
|
+ <CopyPage />
|
|
|
+ <span>{{ $t('documentEditor.copy') }}</span>
|
|
|
+ </Button>
|
|
|
+ <Button class="with-text" :class="{ disabled: !selectedPageList.length }" @click="openExtractPageDialog">
|
|
|
+ <ExtractPage />
|
|
|
+ <span>{{ $t('documentEditor.extract') }}</span>
|
|
|
+ </Button>
|
|
|
+ <Button class="with-text" :class="{ disabled: !selectedPageList.length }" @click="replacePage">
|
|
|
+ <ReplacePage />
|
|
|
+ <span>{{ $t('documentEditor.replace') }}</span>
|
|
|
+ </Button>
|
|
|
+ <Button class="with-text" :class="{ disabled: !selectedPageList.length }" @click="openMovePageDialog">
|
|
|
+ <MovePage />
|
|
|
+ <span>{{ $t('documentEditor.move') }}</span>
|
|
|
+ </Button>
|
|
|
+ <Button class="select-all" @click="selectAll">
|
|
|
+ <UnselectAll v-if="selectedPageList.length === pageList.length" />
|
|
|
+ <SelectAll v-else />
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 页面展示 -->
|
|
|
+ <div class="page-container" ref="dragContainer" :style="{ 'margin-left': marginLeft + 'px' }">
|
|
|
+ <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-disable': dragFromIndex >= 0 && dragToIndex >= -1 && dragIndicateDisable(index),
|
|
|
+ 'has-hover': !isMobileDevice && dragFromIndex < 0 && !selectedPageList.includes(index)
|
|
|
+ }"
|
|
|
+ ref="imgBoxEl"
|
|
|
+ >
|
|
|
+ <div class="img-box" @click="selectPage(index)">
|
|
|
+ <img v-if="item.img" :src="item.img" :style="`transform: rotate(${rotationToAngle(item.rotation)}deg);`" />
|
|
|
+ <div v-else-if="item.type === 'blank'"
|
|
|
+ :class="{ 'blank-page': item.type === 'blank' }"
|
|
|
+ :style="{ 'transform': 'rotate(' + rotationToAngle(item.rotation) + 'deg)', 'width': blankPageScaleSize(item.size).width + 'px', 'height': blankPageScaleSize(item.size).height + 'px' }">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <p>{{ index + 1 }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ <InsertPageSettingDialog @insertPage="handleInsertPage" :selectedPageIndex="selectedPageList.length === 1 ? selectedPageList[0] + 1 : -1" :totalPages="pageList.length" />
|
|
|
+ <DeletePageDialog @deletePage="handleDeletePage" :selectedPageList="selectedPageList" />
|
|
|
+ <ExtractPageSettingDialog @extractPage="handleExtractPage" :totalPages="pageList.length" :selectedPageList="selectedPageList" />
|
|
|
+ <MovePageSettingDialog @movePage="handleMovePage" :totalPages="pageList.length" :selectedPageList="selectedPageList" />
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, onMounted, reactive, onUnmounted, computed } from 'vue'
|
|
|
+import { useViewerStore } from '@/stores/modules/viewer'
|
|
|
+import { useDocumentStore } from '@/stores/modules/document'
|
|
|
+import core from '@/core'
|
|
|
+import { isMobileDevice } from '@/helpers/device'
|
|
|
+
|
|
|
+const { Sortable } = defineProps(['Sortable'])
|
|
|
+
|
|
|
+const useViewer = useViewerStore()
|
|
|
+const useDocument = useDocumentStore()
|
|
|
+
|
|
|
+const data = reactive({
|
|
|
+ pageList: [], // 展示的所有页面
|
|
|
+ selectedPageList: [], // 选中的页面index列表
|
|
|
+})
|
|
|
+let { pageList, selectedPageList } = data
|
|
|
+
|
|
|
+const dragContainer = ref(null)
|
|
|
+const dragFromIndex = ref(-2)
|
|
|
+const dragToIndex = ref(-2)
|
|
|
+const isDragToLeft = ref(false)
|
|
|
+const imgBoxEl = ref(null)
|
|
|
+const marginLeft = ref(0)
|
|
|
+const docEditorContainer = ref(null)
|
|
|
+
|
|
|
+const ratio = window.devicePixelRatio || 1
|
|
|
+const isMobileWidth = window.innerWidth < 768
|
|
|
+let timer = null
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ Sortable.create(dragContainer.value, {
|
|
|
+ multiDrag: true,
|
|
|
+ selectedClass: 'sortable-selected',
|
|
|
+ // handle: '.img-box',
|
|
|
+ dragClass: "sortable-drag",
|
|
|
+ sort: false,
|
|
|
+ forceFallback: true,
|
|
|
+ fallbackTolerance: isMobileDevice ? 0 : 20,
|
|
|
+ onStart: dragStart,
|
|
|
+ onEnd: dragEnd,
|
|
|
+ delay: isMobileDevice ? 200 : 0
|
|
|
+ })
|
|
|
+
|
|
|
+ const { width, height } = imgBoxEl.value[0].getBoundingClientRect()
|
|
|
+ const pageSize = {
|
|
|
+ width,
|
|
|
+ height
|
|
|
+ }
|
|
|
+ core.getDocEditorPages(pageSize)
|
|
|
+
|
|
|
+ window.addEventListener('resize', updateMarginLeft)
|
|
|
+ updateMarginLeft()
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ core.removeEvent('getDocEditorPages', getPages)
|
|
|
+ window.addEventListener('resize', updateMarginLeft)
|
|
|
+})
|
|
|
+
|
|
|
+// 初始化 获取所有页面数量和序号
|
|
|
+const totalPages = core.getPagesCount()
|
|
|
+for (let i = 0; i < totalPages; i++) {
|
|
|
+ pageList.push({
|
|
|
+ type: 'page'
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 获取单个页面图片
|
|
|
+const getPages = (data) => {
|
|
|
+ const { index, pageData } = data
|
|
|
+ pageList[index] = pageData
|
|
|
+}
|
|
|
+core.addEvent('getDocEditorPages', getPages)
|
|
|
+
|
|
|
+// 打开插入页面选项弹窗
|
|
|
+const openDeletePageDialog = () => {
|
|
|
+ useViewer.openElement('deletePageDialog')
|
|
|
+}
|
|
|
+
|
|
|
+// 插入页面
|
|
|
+const handleInsertPage = async (data) => {
|
|
|
+ const operation = {
|
|
|
+ type: 'insert',
|
|
|
+ pageIndex: data.place
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data.type === 'blank') {
|
|
|
+ // 添加数据到用于展示的pageList
|
|
|
+ const adjacentPage = data.targetPlace ? pageList[data.place - 1] : (pageList[data.place] || pageList[data.place - 1])
|
|
|
+ const pageListData = {
|
|
|
+ type: data.type,
|
|
|
+ rotation: 0,
|
|
|
+ size: data.size || {
|
|
|
+ width: adjacentPage.size.width,
|
|
|
+ height: adjacentPage.size.height
|
|
|
+ }
|
|
|
+ }
|
|
|
+ pageList.splice(data.place, 0, pageListData)
|
|
|
+
|
|
|
+ // 要传到core处理的数据
|
|
|
+ operation.size = pageListData.size
|
|
|
+ core.saveDocumentEdit(operation)
|
|
|
+
|
|
|
+ } else if (data.type === 'pdf') {
|
|
|
+ operation.file = data.file
|
|
|
+ operation.range = data.range
|
|
|
+ operation.password = data.password || ''
|
|
|
+
|
|
|
+ const { width, height } = imgBoxEl.value[0].getBoundingClientRect()
|
|
|
+
|
|
|
+ const totalPages = await core.saveDocumentEdit(operation)
|
|
|
+ const oldLenth = pageList.length
|
|
|
+ for (let i = 0; i < totalPages - oldLenth; i++) {
|
|
|
+ pageList.splice(data.place + i, 0, { type: 'page' })
|
|
|
+ }
|
|
|
+
|
|
|
+ const pageSize = {
|
|
|
+ width,
|
|
|
+ height
|
|
|
+ }
|
|
|
+ core.getDocEditorPages(pageSize, data.place, totalPages - oldLenth)
|
|
|
+ }
|
|
|
+ useDocument.setDocEditorOperationList(operation)
|
|
|
+ selectedPageList.length = 0
|
|
|
+}
|
|
|
+
|
|
|
+// 计算空白页的显示大小
|
|
|
+const blankPageScaleSize = (size) => {
|
|
|
+ const { width, height } = size
|
|
|
+ const boxWidth = isMobileWidth ? 140 : 204
|
|
|
+ const boxHeight = isMobileWidth ? 141 : 216
|
|
|
+
|
|
|
+ const scaleWidth = width / ratio
|
|
|
+ const scaleHeight = height / ratio
|
|
|
+
|
|
|
+ const widthRatio = boxWidth / scaleWidth
|
|
|
+ const heightRatio = boxHeight / scaleHeight
|
|
|
+
|
|
|
+ let scaleRatio = Math.min(widthRatio, heightRatio)
|
|
|
+
|
|
|
+ if (scaleRatio > 1) {
|
|
|
+ scaleRatio = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ const scaledWidth = scaleWidth * scaleRatio
|
|
|
+ const scaledHeight = scaleHeight * scaleRatio
|
|
|
+
|
|
|
+ return { width: scaledWidth, height: scaledHeight}
|
|
|
+}
|
|
|
+
|
|
|
+// 选中页面
|
|
|
+const selectPage = (index) => {
|
|
|
+ const i = selectedPageList.indexOf(index)
|
|
|
+ i === -1 ? selectedPageList.push(index) : selectedPageList.splice(i, 1)
|
|
|
+}
|
|
|
+
|
|
|
+// 全选
|
|
|
+const selectAll = () => {
|
|
|
+ if (selectedPageList.length < pageList.length) {
|
|
|
+ selectedPageList.length = 0
|
|
|
+ for (let i = 0; i < pageList.length; i++) {
|
|
|
+ selectedPageList.push(i)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ selectedPageList.length = 0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 打开删除页面弹窗
|
|
|
+const openInsertPageDialog = () => {
|
|
|
+ useViewer.openElement('insertPageSettingDialog')
|
|
|
+}
|
|
|
+
|
|
|
+// 删除页面
|
|
|
+const handleDeletePage = () => {
|
|
|
+ selectedPageList.sort((a, b) => b - a)
|
|
|
+ selectedPageList.forEach(index => {
|
|
|
+ pageList.splice(index, 1)
|
|
|
+ })
|
|
|
+
|
|
|
+ const operation = {
|
|
|
+ type: 'delete',
|
|
|
+ selectedPageIndexArray: Array.from(selectedPageList)
|
|
|
+ }
|
|
|
+ core.saveDocumentEdit(operation)
|
|
|
+ useDocument.setDocEditorOperationList(operation)
|
|
|
+ selectedPageList.length = 0
|
|
|
+}
|
|
|
+
|
|
|
+// 旋转页面
|
|
|
+const rotatePage = (rotation) => {
|
|
|
+ selectedPageList.forEach(index => {
|
|
|
+ const page = pageList[index]
|
|
|
+ page.rotation += rotation
|
|
|
+ if (page.rotation > 3 || page.rotation < -3) page.rotation = 0
|
|
|
+ })
|
|
|
+
|
|
|
+ const operation = {
|
|
|
+ type: 'rotate',
|
|
|
+ selectedPageIndexArray: Array.from(selectedPageList),
|
|
|
+ rotation
|
|
|
+ }
|
|
|
+ core.saveDocumentEdit(operation)
|
|
|
+ useDocument.setDocEditorOperationList(operation)
|
|
|
+}
|
|
|
+// 旋转角度值(为:-3,-2,-1,0,1,2,3)(0是0度,1是90度,2是180度,3是270度)
|
|
|
+const rotationToAngle = (rotation) => {
|
|
|
+ const map = new Map([[-3, -270], [-2, 180], [-1, -90], [0, 0], [1, 90], [2, 180], [3, 270]])
|
|
|
+ return map.get(rotation)
|
|
|
+}
|
|
|
+
|
|
|
+// 复制页面
|
|
|
+const copyPage = () => {
|
|
|
+ selectedPageList.sort((a, b) => b - a)
|
|
|
+ selectedPageList.forEach(index => {
|
|
|
+ pageList.splice(index, 0, { ...pageList[index] })
|
|
|
+ })
|
|
|
+
|
|
|
+ const operation = {
|
|
|
+ type: 'copy',
|
|
|
+ selectedPageIndexArray: Array.from(selectedPageList)
|
|
|
+ }
|
|
|
+ core.saveDocumentEdit(operation)
|
|
|
+ useDocument.setDocEditorOperationList(operation)
|
|
|
+ selectedPageList.length = 0
|
|
|
+}
|
|
|
+
|
|
|
+// 替换页面
|
|
|
+const replacePage = () => {
|
|
|
+ const fileInput = document.createElement('input')
|
|
|
+ fileInput.type = 'file'
|
|
|
+ fileInput.accept = '.pdf'
|
|
|
+ fileInput.click()
|
|
|
+
|
|
|
+ fileInput.addEventListener('change', async function(event) {
|
|
|
+ const file = event.target.files[0]
|
|
|
+ const startIndex = selectedPageList[0]
|
|
|
+
|
|
|
+ let url = URL.createObjectURL(file)
|
|
|
+ const pass = await core.checkPassword(url, true)
|
|
|
+ if (pass === false) return
|
|
|
+
|
|
|
+ handleInsertPage({
|
|
|
+ type: 'pdf',
|
|
|
+ file,
|
|
|
+ range: 'all',
|
|
|
+ place: startIndex,
|
|
|
+ password: pass
|
|
|
+ })
|
|
|
+ handleDeletePage()
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 打开提取页面弹窗
|
|
|
+const openExtractPageDialog = () => {
|
|
|
+ useViewer.openElement('extractPageSettingDialog')
|
|
|
+}
|
|
|
+
|
|
|
+// 提取页面
|
|
|
+const handleExtractPage = async (data) => {
|
|
|
+ let exportRange = data.range.replace(/\s/g, '')
|
|
|
+ const pagesCount = pageList.length
|
|
|
+ if (data.range === 'all') {
|
|
|
+ exportRange = '1-' + pagesCount
|
|
|
+ // exportRange = ''
|
|
|
+ } else if (data.range === 'odd') {
|
|
|
+ exportRange = Array.from({ length: pagesCount }, (_, i) => i + 1).filter(num => num % 2 !== 0).join(",")
|
|
|
+ } else if (data.range === 'even') {
|
|
|
+ exportRange = Array.from({ length: pagesCount }, (_, i) => i + 1).filter(num => num % 2 === 0).join(",")
|
|
|
+ }
|
|
|
+
|
|
|
+ let parts = exportRange.split(',')
|
|
|
+ let indexArray = []
|
|
|
+
|
|
|
+ parts.forEach(part => {
|
|
|
+ if (part.includes('-')) {
|
|
|
+ let [start, end] = part.split('-').map(num => parseInt(num))
|
|
|
+ for (let i = start; i <= end; i++) {
|
|
|
+ indexArray.push(i - 1)
|
|
|
+ }
|
|
|
+ } else if (part) {
|
|
|
+ indexArray.push(parseInt(part) - 1)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const operation = {
|
|
|
+ type: 'extract',
|
|
|
+ range: data.separateFile ? indexArray : exportRange,
|
|
|
+ separateFile: data.separateFile
|
|
|
+ }
|
|
|
+ await core.saveDocumentEdit(operation)
|
|
|
+ useDocument.setDocEditorOperationList(operation)
|
|
|
+
|
|
|
+ if (data.deleteAfter) {
|
|
|
+ indexArray.reverse().forEach(index => {
|
|
|
+ pageList.splice(index, 1)
|
|
|
+ })
|
|
|
+
|
|
|
+ const operation = {
|
|
|
+ type: 'delete',
|
|
|
+ selectedPageIndexArray: indexArray
|
|
|
+ }
|
|
|
+ core.saveDocumentEdit(operation)
|
|
|
+ useDocument.setDocEditorOperationList(operation)
|
|
|
+ selectedPageList.length = 0
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 打开移动页面弹窗
|
|
|
+const openMovePageDialog = () => {
|
|
|
+ useViewer.openElement('movePageSettingDialog')
|
|
|
+}
|
|
|
+
|
|
|
+// 移动页面
|
|
|
+const handleMovePage = (targetIndex) => {
|
|
|
+ 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
|
|
|
+ if (index === tIndex) return
|
|
|
+
|
|
|
+ const [page] = pageList.splice(index, 1)
|
|
|
+ pageList.splice(tIndex, 0, page)
|
|
|
+
|
|
|
+ const operation = {
|
|
|
+ type: 'move',
|
|
|
+ pageIndex: index,
|
|
|
+ targetPageIndex: tIndex
|
|
|
+ }
|
|
|
+ core.saveDocumentEdit(operation)
|
|
|
+ useDocument.setDocEditorOperationList(operation)
|
|
|
+ if (dragSelectedOrNoDrag) selectedPageList[0] = tIndex
|
|
|
+
|
|
|
+ } else { // 移动多页
|
|
|
+ selectedPageList.sort((a, b) => a - b)
|
|
|
+
|
|
|
+ for (let i = 0; i < selectedPageList.length; i++) {
|
|
|
+ let index, tIndex
|
|
|
+ if (selectedPageList[i] === targetIndex) {
|
|
|
+ index = selectedPageList[i]
|
|
|
+ tIndex = targetIndex
|
|
|
+ } else if (selectedPageList[i] > targetIndex) {
|
|
|
+ index = selectedPageList[i]
|
|
|
+ if (selectedPageList.includes(targetIndex)) {
|
|
|
+ tIndex = targetIndex
|
|
|
+ for (let j = 0; j < selectedPageList.length; j++) {
|
|
|
+ if (selectedPageList[j] === tIndex && index !== tIndex) tIndex++
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ tIndex = targetIndex
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ index = selectedPageList[i] - i
|
|
|
+ tIndex = targetIndex - 1
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedPageList.includes(index) && selectedPageList.includes(tIndex) && index === tIndex) continue
|
|
|
+
|
|
|
+ const [page] = pageList.splice(index, 1)
|
|
|
+ pageList.splice(tIndex, 0, page)
|
|
|
+
|
|
|
+ const operation = {
|
|
|
+ type: 'move',
|
|
|
+ pageIndex: index,
|
|
|
+ targetPageIndex: tIndex
|
|
|
+ }
|
|
|
+ core.saveDocumentEdit(operation)
|
|
|
+ useDocument.setDocEditorOperationList(operation)
|
|
|
+ selectedPageList[i] = selectedPageList[i] > targetIndex ? tIndex : tIndex - i
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 拖拽开始
|
|
|
+const dragStart = (e) => {
|
|
|
+ dragFromIndex.value = e.oldIndex
|
|
|
+ dragContainer.value.addEventListener(isMobileDevice ? 'touchmove' : 'mousemove', dragging)
|
|
|
+
|
|
|
+ if (isMobileDevice) {
|
|
|
+ document.body.style.overscrollBehavior = 'none'
|
|
|
+ document.body.style.userSelect = 'none'
|
|
|
+ document.getElementById('app').style.touchAction = 'none'
|
|
|
+ docEditorContainer.value.style.overflow = 'hidden'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 拖拽中
|
|
|
+const dragging = (e) => {
|
|
|
+ if (selectedPageList.length > 1 && selectedPageList.includes(dragFromIndex.value)) {
|
|
|
+ dragContainer.value.querySelector('.sortable-drag').style.setProperty("--after-content", "'" + selectedPageList.length + "'")
|
|
|
+ }
|
|
|
+
|
|
|
+ let target = isMobileDevice ? document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY) : 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()
|
|
|
+ const offsetX = isMobileDevice ? (e.touches[0].clientX - domRect.left) : (e.offsetX + 30)
|
|
|
+ isDragToLeft.value = offsetX < domRect.width / 2
|
|
|
+
|
|
|
+ if (isDragToLeft.value) {
|
|
|
+ dragToIndex.value = i - 1
|
|
|
+ } else {
|
|
|
+ dragToIndex.value = i
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isMobileDevice) handleAutoScroll(e.touches[0].clientY)
|
|
|
+}
|
|
|
+
|
|
|
+// 拖拽结束
|
|
|
+const dragEnd = () => {
|
|
|
+ dragContainer.value.removeEventListener(isMobileDevice ? 'touchmove' : 'mousemove', dragging)
|
|
|
+ if (dragToIndex.value > -2 && !dragIndicateDisable(dragToIndex.value)) {
|
|
|
+ handleMovePage(dragToIndex.value + 1)
|
|
|
+ }
|
|
|
+ dragFromIndex.value = -2
|
|
|
+ dragToIndex.value = -2
|
|
|
+
|
|
|
+ if (isMobileDevice) {
|
|
|
+ document.body.style.overscrollBehavior = 'auto'
|
|
|
+ document.body.style.userSelect = 'auto'
|
|
|
+ document.getElementById('app').style.touchAction = 'auto'
|
|
|
+ docEditorContainer.value.style.overflow = 'auto'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 拖拽计算显示位置的竖条是否置灰
|
|
|
+const dragIndicateDisable = (index) => {
|
|
|
+ if (![index - 1, index, index + 1].includes(dragToIndex.value + 1)) return false
|
|
|
+
|
|
|
+ if (selectedPageList.includes(dragFromIndex.value)) {
|
|
|
+ if (selectedPageList.length === 1 && (selectedPageList[0] === dragToIndex.value + 1 || selectedPageList[0] === dragToIndex.value)) return true
|
|
|
+ if (selectedPageList.length > 1 && isConsecutive(selectedPageList) && (selectedPageList.includes(dragToIndex.value + 1) || selectedPageList.includes(dragToIndex.value))) return true
|
|
|
+ } else {
|
|
|
+ if ([dragFromIndex.value, dragFromIndex.value - 1].includes(dragToIndex.value)) return true
|
|
|
+ }
|
|
|
+
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+// 是否是连续的数字
|
|
|
+const isConsecutive = (arr) => {
|
|
|
+ arr.sort(function(a, b) {
|
|
|
+ return a - b
|
|
|
+ })
|
|
|
+
|
|
|
+ for (var i = 0; i < arr.length - 1; i++) {
|
|
|
+ if (arr[i] + 1 !== arr[i + 1]) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+// 计算左边距
|
|
|
+const updateMarginLeft = () => {
|
|
|
+ const width = window.innerWidth
|
|
|
+ const pageWidth = width > 767 ? 280 : 170
|
|
|
+ marginLeft.value = parseInt(width % pageWidth / 2)
|
|
|
+}
|
|
|
+
|
|
|
+// 移动端处理自动滚动
|
|
|
+const handleAutoScroll = (clientY) => {
|
|
|
+ const threshold = 100
|
|
|
+ const containerEl = docEditorContainer.value
|
|
|
+ const containerRect = containerEl.getBoundingClientRect()
|
|
|
+ const containerHeight = containerRect.height
|
|
|
+ const deltaY = clientY
|
|
|
+ const totalHeight = containerEl.scrollHeight
|
|
|
+
|
|
|
+ // 判断鼠标位置是否在阈值范围内
|
|
|
+ if (deltaY <= threshold || deltaY >= containerHeight - threshold) {
|
|
|
+ clearInterval(timer)
|
|
|
+ timer = setInterval(() => {
|
|
|
+ // 向上滚动
|
|
|
+ if (deltaY <= threshold) {
|
|
|
+ containerEl.scrollTop -= 10
|
|
|
+ if (containerEl.scrollTop <= 0) {
|
|
|
+ clearInterval(timer)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 向下滚动
|
|
|
+ else if (deltaY >= containerHeight - threshold) {
|
|
|
+ containerEl.scrollTop += 10
|
|
|
+ if (containerEl.scrollTop + containerHeight >= totalHeight) {
|
|
|
+ clearInterval(timer)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 10)
|
|
|
+ } else {
|
|
|
+ // 鼠标位置不在阈值范围内,停止滚动
|
|
|
+ clearInterval(timer)
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss">
|
|
|
+.document-editor-container {
|
|
|
+ margin-top: 44px;
|
|
|
+ width: 100%;
|
|
|
+ background-color: var(--c-doc-editor-bg);
|
|
|
+ overflow: auto;
|
|
|
+
|
|
|
+ .tools-container {
|
|
|
+ position: absolute;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-top: -44px;
|
|
|
+ width: 100%;
|
|
|
+ height: 44px;
|
|
|
+ z-index: 71;
|
|
|
+ background-color: var(--c-toolbar-bg);
|
|
|
+ border-bottom: 1px solid var(--c-toolbar-border);
|
|
|
+ opacity: 1;
|
|
|
+ overflow-x: auto;
|
|
|
+ overflow-y: hidden;
|
|
|
+
|
|
|
+ &.hidden {
|
|
|
+ display: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ button {
|
|
|
+ &.disabled {
|
|
|
+ opacity: 1;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-right: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-container {
|
|
|
+ padding-bottom: 30px;
|
|
|
+ display: flex;
|
|
|
+ flex-flow: wrap;
|
|
|
+
|
|
|
+ .page {
|
|
|
+ position: relative;
|
|
|
+ padding: 8px;
|
|
|
+ margin: 30px 30px 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ width: 220px;
|
|
|
+ height: 256px;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ .img-box {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100%;
|
|
|
+ height: calc(100% - 24px);
|
|
|
+
|
|
|
+ img {
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ img,
|
|
|
+ .blank-page {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 100%;
|
|
|
+ border: 0.5px solid rgba(0, 0, 0, 0.12);
|
|
|
+ -webkit-touch-callout: none;
|
|
|
+ -ms-touch-action: manipulation;
|
|
|
+ touch-action: manipulation;
|
|
|
+ }
|
|
|
+
|
|
|
+ .blank-page {
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ p {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 16px;
|
|
|
+ color: var(--c-right-side-header-text);
|
|
|
+ text-align: center;
|
|
|
+ user-select: none;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.selected {
|
|
|
+ background-color: var(--c-header-button-active);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.drag-indicate-right:after {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ right: -32px;
|
|
|
+ display: block;
|
|
|
+ content: '';
|
|
|
+ height: 100%;
|
|
|
+ width: 4px;
|
|
|
+ background-color: var(--c-popup-bg-active);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.drag-indicate-left::before {
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: -32px;
|
|
|
+ display: block;
|
|
|
+ content: '';
|
|
|
+ height: 100%;
|
|
|
+ width: 4px;
|
|
|
+ background-color: var(--c-popup-bg-active);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.drag-indicate-disable::after, &.drag-indicate-disable::before {
|
|
|
+ background-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;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media screen and (min-width: 820px) {
|
|
|
+ .document-editor-container {
|
|
|
+ .tools-container {
|
|
|
+ button.select-all {
|
|
|
+ position: absolute;
|
|
|
+ right: 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .page-container .page.has-hover:hover {
|
|
|
+ background-color: rgba(0, 0, 0, 0.10);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media screen and (max-width: 819px) {
|
|
|
+ .document-editor-container .tools-container {
|
|
|
+ padding: 0 10px;
|
|
|
+ justify-content: flex-start;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media screen and (min-width: 578px) {
|
|
|
+ .document-editor-container .tools-container .button {
|
|
|
+ &:not(.disabled):hover {
|
|
|
+ background-color: var(--c-header-button-active);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@media screen and (max-width: 767px) {
|
|
|
+ .document-editor-container .page-container {
|
|
|
+ .page {
|
|
|
+ margin: 12px 10px;
|
|
|
+ padding: 5px;
|
|
|
+ width: 150px;
|
|
|
+ height: 175px;
|
|
|
+
|
|
|
+ &.drag-indicate-right:after {
|
|
|
+ right: -12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.drag-indicate-left::before {
|
|
|
+ left: -12px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|