Browse Source

feat: 5703 高拍仪接入:要和现有附件上传组件融合

johnsen 2 months ago
parent
commit
392d973cfc

File diff suppressed because it is too large
+ 168 - 34
package-lock.json


+ 1 - 0
package.json

@@ -141,6 +141,7 @@
     "svg-sprite-loader": "^5.0.0",
     "text-loader": "^0.0.1",
     "vue-cli-plugin-i18n": "^1.0.1",
+    "vue-code-diff": "^1.2.0",
     "vue-template-compiler": "^2.6.12",
     "webpack-bundle-analyzer": "^3.8.0",
     "webpack-theme-color-replacer": "^1.3.14"

+ 983 - 0
src/business/platform/file/uploader/CameraDialogV2.vue

@@ -0,0 +1,983 @@
+<template>
+  <div>
+    <el-dialog
+      :visible.sync="dialogVisible"
+      top="2vh"
+      :modal="false"
+      title="高拍仪"
+      width="800px"
+      :before-close="handleClose"
+      custom-class="camera-dialog"
+    >
+      <div class="camera-dialog-content">
+        <!-- 视频预览区 -->
+        <div
+          class="video-preview"
+          v-loading="videoLoading"
+          element-loading-text="正在加载摄像头..."
+        >
+          <img
+            ref="videoRef"
+            :src="videoUrl"
+            alt="主摄像头"
+            class="video-stream"
+            @load="handleVideoLoad"
+            @error="handleVideoError"
+            v-show="!videoLoading"
+          />
+        </div>
+
+        <!-- 操作按钮区 -->
+        <div class="button-group">
+          <el-button
+            type="primary"
+            :icon="'el-icon-refresh-left'"
+            @click="handleRotate(90)"
+            :loading="rotating"
+          >
+            左转
+          </el-button>
+          <el-button
+            type="success"
+            icon="el-icon-camera"
+            @click="handleCapture"
+            :loading="capturing"
+          >
+            拍照
+          </el-button>
+          <el-button
+            type="primary"
+            :icon="'el-icon-refresh-right'"
+            @click="handleRotate(270)"
+            :loading="rotating"
+          >
+            右转
+          </el-button>
+          <el-popover placement="top" width="160" v-model="visible">
+            <p><el-input v-model="port"></el-input></p>
+            <div style="text-align: right; margin: 0">
+              <el-button size="mini" type="text" @click="visible = false"
+                >取消</el-button
+              >
+              <el-button type="primary" size="mini" @click="changePort"
+                >确定</el-button
+              >
+            </div>
+            <el-button icon="el-icon-s-tools" slot="reference"
+              >端口设置</el-button
+            >
+          </el-popover>
+          <!-- <el-button
+          type="warning"
+          icon="el-icon-search"
+          @click="handleScanQRCode"
+          :loading="scanning"
+        >
+          识别二维码
+        </el-button> -->
+          <!-- <el-button
+          type="info"
+          icon="el-icon-document"
+          @click="handleReadIDCard"
+          :loading="readingIDCard"
+        >
+          读取身份证
+        </el-button>
+        <el-button
+          type="danger"
+          icon="el-icon-menu"
+          @click="handleScanBarcode"
+          :loading="scanningBarcode"
+        >
+          条码识别
+        </el-button> -->
+        </div>
+
+        <!-- 拍照历史记录 -->
+        <div class="photo-history">
+          <el-divider>
+            {{
+              multiple
+                ? '请选择照片(单击选择,双击预览)'
+                : '请选择照片(单击选择一张,双击预览)'
+            }}
+          </el-divider>
+          <div v-if="photoHistory.length > 0" class="thumbnail-container">
+            <div
+              v-for="(photo, index) in displayedPhotos"
+              :key="index"
+              class="thumbnail-item"
+              :class="{ selected: isPhotoSelected(photo) }"
+              @click="handleSelectPhoto(photo)"
+              @dblclick="handlePreviewPhoto(photo, index)"
+            >
+              <el-image
+                :src="photo"
+                class="thumbnail-image"
+                fit="cover"
+                :hide-on-click-modal="true"
+                :preview-disabled="true"
+              />
+              <div v-if="isPhotoSelected(photo)" class="selected-badge">
+                <i class="el-icon-check"></i>
+              </div>
+            </div>
+            <div v-if="photoHistory.length > 5" class="more-button-container">
+              <el-button
+                type="primary"
+                icon="el-icon-more"
+                @click="showAllPhotos = true"
+                class="more-button"
+              >
+                更多 ({{ photoHistory.length - 5 }})
+              </el-button>
+            </div>
+          </div>
+          <div v-else class="photo-history-empty">
+            <el-empty description="暂无拍照记录">
+              <template v-slot:image>
+                <i
+                  class="el-icon-camera"
+                  style="font-size: 80px; color: #c0c4cc"
+                ></i>
+              </template>
+              <template v-slot:description>
+                <p class="empty-tip">点击上方"拍照"按钮开始拍照</p>
+              </template>
+            </el-empty>
+          </div>
+          <div v-if="selectedPhotos.length > 0" class="selected-info">
+            {{
+              multiple
+                ? `已选择 ${selectedPhotos.length} 张图片`
+                : '已选择 1 张图片'
+            }}
+          </div>
+        </div>
+        <!-- 身份证识别结果 -->
+        <div v-if="idCardResult" class="idcard-result">
+          <el-divider>身份证信息</el-divider>
+          <el-card class="idcard-card">
+            <div class="idcard-content">
+              <div class="idcard-photo" v-if="idCardResult.photoBase64">
+                <el-image
+                  :src="'data:image/jpeg;base64,' + idCardResult.photoBase64"
+                  fit="contain"
+                  class="idcard-image"
+                  :preview-src-list="idCardPreviewList"
+                  :preview-teleported="true"
+                />
+              </div>
+              <div class="idcard-info">
+                <el-descriptions :column="1" border>
+                  <el-descriptions-item label="姓名">{{
+                    idCardResult.name
+                  }}</el-descriptions-item>
+                  <el-descriptions-item label="身份证号">{{
+                    idCardResult.cardID
+                  }}</el-descriptions-item>
+                  <el-descriptions-item label="性别">{{
+                    idCardResult.sex
+                  }}</el-descriptions-item>
+                  <el-descriptions-item label="出生日期">{{
+                    idCardResult.birthday
+                  }}</el-descriptions-item>
+                  <el-descriptions-item label="地址">{{
+                    idCardResult.address
+                  }}</el-descriptions-item>
+                  <el-descriptions-item label="签发机关">{{
+                    idCardResult.issueOrgan
+                  }}</el-descriptions-item>
+                  <el-descriptions-item label="有效期限">
+                    {{ idCardResult.validStart }} 至 {{ idCardResult.validEnd }}
+                  </el-descriptions-item>
+                </el-descriptions>
+                <div class="idcard-actions">
+                  <el-button
+                    size="small"
+                    type="primary"
+                    @click="handleCopyIDCard"
+                    >复制信息</el-button
+                  >
+                  <el-button size="small" @click="idCardResult = null"
+                    >关闭</el-button
+                  >
+                </div>
+              </div>
+            </div>
+          </el-card>
+        </div>
+
+        <!-- 条码识别结果 -->
+        <div v-if="barcodeResult" class="barcode-result">
+          <el-divider>条码识别结果</el-divider>
+          <el-alert
+            :title="barcodeResult"
+            type="success"
+            :closable="true"
+            @close="barcodeResult = ''"
+            show-icon
+          >
+            <template slot="default">
+              <div class="barcode-content">
+                <p><strong>识别内容:</strong></p>
+                <p class="barcode-text">{{ barcodeResult }}</p>
+                <el-button
+                  size="small"
+                  type="primary"
+                  @click="handleCopyBarcode"
+                  >复制</el-button
+                >
+              </div>
+            </template>
+          </el-alert>
+        </div>
+      </div>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="handleClose">取消</el-button>
+        <el-button
+          type="danger"
+          @click="handleClearAll"
+          :disabled="photoHistory.length === 0"
+        >
+          清空图片
+        </el-button>
+        <el-button type="primary" @click="handleConfirm">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 全部图片展示弹窗 -->
+    <el-dialog
+      :visible.sync="showAllPhotos"
+      :modal="false"
+      :title="
+        multiple
+          ? '全部拍照记录(单击选择,双击预览)'
+          : '全部拍照记录(单击选择一张,双击预览)'
+      "
+      width="90%"
+      :before-close="
+        () => {
+          showAllPhotos = false
+        }
+      "
+    >
+      <div class="all-photos-container">
+        <div
+          v-for="(photo, index) in photoHistory"
+          :key="index"
+          class="all-photo-item"
+          :class="{ selected: isPhotoSelected(photo) }"
+          @click="handleSelectPhoto(photo)"
+          @dblclick="handlePreviewPhoto(photo, index)"
+        >
+          <el-image
+            :src="photo"
+            class="all-photo-image"
+            fit="cover"
+            :hide-on-click-modal="true"
+            :preview-disabled="true"
+          />
+          <div class="photo-index">第 {{ index + 1 }} 张</div>
+          <div v-if="isPhotoSelected(photo)" class="selected-badge-large">
+            <i class="el-icon-check"></i>
+          </div>
+        </div>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="showAllPhotos = false">取消</el-button>
+        <el-button type="danger" @click="handleClearAll">清空全部</el-button>
+        <el-button type="primary" @click="handleConfirm">确定</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 全屏图片预览 -->
+    <div
+      v-if="showPreview"
+      class="fullscreen-preview"
+      @click.self="showPreview = false"
+    >
+      <div class="preview-content">
+        <el-image
+          v-if="previewImageList.length > 0"
+          :src="previewImageList[previewIndex]"
+          fit="contain"
+          class="preview-image-fullscreen"
+        />
+
+        <!-- 左箭头 - 上一张 -->
+        <div
+          v-if="previewIndex > 0"
+          class="arrow-left"
+          @click.stop="handlePrevImage"
+        >
+          <i class="el-icon-arrow-left" style="font-size: 40px"></i>
+        </div>
+
+        <!-- 右箭头 - 下一张 -->
+        <div
+          v-if="previewIndex < previewImageList.length - 1"
+          class="arrow-right"
+          @click.stop="handleNextImage"
+        >
+          <i class="el-icon-arrow-right" style="font-size: 40px"></i>
+        </div>
+
+        <!-- 关闭按钮 -->
+        <div class="preview-close" @click.stop="showPreview = false">
+          <i class="el-icon-close" style="font-size: 24px"></i>
+        </div>
+
+        <!-- 图片信息 -->
+        <div class="preview-info-fullscreen">
+          {{ previewIndex + 1 }} / {{ previewImageList.length }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import axios from 'axios'
+
+export default {
+  name: 'CameraDialogV2',
+  props: {
+    value: {
+      type: Boolean,
+      default: false
+    },
+    multiple: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      videoUrl: '',
+      videoLoading: true,
+      rotating: false,
+      capturing: false,
+      scanning: false,
+      readingIDCard: false,
+      scanningBarcode: false,
+      qrCodeResult: '',
+      idCardResult: null,
+      barcodeResult: '',
+      photoHistory: [],
+      showAllPhotos: false,
+      selectedPhotos: [],
+      previewImageList: [],
+      previewIndex: 0,
+      showPreview: false,
+      videoTimer: null,
+      port: '38088',
+      visible: false
+    }
+  },
+  computed: {
+    apiBaseUrl() {
+      return 'http://127.0.0.1:' + this.port
+    },
+    displayedPhotos() {
+      return this.photoHistory.slice(0, 5)
+    },
+    idCardPreviewList() {
+      if (!this.idCardResult) return []
+      const list = []
+      if (this.idCardResult.photoBase64) {
+        list.push('data:image/jpeg;base64,' + this.idCardResult.photoBase64)
+      }
+      if (this.idCardResult.photoBase64_Z) {
+        list.push('data:image/jpeg;base64,' + this.idCardResult.photoBase64_Z)
+      }
+      if (this.idCardResult.photoBase64_F) {
+        list.push('data:image/jpeg;base64,' + this.idCardResult.photoBase64_F)
+      }
+      return list
+    }
+  },
+  watch: {
+    value(newVal) {
+      this.dialogVisible = newVal
+      if (newVal) {
+        this.openVideo()
+      } else {
+        this.closeVideo()
+      }
+    },
+    dialogVisible(newVal) {
+      this.$emit('input', newVal)
+    },
+    showPreview(newVal) {
+      if (newVal) {
+        document.addEventListener('keydown', this.handleKeydown)
+      } else {
+        document.removeEventListener('keydown', this.handleKeydown)
+        document.body.style.overflow = ''
+      }
+    }
+  },
+  methods: {
+    changePort() {
+      this.visible = false
+    },
+    openVideo() {
+      this.videoLoading = true
+      this.videoUrl = `${this.apiBaseUrl}/video=stream&camidx=0?${Date.now()}`
+    },
+    handleVideoLoad() {
+      this.videoLoading = false
+    },
+    handleVideoError() {
+      this.videoLoading = false
+      // this.$message.error('摄像头加载失败,请检查设备连接')
+    },
+    closeVideo() {
+      if (this.videoUrl) {
+        const data = { camidx: '0' }
+        axios
+          .post(`${this.apiBaseUrl}/video=close`, JSON.stringify(data))
+          .catch(() => {
+            // 静默处理错误
+          })
+        this.videoUrl = ''
+      }
+    },
+    async handleRotate(angle) {
+      this.rotating = true
+      try {
+        const data = {
+          camidx: '0',
+          rotate: String(angle)
+        }
+        const response = await axios.post(
+          `${this.apiBaseUrl}/video=rotate`,
+          JSON.stringify(data)
+        )
+        if (response.data) {
+          // this.$message.success(`旋转${angle}度成功`)
+        }
+      } catch (error) {
+        this.$message.error('旋转失败:' + (error.message || '未知错误'))
+      } finally {
+        this.rotating = false
+      }
+    },
+    async handleCapture() {
+      this.capturing = true
+      try {
+        const data = {
+          filepath: 'base64',
+          rotate: '0',
+          cutpage: '0',
+          camidx: '0',
+          ColorMode: '0',
+          quality: '3'
+        }
+        const response = await axios.post(
+          `${this.apiBaseUrl}/video=grabimage`,
+          JSON.stringify(data)
+        )
+
+        if (response.data && response.data.photoBase64) {
+          const jpg = 'data:image/jpg;base64,' + response.data.photoBase64
+          // const pngBase64 = await this.convertToPng(response.data.photoBase64)
+          this.photoHistory.unshift(jpg)
+          if (this.multiple) {
+            this.selectedPhotos.unshift(jpg)
+          } else {
+            this.selectedPhotos = [jpg]
+          }
+          this.$emit('capture', jpg)
+          // this.$message.success('拍照成功')
+        } else {
+          this.$message.error('拍照失败:未获取到图片数据')
+        }
+      } catch (error) {
+        this.$message.error('拍照失败:' + (error.message || '未知错误'))
+      } finally {
+        this.capturing = false
+      }
+    },
+    convertToPng(base64String) {
+      return new Promise((resolve, reject) => {
+        try {
+          if (base64String.startsWith('data:image/png')) {
+            resolve(base64String)
+            return
+          }
+          const img = new Image()
+          img.onload = () => {
+            const canvas = document.createElement('canvas')
+            canvas.width = img.width
+            canvas.height = img.height
+            const ctx = canvas.getContext('2d')
+            ctx.drawImage(img, 0, 0)
+            const pngBase64 = canvas.toDataURL('image/png')
+            resolve(pngBase64)
+          }
+          img.onerror = () => {
+            reject(new Error('图片加载失败'))
+          }
+          if (!base64String.startsWith('data:')) {
+            img.src = 'data:image/jpeg;base64,' + base64String
+          } else {
+            img.src = base64String
+          }
+        } catch (error) {
+          reject(error)
+        }
+      })
+    },
+    handleClearAll() {
+      this.photoHistory = []
+      this.selectedPhotos = []
+      this.showAllPhotos = false
+      this.$message.success('已清空全部拍照记录')
+    },
+    isPhotoSelected(photo) {
+      return this.selectedPhotos.includes(photo)
+    },
+    handleSelectPhoto(photo) {
+      const index = this.selectedPhotos.indexOf(photo)
+      if (index > -1) {
+        // 如果已选中,则取消选择
+        this.selectedPhotos.splice(index, 1)
+      } else {
+        // 如果未选中
+        if (this.multiple) {
+          // 多选模式:直接添加
+          this.selectedPhotos.push(photo)
+        } else {
+          // 单选模式:清空之前的选择,只选择当前照片
+          this.selectedPhotos = [photo]
+        }
+      }
+    },
+    handlePreviewPhoto(photo, index) {
+      this.previewImageList = this.photoHistory
+      this.previewIndex = index
+      this.showPreview = true
+      document.body.style.overflow = 'hidden'
+    },
+    handlePrevImage() {
+      if (this.previewIndex > 0) {
+        this.previewIndex--
+      }
+    },
+    handleNextImage() {
+      if (this.previewIndex < this.previewImageList.length - 1) {
+        this.previewIndex++
+      }
+    },
+    handleKeydown(event) {
+      if (!this.showPreview) return
+      if (event.key === 'ArrowLeft') {
+        this.handlePrevImage()
+      } else if (event.key === 'ArrowRight') {
+        this.handleNextImage()
+      } else if (event.key === 'Escape') {
+        this.showPreview = false
+      }
+    },
+    handleConfirm() {
+      if (this.selectedPhotos.length === 0) {
+        this.$message.warning('请选择至少一张图片')
+        return
+      }
+      this.$emit('confirm', this.selectedPhotos)
+      this.dialogVisible = false
+      this.showAllPhotos = false
+    },
+    handleClose() {
+      this.dialogVisible = false
+      this.closeVideo()
+    }
+  },
+  beforeUnmount() {
+    this.closeVideo()
+    if (this.videoTimer) {
+      clearInterval(this.videoTimer)
+    }
+    document.removeEventListener('keydown', this.handleKeydown)
+    document.body.style.overflow = ''
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.camera-dialog-content {
+  padding: 16px;
+}
+
+.video-preview {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 20px;
+  background-color: #000;
+  border-radius: 4px;
+  overflow: hidden;
+  min-height: 400px;
+  height: 400px;
+  position: relative;
+}
+
+.video-stream {
+  max-width: 100%;
+  max-height: 400px;
+  height: auto;
+  width: auto;
+  display: block;
+}
+
+.button-group {
+  display: flex;
+  justify-content: center;
+  gap: 12px;
+  margin-bottom: 20px;
+}
+
+.photo-history {
+  margin-top: 20px;
+  min-height: 140px;
+  display: flex;
+  flex-direction: column;
+}
+
+.photo-history-empty {
+  min-height: 140px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #fafafa;
+  border-radius: 4px;
+  border: 1px dashed #dcdfe6;
+}
+
+.empty-tip {
+  margin-top: 10px;
+  color: #909399;
+  font-size: 14px;
+}
+
+.thumbnail-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  justify-content: flex-start;
+  align-items: center;
+}
+
+.thumbnail-item {
+  width: 100px;
+  height: 100px;
+  cursor: pointer;
+  border: 2px solid #dcdfe6;
+  border-radius: 4px;
+  overflow: hidden;
+  transition: all 0.3s;
+  position: relative;
+}
+
+.thumbnail-item:hover {
+  border-color: #409eff;
+  transform: scale(1.05);
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+}
+
+.thumbnail-item.selected {
+  border-color: #409eff;
+  border-width: 3px;
+  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+}
+
+.selected-badge {
+  position: absolute;
+  top: 5px;
+  right: 5px;
+  width: 24px;
+  height: 24px;
+  background-color: #409eff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 14px;
+  z-index: 10;
+}
+
+.thumbnail-image {
+  width: 100%;
+  height: 100%;
+}
+
+.more-button-container {
+  width: 100px;
+  height: 100px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.more-button {
+  width: 100%;
+  height: 100%;
+}
+
+.all-photos-container {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+  gap: 15px;
+  max-height: 60vh;
+  overflow-y: auto;
+  padding: 10px;
+}
+
+.all-photo-item {
+  position: relative;
+  width: 100%;
+  aspect-ratio: 1;
+  cursor: pointer;
+  border: 2px solid #dcdfe6;
+  border-radius: 4px;
+  overflow: hidden;
+  transition: all 0.3s;
+}
+
+.all-photo-item:hover {
+  border-color: #409eff;
+  transform: scale(1.05);
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+}
+
+.all-photo-item.selected {
+  border-color: #409eff;
+  border-width: 3px;
+  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+}
+
+.selected-badge-large {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  width: 32px;
+  height: 32px;
+  background-color: #409eff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 18px;
+  z-index: 10;
+}
+
+.selected-info {
+  margin-top: 15px;
+  padding: 10px;
+  background-color: #f0f9ff;
+  border: 1px solid #409eff;
+  border-radius: 4px;
+  text-align: center;
+  color: #409eff;
+  font-weight: 500;
+}
+
+.qr-result {
+  margin-top: 20px;
+}
+
+.qr-content {
+  padding: 10px 0;
+}
+
+.qr-text {
+  word-break: break-all;
+  margin: 10px 0;
+  padding: 8px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  font-family: monospace;
+}
+
+.idcard-result {
+  margin-top: 20px;
+}
+
+.idcard-card {
+  margin-top: 10px;
+}
+
+.idcard-content {
+  display: flex;
+  gap: 20px;
+  flex-wrap: wrap;
+}
+
+.idcard-photo {
+  flex: 0 0 200px;
+}
+
+.idcard-image {
+  width: 200px;
+  height: auto;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.idcard-info {
+  flex: 1;
+  min-width: 300px;
+}
+
+.idcard-actions {
+  margin-top: 15px;
+  display: flex;
+  gap: 10px;
+}
+
+.barcode-result {
+  margin-top: 20px;
+}
+
+.barcode-content {
+  padding: 10px 0;
+}
+
+.barcode-text {
+  word-break: break-all;
+  margin: 10px 0;
+  padding: 8px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  font-family: monospace;
+}
+
+.fullscreen-preview {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 8888;
+  width: 100vw;
+  height: 100vh;
+  background-color: rgba(0, 0, 0, 0.9);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+}
+
+.preview-content {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.preview-image-fullscreen {
+  max-width: 90%;
+  max-height: 90%;
+  cursor: default;
+}
+
+.arrow-left,
+.arrow-right {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 60px;
+  height: 60px;
+  background-color: rgba(255, 255, 255, 0.2);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  transition: all 0.3s;
+  color: white;
+  z-index: 10;
+}
+
+.arrow-left:hover,
+.arrow-right:hover {
+  background-color: rgba(255, 255, 255, 0.4);
+  transform: translateY(-50%) scale(1.1);
+}
+
+.arrow-left {
+  left: 30px;
+}
+
+.arrow-right {
+  right: 30px;
+}
+
+.preview-close {
+  position: absolute;
+  top: 30px;
+  right: 30px;
+  width: 40px;
+  height: 40px;
+  background-color: rgba(255, 255, 255, 0.2);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  transition: all 0.3s;
+  color: white;
+  z-index: 10;
+}
+
+.preview-close:hover {
+  background-color: rgba(255, 255, 255, 0.4);
+  transform: scale(1.1);
+}
+
+.preview-info-fullscreen {
+  position: absolute;
+  bottom: 30px;
+  left: 50%;
+  transform: translateX(-50%);
+  padding: 10px 20px;
+  background-color: rgba(0, 0, 0, 0.6);
+  color: white;
+  border-radius: 20px;
+  font-size: 16px;
+  z-index: 10;
+}
+
+.all-photo-image {
+  width: 100%;
+  height: 100%;
+}
+
+.photo-index {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
+  color: white;
+  padding: 5px;
+  font-size: 12px;
+  text-align: center;
+}
+</style>
+<style lang="scss">
+.attachment-uploader-dialog {
+  .camera-dialog {
+    .el-dialog__body {
+      height: auto !important;
+    }
+  }
+}
+</style>

+ 94 - 0
src/business/platform/file/uploader/RequestConcurrencyController.js

@@ -0,0 +1,94 @@
+/**
+* 并发请求控制器(限制最大并发数)
+* @param {number} maxConcurrency - 最大并发数,此处设为 6
+*/
+class RequestConcurrencyController {
+  constructor(maxConcurrency = 6) {
+    this.maxConcurrency = maxConcurrency; // 最大并发数
+    this.taskQueue = []; // 待执行的任务队列
+    this.runningCount = 0; // 当前正在执行的请求数
+  }
+
+  /**
+   * 添加请求任务到队列
+   * @param {Function} task - 异步请求函数(需返回 Promise)
+   * @returns {Promise} 返回任务执行结果的 Promise
+   */
+  addTask(task) {
+    return new Promise((resolve, reject) => {
+      // 将任务和回调封装后加入队列
+      this.taskQueue.push({
+        task,
+        resolve,
+        reject
+      });
+      // 立即尝试执行任务(核心:每次添加任务后检查是否可执行)
+      this.runTasks();
+    });
+  }
+
+  /**
+   * 执行队列中的任务(核心逻辑)
+   */
+  runTasks() {
+    // 终止条件:无待执行任务 或 已达最大并发数
+    if (this.taskQueue.length === 0 || this.runningCount >= this.maxConcurrency) {
+      return;
+    }
+
+    // 取出队列第一个任务执行
+    const { task, resolve, reject } = this.taskQueue.shift();
+    this.runningCount++; // 正在执行的请求数+1
+
+    // 执行异步任务
+    task()
+      .then((result) => {
+        resolve(result); // 任务成功,返回结果
+      })
+      .catch((error) => {
+        reject(error); // 任务失败,返回错误
+      })
+      .finally(() => {
+        this.runningCount--; // 执行完成,计数器-1
+        this.runTasks(); // 递归执行下一个任务
+      });
+  }
+}
+
+// // -------------------------- 实战示例 --------------------------
+// // 模拟异步请求函数(可替换为真实的 fetch/axios 请求)
+// function mockRequest(id) {
+//   return new Promise((resolve) => {
+//     // 模拟请求耗时 1-3 秒
+//     const delay = Math.random() * 2000 + 1000;
+//     console.log(`[${new Date().toLocaleTimeString()}] 开始执行请求 ${id},耗时 ${delay.toFixed(0)}ms`);
+//     setTimeout(() => {
+//       resolve(`请求 ${id} 执行完成`);
+//     }, delay);
+//   });
+// }
+
+// // 使用并发控制器(最大并发数 6)
+// async function testConcurrency() {
+//   const controller = new RequestConcurrencyController(6);
+//   const taskResults = [];
+
+//   // 模拟 20 个请求任务
+//   for (let i = 1; i <= 20; i++) {
+//     // 添加任务到控制器,收集 Promise
+//     taskResults.push(controller.addTask(() => mockRequest(i)));
+//   }
+
+//   // 等待所有任务完成
+//   const results = await Promise.allSettled(taskResults);
+//   // 打印最终结果
+//   results.forEach((result, index) => {
+//     if (result.status === 'fulfilled') {
+//       console.log(`[结果] ${result.value}`);
+//     } else {
+//       console.log(`[结果] 请求 ${index + 1} 失败:${result.reason}`);
+//     }
+//   });
+// }
+
+export default RequestConcurrencyController

+ 158 - 2
src/business/platform/file/uploader/upload.vue

@@ -23,6 +23,16 @@
             size="mini"
             >选择要上传的文件</el-button
           >
+          <el-button
+            type="primary"
+            icon="el-icon-camera-solid"
+            class="primary"
+            size="mini"
+            v-if="showCamera"
+            style="margin-left: 6px"
+            @click="cameraUplod"
+            >高拍仪拍照上传</el-button
+          >
           <el-button
             type="danger"
             icon="ibps-icon-remove"
@@ -58,17 +68,27 @@
         <img :src="dialogImageUrl" width="100%" alt="" />
       </el-dialog>
     </div>
+    <CameraDialogV2
+      v-model="showDialog"
+      :multiple="multiple"
+      :api-base-url="apiBaseUrl"
+      @capture="handleCapture"
+      @confirm="handleConfirm"
+    />
   </div>
 </template>
 <script>
 import { uploadFile, remove, deleteFile } from '@/api/platform/file/attachment'
 import { uploadTemplateFile } from '@/api/platform/file/onlyoffice'
+import RequestConcurrencyController from './RequestConcurrencyController'
+import dayjs from 'dayjs'
 import {
   fileTypes,
   allFileTypes,
   accept as acceptTypes
 } from '@/business/platform/file/constants/fileTypes'
 import { compress } from '../utils/compress.js'
+import CameraDialogV2 from './CameraDialogV2.vue'
 
 export default {
   props: {
@@ -94,17 +114,35 @@ export default {
       default: 'attachment'
     }
   },
+  components: {
+    CameraDialogV2
+  },
+  mounted() {
+    console.log('this.accept', this.accept)
+  },
   data() {
     return {
       uploadData: {}, // 可以添加分类、文件等信息
       fileList: [],
       dialogVisible: false,
+      showDialog: false,
       dialogImageUrl: '',
+      apiBaseUrl: 'http://127.0.0.1:38088',
       fileTypes: fileTypes,
       allFileTypes: allFileTypes,
       acceptTypes: acceptTypes
     }
   },
+  computed: {
+    showCamera() {
+      const { setting } = this.$store.getters || {}
+      console.log(setting)
+      return (
+        setting.system?.cameraTool &&
+        (this.accept.includes('jpg') || this.accept === '*')
+      )
+    }
+  },
   watch: {
     init: {
       handler() {
@@ -116,6 +154,121 @@ export default {
     }
   },
   methods: {
+    cameraUplod() {
+      this.showDialog = true
+    },
+    handleCapture() {},
+    async handleConfirm(pics) {
+      // 处理单选和多选的情况
+      const picArray = Array.isArray(pics) ? pics : [pics]
+      if (picArray.length === 0) {
+        return
+      }
+      // 单选模式下,如果已有文件,先清空
+      if (!this.multiple && this.fileList.length > 0) {
+        // 如果有已上传的文件,需要先删除
+        const uploadedFiles = this.fileList.filter(
+          (f) => f.response && f.response.data
+        )
+        if (uploadedFiles.length > 0) {
+          const ids = uploadedFiles.map((f) => f.response.data.id).join(',')
+          try {
+            await this.handleRemoteRemove(ids, () => {})
+          } catch (error) {
+            console.error('删除旧文件失败:', error)
+          }
+        }
+        if (this.$refs.upload) {
+          this.$refs.upload.clearFiles()
+        }
+        this.fileList = []
+      }
+
+      // 将 base64 转换为 File 对象并触发上传
+      for (let i = 0; i < picArray.length; i++) {
+        const base64Str = picArray[i]
+        const timestamp = dayjs().format('YYYY-MM-DD_HH-mm-ss')
+        const file = this.base64ToPngFile(
+          base64Str,
+          `拍照_${timestamp}${picArray.length > 1 ? `_${i + 1}` : ''}.jpg`
+        )
+
+        // 文件校验
+        const canUpload = this.beforeUpload(file)
+        if (canUpload === false) {
+          continue
+        }
+
+        // 创建符合 el-upload 格式的文件对象
+        const uploadFileObj = {
+          uid: Date.now() + i + Math.random(),
+          name: file.name,
+          size: file.size,
+          type: file.type,
+          raw: file,
+          status: 'ready',
+          url: base64Str // 使用 base64 作为预览图
+        }
+
+        // 添加到 fileList(先添加,这样可以看到上传进度)
+        this.fileList.push(uploadFileObj)
+
+        // 触发上传
+        try {
+          uploadFileObj.status = 'uploading'
+          const response = await this.httpRequest({
+            file: file,
+            onProgress: (event) => {
+              if (event && event.total) {
+                uploadFileObj.percentage = Math.round(
+                  (event.loaded / event.total) * 100
+                )
+              }
+            }
+          })
+
+          // 上传成功
+          uploadFileObj.status = 'success'
+          uploadFileObj.response = response
+          uploadFileObj.percentage = 100
+
+          // 更新预览图 URL
+          if (response && response.data) {
+            const ext = this.getExtName(file.name)
+            if (['jpg', 'jpeg', 'bmp', 'png'].includes(ext)) {
+              uploadFileObj.url = response.data.url || base64Str
+            } else {
+              uploadFileObj.url = `${this.$baseUrl}images/file/${
+                ext || 'file'
+              }.png`
+            }
+          }
+
+          // 调用成功回调
+          this.handleSuccess(response, uploadFileObj, this.fileList)
+        } catch (error) {
+          // 上传失败
+          uploadFileObj.status = 'fail'
+          uploadFileObj.percentage = 0
+          this.handleError(error, uploadFileObj, this.fileList)
+        }
+      }
+    },
+    base64ToPngFile(
+      base64Str,
+      fileName = '拍照' + dayjs().format('YYYY-MM-DD HH:mm:ss')
+    ) {
+      const base64Data = base64Str.replace(/^data:image\/jpg;base64,/, '')
+      const binaryStr = atob(base64Data)
+      const len = binaryStr.length
+      const uint8Array = new Uint8Array(len)
+      for (let i = 0; i < len; i++) {
+        uint8Array[i] = binaryStr.charCodeAt(i)
+      }
+      const blob = new Blob([uint8Array], { type: 'image/jpg' })
+      const file = new File([blob], fileName, { type: 'image/jpg' })
+      return file
+    },
     /**
      * 文件上传
      */
@@ -279,6 +432,7 @@ export default {
       }
       file.url = url
       this.fileList = fileList
+      console.log(fileList)
       this.emitCallback(fileList)
     },
     // 获取扩展名
@@ -343,11 +497,13 @@ export default {
     },
     handleRemoteRemove(ids, callback) {
       // 删除附件文件
-      deleteFile({ attachmentIds: ids })
+      return deleteFile({ attachmentIds: ids })
         .then(() => {
           const _this = this
           // this.fileList = []
-          callback(_this)
+          if (callback) {
+            callback(_this)
+          }
         })
         .catch(() => {})
     },

+ 3 - 1
src/locales/zh-CN.json

@@ -241,7 +241,9 @@
       "504": "[504]网关超时",
       "505": "[505]HTTP版本不受支持",
       "6010101": "非法请求",
-      "6020101": "用户名或密码错误"
+      "6020101": "用户名或密码错误",
+      "201": "用户token权限不足,请联系仓库管理员添加读写权限",
+      "202": "缺少用户Gogs令牌,请先创建个人访问令牌"
     },
     "unknown": "未知错误,错误码:{state}"
   },

+ 362 - 335
src/views/business/performNew/myRecord.vue

@@ -1,360 +1,387 @@
 <template>
-    <div class="main-container">
-      <ibps-crud
-        ref="crud"
-        :display-field="title"
-        :height="height"
-        :data="listData"
-        :toolbars="listConfig.toolbars"
-        :search-form="listConfig.searchForm"
-        :pk-key="pkKey"
-        :columns="listConfig.columns"
-        :row-handle="listConfig.rowHandle"
-        :pagination="pagination"
-        :loading="loading"
-        @action-event="handleAction"
-        @sort-change="handleSortChange"
-        @pagination-change="handlePaginationChange"
-        @row-dblclick="handleRowDblclick"
-      >
-        <template slot="time" slot-scope="scope">
-          <div>起:{{ scope.row.kaiShiShiJian }}</div>
-          <div>止:{{ scope.row.jieShuShiJian }}</div>
-        </template>
-        
-        <template slot="xingNengZhiBiaSearch">
-          <ibps-link-data
-            v-model="searchFormData.xingNengZhiBiao"
-            template-key="xnyzxnzbzly"
-            placeholder="请选择"
-            style="width: 250px"
-          />
-        </template>
+  <div class="main-container">
+    <ibps-crud
+      ref="crud"
+      :display-field="title"
+      :height="height"
+      :data="listData"
+      :toolbars="listConfig.toolbars"
+      :search-form="listConfig.searchForm"
+      :pk-key="pkKey"
+      :columns="listConfig.columns"
+      :row-handle="listConfig.rowHandle"
+      :pagination="pagination"
+      :loading="loading"
+      @action-event="handleAction"
+      @sort-change="handleSortChange"
+      @pagination-change="handlePaginationChange"
+      @row-dblclick="handleRowDblclick"
+    >
+      <template slot="time" slot-scope="scope">
+        <div>起:{{ scope.row.kaiShiShiJian }}</div>
+        <div>止:{{ scope.row.jieShuShiJian }}</div>
+      </template>
 
-        <template slot="shiYanFangFaSearch">
-          <ibps-custom-dialog
-            v-model="searchFormData.shiYanFangFa"
-            size="mini"
-            :template-key="'ffglxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验方法"
-            icon="el-icon-search"
-            style="width: 250px"
-          />
-        </template>
-        <template slot="shiYanXiangMuSearch">
-          <ibps-custom-dialog
-            v-model="searchFormData.shiYanXiangMu"
-            size="mini"
-            :template-key="'nlfwxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验项目"
-            icon="el-icon-search"
-            style="width: 250px"
-          />
-        </template>
-        <template slot="shiYanFangFa" slot-scope="{ row }">
-          <ibps-custom-dialog
-            v-model="row.shiYanFangFa"
-            size="mini"
-            :template-key="'ffglxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验方法"
-            icon="el-icon-search"
-            :disabled="true"
-          />
-        </template>
-        <template slot="shiYanXiangMu" slot-scope="{ row }">
-          <ibps-custom-dialog
-            v-model="row.shiYanXiangMu"
-            size="mini"
-            :template-key="'nlfwxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验项目"
-            icon="el-icon-search"
-            :disabled="true"
-          />
-        </template>
-      </ibps-crud>
-      <ExperimentalView
-        v-if="showConfig"
-        :visible.sync="showConfig"
-        :params="params"
-        :readonly="readonly"
-        @refresh="loadData"
-        @close="handleClose"
-      />
-    </div>
-  </template>
-  
-  <script>
-  import { queryExperimental, removeExperimental } from '@/api/business/pv'
-  import ActionUtils from '@/utils/action'
-  import FixHeight from '@/mixins/height'
-  import IbpsLinkData from '@/business/platform/data/templaterender/link-data'
-  import IbpsCustomDialog from '@/business/platform/data/templaterender/custom-dialog'
-  
-  export default {
-    components: {
-      ExperimentalView: () => import('./experimentalView'),
-      'ibps-link-data': IbpsLinkData,
-      IbpsCustomDialog
-    },
-    mixins: [FixHeight],
-    data() {
-      const { userList = [] } = this.$store.getters || {}
-      const userOption = userList.map((item) => ({
-        label: item.userName,
-        value: item.userId
-      }))
-      return {
-        userOption,
-        title: '我的快速评价',
-        pkKey: 'id', // 主键  如果主键不是pk需要传主键
-        loading: true,
-        height: document.clientHeight,
-        listData: [],
-        pagination: {},
-        sorts: {},
-        searchFormData: {
-          xingNengZhiBiao: '',
-          shiYanFangFa: '',
-          shiYanXiangMu: ''
-        }, // 新增:为搜索表单创建独立的数据模型
-        showConfig: false,
-        readonly: false,
-        params: {},
-        targetOption: [],
-        methodOption: [],
-        
-        listConfig: {
-          toolbars: [{ key: 'search' }, { key: 'remove' }],
-          searchForm: {
-            model: this.searchFormData, // 新增:绑定数据模型               
-            itemWidth: 250,
-            forms: [
+      <template slot="xingNengZhiBiaSearch">
+        <ibps-link-data
+          v-model="searchFormData.xingNengZhiBiao"
+          template-key="xnyzxnzbzly"
+          placeholder="请选择"
+          style="width: 250px"
+        />
+      </template>
 
-              { prop: 'Q^shi_yan_xiang_mu_^SL', label: '实验项目', fieldType: 'slot', slotName: 'shiYanXiangMuSearch' },
-              { prop: 'Q^xing_neng_zhi_bia^SL', label: '性能指标', fieldType: 'slot', slotName: 'xingNengZhiBiaSearch' },
-              { prop: 'Q^fang_an_lei_xing_^SL', label: '方案类型' },
-              { prop: 'Q^shi_yan_fang_fa_^SL', label: '实验方法', fieldType: 'slot', slotName: 'shiYanFangFaSearch' },
-              { prop: 'Q^yang_ben_lei_xing^SL', label: '样本类型' },
-              {
-                prop: 'Q^shi_fou_guo_shen_^SL',
-                label: '状态',
-                fieldType: 'select',
-                options: [
-                  { label: '已暂存', value: '已暂存' },
-                  { label: '已编制', value: '已编制' },
-                  { label: '已审核', value: '已审核' },
-                  { label: '已完成', value: '已完成' },
-                  { label: '已退回', value: '已退回' },
-                  { label: '已终止', value: '已终止' }
-                ]
-              },
-              {
-                prop: ['Q^create_time_^DL', 'Q^create_time_^DG'],
-                label: '创建时间',
-                fieldType: 'daterange'
-              }
-            ]
-          },
-          // 表格字段配置
-          columns: [
-            { prop: 'xingNengZhiBia', label: '性能指标', tags: [], width: 120 },
+      <template slot="shiYanFangFaSearch">
+        <ibps-custom-dialog
+          v-model="searchFormData.shiYanFangFa"
+          size="mini"
+          :template-key="'ffglxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验方法"
+          icon="el-icon-search"
+          style="width: 250px"
+        />
+      </template>
+      <template slot="shiYanXiangMuSearch">
+        <ibps-custom-dialog
+          v-model="searchFormData.shiYanXiangMu"
+          size="mini"
+          :template-key="'nlfwxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验项目"
+          icon="el-icon-search"
+          style="width: 250px"
+        />
+      </template>
+      <template slot="shiYanFangFa" slot-scope="{ row }">
+        <ibps-custom-dialog
+          v-model="row.shiYanFangFa"
+          size="mini"
+          :template-key="'ffglxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验方法"
+          icon="el-icon-search"
+          :disabled="true"
+        />
+      </template>
+      <template slot="shiYanXiangMu" slot-scope="{ row }">
+        <ibps-custom-dialog
+          v-model="row.shiYanXiangMu"
+          size="mini"
+          :template-key="'nlfwxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验项目"
+          icon="el-icon-search"
+          :disabled="true"
+        />
+      </template>
+    </ibps-crud>
+    <ExperimentalView
+      v-if="showConfig"
+      :visible.sync="showConfig"
+      :params="params"
+      :readonly="readonly"
+      @refresh="loadData"
+      @close="handleClose"
+    />
+  </div>
+</template>
+
+<script>
+import { queryExperimental, removeExperimental } from '@/api/business/pv'
+import ActionUtils from '@/utils/action'
+import FixHeight from '@/mixins/height'
+import IbpsLinkData from '@/business/platform/data/templaterender/link-data'
+import IbpsCustomDialog from '@/business/platform/data/templaterender/custom-dialog'
+
+export default {
+  components: {
+    ExperimentalView: () => import('./experimentalView'),
+    'ibps-link-data': IbpsLinkData,
+    IbpsCustomDialog
+  },
+  mixins: [FixHeight],
+  data() {
+    const { userList = [] } = this.$store.getters || {}
+    const userOption = userList.map((item) => ({
+      label: item.userName,
+      value: item.userId
+    }))
+    return {
+      userOption,
+      title: '我的快速评价',
+      pkKey: 'id', // 主键  如果主键不是pk需要传主键
+      loading: true,
+      height: document.clientHeight,
+      listData: [],
+      pagination: {},
+      sorts: {},
+      searchFormData: {
+        xingNengZhiBiao: '',
+        shiYanFangFa: '',
+        shiYanXiangMu: ''
+      }, // 新增:为搜索表单创建独立的数据模型
+      showConfig: false,
+      readonly: false,
+      params: {},
+      targetOption: [],
+      methodOption: [],
+
+      listConfig: {
+        toolbars: [{ key: 'search' }, { key: 'remove' }],
+        searchForm: {
+          model: this.searchFormData, // 新增:绑定数据模型
+          itemWidth: 250,
+          forms: [
             {
-              prop: 'fangAnLeiXing',
-              label: '方案类型',
-              tags: [],
-              minWidth: 125
+              prop: 'Q^shi_yan_xiang_mu_^SL',
+              label: '实验项目',
+              fieldType: 'slot',
+              slotName: 'shiYanXiangMuSearch'
             },
-            
-            { prop: 'shiYanXiangMu', label: '实验项目', slotName: 'shiYanXiangMu', width: 130 },
-            { prop: 'shiYanFangFa', label: '实验方法', slotName: 'shiYanFangFa', width: 190 },
-            { prop: 'yangBenLeiXing', label: '样本类型', width: 100 },
-            { prop: 'shiYanYiQi', label: '实验仪器', width: 120 },
             {
-              prop: 'timeRange',
-              label: '实验时间',
-              slotName: 'time',
-              width: 140
+              prop: 'Q^xing_neng_zhi_bia^SL',
+              label: '性能指标',
+              fieldType: 'slot',
+              slotName: 'xingNengZhiBiaSearch'
             },
-            { prop: 'shiFouGuoShen', label: '状态', width: 80 },
+            { prop: 'Q^fang_an_lei_xing_^SL', label: '方案类型' },
             {
-              prop: 'bianZhiRen',
-              label: '实验人',
-              tags: userOption,
-              width: 90
+              prop: 'Q^shi_yan_fang_fa_^SL',
+              label: '实验方法',
+              fieldType: 'slot',
+              slotName: 'shiYanFangFaSearch'
             },
-            { prop: 'createBy', label: '评价人', tags: userOption, width: 90 },
+            { prop: 'Q^yang_ben_lei_xing^SL', label: '样本类型' },
             {
-              prop: 'createTime',
+              prop: 'Q^shi_fou_guo_shen_^SL',
+              label: '状态',
+              fieldType: 'select',
+              options: [
+                { label: '已暂存', value: '已暂存' },
+                { label: '已编制', value: '已编制' },
+                { label: '已审核', value: '已审核' },
+                { label: '已完成', value: '已完成' },
+                { label: '已退回', value: '已退回' },
+                { label: '已终止', value: '已终止' }
+              ]
+            },
+            {
+              prop: ['Q^create_time_^DL', 'Q^create_time_^DG'],
               label: '创建时间',
-              dateFormat: 'yyyy-MM-dd HH:mm',
-              sortable: 'custom',
-              width: 130
+              fieldType: 'daterange'
             }
-          ],
-          rowHandle: {
-            effect: 'display',
-            width: 80,
-            align: 'right',  // 添加这一行
-            //fixed: 'right',  // 如果有需要,可以固定在右侧
-            actions: [
-              {
-                key: 'edit',
-                label: '编辑',
-                type: 'primary',
-                icon: 'ibps-icon-edit',
-                hidden: (row) => row.shiFouGuoShen !== '已暂存' 
-                && row.shiFouGuoShen !== '已退回'
-              },
-              {
-                key: 'detail',
-                label: '查阅',
-                type: 'primary',
-                icon: 'ibps-icon-eye',
-                //hidden: (row) => row.shiFouGuoShen === '已暂存'
-              }
-            ]
+          ]
+        },
+        // 表格字段配置
+        columns: [
+          { prop: 'xingNengZhiBia', label: '性能指标', tags: [], width: 120 },
+          {
+            prop: 'fangAnLeiXing',
+            label: '方案类型',
+            tags: [],
+            minWidth: 125
+          },
+
+          {
+            prop: 'shiYanXiangMu',
+            label: '实验项目',
+            slotName: 'shiYanXiangMu',
+            width: 130
+          },
+          {
+            prop: 'shiYanFangFa',
+            label: '实验方法',
+            slotName: 'shiYanFangFa',
+            width: 190
+          },
+          { prop: 'yangBenLeiXing', label: '样本类型', width: 100 },
+          { prop: 'shiYanYiQi', label: '实验仪器', width: 120 },
+          {
+            prop: 'timeRange',
+            label: '实验时间',
+            slotName: 'time',
+            width: 140
+          },
+          { prop: 'shiFouGuoShen', label: '状态', width: 80 },
+          {
+            prop: 'bianZhiRen',
+            label: '实验人',
+            tags: userOption,
+            width: 90
+          },
+          { prop: 'createBy', label: '评价人', tags: userOption, width: 90 },
+          {
+            prop: 'createTime',
+            label: '创建时间',
+            dateFormat: 'yyyy-MM-dd HH:mm',
+            sortable: 'custom',
+            width: 130
           }
+        ],
+        rowHandle: {
+          effect: 'display',
+          width: 80,
+          align: 'right', // 添加这一行
+          //fixed: 'right',  // 如果有需要,可以固定在右侧
+          actions: [
+            {
+              key: 'edit',
+              label: '编辑',
+              type: 'primary',
+              icon: 'ibps-icon-edit',
+              hidden: (row) =>
+                row.shiFouGuoShen !== '已暂存' && row.shiFouGuoShen !== '已退回'
+            },
+            {
+              key: 'detail',
+              label: '查阅',
+              type: 'primary',
+              icon: 'ibps-icon-eye'
+              //hidden: (row) => row.shiFouGuoShen === '已暂存'
+            }
+          ]
         }
       }
+    }
+  },
+  created() {
+    this.loadData()
+  },
+  methods: {
+    // 加载数据
+    loadData() {
+      this.loading = true
+      queryExperimental(this.getSearchFormData())
+        .then((res) => {
+          ActionUtils.handleListData(this, res.data)
+          this.loading = false
+        })
+        .catch(() => {
+          this.loading = false
+        })
     },
-    created() {
+    /**
+     * 获取格式化参数
+     */
+    getSearchFormData() {
+      const searchParam = this.$refs['crud']
+        ? this.$refs['crud'].getSearcFormData()
+        : {}
+      const { userId } = this.$store.getters || {}
+      searchParam['Q^create_by_^S'] = userId
+      searchParam['Q^xing_neng_zhi_bia^SL'] =
+        this.searchFormData.xingNengZhiBiao
+      searchParam['Q^shi_yan_xiang_mu_^S'] = this.searchFormData.shiYanXiangMu
+      searchParam['Q^shi_yan_fang_fa_^S'] = this.searchFormData.shiYanFangFa
+
+      const statusArr = Object.keys(searchParam).filter((key) =>
+        key.includes('shi_fou_guo_shen_')
+      )
+      if (statusArr.length == 0) {
+        searchParam['Q^shi_fou_guo_shen_^NN'] = '1'
+        searchParam['Q^shi_fou_guo_shen_^SNE'] = '1'
+      }
+      return ActionUtils.formatParams(searchParam, this.pagination, this.sorts)
+    },
+    /**
+     * 处理分页事件
+     */
+    handlePaginationChange(page) {
+      ActionUtils.setPagination(this.pagination, page)
       this.loadData()
     },
-    methods: {
-      // 加载数据
-      loadData() {
-        this.loading = true
-        queryExperimental(this.getSearchFormData())
-          .then((res) => {
-            ActionUtils.handleListData(this, res.data)
-            this.loading = false
-          })
-          .catch(() => {
-            this.loading = false
-          })
-      },
-      /**
-       * 获取格式化参数
-       */
-      getSearchFormData() {
-        const searchParam = this.$refs['crud'] ? this.$refs['crud'].getSearcFormData() : {}
-        const { userId } = this.$store.getters || {}
-        searchParam['Q^create_by_^S'] = userId
-        searchParam['Q^xing_neng_zhi_bia^SL'] = this.searchFormData.xingNengZhiBiao
-        searchParam['Q^shi_yan_xiang_mu_^S'] = this.searchFormData.shiYanXiangMu
-        searchParam['Q^shi_yan_fang_fa_^S'] = this.searchFormData.shiYanFangFa
-        
-        const statusArr = Object.keys(searchParam).filter((key) =>
-          key.includes('shi_fou_guo_shen_')
-        )
-        if(statusArr.length == 0){
-          searchParam['Q^shi_fou_guo_shen_^NN'] = '1'
-          searchParam['Q^shi_fou_guo_shen_^SNE'] = '1'
-        } 
-        return ActionUtils.formatParams(searchParam, this.pagination, this.sorts)
-      },
-      /**
-       * 处理分页事件
-       */
-      handlePaginationChange(page) {
-        ActionUtils.setPagination(this.pagination, page)
-        this.loadData()
-      },
-      /**
-       * 处理排序
-       */
-      handleSortChange(sort) {
-        ActionUtils.setSorts(this.sorts, sort)
-        this.loadData()
-      },
-      /**
-       * 查询
-       */
-      search() {
-        this.loadData()
-      },
-      /**
-       * 处理按钮事件
-       */
-      handleAction(command, position, selection, data) {
-        switch (command) {
-          case 'search':
-            ActionUtils.setFirstPagination(this.pagination)
-            this.search()
-            break
-          case 'edit':
-          case 'detail': // 新增查阅只读
-            this.handleEdit(data, command)
-            break
-          case 'report':
-            this.handleReport(data)
-            break
-          case 'remove':
-            ActionUtils.removeRecord(selection)
-              .then((ids) => {
-                this.handleRemove(ids)
-              })
-              .catch(() => {})
-            break
-          default:
-            break
-        }
-      },
-      /**
-       * 处理编辑
-       */
-      async handleEdit(data, key) {
-        const { id, zhiBiaoId, fangFaId, fangFaKey, shiFouGuoShen } = data || {}
-        this.params = {
-          targetId: zhiBiaoId,
-          methodId: fangFaId,
-          methodKey: fangFaKey,
-          recordId: id,
-          action: key,
-          shiFouGuoShen: shiFouGuoShen
-        }
-        this.readonly = key === 'detail'
-        this.showConfig = true 
-      },
-      handleReport(data) {
-        console.log('wwww')
-      },
-      /**
-       * 处理删除
-       */
-      handleRemove(ids) {
-        // return this.$message.warning('避免误删测试数据,联系开发删除')
-        removeExperimental({ ids })
-          .then(() => {
-            ActionUtils.removeSuccessMessage()
-            this.search()
-          })
-          .catch(() => {})
-      },
-      handleRowDblclick(row) {
-        this.handleEdit(row, 'detail')
-      },
-      handleClose() {
-        this.showConfig = false;
+    /**
+     * 处理排序
+     */
+    handleSortChange(sort) {
+      ActionUtils.setSorts(this.sorts, sort)
+      this.loadData()
+    },
+    /**
+     * 查询
+     */
+    search() {
+      this.loadData()
+    },
+    /**
+     * 处理按钮事件
+     */
+    handleAction(command, position, selection, data) {
+      switch (command) {
+        case 'search':
+          ActionUtils.setFirstPagination(this.pagination)
+          this.search()
+          break
+        case 'edit':
+        case 'detail': // 新增查阅只读
+          this.handleEdit(data, command)
+          break
+        case 'report':
+          this.handleReport(data)
+          break
+        case 'remove':
+          ActionUtils.removeRecord(selection)
+            .then((ids) => {
+              this.handleRemove(ids)
+            })
+            .catch(() => {})
+          break
+        default:
+          break
+      }
+    },
+    /**
+     * 处理编辑
+     */
+    async handleEdit(data, key) {
+      const { id, zhiBiaoId, fangFaId, fangFaKey, shiFouGuoShen } = data || {}
+      this.params = {
+        targetId: zhiBiaoId,
+        methodId: fangFaId,
+        methodKey: fangFaKey,
+        recordId: id,
+        action: key,
+        shiFouGuoShen: shiFouGuoShen
       }
+      this.readonly = key === 'detail'
+      this.showConfig = true
+    },
+    handleReport(data) {
+      console.log('wwww')
+    },
+    /**
+     * 处理删除
+     */
+    handleRemove(ids) {
+      // return this.$message.warning('避免误删测试数据,联系开发删除')
+      removeExperimental({ ids })
+        .then(() => {
+          ActionUtils.removeSuccessMessage()
+          this.search()
+        })
+        .catch(() => {})
+    },
+    handleRowDblclick(row) {
+      this.handleEdit(row, 'detail')
+    },
+    handleClose() {
+      this.showConfig = false
     }
   }
-  </script>
-  <style lang="scss">
-  .attachment-uploader-dialog {
-    .el-dialog__body {
-      height: calc(57vh - 100px) !important;
-    }
+}
+</script>
+<style lang="scss">
+.attachment-uploader-dialog {
+  .el-dialog__body {
+    height: calc(57vh - 100px) !important;
   }
-  </style>
+}
+</style>
 <style lang="scss" scoped>
 .main-container ::v-deep .el-table__body-wrapper {
   .el-table__row {

+ 128 - 104
src/views/business/performNew/record.vue

@@ -22,62 +22,62 @@
         <div>止:{{ scope.row.jieShuShiJian }}</div>
       </template>
       <template slot="xingNengZhiBiaSearch">
-          <ibps-link-data
-            v-model="searchFormData.xingNengZhiBiao"
-            template-key="xnyzxnzbzly"
-            placeholder="请选择"
-            style="width: 250px"
-          />
-        </template>
+        <ibps-link-data
+          v-model="searchFormData.xingNengZhiBiao"
+          template-key="xnyzxnzbzly"
+          placeholder="请选择"
+          style="width: 250px"
+        />
+      </template>
 
-        <template slot="shiYanFangFaSearch">
-          <ibps-custom-dialog
-            v-model="searchFormData.shiYanFangFa"
-            size="mini"
-            :template-key="'ffglxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验方法"
-            icon="el-icon-search"
-            style="width: 250px"
-          />
-        </template>
-        <template slot="shiYanXiangMuSearch">
-          <ibps-custom-dialog
-            v-model="searchFormData.shiYanXiangMu"
-            size="mini"
-            :template-key="'nlfwxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验项目"
-            icon="el-icon-search"
-            style="width: 250px"
-          />
-        </template>
-        <template slot="shiYanFangFa" slot-scope="{ row }">
-          <ibps-custom-dialog
-            v-model="row.shiYanFangFa"
-            size="mini"
-            :template-key="'ffglxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验方法"
-            icon="el-icon-search"
-            :disabled="true"
-          />
-        </template>
-        <template slot="shiYanXiangMu" slot-scope="{ row }">
-          <ibps-custom-dialog
-            v-model="row.shiYanXiangMu"
-            size="mini"
-            :template-key="'nlfwxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验项目"
-            icon="el-icon-search"
-            :disabled="true"
-          />
-        </template>
+      <template slot="shiYanFangFaSearch">
+        <ibps-custom-dialog
+          v-model="searchFormData.shiYanFangFa"
+          size="mini"
+          :template-key="'ffglxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验方法"
+          icon="el-icon-search"
+          style="width: 250px"
+        />
+      </template>
+      <template slot="shiYanXiangMuSearch">
+        <ibps-custom-dialog
+          v-model="searchFormData.shiYanXiangMu"
+          size="mini"
+          :template-key="'nlfwxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验项目"
+          icon="el-icon-search"
+          style="width: 250px"
+        />
+      </template>
+      <template slot="shiYanFangFa" slot-scope="{ row }">
+        <ibps-custom-dialog
+          v-model="row.shiYanFangFa"
+          size="mini"
+          :template-key="'ffglxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验方法"
+          icon="el-icon-search"
+          :disabled="true"
+        />
+      </template>
+      <template slot="shiYanXiangMu" slot-scope="{ row }">
+        <ibps-custom-dialog
+          v-model="row.shiYanXiangMu"
+          size="mini"
+          :template-key="'nlfwxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验项目"
+          icon="el-icon-search"
+          :disabled="true"
+        />
+      </template>
     </ibps-crud>
     <ExperimentalView
       v-if="showConfig"
@@ -125,33 +125,48 @@ export default {
       targetOption: [],
       methodOption: [],
       searchFormData: {
-          xingNengZhiBiao: '',
-          shiYanFangFa: '',
-          shiYanXiangMu: ''
+        xingNengZhiBiao: '',
+        shiYanFangFa: '',
+        shiYanXiangMu: ''
       }, // 新增:为搜索表单创建独立的数据模型
       listConfig: {
         toolbars: [{ key: 'search' }, { key: 'remove' }],
         searchForm: {
           itemWidth: 250,
           forms: [
-            { prop: 'Q^shi_yan_xiang_mu_^SL', label: '实验项目', fieldType: 'slot', slotName: 'shiYanXiangMuSearch' },
-            { prop: 'Q^xing_neng_zhi_bia^SL', label: '性能指标', fieldType: 'slot', slotName: 'xingNengZhiBiaSearch' },
+            {
+              prop: 'Q^shi_yan_xiang_mu_^SL',
+              label: '实验项目',
+              fieldType: 'slot',
+              slotName: 'shiYanXiangMuSearch'
+            },
+            {
+              prop: 'Q^xing_neng_zhi_bia^SL',
+              label: '性能指标',
+              fieldType: 'slot',
+              slotName: 'xingNengZhiBiaSearch'
+            },
             { prop: 'Q^fang_an_lei_xing_^SL', label: '方案类型' },
-            { prop: 'Q^shi_yan_fang_fa_^SL', label: '实验方法', fieldType: 'slot', slotName: 'shiYanFangFaSearch' },
+            {
+              prop: 'Q^shi_yan_fang_fa_^SL',
+              label: '实验方法',
+              fieldType: 'slot',
+              slotName: 'shiYanFangFaSearch'
+            },
             { prop: 'Q^yang_ben_lei_xing^SL', label: '样本类型' },
             {
-                prop: 'Q^shi_fou_guo_shen_^SL',
-                label: '状态',
-                fieldType: 'select',
-                options: [
-                  { label: '已暂存', value: '已暂存' },
-                  { label: '已编制', value: '已编制' },
-                  { label: '已审核', value: '已审核' },
-                  { label: '已完成', value: '已完成' },
-                  { label: '已退回', value: '已退回' },
-                  { label: '已终止', value: '已终止' }
-                ]
-              },
+              prop: 'Q^shi_fou_guo_shen_^SL',
+              label: '状态',
+              fieldType: 'select',
+              options: [
+                { label: '已暂存', value: '已暂存' },
+                { label: '已编制', value: '已编制' },
+                { label: '已审核', value: '已审核' },
+                { label: '已完成', value: '已完成' },
+                { label: '已退回', value: '已退回' },
+                { label: '已终止', value: '已终止' }
+              ]
+            },
             {
               prop: ['Q^create_time_^DL', 'Q^create_time_^DG'],
               label: '创建时间',
@@ -168,8 +183,18 @@ export default {
             tags: [],
             minWidth: 125
           },
-          { prop: 'shiYanXiangMu', label: '实验项目', slotName: 'shiYanXiangMu', width: 120 },
-          { prop: 'shiYanFangFa', label: '实验方法', slotName: 'shiYanFangFa', width: 180 },
+          {
+            prop: 'shiYanXiangMu',
+            label: '实验项目',
+            slotName: 'shiYanXiangMu',
+            width: 120
+          },
+          {
+            prop: 'shiYanFangFa',
+            label: '实验方法',
+            slotName: 'shiYanFangFa',
+            width: 180
+          },
           { prop: 'yangBenLeiXing', label: '样本类型', width: 100 },
           { prop: 'shiYanYiQi', label: '实验仪器', width: 120 },
           {
@@ -205,11 +230,11 @@ export default {
               icon: 'ibps-icon-edit'
             }*/
             {
-                key: 'detail',
-                label: '查阅',
-                type: 'primary',
-                icon: 'ibps-icon-eye',
-              }
+              key: 'detail',
+              label: '查阅',
+              type: 'primary',
+              icon: 'ibps-icon-eye'
+            }
           ]
         }
       }
@@ -235,22 +260,21 @@ export default {
      * 获取格式化参数
      */
     getSearchFormData() {
-      const searchParam = this.$refs['crud'] ? this.$refs['crud'].getSearcFormData() : {}
-      searchParam['Q^xing_neng_zhi_bia^SL'] = this.searchFormData.xingNengZhiBiao
+      const searchParam = this.$refs['crud']
+        ? this.$refs['crud'].getSearcFormData()
+        : {}
+      searchParam['Q^xing_neng_zhi_bia^SL'] =
+        this.searchFormData.xingNengZhiBiao
       searchParam['Q^shi_yan_xiang_mu_^S'] = this.searchFormData.shiYanXiangMu
       searchParam['Q^shi_yan_fang_fa_^S'] = this.searchFormData.shiYanFangFa
       const statusArr = Object.keys(searchParam).filter((key) =>
-          key.includes('shi_fou_guo_shen_')
-        )
-        if(statusArr.length == 0){
-          searchParam['Q^shi_fou_guo_shen_^NN'] = '1'
-          searchParam['Q^shi_fou_guo_shen_^SNE'] = '1'
-        } 
-      return ActionUtils.formatParams(
-        searchParam,
-        this.pagination,
-        this.sorts
+        key.includes('shi_fou_guo_shen_')
       )
+      if (statusArr.length == 0) {
+        searchParam['Q^shi_fou_guo_shen_^NN'] = '1'
+        searchParam['Q^shi_fou_guo_shen_^SNE'] = '1'
+      }
+      return ActionUtils.formatParams(searchParam, this.pagination, this.sorts)
     },
     /**
      * 处理分页事件
@@ -299,19 +323,19 @@ export default {
       }
     },
     /**
-      * 处理编辑\查阅
-    */
+     * 处理编辑\查阅
+     */
     async handleEdit(data, key) {
-        const { id, zhiBiaoId, fangFaId, fangFaKey, shiFouGuoShen } = data || {}
-        this.params = {
-          targetId: zhiBiaoId,
-          methodId: fangFaId,
-          methodKey: fangFaKey,
-          recordId: id,
-          shiFouGuoShen: shiFouGuoShen
-        }
-        this.readonly = key === 'detail'
-        this.showConfig = true 
+      const { id, zhiBiaoId, fangFaId, fangFaKey, shiFouGuoShen } = data || {}
+      this.params = {
+        targetId: zhiBiaoId,
+        methodId: fangFaId,
+        methodKey: fangFaKey,
+        recordId: id,
+        shiFouGuoShen: shiFouGuoShen
+      }
+      this.readonly = key === 'detail'
+      this.showConfig = true
     },
 
     handleReport(data) {

+ 191 - 166
src/views/business/performNew/recordVerify.vue

@@ -22,73 +22,72 @@
         <div>止:{{ scope.row.jieShuShiJian }}</div>
       </template>
       <template slot="xingNengZhiBiaSearch">
-          <ibps-link-data
-            v-model="searchFormData.xingNengZhiBiao"
-            template-key="xnyzxnzbzly"
-            placeholder="请选择"
-            style="width: 250px"
-          />
-        </template>
-
-        <template slot="shiYanFangFaSearch">
-          <ibps-custom-dialog
-            v-model="searchFormData.shiYanFangFa"
-            size="mini"
-            :template-key="'ffglxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验方法"
-            icon="el-icon-search"
-            style="width: 250px"
-          />
-        </template>
-        <template slot="shiYanXiangMuSearch">
-          <ibps-custom-dialog
-            v-model="searchFormData.shiYanXiangMu"
-            size="mini"
-            :template-key="'nlfwxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验项目"
-            icon="el-icon-search"
-            style="width: 250px"
-          />
-        </template>
-        <template slot="shiYanFangFa" slot-scope="{ row }">
-          <ibps-custom-dialog
-            v-model="row.shiYanFangFa"
-            size="mini"
-            :template-key="'ffglxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验方法"
-            icon="el-icon-search"
-            :disabled="true"
-          />
-        </template>
-        <template slot="shiYanXiangMu" slot-scope="{ row }">
-          <ibps-custom-dialog
-            v-model="row.shiYanXiangMu"
-            size="mini"
-            :template-key="'nlfwxz'"
-            type="dialog"
-            class="custom-dialog"
-            placeholder="请选择实验项目"
-            icon="el-icon-search"
-            :disabled="true"
-          />
-        </template>
-        <template slot="operation" slot-scope="scope">
-          <el-button
-            type="text"
-            size="mini"
-            icon="ibps-icon-edit"
-            @click="handleEdit(scope.row, 'edit')"
-          >
-            {{ scope.row.NAME_ === '审核' ? '审核' : '审批' }}
-          </el-button>
+        <ibps-link-data
+          v-model="searchFormData.xingNengZhiBiao"
+          template-key="xnyzxnzbzly"
+          placeholder="请选择"
+          style="width: 250px"
+        />
       </template>
 
+      <template slot="shiYanFangFaSearch">
+        <ibps-custom-dialog
+          v-model="searchFormData.shiYanFangFa"
+          size="mini"
+          :template-key="'ffglxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验方法"
+          icon="el-icon-search"
+          style="width: 250px"
+        />
+      </template>
+      <template slot="shiYanXiangMuSearch">
+        <ibps-custom-dialog
+          v-model="searchFormData.shiYanXiangMu"
+          size="mini"
+          :template-key="'nlfwxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验项目"
+          icon="el-icon-search"
+          style="width: 250px"
+        />
+      </template>
+      <template slot="shiYanFangFa" slot-scope="{ row }">
+        <ibps-custom-dialog
+          v-model="row.shiYanFangFa"
+          size="mini"
+          :template-key="'ffglxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验方法"
+          icon="el-icon-search"
+          :disabled="true"
+        />
+      </template>
+      <template slot="shiYanXiangMu" slot-scope="{ row }">
+        <ibps-custom-dialog
+          v-model="row.shiYanXiangMu"
+          size="mini"
+          :template-key="'nlfwxz'"
+          type="dialog"
+          class="custom-dialog"
+          placeholder="请选择实验项目"
+          icon="el-icon-search"
+          :disabled="true"
+        />
+      </template>
+      <template slot="operation" slot-scope="scope">
+        <el-button
+          type="text"
+          size="mini"
+          icon="ibps-icon-edit"
+          @click="handleEdit(scope.row, 'edit')"
+        >
+          {{ scope.row.NAME_ === '审核' ? '审核' : '审批' }}
+        </el-button>
+      </template>
     </ibps-crud>
     <ExperimentalView
       v-if="showConfig"
@@ -107,7 +106,7 @@ import ActionUtils from '@/utils/action'
 import FixHeight from '@/mixins/height'
 import IbpsLinkData from '@/business/platform/data/templaterender/link-data'
 import IbpsCustomDialog from '@/business/platform/data/templaterender/custom-dialog'
-  
+
 export default {
   components: {
     ExperimentalView: () => import('./experimentalView'),
@@ -121,24 +120,39 @@ export default {
       label: item.userName,
       value: item.userId
     }))
-    
+
     // 获取用户角色信息
     const userRole = this.$store.getters.userInfo.role || []
-    const jsfzrFlag = userRole.some(role => role.alias === 'jsfzr')
-    const zcFlag = userRole.some(role => role.alias === 'zhsfzr')
-    const isSystemAdmin = userRole.some(role => role.alias === 'xtgljs')
+    const jsfzrFlag = userRole.some((role) => role.alias === 'jsfzr')
+    const zcFlag = userRole.some((role) => role.alias === 'zhsfzr')
+    const isSystemAdmin = userRole.some((role) => role.alias === 'xtgljs')
     // 如果用户同时拥有专业组组长和技术负责人身份,视为系统管理员
     const isMultiRoleAdmin = jsfzrFlag && zcFlag
-    
+
     // 系统管理员或多身份用户才显示状态字段
     const showStatusField = isSystemAdmin || isMultiRoleAdmin
-    
+
     // 基础搜索表单字段
     const baseForms = [
-      { prop: 'Q^shi_yan_xiang_mu_^SL', label: '实验项目', fieldType: 'slot', slotName: 'shiYanXiangMuSearch' },
-      { prop: 'Q^xing_neng_zhi_bia^SL', label: '性能指标', fieldType: 'slot', slotName: 'xingNengZhiBiaSearch' },
+      {
+        prop: 'Q^shi_yan_xiang_mu_^SL',
+        label: '实验项目',
+        fieldType: 'slot',
+        slotName: 'shiYanXiangMuSearch'
+      },
+      {
+        prop: 'Q^xing_neng_zhi_bia^SL',
+        label: '性能指标',
+        fieldType: 'slot',
+        slotName: 'xingNengZhiBiaSearch'
+      },
       { prop: 'Q^fang_an_lei_xing_^SL', label: '方案类型' },
-      { prop: 'Q^shi_yan_fang_fa_^SL', label: '实验方法', fieldType: 'slot', slotName: 'shiYanFangFaSearch' },
+      {
+        prop: 'Q^shi_yan_fang_fa_^SL',
+        label: '实验方法',
+        fieldType: 'slot',
+        slotName: 'shiYanFangFaSearch'
+      },
       { prop: 'Q^yang_ben_lei_xing^SL', label: '样本类型' },
       {
         prop: ['Q^create_time_^DL', 'Q^create_time_^DG'],
@@ -146,7 +160,7 @@ export default {
         fieldType: 'daterange'
       }
     ]
-    
+
     // 只有系统管理员或多身份用户才显示状态字段
     if (showStatusField) {
       baseForms.splice(5, 0, {
@@ -159,7 +173,7 @@ export default {
         ]
       })
     }
-    
+
     return {
       userOption,
       title: '快速评价审核/审批',
@@ -170,13 +184,13 @@ export default {
       pagination: {},
       liuchengData: [],
       sorts: {
-        create_time_: "asc"
+        create_time_: 'asc'
       },
       searchFormData: {
-          xingNengZhiBiao: '',
-          shiYanFangFa: '',
-          shiYanXiangMu: ''
-        }, // 新增:为搜索表单创建独立的数据模型
+        xingNengZhiBiao: '',
+        shiYanFangFa: '',
+        shiYanXiangMu: ''
+      }, // 新增:为搜索表单创建独立的数据模型
       showConfig: false,
       readonly: false,
       params: {},
@@ -191,7 +205,7 @@ export default {
         },
         // 表格字段配置
         columns: [
-          { prop: 'id', label: '主键', width: 0 ,hidden: true},
+          { prop: 'id', label: '主键', width: 0, hidden: true },
           { prop: 'NAME_', label: '当前节点', width: 90 },
           { prop: 'xingNengZhiBia', label: '性能指标', tags: [], width: 120 },
           {
@@ -200,9 +214,19 @@ export default {
             tags: [],
             minWidth: 140
           },
-          
-          { prop: 'shiYanXiangMu', label: '实验项目', slotName: 'shiYanXiangMu', minWidth: 140 },
-            { prop: 'shiYanFangFa', label: '实验方法', slotName: 'shiYanFangFa', minWidth: 140 },
+
+          {
+            prop: 'shiYanXiangMu',
+            label: '实验项目',
+            slotName: 'shiYanXiangMu',
+            minWidth: 140
+          },
+          {
+            prop: 'shiYanFangFa',
+            label: '实验方法',
+            slotName: 'shiYanFangFa',
+            minWidth: 140
+          },
           { prop: 'yangBenLeiXing', label: '样本类型', width: 100 },
           { prop: 'shiYanYiQi', label: '实验仪器', width: 120 },
           {
@@ -226,16 +250,14 @@ export default {
             sortable: 'custom',
             width: 130
           },
-           {
-              prop: 'operation',
-              label: '操作',
-              slotName: 'operation',
-              width: 90,
-              fixed: 'right'
-            }
+          {
+            prop: 'operation',
+            label: '操作',
+            slotName: 'operation',
+            width: 90,
+            fixed: 'right'
+          }
         ]
- 
-
       }
     }
   },
@@ -250,20 +272,20 @@ export default {
       queryExperimental(searchparam)
         .then((res) => {
           //赋值数据当前流程节点
-          const liuchengMap = new Map();
-          this.liuchengData.forEach(item => {
-              liuchengMap.set(item.BIZ_KEY_, item.NAME_);
-          });
+          const liuchengMap = new Map()
+          this.liuchengData.forEach((item) => {
+            liuchengMap.set(item.BIZ_KEY_, item.NAME_)
+          })
 
           // 如果需要原地修改res.data
-          res.data.dataResult.forEach(item => {
-              if (liuchengMap.has(item.id)) {
-                  item.NAME_ = liuchengMap.get(item.id);
-              } else {
-                  item.NAME_ = null;  
-              }
-          });
-          console.log('列表数据',res.data)
+          res.data.dataResult.forEach((item) => {
+            if (liuchengMap.has(item.id)) {
+              item.NAME_ = liuchengMap.get(item.id)
+            } else {
+              item.NAME_ = null
+            }
+          })
+          console.log('列表数据', res.data)
           ActionUtils.handleListData(this, res.data)
           this.loading = false
         })
@@ -274,62 +296,67 @@ export default {
     /**
      * 获取格式化参数
      */
-     async getSearchFormData() {
-        const searchParam = this.$refs['crud'] ? this.$refs['crud'].getSearcFormData() : {}
-        searchParam['Q^xing_neng_zhi_bia^SL'] = this.searchFormData.xingNengZhiBiao
-        searchParam['Q^shi_yan_xiang_mu_^S'] = this.searchFormData.shiYanXiangMu
-        searchParam['Q^shi_yan_fang_fa_^S'] = this.searchFormData.shiYanFangFa
-        
-        const userRole = this.$store.getters.userInfo.role || []
-        const user = this.$store.getters.userInfo.user || []
-        const jsfzrFlag = userRole.some(role => role.alias === 'jsfzr') 
-        const zcFlag = userRole.some(role => role.alias === 'zhsfzr')
-        const isSystemAdmin = userRole.some(role => role.alias === 'xtgljs')
-        
-        // 系统管理员 返回已编制/已审核数据
-        if (isSystemAdmin) {
-          return this.buildDefaultSearchParams(searchParam)
-        }
-        console.log('当前用户', user.id)
-        try {
-          // 使用 await 等待异步结果
-          const res = await this.$common.request('query', {
-            key: 'xnyzhqdb',
-            params: [user.id]
-          })
-          this.liuchengData = res.variables.data
-          const bizKeys = res.variables.data.map(item => item.BIZ_KEY_).join(',')
-          if(bizKeys){
-              searchParam['Q^id_^SIN'] = bizKeys
-          }else{
-              searchParam['Q^id_^SIN'] = '-1'
-          }
-          return ActionUtils.formatParams(
-            searchParam,
-            this.pagination,
-            this.sorts
-          )
-        } catch (error) {
-          console.error('获取待办列表失败:', error)
-          // 出错时返回默认参数
-          return ActionUtils.formatParams(
-            searchParam,
-            this.pagination,
-            this.sorts
-          )
+    async getSearchFormData() {
+      const searchParam = this.$refs['crud']
+        ? this.$refs['crud'].getSearcFormData()
+        : {}
+      searchParam['Q^xing_neng_zhi_bia^SL'] =
+        this.searchFormData.xingNengZhiBiao
+      searchParam['Q^shi_yan_xiang_mu_^S'] = this.searchFormData.shiYanXiangMu
+      searchParam['Q^shi_yan_fang_fa_^S'] = this.searchFormData.shiYanFangFa
+
+      const userRole = this.$store.getters.userInfo.role || []
+      const user = this.$store.getters.userInfo.user || []
+      const jsfzrFlag = userRole.some((role) => role.alias === 'jsfzr')
+      const zcFlag = userRole.some((role) => role.alias === 'zhsfzr')
+      const isSystemAdmin = userRole.some((role) => role.alias === 'xtgljs')
+
+      // 系统管理员 返回已编制/已审核数据
+      if (isSystemAdmin) {
+        return this.buildDefaultSearchParams(searchParam)
+      }
+      console.log('当前用户', user.id)
+      try {
+        // 使用 await 等待异步结果
+        const res = await this.$common.request('query', {
+          key: 'xnyzhqdb',
+          params: [user.id]
+        })
+        this.liuchengData = res.variables.data
+        const bizKeys = res.variables.data
+          .map((item) => item.BIZ_KEY_)
+          .join(',')
+        if (bizKeys) {
+          searchParam['Q^id_^SIN'] = bizKeys
+        } else {
+          searchParam['Q^id_^SIN'] = '-1'
         }
-      },
+        return ActionUtils.formatParams(
+          searchParam,
+          this.pagination,
+          this.sorts
+        )
+      } catch (error) {
+        console.error('获取待办列表失败:', error)
+        // 出错时返回默认参数
+        return ActionUtils.formatParams(
+          searchParam,
+          this.pagination,
+          this.sorts
+        )
+      }
+    },
 
     // 处理额外的搜索参数
     handleAdditionalSearchParams(searchParam, parameters) {
       if (Object.keys(searchParam).length) {
-        const statusArr = Object.keys(searchParam).filter(key => 
+        const statusArr = Object.keys(searchParam).filter((key) =>
           key.includes('shi_fou_guo_shen_')
         )
         const notstatusArr = Object.keys(searchParam).filter(
-          key => !key.includes('shi_fou_guo_shen_')
+          (key) => !key.includes('shi_fou_guo_shen_')
         )
-        
+
         // 更新状态查询条件
         if (statusArr.length > 0) {
           parameters[0].parameters[0] = {
@@ -338,9 +365,9 @@ export default {
             param: 'status'
           }
         }
-        
+
         // 添加其他查询条件
-        notstatusArr.forEach(el => {
+        notstatusArr.forEach((el) => {
           parameters[0].parameters.push({
             key: el,
             value: searchParam[el]
@@ -350,7 +377,6 @@ export default {
     },
     // 构建默认搜索参数(管理员和其他用户)
     buildDefaultSearchParams(searchParam) {
-
       const parameters = [
         {
           parameters: [
@@ -373,13 +399,12 @@ export default {
           ]
         }
       ]
-      
-      
+
       // 处理搜索栏的查询条件
       if (Object.keys(searchParam).length) {
         this.handleAdditionalSearchParams(searchParam, parameters)
       }
-      
+
       return {
         parameters: parameters,
         ...ActionUtils.formatParams(null, this.pagination, this.sorts)
@@ -479,12 +504,12 @@ export default {
 </style>
 <style lang="scss" scoped>
 .main-container ::v-deep .el-table__body-wrapper {
-.el-table__row {
-  td:last-child {
-    .cell {
-      text-align: center;
+  .el-table__row {
+    td:last-child {
+      .cell {
+        text-align: center;
+      }
     }
   }
 }
-}
-</style>
+</style>

+ 3 - 1
src/views/platform/office/bpmReceivedProcess/handled.vue

@@ -160,9 +160,11 @@ export default {
             item.curNode = item.curNode ? `待${item.curNode}` : '已结束'
           })
           ActionUtils.handleListData(this, data)
+          console.log('data===>', data)
           this.loading = false
         })
-        .catch(() => {
+        .catch((err) => {
+          console.log(err)
           this.loading = false
         })
     },

+ 6 - 1
src/views/platform/office/mixin/utils.js

@@ -103,7 +103,12 @@ export default {
         return ''
       }
       arr[2] = arr[2].replace(':', ':')
-      const result = JSON.parse(`{${arr[2]}}`)
+      let result = {}
+      try {
+        result = JSON.parse(`{${arr[2]}}`)
+      } catch (error) {
+        console.log(val)
+      }
       if (!result.dept) {
         return ''
       }

Some files were not shown because too many files changed in this diff