From b4c2cbc7825fe4732fb6333447fdada9567bcb97 Mon Sep 17 00:00:00 2001
From: 吴羡 <16976746+Xian-TT3@user.noreply.gitee.com>
Date: 星期一, 12 十月 2026 17:48:36 +0800
Subject: [PATCH] 资源需求(标准物质相关)模块调整;新增统一附件组件并接入供应商管理与客户满意度;客户满意度表单改客户卡片下拉并关联已完成委托单;预览、图表、导航栏等组件调整

---
 src/components/Preview/filePreview.vue |  275 ++++++++++++++++++++++++++++++++----------------------
 1 files changed, 161 insertions(+), 114 deletions(-)

diff --git a/src/components/Preview/filePreview.vue b/src/components/Preview/filePreview.vue
index df8445e..e8ec772 100644
--- a/src/components/Preview/filePreview.vue
+++ b/src/components/Preview/filePreview.vue
@@ -1,53 +1,67 @@
 <template>
   <div>
-    <div v-if="isImage">
-      <img :src="imgUrl" alt="Image Preview" />
+    <!-- 鍥剧墖 -->
+    <div v-if="isImage" style="text-align: center">
+      <img :src="imgUrl" alt="鍥剧墖棰勮" />
     </div>
-    <div v-if="isPdf" style="height: 80vh;">
+    <!-- 瑙嗛 -->
+    <div v-else-if="isVideo" style="text-align: center">
+      <video :src="fileUrl" controls style="max-width: 100%; max-height: 85vh"></video>
+    </div>
+    <!-- 闊抽 -->
+    <div v-else-if="isAudio" style="text-align: center; padding-top: 10vh">
+      <audio :src="fileUrl" controls style="width: 60%"></audio>
+    </div>
+    <!-- PDF锛氫紶浜� OnlyOffice 閰嶇疆璧版枃妗f湇鍔★紝鍚﹀垯鐢ㄦ祻瑙堝櫒鍐呯疆闃呰鍣� -->
+    <div v-else-if="isPdf" style="height: 80vh;">
       <onlyoffice v-if="useOnlyOffice" ref="onlyoffice" :options="option" style="width: 100%;height: 100%;" />
       <iframe v-else-if="pdfUrl" :src="pdfUrl" title="PDF 棰勮" style="width: 100%;height: 100%;border: none;"></iframe>
       <p v-else-if="pdfError">PDF 棰勮澶辫触锛�<a :href="fileUrl" style="color: var(--el-color-primary);" target="_blank">鐐规涓嬭浇鏌ョ湅</a></p>
       <p v-else>姝e湪鍔犺浇 PDF鈥�</p>
     </div>
-    <div v-if="isDoc">
-      <p v-if="!isDocShow">鏂囨。鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p>
-      <a :href="fileUrl" v-if="!isDocShow">涓嬭浇鏂囦欢</a>
-      <vue-office-docx v-else :src="fileUrl" style="height: 100vh;" @rendered="renderedHandler" @error="errorHandler" />
-    </div>
-    <div v-if="isXls">
-      <p v-if="!isDocShow">鏂囨。鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p>
-      <a :href="fileUrl" v-if="!isDocShow">涓嬭浇鏂囦欢</a>
-      <vue-office-excel v-else :src="fileUrl" :options="excelOptions" style="height: 100vh;" @rendered="renderedHandler"
+    <!-- Word -->
+    <div v-else-if="isDoc">
+      <vue-office-docx v-if="isDocShow" :key="fileUrl" :src="fileUrl" style="height: 100vh;" @rendered="renderedHandler"
         @error="errorHandler" />
+      <div v-else class="preview-fallback">
+        <p>Word 鏂囨。鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p>
+        <el-button type="primary" @click="downloadFile">涓嬭浇鏂囦欢</el-button>
+      </div>
     </div>
-    <div v-if="isZipOrRar">
-      <p>鍘嬬缉鏂囦欢鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p>
-      <a :href="fileUrl">涓嬭浇鏂囦欢</a>
+    <!-- Excel -->
+    <div v-else-if="isXls">
+      <vue-office-excel v-if="isDocShow" :key="fileUrl" :src="fileUrl" :options="excelOptions" style="height: 100vh;"
+        @rendered="renderedHandler" @error="errorHandler" />
+      <div v-else class="preview-fallback">
+        <p>Excel 鏂囨。鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p>
+        <el-button type="primary" @click="downloadFile">涓嬭浇鏂囦欢</el-button>
+      </div>
     </div>
-    <div v-if="isCsv">
-      <p v-if="csvList.length == 0">CSV 鏂囦欢鏃犳硶鐩存帴棰勮锛岃涓嬭浇鏌ョ湅銆�</p>
-      <a :href="fileUrl" v-if="csvList.length == 0">涓嬭浇鏂囦欢</a>
-      <el-tabs type="border-card" v-if="csvList.length > 0" tab-position="bottom">
-        <el-tab-pane :label="item.sheetName" v-for="(item, index) in csvList" :key="index">
-          <el-table :data="item.tableData" height="75vh">
-            <el-table-column :label="m.label" :prop="m.prop" v-for="(m, i) in item.column" :key="i" min-width="120px"
-              show-overflow-tooltip>
-              <template #header="scope">
-                <div>
-                  <el-tooltip :content="m.label" placement="top">
-                    <div class="oneLine">
-                      <span>{{ m.label }}</span>
-                    </div>
-                  </el-tooltip>
-                </div>
-              </template>
-            </el-table-column>
-          </el-table>
-        </el-tab-pane>
-      </el-tabs>
+    <!-- CSV 琛ㄦ牸棰勮 -->
+    <div v-else-if="isCsv">
+      <el-table v-if="csvRows.length" :data="csvRows" height="75vh">
+        <el-table-column :label="col" :prop="col" v-for="(col, i) in csvColumns" :key="i" min-width="120px"
+          show-overflow-tooltip />
+      </el-table>
+      <div v-else-if="csvError" class="preview-fallback">
+        <p>CSV 鏂囦欢瑙f瀽澶辫触锛岃涓嬭浇鏌ョ湅銆�</p>
+        <el-button type="primary" @click="downloadFile">涓嬭浇鏂囦欢</el-button>
+      </div>
+      <p v-else>姝e湪鍔犺浇鈥�</p>
     </div>
-    <div v-if="!isSupported">
-      <p>涓嶆敮鎸佺殑鏂囦欢鏍煎紡</p>
+    <!-- 鏂囨湰鏂囦欢 -->
+    <div v-else-if="isText">
+      <pre v-if="textLoaded" class="text-preview">{{ textContent }}</pre>
+      <div v-else-if="textError" class="preview-fallback">
+        <p>鏂囨湰鏂囦欢璇诲彇澶辫触锛岃涓嬭浇鏌ョ湅銆�</p>
+        <el-button type="primary" @click="downloadFile">涓嬭浇鏂囦欢</el-button>
+      </div>
+      <p v-else>姝e湪鍔犺浇鈥�</p>
+    </div>
+    <!-- 鍏滃簳锛氬帇缂╁寘銆佺▼搴忔枃浠剁瓑娴忚鍣ㄦ棤娉曠洿鎺ユ樉绀虹殑鏍煎紡锛屼繚璇佷篃鍙笅杞� -->
+    <div v-else class="preview-fallback">
+      <p>璇ョ被鍨嬫枃浠讹紙{{ fileExt }}锛夋祻瑙堝櫒鏃犳硶鍦ㄧ嚎鏄剧ず锛岃涓嬭浇鍚庢煡鐪嬨��</p>
+      <el-button type="primary" @click="downloadFile">涓嬭浇鏂囦欢</el-button>
     </div>
   </div>
 </template>
@@ -85,22 +99,29 @@
   data() {
     return {
       isDocShow: true,
-      csvList: [], //csv鏂囦欢鏁版嵁
+      csvColumns: [],
+      csvRows: [],
+      csvError: false,
+      textContent: '',
+      textLoaded: false,
+      textError: false,
       pdfUrl: '', //pdf鏈湴棰勮鍦板潃
       pdfError: false
     }
   },
   computed: {
-    // 鍘熷疄鐜版妸 isImage / isXls / isCsv 鍐欐垚浜嗐�岃鏂囦欢鍚庣紑 + 椤烘墜鏀瑰埆鐨勫搷搴斿紡鐘舵�併�嶇殑 computed锛�
-    // isImage 閲屽啓 imgUrl銆乮sXls 閲屽啓 options.xls銆乮sCsv 閲岀洿鎺ュ彂璇锋眰銆�
-    // computed 閲屽啓鐘舵�佸睘浜庡壇浣滅敤锛屽湪 Vue3 涓細鍦ㄦ覆鏌撴湡瑙﹀彂鐘舵�佸彉鏇达紝寰堝鏄撴紨鍙樻垚閫掑綊鏇存柊銆�
-    // 杩欓噷鎷嗘垚绾绠楀睘鎬э紝鍓綔鐢ㄧ粺涓�浜ょ粰涓嬮潰閭d釜 watch銆�
     isImage() {
       return /\.(jpg|jpeg|png|gif)$/i.test(this.fileUrl)
     },
     imgUrl() {
-      // 鍘� isImage 鏃犺鐪熷亣閮戒細鍐� imgUrl = fileUrl锛堝懡涓浘鐗囨椂鎶� word 鎹㈡垚 img锛�
+      // 鍥剧墖瀛樺湪 /img 闈欐�佽矾寰勪笅锛岃皟鐢ㄦ柟浼犵殑鍙兘鏄� /word 鍦板潃锛岃繖閲岀粺涓�鎹㈡垚 /img
       return this.isImage ? this.fileUrl.replaceAll('word', 'img') : ''
+    },
+    isVideo() {
+      return /\.(mp4|webm|ogv|ogg)$/i.test(this.fileUrl)
+    },
+    isAudio() {
+      return /\.(mp3|wav|m4a|aac|flac|oga)$/i.test(this.fileUrl)
     },
     isPdf() {
       return /\.pdf$/i.test(this.fileUrl)
@@ -110,6 +131,12 @@
     },
     isXls() {
       return /\.(xls|xlsx)$/i.test(this.fileUrl)
+    },
+    isCsv() {
+      return /\.csv$/i.test(this.fileUrl)
+    },
+    isText() {
+      return /\.(txt|log|json|xml|md|ini|conf|cfg|yml|yaml|sql|properties)$/i.test(this.fileUrl)
     },
     // vue-office 鐨� excel 缁勪欢鐢� xls 鏍囧織鍖哄垎 xlsx / xls
     excelOptions() {
@@ -121,20 +148,15 @@
         heightOffset: 10, //鍦ㄩ粯璁ゆ覆鏌撶殑鍒楄〃楂樺害涓婂啀鍔� Npx楂�
         beforeTransformData: workbookData => {
           return workbookData
-        }, //搴曞眰閫氳繃exceljs鑾峰彇excel鏂囦欢鍐呭锛岄�氳繃璇ラ挬瀛愬嚱鏁帮紝鍙互瀵硅幏鍙栫殑excel鏂囦欢鍐呭杩涜淇敼锛屾瘮濡傛煇涓崟鍏冩牸鐨勬暟鎹樉绀轰笉姝g‘锛屽彲浠ュ湪姝よ嚜琛屼慨鏀规瘡涓崟鍏冩牸鐨剉alue鍊笺��
+        }, //搴曞眰閫氳繃exceljs鑾峰彇excel鏂囦欢鍐呭锛岄�氳繃璇ラ挬瀛愬嚱鏁帮紝鍙互瀵硅幏鍙栧埌鐨別xcel鏂囦欢鍐呭杩涜淇敼锛屾瘮濡傛煇涓崟鍏冩牸鐨勬暟鎹樉绀轰笉姝g‘锛屽彲浠ュ湪姝よ嚜琛屼慨鏀规瘡涓崟鍏冩牸鐨剉alue鍊笺��
         transformData: workbookData => {
           return workbookData
         } //灏嗚幏鍙栧埌鐨別xcel鏁版嵁杩涜澶勭悊涔嬪悗涓旀覆鏌撳埌椤甸潰涔嬪墠锛屽彲閫氳繃transformData瀵瑰嵆灏嗘覆鏌撶殑鏁版嵁鍙婃牱寮忚繘琛屼慨鏀癸紝姝ゆ椂姣忎釜鍗曞厓鏍肩殑text鍊煎氨鏄嵆灏嗘覆鏌撳埌椤甸潰涓婄殑鍐呭
       }
     },
-    isZipOrRar() {
-      return /\.(zip|rar)$/i.test(this.fileUrl)
-    },
-    isCsv() {
-      return /\.csv$/i.test(this.fileUrl)
-    },
-    isSupported() {
-      return this.isImage || this.isPdf || this.isDoc || this.isZipOrRar || this.isCsv || this.isXls
+    fileExt() {
+      const match = this.fileUrl.match(/\.([a-zA-Z0-9]+)$/)
+      return match ? match[1].toUpperCase() : '鏈煡'
     },
     // 璋冪敤鏂逛紶浜� OnlyOffice 鐨勯厤缃墠璧版枃妗f湇鍔★紝鍚﹀垯鐢ㄦ祻瑙堝櫒鍐呯疆闃呰鍣�
     useOnlyOffice() {
@@ -144,10 +166,9 @@
   watch: {
     fileUrl: {
       handler() {
+        this.resetState()
         this.loadPdf()
-        if (this.isCsv) {
-          this.loadCSVData()
-        }
+        this.loadTextContent()
       },
       immediate: true
     }
@@ -156,6 +177,16 @@
     this.releasePdfUrl()
   },
   methods: {
+    // 鍒囨崲鏂囦欢鏃舵竻鎺変笂涓�涓枃浠剁殑棰勮鐘舵�侊紝閬垮厤娌跨敤鏃х粨鏋滐紙姣斿涓婃 Word 娓叉煋澶辫触锛岃繖娆¤繕琚崱鍦ㄤ笅杞藉厹搴曢〉锛�
+    resetState() {
+      this.isDocShow = true
+      this.csvColumns = []
+      this.csvRows = []
+      this.csvError = false
+      this.textContent = ''
+      this.textLoaded = false
+      this.textError = false
+    },
     releasePdfUrl() {
       if (this.pdfUrl) {
         URL.revokeObjectURL(this.pdfUrl)
@@ -178,51 +209,44 @@
         this.pdfError = true
       }
     },
-    renderedHandler() {
-      console.log('娓叉煋瀹屾垚')
-      this.isDocShow = true
-      this.resetStyle()
-    },
-    errorHandler() {
-      console.log('娓叉煋澶辫触')
-      this.isDocShow = false
-    },
-    async loadCSVData() {
-      // 鍘熷疄鐜颁緷璧� this.$axios / this.$api锛岃�岃繖涓や釜鍏ㄥ眬鍙橀噺鍦ㄩ」鐩噷浠庢湭娉ㄥ唽杩�
-      // 锛坢ain.js 娌℃湁鎸傝浇锛屼篃娌℃湁 api 鑱氬悎瀵硅薄锛夛紝涓�鏃﹁繘鍏� CSV 鍒嗘敮灏辨姏 TypeError銆�
-      // 鍦ㄦ嬁鍒扮湡瀹炴帴鍙d箣鍓嶅厛鏄惧紡闄嶇骇涓恒�屼笅杞芥煡鐪嬨�嶏紝涓庣敤鎴峰疄闄呯湅鍒扮殑缁撴灉涓�鑷淬��
-      if (!this.$axios || !this.$api?.insOrderPlan?.preview) {
-        console.warn('[filePreview] 鏈厤缃� $axios/$api锛孋SV 杞〃鏍间笉鍙敤锛屽凡闄嶇骇涓轰笅杞芥煡鐪�')
-        this.csvList = []
+    // CSV 瑙f瀽鎴愯〃鏍笺�佹枃鏈枃浠剁洿鎺ュ睍绀猴紱鍐呭甯歌涓� GBK 缂栫爜锛孶TF-8 瑙e嚭涔辩爜鏃舵崲 GBK 鍐嶈В涓�娆�
+    async loadTextContent() {
+      if (!this.isCsv && !this.isText) {
         return
       }
-      this.$axios
-        .post(this.$api.insOrderPlan.preview, {
-          id: this.currentFile.id
-        })
-        .then(res => {
-          let arr = res.data
-          arr = arr.map(m => {
-            const obj = {
-              sheetName: m.sheetName,
-              tableData: [],
-              column: []
-            }
-            obj.tableData = this.formatCSVToTable(m.content.replaceAll('null', ' '))
-            // .replaceAll('MIN','=MIN').replaceAll('MAX','=MAX').replaceAll('AVERAGE','=AVERAGE')
-            for (const item in obj.tableData[0]) {
-              obj.column.push({
-                label: item,
-                prop: item
-              })
-            }
-            return obj
-          })
-          this.csvList = arr
-        })
-        .catch(err => {
-          console.log(err)
-        })
+      try {
+        const { data } = await axios.get(this.fileUrl, { responseType: 'arraybuffer' })
+        let text = new TextDecoder('utf-8').decode(data)
+        // 瑙g爜缁撴灉閲屽嚭鐜� U+FFFD 鏇挎崲绗︼紝璇存槑鍘熸枃涓嶆槸 UTF-8锛屽父瑙佹槸 GBK 缂栫爜鐨勪腑鏂囨枃浠�
+        if (text.indexOf(String.fromCharCode(0xFFFD)) !== -1) {
+          try {
+            text = new TextDecoder('gbk').decode(data)
+          } catch (e) {
+            // 涓埆娴忚鍣ㄤ笉鏀寔 gbk 瑙g爜锛屼繚鐣� UTF-8 缁撴灉
+          }
+        }
+        if (this.isCsv) {
+          const rows = this.formatCSVToTable(text.replaceAll('\r\n', '\n').replaceAll('\r', '\n'))
+          if (rows.length) {
+            this.csvColumns = Object.keys(rows[0])
+            this.csvRows = rows.slice(0, 5000)
+          } else {
+            this.csvError = true
+          }
+        } else {
+          this.textContent = text.length > 1000000
+            ? text.slice(0, 1000000) + '\n\n鈥︹�︼紙鏂囦欢杩囧ぇ锛屼粎棰勮鍓嶄竴鐧句竾瀛楋紝瀹屾暣鍐呭璇蜂笅杞芥煡鐪嬶級'
+            : (text || '锛堟枃浠跺唴瀹逛负绌猴級')
+          this.textLoaded = true
+        }
+      } catch (error) {
+        console.error(error)
+        if (this.isCsv) {
+          this.csvError = true
+        } else {
+          this.textError = true
+        }
+      }
     },
     formatCSVToTable(str) {
       const result = []
@@ -239,19 +263,10 @@
             } else {
               const obj = {}
               for (let index = 0; index < arrHeader.length; index++) {
-                // 缁勮琛ㄦ牸鏁版嵁
-                const name = String(arrHeader[index])
+                const name = String(arrHeader[index]).trim()
                 if (!arrHeader[index]) continue
                 if (!obj[name]) {
-                  try {
-                    if (kv[index]) {
-                      obj[name] = String(kv[index])
-                    } else {
-                      obj[name] = ''
-                    }
-                  } catch (err) {
-                    obj[name] = ''
-                  }
+                  obj[name] = kv[index] ? String(kv[index]) : ''
                 }
               }
               result.push(obj)
@@ -260,6 +275,21 @@
         }
       }
       return result
+    },
+    downloadFile() {
+      const name = this.currentFile && this.currentFile.fileName
+        ? this.currentFile.fileName
+        : this.fileUrl.split('/').pop()
+      this.$download.saveAs(this.fileUrl, name)
+    },
+    renderedHandler() {
+      console.log('娓叉煋瀹屾垚')
+      this.isDocShow = true
+      this.resetStyle()
+    },
+    errorHandler() {
+      console.log('娓叉煋澶辫触')
+      this.isDocShow = false
     },
     resetStyle() {
       // 娉細鍘熷疄鐜版槸 style.replace(/pt/g, 'px')锛屼細鎶婃牱寮忛噷浠绘剰浣嶇疆鐨� pt 閮芥崲鎺�
@@ -282,9 +312,26 @@
   max-width: 100%;
 }
 
-.oneLine {
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
+.text-preview {
+  white-space: pre-wrap;
+  word-break: break-all;
+  max-height: 85vh;
+  overflow: auto;
+  padding: 12px;
+  margin: 0;
+  background: #f8f9fa;
+  font-family: Consolas, Monaco, monospace;
+  font-size: 13px;
+  line-height: 1.6;
+}
+
+.preview-fallback {
+  text-align: center;
+  padding-top: 15vh;
+  color: #606266;
+}
+
+.preview-fallback p {
+  margin-bottom: 16px;
 }
 </style>

--
Gitblit v1.9.3