xwt
2025-09-25 67d42a761ea34624cfb2de3cbb38f37a319d2631
SJ,XJ,RKJ优化
已修改7个文件
2738 ■■■■■ 文件已修改
pages/QC/RKJ/Add.vue 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/RKJ/List.vue 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/SJ/Add.vue 1142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/SJ/detail.vue 906 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/XJ/Add.vue 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/XJ/List.vue 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
store/index.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/QC/RKJ/Add.vue
@@ -240,6 +240,7 @@
                Bom用料清单
            </button>
            <button class="action-btn small" @click="viewAttachmentInfo">查看附件信息</button>
            <button class="action-btn small danger" @click="showDeleteConfirmDialog" v-if="formData.id && formData.fsubmit != 1">删除单据</button>
            <button class="action-btn small" @click="saveRemarks" v-if="formData.fsubmit != 1">添加不合格描述</button>
            <button class="action-btn small primary" @click="submitInspection"
                v-if="formData.fsubmit != 1 && tableData.length > 0">提交检验</button>
@@ -419,6 +420,32 @@
                </div>
            </view>
        </view>
        <!-- 删除确认弹窗 -->
        <view v-if="showDeleteConfirm" class="overlay">
            <view class="popup delete-confirm-popup">
                <h3 class="delete-confirm-title">⚠️ 确认删除</h3>
                <div class="delete-confirm-divider"></div>
                <div class="delete-confirm-content">
                    <view class="delete-warning-icon">🗑️</view>
                    <view class="delete-warning-text">您确定要删除此检验单吗?</view>
                    <view class="delete-warning-detail">删除后将无法恢复,请谨慎操作!</view>
                    <view class="delete-countdown">
                        <view class="countdown-text">确认按钮将在 <text class="countdown-number">{{ deleteCountdown }}</text> 秒后可用</view>
                        <view class="countdown-progress">
                            <view class="countdown-bar" :style="{ width: countdownProgress + '%' }"></view>
                        </view>
                    </view>
                </div>
                <div class="delete-confirm-actions">
                    <button class="delete-confirm-btn cancel-btn" @click="cancelDelete">取消</button>
                    <button class="delete-confirm-btn confirm-btn"
                        :disabled="deleteCountdown > 0"
                        :class="{ 'disabled': deleteCountdown > 0 }"
                        @click="confirmDelete">确认删除</button>
                </div>
            </view>
        </view>
    </view>
</template>
@@ -499,6 +526,12 @@
                workshopIndex: 0,
                pstypeOptions: ['', '特采/让步使用', '挑选/返工使用', '退货', '待判'],
                pstypeIndex: 0,
                // 删除确认相关数据
                showDeleteConfirm: false,
                deleteCountdown: 5,
                countdownProgress: 0,
                deleteTimer: null,
            };
        },
        onLoad(options) {
@@ -552,6 +585,53 @@
                }
            },
            // 显示删除确认弹窗
            showDeleteConfirmDialog() {
                this.showDeleteConfirm = true;
                this.deleteCountdown = 5;
                this.countdownProgress = 0;
                this.startDeleteCountdown();
            },
            // 开始倒计时
            startDeleteCountdown() {
                this.deleteTimer = setInterval(() => {
                    this.deleteCountdown--;
                    this.countdownProgress = ((5 - this.deleteCountdown) / 5) * 100;
                    if (this.deleteCountdown <= 0) {
                        clearInterval(this.deleteTimer);
                        this.deleteTimer = null;
                    }
                }, 1000);
            },
            // 取消删除
            cancelDelete() {
                this.showDeleteConfirm = false;
                if (this.deleteTimer) {
                    clearInterval(this.deleteTimer);
                    this.deleteTimer = null;
                }
                this.deleteCountdown = 5;
                this.countdownProgress = 0;
            },
            // 确认删除
            confirmDelete() {
                if (this.deleteCountdown > 0) {
                    return;
                }
                this.showDeleteConfirm = false;
                if (this.deleteTimer) {
                    clearInterval(this.deleteTimer);
                    this.deleteTimer = null;
                }
                this.removeXJ();
            },
            removeXJ() {
                if (this.formData.id) {
                    this.$post({
@@ -2622,6 +2702,15 @@
        background-color: #2980b9;
    }
    .action-btn.danger {
        background-color: #e74c3c;
        color: #fff;
    }
    .action-btn.danger:hover {
        background-color: #c0392b;
    }
    /* 小尺寸按钮样式 */
    .action-btn.small {
        padding: 10px 12px;
@@ -3097,4 +3186,145 @@
        flex-direction: column;
        align-items: center;
    }
    /* 删除确认弹窗样式 */
    .delete-confirm-popup {
        width: 90vw;
        max-width: 400px;
        max-height: 70vh;
        display: flex;
        flex-direction: column;
    }
    .delete-confirm-title {
        font-size: 20px;
        font-weight: 700;
        color: #e74c3c;
        margin-bottom: 8px;
        text-align: center;
        letter-spacing: 1px;
    }
    .delete-confirm-divider {
        height: 2px;
        background: linear-gradient(90deg, #e74c3c 0%, #c0392b 100%);
        margin-bottom: 20px;
    }
    .delete-confirm-content {
        text-align: center;
        margin-bottom: 20px;
    }
    .delete-warning-icon {
        font-size: 48px;
        margin-bottom: 16px;
        animation: shake 0.5s ease-in-out infinite alternate;
    }
    @keyframes shake {
        0% { transform: translateX(-2px); }
        100% { transform: translateX(2px); }
    }
    .delete-warning-text {
        font-size: 18px;
        font-weight: 600;
        color: #2c3e50;
        margin-bottom: 8px;
    }
    .delete-warning-detail {
        font-size: 14px;
        color: #7f8c8d;
        margin-bottom: 20px;
        line-height: 1.4;
    }
    .delete-countdown {
        background: #f8f9fa;
        border-radius: 8px;
        padding: 16px;
        margin: 16px 0;
        border: 1px solid #e9ecef;
    }
    .countdown-text {
        font-size: 14px;
        color: #495057;
        margin-bottom: 8px;
        text-align: center;
    }
    .countdown-number {
        font-weight: 700;
        color: #e74c3c;
        font-size: 16px;
    }
    .countdown-progress {
        width: 100%;
        height: 6px;
        background: #e9ecef;
        border-radius: 3px;
        overflow: hidden;
    }
    .countdown-bar {
        height: 100%;
        background: linear-gradient(90deg, #e74c3c, #c0392b);
        border-radius: 3px;
        transition: width 1s ease;
    }
    .delete-confirm-actions {
        display: flex;
        gap: 12px;
        justify-content: center;
    }
    .delete-confirm-btn {
        padding: 12px 24px;
        border: none;
        border-radius: 8px;
        font-size: 16px;
        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);
    }
    .delete-confirm-btn.cancel-btn {
        background: linear-gradient(135deg, #95a5a6, #7f8c8d);
        color: white;
    }
    .delete-confirm-btn.cancel-btn:hover {
        background: linear-gradient(135deg, #7f8c8d, #6c7b7d);
        transform: translateY(-1px);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
    }
    .delete-confirm-btn.confirm-btn {
        background: linear-gradient(135deg, #e74c3c, #c0392b);
        color: white;
    }
    .delete-confirm-btn.confirm-btn:hover:not(.disabled) {
        background: linear-gradient(135deg, #c0392b, #a93226);
        transform: translateY(-1px);
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
    }
    .delete-confirm-btn.disabled {
        background: #bdc3c7;
        color: #7f8c8d;
        cursor: not-allowed;
        transform: none;
        box-shadow: none;
    }
</style>
pages/QC/RKJ/List.vue
@@ -6,13 +6,26 @@
      <!-- 顶部筛选区 -->
      <view class="filter-section">
        <view class="filter-controls">
          <!-- 查询条件选择器 -->
          <view class="dropdown-filter">
            <picker @change="onOptionsChange" :value="optionsIndex" :range="options">
              <view class="picker">{{options[optionsIndex]}}</view>
            </picker>
          </view>
          <!-- 搜索框 -->
          <view class="search-container">
            <input class="search-input" v-model="searchValue" :placeholder="'请输入'+options[optionsIndex]" @confirm="btnclicked" />
            <button class="search-button" @click="btnclicked">搜索</button>
          </view>
          <!-- 状态切换标签 -->
          <view class="status-tabs">
            <button :class="['tab-button', current === 0 ? 'active' : '']" @click="onClickItem({currentIndex: 0})">
              {{items[0]}}
              未提交({{unsubmittedCount}})
            </button>
            <button :class="['tab-button', current === 1 ? 'active' : '']" @click="onClickItem({currentIndex: 1})">
              {{items[1]}}
              已提交({{submittedCount}})
            </button>
          </view>
        </view>
@@ -115,7 +128,15 @@
        totalCount: 0,
        noData: false, // 没有更多数据了
        isLoading: false, // 是否正在加载
        tipShow: false
        tipShow: false,
        searchValue: '',
        // ===== 新增搜索功能相关数据 =====
        optionsIndex: 0, // 当前选择的查询条件索引
        options: ['工单', '检验单号', '产线', '物料编码', '物料名称'], // 查询条件选项
        selectedField: 'billNo', // 当前选择的查询字段,默认为工单
        // ===== 新增数量统计变量 =====
        unsubmittedCount: 0, // 未提交数量
        submittedCount: 0 // 已提交数量
      };
    },
    onLoad() {
@@ -123,7 +144,30 @@
      this.init();
    },
    methods: {
            init() {
      // ===== 新增查询条件选择方法 =====
      onOptionsChange(e) {
        this.optionsIndex = e.detail.value;
        // 根据选择的选项设置搜索字段
        const fieldMap = {
          0: 'billNo',     // 工单
          1: 'releaseNo',  // 检验单号
          2: 'daa015',     // 产线
          3: 'itemNo',     // 物料编码
          4: 'itemName'    // 物料名称
        };
        this.selectedField = fieldMap[this.optionsIndex];
      },
      //搜索框点击事件
      btnclicked() {
        this.pageIndex = 1;
        this.data = [];
        this.init();
      },
      init() {
        //获取搜索条件内容
        let SearchValue = this.searchValue;
        let fsubmit = null; // 默认查询所有未提交的记录(包括fsubmit = 0和fsubmit为空)
        if (this.current == 1) {
@@ -141,7 +185,10 @@
            pageIndex: this.pageIndex,
            limit: this.limit,
            createUser: this.$loginInfo.account,
            fsubmit: fsubmit
            fsubmit: fsubmit,
            SearchValue: SearchValue,
            selectedIndex: this.optionsIndex, // 新增:搜索条件索引
            searchField: this.selectedField   // 新增:搜索字段名
          }
        }).then(res => {
          if (this.pageIndex === 1) {
@@ -157,6 +204,13 @@
          }
          this.totalCount = res.data.totalCount;
          this.totalPage = Math.ceil(this.totalCount / this.limit);
          // 设置数量统计 - 参考XJ的实现方式
          if (this.current === 1) {
            this.submittedCount = res.data.totalCount;
          } else {
            this.unsubmittedCount = res.data.totalCount;
          }
  
          this.noData = this.pageIndex >= this.totalPage;
          this.isLoading = false; // 结束加载
@@ -164,6 +218,7 @@
          this.isLoading = false; // 出现错误时结束加载
        });
      },
      handleFabClick() {
        uni.navigateTo({
          url: 'Add?id'
@@ -231,8 +286,51 @@
  
  .filter-controls {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
  }
  /* 查询条件选择器 */
  .dropdown-filter {
    min-width: 80px;
    margin-right: 10px;
  }
  .picker {
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    background-color: white;
    font-size: 14px;
    min-width: 80px;
  }
  /* 搜索框样式 */
  .search-container {
    display: flex;
    flex: 1;
    margin-right: 10px;
    height: 36px;
    min-width: 200px;
  }
  .search-input {
    flex: 1;
    padding: 8px 12px;
    border: 1px solid #ddd;
    border-radius: 4px 0 0 4px;
    font-size: 14px;
    background-color: white;
  }
  .search-button {
    padding: 0 12px;
    border: 1px solid #3498db;
    border-radius: 0 4px 4px 0;
    background-color: #3498db;
    color: white;
    font-size: 14px;
    margin: 0;
  }
  
  .status-tabs {
@@ -240,7 +338,8 @@
    border-radius: 4px;
    overflow: hidden;
    background-color: #ecf0f1;
    width: 300px;
    flex: 1;
    min-width: 200px;
  }
  
  .tab-button {
pages/QC/SJ/Add.vue
@@ -123,10 +123,11 @@
        <!-- 表单下方操作按钮区 -->
        <view class="bottom-action-buttons">
            <button class="action-btn small" v-if="!isUpdate && !isShowTable" @click="toImage">上传/查看图片</button>
            <button class="action-btn small" v-if="!isUpdate && !isShowTable" @click="viewAttachmentInfo">查看附件信息</button>
            <button class="action-btn small" v-if="!isUpdate && !isShowTable" @click="fetchDrawingNumber(formData.itemNo)">调取PLM图纸</button>
            <button class="action-btn small" v-if="!isUpdate && !isShowTable" @click="getBom">Bom用料清单</button>
            <button class="action-btn small" v-if="!isUpdate && !isShowTable && formData.xjGenFlag == 0" @click="saveXJ">生成巡检</button>
            <button class="action-btn small" v-if="false" @click="removeXJ">删除单据</button>
            <button class="action-btn small danger" v-if="!isUpdate && !isShowTable && formData.fsubmit !== 1" @click="showDeleteConfirmDialog">删除单据</button>
            <button class="action-btn small" v-if="!isUpdate && !isShowTable" @click="saveRemarks">添加不合格描述</button>
            <button class="action-btn small" v-if="!isUpdate && formData.fsubmit == 0 && !isShowTable" @click="getGenUpdate">获取检验项目</button>
            <button class="action-btn small primary" v-if="!isUpdate && formData.fsubmit ==0 && !isShowTable" @click="submitInspection">提交检验</button>
@@ -204,6 +205,194 @@
                </view>
            </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 v-if="showDeleteConfirm" class="overlay">
            <view class="popup delete-confirm-popup">
                <h3 class="delete-confirm-title">⚠️ 确认删除</h3>
                <div class="delete-confirm-divider"></div>
                <div class="delete-confirm-content">
                    <view class="delete-warning-icon">🗑️</view>
                    <view class="delete-warning-text">您确定要删除此检验单吗?</view>
                    <view class="delete-warning-detail">删除后将无法恢复,请谨慎操作!</view>
                    <view class="delete-countdown">
                        <view class="countdown-text">确认按钮将在 <text class="countdown-number">{{ deleteCountdown }}</text> 秒后可用</view>
                        <view class="countdown-progress">
                            <view class="countdown-bar" :style="{ width: countdownProgress + '%' }"></view>
                        </view>
                    </view>
                </div>
                <div class="delete-confirm-actions">
                    <button class="delete-confirm-btn cancel-btn" @click="cancelDelete">取消</button>
                    <button class="delete-confirm-btn confirm-btn"
                        :disabled="deleteCountdown > 0"
                        :class="{ 'disabled': deleteCountdown > 0 }"
                        @click="confirmDelete">确认删除</button>
                </div>
            </view>
        </view>
    </view>
</template>
<script>
@@ -224,6 +413,8 @@
                    statusUser: "",
                    SJ_MJ: '首检',
                    fsubmit:"",
                    mnum: 1,  // 添加 mnum 字段
                    dnum: "", // 添加 dnum 字段
                },
                DAA020List: [],
@@ -256,6 +447,24 @@
                SJ_MJList: ['首检', '末检'],
                SJ_MJIndex: 0,
                // 附件相关数据
                showAttachmentPopup: false,
                showAttachmentDetail: false,
                showFilePreviewPopup: false,
                attachments: [],
                attachmentsLoading: false,
                selectedAttachment: null,
                previewTitle: '',
                previewContent: '',
                previewType: '',
                previewFileUrl: '',
                // 删除确认相关数据
                showDeleteConfirm: false,
                deleteCountdown: 5,
                countdownProgress: 0,
                deleteTimer: null
            };
        },
        onLoad(options) {
@@ -322,6 +531,53 @@
                    return '检验项目详情';
                }
            },
            // 显示删除确认弹窗
            showDeleteConfirmDialog() {
                this.showDeleteConfirm = true;
                this.deleteCountdown = 5;
                this.countdownProgress = 0;
                this.startDeleteCountdown();
            },
            // 开始倒计时
            startDeleteCountdown() {
                this.deleteTimer = setInterval(() => {
                    this.deleteCountdown--;
                    this.countdownProgress = ((5 - this.deleteCountdown) / 5) * 100;
                    if (this.deleteCountdown <= 0) {
                        clearInterval(this.deleteTimer);
                        this.deleteTimer = null;
                    }
                }, 1000);
            },
            // 取消删除
            cancelDelete() {
                this.showDeleteConfirm = false;
                if (this.deleteTimer) {
                    clearInterval(this.deleteTimer);
                    this.deleteTimer = null;
                }
                this.deleteCountdown = 5;
                this.countdownProgress = 0;
            },
            // 确认删除
            confirmDelete() {
                if (this.deleteCountdown > 0) {
                    return;
                }
                this.showDeleteConfirm = false;
                if (this.deleteTimer) {
                    clearInterval(this.deleteTimer);
                    this.deleteTimer = null;
                }
                this.removeXJ();
            },
            removeXJ() {
                if (this.formData.id) {
                    this.$post({
@@ -372,7 +628,17 @@
                    }).then(res => {
                        let tbBillListElement = res.data.tbBillList[0];
                        if (tbBillListElement) {
                            // 保留原有的 mnum 和 dnum 默认值
                            const originalMnum = this.formData.mnum;
                            const originalDnum = this.formData.dnum;
                            this.formData = tbBillListElement;
                            // 如果后端没有返回这些字段,使用默认值
                            if (this.formData.mnum === undefined || this.formData.mnum === null) {
                                this.formData.mnum = originalMnum || 1;
                            }
                            if (this.formData.dnum === undefined || this.formData.dnum === null) {
                                this.formData.dnum = originalDnum || "";
                            }
                            this.$post({
                                url: "/SJ/getQSItems",
                                data: {
@@ -955,64 +1221,267 @@
                    return;
                }
                
                // 先获取检验单的详细信息,包含mnum和dnum
                // 参数验证
                if (!this.$loginInfo.account) {
                    this.$showMessage("用户信息不完整,无法获取检验项目");
                    return;
                }
                // 直接使用 formData 中的值
                const requestData = {
                    id: this.formData.id,
                    no: this.formData.billNo,
                    user: this.$loginInfo.account,
                    mnum: this.formData.mnum || 1,
                    dnum: this.formData.dnum || null
                };
                // 添加调试日志
                console.log("SJ GenUpdate 请求参数:", requestData);
                this.$post({
                    url: "/SJ/getQSItems",
                    data: {
                        pid: this.formData.id
                    }
                }).then(res => {
                    if (res.data.tbBillList && res.data.tbBillList.length > 0) {
                        // 从第一个检验项目中获取mnum和dnum
                        const firstItem = res.data.tbBillList[0];
                        const mnum = firstItem.mnum || 1;
                        const dnum = firstItem.dnum || "";
                        // 调用GenUpdate
                        this.$post({
                            url: "/SJ/GenUpdate",
                            data: {
                                id: this.formData.id,
                                no: this.formData.billNo,
                                user: this.$loginInfo.account,
                                mnum: mnum,
                                dnum: dnum
                            }
                        }).then(genRes => {
                            if (genRes.data.result === 0) {
                                this.$showMessage("获取检验项目成功");
                                this.init();
                                // 获取成功后直接进入填写阶段
                                this.isShowTable = false;
                                this.isUpdate = false;
                            } else {
                                this.$showMessage(genRes.data.message || "获取失败");
                            }
                        });
                    url: "/SJ/GenUpdate",
                    data: requestData
                }).then(genRes => {
                    console.log("SJ GenUpdate 响应:", genRes);
                    if (genRes.data.result === 0) {
                        this.$showMessage("获取检验项目成功");
                        this.init();
                        // 获取成功后直接进入填写阶段
                        this.isShowTable = false;
                        this.isUpdate = false;
                    } else {
                        // 如果没有检验项目,使用默认值
                        this.$post({
                            url: "/SJ/GenUpdate",
                            data: {
                                id: this.formData.id,
                                no: this.formData.billNo,
                                user: this.$loginInfo.account,
                                mnum: 1,
                                dnum: ""
                            }
                        }).then(genRes => {
                            if (genRes.data.result === 0) {
                                this.$showMessage("获取检验项目成功");
                                this.init();
                                // 获取成功后直接进入填写阶段
                                this.isShowTable = false;
                                this.isUpdate = false;
                            } else {
                                this.$showMessage(genRes.data.message || "获取失败");
                            }
                        this.$showMessage(genRes.data.message || "获取失败");
                    }
                }).catch(err => {
                    console.error("SJ GenUpdate 错误:", err);
                    this.$showMessage("获取检验项目失败:" + (err.message || "未知错误"));
                });
            },
            // 附件相关方法
            viewAttachmentInfo() {
                this.showAttachmentPopup = true;
                this.attachmentsLoading = true;
                this.attachments = [];
                this.$post({
                    url: "/SJ/getAttachments",
                    data: { itemNo: this.formData.itemNo }
                }).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" });
                    }
                });
            },
            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 + "/SJ/DownloadFtpFile?itemNo=" +
                    encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) +
                    "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer);
                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 + "/SJ/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 {
                    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
                    });
                }
            }
        },
        onShow() {
@@ -1326,6 +1795,15 @@
  background-color: #2980b9;
}
.action-btn.danger {
  background-color: #e74c3c;
  color: #fff;
}
.action-btn.danger:hover {
  background-color: #c0392b;
}
/* 小尺寸按钮样式 */
.action-btn.small {
  padding: 10px 12px;
@@ -1475,4 +1953,560 @@
        overflow-x: auto;
    }
}
/* 附件相关样式 */
.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-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;
}
/* 文件预览弹窗样式 */
.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);
}
/* 删除确认弹窗样式 */
.delete-confirm-popup {
    width: 90vw;
    max-width: 400px;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
}
.delete-confirm-title {
    font-size: 20px;
    font-weight: 700;
    color: #e74c3c;
    margin-bottom: 8px;
    text-align: center;
    letter-spacing: 1px;
}
.delete-confirm-divider {
    height: 2px;
    background: linear-gradient(90deg, #e74c3c 0%, #c0392b 100%);
    margin-bottom: 20px;
}
.delete-confirm-content {
    text-align: center;
    margin-bottom: 20px;
}
.delete-warning-icon {
    font-size: 48px;
    margin-bottom: 16px;
    animation: shake 0.5s ease-in-out infinite alternate;
}
@keyframes shake {
    0% { transform: translateX(-2px); }
    100% { transform: translateX(2px); }
}
.delete-warning-text {
    font-size: 18px;
    font-weight: 600;
    color: #2c3e50;
    margin-bottom: 8px;
}
.delete-warning-detail {
    font-size: 14px;
    color: #7f8c8d;
    margin-bottom: 20px;
    line-height: 1.4;
}
.delete-countdown {
    background: #f8f9fa;
    border-radius: 8px;
    padding: 16px;
    margin: 16px 0;
    border: 1px solid #e9ecef;
}
.countdown-text {
    font-size: 14px;
    color: #495057;
    margin-bottom: 8px;
    text-align: center;
}
.countdown-number {
    font-weight: 700;
    color: #e74c3c;
    font-size: 16px;
}
.countdown-progress {
    width: 100%;
    height: 6px;
    background: #e9ecef;
    border-radius: 3px;
    overflow: hidden;
}
.countdown-bar {
    height: 100%;
    background: linear-gradient(90deg, #e74c3c, #c0392b);
    border-radius: 3px;
    transition: width 1s ease;
}
.delete-confirm-actions {
    display: flex;
    gap: 12px;
    justify-content: center;
}
.delete-confirm-btn {
    padding: 12px 24px;
    border: none;
    border-radius: 8px;
    font-size: 16px;
    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);
}
.delete-confirm-btn.cancel-btn {
    background: linear-gradient(135deg, #95a5a6, #7f8c8d);
    color: white;
}
.delete-confirm-btn.cancel-btn:hover {
    background: linear-gradient(135deg, #7f8c8d, #6c7b7d);
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.delete-confirm-btn.confirm-btn {
    background: linear-gradient(135deg, #e74c3c, #c0392b);
    color: white;
}
.delete-confirm-btn.confirm-btn:hover:not(.disabled) {
    background: linear-gradient(135deg, #c0392b, #a93226);
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.delete-confirm-btn.disabled {
    background: #bdc3c7;
    color: #7f8c8d;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
}
</style>
pages/QC/SJ/detail.vue
@@ -122,6 +122,11 @@
                不合格描述
              </button>
              
              <button class="btn attachment-btn" @tap="viewAttachmentInfo">
                <uni-icons type="folder" size="16" color="#fff"></uni-icons>
                查看附件信息
              </button>
              <input v-if="!isAllCompleted"
                v-model="formData.fcheckResu"
                type="text"
@@ -222,6 +227,168 @@
      </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>
</template>
@@ -244,7 +411,18 @@
      remarksPopup: false,
      // ===== 新增LLJ样式相关数据 =====
      currentTab: 0,
      tabs: []
      tabs: [],
      // ===== 附件相关数据 =====
      showAttachmentPopup: false,
      showAttachmentDetail: false,
      showFilePreviewPopup: false,
      attachments: [],
      attachmentsLoading: false,
      selectedAttachment: null,
      previewTitle: '',
      previewContent: '',
      previewType: '',
      previewFileUrl: ''
    }
  },
  computed: {
@@ -547,6 +725,10 @@
      }).then(res => {
        this.formData = res.data.tbBillList[0];
        this.formData.billNo = this.billNo;
        console.log("获取到的 formData:", this.formData);
        console.log("itemNo 值:", this.formData.itemNo);
        if (this.formData.imageData) {
          this.isShowImg = true;
          this.base64Image = 'data:image/jpeg;base64,' + this.formData.imageData;
@@ -692,6 +874,254 @@
          }
        })
      }
    },
    // ===== 附件相关方法 =====
    viewAttachmentInfo() {
      this.showAttachmentPopup = true;
      this.attachmentsLoading = true;
      this.attachments = [];
      // 先获取物料编码,然后获取附件
      this.$post({
        url: "/SJ/GetPage",
        data: {
          pageIndex: 1,
          limit: 1,
          id: this.gid
        }
      }).then(res => {
        let tbBillListElement = res.data.tbBillList[0];
        if (tbBillListElement && tbBillListElement.itemNo) {
          console.log("获取到的物料编码:", tbBillListElement.itemNo);
          // 使用获取到的物料编码调用附件接口
          return this.$post({
            url: "/SJ/getAttachments",
            data: { itemNo: tbBillListElement.itemNo }
          });
        } 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 + "/SJ/DownloadFtpFile?itemNo=" +
        encodeURIComponent(item.itemNo) + "&fileName=" + encodeURIComponent(fileName) +
        "&ftpServer=" + encodeURIComponent(this.$store.state.serverInfo.ftpServer);
      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 + "/SJ/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 {
        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
        });
      }
    }
  },
  onLoad(options) {
@@ -829,8 +1259,29 @@
    .upload-btn {
      background-color: #909399;
      color: #fff;
      padding: 0 10px;
      padding: 10px 12px;
      margin: 0;
      font-size: 14px;
      min-height: 44px;
      white-space: nowrap;
      flex-shrink: 0;
      min-width: 80px;
      max-width: 120px;
      flex: 1;
    }
    .attachment-btn {
      background-color: #17a2b8;
      color: #fff;
      padding: 10px 12px;
      margin: 0;
      font-size: 14px;
      min-height: 44px;
      white-space: nowrap;
      flex-shrink: 0;
      min-width: 80px;
      max-width: 120px;
      flex: 1;
    }
  }
}
@@ -1016,17 +1467,22 @@
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
  z-index: 2000;
}
.popup {
  background-color: #fff;
  padding: 20px;
  border: 1px solid #ccc;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
  padding: 0;
  border: none;
  box-shadow: 0 8px 32px rgba(60,60,60,0.18);
  width: 68vw;
  height: 25vh;
  border-radius: 8px;
  max-width: 600px;
  border-radius: 16px;
  overflow: hidden;
  position: relative;
  z-index: 2001;
  max-height: 80vh;
  overflow-y: auto;
}
.popup-header {
@@ -1109,6 +1565,11 @@
      background-color: #95a5a6;
      color: white;
    }
    &:hover {
      transform: translateY(-1px);
      box-shadow: 0 2px 8px rgba(0,0,0,0.15);
    }
  }
}
@@ -1139,5 +1600,434 @@
  .input-label {
    margin-bottom: 5px;
  }
  .input-wrapper {
    flex-direction: column;
    gap: 8px;
  }
  .upload-btn,
  .attachment-btn {
    min-width: 70px;
    max-width: 100px;
    padding: 8px 10px;
    font-size: 13px;
    min-height: 40px;
  }
}
/* 附件相关样式 */
.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-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;
}
/* 文件预览弹窗样式 */
.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/XJ/Add.vue
@@ -127,7 +127,7 @@
         </button>
         <button class="action-btn secondary" v-if="!isUpdate && !isShowTable" @click="viewAttachmentInfo">查看附件信息</button>
         <button class="action-btn danger" v-if="!isUpdate && !formData.fcheckResu && !isShowTable && formData.fsubmit !== 1" @click="removeXJ">删除单据</button>
         <button class="action-btn danger" v-if="!isUpdate && !formData.fcheckResu && !isShowTable && formData.fsubmit !== 1" @click="showDeleteConfirmDialog">删除单据</button>
         <button class="action-btn warning" v-if="!isUpdate && !isShowTable && formData.fsubmit !== 1" @click="saveRemarks">添加不合格描述</button>
         
         <!-- 检验项目管理按钮 -->
@@ -372,6 +372,32 @@
        </view>
      </view>
      <!-- 删除确认弹窗 -->
      <view v-if="showDeleteConfirm" class="overlay">
        <view class="popup delete-confirm-popup">
          <h3 class="delete-confirm-title">⚠️ 确认删除</h3>
          <div class="delete-confirm-divider"></div>
          <div class="delete-confirm-content">
            <view class="delete-warning-icon">🗑️</view>
            <view class="delete-warning-text">您确定要删除此检验单吗?</view>
            <view class="delete-warning-detail">删除后将无法恢复,请谨慎操作!</view>
            <view class="delete-countdown">
              <view class="countdown-text">确认按钮将在 <text class="countdown-number">{{ deleteCountdown }}</text> 秒后可用</view>
              <view class="countdown-progress">
                <view class="countdown-bar" :style="{ width: countdownProgress + '%' }"></view>
              </view>
            </view>
          </div>
          <div class="delete-confirm-actions">
            <button class="delete-confirm-btn cancel-btn" @click="cancelDelete">取消</button>
            <button class="delete-confirm-btn confirm-btn"
              :disabled="deleteCountdown > 0"
              :class="{ 'disabled': deleteCountdown > 0 }"
              @click="confirmDelete">确认删除</button>
          </div>
        </view>
      </view>
  
    </view>
  </template>
@@ -446,7 +472,13 @@
        previewTitle: '',
        previewContent: '',
        previewType: '',
        previewFileUrl: ''
        previewFileUrl: '',
        // 删除确认相关数据
        showDeleteConfirm: false,
        deleteCountdown: 5,
        countdownProgress: 0,
        deleteTimer: null
      };
    },
@@ -520,6 +552,53 @@
        } else {
          return '检验项目详情';
        }
      },
      // 显示删除确认弹窗
      showDeleteConfirmDialog() {
        this.showDeleteConfirm = true;
        this.deleteCountdown = 5;
        this.countdownProgress = 0;
        this.startDeleteCountdown();
      },
      // 开始倒计时
      startDeleteCountdown() {
        this.deleteTimer = setInterval(() => {
          this.deleteCountdown--;
          this.countdownProgress = ((5 - this.deleteCountdown) / 5) * 100;
          if (this.deleteCountdown <= 0) {
            clearInterval(this.deleteTimer);
            this.deleteTimer = null;
          }
        }, 1000);
      },
      // 取消删除
      cancelDelete() {
        this.showDeleteConfirm = false;
        if (this.deleteTimer) {
          clearInterval(this.deleteTimer);
          this.deleteTimer = null;
        }
        this.deleteCountdown = 5;
        this.countdownProgress = 0;
      },
      // 确认删除
      confirmDelete() {
        if (this.deleteCountdown > 0) {
          return;
        }
        this.showDeleteConfirm = false;
        if (this.deleteTimer) {
          clearInterval(this.deleteTimer);
          this.deleteTimer = null;
        }
        this.removeXJ();
      },
      
      removeXJ() {
@@ -1689,6 +1768,11 @@
    display: flex;
    align-items: center;
    justify-content: center;
    white-space: nowrap;
    flex-shrink: 0;
    min-width: 80px;
    max-width: 120px;
    flex: 1;
  }
  .action-btn:hover {
@@ -2400,6 +2484,7 @@
      padding: 8px 10px;
      font-size: 11px;
      min-height: 40px;
      flex: 1;
    }
    
    .action-btn.small {
@@ -2489,4 +2574,145 @@
      font-size: 28px;
    }
  }
  /* 删除确认弹窗样式 */
  .delete-confirm-popup {
    width: 90vw;
    max-width: 400px;
    max-height: 70vh;
    display: flex;
    flex-direction: column;
  }
  .delete-confirm-title {
    font-size: 20px;
    font-weight: 700;
    color: #e74c3c;
    margin-bottom: 8px;
    text-align: center;
    letter-spacing: 1px;
  }
  .delete-confirm-divider {
    height: 2px;
    background: linear-gradient(90deg, #e74c3c 0%, #c0392b 100%);
    margin-bottom: 20px;
  }
  .delete-confirm-content {
    text-align: center;
    margin-bottom: 20px;
  }
  .delete-warning-icon {
    font-size: 48px;
    margin-bottom: 16px;
    animation: shake 0.5s ease-in-out infinite alternate;
  }
  @keyframes shake {
    0% { transform: translateX(-2px); }
    100% { transform: translateX(2px); }
  }
  .delete-warning-text {
    font-size: 18px;
    font-weight: 600;
    color: #2c3e50;
    margin-bottom: 8px;
  }
  .delete-warning-detail {
    font-size: 14px;
    color: #7f8c8d;
    margin-bottom: 20px;
    line-height: 1.4;
  }
  .delete-countdown {
    background: #f8f9fa;
    border-radius: 8px;
    padding: 16px;
    margin: 16px 0;
    border: 1px solid #e9ecef;
  }
  .countdown-text {
    font-size: 14px;
    color: #495057;
    margin-bottom: 8px;
    text-align: center;
  }
  .countdown-number {
    font-weight: 700;
    color: #e74c3c;
    font-size: 16px;
  }
  .countdown-progress {
    width: 100%;
    height: 6px;
    background: #e9ecef;
    border-radius: 3px;
    overflow: hidden;
  }
  .countdown-bar {
    height: 100%;
    background: linear-gradient(90deg, #e74c3c, #c0392b);
    border-radius: 3px;
    transition: width 1s ease;
  }
  .delete-confirm-actions {
    display: flex;
    gap: 12px;
    justify-content: center;
  }
  .delete-confirm-btn {
    padding: 12px 24px;
    border: none;
    border-radius: 8px;
    font-size: 16px;
    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);
  }
  .delete-confirm-btn.cancel-btn {
    background: linear-gradient(135deg, #95a5a6, #7f8c8d);
    color: white;
  }
  .delete-confirm-btn.cancel-btn:hover {
    background: linear-gradient(135deg, #7f8c8d, #6c7b7d);
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  }
  .delete-confirm-btn.confirm-btn {
    background: linear-gradient(135deg, #e74c3c, #c0392b);
    color: white;
  }
  .delete-confirm-btn.confirm-btn:hover:not(.disabled) {
    background: linear-gradient(135deg, #c0392b, #a93226);
    transform: translateY(-1px);
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
  }
  .delete-confirm-btn.disabled {
    background: #bdc3c7;
    color: #7f8c8d;
    cursor: not-allowed;
    transform: none;
    box-shadow: none;
  }
  </style>
pages/QC/XJ/List.vue
@@ -6,13 +6,26 @@
    <!-- 顶部筛选区 -->
    <view class="filter-section">
      <view class="filter-controls">
        <!-- 查询条件选择器 -->
        <view class="dropdown-filter">
          <picker @change="onOptionsChange" :value="optionsIndex" :range="options">
            <view class="picker">{{options[optionsIndex]}}</view>
          </picker>
        </view>
        <!-- 搜索框 -->
        <view class="search-container">
          <input class="search-input" v-model="searchValue" :placeholder="'请输入'+options[optionsIndex]" @confirm="btnclicked" />
          <button class="search-button" @click="btnclicked">搜索</button>
        </view>
        <!-- 状态切换标签 -->
        <view class="status-tabs">
          <button :class="['tab-button', current === 0 ? 'active' : '']" @click="onClickItem({currentIndex: 0})">
            {{items[0]}}
            未提交({{unsubmittedCount}})
          </button>
          <button :class="['tab-button', current === 1 ? 'active' : '']" @click="onClickItem({currentIndex: 1})">
            {{items[1]}}
            已提交({{submittedCount}})
          </button>
        </view>
      </view>
@@ -98,7 +111,15 @@
      items: ['未提交', '已提交'],
      current: 0,
      data: [],
      tipShow: false // 是否显示顶部提示框
      tipShow: false, // 是否显示顶部提示框
      searchValue: '',
      // ===== 新增搜索功能相关数据 =====
      optionsIndex: 0, // 当前选择的查询条件索引
      options: ['工单', '检验单号', '产线', '物料编码', '物料名称'], // 查询条件选项
      selectedField: 'billNo', // 当前选择的查询字段,默认为工单
      // ===== 新增数量统计变量 =====
      unsubmittedCount: 0, // 未提交数量
      submittedCount: 0 // 已提交数量
    };
  },
  onLoad() {
@@ -106,8 +127,29 @@
    this.init();
  },
  methods: {
    // ===== 新增查询条件选择方法 =====
    onOptionsChange(e) {
      this.optionsIndex = e.detail.value;
      // 根据选择的选项设置搜索字段
      const fieldMap = {
        0: 'billNo',     // 工单
        1: 'releaseNo',  // 检验单号
        2: 'daa020',     // 产线
        3: 'itemNo',     // 物料编码
        4: 'itemName'    // 物料名称
      };
      this.selectedField = fieldMap[this.optionsIndex];
    },
    //搜索框点击事件
    btnclicked() {
      this.init();
    },
    init() {
      //获取搜索条件内容
      let SearchValue = this.searchValue;
      let result = "未完成";
      if (this.current === 1) {
        result = "已完成";
@@ -120,10 +162,19 @@
          pageIndex: 1,
          limit: 20,
          createUser: this.$loginInfo.account,
          result: result
          result: result,
          SearchValue: SearchValue,
          selectedIndex: this.optionsIndex, // 新增:搜索条件索引
          searchField: this.selectedField   // 新增:搜索字段名
        }
      }).then(res => {
        this.data = res.data.tbBillList;
        // 设置数量统计
        if (this.current === 1) {
          this.submittedCount = res.totalCount;
        } else {
          this.unsubmittedCount = res.totalCount;
        }
      })
    },
    handleFabClick() {
@@ -186,8 +237,51 @@
.filter-controls {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}
/* 查询条件选择器 */
.dropdown-filter {
  min-width: 80px;
  margin-right: 10px;
}
.picker {
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background-color: white;
  font-size: 14px;
  min-width: 80px;
}
/* 搜索框样式 */
.search-container {
  display: flex;
  flex: 1;
  margin-right: 10px;
  height: 36px;
  min-width: 200px;
}
.search-input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px 0 0 4px;
  font-size: 14px;
  background-color: white;
}
.search-button {
  padding: 0 12px;
  border: 1px solid #3498db;
  border-radius: 0 4px 4px 0;
  background-color: #3498db;
  color: white;
  font-size: 14px;
  margin: 0;
}
.status-tabs {
@@ -195,7 +289,8 @@
  border-radius: 4px;
  overflow: hidden;
  background-color: #ecf0f1;
  width: 300px;
  flex: 1;
  min-width: 200px;
}
.tab-button {
store/index.js
@@ -11,8 +11,8 @@
            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:10054/api',
            serverAPI:'http://192.168.1.22:10054/api',//内网
            //serverAPI:'http://36.26.21.214:10054/api',
            ftpServer:'ftp://36.26.21.214',//FTP服务器地址
        }
    },