需求:支持图片预览,并放大缩小查看图片

0 阅读21分钟

效果:

image.png

功能点:

  • 放大/缩小按钮:每次缩放后自动滚动到顶部,确保用户总能看到图片的最顶部内容
  • 重置功能:重置缩放后同时重置横向和纵向滚动位置
  • 鼠标滚轮缩放:以鼠标位置为中心的智能缩放,缩放时保持鼠标指向的内容位置不变

代码:

<template>
  <div
    class="warp-card"
    v-loading="isLoading"
    element-loading-text="Loading..."
    element-loading-svg-view-box="-10, -10, 50, 50"
    element-loading-background="rgba(122, 122, 122, 0.8)"
  >
    <div class="breadcrumb">
      <el-breadcrumb separator="/">
        <el-breadcrumb-item>
          <a type="primary" href="/homepage">Home</a>
        </el-breadcrumb-item>
        <el-breadcrumb-item
          >Performance Management / Approval Form Details-{{
            userId
          }}</el-breadcrumb-item
        >
      </el-breadcrumb>
    </div>
    <div style="padding: 0px 10px 0px 10px">
      <div
        class="search-container"
        style="margin: 10px 0; min-height: calc(-110px + 100vh)"
      >
        <div class="box-card">
          <div class="performance-setting-container">
            <div class="form-title">
              Approval Form Details-{{ userId }}
              <div
                class="form-status"
                :class="statusClass"
                v-if="applicationStatusValue"
              >
                {{ applicationStatusValue }}
              </div>
            </div>
            <el-divider class="custom-divider" />

            <!-- 流程图 -->
            <div class="process-section">
              <div style="font-size: 16px; color: #333; font-weight: bold">
                Process
              </div>

              <div class="process-steps">
                <template v-for="(step, index) in stepsData" :key="index">
                  <div
                    :class="[
                      'step',
                      {
                        active: step.active && step.statusId !== 4,
                        rejected: step.active && step.statusId === 4,
                        disabled: hasRejectedBefore(index),
                      },
                    ]"
                  >
                    <div class="step-icon">
                      <template v-if="step.active">
                        <el-icon v-if="step.statusId === 4"><Close /></el-icon>
                        <el-icon v-else><Check /></el-icon>
                      </template>
                      <span v-else style="color: #a8abb2">{{ index + 1 }}</span>
                    </div>
                    <div class="step-info">
                      <div class="step-name">{{ step.name }}</div>
                      <div class="step-user">{{ step.user }}</div>
                      <div class="step-time">{{ step.time }}</div>
                    </div>
                  </div>
                  <!-- 渲染连接线,最后一个不显示 -->
                  <div
                    v-if="index < stepsData.length - 1"
                    :class="[
                      'step-line',
                      stepsData[index].active && stepsData[index].statusId !== 4
                        ? 'completed'
                        : '',
                      hasRejectedBefore(index + 1) ? 'disabled' : '',
                    ]"
                  ></div>
                </template>
              </div>
            </div>
            <el-divider  class="custom-divider" />

            <template v-if="rejectComment">
              <div class="info-section">
                <div style="display: flex; align-items: center">
                  <div style="font-size: 16px; color: #333; font-weight: bold">
                    Comments
                  </div>
                </div>

                <div class="comments-content">
                  <div
                    v-html="
                      processHtmlContent(
                        formatMessageWithLineBreaks(rejectComment)
                      )
                    "
                  ></div>
                </div>
              </div>

              <el-divider class="custom-divider" />
            </template>

            <!-- 个人信息 -->
            <div class="info-section">
              <h3>Personal Information</h3>
              <el-row :gutter="20">
                <el-col :span="24">
                  <div class="info-item">
                    <span class="label">Chinese Name:</span>
                    <span class="value">{{
                      personalInformationObj.chineseName
                    }}</span>
                  </div>
                </el-col>
                <!-- 在JM类型下不显示Role -->
                <el-col :span="24" v-if="approvalType !== 'JM'">
                  <div class="info-item">
                    <span class="label">Role:</span>
                    <span class="value">{{ personalInformationObj.role }}</span>
                  </div>
                </el-col>
                <el-col :span="24">
                  <div class="info-item">
                    <span class="label">Time:</span>
                    <span class="value"> {{ yearQuarterDisplay }}</span>
                  </div>
                </el-col>
                <!-- AE类型特有的字段 -->
                <template
                  v-if="
                    approvalType === 'AE' ||
                    approvalType === 'DEJob' ||
                    approvalType === 'AEJob'
                  "
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">QC Pass Rate:</span>
                      <span class="value">
                        <span v-if="personalInformationObj.qcPassRate">
                          {{ personalInformationObj.qcPassRate }}%</span
                        >
                        <span v-else>0%</span>
                      </span>
                    </div>
                  </el-col>
                </template>

                <template
                  v-if="
                    approvalType === 'DE' ||
                    approvalType === 'AE' ||
                    approvalType === 'DEJob' ||
                    approvalType === 'JM'
                  "
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Paid Confirmed:</span>
                      <span class="value">
                        <a
                          v-if="personalInformationObj.paidConfirmed"
                          href="javascript:void(0)"
                          @click="handlePaidConfirmClick"
                          class="clickable-value"
                          >{{ personalInformationObj.paidConfirmed }}</a
                        >
                        <span v-else>0</span>
                      </span>
                    </div>
                  </el-col>
                </template>


                <template
                  v-if="
                    approvalType === 'DE' ||
                    approvalType === 'AE'
                  "
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Final Confirmed:</span>
                      <span class="value">
                        {{  personalInformationObj.finalConfirmed  }}
                      </span>
                    </div>
                  </el-col>
                </template>



                <template
                  v-if="
                    approvalType === 'Other' ||
                    approvalType === 'DE' ||
                    approvalType === 'DM' ||
                    approvalType === 'PE' ||
                    approvalType === 'SI' ||
                    approvalType === 'JM'
                  "
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Journal:</span>
                      <span class="value">{{
                        personalInformationObj.journalName
                      }}</span>
                    </div>
                  </el-col>
                </template>

                <template
                  v-if="approvalType === 'DEJob' || approvalType === 'AEJob'"
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span
                        class="label"
                        v-if="
                          approvalType === 'DEJob' || approvalType === 'AEJob'
                        "
                        >Total PI:</span
                      >
                      <span class="label" v-else>PI:</span>
                      <span class="value">{{ personalInformationObj.pi }}</span>
                    </div>
                  </el-col>
                </template>
              </el-row>
            </div>

            <el-divider class="custom-divider" />

            <!-- 审批信息 -->
            <div class="info-section">
              <h3>Approval Information</h3>
              <el-row :gutter="20">
                <template v-if="approvalType === 'JM'">
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Journal Name:</span>
                      <span class="value">
                        {{ approvalInformationObj.journalName }}
                      </span>
                    </div>
                  </el-col>
                </template>

                <el-col :span="24">
                  <div class="info-item">
                    <span class="label">Commission Type:</span>
                    <span class="value">
                      {{ approvalInformationObj.commissionType }}
                    </span>
                  </div>
                </el-col>

                <template
                  v-if="
                    approvalType === 'PE' ||
                    approvalType === 'Other' ||
                    approvalType === 'SI' ||
                    approvalType === 'DEJob' ||
                    approvalType === 'AEJob'
                  "
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Category:</span>
                      <span class="value">
                        <el-select
                          v-model="selectedCategory"
                          placeholder="Select category"
                          style="width: 200px"
                          :disabled="true"
                          :title="selectedCategoryLabel"
                        >
                          <el-option
                            v-for="item in categoryList"
                            :key="item.id"
                            :label="item.value"
                            :value="item.id"
                          />
                        </el-select>
                      </span>
                    </div>
                  </el-col>
                </template>
                <!-- PB类型特有的字段 -->
                <template
                  v-if="approvalType === 'PE' || approvalType === 'Other'"
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Paper ID:</span>
                      <span
                        class="value"
                        style="display: flex; align-items: flex-start"
                        v-if="personalInformationObj.paperId"
                      >
                        <el-link
                          type="primary"
                          @click="
                            showPaperDetails(
                              personalInformationObj.paperId,
                              'paper'
                            )
                          "
                        >
                          {{ personalInformationObj.paperId }}</el-link
                        >
                        <el-tooltip
                          v-if="hasCommission"
                          :content="tooltipContent"
                          placement="top"
                          :disabled="!tooltipContent"
                          popper-class="ebm-tooltip"
                          raw-content
                        >
                          <span
                            @mouseenter="
                              fetchInfo(personalInformationObj.paperId, 'paper')
                            "
                            style="
                              position: relative;
                              margin-left: 3px;
                              cursor: pointer;
                            "
                          >
                            <svg-icon icon-class="c" />
                          </span>
                        </el-tooltip>
                      </span>
                      <span class="value" v-else>-</span>
                    </div>
                  </el-col>
                </template>

                <template
                  v-if="approvalType === 'SI' || approvalType === 'Other'"
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">SI ID:</span>
                      <span
                        class="value"
                        style="display: flex; align-items: flex-start"
                        v-if="approvalInformationObj.sectionId"
                      >
                        <el-link
                          type="primary"
                          @click="
                            showPaperDetails(
                              approvalInformationObj.sectionId,
                              'si'
                            )
                          "
                        >
                          {{ approvalInformationObj.sectionId }}</el-link
                        >
                      </span>
                      <span class="value" v-else>-</span>

                      <el-tooltip
                        v-if="sectionHasCommission"
                        :content="tooltipContent"
                        placement="top"
                        :disabled="!tooltipContent"
                        popper-class="ebm-tooltip"
                        raw-content
                      >
                        <span
                          @mouseenter="
                            fetchInfo(approvalInformationObj.sectionId, 'si')
                          "
                          style="
                            position: relative;
                            margin-left: 3px;
                            cursor: pointer;
                          "
                        >
                          <svg-icon icon-class="c" />
                        </span>
                      </el-tooltip>
                    </div>
                  </el-col>
                </template>

                <!-- SI类型特有的字段 -->
                <template v-if="approvalType === 'SI'">
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">SI Title:</span>
                      <span class="value">{{
                        approvalInformationObj.sectionTitle
                      }}</span>
                    </div>
                  </el-col>
                  <!-- <el-col :span="24">
                    <div class="info-item">
                      <span class="label">SI Published:</span>
                      <span class="value">{{
                        approvalInformationObj.siPublished
                      }}</span>
                    </div>
                  </el-col> -->
                </template>

                <!-- AE类型特有的字段 -->
                <template
                  v-if="
                    approvalType === 'AE' ||
                    approvalType === 'DE' ||
                    approvalType === 'DM' ||
                    approvalType === 'JM'
                  "
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Calculation Formula:</span>
                      <span class="value">
                        <span
                          v-html="
                            removeFormulaPrefix(
                              approvalInformationObj.calculationFormula
                            )
                          "
                        ></span
                      ></span>
                    </div>
                  </el-col>
                </template>

                <template
                  v-if="
                    approvalType === 'PE' && [5, 6].includes(selectedCategory)
                  "
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Calculation formula:</span>
                      <span class="value">
                        <span
                          v-html="
                            removeFormulaPrefix(
                              approvalInformationObj.calculationFormula
                            )
                          "
                        ></span
                      ></span>
                    </div>
                  </el-col>
                </template>

                <!-- other类型特有的字段 (基于PB) -->
                <template v-if="approvalType === 'Other'">
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Journal:</span>
                      <span class="value">
                        <el-select
                          v-model="selectedJournal"
                          placeholder="Select journal"
                          style="width: 200px"
                          :disabled="!showSave"
                        >
                          <el-option
                            v-for="item in journalOptions"
                            :key="item.journalId"
                            :label="item.abbreviation"
                            :value="item.journalId"
                          />
                        </el-select>
                      </span>
                    </div>
                  </el-col>

                  <template v-if="approvalInformationObj.paymentAmount">
                    <el-col :span="24">
                      <div class="info-item">
                        <span class="label">SI Type:</span>
                        <span class="value">
                          {{ approvalInformationObj.sectionType }}
                        </span>
                      </div>
                    </el-col>
                    <el-col :span="24">
                      <div class="info-item">
                        <span class="label">Article Type:</span>
                        <span class="value">
                          {{ approvalInformationObj.articleType }}
                        </span>
                      </div>
                    </el-col>
                    <el-col :span="24">
                      <div class="info-item">
                        <span class="label">Payment Amount($):</span>
                        <span class="value">
                          {{ approvalInformationObj.paymentAmount }}
                        </span>
                      </div>
                    </el-col>
                  </template>
                </template>

                <template
                  v-if="
                    approvalType === 'PE' &&
                    [5, 6].includes(selectedCategory) &&
                    approvalInformationObj.paymentAmount
                  "
                >
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Payment Amount($):</span>
                      <span class="value">
                        {{ approvalInformationObj.paymentAmount }}
                      </span>
                    </div>
                  </el-col>
                </template>

                <!-- JM类型特有的字段 -->
                <template v-if="approvalType === 'JM'">
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Role:</span>
                      <span class="value">{{
                        personalInformationObj.role
                      }}</span>
                    </div>
                  </el-col>

                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Journal QC Pass Rate:</span>

                      <span class="value">
                        <span v-if="personalInformationObj.qcPassRate">
                          {{ personalInformationObj.qcPassRate }}%</span
                        >
                        <span v-else>0%</span>
                      </span>
                    </div>
                  </el-col>
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">Proportion:</span>
                      <span class="value"
                        >{{ approvalInformationObj.proportion }}%</span
                      >
                    </div>
                  </el-col>
                </template>

                <!-- DM类型特有的字段 -->
                <template v-if="approvalType === 'DM'">
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label">DE total commission:</span>
                      <span class="value">{{
                        approvalInformationObj.totalCommission
                      }}</span>
                    </div>
                  </el-col>
                </template>

                <!-- 证明材料 - 仅PB和other类型显示 -->
                <el-col
                  :span="24"
                  v-if="
                    approvalType === 'PE' ||
                    approvalType === 'Other' ||
                    approvalType === 'SI'
                  "
                >
                  <div class="info-item" style="align-items: start">
                    <span class="label">Proof:</span>
                    <div class="two-col-container">
                      <!-- 动态渲染证明材料,保持原有的四个固定区域布局 -->
                      <div
                        class="item"
                        v-for="(proofItem, index) in getDisplayProofItems()"
                        :key="index"
                      >
                        <div class="proof-item">
                          <span
                            class="label"
                            :title="proofItem.title"
                            style="text-align: left"
                            >{{ proofItem.title }}:</span
                          >
                          <div class="upload-container">
                            <!-- 如果状态为pending或rejected且可编辑 -->
                            <template v-if="isProofEditable">
                              <!-- 图片上传类型 (type: 0) -->
                              <template v-if="proofItem.originalType === 0">
                                <div
                                  :class="`upload-area ${
                                    currentProofFocusArea === index
                                      ? 'active-upload'
                                      : ''
                                  }`"
                                  @click="setActiveProofUploadArea(index)"
                                  @mouseenter="showProofUploadHint(index, $event)"
                                  @mouseleave="hideProofUploadHint()"
                                >
                                  <el-upload
                                    :class="`image-uploader proof-uploader-${index}`"
                                    :action="uploadAction"
                                    :show-file-list="false"
                                    :on-success="(response: any, file: any) => onProofUploadSuccess(response, file, index)"
                                    :before-upload="beforeProofUpload"
                                    :http-request="(options: any) => customProofUploadHandler(options, index)"
                                    :auto-upload="true"
                                    list-type="picture-card"
                                  >
                                    <img
                                      v-if="proofEditData[index]?.displayUrl"
                                      :src="proofEditData[index].displayUrl"
                                      class="upload-image upload-image-2"
                                    />
                                    <div v-else class="upload-placeholder">
                                      <i class="el-icon-plus"></i>
                                      <div>Upload pictures</div>
                                    </div>
                                  </el-upload>
                                </div>

                                <!-- 显示已上传的多个图片 -->
                                <div
                                  v-if="proofEditData[index]?.images?.length > 0"
                                  class="uploaded-images"
                                >
                                  <div
                                    v-for="(image, imageIndex) in proofEditData[index]
                                      .images"
                                    :key="imageIndex"
                                    class="image-item"
                                    :class="{
                                      active:
                                        image.url === proofEditData[index].displayUrl,
                                    }"
                                  >
                                    <img
                                      :src="image.url"
                                      class="thumbnail"
                                      @click="
                                        changeProofDisplayImage(index, image.url)
                                      "
                                    />
                                    <div class="image-actions">
                                      <el-button
                                        type="primary"
                                        :icon="Search"
                                        circle
                                        @click.stop="previewImage(image.url, proofItem)"
                                        title="preview"
                                      />
                                      <el-button
                                        type="danger"
                                        :icon="Delete"
                                        circle
                                        @click.stop="
                                          removeProofImage(index, imageIndex)
                                        "
                                        title="delete"
                                      />
                                    </div>
                                  </div>
                                </div>
                              </template>

                              <!-- 链接输入类型 (type: 1) -->
                              <template v-else-if="proofItem.originalType === 1">
                                <el-input
                                  v-model="proofEditData[index].url"
                                  type="textarea"
                                  :rows="3"
                                  :placeholder="`Please input ${proofItem.title} URL or text content`"
                                  style="margin-top: 10px"
                                />
                              </template>
                            </template>

                            <!-- 只读状态:保持原有展示逻辑 -->
                            <template v-else>
                              <!-- 如果是链接类型 -->
                              <div v-if="proofItem.type === 'link'">
                                <a
                                  :href="proofItem.value"
                                  target="_blank"
                                  :title="proofItem.value"
                                  class="custom-link"
                                  >{{ proofItem.value }}</a
                                >
                              </div>

                              <!-- 如果是图片类型 -->
                              <div v-else class="image-display-area">
                                <div
                                  v-if="
                                    proofItem.images &&
                                    proofItem.images.length > 0
                                  "
                                  class="images-container"
                                  title="Preview Picture"
                                >
                                  <img
                                    v-for="(image, imgIndex) in proofItem.images"
                                    :key="imgIndex"
                                    :src="getImageUrl(image.fileId)"
                                    class="upload-image"
                                    @click="
                                      previewImage(
                                        getImageUrl(image.fileId),
                                        proofItem
                                      )
                                    "
                                    @error="handleImageError"
                                  />
                                </div>
                                <div v-else class="upload-placeholder">
                                  <div>No images uploaded</div>
                                </div>
                              </div>
                            </template>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </el-col>

                <el-col :span="24">
                  <div class="info-item" style="align-items: start">
                    <span class="label">Remark:</span>
                    <span class="value" style="width: 60%">
                      <el-input
                        v-model="approvalInformationObj.remark"
                        :rows="5"
                        type="textarea"
                        placeholder="Please input"
                        :disabled="!showSave"
                      />
                    </span>
                  </div>
                </el-col>

                <!-- 只有approver的角色才显示 -->
                <template v-if="showExchangeRate">
                  <el-col :span="24">
                    <div class="info-item">
                      <span class="label"><span style="color: red">*</span> USD Exchange Rate:</span>
                      <el-input
                        v-model="exchangeRateInput"
                        placeholder="Please input"
                        style="margin-left: 10px; width: 200px"
                        @input="handleExchangeRateInput"
                        @blur="calculateAmount"
                        :disabled="!showApprove"
                        clearable
                      ></el-input>
                    </div>
                  </el-col>
                </template>

                <!-- 金额 -->
                <template
                  v-if="
                    approvalType === 'PE' && [5, 6].includes(selectedCategory)
                  "
                >
                  <el-col :span="24" v-if="exchangeRateInput">
                    <div class="amount-item">
                      <span class="label">Amount(¥):</span>
                      <el-input
                        v-model="approvalInformationObj.amount"
                        placeholder="250"
                        style="margin-left: 10px; width: 200px"
                        disabled
                      ></el-input>
                      <!-- <el-button
                      v-if="showApprove"
                      type="primary"
                      link
                      style="margin: 0 10px"
                      @click="toggleEdit"
                    >
                      {{ isEditing ? "Save" : "Modify" }}
                    </el-button> -->
                      <!-- <div
                      v-if="approvalInformationObj.id"
                      class="amount-item remaining"
                      @click="showMatchingRulesDetails"
                    >
                      <span>Matching rules: </span>
                      <span class="quota"
                        >ID {{ approvalInformationObj.id }}</span
                      >
                    </div> -->
                    </div>
                  </el-col>
                </template>
                <template v-else>
                  <el-col :span="24">
                    <div class="amount-item">
                      <span class="label">Amount(¥):</span>
                      <el-input
                        v-model="approvalInformationObj.amount"
                        placeholder="250"
                        style="margin-left: 10px; width: 200px"
                        :disabled="!showApprove"
                      ></el-input>
                    </div>
                  </el-col>
                </template>


                <!-- 匹配规则 -->
                <el-col :span="24">
                  <div class="amount-item">
                    <span class="label">Matching rules:</span>
                    <span class="quota" @click="showMatchingRulesDetails"
                      >ID {{ approvalInformationObj.id }}</span
                    >
                  </div>
                </el-col>
              </el-row>
            </div>

            <!-- <el-divider /> -->

            <!-- 按钮 -->
            <div class="action-buttons">
              <template v-if="showApprove">
                <el-button type="primary" @click="handleApprove"
                  >Approve</el-button
                >
              </template>
              <template v-if="showReject">
                <el-button type="primary" @click="handleReject">Fail</el-button>
              </template>

              <template v-if="showSave">
                <el-button type="primary" @click="handleSave">Save</el-button>
              </template>

              <template v-if="showSendToApprove">
                <el-button type="primary" @click="handleSendToApproval"
                  >Send to Approve</el-button
                >
              </template>
            </div>

            <!-- <el-divider
              v-if="showSave || showSendToApprove || showApprove || showReject"
            /> -->

            <!-- history notes  -->
            <div class="history-section">
              <div class="table-header">
                <h3>History Notes</h3>
                <el-button
                  type="primary"
                  @click="handleAddNotes"
                  class="add-notes-btn"
                  >Add Notes</el-button
                >
              </div>
              <el-table :data="notificationList" style="width: 100%" border>
                <el-table-column
                  prop="createDate"
                  label="Created on"
                  width="180"
                ></el-table-column>
                <el-table-column
                  prop="name"
                  label="Operator"
                  width="180"
                ></el-table-column>
                <el-table-column label="Message">
                  <template #default="scope">
                    <!-- 如果包含图片,单独显示预览按钮 -->
                    <!-- <div
                      v-if="getImagesFromHtml(scope.row.message).length > 0"
                      class="image-preview-buttons"
                    >
                      <el-icon
                        v-for="(imageUrl, index) in getImagesFromHtml(
                          scope.row.message
                        )"
                        :key="index"
                        class="preview-icon"
                        @click="previewCommentImage(imageUrl)"
                        title="Preview Picture"
                      >
                        <Picture />
                      </el-icon>
                    </div> -->

                    <template
                      v-if="getImagesFromHtml(scope.row.message).length > 0"
                    >
                      <div
                        v-for="(imageUrl, index) in getImagesFromHtml(
                          scope.row.message
                        )"
                        :key="index"
                        @click="previewCommentImage(imageUrl)"
                        title="Preview Picture"
                      >
                        <div
                          v-html="
                            formatMessageWithLineBreaks(scope.row.message)
                          "
                          class="history-message history-message-2"
                        ></div>
                      </div>
                    </template>
                    <template v-else>
                      <div
                        v-html="formatMessageWithLineBreaks(scope.row.message)"
                        class="history-message"
                      ></div>
                    </template>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <!-- 添加拒绝弹窗 -->
  <el-dialog
    title="Rejection Comments"
    v-model="rejectDialogVisible"
    width="1000px"
    :before-close="cancelModal"
  >
    <div class="reject-dialog-content">
      <Editor2
        styleVale="width: 100%;min-height:200px;"
        v-model="rejectReason"
      />
    </div>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="cancelModal">Cancel</el-button>
        <el-button type="primary" @click="confirmReject">Confirm</el-button>
      </span>
    </template>
  </el-dialog>

  <!-- 添加笔记弹窗 -->
  <div class="notes-modal">
    <el-dialog
      title="Add History Notes"
      v-model="addNotesDialogVisible"
      width="800"
      :before-close="cancelModal"
      class="addNotesModal"
    >
      <el-form ref="ruleFormRef" label-width="150px" size="default">
        <el-form-item label="Content:">
          <el-input
            :rows="4"
            placeholder="Please input"
            type="textarea"
            v-model="notesContent"
            style="width: 500px"
          />
        </el-form-item>

        <!-- <el-form-item label="Upload:">
          <div style="text-align: left">
            <el-upload
              ref="uploadNoteRef"
              :http-request="uploadNoteFile"
              class="upload-demo"
              :on-exceed="handleExceed"
              :on-remove="handleRemove"
              :limit="1"
              v-model:file-list="fileList"
            >
              <el-button size="small" type="primary">Upload</el-button>
              <template #tip>
                <div class="el-upload__tip">
                  Click the upload File button to begin upload [.doc, .zip,
                  .docx, .pdf, .jpg, .jpeg, .gif, .png]
                </div>
              </template>
            </el-upload>
          </div>
        </el-form-item> -->
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="cancelModal">Cancel</el-button>
          <el-button
            type="primary"
            @click="saveNote(ruleFormRef)"
            :loading="saveNoteLoading"
            >Confirm</el-button
          >
        </span>
      </template>
    </el-dialog>
  </div>

  <!-- Matching rules弹窗 -->
  <el-dialog class="rules-dialog" v-model="rulesDialogVisible" width="1000px">
    <template #header>
      <div class="custom-dialog-header">
        <span class="dialog-title">Matching Rules Details</span>
      </div>
    </template>
    <DescriptionList type="matchingRules" :data="performanceSettingData" />
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="cancelModal">Closed</el-button>
      </span>
    </template>
  </el-dialog>

  <!-- Paper详情弹窗 -->
  <el-dialog class="paper-dialog" v-model="paperDialogVisible" width="1200px">
    <template #header>
      <div class="custom-dialog-header">
        <span class="dialog-title">Description Details</span>
      </div>
    </template>
    <DescriptionList
      :type="showDetailsType === 'paper' ? 'paperDetails' : 'siDetails'"
      :data="paperDetailData"
    />
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="cancelModal">Closed</el-button>
      </span>
    </template>
  </el-dialog>

  <!-- 添加表格弹窗组件 -->
  <TableDialog
    v-model:visible="tableDialogVisible"
    :title="tableDialogTitle"
    :beginTime="personalInformationObj.beginTime"
    :endTime="personalInformationObj.endTime"
    :commissionTypeId="approvalInformationObj.commissionTypeId"
    :email="personalInformationObj.email"
    :jid="selectedJournal"
    status="Paid"
    :approvalType="approvalType"
    @dialog-closed="handleDialogClosed"
  />

  <!-- 图片预览弹窗 -->
  <el-dialog
    v-model="imagePreviewVisible"
    title="Preview Picture"
    width="90%"
    :style="{ maxWidth: '1400px', minWidth: '800px' }"
    append-to-body
  >
    <div class="image-preview-container" ref="previewContainer">
      <div class="image-controls">
        <el-button-group>
          <el-button @click="zoomIn"  size="small" :icon="ZoomIn" title="Zoom In">Zoom In</el-button>
          <el-button @click="zoomOut" size="small" :icon="ZoomOut" title="Zoom Out">Zoom Out</el-button>
          <el-button @click="resetZoom" size="small" :icon="Refresh" title="Reset">Reset</el-button>
          <el-button @click="toggleFullscreen" size="small" :icon="FullScreen" title="Fullscreen">{{ isFullscreen ? 'Exit Fullscreen' : 'Fullscreen' }}</el-button>
        </el-button-group>
        <span class="zoom-indicator">{{ Math.round(imageScale * 100) }}%</span>
      </div>
      <div 
        class="image-wrapper" 
        @wheel="handleWheel" 
        ref="imageWrapper"
      >
        <img
          :src="previewImageUrl"
          alt="预览图片"
          class="preview-image"
          :style="imageStyle"
          @error="handleImageError"
          @load="handleImageLoad"
          draggable="false"
        />
      </div>
    </div>
    <template #footer>
      <el-button @click="closePreview">Closed</el-button>
    </template>
  </el-dialog>
</template>

<script lang="ts" setup>
import {
  onMounted,
  reactive,
  ref,
  h,
  getCurrentInstance,
  onUnmounted,
  computed,
  watch,
  nextTick,
} from "vue";
import {
  ElMessage,
  UploadRequestOptions,
  genFileId,
  ElMessageBox,
} from "element-plus";
import type { UploadProps, FormInstance } from "element-plus";
import { formatAmount } from "@/utils/format";
import { postNotification5 } from "@/api/developmentManagement/index";
import TableDialog from "@/views/performanceManagement/components/TableDialog.vue";
import DescriptionList from "@/views/performanceManagement/components/DescriptionList.vue";
import {  Delete, Search, FullScreen, ZoomIn,  ZoomOut, Refresh } from "@element-plus/icons-vue";
import { useRoute, useRouter } from "vue-router";
const router = useRouter();
import SvgIcon from "@/components/SvgIcon/index.vue";
import {
  performanceApplicationDetails,
  performanceSendToApprove,
  performanceApprove,
  performanceAppUpdate,
  performanceSettingsByCommissionId,
  performanceSettingsRuleId,
  performanceCommissionDetailsByAritcleId,
  performancePaperDetails,
} from "@/api/performanceManagement/index.ts";
import { removeFormulaPrefix } from "@/utils/format";
import Editor2 from "@/components/WangEditor/performanceManagementIndex.vue";

import { uploadFileApiNew } from "@/api/index";
import { CommissionType } from "@/types/performance";
const { proxy, ctx: that } = getCurrentInstance();
import { fetchSelect } from "@/api/index.ts";
interface PersonWorkload {
  id: number;
  name: string;
  role: string;
  proportion: string;
}

interface WorkloadGroup {
  journal?: string;
  people: PersonWorkload[];
}

interface HistoryNote {
  id: number;
  createdOn: string;
  operator: string;
  time: string;
  journal: string;
  content: string;
}

// 获取路由参数
const route = useRoute();
const approvalType = ref("AE");

const userId = computed(() => {
  const id = route.query.id;
  return id ? Number(id) : 0;
});

const approverEmail =
  route.query.approverEmail || sessionStorage.getItem("userEmail");

const yearQuarterDisplay = computed(() => {
  return personalInformationObj.year && personalInformationObj.quarter
    ? `${personalInformationObj.year} Q${personalInformationObj.quarter}`
    : personalInformationObj.createTime;
});

// 从HTML内容中提取图片URL
const getImagesFromHtml = (htmlContent: string) => {
  if (!htmlContent) return [];

  // 创建一个DOM解析器来处理HTML
  const parser = new DOMParser();
  const doc = parser.parseFromString(htmlContent, "text/html");
  const images = doc.querySelectorAll("img");

  const imageUrls: string[] = [];
  images.forEach((img) => {
    const src = img.getAttribute("src");
    if (src) {
      imageUrls.push(src);
    }
  });

  return imageUrls;
};

// 处理富文本中的图片点击事件
const processHtmlContent = (htmlContent: string) => {
  if (!htmlContent) return "";

  // 使用正则表达式匹配img标签并添加点击事件和样式
  const processedContent = htmlContent.replace(
    /<img([^>]*?)>/gi,
    (match, attributes) => {
      // 为图片添加点击事件和样式类
      const imgTag = `<img${attributes} class="comment-image clickable-image" onclick="handleCommentImageClick(this.src)" title="Preview Picture">`;
      return imgTag;
    }
  );

  return processedContent;
};

// 格式化消息内容,将换行符转换为HTML换行标签
const formatMessageWithLineBreaks = (message: string) => {
  if (!message) return "";
  // 将换行符 \n 转换为 <br> 标签
  return message.replace(/\n/g, "<br>");
};

// Paid确认点击处理
const handlePaidConfirmClick = () => {
  tableDialogVisible.value = true;
  tableDialogTitle.value = "Paid Confirmed Details";
};

const hasCommission = ref(false);
const sectionHasCommission = ref(false);

// 表单数据
const assignmentTime = ref("2025 Q1");
const scopusUrl = ref("");
const identityUrl = ref("");

// 拒绝弹窗相关
const rejectDialogVisible = ref(false);
const rulesDialogVisible = ref(false);
const rejectReason = ref("");

// 添加笔记相关
const addNotesDialogVisible = ref(false);
const notesContent = ref("");

// 图片上传相关
const uploadAction = ref(""); // 实际使用时替换为后端上传接口
const invitationProofUrl = ref("");
const scopusProofUrl = ref("");
const referencesProofUrl = ref("");
const identityProofUrl = ref("");
const invitationImages = ref<{ url: string; name: string }[]>([]);

interface StepItem {
  name: string;
  user: string;
  time: string;
  status: string;
  active?: boolean; // 新增可选属性
  statusId?: number; // 修改为 number 类型
}

const stepsData = ref<StepItem[]>([
  {
    name: "申请",
    user: "研究人员",
    time: "2023-12-01 10:00",
    status: "completed",
  },
  {
    name: "初审",
    user: "部门主管",
    time: "2023-12-05 14:30",
    status: "completed",
  },
  {
    name: "复审",
    user: "论文委员会",
    time: "2023-12-10 09:15",
    status: "completed",
  },
  {
    name: "等审",
    user: "最终审核",
    time: "处理中",
    status: "current",
  },
]);

// 详情页个人信息
const personalInformationObj = reactive<any>({
  chineseName: "",
  role: "",
  journalName: "",
  year: "",
  quarter: "",
  createTime: "",
  qcPassRate: "",
  paidConfirmed: "",
  finalConfirmed: '',
  email: "",
  pi: "",
  beginTime: "",
  endTime: "",
  paperId: "",
});

// 详情页审批信息
const approvalInformationObj = reactive({
  commissionType: "",
  journalName: "",
  calculationFormula: "",
  remark: "",
  amount: "",
  id: "",
  categoryId: null,
  commissionTypeId: null,

  sectionId: "",
  sectionTitle: "",
  siPublished: "",

  sectionType: "",
  articleType: "",
  paymentAmount: "",
  proportion: "",
  totalCommission: "",
  proofDtoList: [],
  exchangeRate: "",
});

const isLoading = ref(false);

const showApprove = ref(false);
const showReject = ref(false);
const showSendToApprove = ref(false);
const showSave = ref(false);
const showExchangeRate = ref(false);
const applicationStatusValue = ref("");

const categoryList = ref([]);

const notificationList = ref([]);
const selectedJournal = ref();
const journalOptions = ref<any[]>([]);

const rejectComment = ref("");

// 汇率相关变量和方法
const exchangeRateInput = ref("");

// 处理汇率输入,限制最多两位小数
const handleExchangeRateInput = (value: string) => {
  // 移除非数字和小数点的字符
  let cleanValue = value.replace(/[^\d.]/g, "");

  // 确保只有一个小数点
  const parts = cleanValue.split(".");
  if (parts.length > 2) {
    cleanValue = parts[0] + "." + parts.slice(1).join("");
  }

  // 限制小数点后最多两位
  if (parts.length === 2 && parts[1].length > 2) {
    cleanValue = parts[0] + "." + parts[1].substring(0, 2);
  }

  exchangeRateInput.value = cleanValue;
  approvalInformationObj.exchangeRate = cleanValue;
};

// 计算新的Amount值
const calculateAmount = () => {
  const exchangeRate = parseFloat(exchangeRateInput.value);
  const paymentAmount = parseFloat(approvalInformationObj.paymentAmount);

  if (!isNaN(exchangeRate) && !isNaN(paymentAmount) && exchangeRate > 0) {
    const newAmount = (exchangeRate * paymentAmount * 0.01).toFixed(2);
    approvalInformationObj.amount = newAmount;
  }
};

// 预览评论中的图片
const previewCommentImage = (imageUrl: string) => {
  previewImageUrl.value = imageUrl;
  imagePreviewVisible.value = true;
};

// 监听粘贴事件,实现粘贴上传图片
onMounted(() => {
  document.addEventListener("paste", handlePaste);
  document.addEventListener("paste", handleProofPasteEvent);

  // 将预览函数挂载到window对象上,供HTML中的onclick调用
  (window as any).previewCommentImage = previewCommentImage;
  (window as any).handleCommentImageClick = (src: string) => {
    previewCommentImage(src);
  };

  fetchSelect({
    types: [1, 20],
  }).then((res: any) => {
    if (res.code === 2000) {
      journalOptions.value = res.data.bases || [];
    }
  });

  getDetailsData();
});

// 监听汇率变化,自动计算金额
watch(exchangeRateInput, (newValue) => {
  if (newValue && approvalInformationObj.paymentAmount) {
    calculateAmount();
  }
});

// 计算当前选中category的label值,用于title属性
const selectedCategoryLabel = computed(() => {
  if (!selectedCategory.value || !categoryList.value.length) {
    return "";
  }
  const selectedItem = categoryList.value.find(
    (item: any) => item.id === selectedCategory.value
  );
  return selectedItem ? selectedItem.value : "";
});

// 根据状态值返回对应的CSS类名
const statusClass = computed(() => {
  if (!applicationStatusValue.value) return "";

  const status = applicationStatusValue.value.toLowerCase();

  if (status.includes("in review")) {
    return "in-review";
  } else if (status.includes("approved")) {
    return "approved";
  } else if (status.includes("pending")) {
    return "pending";
  } else if (status.includes("rejected")) {
    return "rejected";
  }

  return "";
});

const getPerformanceSettingsByCommissionId = () => {
  performanceSettingsByCommissionId({
    commissionId: approvalInformationObj.commissionTypeId,
    categoryId: selectedCategory.value || 0,
    requestedId: personalInformationObj.paperId, // 文章id
    sectionId: approvalInformationObj.sectionId,
    journalId: selectedJournal.value,
    beginTime: personalInformationObj.beginTime || null,
    endTime: personalInformationObj.endTime || null,
  }).then((res) => {
    if (res.code === 2000 && res.data) {
      categoryList.value = res.data.performanceCategoryEnumList || [];

      approvalInformationObj.articleType = res.data.article?.articleType;
      approvalInformationObj.paymentAmount = res.data.article?.apc;
      calculateAmount();
    }
  });
};

const proofRelations = ref([]);


const handleDialogClosed = () => {
  getDetailsData()
}


const getDetailsData = async () => {
  isLoading.value = true;
  await performanceApplicationDetails({
    id: userId.value,
  })
    .then((res: any) => {
      stepsData.value = [];

      if (res.code === 2000) {
        if (
          res.data.application?.approvalFlows &&
          res.data.application?.approvalFlows.length > 0
        ) {
          res.data.application?.approvalFlows.forEach(
            (item: any, index: number) => {
              stepsData.value.push({
                // name: item.stepDescription ? item.stepDescription : "申请",
                // name: getStatusName(index),
                name: getStatusName(item, index),
                // user: item.approver.chineseName,
                user: "",
                time: item.approvalTime,
                status: item.stepId,
                active: item.active,
                statusId: Number(item.statusId), // 确保转换为 number 类型
              });
            }
          );

          // 模块展示
          approvalType.value = res.data.commissionType || "AE";

          // 个人信息
          personalInformationObj.chineseName =
            res.data.application.user?.chineseName;
          personalInformationObj.role = res.data.application.user?.roles
            .map((item: any) => item.role)
            .join(",");
          personalInformationObj.journalName = res.data.application.user?.roles
            .map((item: any) => item.journalName)
            .join(",");

          personalInformationObj.year = res.data.application.year;
          personalInformationObj.quarter = res.data.application.quarter;
          personalInformationObj.createTime = res.data.application.createTime;
          personalInformationObj.beginTime = res.data.beginTime;
          personalInformationObj.endTime = res.data.endTime;

          personalInformationObj.qcPassRate = res.data.application.qcPassRate;
          personalInformationObj.paidConfirmed =
            res.data.application.paidConfirmed;

          personalInformationObj.finalConfirmed = res.data.application.paidConfirmed - res.data.application.uncheckedNumber

          personalInformationObj.email = res.data.application.email;
          personalInformationObj.pi = res.data.application.pi || 0;
          personalInformationObj.paperId = res.data.application?.paperId;

          // 审批信息
          approvalInformationObj.journalName =
            res.data.application?.journalName;
          approvalInformationObj.commissionType =
            res.data.performanceSetting?.commissionTypeValue;
          approvalInformationObj.calculationFormula =
            res.data.performanceSetting?.amount;
          approvalInformationObj.remark = res.data.application.remark;
          approvalInformationObj.amount = res.data.application.amount;
          selectedJournal.value = res.data.application?.jid;
          approvalInformationObj.proportion = res.data.application?.proportion;
          approvalInformationObj.totalCommission =
            res.data.application?.totalCommission;

          // 初始化汇率
          exchangeRateInput.value = res.data.application.exchangeRate || "";

          approvalInformationObj.id = res.data.performanceSetting?.id;
          approvalInformationObj.categoryId =
            res.data.performanceSetting?.categoryId;
          approvalInformationObj.commissionTypeId =
            res.data.performanceSetting?.commissionTypeId;

          approvalInformationObj.sectionId = res.data.section?.sectionid;
          approvalInformationObj.sectionTitle = res.data.section?.title;
          approvalInformationObj.siPublished = res.data.section?.publishNumber;
          approvalInformationObj.sectionType = res.data.section?.sectionType;

          // 权限
          showApprove.value = res.data.showApprove;
          showReject.value = res.data.showReject;
          showSendToApprove.value = res.data.showSendToApprove;
          showSave.value = res.data.showSave;
          showExchangeRate.value = res.data.showExchangeRate;
          applicationStatusValue.value = res.data.applicationStatusValue;

          notificationList.value = res.data.notificationList || [];
          approvalInformationObj.proofDtoList = res.data.proofDtoList || [];
          rejectComment.value = res.data.rejectComment;
          proofRelations.value = res.data.application?.proofs;

          hasCommission.value = res.data.application?.hasCommission || false;
          sectionHasCommission.value =
            res.data.application?.sectionHasCommission || false;
        } else {
          stepsData.value = [];
        }

        isLoading.value = false;
      }
    })
    .catch(() => {
      isLoading.value = false;
    })
    .finally(() => {
      isLoading.value = false;
    });

  selectedCategory.value = approvalInformationObj.categoryId;

  await getPerformanceSettingsByCommissionId();
  
  // 初始化证明材料编辑数据
  setTimeout(() => {
    initializeProofEditData();
  }, 100);
};

function getStatusName(item: any, index: number) {
  return index == 0
    ? `申请:${item.approver.chineseName}`
    : `${item.statusDescription}: ${item.approver.chineseName}`;
}

// 检查指定索引之前是否有被拒绝的步骤
const hasRejectedBefore = (index: number) => {
  for (let i = 0; i < index && i < stepsData.value.length; i++) {
    if (stepsData.value[i].active && stepsData.value[i].statusId === 4) {
      return true;
    }
  }
  return false;
};

// 组件卸载时移除事件监听
onUnmounted(() => {
  document.removeEventListener("paste", handlePaste);
  document.removeEventListener("paste", handleProofPasteEvent);
  // 清理window上的函数
  delete (window as any).previewCommentImage;
  delete (window as any).handleCommentImageClick;
});

const selectedCategory = ref();

// 处理粘贴事件
const handlePaste = (event: ClipboardEvent) => {
  const items = event.clipboardData?.items;
  if (!items) return;

  for (let i = 0; i < items.length; i++) {
    if (items[i].type.indexOf("image") !== -1) {
      const file = items[i].getAsFile();
      if (!file) continue;

      // 根据当前焦点决定上传到哪个区域
      if (rejectDialogVisible.value) {
        handleImageFile(file, "reject");
      } else if (addNotesDialogVisible.value) {
        handleImageFile(file, "notes");
      } else {
        handleImageFile(file, "invitation");
      }

      // 阻止默认粘贴行为
      event.preventDefault();
      break;
    }
  }
};

// 上传前检查
const beforeUpload = (file: File) => {
  const isImage = file.type.startsWith("image/");
  const isLt5M = file.size / 1024 / 1024 < 5;

  if (!isImage) {
    ElMessage.error("Only image files can be uploaded.");
    return false;
  }
  if (!isLt5M) {
    ElMessage.error("The image size must not exceed 5MB.");
    return false;
  }
  return true;
};

// 处理图片文件
const handleImageFile = (file: File, type: string) => {
  if (!beforeUpload(file)) return;

  const reader = new FileReader();
  reader.readAsDataURL(file);
  reader.onload = () => {
    const imgUrl = reader.result as string;

    if (type === "invitation") {
      invitationProofUrl.value = imgUrl;
      invitationImages.value.push({
        url: imgUrl,
        name: file.name,
      });
    } else if (type === "scopus") {
      scopusProofUrl.value = imgUrl;
    } else if (type === "references") {
      referencesProofUrl.value = imgUrl;
    } else if (type === "identity") {
      identityProofUrl.value = imgUrl;
    } else if (type === "reject") {
    }
  };
};

// 自定义上传函数
const customUploadInvitation = (options: any) => {
  handleImageFile(options.file, "invitation");
};

const customUploadScopus = (options: any) => {
  handleImageFile(options.file, "scopus");
};

const customUploadReferences = (options: any) => {
  handleImageFile(options.file, "references");
};

const customUploadIdentity = (options: any) => {
  handleImageFile(options.file, "identity");
};

const customUploadReject = (options: any) => {
  handleImageFile(options.file, "reject");
};

const customUploadNotes = (options: any) => {
  handleImageFile(options.file, "notes");
};

const fileList = ref([]);
const uploadNoteRef = ref(null);
const fileId = ref("");
const handleExceed: UploadProps["onExceed"] = (files) => {
  uploadNoteRef.value!.clearFiles();
  const file = files[0] as UploadRawFile;
  file.uid = genFileId();
  uploadNoteRef.value!.handleStart(file);
  uploadNoteRef.value!.submit();
};
const handleRemove: UploadProps["onRemove"] = (file, uploadFiles) => {
  fileId.value = "";
};
async function uploadNoteFile(options: UploadRequestOptions): Promise<any> {
  const { data } = await uploadFileApiNew(options.file);
  fileId.value = data.uploadFileData.id;
}

// 上传成功回调
const handleInvitationSuccess = (response: any, file: any) => {
  // 实际项目中处理后端返回的URL
  invitationProofUrl.value = URL.createObjectURL(file.raw);
};

const handleScopusSuccess = (response: any, file: any) => {
  scopusProofUrl.value = URL.createObjectURL(file.raw);
};

const handleReferencesSuccess = (response: any, file: any) => {
  referencesProofUrl.value = URL.createObjectURL(file.raw);
};

const handleIdentitySuccess = (response: any, file: any) => {
  identityProofUrl.value = URL.createObjectURL(file.raw);
};

// 移除图片
const removeInvitationImage = (index: number) => {
  invitationImages.value.splice(index, 1);
  if (invitationImages.value.length === 0) {
    invitationProofUrl.value = "";
  } else {
    invitationProofUrl.value = invitationImages.value[0].url;
  }
};

// 预览图片
const previewImage = (url: string, item) => {
  // console.log("previewImage", item);

  // 使用 el-dialog 预览图片
  previewImageUrl.value = url;
  imagePreviewVisible.value = true;
};
const handleSave = () => {
  // if (!invitationProofUrl.value) {
  //   return ElMessage.warning(
  //     "Please upload the proof materials of the invitation letter"
  //   );
  // }

  // 构建证明材料数据 - 如果是编辑模式,使用编辑后的数据
  let finalProofRelations: any = proofRelations.value;
  
  if (isProofEditable.value && Object.keys(proofEditData.value).length > 0) {
    // 构建编辑后的证明材料数据
    finalProofRelations = approvalInformationObj.proofDtoList
      .map((proofType: any, index: number) => {
        const data = proofEditData.value[index];

        console.log('材料数据', data)
        const isLinkType = 
          proofType.value?.toLowerCase().includes("url") ||
          proofType.value?.toLowerCase().includes("link");

        if (!isLinkType) {
          // 图片类型:如果有多张图片,每张图片作为一条数据
          if (data?.images?.length > 0) {
            return data.images.map((image: any) => ({
              fileId: image.fileId || null,
              note: "",
              fileType: proofType.id,
            }));
          } else {
            // 没有图片时返回一条空数据
            return [
              {
                fileId: null,
                note: "",
                fileType: proofType.id,
              },
            ];
          }
        } else {
          // 文本输入类型:只返回用户输入的文本,fileId为空
          return [
            {
              fileId: null,
              note: data?.url || "",
              fileType: proofType.id,
            },
          ];
        }
      })
      .filter((item: any) => item !== null) // 过滤掉空值
      .flat(); // 将嵌套数组展平
  }

  let params = {
    id: userId.value,
    email: personalInformationObj.email,
    performanceSettingId: approvalInformationObj.id,
    year: personalInformationObj.year,
    quarter: personalInformationObj.quarter,
    qcPassRate: personalInformationObj.qcPassRate,
    paidConfirmed: personalInformationObj.paidConfirmed,
    pi: personalInformationObj.pi,
    amount: approvalInformationObj.amount,
    paperId: personalInformationObj.paperId,
    sectionId: approvalInformationObj.sectionId,
    remark: approvalInformationObj.remark,
    proofRelations: finalProofRelations,
    jid: selectedJournal.value,
    proportion: approvalInformationObj.proportion,
    totalCommission: approvalInformationObj.totalCommission,
  };

  ElMessageBox.confirm("Confirm the approval of this application?", "Tips", {
    confirmButtonText: "Confirm",
    cancelButtonText: "Cancel",
    type: "success",
  })
    .then(() => {
      performanceAppUpdate(params).then((res) => {
        if (res.code === 2000) {
          getDetailsData();
          ElMessage({
            type: "success",
            message: res.message,
          });
        }
      });
    })
    .catch(() => {});
};

const handleSendToApproval = () => {
  let params = {
    id: userId.value,
    email: personalInformationObj.email,
    performanceSettingId: approvalInformationObj.id,
    year: personalInformationObj.year,
    quarter: personalInformationObj.quarter,
    qcPassRate: personalInformationObj.qcPassRate,
    paidConfirmed: personalInformationObj.paidConfirmed,
    pi: personalInformationObj.pi,
    amount: approvalInformationObj.amount,
    paperId: personalInformationObj.paperId,
    sectionId: approvalInformationObj.sectionId,
    remark: approvalInformationObj.remark,
    proofRelations: proofRelations.value,
    jid: selectedJournal.value,
    proportion: approvalInformationObj.proportion,
    totalCommission: approvalInformationObj.totalCommission,
  };

  ElMessageBox.confirm("Are you sure send to approve ?", "Tip", {
    confirmButtonText: "Confirm",
    cancelButtonText: "Cancel",
    type: "success",
  })
    .then(async () => {
      await performanceAppUpdate(params).then((res) => {
        if (res.code === 2000) {
          getDetailsData();
          ElMessage({
            type: "success",
            message: res.message,
          });

          performanceSendToApprove({
            applicationId: userId.value,
          }).then((res: any) => {
            if (res.code === 2000) {
              getDetailsData();
              ElMessage({
                type: "success",
                message: res.message,
              });
            }
          });
        }
      });
    })
    .catch(() => {});
};

// 审批和拒绝处理
const handleApprove = () => {
  // 验证USD Exchange Rate必填(当显示汇率字段时)
  if (showExchangeRate.value && !exchangeRateInput.value?.trim()) {
    ElMessage.warning("USD Exchange Rate cannot be empty.");
    return;
  }

  // if (!invitationProofUrl.value) {
  //   ElMessage.warning("请上传邀请函证明材料");
  //   return;
  // }

  ElMessageBox.confirm("Confirm the approval of this application?", "Tip", {
    confirmButtonText: "Confirm",
    cancelButtonText: "Cancel",
    type: "success",
  })
    .then(() => {
      let params = {
        applicationId: userId.value,
        approverEmail,
        approved: true, // true 通过; false 拒绝
        remark: approvalInformationObj.remark,
        amount: approvalInformationObj.amount,
        exchangeRate: approvalInformationObj.exchangeRate,
      };
      performanceApprove(params).then((res: any) => {
        if (res.code === 2000) {
          getDetailsData();

          ElMessage({
            type: "success",
            message: res.message,
          });
        }
      });
    })
    .catch(() => {});
};

const handleReject = () => {
  rejectDialogVisible.value = true;
};

const confirmReject = () => {
  // 验证USD Exchange Rate必填(当显示汇率字段时)
  if (showExchangeRate.value && !exchangeRateInput.value?.trim()) {
    ElMessage.warning("USD Exchange Rate cannot be empty.");
    return;
  }

  if (!rejectReason.value) {
    ElMessage.warning("Please enter rejection reason");
    return;
  }

  ElMessageBox.confirm("Confirm to reject this application?", "Tip", {
    confirmButtonText: "Confirm",
    cancelButtonText: "Cancel",
    type: "warning",
  })
    .then(() => {
      let params = {
        applicationId: userId.value,
        approverEmail,
        approved: false, // false 表示拒绝
        remark: rejectReason.value,
        amount: approvalInformationObj.amount,
        exchangeRate: approvalInformationObj.exchangeRate,
      };
      performanceApprove(params)
        .then((res: any) => {
          if (res.code === 2000) {
            getDetailsData();
            rejectDialogVisible.value = false;
            rejectReason.value = "";
            ElMessage({
              type: "success",
              message: res.message,
            });
          }
        })
        .catch(() => {
          ElMessage.error("Operation failed");
        });
    })
    .catch(() => {});
};

const cancelModal = () => {
  rejectDialogVisible.value = false;
  rejectReason.value = "";

  rulesDialogVisible.value = false;

  addNotesDialogVisible.value = false;
  notesContent.value = "";

  paperDialogVisible.value = false;
};

// 添加笔记相关处理
const handleAddNotes = () => {
  addNotesDialogVisible.value = true;
  notesContent.value = "";
};

const ruleFormRef = ref();
const saveNoteLoading = ref(false);
const saveNote = () => {
  const data = {
    value: notesContent.value,
    id: userId.value,
    fileId: fileId.value,
  };

  if (!notesContent.value) {
    ElMessage({
      message: "History notes content is empty!",
      type: "warning",
    });
    return;
  }

  saveNoteLoading.value = true;

  postNotification5(data)
    .then((res) => {
      ElMessage({
        message: "Successful operation!",
        type: "success",
      });

      performanceApplicationDetails({
        id: userId.value,
      }).then((res: any) => {
        if (res.code === 2000) {
          notificationList.value = res.data.notificationList || [];
        }
      });

      // 清理表单数据
      fileList.value = [];
      fileId.value = "";
      proxy.$refs.uploadNoteRef.clearFiles();
      notesContent.value = "";
    })
    .catch((error) => {
      addNotesDialogVisible.value = false;
      saveNoteLoading.value = false;
    })
    .finally(() => {
      addNotesDialogVisible.value = false;
      saveNoteLoading.value = false;
    });
};

const isEditing = ref(false);

// 切换编辑状态
const toggleEdit = () => {
  isEditing.value = !isEditing.value;
};

// 表格弹窗相关数据
const tableDialogVisible = ref(false);
const tableDialogTitle = ref("");

const performanceSettingData = ref(); // matching rules 弹窗数据
const showMatchingRulesDetails = () => {
  rulesDialogVisible.value = true;
  tableDialogTitle.value = "Matching Rules Details";

  performanceSettingsRuleId({
    id: approvalInformationObj.id,
  }).then((res) => {
    if (res.code === 2000) {
      performanceSettingData.value = res.data.performanceSetting;
    }
  });
};

// Paper详情弹窗相关
const paperDialogVisible = ref(false);
const paperDetailData = ref();
const showDetailsType = ref();

const showPaperDetails = (paperId: string, nType: string) => {
  if (!paperId) return;

  showDetailsType.value = nType;

  paperDialogVisible.value = true;

  // 调用API获取Paper详情
  performancePaperDetails({
    articleId: paperId,
    type: nType === "si" ? 1 : undefined,
  })
    .then((res: any) => {
      if (res.code === 2000) {
        paperDetailData.value =
          nType === "paper" ? res.data.article : res.data.section;
      } else {
        ElMessage.error(res.message);
        paperDetailData.value = null;
      }
    })
    .catch(() => {
      paperDetailData.value = null;
    });
};

// 获取图片URL
const getImageUrl = (fileId: number) => {
  if (!fileId) return "";
  return `/api/poster/show/image/${fileId}`;
};

// 处理图片加载错误
const handleImageError = (event: Event) => {
  const target = event.target as HTMLImageElement;
  // target.src = "/src/assets/icons/no-image.svg";  // 使用默认图片或隐藏
  // target.src = "";
  target.src = new URL("@/assets/no-image.png", import.meta.url).href;
  target.alt = "Image failed to load";
};

// 获取显示的proof项目,保持原有布局结构
const getDisplayProofItems = () => {
  if (
    !approvalInformationObj.proofDtoList ||
    approvalInformationObj.proofDtoList.length === 0
  ) {
    return [];
  }

  // 直接使用后端返回的数据,保持原有顺序
  return approvalInformationObj.proofDtoList.map((proofItem: any) => {
    const images =
      proofItem.list?.filter((item: any) => item.fileId !== null) || [];
    const links =
      proofItem.list?.filter(
        (item: any) => item.fileId === null && item.note
      ) || [];

    // 判断是否为链接类型:有链接数据且没有图片数据,或者value中包含URL相关关键词
    const isLinkType =
      (links.length > 0 && images.length === 0) ||
      proofItem.value?.toLowerCase().includes("url") ||
      proofItem.value?.toLowerCase().includes("link");

    return {
      title: proofItem.value || "Proof Material",
      type: isLinkType ? "link" : "image",
      originalType: isLinkType ? 1 : 0, // 新增:保存原始类型用于编辑模式判断
      value: links.length > 0 ? links[0].note : "",
      images: images,
      placeholder: isLinkType ? "No link provided" : "",
    };
  });
};

const goPage = (id: any) => {
  // let routeData = router.resolve({
  //   path: "/papersMain/page",
  //   query: { id },
  // });
  // window.open(routeData.href, "_blank");
};

// 图片预览相关
const imagePreviewVisible = ref(false);
const previewImageUrl = ref("");
const imageScale = ref(1);
const isFullscreen = ref(false);
const previewContainer = ref();
const imageWrapper = ref();

const tooltipContent = ref("");
const fetchInfo = async (id, nType) => {
  if (!id) {
    tooltipContent.value = "No available";
    return;
  }

  try {
    tooltipContent.value = "Loading...";
    const { code, data } = await performanceCommissionDetailsByAritcleId({
      articleId: id,
      type: nType === "si" ? 1 : undefined,
    });
    if (code === 2000) {
      tooltipContent.value = data.details.commissionDetails
        .map(
          (item) =>
            `${item.performanceApplicationName}:${item.commissionTypeName}  ${item.stepId ? `(Step ${item.stepId}: Approved)` : ''}`
        )
        .join("<br>");
    }
  } catch {
    tooltipContent.value = "Failed to load data";
  }
};

// 新增编辑相关的数据和计算属性
const isProofEditable = computed(() => {
  if (!applicationStatusValue.value || !showSave.value) return false;
  const status = applicationStatusValue.value.toLowerCase();
  return status.includes("pending") || status.includes("rejected");
});

// 编辑模式的证明材料数据结构
const proofEditData = ref<{
  [key: number]: {
    displayUrl?: string;
    url?: string;
    note?: string;
    fileId?: number;
    images?: { url: string; name: string; fileId?: number }[];
  };
}>({});

// 当前焦点区域
const currentProofFocusArea = ref<any>(0);
// 是否显示焦点提示
const showProofFocusHint = ref(false);
// 焦点提示的位置
const proofFocusHintPosition = ref({ top: 0, left: 0 });

// 设置活跃上传区域
const setActiveProofUploadArea = (index: number) => {
  currentProofFocusArea.value = index;

  // 显示简短提示
  showProofFocusHint.value = true;
  setTimeout(() => {
    showProofFocusHint.value = false;
  }, 1500);
};

// 显示上传提示
const showProofUploadHint = (index: number, event: MouseEvent) => {
  const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
  proofFocusHintPosition.value = {
    top: rect.top - 30,
    left: rect.left + rect.width / 2 - 100,
  };

  // 临时更新焦点区域用于显示提示文本
  const tempFocus = currentProofFocusArea.value;
  currentProofFocusArea.value = index;
  showProofFocusHint.value = true;

  // 恢复原来的焦点区域
  setTimeout(() => {
    if (currentProofFocusArea.value === index && !showProofFocusHint.value) {
      currentProofFocusArea.value = tempFocus;
    }
  }, 100);
};

// 隐藏上传提示
const hideProofUploadHint = () => {
  showProofFocusHint.value = false;
};

// 处理证明材料粘贴事件
const handleProofPasteEvent = (event: ClipboardEvent) => {
  const items = event.clipboardData?.items;
  if (!items) return;

  // 检查当前焦点元素,判断应该上传到哪个区域
  const activeElement = document.activeElement;
  let targetIndex = currentProofFocusArea.value;
  let foundArea = false;

  // 根据当前激活的元素判断上传区域
  if (activeElement) {
    // 尝试从当前元素向上查找上传容器
    let element = activeElement as HTMLElement;
    while (element && !foundArea) {
      if (element.classList?.contains("upload-container")) {
        // 查找proof-uploader的索引
        const uploader = element.querySelector("[class*='proof-uploader-']");
        if (uploader) {
          const classList = Array.from(uploader.classList);
          const uploaderClass = classList.find((cls) =>
            cls.startsWith("proof-uploader-")
          );
          if (uploaderClass) {
            targetIndex = parseInt(uploaderClass.split("-")[2]);
            foundArea = true;
          }
        }
        break;
      }

      // 直接检查当前元素是否是上传器
      if (element.classList.value.includes("proof-uploader-")) {
        const classList = Array.from(element.classList);
        const uploaderClass = classList.find((cls) =>
          cls.startsWith("proof-uploader-")
        );
        if (uploaderClass) {
          targetIndex = parseInt(uploaderClass.split("-")[2]);
          foundArea = true;
        }
        break;
      }

      element = element.parentElement as HTMLElement;
    }
  }

  let imageFound = false;

  // 处理所有粘贴的图片
  for (let i = 0; i < items.length; i++) {
    if (items[i].type.indexOf("image") !== -1) {
      const file = items[i].getAsFile();
      if (!file) continue;

      imageFound = true;
      // 使用确定的目标区域上传图片
      processProofImageFile(file, targetIndex);
    }
  }

  // 只有找到图片时才阻止默认粘贴行为
  if (imageFound) {
    event.preventDefault();
    // 更新当前焦点区域,以便下次粘贴时使用
    currentProofFocusArea.value = targetIndex;
  }
};

// 上传前检查
const beforeProofUpload = (file: File) => {
  const isImage = file.type.startsWith("image/");
  const isLt5M = file.size / 1024 / 1024 < 5;

  if (!isImage) {
    ElMessage.error("Only image files can be uploaded!");
    return false;
  }
  if (!isLt5M) {
    ElMessage.error("The image size must not exceed 5MB.");
    return false;
  }
  return true;
};

// 处理图片文件
const processProofImageFile = async (file: File, index: number) => {
  if (!beforeProofUpload(file)) return;

  // 初始化该索引的数据对象
  if (!proofEditData.value[index]) {
    proofEditData.value[index] = { images: [] };
  }
  if (!proofEditData.value[index].images) {
    proofEditData.value[index].images = [];
  }

  try {
    // 调用真实上传接口
    const uploadResponse = await uploadFileApiNew(file);

    if (uploadResponse.data && uploadResponse.data.uploadFileData) {
      const fileId = uploadResponse.data.uploadFileData.id || "";
      const imgUrl = URL.createObjectURL(file);
      const imageData = {
        url: imgUrl,
        name: file.name,
        fileId: fileId,
      };

      proofEditData.value[index].images!.push(imageData);
      proofEditData.value[index].displayUrl = imgUrl;
      proofEditData.value[index].fileId = fileId; // 记录当前显示图片的文件ID

      ElMessage.success("Image uploaded successfully.");
    } else {
      ElMessage.error("Image upload failed.");
    }
  } catch (error) {
    console.error("Upload error:", error);
    ElMessage.error("Image upload failed.");
  }
};

// 自定义上传函数
const customProofUploadHandler = (options: any, index: number) => {
  processProofImageFile(options.file, index);
};

// 上传成功回调
const onProofUploadSuccess = async (response: any, file: any, index: number) => {
  // 初始化该索引的数据对象
  if (!proofEditData.value[index]) {
    proofEditData.value[index] = { images: [] };
  }
  if (!proofEditData.value[index].images) {
    proofEditData.value[index].images = [];
  }

  try {
    // 调用真实上传接口
    const uploadResponse = await uploadFileApiNew(file.raw);

    if (uploadResponse.data && uploadResponse.data.uploadFileData) {
      const fileId = uploadResponse.data.uploadFileData.id;
      const url = URL.createObjectURL(file.raw);

      proofEditData.value[index].images!.push({
        url,
        name: file.name,
        fileId: fileId,
      });
      proofEditData.value[index].displayUrl = url;
      proofEditData.value[index].fileId = fileId; // 记录当前显示图片的文件ID

      ElMessage.success("Image uploaded successfully.");
    } else {
      ElMessage.error("Image upload failed.");
    }
  } catch (error) {
    console.error("Upload error:", error);
    ElMessage.error("Image upload failed.");
  }
};

// 移除图片
const removeProofImage = (proofIndex: number, imageIndex: number) => {
  if (proofEditData.value[proofIndex]?.images) {
    proofEditData.value[proofIndex].images!.splice(imageIndex, 1);

    if (proofEditData.value[proofIndex].images!.length === 0) {
      proofEditData.value[proofIndex].displayUrl = "";
    } else {
      proofEditData.value[proofIndex].displayUrl =
        proofEditData.value[proofIndex].images![0].url;
    }
  }
};

// 切换展示图片
const changeProofDisplayImage = (proofIndex: number, url: string) => {
  if (proofEditData.value[proofIndex]) {
    proofEditData.value[proofIndex].displayUrl = url;

    // 找到对应图片的文件ID并更新
    const selectedImage = proofEditData.value[proofIndex].images?.find(
      (img) => img.url === url
    );
    if (selectedImage?.fileId) {
      proofEditData.value[proofIndex].fileId = selectedImage.fileId;
    }
  }
};

// 初始化证明材料编辑数据
const initializeProofEditData = () => {
  if (
    !approvalInformationObj.proofDtoList ||
    approvalInformationObj.proofDtoList.length === 0
  ) {
    return;
  }

  approvalInformationObj.proofDtoList.forEach((proofItem: any, index: number) => {
    const images =
      proofItem.list?.filter((item: any) => item.fileId !== null) || [];
    const links =
      proofItem.list?.filter(
        (item: any) => item.fileId === null && item.note
      ) || [];

    // 判断是否为链接类型
    const isLinkType =
      (links.length > 0 && images.length === 0) ||
      proofItem.value?.toLowerCase().includes("url") ||
      proofItem.value?.toLowerCase().includes("link");

    if (!proofEditData.value[index]) {
      proofEditData.value[index] = {
        images: [],
        url: "",
        displayUrl: "",
        note: "",
        fileId: undefined,
      };
    }

    if (isLinkType) {
      // 链接类型:回填文本内容
      proofEditData.value[index].url = links.length > 0 ? links[0].note : "";
    } else {
      // 图片类型:回填图片数据
      const imageData = images.map((img: any) => ({
        url: getImageUrl(img.fileId),
        name: `image_${img.fileId}`,
        fileId: img.fileId,
      }));
      proofEditData.value[index].images = imageData;
      if (imageData.length > 0) {
        proofEditData.value[index].displayUrl = imageData[0].url;
        proofEditData.value[index].fileId = imageData[0].fileId;
      }
    }
  });
};

// 图片预览控制方法
const imageStyle = computed(() => ({
  transform: `scale(${imageScale.value})`,
  transition: 'transform 0.3s ease',
}));

// 放大图片
const zoomIn = () => {
  imageScale.value = Math.min(imageScale.value * 1.2, 5);
  // 缩放后保持顶部可见
  nextTick(() => {
    if (imageWrapper.value) {
      imageWrapper.value.scrollTop = 0;
    }
  });
};

// 缩小图片
const zoomOut = () => {
  imageScale.value = Math.max(imageScale.value / 1.2, 0.1);
  // 缩放后保持顶部可见
  nextTick(() => {
    if (imageWrapper.value) {
      imageWrapper.value.scrollTop = 0;
    }
  });
};

// 重置缩放
const resetZoom = () => {
  imageScale.value = 1;
  // 重置后滚动到顶部
  nextTick(() => {
    if (imageWrapper.value) {
      imageWrapper.value.scrollTop = 0;
      imageWrapper.value.scrollLeft = 0;
    }
  });
};

// 处理鼠标滚轮缩放
const handleWheel = (event: WheelEvent) => {
  event.preventDefault();
  
  const wrapper = imageWrapper.value;
  if (!wrapper) return;
  
  // 记录当前滚动位置和鼠标相对位置
  const rect = wrapper.getBoundingClientRect();
  const mouseX = event.clientX - rect.left;
  const mouseY = event.clientY - rect.top;
  const scrollLeft = wrapper.scrollLeft;
  const scrollTop = wrapper.scrollTop;
  
  // 计算鼠标在图片中的相对位置
  const relativeX = (scrollLeft + mouseX) / imageScale.value;
  const relativeY = (scrollTop + mouseY) / imageScale.value;
  
  const oldScale = imageScale.value;
  const delta = event.deltaY > 0 ? 0.9 : 1.1;
  imageScale.value = Math.max(0.1, Math.min(5, imageScale.value * delta));
  
  // 缩放完成后调整滚动位置,保持鼠标位置不变
  nextTick(() => {
    if (wrapper) {
      const newScrollLeft = relativeX * imageScale.value - mouseX;
      const newScrollTop = relativeY * imageScale.value - mouseY;
      
      wrapper.scrollLeft = Math.max(0, newScrollLeft);
      wrapper.scrollTop = Math.max(0, newScrollTop);
    }
  });
};



// 图片加载完成时重置状态
const handleImageLoad = () => {
  resetZoom();
};

// 全屏切换
const toggleFullscreen = () => {
  if (!document.fullscreenElement) {
    previewContainer.value?.requestFullscreen?.();
    isFullscreen.value = true;
  } else {
    document.exitFullscreen?.();
    isFullscreen.value = false;
  }
};

// 关闭预览
const closePreview = () => {
  if (isFullscreen.value) {
    document.exitFullscreen?.();
    isFullscreen.value = false;
  }
  imagePreviewVisible.value = false;
  resetZoom();
};

// 监听全屏状态变化
document.addEventListener('fullscreenchange', () => {
  isFullscreen.value = !!document.fullscreenElement;
});




</script>

<style lang="scss" scoped>
.box-card {
  margin: 10px 0px;
  min-height: 100vh;
  background: #fff;
  border-radius: 5px;
  padding: 0px 2px 0px 2px;
  box-shadow: 0 0 12px rgba(0, 0, 0, 0.12);
}

.performance-setting-container {
  padding: 20px;
}

.form-title {
  display: flex;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 20px;
  color: #333;
  .form-status {
    padding: 0 10px;
    background-color: #f2f2f2;
    margin-left: 10px;
    border-radius: 5px;
    font-weight: 100;
    color: white;

    &.in-review {
      background-color: #d4b86a; /* 柔和黄色 */
    }

    &.approved {
      background-color: #85ce61; /* 柔和绿色 */
    }

    &.pending {
      background-color: #a6a9ad; /* 柔和灰色 */
    }

    &.rejected {
      background-color: #f78989; /* 柔和红色 */
    }
  }
}

/* 流程样式 */
.process-section {
  border-radius: 4px;
  padding: 0 15px;
}

.process-steps {
  display: flex;
  align-items: center;
  margin-top: 15px;
}

.step {
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  flex: 1;
}

.step-icon {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background-color: #f0f0f0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 8px;
}

.step.completed .step-icon {
  background-color: #67c23a;
  color: white;
}

.step.current .step-icon {
  background-color: #409eff;
  color: white;
}

.step.active .step-icon {
  background-color: #67c23a;
  color: white;
}

.step.rejected .step-icon {
  background-color: #f56c6c;
  color: white;
}

.step.disabled .step-icon {
  background-color: #e4e7ed;
  color: #c0c4cc;
}

.step.disabled .step-info {
  color: #c0c4cc;
}

.step-line {
  height: 2px;
  background-color: #f0f0f0;
  flex: 1;
}

.step-line.completed {
  background-color: #67c23a;
}

.step-line.disabled {
  background-color: #e4e7ed;
}

.step-info {
  text-align: center;
  font-size: 12px;
}

.step-name {
  font-weight: bold;
  margin-bottom: 4px;
  font-size: 18px;
}

.step-user,
.step-time {
  color: #666;
}

/* 信息区域样式 */
.info-section {
  // margin-bottom: 30px;
  // border: 1px solid #ebeef5;
  border-radius: 4px;
  padding: 15px;
}

.history-section {
  border-radius: 4px;
  padding: 0 15px;
}

.info-section h3 {
  margin-top: 0;
  margin-bottom: 15px;
  font-size: 16px;
  color: #333;
}

.info-item {
  margin-bottom: 15px;
  display: flex;
  align-items: center;
}

.label {
  min-width: 155px;
  // font-weight: bold;
  color: #606266;
  text-align: right;
}

.value {
  color: #333;
  margin-left: 10px;
}

.clickable-value {
  color: #409eff;
  cursor: pointer;
  text-decoration: none;
}

.proof-item {
  margin-bottom: 20px;
}

.upload-container {
  margin-top: 10px;
}

.image-uploader {
  border: 1px dashed #d9d9d9;
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  width: 148px;
  height: 148px;
  display: inline-block;
}

.image-uploader:hover {
  border-color: #409eff;
}

.upload-image {
  width: 146px;
  height: 146px;
  display: block;
  object-fit: cover;
}

.upload-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: #8c939d;
}

.uploaded-images {
  display: flex;
  flex-wrap: wrap;
  margin-top: 10px;
}

.image-item {
  position: relative;
  width: 80px;
  height: 80px;
  border: 2px solid #e4e7ed;
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);


  &.active {
    border: 2px solid #409eff;
    transform: scale(1.05);
    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
  }

  &:hover {
    border-color: #409eff;
    transform: scale(1.05);
    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
  }

  &:hover .image-actions {
    opacity: 1;
  }

}

.thumbnail {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.image-actions {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  opacity: 0;
  transition: opacity 0.3s;
  gap: 5px;
}

.image-item:hover .image-actions {
  opacity: 1;
}

.image-actions .el-button {
  color: white;
}

/* 金额部分样式 */
.amount-section {
  margin-bottom: 30px;
}

.amount-item {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}

.modify-link {
  justify-content: center;
}

.remaining {
  justify-content: flex-end;
  color: #3c8cbb;
  margin-left: 10px;
  cursor: pointer;
}

.readonly-textarea {
  min-height: 80px;
  padding: 8px 12px;
  border: 1px solid #ebeef5;
  border-radius: 4px;
  background-color: #f8f8f8;
  color: #606266;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

.quota {
  color: #3c8cbb;
  // font-weight: bold;
  margin-left: 10px;
  cursor: pointer;
}

/* 按钮样式 */
.action-buttons {
  display: flex;
  justify-content: center;
  gap: 20px;
  margin-top: 20px;
}

.el-button--primary.is-link {
  color: #409eff;
}

.el-button--primary.is-link:hover {
  color: #66b1ff;
}

.two-col-container {
  display: flex;
  flex-wrap: wrap;
  gap: 16px; /* 子项之间的间距 */
  // width: 100%;
  width: 60%;
  margin-left: 10px;
}

.item {
  width: calc(50% - 8px); /* 50% 减去一半 gap 实现两列等宽 */
  border: 1px dashed #ccc;
  box-sizing: border-box;
  padding: 12px;
  // text-align: center;
  border-radius: 6px;
  background-color: #fff;
}

.el-divider--horizontal {
  margin: 10px 0;
}

/* 拒绝弹窗样式 */
.reject-dialog-content {
  padding: 0 20px;
}

.image-uploader ::v-deep .el-upload {
  width: 148px;
  height: 148px;
  line-height: 146px;
}

.table-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.add-notes-btn {
  padding: 6px 12px;
  font-size: 12px;
}

.notes-modal ::v-deep .el-dialog__header {
  padding: 16px !important;
}

/* 自定义对话框标题头样式 */
.custom-dialog-header {
  padding: 10px 0 0 0;
  font-size: 18px;
  font-weight: 500;
  color: #303133;
}

.dialog-title {
  line-height: 24px;
}

/* 保持原有样式,只添加必要的图片展示样式 */
.image-display-area {
  width: 100%;
  min-height: 100px;
}

.images-container {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-start;
  align-items: flex-start;
}

.upload-image {
  width: 80px;
  height: 80px;
  object-fit: cover;
  border: 2px solid #e4e7ed;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.upload-image-2{
  width: 100%;
  height: 100%;
}

.upload-image:hover {
  border-color: #409eff;
  transform: scale(1.05);
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}

/* Proof标题样式 - 超过两行显示省略号 */
.proof-item .label {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.4;
  max-height: 2.8em; /* 2行的高度 */
  word-break: break-word;
  min-width: auto;
}

.custom-link {
  color: #1890ff;
  text-decoration: underline;
  display: inline-block;
  max-width: 100%;
  word-break: break-all;
  overflow-wrap: break-word;
  white-space: normal;
  line-height: 1.4;
}

/* Comments 图片样式 */
.comments-content ::v-deep .comment-image {
  max-width: 120px !important;
  max-height: 80px !important;
  width: auto;
  height: auto;
  object-fit: cover;
  border-radius: 6px;
  border: 2px solid #e4e7ed;
  margin: 5px;
  transition: all 0.3s ease;
  cursor: pointer;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.comments-content ::v-deep .comment-image:hover {
  border-color: #409eff;
  transform: scale(1.05);
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}

.comments-content ::v-deep .comment-image:active {
  transform: scale(0.98);
}

/* History Notes 图片预览图标样式 */
.history-message ::v-deep .image-preview-icon {
  display: inline-block;
  margin-right: 8px;
  font-size: 16px;
  color: #409eff;
  cursor: pointer;
  vertical-align: middle;
  padding: 2px 4px;
  border-radius: 3px;
  background-color: #f0f9ff;
  border: 1px solid #409eff;
  transition: all 0.3s ease;
}

.history-message ::v-deep .image-preview-icon:hover {
  background-color: #409eff;
  color: white;
  transform: scale(1.1);
}

.history-message ::v-deep img {
  margin-left: 5px;
  max-width: 100%;
  height: auto;
}

/* History Notes 表格中图片固定尺寸 */
.history-message ::v-deep img {
  max-width: 150px !important;
  max-height: 100px !important;
  width: auto;
  height: auto;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  cursor: pointer;
}

.history-message-2 ::v-deep img {
  max-width: 50px !important;
  max-height: 50px !important;
  width: auto;
  height: auto;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid #dcdfe6;
  cursor: pointer;
}

.history-message ::v-deep img:hover {
  opacity: 0.8;
  border-color: #409eff;
}

/* 图片预览按钮样式 */
.image-preview-buttons {
  margin-top: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.preview-btn {
  display: flex;
  align-items: center;
  gap: 4px;
}

.preview-btn .el-icon {
  font-size: 14px;
}

/* 图片预览图标样式 */
.preview-icon {
  font-size: 24px;
  color: #409eff;
  cursor: pointer;
  margin-right: 8px;
  padding: 4px;
  border-radius: 4px;
  transition: all 0.3s ease;
}

.preview-icon:hover {
  background-color: #ecf5ff;
  color: #337ecc;
  transform: scale(1.1);
}

/* 图片预览弹窗样式 */
::v-deep(.image-preview-dialog) {
  width: 80vw !important;
  max-width: 1200px !important;
  min-width: 800px !important;
}

::v-deep(.image-preview-dialog .el-message-box__content) {
  padding: 20px !important;
}

::v-deep(.image-preview-dialog .el-message-box__message) {
  padding: 0 !important;
}

/* 图片预览容器样式 */
.image-preview-container {
  display: flex;
  flex-direction: column;
  height: 70vh;
  user-select: none;
}

.image-controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid #ebeef5;
  margin-bottom: 10px;
  flex-shrink: 0;
}

.zoom-indicator {
  font-size: 14px;
  color: #606266;
  font-weight: 500;
}

.image-wrapper {
  flex: 1;
  overflow: auto;
  position: relative;
  background-color: #f5f7fa;
  border-radius: 8px;
  /* 优化滚动性能 */
  scroll-behavior: smooth;
}

.preview-image {
  display: block;
  margin: 0 auto;
  border-radius: 8px;
  transform-origin: top center;
  user-select: none;
  /* 移除最大尺寸限制,确保图片可以正常缩放 */
  max-width: none;
  max-height: none;
  width: auto;
  height: auto;
  /* 当图片小于容器时居中显示,当图片大于容器时从顶部开始显示 */
  vertical-align: top;
}

/* 全屏状态下的样式调整 */
.image-preview-container:fullscreen {
  height: 100vh;
  background-color: #000;
  padding: 20px;
}

.image-preview-container:fullscreen .image-wrapper {
  background-color: #000;
}

.image-preview-container:fullscreen .image-controls {
  background-color: rgba(0, 0, 0, 0.8);
  color: white;
  border-radius: 8px;
  padding: 15px;
  border-bottom: 1px solid #666;
}

.image-preview-container:fullscreen .zoom-indicator {
  color: white;
}


.custom-divider{
  border-color: #f0f0f0;
}

/* 编辑模式相关样式 */
.upload-area {
  position: relative;
  border: 2px dashed transparent;
  border-radius: 8px;
  padding: 4px;
  transition: all 0.3s ease;
  cursor: pointer;
}

.upload-area:hover {
  border-color: #409eff;
  background-color: rgba(64, 158, 255, 0.05);
}

.upload-area.active-upload {
  border-color: #409eff;
  background-color: rgba(64, 158, 255, 0.1);
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}

.uploaded-images {
  display: flex;
  flex-wrap: wrap;
  margin-top: 10px;
  gap: 8px;
  justify-content: flex-start;
  align-items: flex-start;
}

.image-item.active {
  border: 2px solid #409eff;
  transform: scale(1.05);
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}


</style>

核心部分:

<!-- 图片预览弹窗 -->
  <el-dialog
    v-model="imagePreviewVisible"
    title="Preview Picture"
    width="90%"
    :style="{ maxWidth: '1400px', minWidth: '800px' }"
    append-to-body
  >
    <div class="image-preview-container" ref="previewContainer">
      <div class="image-controls">
        <el-button-group>
          <el-button @click="zoomIn"  size="small" :icon="ZoomIn" title="Zoom In">Zoom In</el-button>
          <el-button @click="zoomOut" size="small" :icon="ZoomOut" title="Zoom Out">Zoom Out</el-button>
          <el-button @click="resetZoom" size="small" :icon="Refresh" title="Reset">Reset</el-button>
          <el-button @click="toggleFullscreen" size="small" :icon="FullScreen" title="Fullscreen">{{ isFullscreen ? 'Exit Fullscreen' : 'Fullscreen' }}</el-button>
        </el-button-group>
        <span class="zoom-indicator">{{ Math.round(imageScale * 100) }}%</span>
      </div>
      <div 
        class="image-wrapper" 
        @wheel="handleWheel" 
        ref="imageWrapper"
      >
        <img
          :src="previewImageUrl"
          alt="预览图片"
          class="preview-image"
          :style="imageStyle"
          @error="handleImageError"
          @load="handleImageLoad"
          draggable="false"
        />
      </div>
    </div>
    <template #footer>
      <el-button @click="closePreview">Closed</el-button>
    </template>
  </el-dialog>