5 天以前 f51c5018dc9088f393f34e3e2508d7caf80002c4
src/views/collaborativeApproval/approvalManagement/index.vue
@@ -36,7 +36,7 @@
            <div class="header-info">
              <span class="type-name">{{ currentApproveTypeName }}</span>
              <el-tag :type="approverList.length > 0 ? 'success' : 'warning'" size="small" effect="light">
                {{ approverList.length > 0 ? `已配置 ${approverList.length} 个审批人` : '未配置审批人' }}
                {{ approverCountText }}
              </el-tag>
            </div>
          </div>
@@ -62,18 +62,18 @@
            class="flow-item"
          >
            <!-- 审批节点卡片 -->
            <div class="node-card" :class="{ 'empty': !item.approverId }">
            <div class="node-card" :class="{ 'empty': !(item.approverIds && item.approverIds.length) }">
              <!-- 顶部序号和级别 -->
              <div class="node-badge">{{ index + 1 }}</div>
              <!-- 头像区域 -->
              <div class="node-avatar-section">
                <div
                  class="node-avatar"
                  :class="{ 'has-user': item.approverId }"
                  :style="item.approverId ? { backgroundColor: getAvatarColor(item.approverName) } : {}"
                <div
                  class="node-avatar"
                  :class="{ 'has-user': item.approverIds && item.approverIds.length }"
                  :style="item.approverIds && item.approverIds.length ? { backgroundColor: getAvatarColor(item.approverNames?.[0] || '') } : {}"
                >
                  <span v-if="item.approverId">{{ item.approverName.charAt(0) }}</span>
                  <span v-if="item.approverIds && item.approverIds.length">{{ (item.approverNames?.[0] || '?').charAt(0) }}</span>
                  <el-icon v-else :size="24"><User /></el-icon>
                </div>
                <div class="node-level">{{ getLevelText(index) }}</div>
@@ -82,8 +82,11 @@
              <!-- 选择区域 -->
              <div class="node-select-section">
                <el-select
                  v-model="item.approverId"
                  placeholder="选择审批人"
                  v-model="item.approverIds"
                  multiple
                  collapse-tags
                  collapse-tags-tooltip
                  placeholder="选择审批人(可多选)"
                  filterable
                  size="default"
                  @change="(val) => handleApproverChange(val, item)"
@@ -95,6 +98,13 @@
                    :value="user.userId"
                  />
                </el-select>
                <div class="node-mode">
                  <span class="mode-label">审批方式</span>
                  <el-radio-group v-model="item.approveMode" size="small">
                    <el-radio-button value="or">或签</el-radio-button>
                    <el-radio-button value="and">会签</el-radio-button>
                  </el-radio-group>
                </div>
              </div>
              <!-- 操作按钮 -->
@@ -150,7 +160,7 @@
              <div class="add-icon-wrapper">
                <el-icon :size="28"><Plus /></el-icon>
              </div>
              <span class="add-text">新增审批人</span>
              <span class="add-text">新增审批节点</span>
            </div>
          </div>
        </div>
@@ -166,7 +176,7 @@
          <div class="empty-subtext">点击下方按钮添加第一个审批人</div>
          <el-button type="primary" size="large" @click="handleAdd" class="empty-add-btn">
            <el-icon><Plus /></el-icon>
            新增审批人
            新增审批节点
          </el-button>
        </div>
      </div>
@@ -175,7 +185,7 @@
    <!-- 底部提示 -->
    <div class="bottom-tips">
      <el-icon><InfoFilled /></el-icon>
      <span>提示:每个流程至少配置一个审批人,审批按顺序流转,可通过箭头调整顺序</span>
      <span>提示:每个节点可配置多名审批人并选择或签/会签;或签=任意一人通过即流转,会签=需全部人通过;审批按节点顺序流转</span>
    </div>
  </div>
</template>
@@ -186,7 +196,7 @@
import {
  Plus, ArrowLeft, Delete, Check, RefreshLeft, Setting,
  Suitcase, Calendar, Location, Money, ShoppingCart, DocumentChecked,
  Van, ArrowRight, User, InfoFilled
  Van, ArrowRight, User, InfoFilled, Stamp, WarningFilled
} from '@element-plus/icons-vue';
import { getApproveProcessConfigNodeList, addApproveProcessConfigNode } from '@/api/collaborativeApproval/approvalManagement';
import { userListNoPage } from '@/api/system/user';
@@ -203,6 +213,7 @@
  { value: '5', label: '采购审批', icon: 'ShoppingCart', color: '#909399' },
  { value: '6', label: '报价审批', icon: 'DocumentChecked', color: '#9B59B6' },
  { value: '7', label: '发货审批', icon: 'Van', color: '#1ABC9C' },
  { value: '8', label: '危险作业审批', icon: 'WarningFilled', color: '#E74C3C' },
  { value: '9', label: '用印审批', icon: 'Stamp', color: '#7D3C98' },
];
@@ -215,6 +226,7 @@
  5: '采购审批',
  6: '报价审批',
  7: '发货审批',
  8: '危险作业审批',
  9: '用印审批',
};
@@ -227,6 +239,7 @@
  5: 'ShoppingCart',
  6: 'DocumentChecked',
  7: 'Van',
  8: 'WarningFilled',
  9: 'Stamp',
};
@@ -239,6 +252,7 @@
  5: '#909399',
  6: '#9B59B6',
  7: '#1ABC9C',
  8: '#E74C3C',
  9: '#7D3C98',
};
@@ -253,6 +267,14 @@
// 当前审批类型
const currentApproveType = computed(() => {
  return Number(activeTab.value);
});
// 头部统计文案:节点数 / 审批人数
const approverCountText = computed(() => {
  const nodes = approverList.value.length;
  const users = approverList.value.reduce((sum, item) => sum + (item.approverIds ? item.approverIds.length : 0), 0);
  if (nodes === 0) return '未配置审批人';
  return `已配置 ${nodes} 节点 / ${users} 审批人`;
});
// 获取类型图标
@@ -295,7 +317,7 @@
  loadData();
};
// 加载审批配置数据(模拟)
// 加载审批配置数据:接口扁平行 → 按 nodeOrder 分组合并为节点卡片(同节点多审批人)
const loadData = async () => {
  loading.value = true;
  try {
@@ -307,12 +329,19 @@
        : Array.isArray(res?.data?.records)
          ? res.data.records
          : [];
    const data = source.map((item, index) => ({
      ...item,
      sortOrder: item.nodeOrder ?? item.sortOrder ?? index + 1,
    }));
    approverList.value = data.sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0));
    originalList.value = JSON.parse(JSON.stringify(approverList.value));
    const map = new Map();
    source.forEach(item => {
      const order = item.nodeOrder ?? 1;
      if (!map.has(order)) {
        map.set(order, { nodeOrder: order, approverIds: [], approverNames: [], approveMode: item.approveMode || 'or' });
      }
      const card = map.get(order);
      card.approverIds.push(item.approverId);
      if (item.approverName) card.approverNames.push(item.approverName);
    });
    const cards = [...map.values()].sort((a, b) => a.nodeOrder - b.nodeOrder);
    approverList.value = cards;
    originalList.value = JSON.parse(JSON.stringify(cards));
  } catch (error) {
    approverList.value = [];
    originalList.value = [];
@@ -332,29 +361,28 @@
  }
};
// 审批人选择变化
const handleApproverChange = (userId, row) => {
  const user = userList.value.find((u) => u.userId === userId);
  if (user) {
    row.approverName = user.nickName;
  }
// 审批人选择变化(多选)
const handleApproverChange = (ids, row) => {
  row.approverNames = (ids || []).map(id => {
    const user = userList.value.find((u) => u.userId === id);
    return user ? user.nickName : '';
  }).filter(Boolean);
};
// 新增审批人
// 新增审批节点
const handleAdd = () => {
  const newOrder = approverList.value.length + 1;
  approverList.value.push({
    id: null,
    approveType: currentApproveType.value,
    approverId: null,
    approverName: '',
    sortOrder: newOrder,
    nodeOrder: newOrder,
    approverIds: [],
    approverNames: [],
    approveMode: 'or',
  });
};
// 删除审批人
// 删除审批节点
const handleDelete = (index) => {
  ElMessageBox.confirm('确定删除该审批人吗?', '提示', {
  ElMessageBox.confirm('确定删除该审批节点吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
@@ -362,7 +390,7 @@
    .then(() => {
      approverList.value.splice(index, 1);
      approverList.value.forEach((item, idx) => {
        item.sortOrder = idx + 1;
        item.nodeOrder = idx + 1;
      });
      ElMessage.success('删除成功');
    })
@@ -375,8 +403,7 @@
  const temp = approverList.value[index];
  approverList.value[index] = approverList.value[index - 1];
  approverList.value[index - 1] = temp;
  approverList.value[index].sortOrder = index + 1;
  approverList.value[index - 1].sortOrder = index;
  approverList.value.forEach((item, idx) => { item.nodeOrder = idx + 1; });
};
// 后移
@@ -385,38 +412,38 @@
  const temp = approverList.value[index];
  approverList.value[index] = approverList.value[index + 1];
  approverList.value[index + 1] = temp;
  approverList.value[index].sortOrder = index + 1;
  approverList.value[index + 1].sortOrder = index + 2;
  approverList.value.forEach((item, idx) => { item.nodeOrder = idx + 1; });
};
// 保存配置
// 保存配置:节点卡片按审批人展开为多行 payload
const handleSave = async () => {
  if (approverList.value.length === 0) {
    ElMessage.warning('请至少配置一个审批人');
    ElMessage.warning('请至少配置一个审批节点');
    return;
  }
  const hasEmptyApprover = approverList.value.some((item) => !item.approverId);
  const hasEmptyApprover = approverList.value.some((item) => !item.approverIds || item.approverIds.length === 0);
  if (hasEmptyApprover) {
    ElMessage.warning('请选择所有审批人');
    return;
  }
  const approverIds = approverList.value.map((item) => item.approverId);
  const uniqueIds = [...new Set(approverIds)];
  if (uniqueIds.length !== approverIds.length) {
    ElMessage.warning('审批人不能重复');
    ElMessage.warning('请为每个节点选择审批人');
    return;
  }
  saveLoading.value = true;
  try {
    const payload = approverList.value.map((item, index) => ({
      approveType: currentApproveType.value,
      nodeOrder: index + 1,
      approverId: item.approverId,
      approverName: item.approverName,
    }));
    const payload = [];
    approverList.value.forEach((card) => {
      const uniqueIds = [...new Set(card.approverIds || [])];
      uniqueIds.forEach(id => {
        const user = userList.value.find(u => u.userId === id);
        payload.push({
          approveType: currentApproveType.value,
          nodeOrder: card.nodeOrder,
          approverId: id,
          approverName: user ? user.nickName : '',
          approveMode: card.approveMode || 'or',
        });
      });
    });
    await addApproveProcessConfigNode(payload);
    ElMessage.success('保存成功');
    await loadData();
@@ -684,6 +711,20 @@
  width: 100%;
}
.node-mode {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed var(--el-border-color-light, #ebeef5);
}
.mode-label {
  font-size: 12px;
  color: var(--el-text-color-secondary, #909399);
}
.node-actions {
  display: flex;
  justify-content: center;