zhangwencui
3 天以前 f670b79094c95d791fc43c8fc8b5858ebf8426dc
src/pages/cooperativeOffice/collaborativeApproval/approve.vue
@@ -49,58 +49,14 @@
        </view>
      </view>
    </view>
    <!-- 审批流程 -->
    <view class="approval-process">
      <view class="process-header">
        <text class="process-title">审批流程</text>
      </view>
      <view class="process-steps">
        <view v-for="(step, index) in approvalSteps"
              :key="index"
              class="process-step"
              :class="{
            'completed': step.status === 'completed',
            'current': step.status === 'current',
            'pending': step.status === 'pending',
            'rejected': step.status === 'rejected'
          }">
          <view class="step-indicator">
            <view class="step-dot">
              <text v-if="step.status === 'completed'"
                    class="step-icon">✓</text>
              <text v-else-if="step.status === 'rejected'"
                    class="step-icon">✗</text>
              <text v-else
                    class="step-number">{{ index + 1 }}</text>
            </view>
            <view v-if="index < approvalSteps.length - 1"
                  class="step-line"></view>
          </view>
          <view class="step-content">
            <view class="step-info">
              <text class="step-title">{{ step.title }}</text>
              <text class="step-approver">{{ step.approverName }}</text>
              <text v-if="step.approveTime"
                    class="step-time">{{ step.approveTime }}</text>
            </view>
            <view v-if="step.opinion"
                  class="step-opinion">
              <text class="opinion-label">审批意见:</text>
              <text class="opinion-content">{{ step.opinion }}</text>
            </view>
            <!-- 签名展示 -->
            <view v-if="step.urlTem"
                  class="step-opinion"
                  style="margin-top:8px;">
              <text class="opinion-label">签名:</text>
              <image :src="step.urlTem"
                     mode="widthFix"
                     style="width:180px;border-radius:6px;border:1px solid #eee;" />
            </view>
          </view>
        </view>
      </view>
    <!-- 采购/报价/发货审批详情 -->
    <view v-if="isBusinessApproval"
          class="business-detail-wrap">
      <ApprovalBusinessDetail :approve-type="approvalData.approveType || approveType"
                              :business-no="approvalData.approveReason" />
    </view>
    <!-- 审批流程 -->
    <ApproveFlowSteps :steps="approvalSteps" />
    <!-- 审核意见输入 -->
    <view v-if="canApprove"
          class="approval-input">
@@ -134,6 +90,9 @@
    updateApproveNode,
  } from "@/api/collaborativeApproval/approvalProcess";
  import useUserStore from "@/store/modules/user";
  import ApprovalBusinessDetail from "./_components/ApprovalBusinessDetail.vue";
  import ApproveFlowSteps from "./_components/ApproveFlowSteps.vue";
  import { mapApprovalSteps } from "./_utils/approveFlowSteps.js";
  const showToast = message => {
    uni.showToast({
      title: message,
@@ -147,6 +106,14 @@
  const approvalSteps = ref([]);
  const approvalOpinion = ref("");
  const approveId = ref("");
  const approveType = ref(0);
  /** 5 采购 / 6 报价 / 7 发货:需展示业务单据详情 */
  const isBusinessApproval = computed(() =>
    [5, 6, 7].includes(
      Number(approvalData.value.approveType || approveType.value)
    )
  );
  // 从详情接口字段对齐 canApprove:仅当有 isShen 的节点时可审批
  const canApprove = computed(() => {
@@ -155,6 +122,7 @@
  onMounted(() => {
    approveId.value = uni.getStorageSync("approveId");
    approveType.value = Number(uni.getStorageSync("approveType")) || 0;
    if (approveId.value) {
      loadApprovalData();
    }
@@ -170,23 +138,7 @@
      const list = Array.isArray(res.data) ? res.data : [];
      // 保存原始节点数据供提交使用
      activities.value = list;
      approvalSteps.value = list.map((it, idx) => {
        // 节点状态映射:1=通过,2=不通过,否则看是否当前(isShen),再默认为待处理
        let status = "pending";
        if (it.approveNodeStatus === 1) status = "completed";
        else if (it.approveNodeStatus === 2) status = "rejected";
        else if (it.isShen) status = "current";
        return {
          title: `第${idx + 1}步审批`,
          approverName: it.approveNodeUser || "未知用户",
          status,
          approveTime: it.approveTime || null,
          opinion: it.approveNodeReason || "",
          urlTem: it.urlTem || "",
          isShen: !!it.isShen,
        };
      });
      approvalSteps.value = mapApprovalSteps(list);
    });
  };
@@ -282,6 +234,14 @@
    overflow: hidden;
  }
  .business-detail-wrap {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
    padding: 1px 0;
  }
  .info-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
@@ -319,148 +279,6 @@
    font-size: 14px;
    color: #333;
    flex: 1;
  }
  .approval-process {
    background: #fff;
    margin: 16px;
    border-radius: 12px;
    overflow: hidden;
  }
  .process-header {
    padding: 16px;
    border-bottom: 1px solid #f0f0f0;
    background: #f8f9fa;
  }
  .process-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
  }
  .process-steps {
    padding: 20px;
  }
  .process-step {
    display: flex;
    position: relative;
    margin-bottom: 24px;
    &:last-child {
      margin-bottom: 0;
      .step-line {
        display: none;
      }
    }
  }
  .step-indicator {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-right: 16px;
  }
  .step-dot {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 600;
    position: relative;
    z-index: 2;
  }
  .process-step.completed .step-dot {
    background: #52c41a;
    color: #fff;
  }
  .process-step.current .step-dot {
    background: #1890ff;
    color: #fff;
    animation: pulse 2s infinite;
  }
  .process-step.pending .step-dot {
    background: #d9d9d9;
    color: #999;
  }
  .step-line {
    width: 2px;
    height: 40px;
    background: #d9d9d9;
    margin-top: 8px;
  }
  .process-step.completed .step-line {
    background: #52c41a;
  }
  .process-step.rejected .step-dot {
    background: #ff4d4f;
    color: #fff;
  }
  .process-step.rejected .step-line {
    background: #ff4d4f;
  }
  .step-content {
    flex: 1;
    padding-top: 4px;
  }
  .step-info {
    margin-bottom: 8px;
  }
  .step-title {
    font-size: 16px;
    font-weight: 600;
    color: #333;
    display: block;
    margin-bottom: 4px;
  }
  .step-approver {
    font-size: 14px;
    color: #666;
    display: block;
    margin-bottom: 4px;
  }
  .step-time {
    font-size: 12px;
    color: #999;
    display: block;
  }
  .step-opinion {
    background: #f8f9fa;
    padding: 12px;
    border-radius: 8px;
    border-left: 4px solid #52c41a;
  }
  .opinion-label {
    font-size: 12px;
    color: #666;
    display: block;
    margin-bottom: 4px;
  }
  .opinion-content {
    font-size: 14px;
    color: #333;
    line-height: 1.5;
  }
  .approval-input {
@@ -517,17 +335,6 @@
    border-radius: 6px;
  }
  @keyframes pulse {
    0% {
      box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7);
    }
    70% {
      box-shadow: 0 0 0 10px rgba(24, 144, 255, 0);
    }
    100% {
      box-shadow: 0 0 0 0 rgba(24, 144, 255, 0);
    }
  }
  .signature-section {
    background: #fff;
    padding: 12px 16px 16px;