From 7c2cdcbc7f5585b96fba76a07b0e4417a09c4d7e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 02 六月 2026 11:06:10 +0800
Subject: [PATCH] 新疆马铃薯 1.过程检验下载添加3个模版

---
 src/views/collaborativeApproval/approvalManagement/index.vue |  881 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 881 insertions(+), 0 deletions(-)

diff --git a/src/views/collaborativeApproval/approvalManagement/index.vue b/src/views/collaborativeApproval/approvalManagement/index.vue
new file mode 100644
index 0000000..4638392
--- /dev/null
+++ b/src/views/collaborativeApproval/approvalManagement/index.vue
@@ -0,0 +1,881 @@
+<template>
+  <div class="app-container">
+    <!-- 椤甸潰鏍囬 -->
+    <div class="page-header">
+      <div class="header-title">
+        <el-icon class="title-icon"><Setting /></el-icon>
+        <span>瀹℃壒娴佺▼閰嶇疆</span>
+      </div>
+      <div class="header-desc">涓轰笉鍚屽鎵圭被鍨嬮厤缃鎵规祦绋嬪拰瀹℃壒浜�</div>
+    </div>
+
+    <!-- 瀹℃壒绫诲瀷鍒囨崲 - 绱у噾鏍囩寮� -->
+    <div class="type-tabs">
+      <div
+        v-for="type in approveTypes"
+        :key="type.value"
+        class="type-tab"
+        :class="{ active: activeTab === type.value }"
+        @click="activeTab = type.value; handleTabChange()"
+      >
+        <el-icon :size="14" :style="{ color: activeTab === type.value ? type.color : '#909399' }">
+          <component :is="type.icon" />
+        </el-icon>
+        <span class="tab-name">{{ type.label }}</span>
+      </div>
+    </div>
+
+    <!-- 涓昏鍐呭鍖哄煙 -->
+    <el-card class="config-card" shadow="hover" v-loading="loading">
+      <template #header>
+        <div class="card-header">
+          <div class="header-left">
+            <div class="type-icon" :style="{ backgroundColor: getTypeColor(currentApproveType) }">
+              <el-icon :size="20" color="#fff"><component :is="getTypeIcon(currentApproveType)" /></el-icon>
+            </div>
+            <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} 涓鎵逛汉` : '鏈厤缃鎵逛汉' }}
+              </el-tag>
+            </div>
+          </div>
+          <div class="header-actions" v-if="approverList.length > 0">
+            <el-button @click="handleReset" size="default">
+              <el-icon><RefreshLeft /></el-icon>
+              閲嶇疆
+            </el-button>
+            <el-button type="primary" @click="handleSave" :loading="saveLoading" size="default">
+              <el-icon><Check /></el-icon>
+              淇濆瓨閰嶇疆
+            </el-button>
+          </div>
+        </div>
+      </template>
+
+      <!-- 瀹℃壒娴佺▼灞曠ず -->
+      <div class="flow-wrapper" v-if="approverList.length > 0">
+        <div class="flow-container">
+          <div
+            v-for="(item, index) in approverList"
+            :key="index"
+            class="flow-item"
+          >
+            <!-- 瀹℃壒鑺傜偣鍗$墖 -->
+            <div class="node-card" :class="{ 'empty': !item.approverId }">
+              <!-- 椤堕儴搴忓彿鍜岀骇鍒� -->
+              <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) } : {}"
+                >
+                  <span v-if="item.approverId">{{ item.approverName.charAt(0) }}</span>
+                  <el-icon v-else :size="24"><User /></el-icon>
+                </div>
+                <div class="node-level">{{ getLevelText(index) }}</div>
+              </div>
+
+              <!-- 閫夋嫨鍖哄煙 -->
+              <div class="node-select-section">
+                <el-select
+                  v-model="item.approverId"
+                  placeholder="閫夋嫨瀹℃壒浜�"
+                  filterable
+                  size="default"
+                  @change="(val) => handleApproverChange(val, item)"
+                >
+                  <el-option
+                    v-for="user in userList"
+                    :key="user.userId"
+                    :label="user.nickName"
+                    :value="user.userId"
+                  />
+                </el-select>
+              </div>
+
+              <!-- 鎿嶄綔鎸夐挳 -->
+              <div class="node-actions">
+                <el-button
+                  type="primary"
+                  circle
+                  :disabled="index === 0"
+                  @click="moveLeft(index)"
+                  size="small"
+                  class="action-btn"
+                  title="鍓嶇Щ"
+                >
+                  <el-icon><ArrowLeft /></el-icon>
+                </el-button>
+                <el-button
+                  type="primary"
+                  circle
+                  :disabled="index === approverList.length - 1"
+                  @click="moveRight(index)"
+                  size="small"
+                  class="action-btn"
+                  title="鍚庣Щ"
+                >
+                  <el-icon><ArrowRight /></el-icon>
+                </el-button>
+                <el-button
+                  type="danger"
+                  circle
+                  @click="handleDelete(index)"
+                  size="small"
+                  class="action-btn"
+                >
+                  <el-icon><Delete /></el-icon>
+                </el-button>
+              </div>
+            </div>
+
+            <!-- 杩炴帴绠ご -->
+            <div class="arrow-connector" v-if="index < approverList.length - 1">
+              <div class="arrow-line"></div>
+              <el-icon class="arrow-icon"><ArrowRight /></el-icon>
+            </div>
+          </div>
+
+          <!-- 鏂板鑺傜偣鎸夐挳 - 鏀惧湪娴佺▼鏈�鍚� -->
+          <div class="add-node-item">
+            <div class="arrow-connector" v-if="approverList.length > 0">
+              <div class="arrow-line"></div>
+              <el-icon class="arrow-icon"><ArrowRight /></el-icon>
+            </div>
+            <div class="add-node-card" @click="handleAdd">
+              <div class="add-icon-wrapper">
+                <el-icon :size="28"><Plus /></el-icon>
+              </div>
+              <span class="add-text">鏂板瀹℃壒浜�</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 绌虹姸鎬� -->
+      <div class="empty-state" v-else>
+        <div class="empty-content">
+          <div class="empty-icon-wrapper">
+            <el-icon :size="48" color="#c0c4cc"><User /></el-icon>
+          </div>
+          <div class="empty-text">鏆傛棤瀹℃壒浜洪厤缃�</div>
+          <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>
+    </el-card>
+
+    <!-- 搴曢儴鎻愮ず -->
+    <div class="bottom-tips">
+      <el-icon><InfoFilled /></el-icon>
+      <span>鎻愮ず锛氭瘡涓祦绋嬭嚦灏戦厤缃竴涓鎵逛汉锛屽鎵规寜椤哄簭娴佽浆锛屽彲閫氳繃绠ご璋冩暣椤哄簭</span>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import {
+  Plus, ArrowLeft, Delete, Check, RefreshLeft, Setting,
+  Suitcase, Calendar, Location, Money, ShoppingCart, DocumentChecked,
+  Van, ArrowRight, User, InfoFilled
+} from '@element-plus/icons-vue';
+import { getApproveProcessConfigNodeList, addApproveProcessConfigNode } from '@/api/collaborativeApproval/approvalManagement';
+import { userListNoPage } from '@/api/system/user';
+
+// 褰撳墠閫変腑鐨勬爣绛鹃〉
+const activeTab = ref('1');
+
+// 瀹℃壒绫诲瀷閰嶇疆鏁扮粍
+const approveTypes = [
+  { value: '1', label: '鍏嚭绠$悊', icon: 'Suitcase', color: '#409EFF' },
+  { value: '2', label: '璇峰亣绠$悊', icon: 'Calendar', color: '#67C23A' },
+  { value: '3', label: '鍑哄樊绠$悊', icon: 'Location', color: '#E6A23C' },
+  { value: '4', label: '鎶ラ攢绠$悊', icon: 'Money', color: '#F56C6C' },
+  { value: '5', label: '閲囪喘瀹℃壒', icon: 'ShoppingCart', color: '#909399' },
+  { value: '6', label: '鎶ヤ环瀹℃壒', icon: 'DocumentChecked', color: '#9B59B6' },
+  { value: '7', label: '鍙戣揣瀹℃壒', icon: 'Van', color: '#1ABC9C' },
+];
+
+// 瀹℃壒绫诲瀷鍚嶇О鏄犲皠
+const approveTypeNameMap = {
+  1: '鍏嚭绠$悊',
+  2: '璇峰亣绠$悊',
+  3: '鍑哄樊绠$悊',
+  4: '鎶ラ攢绠$悊',
+  5: '閲囪喘瀹℃壒',
+  6: '鎶ヤ环瀹℃壒',
+  7: '鍙戣揣瀹℃壒',
+};
+
+// 瀹℃壒绫诲瀷鍥炬爣鏄犲皠
+const typeIconMap = {
+  1: 'Suitcase',
+  2: 'Calendar',
+  3: 'Location',
+  4: 'Money',
+  5: 'ShoppingCart',
+  6: 'DocumentChecked',
+  7: 'Van',
+};
+
+// 瀹℃壒绫诲瀷棰滆壊鏄犲皠
+const typeColorMap = {
+  1: '#409EFF',
+  2: '#67C23A',
+  3: '#E6A23C',
+  4: '#F56C6C',
+  5: '#909399',
+  6: '#9B59B6',
+  7: '#1ABC9C',
+};
+
+// 澶村儚棰滆壊姹�
+const avatarColors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#9B59B6', '#1ABC9C', '#FF6B6B', '#4ECDC4'];
+
+// 褰撳墠瀹℃壒绫诲瀷鍚嶇О
+const currentApproveTypeName = computed(() => {
+  return approveTypeNameMap[activeTab.value] || '鏈煡绫诲瀷';
+});
+
+// 褰撳墠瀹℃壒绫诲瀷
+const currentApproveType = computed(() => {
+  return Number(activeTab.value);
+});
+
+// 鑾峰彇绫诲瀷鍥炬爣
+const getTypeIcon = (type) => typeIconMap[type] || 'Setting';
+
+// 鑾峰彇绫诲瀷棰滆壊
+const getTypeColor = (type) => typeColorMap[type] || '#409EFF';
+
+// 鑾峰彇澶村儚棰滆壊
+const getAvatarColor = (name) => {
+  if (!name) return '#C0C4CC';
+  let hash = 0;
+  for (let i = 0; i < name.length; i++) {
+    hash = name.charCodeAt(i) + ((hash << 5) - hash);
+  }
+  return avatarColors[Math.abs(hash) % avatarColors.length];
+};
+
+// 鑾峰彇绾у埆鏂囨湰
+const getLevelText = (index) => {
+  const texts = ['绗竴绾�', '绗簩绾�', '绗笁绾�', '绗洓绾�', '绗簲绾�', '绗叚绾�', '绗竷绾�', '绗叓绾�'];
+  return texts[index] || `绗�${index + 1}绾;
+};
+
+// 瀹℃壒浜哄垪琛紙鐪熷疄鎺ュ彛锛�
+const userList = ref([]);
+
+// 瀹℃壒浜哄垪琛�
+const approverList = ref([]);
+
+// 鍘熷鏁版嵁锛岀敤浜庨噸缃�
+const originalList = ref([]);
+
+// 鍔犺浇鐘舵��
+const loading = ref(false);
+const saveLoading = ref(false);
+
+// 鏍囩椤靛垏鎹㈠鐞�
+const handleTabChange = () => {
+  loadData();
+};
+
+// 鍔犺浇瀹℃壒閰嶇疆鏁版嵁锛堟ā鎷燂級
+const loadData = async () => {
+  loading.value = true;
+  try {
+    const res = await getApproveProcessConfigNodeList(currentApproveType.value);
+    const source = Array.isArray(res?.data)
+      ? res.data
+      : Array.isArray(res?.rows)
+        ? res.rows
+        : 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));
+  } catch (error) {
+    approverList.value = [];
+    originalList.value = [];
+    ElMessage.error('鍔犺浇瀹℃壒閰嶇疆澶辫触');
+  } finally {
+    loading.value = false;
+  }
+};
+
+const loadUserList = async () => {
+  try {
+    const res = await userListNoPage();
+    userList.value = Array.isArray(res?.data) ? res.data : [];
+  } catch (error) {
+    userList.value = [];
+    ElMessage.error('鍔犺浇浜哄憳鍒楄〃澶辫触');
+  }
+};
+
+// 瀹℃壒浜洪�夋嫨鍙樺寲
+const handleApproverChange = (userId, row) => {
+  const user = userList.value.find((u) => u.userId === userId);
+  if (user) {
+    row.approverName = user.nickName;
+  }
+};
+
+// 鏂板瀹℃壒浜�
+const handleAdd = () => {
+  const newOrder = approverList.value.length + 1;
+  approverList.value.push({
+    id: null,
+    approveType: currentApproveType.value,
+    approverId: null,
+    approverName: '',
+    sortOrder: newOrder,
+  });
+};
+
+// 鍒犻櫎瀹℃壒浜�
+const handleDelete = (index) => {
+  ElMessageBox.confirm('纭畾鍒犻櫎璇ュ鎵逛汉鍚楋紵', '鎻愮ず', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning',
+  })
+    .then(() => {
+      approverList.value.splice(index, 1);
+      approverList.value.forEach((item, idx) => {
+        item.sortOrder = idx + 1;
+      });
+      ElMessage.success('鍒犻櫎鎴愬姛');
+    })
+    .catch(() => {});
+};
+
+// 鍓嶇Щ
+const moveLeft = (index) => {
+  if (index === 0) return;
+  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;
+};
+
+// 鍚庣Щ
+const moveRight = (index) => {
+  if (index === approverList.value.length - 1) return;
+  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;
+};
+
+// 淇濆瓨閰嶇疆
+const handleSave = async () => {
+  if (approverList.value.length === 0) {
+    ElMessage.warning('璇疯嚦灏戦厤缃竴涓鎵逛汉');
+    return;
+  }
+
+  const hasEmptyApprover = approverList.value.some((item) => !item.approverId);
+  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('瀹℃壒浜轰笉鑳介噸澶�');
+    return;
+  }
+
+  saveLoading.value = true;
+  try {
+    const payload = approverList.value.map((item, index) => ({
+      approveType: currentApproveType.value,
+      nodeOrder: index + 1,
+      approverId: item.approverId,
+      approverName: item.approverName,
+    }));
+    await addApproveProcessConfigNode(payload);
+    ElMessage.success('淇濆瓨鎴愬姛');
+    await loadData();
+  } catch (error) {
+    ElMessage.error('淇濆瓨澶辫触');
+  } finally {
+    saveLoading.value = false;
+  }
+};
+
+// 閲嶇疆
+const handleReset = () => {
+  if (originalList.value.length === 0) {
+    approverList.value = [];
+    return;
+  }
+  ElMessageBox.confirm('纭畾瑕侀噸缃綋鍓嶉厤缃悧锛熸湭淇濆瓨鐨勬洿鏀瑰皢涓㈠け銆�', '鎻愮ず', {
+    confirmButtonText: '纭畾',
+    cancelButtonText: '鍙栨秷',
+    type: 'warning',
+  })
+    .then(() => {
+      approverList.value = JSON.parse(JSON.stringify(originalList.value));
+      ElMessage.success('宸查噸缃�');
+    })
+    .catch(() => {});
+};
+
+onMounted(async () => {
+  await loadUserList();
+  await loadData();
+});
+</script>
+
+<style scoped>
+.page-header {
+  margin-bottom: 20px;
+}
+
+.header-title {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  font-size: 20px;
+  font-weight: 600;
+  color: var(--el-text-color-primary, #303133);
+  margin-bottom: 6px;
+}
+
+.title-icon {
+  font-size: 24px;
+  color: var(--el-color-primary, #409EFF);
+}
+
+.header-desc {
+  font-size: 13px;
+  color: var(--el-text-color-secondary, #909399);
+  margin-left: 34px;
+}
+
+/* 瀹℃壒绫诲瀷鍒囨崲 - 绱у噾鏍囩寮� */
+.type-tabs {
+  display: flex;
+  gap: 4px;
+  margin-bottom: 16px;
+  padding: 4px;
+  background: var(--el-fill-color-light, #f5f7fa);
+  border-radius: 8px;
+  overflow-x: auto;
+}
+
+.type-tab {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 8px 14px;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  white-space: nowrap;
+  font-size: 13px;
+  color: var(--el-text-color-regular, #606266);
+}
+
+.type-tab:hover {
+  background: var(--el-color-primary-light-9, rgba(64, 158, 255, 0.1));
+  color: var(--el-color-primary, #409EFF);
+}
+
+.type-tab.active {
+  background: var(--el-bg-color, #fff);
+  color: var(--el-color-primary, #409EFF);
+  font-weight: 600;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+
+.tab-name {
+  font-size: 13px;
+}
+
+.tab-count {
+  min-width: 16px;
+  height: 16px;
+  padding: 0 5px;
+  background: var(--el-color-success, #67C23A);
+  color: #fff;
+  border-radius: 8px;
+  font-size: 11px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.config-card {
+  margin-bottom: 16px;
+  border-radius: 12px;
+}
+
+:deep(.el-card__header) {
+  padding: 16px 20px;
+  border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 14px;
+}
+
+.type-icon {
+  width: 44px;
+  height: 44px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.header-info {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.type-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-primary, #303133);
+}
+
+.header-actions {
+  display: flex;
+  gap: 10px;
+}
+
+.flow-wrapper {
+  overflow-x: auto;
+  padding: 8px 4px;
+}
+
+.flow-container {
+  display: flex;
+  align-items: center;
+  gap: 0;
+  min-width: min-content;
+}
+
+.flow-item {
+  display: flex;
+  align-items: center;
+}
+
+.node-card {
+  width: 200px;
+  background: var(--el-bg-color, #fff);
+  border: 2px solid var(--el-border-color, #e4e7ed);
+  border-radius: 12px;
+  padding: 16px;
+  position: relative;
+  transition: all 0.3s ease;
+  flex-shrink: 0;
+}
+
+.node-card:hover {
+  border-color: var(--el-color-primary, #409EFF);
+  box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15);
+  transform: translateY(-2px);
+}
+
+.node-card.empty {
+  border-style: dashed;
+  border-color: var(--el-border-color, #c0c4cc);
+  background: var(--el-fill-color-light, #fafbfc);
+}
+
+.node-card.empty:hover {
+  border-color: var(--el-color-primary, #409EFF);
+  background: var(--el-fill-color-light, #f5f7fa);
+}
+
+.node-badge {
+  position: absolute;
+  top: -10px;
+  left: 16px;
+  width: 24px;
+  height: 24px;
+  background: var(--el-color-primary, #409EFF);
+  color: #fff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 13px;
+  font-weight: 700;
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.4);
+}
+
+.node-avatar-section {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-bottom: 12px;
+  margin-top: 4px;
+}
+
+.node-avatar {
+  width: 56px;
+  height: 56px;
+  border-radius: 50%;
+  background: var(--el-fill-color, #f0f2f5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 8px;
+  color: var(--el-text-color-placeholder, #c0c4cc);
+  transition: all 0.3s ease;
+}
+
+.node-avatar.has-user {
+  color: #fff;
+  font-size: 22px;
+  font-weight: 600;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.node-level {
+  font-size: 12px;
+  color: var(--el-text-color-secondary, #909399);
+  font-weight: 500;
+}
+
+.node-select-section {
+  margin-bottom: 12px;
+}
+
+.node-select-section :deep(.el-select) {
+  width: 100%;
+}
+
+.node-actions {
+  display: flex;
+  justify-content: center;
+  gap: 8px;
+  padding-top: 12px;
+  border-top: 1px solid var(--el-border-color-light, #ebeef5);
+}
+
+.action-btn {
+  transition: all 0.2s;
+}
+
+.action-btn:hover:not(:disabled) {
+  transform: scale(1.1);
+}
+
+.arrow-connector {
+  display: flex;
+  align-items: center;
+  width: 50px;
+  position: relative;
+}
+
+.arrow-line {
+  flex: 1;
+  height: 2px;
+  background: var(--el-border-color, #c0c4cc);
+}
+
+.arrow-icon {
+  color: var(--el-text-color-placeholder, #c0c4cc);
+  font-size: 14px;
+  margin-left: -2px;
+}
+
+/* 鏂板鑺傜偣鏍峰紡 */
+.add-node-item {
+  display: flex;
+  align-items: center;
+}
+
+.add-node-card {
+  width: 140px;
+  height: 200px;
+  border: 2px dashed var(--el-border-color, #c0c4cc);
+  border-radius: 12px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 12px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  background: var(--el-fill-color-light, #fafbfc);
+  flex-shrink: 0;
+  margin-left: 0;
+}
+
+.add-node-card:hover {
+  border-color: var(--el-color-primary, #409EFF);
+  background: var(--el-color-primary-light-9, #f0f7ff);
+  transform: translateY(-2px);
+  box-shadow: 0 4px 16px rgba(64, 158, 255, 0.15);
+}
+
+.add-icon-wrapper {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  background: var(--el-color-primary, #409EFF);
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
+  transition: all 0.3s ease;
+}
+
+.add-node-card:hover .add-icon-wrapper {
+  transform: scale(1.1);
+  box-shadow: 0 6px 16px rgba(64, 158, 255, 0.4);
+}
+
+.add-text {
+  font-size: 14px;
+  color: var(--el-text-color-regular, #606266);
+  font-weight: 500;
+}
+
+.add-node-card:hover .add-text {
+  color: var(--el-color-primary, #409EFF);
+}
+
+/* 绌虹姸鎬� */
+.empty-state {
+  padding: 50px 20px;
+}
+
+.empty-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 16px;
+}
+
+.empty-icon-wrapper {
+  width: 80px;
+  height: 80px;
+  border-radius: 50%;
+  background: var(--el-fill-color-light, #f5f7fa);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 8px;
+}
+
+.empty-text {
+  font-size: 16px;
+  font-weight: 600;
+  color: var(--el-text-color-regular, #606266);
+}
+
+.empty-subtext {
+  font-size: 13px;
+  color: var(--el-text-color-secondary, #909399);
+}
+
+.empty-add-btn {
+  margin-top: 8px;
+  padding: 12px 28px;
+}
+
+/* 搴曢儴鎻愮ず */
+.bottom-tips {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  padding: 12px 20px;
+  background: var(--el-fill-color-light, #f5f7fa);
+  border-radius: 8px;
+  color: var(--el-text-color-regular, #606266);
+  font-size: 13px;
+}
+
+.bottom-tips .el-icon {
+  color: var(--el-color-primary, #409EFF);
+  font-size: 16px;
+}
+
+@media (max-width: 768px) {
+  .type-tabs {
+    padding: 3px;
+  }
+
+  .type-tab {
+    padding: 6px 10px;
+    font-size: 12px;
+  }
+
+  .tab-name {
+    font-size: 12px;
+  }
+
+  .flow-container {
+    flex-wrap: wrap;
+    justify-content: center;
+  }
+
+  .arrow-connector {
+    width: 100%;
+    height: 30px;
+    flex-direction: row;
+    justify-content: center;
+  }
+
+  .arrow-line {
+    width: 2px;
+    height: 30px;
+  }
+
+  .arrow-icon {
+    right: auto;
+    top: auto;
+    bottom: -5px;
+    transform: rotate(90deg);
+  }
+
+  .add-node-item {
+    width: 100%;
+    justify-content: center;
+    margin-top: 10px;
+  }
+
+  .add-node-item .arrow-connector {
+    display: none;
+  }
+}
+</style>

--
Gitblit v1.9.3