From fca192d3c38c5dcfbb6ace8bc71d6078f6a079b2 Mon Sep 17 00:00:00 2001
From: 啊鑫 <t2856754968@163.com>
Date: 星期日, 20 七月 2025 18:09:06 +0800
Subject: [PATCH] LLJ附件系统全面优化:多格式文件预览与APK兼容性

---
 pages/QC/LLJ/Add.vue |  852 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 794 insertions(+), 58 deletions(-)

diff --git a/pages/QC/LLJ/Add.vue b/pages/QC/LLJ/Add.vue
index 04f0623..52ddd2a 100644
--- a/pages/QC/LLJ/Add.vue
+++ b/pages/QC/LLJ/Add.vue
@@ -103,12 +103,6 @@
 
 
 
-		<!-- 鎿嶄綔鎸夐挳鍖� -->
-		<view class="action-buttons" v-if="this.current">
-		            <button class="secondary-btn" @click="handleEmergencyRelease">绱ф�ユ斁琛�</button>
-		            <button class="secondary-btn" @click="handleWithdraw">鎾ゅ洖</button>
-		            <button class="secondary-btn" @click="getInspectionItems">鑾峰彇妫�楠岄」鐩�</button>
-		</view>
 		
 
 
@@ -147,14 +141,22 @@
 			</table>
 		</view>
 
-		<!-- 鎿嶄綔鎸夐挳鍖� -->
-		<view class="action-buttons">
-			<button class="secondary-btn" @click="addDestruction" v-if="this.current">鐮村潖瀹為獙</button>
-			<button class="secondary-btn" @click="uploadImages">涓婁紶/鏌ョ湅鍥剧墖</button>
-			<button class="secondary-btn" @click="fetchDrawingNumber">璋冨彇PLM鍥剧焊</button>
-			<button class="secondary-btn" @click="viewAttachmentInfo">鏌ョ湅闄勪欢淇℃伅</button>
-			<button class="secondary-btn" @click="addDefectDescription" v-if="this.current">娣诲姞涓嶈壇鎻忚堪</button>
-			<button class="primary-btn" @click="submitInspection" v-if="this.current">妫�楠屾彁浜�</button>
+		<!-- 椤甸潰鍐呭鍖哄煙 -->
+		<view class="content-wrapper">
+			<!-- 涓哄簳閮ㄦ寜閽暀鍑虹┖闂� -->
+		</view>
+		
+		<!-- 鍥哄畾鍦ㄥ簳閮ㄧ殑鎿嶄綔鎸夐挳鍖� -->
+		<view class="fixed-action-buttons">
+			<button class="action-btn" @click="handleEmergencyRelease" v-if="this.current">绱ф�ユ斁琛�</button>
+			<button class="action-btn" @click="handleWithdraw" v-if="this.current">鎾ゅ洖</button>
+			<button class="action-btn" @click="getInspectionItems" v-if="this.current">鑾峰彇妫�楠岄」鐩�</button>
+			<button class="action-btn" @click="addDestruction" v-if="this.current">鐮村潖瀹為獙</button>
+			<button class="action-btn" @click="uploadImages">涓婁紶/鏌ョ湅鍥剧墖</button>
+			<button class="action-btn" @click="fetchDrawingNumber">璋冨彇PLM鍥剧焊</button>
+			<button class="action-btn" @click="viewAttachmentInfo">鏌ョ湅闄勪欢淇℃伅</button>
+			<button class="action-btn" @click="addDefectDescription" v-if="this.current">娣诲姞涓嶈壇鎻忚堪</button>
+			<button class="action-btn primary" @click="submitInspection" v-if="this.current">妫�楠屾彁浜�</button>
 		</view>
 		<view v-if="remarksPopup" class="overlay">
 			<view class="popup">
@@ -189,8 +191,8 @@
 		
 		<view class="barcode">
 			<u-modal :show="drawingShow" title="鍥剧焊鏄庣粏" @confirm="drawingConfirm" @cancel="drawingCancel"
-				showCancelButton>
-				<uni-table border stripe emptyText="鏆傛棤鏇村鏁版嵁" style="margin-left: 5px;margin-right: 5px;height: 500px;">
+				showCancelButton :z-index="1000">
+				<uni-table border stripe emptyText="鏆傛棤鏇村鏁版嵁" style="margin-left: 5px;margin-right: 5px;height: 400px;max-height: 60vh;overflow-y: auto;">
 					<uni-tr>
 						<uni-th align="center">鐩稿叧鏂囨。</uni-th>
 						<uni-th align="center" width="90">鏈夋棤鍏宠仈PDF鏂囦欢</uni-th>
@@ -227,19 +229,16 @@
 					<div class="attachment-detail-row"><span class="attachment-label">鍙楁帶鏃ユ湡锛�</span><span>{{ selectedAttachment.fdate }}</span></div>
 					<div class="attachment-detail-row"><span class="attachment-label">涓婁紶浜猴細</span><span>{{ selectedAttachment.createBy }}</span></div>
 					<div class="attachment-detail-row"><span class="attachment-label">涓婁紶鏃堕棿锛�</span><span>{{ selectedAttachment.createDate }}</span></div>
-					<div v-if="isPreviewable(selectedAttachment.fattach)" class="attachment-preview-area">
-						<div v-if="['pdf','jpg','jpeg','png','gif'].includes(selectedAttachment.fattach.trim().split('.').pop().toLowerCase())">
-							<iframe :src="getAttachmentUrl(selectedAttachment)" style="width:100%;height:320px;border-radius:10px;background:#f8fafc;" frameborder="0"></iframe>
-						</div>
-						<div v-else-if="['txt'].includes(selectedAttachment.fattach.trim().split('.').pop().toLowerCase())">
-							<iframe :src="getAttachmentUrl(selectedAttachment)" style="width:100%;height:320px;border-radius:10px;background:#f8fafc;" frameborder="0"></iframe>
-						</div>
-						<div v-else-if="['doc','docx','xls','xlsx'].includes(selectedAttachment.fattach.trim().split('.').pop().toLowerCase())">
-							<iframe :src="'https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(getAttachmentUrl(selectedAttachment))" style="width:100%;height:320px;border-radius:10px;background:#f8fafc;" frameborder="0"></iframe>
-						</div>
-					</div>
-					<div v-else class="attachment-download-area">
-						<button class="attachment-download-link" @click="downloadAttachment(selectedAttachment)">涓嬭浇闄勪欢</button>
+					<div class="attachment-actions-detail">
+						<button class="attachment-action-btn preview-btn" 
+							@click="previewFtpFile(selectedAttachment)" 
+							v-if="isPreviewable(selectedAttachment.fattach)">
+							馃攳 鍦ㄧ嚎棰勮
+						</button>
+						<button class="attachment-action-btn download-btn" 
+							@click="downloadAttachment(selectedAttachment)">
+							馃摜 涓嬭浇鏂囦欢
+						</button>
 					</div>
 				</div>
 				<div v-else class="attachment-detail-empty">鏆傛棤闄勪欢淇℃伅</div>
@@ -254,16 +253,58 @@
 				<div v-else-if="attachments.length === 0">鏆傛棤闄勪欢</div>
 				<ul class="attachment-list" v-else>
 					<li v-for="item in attachments" :key="item.id">
-						<span class="attachment-name" @click="showAttachmentDetailDialog(item)">
-							{{ item.fattach }}
-						</span>
+						<div class="attachment-info">
+							<span class="attachment-name" @click="showAttachmentDetailDialog(item)">
+								{{ item.fattach }}
+							</span>
+							<div class="attachment-meta">
+								<span class="attachment-type">{{ item.ftype || '鏈煡绫诲瀷' }}</span>
+							</div>
+						</div>
 						<div class="attachment-actions">
 							<button class="secondary-btn" @click="showAttachmentDetailDialog(item)">璇︽儏</button>
+							<button class="secondary-btn preview-btn" @click="previewFtpFile(item)" 
+								v-if="isPreviewable(item.fattach)">棰勮</button>
 							<button class="secondary-btn" @click="downloadAttachment(item)">涓嬭浇</button>
 						</div>
 					</li>
 				</ul>
 				<button class="attachment-popup-close" @click="closeAttachmentPopup">鍏抽棴</button>
+			</view>
+		</view>
+		
+		<!-- 鏂囦欢棰勮寮圭獥 -->
+		<view v-if="showFilePreviewPopup" class="overlay">
+			<view class="popup file-preview-popup">
+				<h3 class="file-preview-title">{{ previewTitle }}</h3>
+				<div class="file-preview-divider"></div>
+				<div class="file-preview-content">
+					<!-- 鏂囨湰鍐呭棰勮 -->
+					<pre v-if="previewType === 'text'">{{ previewContent }}</pre>
+					
+					<!-- 鍥剧墖鍐呭棰勮 -->
+					<view v-else-if="previewType === 'image'" class="image-preview-container">
+						<image :src="previewContent" mode="widthFix" style="width: 100%; max-height: 400px;"></image>
+					</view>
+					
+					<!-- Excel 绛� Office 鏂囦欢鎻愮ず -->
+					<view v-else-if="previewType === 'excel'" class="unsupported-preview">
+						<view class="unsupported-icon">馃搳</view>
+						<view class="unsupported-text">Excel 鏂囦欢鏆備笉鏀寔鍦ㄧ嚎棰勮</view>
+						<view class="unsupported-hint">璇风偣鍑讳笅杞芥寜閽幏鍙栧畬鏁存枃浠�</view>
+					</view>
+					
+					<!-- 涓嶆敮鎸佺殑鏂囦欢绫诲瀷 -->
+					<view v-else class="unsupported-preview">
+						<view class="unsupported-icon">馃搫</view>
+						<view class="unsupported-text">姝ゆ枃浠舵牸寮忔殏涓嶆敮鎸侀瑙�</view>
+						<view class="unsupported-hint">璇风偣鍑讳笅杞芥寜閽幏鍙栧畬鏁存枃浠�</view>
+					</view>
+				</div>
+				<div class="file-preview-actions">
+					<button v-if="previewType !== 'text'" class="file-preview-btn download-btn" @click="downloadPreviewFile">馃摜 涓嬭浇鏂囦欢</button>
+					<button class="file-preview-btn close-btn" @click="closeFilePreview">鍏抽棴</button>
+				</div>
 			</view>
 		</view>
 	</view>
@@ -325,6 +366,11 @@
 				attachmentsLoading: false,
 				selectedAttachment: null,
 				showAttachmentDetail: false,
+				showFilePreviewPopup: false,
+				previewContent: '',
+				previewTitle: '',
+				previewItemNo: '',
+				previewType: '', // 'text', 'image', 'excel', 'unsupported'
 				
 			}
 		},
@@ -1286,6 +1332,11 @@
 					this.attachmentsLoading = false;
 					if (res.status === 0) {
 						this.attachments = res.data.tbBillList;
+						// 涓烘瘡涓檮浠惰缃粯璁ゅ彲鐢ㄧ姸鎬�
+						this.attachments.forEach((item, index) => {
+							this.$set(item, 'ftpAvailable', true); // 榛樿璁や负鏂囦欢鍙敤
+							this.$set(item, 'checking', false);
+						});
 					} else if (res.status === 1 && res.message === "璇ユ楠屽崟鏈笂浼犻檮浠朵俊鎭紒") {
 						uni.showToast({ title: res.message, icon: "none" });
 					} else {
@@ -1297,10 +1348,10 @@
 				this.showAttachmentPopup = false;
 			},
 			getAttachmentUrl(item) {
-				const baseUrl = "http://192.168.1.22:10054";
 				// 鍘婚櫎鎵�鏈夌┖鐧藉瓧绗︼紙鍖呮嫭涓嫳鏂囩┖鏍笺�佸埗琛ㄧ绛夛級
-				let fileName = item.fattach.replace(/[\s\u3000]+/g, '').trim();
-				return baseUrl + "/api/LLJ/DownloadAttachment?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName);
+				let fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim();
+				// 缁熶竴浣跨敤FTP涓嬭浇鎺ュ彛锛屽寘鍚獸TP鏈嶅姟鍣ㄥ湴鍧�
+				return this.$store.state.serverInfo.serverAPI + "/LLJ/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer);
 			},
 			showAttachmentDetailDialog(item) {
 				console.log('鏌ョ湅璇︽儏', item);
@@ -1319,7 +1370,13 @@
 				const ext = filename.trim().split('.').pop().toLowerCase();
 				// 鏀寔鍦ㄧ嚎棰勮鐨勬枃浠剁被鍨�
 				return [
-					'pdf', 'jpg', 'jpeg', 'png', 'gif', 'txt', 'doc', 'docx', 'xls', 'xlsx'
+					'pdf',           // PDF鏂囦欢
+					'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp',  // 鍥剧墖鏂囦欢
+					'txt', 'log', 'md',  // 鏂囨湰鏂囦欢
+					'doc', 'docx',   // Word鏂囨。
+					'xls', 'xlsx',   // Excel琛ㄦ牸
+					'ppt', 'pptx',   // PowerPoint婕旂ず鏂囩
+					'csv'            // CSV鏂囦欢
 				].includes(ext);
 			},
 			// 澶勭悊闄勪欢涓嬭浇閿欒
@@ -1330,32 +1387,474 @@
 					showCancel: false
 				});
 			},
-			downloadAttachment(item) {
-				const baseUrl = "http://192.168.1.22:10054";
-				// 鍘婚櫎鎵�鏈夌┖鏍笺�佸叏瑙掔┖鏍笺�佸洖杞︺�佹崲琛�
+			// 棰勮FTP鏂囦欢
+			previewFtpFile(item) {
 				const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim();
-				const url = baseUrl + "/api/Llj/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName);
-				uni.downloadFile({
+				const fileExt = fileName.split('.').pop().toLowerCase();
+				
+				// 妫�鏌ユ枃浠剁被鍨嬫槸鍚︽敮鎸侀瑙�
+				if (!this.isPreviewable(fileName)) {
+					uni.showModal({
+						title: '涓嶆敮鎸侀瑙�',
+						content: '璇ユ枃浠剁被鍨嬩笉鏀寔鍦ㄧ嚎棰勮锛岃涓嬭浇鍚庢煡鐪�',
+						showCancel: false
+					});
+					return;
+				}
+				
+				const previewUrl = this.$store.state.serverInfo.serverAPI + "/LLJ/PreviewFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer);
+				
+				// 鏍规嵁鏂囦欢绫诲瀷杩涜涓嶅悓鐨勯瑙堝鐞�
+				if (['pdf'].includes(fileExt)) {
+					this.previewPdfFile(previewUrl, fileName);
+				} else if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(fileExt)) {
+					this.previewImageFile(previewUrl, fileName);
+				} else if (['txt'].includes(fileExt)) {
+					this.previewTextFile(previewUrl, fileName);
+				} else if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(fileExt)) {
+					this.previewOfficeFile(previewUrl, fileName);
+				} else {
+					// 灏濊瘯閫氱敤棰勮
+					this.previewGenericFile(previewUrl, fileName);
+				}
+			},
+			
+			// 棰勮PDF鏂囦欢
+			previewPdfFile(url, fileName) {
+				// 鍏堜笅杞絇DF鏂囦欢锛岃浆涓篵ase64鍚庨瑙�
+				uni.request({
 					url: url,
+					method: 'GET',
+					responseType: 'arraybuffer',
 					success: (res) => {
 						if (res.statusCode === 200) {
-							if (typeof plus !== 'undefined' && plus.runtime && plus.runtime.openFile) {
-								plus.runtime.openFile({ path: res.tempFilePath }, () => {
-									uni.showToast({ title: '鎵撳紑鎴愬姛', icon: 'success' });
-								}, (e) => {
-									uni.showModal({ title: '鎻愮ず', content: '鏂囦欢涓嬭浇鎴愬姛锛屼絾鏃犳硶鑷姩鎵撳紑銆傝鍦ㄦ枃浠剁鐞嗕腑鎵嬪姩鏌ユ壘骞舵墦寮�銆�', showCancel: false });
-								});
-							} else {
-								uni.showModal({ title: '鎻愮ず', content: '鏂囦欢涓嬭浇鎴愬姛锛屼絾褰撳墠鐜鏃犳硶鑷姩鎵撳紑銆傝鍦ㄦ枃浠剁鐞嗕腑鎵嬪姩鏌ユ壘骞舵墦寮�銆�', showCancel: false });
-							}
+							const base64Data = uni.arrayBufferToBase64(res.data);
+							// 瀛樺偍鍒板叏灞�鍙橀噺
+							getApp().globalData.tempPDF = base64Data;
+							uni.navigateTo({
+								url: `/pages/fileView/pdfView`
+							});
 						} else {
-							uni.showModal({ title: '涓嬭浇澶辫触', content: `涓嬭浇澶辫触锛岀姸鎬佺爜锛�${res.statusCode}`, showCancel: false });
+							this.handlePreviewError(res.statusCode, fileName);
 						}
 					},
 					fail: (error) => {
-						uni.showModal({ title: '涓嬭浇澶辫触', content: `涓嬭浇澶辫触锛岃妫�鏌ョ綉缁滆繛鎺ャ��${error.errMsg}`, showCancel: false });
+						this.handlePreviewError(0, fileName, error.errMsg);
 					}
 				});
+			},
+			
+			// 棰勮鍥剧墖鏂囦欢
+			previewImageFile(url, fileName) {
+				// #ifdef APP-PLUS
+				// APP鐜锛氬厛涓嬭浇鍒版湰鍦板啀棰勮锛岄伩鍏嶇綉缁滃浘鐗囧姞杞介棶棰�
+				uni.showLoading({ title: '鍔犺浇鍥剧墖...' });
+				uni.downloadFile({
+					url: url,
+					success: (res) => {
+						uni.hideLoading();
+						if (res.statusCode === 200) {
+							// 浣跨敤鏈湴涓存椂璺緞
+							uni.navigateTo({
+								url: `/pages/fileView/imageView?url=${encodeURIComponent(res.tempFilePath)}`
+							});
+						} else {
+							this.handlePreviewError(res.statusCode, fileName);
+						}
+					},
+					fail: (error) => {
+						uni.hideLoading();
+						this.handlePreviewError(0, fileName, error.errMsg);
+					}
+				});
+				// #endif
+				
+				// #ifdef H5 || MP
+				// H5鍜屽皬绋嬪簭锛氱洿鎺ヤ娇鐢ㄧ綉缁淯RL
+				uni.navigateTo({
+					url: `/pages/fileView/imageView?url=${encodeURIComponent(url)}`
+				});
+				// #endif
+			},
+			
+			// 棰勮鏂囨湰鏂囦欢
+			previewTextFile(url, fileName) {
+				// 鏂囨湰鏂囦欢鐩存帴鏄剧ず鍦ㄥ脊绐椾腑
+				uni.showLoading({ title: '鍔犺浇鏂囦欢鍐呭...' });
+				uni.request({
+					url: url,
+					method: 'GET',
+					success: (res) => {
+						uni.hideLoading();
+						if (res.statusCode === 200) {
+							const fileType = this.getFileType(fileName);
+							
+							if (fileType === 'text') {
+								// 鏂囨湰鏂囦欢锛氭樉绀哄唴瀹�
+								this.showFilePreview(res.data, fileName);
+							} else if (fileType === 'image') {
+								// 鍥剧墖鏂囦欢锛氭樉绀哄浘鐗嘦RL
+								this.showFilePreview(url, fileName);
+							} else {
+								// 鍏朵粬鏂囦欢绫诲瀷锛氭樉绀烘彁绀轰俊鎭�
+								this.showFilePreview('', fileName);
+							}
+						} else {
+							this.handlePreviewError(res.statusCode, fileName);
+						}
+					},
+					fail: (error) => {
+						uni.hideLoading();
+						this.handlePreviewError(0, fileName, error.errMsg);
+					}
+				});
+			},
+			
+			// 妫�娴嬫枃浠剁被鍨�
+			getFileType(fileName) {
+				const fileExt = fileName.split('.').pop().toLowerCase();
+				
+				if (['txt', 'log', 'md', 'csv', 'json', 'xml'].includes(fileExt)) {
+					return 'text';
+				} else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(fileExt)) {
+					return 'image';
+				} else if (['xls', 'xlsx', 'doc', 'docx', 'ppt', 'pptx'].includes(fileExt)) {
+					return 'excel';
+				} else {
+					return 'unsupported';
+				}
+			},
+			
+			// 鏄剧ず鏂囦欢棰勮寮圭獥
+			showFilePreview(content, fileName) {
+				this.previewContent = content;
+				this.previewTitle = fileName;
+				this.previewItemNo = this.selectedAttachment?.itemNo || '';
+				this.previewType = this.getFileType(fileName);
+				this.showFilePreviewPopup = true;
+			},
+			
+			// 鍏抽棴鏂囦欢棰勮寮圭獥
+			closeFilePreview() {
+				this.showFilePreviewPopup = false;
+				this.previewContent = '';
+				this.previewTitle = '';
+				this.previewItemNo = '';
+				this.previewType = '';
+			},
+			
+			// 涓嬭浇棰勮鏂囦欢
+			downloadPreviewFile() {
+				const item = { fattach: this.previewTitle, itemNo: this.previewItemNo };
+				this.downloadAttachment(item);
+				this.closeFilePreview();
+			},
+			
+			// 棰勮Office鏂囦欢
+			previewOfficeFile(url, fileName) {
+				// 鍏堟鏌xcel鏂囦欢锛屼娇鐢ㄤ笓闂ㄧ殑Excel棰勮椤甸潰
+				const fileExt = fileName.split('.').pop().toLowerCase();
+				if (['xls', 'xlsx'].includes(fileExt)) {
+					// Excel鏂囦欢棰勮
+					uni.request({
+						url: url,
+						method: 'GET',
+						responseType: 'arraybuffer',
+						success: (res) => {
+							if (res.statusCode === 200) {
+								const base64Data = uni.arrayBufferToBase64(res.data);
+								// 瀛樺偍 Base64 鏁版嵁鍒版湰鍦板瓨鍌�
+								uni.setStorageSync('excelBase64Data', base64Data);
+								uni.navigateTo({
+									url: `/pages/fileView/excelView`
+								});
+							} else {
+								this.handlePreviewError(res.statusCode, fileName);
+							}
+						},
+						fail: (error) => {
+							this.handlePreviewError(0, fileName, error.errMsg);
+						}
+					});
+				} else if (['doc', 'docx'].includes(fileExt)) {
+					// Word鏂囦欢锛屽皾璇曚娇鐢╓ord棰勮椤甸潰鎴栬�呭井杞湪绾块瑙�
+					try {
+						const officePreviewUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}`;
+						// 濡傛灉鏈墂ebView椤甸潰锛屼娇鐢╳ebView棰勮
+						this.previewGenericFile(officePreviewUrl, fileName);
+					} catch (error) {
+						this.handlePreviewError(0, fileName, '涓嶆敮鎸佹Office鏂囦欢绫诲瀷鐨勯瑙�');
+					}
+				} else {
+					// 鍏朵粬Office鏂囦欢锛屼娇鐢ㄥ井杞湪绾块瑙堟湇鍔�
+					const officePreviewUrl = `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(url)}`;
+					this.previewGenericFile(officePreviewUrl, fileName);
+				}
+			},
+			
+			// 閫氱敤鏂囦欢棰勮
+			previewGenericFile(url, fileName) {
+				// 鐢变簬娌℃湁閫氱敤鐨剋ebView椤甸潰锛屾樉绀烘彁绀哄苟鎻愪緵涓嬭浇
+				uni.showModal({
+					title: '鏂囦欢棰勮',
+					content: `鏂囦欢 "${fileName}" 闇�瑕佷笅杞藉悗鏌ョ湅锛屾槸鍚︾珛鍗充笅杞斤紵`,
+					showCancel: true,
+					confirmText: '涓嬭浇',
+					cancelText: '鍙栨秷',
+					success: (res) => {
+						if (res.confirm) {
+							const item = { fattach: fileName, itemNo: this.selectedAttachment.itemNo };
+							this.downloadAttachment(item);
+						}
+					}
+				});
+			},
+			
+			// 澶勭悊棰勮閿欒
+			handlePreviewError(statusCode, fileName, errorMsg = '') {
+				let message = '';
+				if (statusCode === 404) {
+					message = `鏂囦欢 ${fileName} 鍦‵TP鏈嶅姟鍣ㄤ笂涓嶅瓨鍦╜;
+				} else if (statusCode === 0) {
+					message = `棰勮澶辫触锛�${errorMsg}`;
+				} else {
+					message = `棰勮澶辫触锛岀姸鎬佺爜锛�${statusCode}`;
+				}
+				
+				uni.showModal({
+					title: '棰勮澶辫触',
+					content: message,
+					showCancel: true,
+					confirmText: '涓嬭浇',
+					cancelText: '鍙栨秷',
+					success: (res) => {
+						if (res.confirm) {
+							// 鐢ㄦ埛閫夋嫨涓嬭浇鏂囦欢
+							const item = { fattach: fileName, itemNo: this.selectedAttachment.itemNo };
+							this.downloadAttachment(item);
+						}
+					}
+				});
+			},
+			
+			downloadAttachment(item) {
+				// 鍘婚櫎鎵�鏈夌┖鏍笺�佸叏瑙掔┖鏍笺�佸洖杞︺�佹崲琛�
+				const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim();
+				// 浣跨敤閰嶇疆鐨勬湇鍔″櫒鍦板潃鍜孎TP鏈嶅姟鍣ㄥ湴鍧�
+				const url = this.$store.state.serverInfo.serverAPI + "/LLJ/DownloadFtpFile?itemNo=" + encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) + "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer);
+				
+				// 妫�鏌ヨ繍琛岀幆澧�
+				// #ifdef H5
+				// H5鐜锛氫娇鐢ㄦ祻瑙堝櫒涓嬭浇
+				this.downloadFileInBrowser(url, fileName);
+				// #endif
+				
+				// #ifdef APP-PLUS
+				// APP鐜锛氫娇鐢╱ni.downloadFile
+				this.downloadFileInApp(url, fileName);
+				// #endif
+				
+				// #ifdef MP
+				// 灏忕▼搴忕幆澧冿細浣跨敤uni.downloadFile
+				this.downloadFileInApp(url, fileName);
+				// #endif
+			},
+			
+			// 鍦ㄦ祻瑙堝櫒涓笅杞芥枃浠�
+			downloadFileInBrowser(url, fileName) {
+				uni.showLoading({ title: '姝e湪鍑嗗涓嬭浇...' });
+				
+				// 鏂规硶1锛氬垱寤洪殣钘忕殑a鏍囩涓嬭浇
+				try {
+					const link = document.createElement('a');
+					link.href = url;
+					link.download = fileName;
+					link.style.display = 'none';
+					document.body.appendChild(link);
+					link.click();
+					document.body.removeChild(link);
+					
+					uni.hideLoading();
+					uni.showToast({ 
+						title: '涓嬭浇宸插紑濮�', 
+						icon: 'success',
+						duration: 2000
+					});
+				} catch (error) {
+					console.log('a鏍囩涓嬭浇澶辫触锛屽皾璇晈indow.open鏂瑰紡:', error);
+					// 鏂规硶2锛氫娇鐢╳indow.open
+					try {
+						window.open(url, '_blank');
+						uni.hideLoading();
+						uni.showToast({ 
+							title: '涓嬭浇宸插紑濮�', 
+							icon: 'success',
+							duration: 2000
+						});
+					} catch (error2) {
+						console.log('window.open涓嬭浇澶辫触锛屽皾璇昮etch鏂瑰紡:', error2);
+						// 鏂规硶3锛氫娇鐢╢etch涓嬭浇
+						this.downloadFileWithFetch(url, fileName);
+					}
+				}
+			},
+			
+			// 浣跨敤fetch涓嬭浇鏂囦欢
+			downloadFileWithFetch(url, fileName) {
+				fetch(url)
+					.then(response => {
+						if (!response.ok) {
+							throw new Error(`HTTP error! status: ${response.status}`);
+						}
+						return response.blob();
+					})
+					.then(blob => {
+						// 鍒涘缓blob URL
+						const blobUrl = window.URL.createObjectURL(blob);
+						
+						// 鍒涘缓涓嬭浇閾炬帴
+						const link = document.createElement('a');
+						link.href = blobUrl;
+						link.download = fileName;
+						link.style.display = 'none';
+						document.body.appendChild(link);
+						link.click();
+						document.body.removeChild(link);
+						
+						// 閲婃斁blob URL
+						window.URL.revokeObjectURL(blobUrl);
+						
+						uni.hideLoading();
+						uni.showToast({ 
+							title: '涓嬭浇鎴愬姛', 
+							icon: 'success',
+							duration: 2000
+						});
+					})
+					.catch(error => {
+						console.error('Fetch涓嬭浇澶辫触:', error);
+						uni.hideLoading();
+						if (error.message.includes('404')) {
+							uni.showModal({ 
+								title: '鏂囦欢涓嶅瓨鍦�', 
+								content: `璇ラ檮浠跺湪FTP鏈嶅姟鍣ㄤ笂涓嶅瓨鍦╜,
+								showCancel: false 
+							});
+						} else {
+							uni.showModal({ 
+								title: '涓嬭浇澶辫触', 
+								content: `涓嬭浇澶辫触: ${error.message}`,
+								showCancel: false 
+							});
+						}
+					});
+			},
+			
+			// 鍦ˋPP涓笅杞芥枃浠�
+			downloadFileInApp(url, fileName) {
+				// #ifdef APP-PLUS
+				uni.showLoading({ title: '浠嶧TP鏈嶅姟鍣ㄤ笅杞戒腑...' });
+				
+				// Android 鑾峰彇瀛樺偍璺緞
+				const saveDir = plus.os.name === 'Android' ? plus.io.convertLocalFileSystemURL('_downloads/') : plus.io.convertLocalFileSystemURL('_documents/');
+				const filePath = `${saveDir}${fileName}`;
+				
+				const downloadTask = uni.downloadFile({
+					url: url,
+					filePath: filePath, // 鎸囧畾淇濆瓨璺緞
+					success: (res) => {
+						uni.hideLoading();
+						if (res.statusCode === 200) {
+							const fileInfo = {
+								name: fileName,
+								path: res.filePath || filePath,
+								tempPath: res.tempFilePath
+							};
+							
+							uni.showModal({
+								title: '涓嬭浇鎴愬姛',
+								content: `鏂囦欢宸蹭繚瀛樺埌锛�${fileInfo.path}`,
+								showCancel: true,
+								confirmText: '鎵撳紑鏂囦欢',
+								cancelText: '纭畾',
+								success: (modalRes) => {
+									if (modalRes.confirm) {
+										// 鐢ㄦ埛閫夋嫨鎵撳紑鏂囦欢
+										this.openFileInApp(fileInfo);
+									}
+								}
+							});
+						} else if (res.statusCode === 404) {
+							uni.showModal({ 
+								title: '鏂囦欢涓嶅瓨鍦�', 
+								content: `璇ラ檮浠跺湪FTP鏈嶅姟鍣ㄤ笂涓嶅瓨鍦╜,
+								showCancel: false 
+							});
+						} else {
+							uni.showModal({ 
+								title: '涓嬭浇澶辫触', 
+								content: `鐘舵�佺爜锛�${res.statusCode}`,
+								showCancel: false 
+							});
+						}
+					},
+					fail: (error) => {
+						uni.hideLoading();
+						console.error('涓嬭浇澶辫触:', error);
+						uni.showModal({ 
+							title: '涓嬭浇澶辫触', 
+							content: `缃戠粶閿欒锛�${error.errMsg}`,
+							showCancel: false 
+						});
+					}
+				});
+				
+				// 鐩戝惉涓嬭浇杩涘害
+				downloadTask.onProgressUpdate((res) => {
+					const progress = Math.round(res.progress);
+					uni.showLoading({ 
+						title: `涓嬭浇涓� ${progress}%`,
+						mask: true 
+					});
+				});
+				// #endif
+				
+				// #ifdef MP
+				// 灏忕▼搴忕幆澧冪殑绠�鍖栧疄鐜�
+				uni.showLoading({ title: '涓嬭浇涓�...' });
+				uni.downloadFile({
+					url: url,
+					success: (res) => {
+						uni.hideLoading();
+						if (res.statusCode === 200) {
+							uni.showToast({ title: '涓嬭浇瀹屾垚', icon: 'success' });
+						}
+					},
+					fail: (error) => {
+						uni.hideLoading();
+						uni.showModal({ title: '涓嬭浇澶辫触', content: error.errMsg, showCancel: false });
+					}
+				});
+				// #endif
+			},
+			
+			// APP涓墦寮�鏂囦欢
+			openFileInApp(fileInfo) {
+				// #ifdef APP-PLUS
+				if (typeof plus !== 'undefined') {
+					const filePath = fileInfo.path || fileInfo.tempPath;
+					
+					// 灏濊瘯鎵撳紑鏂囦欢
+					plus.runtime.openFile(filePath, {}, (error) => {
+						console.error('鎵撳紑鏂囦欢澶辫触:', error);
+						uni.showModal({
+							title: '鏃犳硶鎵撳紑',
+							content: '绯荤粺涓病鏈夋壘鍒拌兘鎵撳紑姝ゆ枃浠剁殑搴旂敤绋嬪簭',
+							showCancel: false
+						});
+					});
+				}
+				// #endif
 			},
 
 		}
@@ -1368,9 +1867,11 @@
 		font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
 		max-width: 1000px;
 		margin: 0 auto;
-		padding: 20px;
+		padding: 20px 20px 160px 20px; /* 搴曢儴澧炲姞padding涓哄浐瀹氭寜閽暀绌洪棿 */
 		background-color: #fff;
 		box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+		min-height: 100vh;
+		position: relative;
 	}
 
 	/* 澶撮儴鏍峰紡 */
@@ -1491,7 +1992,60 @@
 		background-color: #f1f5f9;
 	}
 
-	/* 鎸夐挳鏍峰紡 */
+	/* 鍥哄畾搴曢儴鎸夐挳鏍峰紡 */
+	.fixed-action-buttons {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		background-color: #fff;
+		box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
+		padding: 10px 15px 20px 15px;
+		z-index: 100;
+		display: flex;
+		flex-direction: column;
+		gap: 8px;
+		max-height: 150px;
+		overflow-y: auto;
+	}
+
+	.action-btn {
+		background-color: #ecf0f1;
+		color: #34495e;
+		padding: 12px 15px;
+		border: none;
+		border-radius: 6px;
+		cursor: pointer;
+		font-size: 14px;
+		font-weight: 500;
+		transition: all 0.3s ease;
+		text-align: center;
+		min-height: 44px;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.action-btn:hover {
+		background-color: #d5dbdb;
+		transform: translateY(-1px);
+	}
+
+	.action-btn.primary {
+		background-color: #3498db;
+		color: #fff;
+	}
+
+	.action-btn.primary:hover {
+		background-color: #2980b9;
+	}
+
+	/* 鍐呭鍖呰鍣紝涓哄簳閮ㄦ寜閽暀鍑虹┖闂� */
+	.content-wrapper {
+		height: 20px; /* 棰濆鐨勭┖鐧藉尯鍩� */
+	}
+
+	/* 鍘熸湁鎸夐挳鏍峰紡淇濇寔鍏煎 */
 	.action-buttons {
 		display: flex;
 		gap: 10px;
@@ -1600,7 +2154,7 @@
 		display: flex;
 		justify-content: center;
 		align-items: center;
-		z-index: 10;
+		z-index: 1000; /* 鎻愰珮灞傜骇锛岀‘淇濆湪鍥哄畾鎸夐挳涓婃柟 */
 	}
 
 	/* 寮圭獥鏁翠綋缇庡寲 */
@@ -1612,6 +2166,9 @@
 		border: none;
 		position: relative;
 		min-width: 260px;
+		z-index: 1001; /* 纭繚寮圭獥鍐呭鍦ㄦ渶涓婂眰 */
+		max-height: 80vh; /* 闄愬埗鏈�澶ч珮搴︼紝閬垮厤琚簳閮ㄦ寜閽伄鎸� */
+		overflow-y: auto; /* 鍐呭杩囧鏃跺彲婊氬姩 */
 	}
 	.attachment-popup-title {
 		font-size: 22px;
@@ -1690,6 +2247,164 @@
 		background: linear-gradient(90deg,#bdbdbd 0%,#e0e0e0 100%);
 		color: #1976d2;
 	}
+	
+	/* 闄勪欢璇︽儏椤甸潰鐨勬搷浣滄寜閽� */
+	.attachment-actions-detail {
+		margin: 20px 0;
+		display: flex;
+		gap: 12px;
+		justify-content: center;
+		flex-wrap: wrap;
+	}
+	.attachment-action-btn {
+		padding: 10px 20px;
+		border: none;
+		border-radius: 8px;
+		font-size: 14px;
+		font-weight: 600;
+		cursor: pointer;
+		transition: all 0.3s ease;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		min-width: 120px;
+		box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+	}
+	.attachment-action-btn.preview-btn {
+		background: linear-gradient(135deg, #4CAF50, #45a049);
+		color: white;
+	}
+	.attachment-action-btn.preview-btn:hover {
+		background: linear-gradient(135deg, #45a049, #3d8b40);
+		transform: translateY(-1px);
+		box-shadow: 0 4px 8px rgba(0,0,0,0.15);
+	}
+	.attachment-action-btn.download-btn {
+		background: linear-gradient(135deg, #2196F3, #1976D2);
+		color: white;
+	}
+	.attachment-action-btn.download-btn:hover {
+		background: linear-gradient(135deg, #1976D2, #1565C0);
+		transform: translateY(-1px);
+		box-shadow: 0 4px 8px rgba(0,0,0,0.15);
+	}
+	
+	/* 鏂囦欢棰勮寮圭獥鏍峰紡 */
+	.file-preview-popup {
+		width: 80vw;
+		max-width: 600px;
+		max-height: 70vh;
+		display: flex;
+		flex-direction: column;
+	}
+	
+	/* APP鐜閫傞厤 */
+	/* #ifdef APP-PLUS */
+	.file-preview-popup {
+		width: 85vw;
+		max-height: 75vh;
+	}
+	.file-preview-content {
+		max-height: 350px;
+	}
+	/* #endif */
+	.file-preview-title {
+		font-size: 18px;
+		font-weight: 700;
+		color: #222;
+		margin-bottom: 8px;
+		text-align: center;
+		word-break: break-all;
+	}
+	.file-preview-divider {
+		height: 1px;
+		background: linear-gradient(90deg,#e0e7ef 0%,#f5f7fa 100%);
+		margin-bottom: 16px;
+	}
+	.file-preview-content {
+		flex: 1;
+		max-height: 400px;
+		overflow-y: auto;
+		background: #f8fafc;
+		border-radius: 8px;
+		padding: 16px;
+		margin-bottom: 16px;
+		border: 1px solid #e2e8f0;
+	}
+	.file-preview-content pre {
+		font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+		font-size: 12px;
+		line-height: 1.5;
+		color: #2d3748;
+		white-space: pre-wrap;
+		word-wrap: break-word;
+		margin: 0;
+	}
+	
+	/* 鍥剧墖棰勮鏍峰紡 */
+	.image-preview-container {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		min-height: 200px;
+	}
+	
+	/* 涓嶆敮鎸佹枃浠剁被鍨嬬殑鎻愮ず鏍峰紡 */
+	.unsupported-preview {
+		text-align: center;
+		padding: 40px 20px;
+		color: #666;
+	}
+	.unsupported-icon {
+		font-size: 48px;
+		margin-bottom: 16px;
+	}
+	.unsupported-text {
+		font-size: 16px;
+		font-weight: 600;
+		color: #333;
+		margin-bottom: 8px;
+	}
+	.unsupported-hint {
+		font-size: 14px;
+		color: #999;
+		line-height: 1.4;
+	}
+	
+	.file-preview-actions {
+		display: flex;
+		gap: 12px;
+		justify-content: center;
+	}
+	.file-preview-btn {
+		padding: 8px 20px;
+		border: none;
+		border-radius: 6px;
+		font-size: 14px;
+		font-weight: 600;
+		cursor: pointer;
+		transition: all 0.3s ease;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		min-width: 120px;
+	}
+	.file-preview-btn.download-btn {
+		background: linear-gradient(135deg, #2196F3, #1976D2);
+		color: white;
+	}
+	.file-preview-btn.download-btn:hover {
+		background: linear-gradient(135deg, #1976D2, #1565C0);
+		transform: translateY(-1px);
+	}
+	.file-preview-btn.close-btn {
+		background: linear-gradient(135deg, #e0e0e0, #bdbdbd);
+		color: #444;
+	}
+	.file-preview-btn.close-btn:hover {
+		background: linear-gradient(135deg, #bdbdbd, #9e9e9e);
+		transform: translateY(-1px);
+	}
 	/* 鍒楄〃寮圭獥缇庡寲锛堜繚鐣欏師鏈夛級 */
 	.attachment-list {
 		padding: 0;
@@ -1702,24 +2417,36 @@
 		display: flex;
 		align-items: center;
 		justify-content: space-between;
-		padding: 8px 0;
+		padding: 12px 0;
 		border-bottom: 1px solid #f0f0f0;
 	}
-	.attachment-name {
+	.attachment-info {
 		flex: 1;
+		margin-right: 10px;
+	}
+	.attachment-name {
 		color: #3498db;
 		cursor: pointer;
 		font-weight: 500;
 		transition: color 0.2s;
-		margin-right: 10px;
+		display: block;
+		margin-bottom: 4px;
 	}
 	.attachment-name:hover {
 		color: #217dbb;
 		text-decoration: underline;
 	}
+	.attachment-meta {
+		font-size: 12px;
+	}
+	.attachment-type {
+		color: #7f8c8d;
+		font-style: italic;
+	}
 	.attachment-actions {
 		display: flex;
 		gap: 8px;
+		flex-shrink: 0;
 	}
 	.attachment-list .secondary-btn {
 		padding: 4px 10px;
@@ -1734,6 +2461,15 @@
 		background: #e6f0fa;
 		color: #1976d2;
 	}
+	.preview-btn {
+		background: #e8f5e8 !important;
+		color: #2e7d2e !important;
+		border-color: #a5d6a5 !important;
+	}
+	.preview-btn:hover {
+		background: #d4eecc !important;
+		color: #1e5f1e !important;
+	}
 	.attachment-popup-close {
 		margin-top: 18px;
 		width: 100%;

--
Gitblit v1.9.3