xwt
2025-10-30 4a9c0f7ddb5eae77b1f833bd2223e33fe6bb2918
XJ,SJ,RKJ序号排序,LLJ单个检验项目FTP附件
已修改12个文件
2041 ■■■■ 文件已修改
manifest.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/BasePages/main.vue 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/BasePages/user.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/LLJ/Add.vue 690 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/LLJ/detail.vue 891 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/RKJ/Add.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/RKJ/List.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/SJ/Add.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/SJ/List.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/XJ/Add.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/XJ/List.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
store/index.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
manifest.json
@@ -2,7 +2,7 @@
    "name" : "GS-MES-AP",
    "appid" : "__UNI__F08FAE3",
    "description" : "",
    "versionName" : "1.1.4.1",
    "versionName" : "1.1.4.2",
    "versionCode" : 1,
    "transformPx" : false,
    /* 5+App特有相关 */
pages/BasePages/main.vue
@@ -1,7 +1,7 @@
<template>
    <uni-base-page :footer="false">
        <view slot="page">
            <view v-if="loginInfo.hasLogin">
            <view v-if="loginInfo.hasLogin" class="main-container">
                <!-- 教学视频、公司内部宣传图片等 -->
                <!--        <swiper indicator-dots="true" :autoplay="true" :interval="3000">-->
                <!--          <swiper-item v-for="(img,key) in imgUrls" :key="key">-->
@@ -10,6 +10,51 @@
                <!--        </swiper>-->
                <!-- 通报批评、消息预警、公告、报告 -->
                <uni-notice-bar :show-icon="true" :scrollable="true" :speed="30" :single="true" :text="msg" />
                <!-- 筛选按钮 -->
                <view class="filter-btn" @click="toggleFilter">
                    <uni-icons type="funnel-fill" size="20" color="#fff"></uni-icons>
                    <text class="filter-btn-text">筛选</text>
                    <view v-if="selectedDepartmentId || selectedLineId" class="filter-badge"></view>
                </view>
                <!-- 筛选弹出层 -->
                <view v-if="showFilter" class="filter-overlay" @click="closeFilter">
                    <view class="filter-panel" @click.stop>
                        <view class="filter-header">
                            <text class="filter-title">筛选条件</text>
                            <view class="close-icon" @click="closeFilter">
                                <uni-icons type="close" size="20" color="#666"></uni-icons>
                            </view>
                        </view>
                        <view class="filter-content">
                            <view class="filter-row">
                                <text class="filter-label">车间</text>
                                <picker @change="onDepartmentChange" :value="departmentIndex" :range="departments" range-key="departmentname" class="filter-picker">
                                    <view class="picker-text">
                                        {{departmentIndex >= 0 ? departments[departmentIndex].departmentname : '请选择车间'}}
                                    </view>
                                </picker>
                            </view>
                            <view class="filter-row">
                                <text class="filter-label">线体</text>
                                <picker @change="onLineChange" :value="lineIndex" :range="lines" range-key="lineName" :disabled="!selectedDepartmentId" class="filter-picker">
                                    <view class="picker-text" :class="{'disabled': !selectedDepartmentId}">
                                        {{lineIndex >= 0 ? lines[lineIndex].lineName : selectedDepartmentId ? '请选择线体' : '请先选择车间'}}
                                    </view>
                                </picker>
                            </view>
                        </view>
                        <view class="filter-footer">
                            <button class="reset-btn" @click="clearFilters">清空</button>
                            <button class="confirm-btn" @click="closeFilter">确定</button>
                        </view>
                    </view>
                </view>
                <!-- 用户系统菜单模块 -->
                <view class="example-body">
                    <uni-grid :column="col" :showBorder="false">
@@ -54,11 +99,153 @@
                ],
                col: 4, //菜单列数
                msg: "宁波广深科技有限公司",
                updateChecked: false
                updateChecked: false,
                // 车间和线体筛选相关数据
                departments: [],
                departmentIndex: -1,
                selectedDepartmentId: '',
                lines: [],
                lineIndex: -1,
                selectedLineId: '',
                showFilter: false // 控制筛选面板显示
            };
        },
        methods: {
            // 切换筛选面板显示
            toggleFilter() {
                this.showFilter = !this.showFilter;
            },
            // 关闭筛选面板
            closeFilter() {
                this.showFilter = false;
            },
            // 获取车间列表
            getDepartments() {
                this.$post({
                    url: "/Base/GetQCDepartments",
                    data: {}
                }).then(res => {
                    this.departments = res.data || [];
                    // 从缓存中恢复选择
                    const cachedDeptId = uni.getStorageSync('qc_filter_departmentId');
                    if (cachedDeptId && this.departments.length > 0) {
                        const index = this.departments.findIndex(d => d.departmentid == cachedDeptId);
                        if (index >= 0) {
                            this.departmentIndex = index;
                            this.selectedDepartmentId = cachedDeptId;
                            this.getLines();
                        }
                    }
                }).catch(err => {
                    console.error('获取车间列表失败:', err);
                });
            },
            // 获取线体列表(根据车间过滤)
            getLines() {
                if (!this.selectedDepartmentId) {
                    this.lines = [];
                    return;
                }
                this.$post({
                    url: "/Base/GetQCLines",
                    data: {
                        departmentId: this.selectedDepartmentId
                    }
                }).then(res => {
                    this.lines = res.data || [];
                    // 从缓存中恢复选择
                    const cachedLineId = uni.getStorageSync('qc_filter_lineId');
                    if (cachedLineId && this.lines.length > 0) {
                        const index = this.lines.findIndex(l => l.lineNo == cachedLineId);
                        if (index >= 0) {
                            this.lineIndex = index;
                            this.selectedLineId = cachedLineId;
                        } else {
                            // 如果缓存的线体不在当前车间下,清除线体选择
                            this.lineIndex = -1;
                            this.selectedLineId = '';
                            uni.removeStorageSync('qc_filter_lineId');
                            uni.removeStorageSync('qc_filter_lineName');
                        }
                    }
                }).catch(err => {
                    console.error('获取线体列表失败:', err);
                });
            },
            // 车间选择改变
            onDepartmentChange(e) {
                const index = parseInt(e.detail.value);
                this.departmentIndex = index;
                if (index >= 0 && this.departments[index]) {
                    this.selectedDepartmentId = this.departments[index].departmentid;
                    const departmentName = this.departments[index].departmentname;
                    // 保存到缓存
                    uni.setStorageSync('qc_filter_departmentId', this.selectedDepartmentId);
                    uni.setStorageSync('qc_filter_departmentName', departmentName);
                    // 清除线体选择
                    this.lineIndex = -1;
                    this.selectedLineId = '';
                    uni.removeStorageSync('qc_filter_lineId');
                    uni.removeStorageSync('qc_filter_lineName');
                    // 加载该车间下的线体
                    this.getLines();
                    uni.showToast({
                        title: `已选择车间:${departmentName}`,
                        icon: 'none'
                    });
                }
            },
            // 线体选择改变
            onLineChange(e) {
                const index = parseInt(e.detail.value);
                this.lineIndex = index;
                if (index >= 0 && this.lines[index]) {
                    this.selectedLineId = this.lines[index].lineNo;
                    const lineName = this.lines[index].lineName;
                    // 保存到缓存
                    uni.setStorageSync('qc_filter_lineId', this.selectedLineId);
                    uni.setStorageSync('qc_filter_lineName', lineName);
                    uni.showToast({
                        title: `已选择线体:${lineName}`,
                        icon: 'none'
                    });
                }
            },
            // 清空筛选条件
            clearFilters() {
                this.departmentIndex = -1;
                this.selectedDepartmentId = '';
                this.lineIndex = -1;
                this.selectedLineId = '';
                this.lines = [];
                // 清除缓存
                uni.removeStorageSync('qc_filter_departmentId');
                uni.removeStorageSync('qc_filter_departmentName');
                uni.removeStorageSync('qc_filter_lineId');
                uni.removeStorageSync('qc_filter_lineName');
                uni.showToast({
                    title: '已清空筛选条件',
                    icon: 'success'
                });
            },
            getMenu(isShowMask) {
                if (isShowMask) uni.showLoading({
                    mask: true,
@@ -199,12 +386,26 @@
                
                this.getMenu(true);
                this.checkForUpdate();
                this.getDepartments(); // 加载车间列表
            }
        },
        onShow() {
            // this.getIsMsg();
            // 每次显示页面时恢复筛选条件显示
            if (this.loginInfo.hasLogin && this.departments.length === 0) {
                this.getDepartments();
            }
            // 检查缓存的筛选条件,如果不存在则重置UI状态
            const cachedDeptId = uni.getStorageSync('qc_filter_departmentId');
            if (!cachedDeptId) {
                this.departmentIndex = -1;
                this.selectedDepartmentId = '';
                this.lineIndex = -1;
                this.selectedLineId = '';
                this.lines = [];
            }
        },
        onPullDownRefresh() {
            if (this.loginInfo.hasLogin)
@@ -268,4 +469,160 @@
        flex-direction: row;
        justify-content: flex-start;
    }
    .main-container {
        position: relative;
    }
    /* 筛选按钮样式 */
    .filter-btn {
        position: fixed;
        top: 20rpx;
        right: 20rpx;
        z-index: 1000;
        display: flex;
        align-items: center;
        gap: 8rpx;
        padding: 12rpx 24rpx;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        border-radius: 50rpx;
        box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4);
    }
    .filter-btn-text {
        font-size: 24rpx;
        color: #fff;
        font-weight: 600;
    }
    .filter-badge {
        position: absolute;
        top: 4rpx;
        right: 4rpx;
        width: 16rpx;
        height: 16rpx;
        background-color: #ff4757;
        border-radius: 50%;
        border: 2rpx solid #fff;
    }
    /* 筛选弹出层 */
    .filter-overlay {
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: rgba(0, 0, 0, 0.5);
        z-index: 999;
        display: flex;
        align-items: flex-start;
        justify-content: flex-end;
        padding: 80rpx 20rpx 20rpx 20rpx;
    }
    .filter-panel {
        width: 500rpx;
        max-width: 90%;
        background-color: #fff;
        border-radius: 16rpx;
        box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
        animation: slideIn 0.3s ease-out;
    }
    @keyframes slideIn {
        from {
            transform: translateX(100%);
            opacity: 0;
        }
        to {
            transform: translateX(0);
            opacity: 1;
        }
    }
    .filter-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 24rpx 28rpx;
        border-bottom: 1rpx solid #f0f0f0;
    }
    .filter-title {
        font-size: 32rpx;
        font-weight: 700;
        color: #333;
    }
    .close-icon {
        padding: 8rpx;
    }
    .filter-content {
        padding: 32rpx 28rpx;
    }
    .filter-row {
        margin-bottom: 32rpx;
    }
    .filter-row:last-child {
        margin-bottom: 0;
    }
    .filter-label {
        display: block;
        font-size: 28rpx;
        color: #666;
        margin-bottom: 16rpx;
        font-weight: 600;
    }
    .filter-picker {
        width: 100%;
    }
    .picker-text {
        padding: 20rpx 24rpx;
        background-color: #f8f9ff;
        border-radius: 12rpx;
        border: 2rpx solid #e0e5ff;
        font-size: 28rpx;
        color: #333;
        text-align: left;
    }
    .picker-text.disabled {
        background-color: #f5f5f5;
        color: #999;
        border-color: #e5e5e5;
    }
    .filter-footer {
        display: flex;
        gap: 16rpx;
        padding: 24rpx 28rpx;
        border-top: 1rpx solid #f0f0f0;
    }
    .reset-btn,
    .confirm-btn {
        flex: 1;
        padding: 20rpx;
        border-radius: 12rpx;
        font-size: 28rpx;
        font-weight: 600;
        border: none;
    }
    .reset-btn {
        background-color: #f5f5f5;
        color: #666;
    }
    .confirm-btn {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: #fff;
    }
</style>
pages/BasePages/user.vue
@@ -46,6 +46,13 @@
            }
        });
        // 清除QC筛选条件
        uni.removeStorageSync('qc_filter_departmentId');
        uni.removeStorageSync('qc_filter_departmentName');
        uni.removeStorageSync('qc_filter_lineId');
        uni.removeStorageSync('qc_filter_lineName');
      /**
       * 如果需要强制登录跳转回登录页面
       */
pages/QC/LLJ/Add.vue
@@ -373,57 +373,119 @@
        <!-- 附件详情弹窗 -->
        <view v-if="showAttachmentDetail" class="overlay">
            <view class="popup attachment-detail-popup">
                <h3 class="attachment-popup-title">附件详情</h3>
                <div class="attachment-popup-divider"></div>
                <div v-if="selectedAttachment" class="attachment-detail-content">
                    <div class="attachment-detail-row"><span class="attachment-label">ID:</span><span>{{ Math.trunc(selectedAttachment.id) }}</span></div>
                    <div class="attachment-detail-row"><span class="attachment-label">附件名:</span><span>{{ selectedAttachment.fattach }}</span></div>
                    <div class="attachment-detail-row"><span class="attachment-label">类型:</span><span>{{ selectedAttachment.ftype }}</span></div>
                    <div class="attachment-detail-row"><span class="attachment-label">版本:</span><span>{{ selectedAttachment.fversion }}</span></div>
                    <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 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 class="attachment-popup-header">
                    <h3 class="attachment-popup-title">附件详情</h3>
                    <button class="attachment-close-btn" @click="closeAttachmentDetail">返回</button>
                </div>
                <div class="attachment-popup-content">
                    <div v-if="selectedAttachment" class="attachment-detail-content">
                        <div class="attachment-detail-header">
                            <div class="file-type-badge large" :class="getFileTypeClass(selectedAttachment.fattach)">
                                {{ getFileTypeIcon(selectedAttachment.fattach) }}
                            </div>
                            <div class="attachment-detail-title">
                                {{ selectedAttachment.fattach }}
                            </div>
                        </div>
                        <div class="attachment-detail-info">
                            <div class="info-row">
                                <div class="info-item">
                                    <text class="info-label">ID</text>
                                    <text class="info-content">{{ Math.trunc(selectedAttachment.id) }}</text>
                                </div>
                                <div class="info-item">
                                    <text class="info-label">类型</text>
                                    <text class="info-content">{{ selectedAttachment.ftype || '未知类型' }}</text>
                                </div>
                            </div>
                            <div class="info-row" v-if="selectedAttachment.fversion">
                                <div class="info-item">
                                    <text class="info-label">版本</text>
                                    <text class="info-content">{{ selectedAttachment.fversion }}</text>
                                </div>
                                <div class="info-item" v-if="selectedAttachment.fdate">
                                    <text class="info-label">受控日期</text>
                                    <text class="info-content">{{ formatDate(selectedAttachment.fdate) }}</text>
                                </div>
                            </div>
                            <div class="info-row" v-if="selectedAttachment.createBy">
                                <div class="info-item">
                                    <text class="info-label">上传人</text>
                                    <text class="info-content">{{ selectedAttachment.createBy }}</text>
                                </div>
                                <div class="info-item" v-if="selectedAttachment.createDate">
                                    <text class="info-label">上传时间</text>
                                    <text class="info-content">{{ formatDate(selectedAttachment.createDate) }}</text>
                                </div>
                            </div>
                        </div>
                        <div class="attachment-detail-actions">
                            <button v-if="isPreviewable(selectedAttachment.fattach)"
                                class="btn-primary"
                                @click="previewFtpFile(selectedAttachment)">预览</button>
                            <button class="btn-success" @click="downloadAttachment(selectedAttachment)">下载</button>
                        </div>
                    </div>
                    <div v-else class="attachment-detail-empty">
                        <div class="empty-icon">❌</div>
                        <div class="empty-text">暂无附件信息</div>
                    </div>
                </div>
                <div v-else class="attachment-detail-empty">暂无附件信息</div>
                <button class="attachment-popup-close" @click="closeAttachmentDetail">返回附件列表</button>
            </view>
        </view>
        <!-- 附件列表弹窗 -->
        <view v-if="showAttachmentPopup" class="overlay">
            <view class="popup" style="width: 60vw; max-width: 500px;">
                <h3>附件列表</h3>
                <div v-if="attachmentsLoading">加载中...</div>
                <div v-else-if="attachments.length === 0">暂无附件</div>
                <ul class="attachment-list" v-else>
                    <li v-for="item in attachments" :key="item.id">
                        <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>
            <view class="popup attachment-list-popup">
                <div class="attachment-popup-header">
                    <h3 class="attachment-popup-title">附件列表</h3>
                    <button class="attachment-close-btn" @click="closeAttachmentPopup">关闭</button>
                </div>
                <div class="attachment-popup-content">
                    <div v-if="attachmentsLoading" class="attachment-loading">
                        <div class="loading-spinner"></div>
                        <span class="loading-text">正在加载附件...</span>
                    </div>
                    <div v-else-if="attachments.length === 0" class="attachment-empty">
                        <div class="empty-icon">📁</div>
                        <div class="empty-text">暂无附件</div>
                        <div class="empty-hint">该物料暂未上传任何附件</div>
                    </div>
                    <div v-else class="attachment-list">
                        <div v-for="item in attachments" :key="item.id" class="attachment-item">
                            <div class="attachment-info">
                                <div class="file-type-badge" :class="getFileTypeClass(item.fattach)">
                                    {{ getFileTypeIcon(item.fattach) }}
                                </div>
                                <div class="attachment-details">
                                    <div class="attachment-name" @click="showAttachmentDetailDialog(item)">
                                        {{ item.fattach }}
                                    </div>
                                    <div class="attachment-meta">
                                        <span class="meta-type">{{ item.ftype || '未知类型' }}</span>
                                        <span v-if="item.fversion" class="meta-version">v{{ item.fversion }}</span>
                                        <span v-if="item.fdate" class="meta-date">{{ formatDate(item.fdate) }}</span>
                                    </div>
                                </div>
                            </div>
                            <div class="attachment-actions">
                                <button class="btn-secondary" @click="showAttachmentDetailDialog(item)">详情</button>
                                <button v-if="isPreviewable(item.fattach)"
                                    class="btn-primary"
                                    @click="previewFtpFile(item)">预览</button>
                                <button class="btn-success" @click="downloadAttachment(item)">下载</button>
                            </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>
                    </div>
                </div>
            </view>
        </view>
        
@@ -516,7 +578,7 @@
                WORKSHOP: '',
                REMARK: '',
                // picker 选项和索引
                badreasonOptions: ['', '外观不良', '尺寸不良', '包装不良', '性能不良', '装配不良', '安规不良'],
                badreasonOptions: ['', '外观不良', '尺寸不良', '包装不良', '性能不良', '装配不良', '安规不良','图纸不良'],
                badreasonIndex: 0,
                workshopOptions: ['', '生产一部', '生产二部', '注塑车间', '其他'],
                workshopIndex: 0,
@@ -771,7 +833,11 @@
                                // 统一推送给HMCS,不管哪个账号
                                this.QcIssueResultDetailes = {
                                    fbatchQty: this.formData.fcovertQty,
                                    itemName: this.formData.itemName,
                                    itemName: (() => {
                                            const combined = `${this.formData.itemName || ''} ${this.formData.itemModel || ''}`.trim();
                                            // 截断处理:超过1000字符保留前1000位
                                            return combined.length > 1000 ? combined.substring(0, 1000) : combined;
                                        })(),
                                    itemNo: this.formData.itemNo,
                                    suppName: this.formData.suppName,
                                    appicationReason: this.formData.fngDesc,
@@ -2198,6 +2264,59 @@
                    'csv'            // CSV文件
                ].includes(ext);
            },
            // 获取文件类型图标
            getFileTypeIcon(filename) {
                if (!filename) return '📄';
                const ext = filename.trim().split('.').pop().toLowerCase();
                const iconMap = {
                    'pdf': '📕',
                    'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️', 'bmp': '🖼️', 'webp': '🖼️',
                    'txt': '📝', 'log': '📝', 'md': '📝',
                    'doc': '📘', 'docx': '📘',
                    'xls': '📊', 'xlsx': '📊',
                    'ppt': '📙', 'pptx': '📙',
                    'csv': '📊',
                    'zip': '📦', 'rar': '📦', '7z': '📦',
                    'dwg': '🏗️', 'dxf': '🏗️'
                };
                return iconMap[ext] || '📄';
            },
            // 获取文件类型CSS类
            getFileTypeClass(filename) {
                if (!filename) return 'file-unknown';
                const ext = filename.trim().split('.').pop().toLowerCase();
                const classMap = {
                    'pdf': 'file-pdf',
                    'jpg': 'file-image', 'jpeg': 'file-image', 'png': 'file-image', 'gif': 'file-image', 'bmp': 'file-image', 'webp': 'file-image',
                    'txt': 'file-text', 'log': 'file-text', 'md': 'file-text',
                    'doc': 'file-word', 'docx': 'file-word',
                    'xls': 'file-excel', 'xlsx': 'file-excel',
                    'ppt': 'file-powerpoint', 'pptx': 'file-powerpoint',
                    'csv': 'file-excel',
                    'zip': 'file-archive', 'rar': 'file-archive', '7z': 'file-archive',
                    'dwg': 'file-cad', 'dxf': 'file-cad'
                };
                return classMap[ext] || 'file-unknown';
            },
            // 格式化日期
            formatDate(dateString) {
                if (!dateString) return '';
                try {
                    const date = new Date(dateString);
                    return date.toLocaleDateString('zh-CN', {
                        year: 'numeric',
                        month: '2-digit',
                        day: '2-digit',
                        hour: '2-digit',
                        minute: '2-digit'
                    });
                } catch (e) {
                    return dateString;
                }
            },
            // 处理附件下载错误
            handleAttachmentError(item) {
                uni.showModal({
@@ -3240,7 +3359,7 @@
    }
    /* 弹窗整体美化 */
    .popup, .attachment-detail-popup {
    .popup {
        background: #fff;
        border-radius: 16px;
        box-shadow: 0 8px 32px rgba(60,60,60,0.18);
@@ -3252,123 +3371,9 @@
        max-height: 80vh; /* 限制最大高度,避免被底部按钮遮挡 */
        overflow-y: auto; /* 内容过多时可滚动 */
    }
    .attachment-popup-title {
        font-size: 22px;
        font-weight: 700;
        color: #222;
        margin-bottom: 8px;
        letter-spacing: 1px;
        text-align: center;
    }
    .attachment-popup-divider {
        height: 1px;
        background: linear-gradient(90deg,#e0e7ef 0%,#f5f7fa 100%);
        margin-bottom: 18px;
    }
    .attachment-detail-content {
        margin-bottom: 18px;
    }
    .attachment-detail-row {
        display: flex;
        align-items: center;
        margin-bottom: 8px;
        font-size: 15px;
    }
    .attachment-label {
        min-width: 80px;
        color: #1976d2;
        font-weight: 500;
        margin-right: 8px;
    }
    .attachment-preview-area {
        margin: 18px 0 8px 0;
        border-radius: 10px;
        background: #f8fafc;
        padding: 10px;
        box-shadow: 0 2px 8px rgba(60,60,60,0.06);
    }
    .attachment-download-area {
        margin: 18px 0 8px 0;
        text-align: center;
    }
    .attachment-download-link {
        display: inline-block;
        padding: 7px 18px;
        background: linear-gradient(90deg,#4f8cff 0%,#1976d2 100%);
        color: #fff;
        border-radius: 6px;
        font-weight: 500;
        text-decoration: none;
        transition: background 0.2s;
        box-shadow: 0 2px 8px rgba(60,60,60,0.08);
    }
    .attachment-download-link:hover {
        background: linear-gradient(90deg,#1976d2 0%,#4f8cff 100%);
    }
    .attachment-detail-empty {
        color: #888;
        text-align: center;
        margin: 30px 0;
        font-size: 16px;
    }
    .attachment-popup-close {
        margin-top: 18px;
        width: 100%;
        background: linear-gradient(90deg,#e0e0e0 0%,#f5f7fa 100%);
        color: #444;
        border-radius: 8px;
        font-size: 16px;
        padding: 10px 0;
        border: none;
        font-weight: 600;
        letter-spacing: 1px;
        transition: background 0.2s, color 0.2s;
        box-shadow: 0 2px 8px rgba(60,60,60,0.06);
    }
    .attachment-popup-close:hover {
        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);
    }
    
    /* 文件预览弹窗样式 */
@@ -3487,84 +3492,321 @@
        background: linear-gradient(135deg, #bdbdbd, #9e9e9e);
        transform: translateY(-1px);
    }
    /* 列表弹窗美化(保留原有) */
    .attachment-list {
        padding: 0;
    /* 附件相关样式 */
    .attachment-list-popup {
        width: 80vw;
        max-width: 800px;
        max-height: 85vh;
    }
    .attachment-detail-popup {
        width: 70vw;
        max-width: 600px;
    }
    .attachment-popup-header {
        padding: 16px;
        border-bottom: 1px solid #eee;
        display: flex;
        justify-content: space-between;
        align-items: center;
        background-color: white;
    }
    .attachment-popup-title {
        font-size: 16px;
        font-weight: 600;
        color: #2c3e50;
        margin: 0;
        list-style: none;
        max-height: 300px;
    }
    .attachment-close-btn {
        padding: 8px 16px;
        border: 1px solid #ddd;
        border-radius: 4px;
        background-color: white;
        font-size: 14px;
        transition: all 0.2s;
        color: #2c3e50;
    }
    .attachment-close-btn:hover {
        background-color: #f8f9fa;
    }
    .attachment-popup-content {
        padding: 16px;
        max-height: 60vh;
        overflow-y: auto;
    }
    .attachment-list li {
    /* 加载状态 */
    .attachment-loading {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 40px 20px;
        gap: 16px;
    }
    .loading-spinner {
        width: 32px;
        height: 32px;
        border: 3px solid #f3f3f3;
        border-top: 3px solid #3498db;
        border-radius: 50%;
        animation: spin 1s linear infinite;
    }
    @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }
    .loading-text {
        font-size: 14px;
        color: #7f8c8d;
    }
    /* 空状态 */
    .attachment-empty {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 40px 20px;
        gap: 12px;
        text-align: center;
    }
    .empty-icon {
        font-size: 36px;
        opacity: 0.6;
    }
    .empty-text {
        font-size: 16px;
        color: #7f8c8d;
        font-weight: 500;
    }
    .empty-hint {
        font-size: 14px;
        color: #95a5a6;
    }
    /* 附件列表布局 */
    .attachment-list {
        display: flex;
        flex-direction: column;
        gap: 16px;
    }
    .attachment-item {
        background-color: white;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
        overflow: hidden;
        transition: all 0.3s;
        border: 1px solid #eee;
    }
    .attachment-item:hover {
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
    }
    .attachment-info {
        padding: 16px;
        border-bottom: 1px solid #eee;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 12px 0;
        border-bottom: 1px solid #f0f0f0;
        gap: 16px;
    }
    .attachment-info {
        flex: 1;
        margin-right: 10px;
    }
    .attachment-name {
        color: #3498db;
        cursor: pointer;
        font-weight: 500;
        transition: color 0.2s;
        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 {
    .file-type-badge {
        width: 40px;
        height: 40px;
        border-radius: 8px;
        display: flex;
        gap: 8px;
        align-items: center;
        justify-content: center;
        font-size: 20px;
        background: #f8f9fa;
        border: 2px solid #e9ecef;
        flex-shrink: 0;
    }
    .attachment-list .secondary-btn {
        padding: 4px 10px;
        font-size: 13px;
        border-radius: 3px;
        background: #f5f7fa;
        color: #333;
        border: 1px solid #dbe2ea;
        transition: background 0.2s, color 0.2s;
    .file-type-badge.large {
        width: 56px;
        height: 56px;
        font-size: 28px;
    }
    .attachment-list .secondary-btn:hover {
        background: #e6f0fa;
        color: #1976d2;
    .file-type-badge.file-pdf { background: #ffe6e6; border-color: #ffcccc; }
    .file-type-badge.file-image { background: #e6f3ff; border-color: #cce7ff; }
    .file-type-badge.file-text { background: #e6ffe6; border-color: #ccffcc; }
    .file-type-badge.file-word { background: #e6f0ff; border-color: #cce0ff; }
    .file-type-badge.file-excel { background: #e6ffe6; border-color: #ccffcc; }
    .file-type-badge.file-powerpoint { background: #fff0e6; border-color: #ffe0cc; }
    .file-type-badge.file-archive { background: #f0e6ff; border-color: #e0ccff; }
    .file-type-badge.file-cad { background: #e6fff0; border-color: #ccffe0; }
    .file-type-badge.file-unknown { background: #f5f5f5; border-color: #e0e0e0; }
    .attachment-details {
        flex: 1;
        min-width: 0;
    }
    .preview-btn {
        background: #e8f5e8 !important;
        color: #2e7d2e !important;
        border-color: #a5d6a5 !important;
    .attachment-name {
        font-size: 16px;
        font-weight: 600;
        color: #2c3e50;
        cursor: pointer;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        margin-bottom: 8px;
        transition: color 0.2s;
    }
    .preview-btn:hover {
        background: #d4eecc !important;
        color: #1e5f1e !important;
    .attachment-name:hover {
        color: #3498db;
    }
    .attachment-popup-close {
        margin-top: 18px;
        width: 100%;
        background: #e0e0e0;
        color: #444;
    .attachment-meta {
        display: flex;
        gap: 16px;
        font-size: 12px;
        color: #95a5a6;
    }
    .meta-type {
        background-color: #ecf0f1;
        padding: 2px 6px;
        border-radius: 10px;
        color: #7f8c8d;
    }
    .meta-version {
        background-color: #e8f5e8;
        padding: 2px 6px;
        border-radius: 10px;
        color: #2e7d32;
    }
    .meta-date {
        background-color: #fff3e0;
        padding: 2px 6px;
        border-radius: 10px;
        color: #f57c00;
    }
    .attachment-actions {
        padding: 12px 16px;
        border-top: 1px solid #eee;
        display: flex;
        gap: 8px;
        background-color: #f8f9fa;
    }
    /* 按钮样式 */
    .btn-secondary {
        padding: 8px 16px;
        border: 1px solid #ddd;
        border-radius: 4px;
        font-size: 15px;
        padding: 8px 0;
        border: none;
        transition: background 0.2s;
        background-color: white;
        font-size: 14px;
        transition: all 0.2s;
        color: #2c3e50;
        flex: 1;
    }
    .attachment-popup-close:hover {
        background: #bdbdbd;
    .btn-secondary:hover {
        background-color: #f8f9fa;
    }
    .btn-primary {
        padding: 8px 16px;
        border: 1px solid #3498db;
        border-radius: 4px;
        background-color: #3498db;
        color: white;
        font-size: 14px;
        transition: all 0.2s;
        flex: 1;
    }
    .btn-primary:hover {
        background-color: #2980b9;
    }
    .btn-success {
        padding: 8px 16px;
        border: 1px solid #2ecc71;
        border-radius: 4px;
        background-color: #2ecc71;
        color: white;
        font-size: 14px;
        transition: all 0.2s;
        flex: 1;
    }
    .btn-success:hover {
        background-color: #27ae60;
    }
    /* 附件详情样式 */
    .attachment-detail-header {
        display: flex;
        align-items: center;
        gap: 20px;
        margin-bottom: 20px;
        padding-bottom: 16px;
        border-bottom: 1px solid #eee;
    }
    .attachment-detail-title {
        font-size: 18px;
        font-weight: 600;
        color: #2c3e50;
        flex: 1;
        word-break: break-all;
    }
    .attachment-detail-info {
        margin-bottom: 20px;
    }
    .info-row {
        display: flex;
        margin-bottom: 12px;
        gap: 16px;
    }
    .info-item {
        flex: 1;
    }
    .info-label {
        display: block;
        font-size: 12px;
        color: #7f8c8d;
        margin-bottom: 4px;
    }
    .info-content {
        font-size: 14px;
        color: #2c3e50;
        line-height: 1.5;
    }
    .attachment-detail-actions {
        padding: 12px 16px;
        border-top: 1px solid #eee;
        display: flex;
        gap: 8px;
        background-color: #f8f9fa;
    }
    /* 响应式设计 */
pages/QC/LLJ/detail.vue
@@ -75,11 +75,19 @@
                    <text class="spec-text">{{ formData.fspecRequ }}</text>
                </view>
            </view>
            <!-- 规格要求 -->
            <!-- 检验描述 -->
            <view class="section">
                <view class="section-header">检验描述</view>
                <view class="section-body">
                    <text class="spec-text">{{ formData.fcheckItemDesc }}</text>
                    <!-- 查看附件信息按钮 -->
                    <view class="attachment-btn-container">
                        <button class="btn attachment-btn" @tap="viewAttachmentInfo">
                            <uni-icons type="folder" size="16" color="#fff"></uni-icons>
                            查看附件信息
                        </button>
                    </view>
                </view>
            </view>
            <!-- 检验结果 -->
@@ -139,6 +147,7 @@
                                 <uni-icons type="upload" size="16" color="#fff"></uni-icons>
                                 上传/查看图片
                             </button>
                             <button v-if="this.current" class="btn upload-btn" @tap="upRemarks">
                                 <uni-icons type="compose" size="16" color="#fff"></uni-icons>
                                 不良描述
@@ -184,6 +193,7 @@
                                 <uni-icons type="upload" size="16" color="#fff"></uni-icons>
                                 上传/查看图片
                             </button>
                             <button v-if="this.current" class="btn upload-btn" @tap="upRemarks">
                                 <uni-icons type="compose" size="16" color="#fff"></uni-icons>
                                 不良描述
@@ -322,7 +332,169 @@
                    </form>
                </view>
            </view>
            <!-- 附件列表弹窗 -->
            <view v-if="showAttachmentPopup" class="overlay">
                <view class="popup attachment-list-popup">
                    <div class="attachment-popup-header">
                        <h3 class="attachment-popup-title">附件列表</h3>
                        <button class="attachment-close-btn" @click="closeAttachmentPopup">关闭</button>
                    </div>
                    <div class="attachment-popup-content">
                        <div v-if="attachmentsLoading" class="attachment-loading">
                            <div class="loading-spinner"></div>
                            <span class="loading-text">正在加载附件...</span>
                        </div>
                        <div v-else-if="attachments.length === 0" class="attachment-empty">
                            <div class="empty-icon">📁</div>
                            <div class="empty-text">暂无附件</div>
                            <div class="empty-hint">该物料暂未上传任何附件</div>
                        </div>
                        <div v-else class="attachment-list">
                            <div v-for="item in attachments" :key="item.id" class="attachment-item">
                                <div class="attachment-info">
                                    <div class="file-type-badge" :class="getFileTypeClass(item.fattach)">
                                        {{ getFileTypeIcon(item.fattach) }}
                                    </div>
                                    <div class="attachment-details">
                                        <div class="attachment-name" @click="showAttachmentDetailDialog(item)">
                                            {{ item.fattach }}
                                        </div>
                                        <div class="attachment-meta">
                                            <span class="meta-type">{{ item.ftype || '未知类型' }}</span>
                                            <span v-if="item.fversion" class="meta-version">v{{ item.fversion }}</span>
                                            <span v-if="item.fdate" class="meta-date">{{ formatDate(item.fdate) }}</span>
                                        </div>
                                    </div>
                                </div>
                                <div class="attachment-actions">
                                    <button class="btn-secondary" @click="showAttachmentDetailDialog(item)">详情</button>
                                    <button v-if="isPreviewable(item.fattach)"
                                        class="btn-primary"
                                        @click="previewFtpFile(item)">预览</button>
                                    <button class="btn-success" @click="downloadAttachment(item)">下载</button>
                                </div>
                            </div>
                        </div>
                    </div>
                </view>
            </view>
            <!-- 附件详情弹窗 -->
            <view v-if="showAttachmentDetail" class="overlay">
                <view class="popup attachment-detail-popup">
                    <div class="attachment-popup-header">
                        <h3 class="attachment-popup-title">附件详情</h3>
                        <button class="attachment-close-btn" @click="closeAttachmentDetail">返回</button>
                    </div>
                    <div class="attachment-popup-content">
                        <div v-if="selectedAttachment" class="attachment-detail-content">
                            <div class="attachment-detail-header">
                                <div class="file-type-badge large" :class="getFileTypeClass(selectedAttachment.fattach)">
                                    {{ getFileTypeIcon(selectedAttachment.fattach) }}
                                </div>
                                <div class="attachment-detail-title">
                                    {{ selectedAttachment.fattach }}
                                </div>
                            </div>
                            <div class="attachment-detail-info">
                                <div class="info-row">
                                    <div class="info-item">
                                        <text class="info-label">ID</text>
                                        <text class="info-content">{{ Math.trunc(selectedAttachment.id) }}</text>
                                    </div>
                                    <div class="info-item">
                                        <text class="info-label">类型</text>
                                        <text class="info-content">{{ selectedAttachment.ftype || '未知类型' }}</text>
                                    </div>
                                </div>
                                <div class="info-row" v-if="selectedAttachment.fversion">
                                    <div class="info-item">
                                        <text class="info-label">版本</text>
                                        <text class="info-content">{{ selectedAttachment.fversion }}</text>
                                    </div>
                                    <div class="info-item" v-if="selectedAttachment.fdate">
                                        <text class="info-label">受控日期</text>
                                        <text class="info-content">{{ formatDate(selectedAttachment.fdate) }}</text>
                                    </div>
                                </div>
                                <div class="info-row" v-if="selectedAttachment.createBy">
                                    <div class="info-item">
                                        <text class="info-label">上传人</text>
                                        <text class="info-content">{{ selectedAttachment.createBy }}</text>
                                    </div>
                                    <div class="info-item" v-if="selectedAttachment.createDate">
                                        <text class="info-label">上传时间</text>
                                        <text class="info-content">{{ formatDate(selectedAttachment.createDate) }}</text>
                                    </div>
                                </div>
                            </div>
                            <div class="attachment-detail-actions">
                                <button v-if="isPreviewable(selectedAttachment.fattach)"
                                    class="btn-primary"
                                    @click="previewFtpFile(selectedAttachment)">预览</button>
                                <button class="btn-success" @click="downloadAttachment(selectedAttachment)">下载</button>
                            </div>
                        </div>
                        <div v-else class="attachment-detail-empty">
                            <div class="empty-icon">❌</div>
                            <div class="empty-text">暂无附件信息</div>
                        </div>
                    </div>
                </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="aspectFit"
                                class="preview-image-clickable"
                                @click="previewImageInPopup"
                                style="width: 100%; max-height: 400px; cursor: pointer;"
                            />
                            <div class="image-zoom-hint">点击图片可放大查看</div>
                        </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>
    </view>
</template>
@@ -357,6 +529,17 @@
                showMeom:false,
                meom: '',
                isFocus: false, // 新增,控制输入框聚焦
                // ===== 附件相关数据 =====
                showAttachmentPopup: false,
                showAttachmentDetail: false,
                showFilePreviewPopup: false,
                attachments: [],
                attachmentsLoading: false,
                selectedAttachment: null,
                previewTitle: '',
                previewContent: '',
                previewType: '',
                previewFileUrl: ''
            }
        },
        computed: {
@@ -664,6 +847,280 @@
                    url: 'ImageItem?id=' + this.formData.id
                });
            },
            // ===== 附件相关方法 =====
            viewAttachmentInfo() {
                this.showAttachmentPopup = true;
                this.attachmentsLoading = true;
                this.attachments = [];
                // fversion 的值是当前检验项目名称
                // 使用当前激活标签的 fcheckItem:tabs[currentTab].fcheckItem
                const currentTabData = this.tabs[this.currentTab];
                const fversion = currentTabData && currentTabData.fcheckItem ? currentTabData.fcheckItem : '';
                console.log("========================================");
                console.log("调用附件接口:");
                console.log("检验单号 releaseNo:", this.releaseNo);
                console.log("当前标签索引 currentTab:", this.currentTab);
                console.log("当前标签数据:", currentTabData);
                console.log("检验项目名称 fcheckItem:", currentTabData ? currentTabData.fcheckItem : '');
                console.log("版本号 fversion (检验项目名称):", fversion);
                console.log("========================================");
                // 先通过 releaseNo 获取物料编码
                this.$post({
                    url: "/LLJ/getPage",
                    data: {
                        PageIndex: 1,
                        Limit: 1,
                        selectedIndex: 5,  // 5表示按检验单号搜索
                        SearchValue: this.releaseNo,
                        createUser: this.$loginInfo.account,
                        UserIndex: this.$loginInfo.userIndex || "0"
                    }
                }).then(res => {
                    let tbBillListElement = res.data.tbBillList[0];
                    if (tbBillListElement && tbBillListElement.itemNo) {
                        const itemNo = tbBillListElement.itemNo;
                        console.log("获取到的物料编码:", itemNo);
                        console.log("最终调用 getAttachments 参数:", { itemNo, fversion, fromPage: 'Detail' });
                        // 调用附件接口
                        return this.$post({
                            url: "/LLJ/getAttachments",
                            data: {
                                itemNo: itemNo,
                                fversion: fversion,  // 使用检验项目ID作为版本号
                                fromPage: 'Detail'
                            }
                        });
                    } else {
                        throw new Error("未找到物料编码信息");
                    }
                }).then(res => {
                    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 {
                        uni.showToast({ title: "获取附件失败", icon: "none" });
                    }
                }).catch(err => {
                    this.attachmentsLoading = false;
                    console.error("获取附件失败:", err);
                    uni.showToast({ title: "获取附件失败,请重试", icon: "none" });
                });
            },
            closeAttachmentPopup() {
                this.showAttachmentPopup = false;
            },
            showAttachmentDetailDialog(item) {
                this.selectedAttachment = item;
                this.showAttachmentPopup = false;
                this.showAttachmentDetail = true;
            },
            closeAttachmentDetail() {
                this.showAttachmentDetail = false;
                this.selectedAttachment = null;
                this.showAttachmentPopup = true;
            },
            isPreviewable(filename) {
                if (!filename) return false;
                const ext = filename.trim().split('.').pop().toLowerCase();
                return [
                    'pdf',
                    'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp',
                    'txt', 'log', 'md',
                    'doc', 'docx',
                    'xls', 'xlsx',
                    'ppt', 'pptx',
                    'csv'
                ].includes(ext);
            },
            // 获取文件类型图标
            getFileTypeIcon(filename) {
                if (!filename) return '📄';
                const ext = filename.trim().split('.').pop().toLowerCase();
                const iconMap = {
                    'pdf': '📕',
                    'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️', 'bmp': '🖼️', 'webp': '🖼️',
                    'txt': '📝', 'log': '📝', 'md': '📝',
                    'doc': '📘', 'docx': '📘',
                    'xls': '📊', 'xlsx': '📊',
                    'ppt': '📙', 'pptx': '📙',
                    'csv': '📊',
                    'zip': '📦', 'rar': '📦', '7z': '📦',
                    'dwg': '🏗️', 'dxf': '🏗️'
                };
                return iconMap[ext] || '📄';
            },
            // 获取文件类型CSS类
            getFileTypeClass(filename) {
                if (!filename) return 'file-unknown';
                const ext = filename.trim().split('.').pop().toLowerCase();
                const classMap = {
                    'pdf': 'file-pdf',
                    'jpg': 'file-image', 'jpeg': 'file-image', 'png': 'file-image', 'gif': 'file-image', 'bmp': 'file-image', 'webp': 'file-image',
                    'txt': 'file-text', 'log': 'file-text', 'md': 'file-text',
                    'doc': 'file-word', 'docx': 'file-word',
                    'xls': 'file-excel', 'xlsx': 'file-excel',
                    'ppt': 'file-powerpoint', 'pptx': 'file-powerpoint',
                    'csv': 'file-excel',
                    'zip': 'file-archive', 'rar': 'file-archive', '7z': 'file-archive',
                    'dwg': 'file-cad', 'dxf': 'file-cad'
                };
                return classMap[ext] || 'file-unknown';
            },
            // 格式化日期
            formatDate(dateString) {
                if (!dateString) return '';
                try {
                    const date = new Date(dateString);
                    return date.toLocaleDateString('zh-CN', {
                        year: 'numeric',
                        month: '2-digit',
                        day: '2-digit',
                        hour: '2-digit',
                        minute: '2-digit'
                    });
                } catch (e) {
                    return dateString;
                }
            },
            downloadAttachment(item) {
                const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim();
                const downloadUrl = this.$store.state.serverInfo.serverAPI + "/LLJ/DownloadFtpFile?itemNo=" +
                    encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) +
                    "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer) +
                    "&fversion=" + encodeURIComponent(item.fversion || '');
                uni.downloadFile({
                    url: downloadUrl,
                    success: (res) => {
                        if (res.statusCode === 200) {
                            uni.showToast({ title: '下载成功', icon: 'success' });
                        } else {
                            uni.showToast({ title: '下载失败', icon: 'none' });
                        }
                    },
                    fail: (err) => {
                        console.error('下载失败:', err);
                        uni.showToast({ title: '下载失败,请重试', icon: 'none' });
                    }
                });
            },
            previewFtpFile(item) {
                const fileName = item.fattach.replace(/[\s\u3000\r\n]+/g, '').trim();
                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) +
                    "&fversion=" + encodeURIComponent(item.fversion || '');
                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 {
                    this.previewOfficeFile(previewUrl, fileName);
                }
            },
            previewPdfFile(url, fileName) {
                this.previewTitle = fileName;
                this.previewContent = url;
                this.previewType = 'pdf';
                this.previewFileUrl = url;
                this.showFilePreviewPopup = true;
                this.showAttachmentDetail = false;
            },
            previewImageFile(url, fileName) {
                this.previewTitle = fileName;
                this.previewContent = url;
                this.previewType = 'image';
                this.previewFileUrl = url;
                this.showFilePreviewPopup = true;
                this.showAttachmentDetail = false;
            },
            previewTextFile(url, fileName) {
                this.previewTitle = fileName;
                this.previewType = 'text';
                this.previewFileUrl = url;
                this.showFilePreviewPopup = true;
                this.showAttachmentDetail = false;
                uni.request({
                    url: url,
                    method: 'GET',
                    success: (res) => {
                        this.previewContent = res.data;
                    },
                    fail: (err) => {
                        this.previewContent = '预览失败,请下载后查看';
                    }
                });
            },
            previewOfficeFile(url, fileName) {
                this.previewTitle = fileName;
                this.previewContent = '';
                this.previewType = 'excel';
                this.previewFileUrl = url;
                this.showFilePreviewPopup = true;
                this.showAttachmentDetail = false;
            },
            closeFilePreview() {
                this.showFilePreviewPopup = false;
                this.showAttachmentDetail = true;
            },
            downloadPreviewFile() {
                if (this.previewFileUrl && this.selectedAttachment) {
                    this.downloadAttachment(this.selectedAttachment);
                }
            },
            previewImageInPopup() {
                // 在新窗口中打开图片进行放大查看
                if (this.previewContent) {
                    uni.previewImage({
                        urls: [this.previewContent],
                        current: this.previewContent
                    });
                }
            },
            upRemarks() {
                this.remarksPopup = true;
            },
@@ -1311,4 +1768,434 @@
        font-weight: bold;
        font-size: 12px;
    }
    /* 附件按钮样式 */
    .attachment-btn-container {
        margin-top: 16px;
        display: flex;
        justify-content: flex-start;
    }
    .attachment-btn {
        background-color: #17a2b8;
        color: #fff;
        padding: 10px 20px;
        margin: 0;
        display: flex;
        align-items: center;
        gap: 8px;
        border-radius: 4px;
        font-size: 14px;
        transition: all 0.2s;
    }
    .attachment-btn:hover {
        background-color: #138496;
        transform: translateY(-1px);
        box-shadow: 0 2px 8px rgba(23, 162, 184, 0.3);
    }
    /* 附件相关样式 */
    .attachment-list-popup {
        width: 80vw;
        max-width: 800px;
        max-height: 85vh;
    }
    .attachment-detail-popup {
        width: 70vw;
        max-width: 600px;
    }
    .attachment-popup-header {
        padding: 16px;
        border-bottom: 1px solid #eee;
        display: flex;
        justify-content: space-between;
        align-items: center;
        background-color: white;
    }
    .attachment-popup-title {
        font-size: 16px;
        font-weight: 600;
        color: #2c3e50;
        margin: 0;
    }
    .attachment-close-btn {
        padding: 8px 16px;
        border: 1px solid #ddd;
        border-radius: 4px;
        background-color: white;
        font-size: 14px;
        transition: all 0.2s;
        color: #2c3e50;
    }
    .attachment-close-btn:hover {
        background-color: #f8f9fa;
    }
    .attachment-popup-content {
        padding: 16px;
        max-height: 60vh;
        overflow-y: auto;
    }
    /* 加载状态 */
    .attachment-loading {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 40px 20px;
        gap: 16px;
    }
    .loading-spinner {
        width: 32px;
        height: 32px;
        border: 3px solid #f3f3f3;
        border-top: 3px solid #3498db;
        border-radius: 50%;
        animation: spin 1s linear infinite;
    }
    @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }
    .loading-text {
        font-size: 14px;
        color: #7f8c8d;
    }
    /* 空状态 */
    .attachment-empty {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 40px 20px;
        gap: 12px;
        text-align: center;
    }
    .empty-icon {
        font-size: 36px;
        opacity: 0.6;
    }
    .empty-text {
        font-size: 16px;
        color: #7f8c8d;
        font-weight: 500;
    }
    .empty-hint {
        font-size: 14px;
        color: #95a5a6;
    }
    /* 附件列表布局 */
    .attachment-list {
        display: flex;
        flex-direction: column;
        gap: 16px;
    }
    .attachment-item {
        background-color: white;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
        overflow: hidden;
        transition: all 0.3s;
        border: 1px solid #eee;
    }
    .attachment-item:hover {
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
    }
    .attachment-info {
        padding: 16px;
        border-bottom: 1px solid #eee;
        display: flex;
        align-items: center;
        gap: 16px;
    }
    .file-type-badge {
        width: 40px;
        height: 40px;
        border-radius: 8px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 20px;
        background: #f8f9fa;
        border: 2px solid #e9ecef;
        flex-shrink: 0;
    }
    .file-type-badge.large {
        width: 56px;
        height: 56px;
        font-size: 28px;
    }
    .file-type-badge.file-pdf { background: #ffe6e6; border-color: #ffcccc; }
    .file-type-badge.file-image { background: #e6f3ff; border-color: #cce7ff; }
    .file-type-badge.file-text { background: #e6ffe6; border-color: #ccffcc; }
    .file-type-badge.file-word { background: #e6f0ff; border-color: #cce0ff; }
    .file-type-badge.file-excel { background: #e6ffe6; border-color: #ccffcc; }
    .file-type-badge.file-powerpoint { background: #fff0e6; border-color: #ffe0cc; }
    .file-type-badge.file-archive { background: #f0e6ff; border-color: #e0ccff; }
    .file-type-badge.file-cad { background: #e6fff0; border-color: #ccffe0; }
    .file-type-badge.file-unknown { background: #f5f5f5; border-color: #e0e0e0; }
    .attachment-details {
        flex: 1;
        min-width: 0;
    }
    .attachment-name {
        font-size: 16px;
        font-weight: 600;
        color: #2c3e50;
        cursor: pointer;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        margin-bottom: 8px;
        transition: color 0.2s;
    }
    .attachment-name:hover {
        color: #3498db;
    }
    .attachment-meta {
        display: flex;
        gap: 16px;
        font-size: 12px;
        color: #95a5a6;
    }
    .meta-type {
        background-color: #ecf0f1;
        padding: 2px 6px;
        border-radius: 10px;
        color: #7f8c8d;
    }
    .meta-version {
        background-color: #e8f5e8;
        padding: 2px 6px;
        border-radius: 10px;
        color: #2e7d32;
    }
    .meta-date {
        background-color: #fff3e0;
        padding: 2px 6px;
        border-radius: 10px;
        color: #f57c00;
    }
    .attachment-actions {
        padding: 12px 16px;
        border-top: 1px solid #eee;
        display: flex;
        gap: 8px;
        background-color: #f8f9fa;
    }
    /* 按钮样式 */
    .btn-secondary {
        padding: 8px 16px;
        border: 1px solid #ddd;
        border-radius: 4px;
        background-color: white;
        font-size: 14px;
        transition: all 0.2s;
        color: #2c3e50;
        flex: 1;
    }
    .btn-secondary:hover {
        background-color: #f8f9fa;
    }
    .btn-primary {
        padding: 8px 16px;
        border: 1px solid #3498db;
        border-radius: 4px;
        background-color: #3498db;
        color: white;
        font-size: 14px;
        transition: all 0.2s;
        flex: 1;
    }
    .btn-primary:hover {
        background-color: #2980b9;
    }
    .btn-success {
        padding: 8px 16px;
        border: 1px solid #2ecc71;
        border-radius: 4px;
        background-color: #2ecc71;
        color: white;
        font-size: 14px;
        transition: all 0.2s;
        flex: 1;
    }
    .btn-success:hover {
        background-color: #27ae60;
    }
    /* 附件详情样式 */
    .attachment-detail-header {
        display: flex;
        align-items: center;
        gap: 20px;
        margin-bottom: 20px;
        padding-bottom: 16px;
        border-bottom: 1px solid #eee;
    }
    .attachment-detail-title {
        font-size: 18px;
        font-weight: 600;
        color: #2c3e50;
        flex: 1;
        word-break: break-all;
    }
    .attachment-detail-info {
        margin-bottom: 20px;
    }
    .info-row {
        display: flex;
        margin-bottom: 12px;
        gap: 16px;
    }
    .info-content {
        font-size: 14px;
        color: #2c3e50;
        line-height: 1.5;
    }
    .attachment-detail-actions {
        padding: 12px 16px;
        border-top: 1px solid #eee;
        display: flex;
        gap: 8px;
        background-color: #f8f9fa;
    }
    /* 文件预览弹窗样式 */
    .file-preview-popup {
        width: 80vw;
        max-width: 800px;
        max-height: 80vh;
    }
    .file-preview-title {
        padding: 20px;
        margin: 0;
        font-size: 16px;
        font-weight: 600;
        color: #2c3e50;
        border-bottom: 1px solid #eee;
        word-break: break-all;
    }
    .file-preview-content {
        padding: 20px;
        max-height: 60vh;
        overflow-y: auto;
    }
    .file-preview-content pre {
        white-space: pre-wrap;
        word-wrap: break-word;
        font-family: 'Courier New', monospace;
        font-size: 12px;
        line-height: 1.4;
        background: #f8f9fa;
        padding: 15px;
        border-radius: 4px;
        border: 1px solid #e9ecef;
    }
    .image-preview-container {
        text-align: center;
    }
    .image-zoom-hint {
        margin-top: 10px;
        font-size: 12px;
        color: #7f8c8d;
    }
    .unsupported-preview {
        text-align: center;
        padding: 40px 20px;
    }
    .unsupported-icon {
        font-size: 48px;
        margin-bottom: 16px;
    }
    .unsupported-text {
        font-size: 16px;
        color: #7f8c8d;
        margin-bottom: 8px;
    }
    .unsupported-hint {
        font-size: 14px;
        color: #95a5a6;
    }
    .file-preview-actions {
        padding: 15px 20px;
        border-top: 1px solid #eee;
        display: flex;
        gap: 10px;
        justify-content: center;
    }
    .file-preview-btn {
        padding: 8px 16px;
        border-radius: 4px;
        border: none;
        font-size: 14px;
        cursor: pointer;
        transition: all 0.2s;
    }
    .file-preview-btn.download-btn {
        background: #2ecc71;
        color: white;
    }
    .file-preview-btn.close-btn {
        background: #95a5a6;
        color: white;
    }
    .file-preview-btn:hover {
        transform: translateY(-1px);
        box-shadow: 0 2px 8px rgba(0,0,0,0.15);
    }
</style>
pages/QC/RKJ/Add.vue
@@ -197,13 +197,15 @@
            <table>
                <thead>
                    <tr>
                        <th width="20%" style="text-align: center;">检验项目</th>
                        <th width="8%" style="text-align: center;">序号</th>
                        <th width="17%" style="text-align: center;">检验项目</th>
                        <th width="50%" style="text-align: center;">检验描述</th>
                        <th width="15%" style="text-align: center;">记录(点击)</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(item, index) in tableData" :key="index">
                        <td style="text-align: center;">{{ item.forder || (index + 1) }}</td>
                        <td>{{ item.projName }}</td>
                        <td>
                            <view v-if="item.result=='合格'" class="watermark approved">
@@ -990,16 +992,10 @@
                    }
                }).then(res1 => {
                    let tableData = res1.data.tbBillList || [];
                    //当已检验个数都不为空时按照检测结构排序
                    // 按FORDER序号排序
                    tableData.sort((a, b) => {
                        if (a.result === '未完成' && b.result === '合格') {
                            return -1;
                        } else if (a.result === '合格' && b.result === '未完成') {
                            return 1;
                        } else {
                            return 0;
                        }
                        return (a.forder || 0) - (b.forder || 0);
                    });
                    this.tableData = tableData;
@@ -2625,6 +2621,7 @@
        padding: 12px 15px;
        border: 1px solid #ddd;
        text-align: left;
        vertical-align: middle;
    }
    .inspection-table th {
@@ -2678,10 +2675,11 @@
    }
    /* 调整表格单元格 */
    .inspection-table td:nth-child(2) {
    .inspection-table td:nth-child(3) {
        position: relative;
        overflow: hidden;
        padding: 0;
        min-height: 80px;
    }
    /* 表单上方操作按钮区样式 */
pages/QC/RKJ/List.vue
@@ -215,6 +215,10 @@
        this.isLoading = true;
        // 获取车间和线体筛选条件
        const departmentId = uni.getStorageSync('qc_filter_departmentId') || '';
        const lineId = uni.getStorageSync('qc_filter_lineId') || '';
        //页面加载时调用的事件
        this.$post({
          url: "/RKJ/getPage",
@@ -225,7 +229,9 @@
            fsubmit: fsubmit,
            SearchValue: SearchValue,
            selectedIndex: this.optionsIndex, // 新增:搜索条件索引
            searchField: this.selectedField   // 新增:搜索字段名
            searchField: this.selectedField,  // 新增:搜索字段名
            departmentId: departmentId,       // 新增:车间ID
            lineId: lineId                     // 新增:线体ID
          }
        }).then(res => {
          if (this.pageIndex === 1) {
pages/QC/SJ/Add.vue
@@ -78,13 +78,15 @@
            <table v-if="tableData.length > 0">
                <thead>
                    <tr>
                        <th width="20%" style="text-align: center;">检验项目</th>
                        <th width="8%" style="text-align: center;">序号</th>
                        <th width="17%" style="text-align: center;">检验项目</th>
                        <th width="50%" style="text-align: center;">检验描述</th>
                        <th width="15%" style="text-align: center;">记录(点击)</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="(item, index) in tableData" :key="index">
                        <td style="text-align: center;">{{ item.forder || (index + 1) }}</td>
                        <td>{{ item.projName }}</td>
                        <td>
                            <view v-if="item.result=='合格'" class="watermark approved">
@@ -647,14 +649,9 @@
                            }).then(res => {
                                this.tableData = res.data.tbBillList;
                                // 按FORDE...
                                this.tableData.sort((a, b) => {
                                    if (a.result === '未完成' && b.result === '合格') {
                                        return -1;
                                    } else if (a.result === '合格' && b.result === '未完成') {
                                        return 1;
                                    } else {
                                        return 0;
                                    }
                                    return (a.forder || 0) - (b.forder || 0);
                                });
                                if (this.tableData.length <= 0) {
@@ -1689,6 +1686,7 @@
  border: none;
  text-align: left;
  border-bottom: 1px solid #eee;
  vertical-align: middle;
}
.inspection-table th {
@@ -1717,10 +1715,10 @@
}
/* 检验描述列特殊样式 */
.inspection-table td:nth-child(2) {
.inspection-table td:nth-child(3) {
  position: relative;
  min-height: 80px;
  vertical-align: top;
  vertical-align: middle;
  padding: 16px 20px;
}
pages/QC/SJ/List.vue
@@ -171,6 +171,10 @@
        result = "已完成";
      }
      
      // 获取车间和线体筛选条件
      const departmentId = uni.getStorageSync('qc_filter_departmentId') || '';
      const lineId = uni.getStorageSync('qc_filter_lineId') || '';
      // ===== 修改后的API调用,添加搜索字段参数 =====
      //页面加载时调用的事件
      this.$post({
@@ -182,7 +186,9 @@
          result: result,
          SearchValue: SearchValue,
          selectedIndex: this.optionsIndex, // 新增:搜索条件索引
          searchField: this.selectedField   // 新增:搜索字段名
          searchField: this.selectedField,  // 新增:搜索字段名
          departmentId: departmentId,       // 新增:车间ID
          lineId: lineId                     // 新增:线体ID
        }
      }).then(res => {
        this.data = res.data.tbBillList;
pages/QC/XJ/Add.vue
@@ -79,13 +79,15 @@
            <table>
              <thead>
                <tr>
                  <th width="20%" style="text-align: center;">检验项目</th>
                  <th width="8%" style="text-align: center;">序号</th>
                  <th width="17%" style="text-align: center;">检验项目</th>
                  <th width="50%" style="text-align: center;">检验描述</th>
                  <th width="15%" style="text-align: center;">记录(点击)</th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="(item, index) in tableData" :key="index">
                  <td style="text-align: center;">{{ item.forder || (index + 1) }}</td>
                  <td>{{ item.projName }}</td>
                  <td>
                    <view v-if="item.result=='合格'" class="watermark approved">
@@ -855,16 +857,10 @@
                 pid: this.formData.id
               }
             }).then(res1 => {
               let tableData = res1.data.tbBillList
               //当已检验个数都不为空时按照检测结构排序
               let tableData = res1.data.tbBillList;
               // 按FORDER序号排序
               tableData.sort((a, b) => {
                 if (a.result === '未完成' && b.result === '合格') {
                   return -1;
                 } else if (a.result === '合格' && b.result === '未完成') {
                   return 1;
                 } else {
                   return 0;
                 }
                 return (a.forder || 0) - (b.forder || 0);
               });
               this.tableData = tableData;
               if (this.tableData.length === 0) {
@@ -1664,6 +1660,7 @@
    padding: 12px 15px;
    text-align: left;
    border: none;
    vertical-align: middle;
  }
  .inspection-table tr:nth-child(even) {
@@ -1713,10 +1710,11 @@
  }
  
  /* 调整表格单元格 */
  .inspection-table td:nth-child(2) {
  .inspection-table td:nth-child(3) {
    position: relative;
    overflow: hidden;
    padding: 0;
    min-height: 80px;
  }
  
  .record-btn {
pages/QC/XJ/List.vue
@@ -155,6 +155,10 @@
        result = "已完成";
      }
      // 获取车间和线体筛选条件
      const departmentId = uni.getStorageSync('qc_filter_departmentId') || '';
      const lineId = uni.getStorageSync('qc_filter_lineId') || '';
      //页面加载时调用的事件
      this.$post({
        url: "/XJ/getPage",
@@ -165,7 +169,9 @@
          result: result,
          SearchValue: SearchValue,
          selectedIndex: this.optionsIndex, // 新增:搜索条件索引
          searchField: this.selectedField   // 新增:搜索字段名
          searchField: this.selectedField,  // 新增:搜索字段名
          departmentId: departmentId,       // 新增:车间ID
          lineId: lineId                     // 新增:线体ID
        }
      }).then(res => {
        this.data = res.data.tbBillList;
store/index.js
@@ -10,9 +10,9 @@
            networkFlag:'内网', 
            serverURLInt:'http://192.168.11.251:10055',//服务器体检 10.0.1.104:10054
            serverURL:'http://localhost:10055',//本地调试地址
            //serverAPI:'http://localhost:5184/api',//当前正在使用的服务器,默认为外网  localhost
            //serverAPI:'http://192.168.1.22:10054/api',//内网
            serverAPI:'http://36.26.21.214:10055/api',
            serverAPI:'http://localhost:5184/api',//当前正在使用的服务器,默认为外网  localhost
            //serverAPI:'http://192.168.1.22:10055/api',//内网
            //serverAPI:'http://36.26.21.214:10055/api',
            ftpServer:'ftp://36.26.21.214',//FTP服务器地址
        }
    },