gaoluyang
2 天以前 025e46e11cb2962fd7692adfa401333758cc779b
src/pages/cooperativeOffice/collaborativeApproval/detail.vue
@@ -1,359 +1,384 @@
<template>
  <view class="account-detail">
    <!-- 顶部标题栏 -->
    <view class="header">
      <up-icon name="arrow-left" size="20" color="#333" @click="goBack" />
      <text class="title">审批流程</text>
    </view>
    <PageHeader title="审批流程" @back="goBack" />
    <!-- 表单区域 -->
    <view class="form-section">
      <van-form ref="formRef" @submit="submitForm" :rules="rules" input-align="right">
        <van-cell-group inset style="height:auto">
          <van-field
            v-model="taxPrice"
            name="taxPrice"
            label="姓名"
            placeholder="请输入姓名"
            :rules="[{ required: true, message: '姓名不能为空' }]"
            required
            readonly
          />
          <van-field
            v-model="result"
            readonly
            name="picker"
            label="申请部门"
            placeholder="请选择申请部门"
            :rules="[{ required: true, message: '请选择申请部门' }]"
            @click="showPicker = true"
            required
          />
          <van-popup
            v-model:show="showPicker"
            destroy-on-close
            position="bottom"
          >
            <van-picker
              :columns="columns"
              :model-value="pickerValue"
              @confirm="onConfirm"
              @cancel="showPicker = false"
            />
          </van-popup>
          <van-field
            v-model="message"
            name="message"
            rows="1"
            autosize
            label="申请事由"
            type="textarea"
            placeholder="请输入申请事由"
            height="100"
            :rules="[{ required: true, message: '申请事由不能为空' }]"
            required
          />
        </van-cell-group>
      </van-form>
    </view>
    <u-form ref="formRef" @submit="submitForm" :rules="rules" :model="form" label-width="140rpx">
      <u-form-item prop="approveReason" label="申请事由" required>
        <u-input
          v-model="form.approveReason"
          type="textarea"
          rows="2"
          auto-height
          maxlength="200"
          placeholder="请输入申请事由"
          show-word-limit
        />
      </u-form-item>
      <u-form-item prop="approveDeptName" label="申请部门" required>
        <u-input
          v-model="form.approveDeptName"
          readonly
          placeholder="请选择申请部门"
          @click="showPicker = true"
        />
        <template #right>
               <up-icon
                  name="arrow-right"
                  @click="showPicker = true"
               ></up-icon>
            </template>
      </u-form-item>
      <u-form-item prop="approveUser" label="申请人" required>
        <u-input
          v-model="form.approveUserName"
          placeholder="请输入申请人"
          readonly
        />
      </u-form-item>
      <u-form-item prop="approveTime" label="申请日期" required>
        <u-input
          v-model="form.approveTime"
          placeholder="请选择"
          readonly
          @click="showDatePicker"
        />
      </u-form-item>
    </u-form>
    <!-- 选择器弹窗 -->
    <up-action-sheet
      :show="showPicker"
      :actions="productOptions"
      title="选择部门"
      @select="onConfirm"
      @close="showPicker = false"
    />
    <!-- 日期选择器 -->
    <u-popup v-model="showDate" mode="bottom">
      <u-datetime-picker
        v-model="currentDate"
        title="选择日期"
        mode="date"
        @confirm="onDateConfirm"
        @cancel="showDate = false"
      />
    </u-popup>
    <!-- 审核流程区域 -->
    <view class="approval-process">
      <view class="approval-header">
        <text class="approval-title">审核流程</text>
        <text class="approval-desc">已由管理员预设不可修改</text>
        <text class="approval-desc">每个步骤只能选择一个审批人</text>
      </view>
      <view class="approval-steps">
        <view v-for="(step, stepIndex) in approvalSteps" :key="stepIndex" class="approval-step">
        <view v-for="(step, stepIndex) in approverNodes" :key="stepIndex" class="approval-step">
          <view class="step-dot"></view>
          <view class="step-title">
            <text>审批人</text>
          </view>
          <view class="approvers-container">
            <view v-for="(approver, approverIndex) in step.approvers" :key="approverIndex" class="approver-item">
              <view class="approver-avatar"></view>
              <text class="approver-name">{{ approver.name }}</text>
              <view class="delete-approver-btn" @click="removeApprover(stepIndex, approverIndex)">×</view>
          <view class="approver-container">
            <view v-if="step.nickName" class="approver-item">
              <view class="approver-avatar">
                <text class="avatar-text">{{ step.nickName.charAt(0) }}</text>
                <view class="status-dot"></view>
              </view>
              <view class="approver-info">
                <text class="approver-name">{{ step.nickName }}</text>
              </view>
              <view class="delete-approver-btn" @click="removeApprover(stepIndex)">×</view>
            </view>
            <view class="add-approver-btn" @click="addApprover(stepIndex)">+
            <view v-else class="add-approver-btn" @click="addApprover(stepIndex)">
              <view class="add-circle">+</view>
              <text class="add-label">选择审批人</text>
            </view>
          </view>
          <view class="step-line" v-if="stepIndex < approvalSteps.length - 1"></view>
          <view class="delete-step-btn" @click="removeApprovalStep(stepIndex)">删除节点</view>
          <view class="step-line" v-if="stepIndex < approverNodes.length - 1"></view>
          <view class="delete-step-btn" v-if="approverNodes.length > 1" @click="removeApprovalStep(stepIndex)">删除节点</view>
        </view>
      </view>
      <view class="add-step-btn" @click="addApprovalStep">
        <text>新增节点审核人</text>
      <view class="add-step-btn">
         <u-button icon="plus" plain type="primary" style="width: 100%" @click="addApprovalStep">新增节点</u-button>
      </view>
    </view>
    <!-- 底部按钮 -->
    <view class="footer-btns">
      <van-button class="cancel-btn" @click="goBack">取消</van-button>
      <van-button class="save-btn" @click="submitForm">保存</van-button>
      <u-button class="cancel-btn" @click="goBack">取消</u-button>
      <u-button class="save-btn" @click="submitForm">保存</u-button>
    </view>
  </view>
</template>
<script>
import { ref, onMounted } from "vue";
<script setup>
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import PageHeader from "@/components/PageHeader.vue";
import useUserStore from "@/store/modules/user";
import { formatDateToYMD } from '@/utils/ruoyi'
import {getDept, approveProcessGetInfo, approveProcessAdd, approveProcessUpdate} from "@/api/collaborativeApproval/approvalProcess";
const showToast = (message) => {
   uni.showToast({
      title: message,
      icon: 'none'
   })
}
import {userListNoPageByTenantId} from "@/api/system/user";
export default {
  setup() {
    const rules = ref({
  taxPrice: {
    rules: [{ required: true, errorMessage: '姓名不能为空' }]
  },
  result: {
    rules: [{ required: true, errorMessage: '请选择申请部门' }]
  },
  message: {
    rules: [{ required: true, errorMessage: '申请事由不能为空' }]
  },
const data = reactive({
   form: {
      approveTime: "",
      approveId: "",
      approveUser: "",
      approveUserName: "",
      approveDeptName: "",
      approveDeptId: "",
      approveReason: "",
      checkResult: "",
      tempFileIds: [],
      approverList: [] // 新增字段,存储所有节点的审批人id
   },
   rules: {
      approveTime: [{ required: false, message: "请输入", trigger: "change" },],
      approveId: [{ required: false, message: "请输入", trigger: "blur" }],
      approveDeptId: [{ required: true, message: "请输入", trigger: "blur" }],
      approveReason: [{ required: true, message: "请输入", trigger: "blur" }],
      checkResult: [{ required: false, message: "请输入", trigger: "blur" }],
   },
});
    const result = ref("");
    const pickerValue = ref([]);
    const showPicker = ref(false);
    const columns = ref([]);
    onMounted(async () => {
      try {
        // 替换为实际接口地址
        // const response = await axios.get('/api/getDepartments');
        columns.value = [
          {
            text: "杭州",
            value: "Hangzhou",
          },
          {
            text: "宁波",
            value: "Ningbo",
          },
          {
            text: "温州",
            value: "Wenzhou",
          },
          {
            text: "绍兴",
            value: "Shaoxing",
          },
          {
            text: "湖州",
            value: "Huzhou",
          },
        ];
      } catch (error) {
        console.error("获取部门数据失败:", error);
      }
    });
    const onConfirm = ({ selectedValues, selectedOptions }) => {
      result.value = selectedOptions[0]?.text;
      pickerValue.value = selectedValues;
      showPicker.value = false;
    };
    const taxPrice = ref("");
    const contractAmount = ref("");
    const approvalSteps = ref([
      { approvers: [{ name: '卢小敏' }, { name: '卢小敏' }] },
      { approvers: [{ name: '卢小敏' }] },
      { approvers: [{ name: '卢小敏' }] },
      { approvers: [{ name: '卢小敏' }] }
    ]);
const { form, rules } = toRefs(data);
const result = ref("");
const showPicker = ref(false);
const productOptions = ref([]);
const operationType = ref("");
const currentApproveStatus = ref("");
const approverNodes = ref([]);
const userList = ref([]);
const formRef = ref(null);
const message = ref("");
const showDate = ref(false)
const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
const userStore = useUserStore()
    const goBack = () => {
       uni.navigateBack();
    };
    const formRef = ref(null);
    const submitForm = () => {
      formRef.value.validate().then(() => {
        // 表单校验通过,可以提交数据
        console.log("表单数据:", {
          taxPrice: taxPrice.value,
          department: result.value,
          message: message.value,
          approvalSteps: approvalSteps.value
        });
        uni.showToast({
          title: "保存成功",
          icon: "success",
        });
      }).catch((error) => {
        console.error("表单校验失败:", error);
        // 显示具体的错误信息
        if (error.length > 0) {
          const firstError = error[0];
          uni.showToast({
            title: firstError.message || '表单校验失败',
            icon: 'none'
          });
        } else {
          uni.showToast({
            title: '表单校验失败,请检查必填项',
            icon: 'none'
          });
        }
      });
    };
    const message = ref("");
    const addApprover = (stepIndex) => {
      // 在指定审批步骤添加新的审批人
      approvalSteps.value[stepIndex].approvers.push({ name: '卢小敏' });
    };
    const addApprovalStep = () => {
      // 添加新的审批步骤
      approvalSteps.value.push({ approvers: [{ name: '卢小敏' }] });
    };
    const removeApprover = (stepIndex, approverIndex) => {
      // 确保每个步骤至少保留一个审批人
      if (approvalSteps.value[stepIndex].approvers.length > 1) {
        approvalSteps.value[stepIndex].approvers.splice(approverIndex, 1);
      } else {
        uni.showToast({
          title: '每个步骤至少需要一个审批人',
          icon: 'none'
        });
      }
    };
    const removeApprovalStep = (stepIndex) => {
      // 确保至少保留一个审批步骤
      if (approvalSteps.value.length > 1) {
        approvalSteps.value.splice(stepIndex, 1);
      } else {
        uni.showToast({
          title: '至少需要一个审批步骤',
          icon: 'none'
        });
      }
    };
    return {
      rules,
      removeApprovalStep,
    removeApprover,
      result,
      pickerValue,
      columns,
      onConfirm,
      showPicker,
      taxPrice,
      contractAmount,
      goBack,
      submitForm,
      approvalSteps,
      addApprover,
      addApprovalStep,
      formRef,
      message
    };
  },
const getProductOptions = () => {
   getDept().then((res) => {
      productOptions.value = res.data.map(item => ({
         value: item.deptId,
         name: item.deptName
      }))
   });
};
const fileList = ref([]);
let nextApproverId = 2;
onMounted(async () => {
  try {
      getProductOptions()
      userListNoPageByTenantId().then((res) => {
         userList.value = res.data
      })
      form.value.approveUser = userStore.id
      form.value.approveUserName = userStore.nickName
      form.value.approveTime = getCurrentDate();
      // 获取URL参数
      const pages = getCurrentPages();
      const currentPage = pages[pages.length - 1];
      const options = currentPage && currentPage.options ? currentPage.options : {};
      operationType.value = options.operationType || 'add';
      // 如果是编辑模式,从本地存储获取数据
      if (operationType.value === 'edit') {
         const storedData = uni.getStorageSync('invoiceLedgerEditRow');
         if (storedData) {
            const row = JSON.parse(storedData);
            fileList.value = row.commonFileList || [];
            form.value.tempFileIds = fileList.value.map(file => file.id);
            currentApproveStatus.value = row.approveStatus;
            approveProcessGetInfo({id: row.approveId, approveReason: '1'}).then(res => {
               form.value = {...res.data};
               // 反显审批人
               if (res.data && res.data.approveUserIds) {
                  const userIds = res.data.approveUserIds.split(',');
                  approverNodes.value = userIds.map((userId, idx) => {
                     const userIdNum = parseInt(userId.trim());
                     // 从userList中找到对应的用户信息
                     const userInfo = userList.value.find(user => user.userId === userIdNum);
                     return {
                        id: idx + 1,
                        userId: userIdNum,
                        nickName: userInfo ? userInfo.nickName : null
                     };
                  });
                  nextApproverId = userIds.length + 1;
               } else {
                  // 新增模式,初始化一个空的审批节点
                  approverNodes.value = [{ id: 1, userId: null, nickName: null }];
                  nextApproverId = 2;
               }
            });
         }
      } else {
         // 新增模式,初始化一个空的审批节点
         approverNodes.value = [{ id: 1, userId: null }];
      }
    // 监听联系人选择事件
    uni.$on('selectContact', handleSelectContact);
  } catch (error) {
    console.error("获取部门数据失败:", error);
  }
});
onUnmounted(() => {
  // 移除事件监听
  uni.$off('selectContact', handleSelectContact);
});
const onConfirm = (item) => {
  // 设置选中的部门
  form.value.approveDeptName = item.name;
  // 确保设置的是字符串类型的部门ID
  form.value.approveDeptId = String(item.value || '');
  console.log('部门选择后的值:', {
    approveDeptId: form.value.approveDeptId,
    approveDeptName: form.value.approveDeptName
  });
  showPicker.value = false;
};
const goBack = () => {
   // 清除本地存储的数据
   uni.removeStorageSync('invoiceLedgerEditRow');
  uni.navigateBack();
};
const submitForm = () => {
  // 检查每个审批步骤是否都有审批人
  const hasEmptyStep = approverNodes.value.some(step => !step.nickName);
  if (hasEmptyStep) {
      showToast('请为每个审批步骤选择审批人');
    return;
  }
  // 手动检查必填字段,防止因数据类型问题导致的校验失败
  if (!form.value.approveReason || !form.value.approveReason.trim()) {
    showToast('请输入申请事由');
    return;
  }
  if (!form.value.approveDeptId || String(form.value.approveDeptId).trim() === '') {
    showToast('请选择申请部门');
    return;
  }
  if (!form.value.approveTime) {
    showToast('请选择申请日期');
    return;
  }
  formRef.value.validate().then((valid) => {
    if (valid) {
      // 表单校验通过,可以提交数据
     // 收集所有节点的审批人id
     console.log('approverNodes---', approverNodes.value)
     form.value.approveUserIds = approverNodes.value.map(node => node.userId).join(',')
     form.value.approveType = 0
     if (operationType.value === "add" || currentApproveStatus.value == 3) {
      approveProcessAdd(form.value).then(res => {
        showToast("提交成功");
        goBack()
      })
     } else {
      approveProcessUpdate(form.value).then(res => {
        showToast("提交成功");
        goBack()
      })
     }
    }
  }).catch((error) => {
    console.error("表单校验失败:", error);
    // 尝试获取具体的错误字段
    if (error && error.errors) {
      const firstError = error.errors[0];
      if (firstError) {
        uni.showToast({
          title: firstError.message || '表单校验失败,请检查必填项',
          icon: 'none'
        });
        return;
      }
    }
    // 显示通用错误信息
    uni.showToast({
      title: '表单校验失败,请检查必填项',
      icon: 'none'
    });
  });
};
// 处理联系人选择结果
const handleSelectContact = (data) => {
  const { stepIndex, contact } = data;
  // 将选中的联系人设置为对应审批步骤的审批人
  approverNodes.value[stepIndex].userId = contact.userId;
  approverNodes.value[stepIndex].nickName = contact.nickName;
};
const addApprover = (stepIndex) => {
  // 跳转到联系人选择页面
  uni.navigateTo({
    url: `/pages/cooperativeOffice/collaborativeApproval/contactSelect?stepIndex=${stepIndex}`
  });
};
const addApprovalStep = () => {
  // 添加新的审批步骤
  approverNodes.value.push({ userId: null, nickName: null });
};
const removeApprover = (stepIndex) => {
  // 移除审批人
  approverNodes.value[stepIndex].userId = null;
  approverNodes.value[stepIndex].nickName = null;
};
const removeApprovalStep = (stepIndex) => {
  // 确保至少保留一个审批步骤
  if (approverNodes.value.length > 1) {
    approverNodes.value.splice(stepIndex, 1);
  } else {
    uni.showToast({
      title: '至少需要一个审批步骤',
      icon: 'none'
    });
  }
};
// 显示日期选择器
const showDatePicker = () => {
   showDate.value = true
}
// 确认日期选择
const onDateConfirm = (e) => {
  form.value.approveTime = formatDateToYMD(e.value)
   currentDate.value = formatDateToYMD(e.value)
   showDate.value = false;
}
// 获取当前日期并格式化为 YYYY-MM-DD
function getCurrentDate() {
   const today = new Date();
   const year = today.getFullYear();
   const month = String(today.getMonth() + 1).padStart(2, "0"); // 月份从0开始
   const day = String(today.getDate()).padStart(2, "0");
   return `${year}-${month}-${day}`;
}
</script>
<style scoped lang="scss">
.account-detail {
  min-height: 100vh;
  background: #f8f9fa;
  padding-bottom: 80px;
}
.header {
  display: flex;
  align-items: center;
  background: #fff;
  padding: 16px 20px;
  border-bottom: 1px solid #f0f0f0;
  position: sticky;
  top: 0;
  z-index: 100;
}
.title {
  flex: 1;
  text-align: center;
  font-size: 18px;
  font-weight: 600;
  color: #333;
}
.form-section {
  margin-top: 16px;
}
.van-field {
  height: 56px;
  line-height: 36px;
}
.product-section {
  background: #fff;
  margin: 16px;
  border-radius: 16px;
  padding: 20px 16px 8px 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.section-title {
  font-size: 16px;
  font-weight: 600;
  color: #333;
}
.add-btn {
  background: #2979ff;
  color: #fff;
  border-radius: 8px;
  padding: 4px 16px;
  font-size: 14px;
}
.product-card {
  background: #f8f9fa;
  border-radius: 12px;
  padding: 12px;
  margin-bottom: 16px;
  box-shadow: 0 1px 4px rgba(41, 121, 255, 0.06);
  position: relative;
}
.product-row {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
}
.product-label {
  min-width: 60px;
  color: #888;
  font-size: 13px;
}
.del-row {
  justify-content: flex-end;
}
.del-btn {
  background: #ff4d4f;
  color: #fff;
  border-radius: 8px;
  padding: 4px 16px;
  font-size: 13px;
  margin-top: 4px;
}
@import '@/static/scss/form-common.scss';
.approval-process {
  background: #fff;
@@ -380,156 +405,347 @@
  color: #999;
}
/* 样式增强为“简洁小圆圈风格” */
.approval-steps {
  padding-left: 16px;
  padding-left: 22px;
  position: relative;
  &::before {
    content: '';
    position: absolute;
    left: 11px;
    top: 40px;
    bottom: 40px;
    width: 2px;
    background: linear-gradient(to bottom, #e6f7ff 0%, #bae7ff 50%, #91d5ff 100%);
    border-radius: 1px;
  }
}
.approval-step {
  position: relative;
  margin-bottom: 20px;
  margin-bottom: 24px;
  &::before {
    content: '';
    position: absolute;
    left: -18px;
    top: 14px; // 从 8px 调整为 14px,与文字中心对齐
    width: 12px;
    height: 12px;
    background: #fff;
    border: 3px solid #006cfb;
    border-radius: 50%;
    z-index: 2;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
}
.step-title {
   top: 12px;
  margin-bottom: 12px;
  position: relative;
   margin-left: 6px;
}
.step-title text {
  font-size: 14px;
  color: #666;
  background: #f0f0f0;
  padding: 2px 8px;
  border-radius: 4px;
}
.approvers-container {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 8px;
  padding: 4px 12px;
  border-radius: 12px;
  position: relative;
  line-height: 1.4; // 确保文字行高一致
}
.approver-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 60px;
  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
  border-radius: 16px;
  padding: 16px;
  gap: 12px;
  position: relative;
  border: 1px solid #e6f7ff;
  box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08);
  transition: all 0.3s ease;
}
.approver-avatar {
  width: 40px;
  height: 40px;
  background: #e6f7ff;
  width: 48px;
  height: 48px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-radius: 50%;
  margin-bottom: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.approver-avatar::after {
  content: '👤';
  font-size: 20px;
.avatar-text {
  color: #fff;
  font-size: 18px;
  font-weight: 600;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.approver-info {
  flex: 1;
  position: relative;
}
.approver-name {
  font-size: 12px;
  display: block;
  font-size: 16px;
  color: #333;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 2px;
  font-weight: 500;
  position: relative;
}
.approver-dept {
  font-size: 12px;
  color: #999;
  background: rgba(0, 108, 251, 0.05);
  padding: 2px 8px;
  border-radius: 8px;
  display: inline-block;
  position: relative;
  &::before {
    content: '';
    position: absolute;
    left: 4px;
    top: 50%;
    transform: translateY(-50%);
    width: 2px;
    height: 2px;
    background: #006cfb;
    border-radius: 50%;
  }
}
.delete-approver-btn {
  font-size: 12px;
  font-size: 16px;
  color: #ff4d4f;
  background: rgba(255, 77, 79, 0.1);
  width: 16px;
  height: 16px;
  background: linear-gradient(135deg, rgba(255, 77, 79, 0.1) 0%, rgba(255, 77, 79, 0.05) 100%);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 2px;
  transition: all 0.3s ease;
  position: relative;
}
.add-approver-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%);
  border: 2px dashed #006cfb;
  border-radius: 16px;
  padding: 20px;
  color: #006cfb;
  font-size: 14px;
  position: relative;
  transition: all 0.3s ease;
  &::before {
    content: '';
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 32px;
    height: 32px;
    border: 2px solid #006cfb;
    border-radius: 50%;
    opacity: 0;
    transition: all 0.3s ease;
  }
}
.delete-step-btn {
  margin-top: 8px;
  color: #ff4d4f;
  font-size: 12px;
  background: rgba(255, 77, 79, 0.1);
  padding: 2px 8px;
  border-radius: 4px;
  background: linear-gradient(135deg, rgba(255, 77, 79, 0.1) 0%, rgba(255, 77, 79, 0.05) 100%);
  padding: 6px 12px;
  border-radius: 12px;
  display: inline-block;
  position: relative;
  transition: all 0.3s ease;
  &::before {
    content: '';
    position: absolute;
    left: 6px;
    top: 50%;
    transform: translateY(-50%);
    width: 4px;
    height: 4px;
    background: #ff4d4f;
    border-radius: 50%;
  }
.add-approver-btn {
  width: 40px;
  height: 40px;
  border: 1px dashed #ccc;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  color: #999;
  margin-top: 8px;
}
.step-line {
  position: absolute;
  left: 20px;
  top: 100%;
  width: 1px;
  height: 30px;
  background: #e0e0e0;
  display: none; // 隐藏原来的线条,使用伪元素代替
}
.add-step-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 16px;
  color: #006cfb;
  font-size: 14px;
  padding: 8px 0;
  border: 1px dashed #006cfb;
  border-radius: 8px;
}
.footer-btns {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  background: #fff;
  display: flex;
  justify-content: space-around;
  align-items: center;
  padding: 12px 0;
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
  z-index: 1000;
   position: fixed;
   left: 0;
   right: 0;
   bottom: 0;
   background: #fff;
   display: flex;
   justify-content: space-around;
   align-items: center;
   padding: 0.75rem 0;
   box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05);
   z-index: 1000;
}
.cancel-btn {
  font-weight: 400;
  font-size: 16px;
  color: #ffffff;
  width: 102px;
  background: #c7c9cc;
  box-shadow: 0px 4px 10px 0px rgba(3, 88, 185, 0.2);
  border-radius: 40px 40px 40px 40px;
   font-weight: 400;
   font-size: 1rem;
   color: #FFFFFF;
   width: 6.375rem;
   background: #C7C9CC;
   box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
   border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
.save-btn {
  font-weight: 400;
  font-size: 16px;
  color: #ffffff;
  width: 224px;
  background: linear-gradient(140deg, #00baff 0%, #006cfb 100%);
  box-shadow: 0px 4px 10px 0px rgba(3, 88, 185, 0.2);
  border-radius: 40px 40px 40px 40px;
   font-weight: 400;
   font-size: 1rem;
   color: #FFFFFF;
   width: 14rem;
   background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%);
   box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2);
   border-radius: 2.5rem 2.5rem 2.5rem 2.5rem;
}
// 动画定义
@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.2);
    opacity: 0.7;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}
@keyframes rotate {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
@keyframes ripple {
  0% {
    transform: translate(-50%, -50%) scale(0.8);
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -50%) scale(1.6);
    opacity: 0;
  }
}
/* 如果已有 .step-line,这里更精准定位到左侧与小圆点对齐 */
.step-line {
  position: absolute;
  left: 4px;
  top: 48px;
  width: 2px;
  height: calc(100% - 48px);
  background: #E5E7EB;
}
.approver-container {
  display: flex;
  align-items: center;
  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
  border-radius: 16px;
  gap: 12px;
  padding: 10px 0;
  background: transparent;
  border: none;
  box-shadow: none;
}
.approver-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  background: transparent;
  border: none;
  box-shadow: none;
  border-radius: 0;
}
.approver-avatar {
  position: relative;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #F3F4F6;
  border: 2px solid #E5E7EB;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: none; /* 禁用旋转等动画,回归简洁 */
}
.avatar-text {
  font-size: 14px;
  color: #374151;
  font-weight: 600;
}
.add-approver-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  background: transparent;
  border: none;
  box-shadow: none;
  padding: 0;
}
.add-approver-btn .add-circle {
  width: 40px;
  height: 40px;
  border: 2px dashed #A0AEC0;
  border-radius: 50%;
  color: #6B7280;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  line-height: 1;
}
.add-approver-btn .add-label {
  color: #3B82F6;
  font-size: 14px;
}
</style>