src/views/business/reportPreparation/index.vue
@@ -130,8 +130,42 @@
           @click="fullscreen = true;"></i>
        <img v-else alt="" src="@/assets/images/no-full.svg" style="cursor: pointer;" @click="fullscreen = false;">
      </div>
      <div v-if="approveVisible" style="height: 70vh;">
      <div v-if="approveVisible" class="approve-preview">
        <onlyoffice ref="onlyoffice" :options="option" style="width: 100%;height: 100%;"/>
      </div>
      <div class="seal-select-block" v-loading="sealLoading">
        <div class="seal-select-title">
          <span>按位置选择印章</span>
          <span class="seal-select-tip">自动匹配 MA章 / 1号章 / 骑缝章,每项可单独调整或清空</span>
        </div>
        <div v-if="sealOptions.length" class="seal-position-grid">
          <div v-for="position in sealPositions" :key="position.key" class="seal-position-card">
            <div class="seal-position-name">{{ position.label }}</div>
            <div class="seal-position-desc">{{ position.description }}</div>
            <el-select
              v-model="selectedSealPositions[position.key]"
              clearable
              placeholder="不盖章"
              style="width: 100%"
            >
              <el-option
                v-for="seal in sealOptions"
                :key="seal.id"
                :label="seal.name"
                :value="Number(seal.id)"
              />
            </el-select>
            <div v-if="getSelectedSeal(position.key)" class="seal-position-preview">
              <img
                :src="javaApi + '/img/' + getSelectedSeal(position.key).address"
                :alt="getSelectedSeal(position.key).name"
              >
              <span>{{ getSelectedSeal(position.key).name }}</span>
            </div>
            <div v-else class="seal-position-empty">该位置不盖章</div>
          </div>
        </div>
        <el-empty v-if="!sealLoading && sealOptions.length === 0" description="暂无启用印章,请先在印章管理中维护" :image-size="54" />
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button :disabled="loadingApprove" @click="approveReasonVisible = true">不批准</el-button>
@@ -249,6 +283,7 @@
import {selectUserCondition} from "@/api/business/inspectionTask";
import limsTable from "@/components/Table/lims-table.vue";
import {delFile, downFile, getFileList} from "@/api/business/rawMaterialOrder";
import {selectEnabledSeals} from "@/api/structural/sealConfig";
export default {
  name: 'ReportPreparation',
@@ -276,6 +311,18 @@
      issuedReasonVisible: false,
      approveVisible: false,
      approveReasonVisible: false,
      sealOptions: [],
      sealPositions: [
        { key: 'maSealId', label: 'MA标志章', description: 'PDF 第1页左上角' },
        { key: 'reportSealId', label: '报告章', description: 'Word 模板各页 seal1 / seal2 预留位置' },
        { key: 'seamSealId', label: '骑缝章', description: 'PDF 每页右侧中间位置' }
      ],
      selectedSealPositions: {
        maSealId: null,
        reportSealId: null,
        seamSealId: null
      },
      sealLoading: false,
      fullscreen: false,
      loadingApproveReason: false,
      loadingApprove: false,
@@ -307,7 +354,10 @@
        {label: '原材料下单', value: 1},
      ],
      orderTypeList: [
        {label: '委托试验', value: 'Customer-ordered test'},
        {label: '委托检测', value: 'Commissioned testing'},
        {label: '实验室间比对', value: 'Inter-laboratory comparison'},
        {label: '能力验证', value: 'Proficiency testing'},
        {label: '委托检验', value: 'Customer-ordered test'},
        {label: '抽检', value: '抽检'},
        // { label: '进厂检验', value: '进厂检验' },
        // { label: '季度检验', value: 'Quarterly inspection' },
@@ -986,6 +1036,7 @@
    // 批准按钮
    handleApprove(row) {
      this.currentInfo = row;
      this.loadEnabledSeals(row);
      let fileName = (row.urlS === null || row.urlS === '') ? row.url : row.urlS
      let fileType = "docx"
      if (row.tempUrlPdf != null || row.tempUrlPdf === '') {
@@ -1009,10 +1060,16 @@
    },
    // 批准通过
    subApprove() {
      const selectedIds = Object.values(this.selectedSealPositions).filter(id => id != null)
      if (!selectedIds.length) {
        return this.$message.error('请至少为一个位置选择印章')
      }
      this.loadingApprove = true;
      ratifyReport({
        id: this.currentInfo.id,
        isRatify: 1
        isRatify: 1,
        ...this.selectedSealPositions,
        sealIds: [...new Set(selectedIds)].join(',')
      }).then(res => {
        this.loadingApprove = false;
        this.$message.success('已批准')
@@ -1022,6 +1079,39 @@
      }).catch(() => {
        this.loadingApprove = false;
      })
    },
    loadEnabledSeals(row) {
      this.sealLoading = true
      this.sealOptions = []
      this.selectedSealPositions = { maSealId: null, reportSealId: null, seamSealId: null }
      selectEnabledSeals().then(res => {
        this.sealOptions = res.data || []
        const availableIds = this.sealOptions.map(item => Number(item.id))
        const legacyIds = String((row && row.sealIds) || '')
          .split(',')
          .map(item => Number(item.trim()))
          .filter(id => availableIds.includes(id))
        const legacyId = legacyIds[0] || null
        const maSeal = this.sealOptions.find(item => String(item.name).toUpperCase().includes('MA'))
        const reportSeal = this.sealOptions.find(item => item.name === '1号章')
          || this.sealOptions.find(item => !String(item.name).toUpperCase().includes('MA') && !String(item.name).includes('骑缝'))
          || this.sealOptions[0]
        const seamSeal = this.sealOptions.find(item => String(item.name).includes('骑缝'))
        this.selectedSealPositions = {
          maSealId: availableIds.includes(Number(row && row.maSealId))
            ? Number(row.maSealId) : (maSeal ? Number(maSeal.id) : null),
          reportSealId: availableIds.includes(Number(row && row.reportSealId))
            ? Number(row.reportSealId) : (legacyId || (reportSeal ? Number(reportSeal.id) : null)),
          seamSealId: availableIds.includes(Number(row && row.seamSealId))
            ? Number(row.seamSealId) : (seamSeal ? Number(seamSeal.id) : legacyId)
        }
      }).finally(() => {
        this.sealLoading = false
      })
    },
    getSelectedSeal(positionKey) {
      const sealId = this.selectedSealPositions[positionKey]
      return this.sealOptions.find(item => Number(item.id) === Number(sealId))
    },
    handle(row) {
      this.getAuthorizedPerson()
@@ -1080,7 +1170,7 @@
      ratifyReport({
        id: this.currentInfo.id,
        isRatify: 0,
        examineTell: this.reason
        ratifyTell: this.reason
      }).then(res => {
        this.loadingApproveReason = false
        this.$message.success('操作成功')
@@ -1091,7 +1181,7 @@
        this.approveReasonVisible = false;
      }).catch(e => {
        this.$message.error('操作失败')
        this.loadingIssuedReason = false;
        this.loadingApproveReason = false;
      })
    }
  }
@@ -1156,4 +1246,77 @@
.fullscreen {
  height: 82vh
}
.approve-preview {
  height: 52vh;
}
.seal-select-block {
  margin-top: 16px;
  padding: 14px 16px;
  border: 1px solid #ebeef5;
  border-radius: 6px;
  background: #fafafa;
}
.seal-select-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
  color: #303133;
  font-weight: 600;
}
.seal-select-tip {
  color: #909399;
  font-size: 12px;
  font-weight: normal;
}
.seal-position-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
.seal-position-card {
  padding: 14px;
  border: 1px solid #dcdfe6;
  border-radius: 6px;
}
.seal-position-name {
  color: #303133;
  font-weight: 600;
}
.seal-position-desc {
  margin: 4px 0 10px;
  color: #909399;
  font-size: 12px;
}
.seal-position-preview {
  display: flex;
  align-items: center;
  margin-top: 12px;
  color: #606266;
}
.seal-position-preview img {
  width: 70px;
  height: 70px;
  margin-right: 10px;
  object-fit: contain;
}
.seal-position-empty {
  height: 70px;
  margin-top: 12px;
  color: #c0c4cc;
  font-size: 12px;
  line-height: 70px;
  text-align: center;
}
</style>