Lee 3 tygodni temu
rodzic
commit
7b839aac29

+ 5 - 0
components.d.ts

@@ -9,6 +9,11 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     CodeViewer: typeof import('./src/components/view_file/vendors/text/CodeViewer.vue')['default']
+    ElButton: typeof import('element-plus/es')['ElButton']
+    ElIcon: typeof import('element-plus/es')['ElIcon']
+    ElOption: typeof import('element-plus/es')['ElOption']
+    ElSelect: typeof import('element-plus/es')['ElSelect']
+    ElUpload: typeof import('element-plus/es')['ElUpload']
     ImageViewer: typeof import('./src/components/view_file/vendors/image/ImageViewer.vue')['default']
     Other: typeof import('./src/components/view_file/vendors/other/index.vue')['default']
     PdfView: typeof import('./src/components/view_file/vendors/pdf/PdfView.vue')['default']

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
     "name": "huijia-viewfile",
     "private": false,
-    "version": "0.1.6",
+    "version": "0.1.18",
     "type": "module",
     "scripts": {
         "dev": "vite",

+ 3 - 2
src/components/view_file/index.less

@@ -1,6 +1,7 @@
 .preview-wrapper {
-  padding: 20px 40px;
-  height: ~"calc(100vh - 320px)";
+//   padding: 20px 40px;
+//   height: ~"calc(100vh - 320px)";
+  height: 100%;
   width: 100%;
   overflow: scroll;
   .loading-process {

+ 61 - 3
src/components/view_file/index.vue

@@ -2,7 +2,7 @@
   <div class="container">
     <div class="modal-height">
       <div class="file-box">
-        <p class="file-name" v-show="!renderLoading">{{ file.filename }}</p>
+        <!-- <p class="file-name" v-show="!renderLoading">{{ file.filename }}</p> -->
         <div class="view-wrapper">
           <div class="preview-wrapper" ref="wrapper" v-if="isAbleView">
             <div class="preview-inner" ref="preview-inner">
@@ -41,6 +41,7 @@
                   >
                   </button>
+
                   <img class="viewfile-image-preview-img" :src="imageSrcList[previewIndex]" alt="" />
                   <button
                     v-if="imageSrcList.length > 1"
@@ -52,7 +53,12 @@
                   </button>
                 </div>
               </div>
-              <div v-else class="output" v-show="!renderLoading" ref="output"></div>
+              <div
+                v-else
+                class="output"
+                :style="{ visibility: renderLoading ? 'hidden' : 'visible' }"
+                ref="output"
+              ></div>
             </div>
           </div>
           <view-other-component
@@ -67,6 +73,7 @@
 
 <script setup>
 import { ref, computed, watch, nextTick, onBeforeUnmount, onMounted } from "vue"
+import { debounce } from "lodash-es"
 import { readDataURL, render } from "./util"
 import ViewOtherComponent from "./vendors/other/index.vue"
 
@@ -164,12 +171,54 @@ function onKeydown(e) {
   if (e.key === "ArrowLeft") prevImage()
 }
 
+const scaleDocx = () => {
+  const docxWrapper = output.value?.querySelector(".docx-wrapper")
+  if (!docxWrapper) return
+  
+  // 重置样式以便重新计算
+  docxWrapper.style.transform = ""
+  docxWrapper.style.transformOrigin = ""
+  
+  const sections = docxWrapper.querySelectorAll("section.docx")
+  if (!sections.length) return
+  
+  // 获取内容宽度(基于第一页)
+  const sectionWidth = sections[0].offsetWidth
+  const contentWidth = sectionWidth + 40 // 加上两侧边距冗余
+  const containerWidth = output.value.clientWidth
+  
+  // 锁定容器宽度为内容宽度,确保缩放基准正确
+  docxWrapper.style.width = `${contentWidth}px`
+
+  // 仅在内容宽度超出容器时进行缩放
+  if (contentWidth > containerWidth) {
+    const scale = (containerWidth - 10) / contentWidth // 留出 10px 间隙
+    docxWrapper.style.transformOrigin = "top left"
+    docxWrapper.style.transform = `scale(${scale})`
+    docxWrapper.style.marginLeft = "5px" // 居中微调
+    docxWrapper.style.height = "auto" // 确保高度自适应
+    // 缩放后,由于transform不改变流体高度,容器底部可能会有大量空白
+    // 可以通过设置父容器高度或者 marginBottom 来修复,但稍微复杂,先保证显示正常
+  } else {
+    docxWrapper.style.margin = "0 auto" // 桌面端居中
+    docxWrapper.style.marginLeft = ""
+  }
+}
+
+const handleResize = debounce(() => {
+  if (props.file?.type === "docx" || props.file?.type === "doc") {
+    scaleDocx()
+  }
+}, 200)
+
 onMounted(() => {
   window.addEventListener("keydown", onKeydown)
+  window.addEventListener("resize", handleResize)
 })
 
 onBeforeUnmount(() => {
   window.removeEventListener("keydown", onKeydown)
+  window.removeEventListener("resize", handleResize)
 })
 
 /**
@@ -182,6 +231,8 @@ const renderResult = (buffer, extend) => {
   }
   output.value.innerHTML = ""
   const node = document.createElement("div")
+  
+  
   const child = output.value.appendChild(node)
   return new Promise((resolve, reject) =>
     render(buffer, extend, child).then(resolve).catch(reject)
@@ -215,6 +266,12 @@ watch(
           previewIndex.value = 0
           renderResult(newFile.fileBuffer, newFile.type).finally(() => {
             renderLoading.value = false
+            if (newFile.type === "docx" || newFile.type === "doc") {
+              nextTick(() => {
+                // 延迟一下等待DOM渲染完成
+                setTimeout(scaleDocx, 100)
+              })
+            }
           })
         })
       } catch (error) {
@@ -309,6 +366,7 @@ watch(
 .viewfile-image-preview-next {
   right: 16px;
 }
+
 </style>
 <style lang="less" scoped>
 @import url("./index.less");
@@ -322,7 +380,7 @@ watch(
   height: 100%;
   .file-box {
     flex: 1;
-    padding: 15px 40px;
+    // padding: 15px 40px;
     overflow: scroll;
     text-align: center;
 

+ 8 - 6
src/components/view_file/renders.js

@@ -2,7 +2,7 @@
  * @Author: LiZhiWei
  * @Date: 2025-04-09 08:10:23
  * @LastEditors: LiZhiWei
- * @LastEditTime: 2025-04-15 14:47:12
+ * @LastEditTime: 2025-12-31 09:37:10
  * @Description:
  */
 import { defaultOptions, renderAsync } from "docx-preview"
@@ -25,11 +25,13 @@ const handlers = [
     accepts: ["docx"],
     handler: async (buffer, target) => {
       const docxOptions = {
-        ...defaultOptions,
-        ...{
-          debug: true,
-          experimental: true,
-        },
+        className: "docx-wrapper",
+        inWrapper: false,
+        ignoreWidth: true,
+        breakPages: true,
+        experimental: true,
+        trimXmlDeclaration: true,
+        debug: false,
       }
       await renderAsync(buffer, target, null, docxOptions)
       return VueWrapper(target)

+ 2 - 1
src/components/view_file/vendors/image/ImageViewer.vue

@@ -2,7 +2,7 @@
  * @Author: LiZhiWei
  * @Date: 2025-04-24 15:29:17
  * @LastEditors: LiZhiWei
- * @LastEditTime: 2025-12-30 12:45:42
+ * @LastEditTime: 2025-12-31 09:57:33
  * @Description: 
 -->
 <template>
@@ -20,6 +20,7 @@
     <div v-if="visible" class="viewfile-image-preview" @click.self="close">
       <button class="viewfile-image-preview-close" type="button" @click="close">×</button>
       <button v-if="srcList.length > 1" class="viewfile-image-preview-prev" type="button" @click.stop="prev">‹</button>
+      
       <img class="viewfile-image-preview-img" :src="srcList[index]" alt="" />
       <button v-if="srcList.length > 1" class="viewfile-image-preview-next" type="button" @click.stop="next">›</button>
     </div>

+ 21 - 9
src/components/view_file/vendors/pdf/PdfView.vue

@@ -35,12 +35,7 @@ const currentPage = ref(1)
 let pdfDoc = null
 
 const scaleD = () => {
-  let max = 0
-  if (window.screen.width > 1440) {
-    max = 1.4
-  } else {
-    max = 1.2
-  }
+  let max = 3.0
   if (pdf_scale.value >= max) {
     return
   }
@@ -49,7 +44,7 @@ const scaleD = () => {
 }
 
 const scaleX = () => {
-  let min = 1.0
+  let min = 0.5
   if (pdf_scale.value <= min) {
     return
   }
@@ -60,13 +55,26 @@ const scaleX = () => {
 const loadFile = async () => {
   pdfDoc = await PDF.getDocument(props.data).promise
   pdf_pages.value = pdfDoc.numPages
-  renderPage()
+  renderPage(1, true)
 }
 
-const renderPage = async (num = 1) => {
+const renderPage = async (num = 1, isInit = false) => {
   currentPage.value = num
   const page = await pdfDoc.getPage(num)
+  
+  if (isInit && num === 1) {
+    const viewport = page.getViewport({ scale: 1.0 })
+    const container = document.querySelector('.preview-wrapper') || document.body
+    if (container) {
+      const availableWidth = container.clientWidth - 20
+      if (availableWidth > 0 && availableWidth < viewport.width) {
+        pdf_scale.value = availableWidth / viewport.width
+      }
+    }
+  }
+
   const canvas = document.getElementById("the_canvas" + num)
+  if (!canvas) return
   const ctx = canvas.getContext("2d")
   const dpr = window.devicePixelRatio || 1
   const bsr =
@@ -162,4 +170,8 @@ onMounted(() => {
   margin-right: 5px;
   cursor: pointer;
 }
+canvas {
+  max-width: 100% !important;
+  height: auto !important;
+}
 </style>

+ 2 - 2
src/components/view_file/vendors/pptx/PPT.vue

@@ -2,7 +2,7 @@
  * @Author: LiZhiWei
  * @Date: 2025-12-26 14:41:26
  * @LastEditors: LiZhiWei
- * @LastEditTime: 2025-12-30 14:10:48
+ * @LastEditTime: 2025-12-31 08:49:39
  * @Description: 
 -->
 <template>
@@ -451,7 +451,7 @@ function getSlideStyle(slide: PptxSlide) {
   left: 0;
   top: 0;
   transform-origin: top left;
-  border-radius: 6px;
+//   border-radius: 6px;
   box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
   overflow: hidden;
 }

+ 103 - 5
src/components/view_file/vendors/xlsx/Table.vue

@@ -1,5 +1,11 @@
 <template>
-  <div>
+  <div
+    class="zoom-wrapper"
+    :style="zoomStyle"
+    @touchstart.capture="onTouchStart"
+    @touchmove.capture="onTouchMove"
+    @touchend.capture="onTouchEnd"
+  >
     <div
       ref="spreadsheetRef"
       class="spreadsheet-container"
@@ -27,6 +33,82 @@ const spreadsheet = ref(null)
 const themeColors = ref([])
 const spreadsheetRef = ref(null)
 
+// 缩放相关状态
+const scale = ref(1)
+const startDistance = ref(0)
+const startScale = ref(1)
+const lastTapTime = ref(0)
+
+const zoomStyle = computed(() => {
+  if (scale.value === 1) return {}
+  return {
+    transform: `scale(${scale.value})`,
+    transformOrigin: "top left",
+    width: `${100 / scale.value}%`,
+    height: `${100 / scale.value}%`,
+  }
+})
+
+const onTouchStart = (e) => {
+  if (e.touches.length === 2) {
+    e.preventDefault()
+    const touch1 = e.touches[0]
+    const touch2 = e.touches[1]
+    startDistance.value = Math.hypot(
+      touch2.clientX - touch1.clientX,
+      touch2.clientY - touch1.clientY
+    )
+    startScale.value = scale.value
+  }
+}
+
+const onTouchMove = (e) => {
+  if (e.touches.length === 2) {
+    e.preventDefault()
+    const touch1 = e.touches[0]
+    const touch2 = e.touches[1]
+    const currentDistance = Math.hypot(
+      touch2.clientX - touch1.clientX,
+      touch2.clientY - touch1.clientY
+    )
+    if (startDistance.value > 0) {
+      const newScale = startScale.value * (currentDistance / startDistance.value)
+      // 限制缩放范围 0.25 - 2
+      scale.value = Math.min(Math.max(0.25, newScale), 2)
+    }
+  }
+}
+
+const onTouchEnd = (e) => {
+  // 缩放结束后重新渲染以适配清晰度和布局
+  if (startDistance.value > 0) {
+    startDistance.value = 0
+    updateSpreadsheet()
+    return
+  }
+
+  // 双击缩放处理
+  if (e.changedTouches.length === 1 && e.touches.length === 0) {
+    const currentTime = Date.now()
+    const tapLength = currentTime - lastTapTime.value
+    
+    if (tapLength < 300 && tapLength > 0) {
+      e.preventDefault()
+      // 如果当前比例大于 0.6,则缩小到 0.5 以显示更多内容
+      // 否则恢复到 1
+      if (scale.value > 0.6) {
+        scale.value = 0.5
+      } else {
+        scale.value = 1
+      }
+      updateSpreadsheet()
+      lastTapTime.value = 0
+    } else {
+      lastTapTime.value = currentTime
+    }
+  }
+}
+
 // 计算属性
 const sheets = computed(() => {
   if (props.workbook.worksheets) {
@@ -40,8 +122,16 @@ const initSpreadsheet = () => {
   // 优化性能配置
   spreadsheet.value = new Spreadsheet(spreadsheetRef.value, {
     view: {
-      height: () => document.documentElement.clientHeight - 120,
-      width: () => document.documentElement.clientWidth - 40,
+      height: () => {
+        const docHeight = document.documentElement.clientHeight
+        const h = window.innerWidth <= 768 ? docHeight - 100 : docHeight - 120
+        return h / scale.value
+      },
+      width: () => {
+        const docWidth = document.documentElement.clientWidth
+        const w = window.innerWidth <= 768 ? docWidth : docWidth - 40
+        return w / scale.value
+      },
     },
     mode: "read", // 只读模式
     showToolbar: false,
@@ -92,8 +182,16 @@ const updateSpreadsheet = () => {
     // 重新创建实例
     spreadsheet.value = new Spreadsheet(spreadsheetRef.value, {
       view: {
-        height: () => document.documentElement.clientHeight - 120,
-        width: () => document.documentElement.clientWidth - 40,
+        height: () => {
+          const docHeight = document.documentElement.clientHeight
+          const h = window.innerWidth <= 768 ? docHeight - 100 : docHeight - 120
+          return h / scale.value
+        },
+        width: () => {
+          const docWidth = document.documentElement.clientWidth
+          const w = window.innerWidth <= 768 ? docWidth : docWidth - 40
+          return w / scale.value
+        },
       },
       mode: "read",
       showToolbar: false,

+ 11 - 5
src/views/index.vue

@@ -2,7 +2,7 @@
  * @Author: LiZhiWei
  * @Date: 2025-04-24 15:23:24
  * @LastEditors: LiZhiWei
- * @LastEditTime: 2025-12-30 12:56:38
+ * @LastEditTime: 2025-12-31 10:57:58
  * @Description: 
 -->
 <template>
@@ -188,20 +188,24 @@ const handleFileChange = (file: any) => {
 <style scoped lang="less">
 .container {
   width: 100%;
-  height: 100vh;
+  min-height: 100vh;
   display: flex;
   flex-direction: column;
   background-color: #f5f7fa;
+//   align-items: center;
 }
 
 .file-input-container {
+  margin: 0 auto;
   margin-bottom: 20px;
-  background-color: #fff;
+
+//   background-color: #fff;
   padding: 20px;
   border-radius: 8px;
-  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
+//   box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
   display: flex;
   gap: 15px;
+  flex-wrap: wrap;
   .input-group {
     display: flex;
     align-items: center;
@@ -229,7 +233,9 @@ const handleFileChange = (file: any) => {
 
 .preview-container {
   flex: 1;
-  border-radius: 8px;
+//   border-radius: 8px;
+  margin: 0 auto;
+  width: 800px;
   overflow: hidden;
   background-color: #fff;
   box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);