| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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'; |
| | | |
| | | // å½åéä¸çæ ç¾é¡µ |
| | | 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 getApproverCount = (typeValue) => { |
| | | const type = Number(typeValue); |
| | | const data = mockConfigData[type] || []; |
| | | return data.length; |
| | | }; |
| | | |
| | | // 模æç¨æ·åè¡¨æ°æ® |
| | | const userList = ref([ |
| | | { userId: 1, nickName: 'å¼ ä¸' }, |
| | | { userId: 2, nickName: 'æå' }, |
| | | { userId: 3, nickName: 'çäº' }, |
| | | { userId: 4, nickName: 'èµµå
' }, |
| | | { userId: 5, nickName: 'åä¸' }, |
| | | { userId: 6, nickName: 'å¨å
«' }, |
| | | { userId: 7, nickName: 'å´ä¹' }, |
| | | { userId: 8, nickName: 'éå' }, |
| | | ]); |
| | | |
| | | // 模æå®¡æ¹é
ç½®æ°æ®åå¨ï¼æå®¡æ¹ç±»ååç±»ï¼ |
| | | const mockConfigData = { |
| | | 1: [ |
| | | { id: 1, approveType: 1, approverId: 1, approverName: 'å¼ ä¸', sortOrder: 1 }, |
| | | { id: 2, approveType: 1, approverId: 2, approverName: 'æå', sortOrder: 2 }, |
| | | ], |
| | | 2: [ |
| | | { id: 3, approveType: 2, approverId: 3, approverName: 'çäº', sortOrder: 1 }, |
| | | ], |
| | | 3: [], |
| | | 4: [ |
| | | { id: 4, approveType: 4, approverId: 1, approverName: 'å¼ ä¸', sortOrder: 1 }, |
| | | { id: 5, approveType: 4, approverId: 3, approverName: 'çäº', sortOrder: 2 }, |
| | | { id: 6, approveType: 4, approverId: 5, approverName: 'åä¸', sortOrder: 3 }, |
| | | ], |
| | | 5: [], |
| | | 6: [], |
| | | 7: [], |
| | | }; |
| | | |
| | | // 审æ¹äººå表 |
| | | const approverList = ref([]); |
| | | |
| | | // åå§æ°æ®ï¼ç¨äºéç½® |
| | | const originalList = ref([]); |
| | | |
| | | // å è½½ç¶æ |
| | | const loading = ref(false); |
| | | const saveLoading = ref(false); |
| | | |
| | | // æ ç¾é¡µåæ¢å¤ç |
| | | const handleTabChange = () => { |
| | | loadData(); |
| | | }; |
| | | |
| | | // å 载审æ¹é
ç½®æ°æ®ï¼æ¨¡æï¼ |
| | | const loadData = () => { |
| | | loading.value = true; |
| | | setTimeout(() => { |
| | | const data = mockConfigData[currentApproveType.value] || []; |
| | | approverList.value = data.sort((a, b) => a.sortOrder - b.sortOrder); |
| | | originalList.value = JSON.parse(JSON.stringify(approverList.value)); |
| | | loading.value = false; |
| | | }, 300); |
| | | }; |
| | | |
| | | // 审æ¹äººéæ©åå |
| | | 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 = () => { |
| | | 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; |
| | | setTimeout(() => { |
| | | mockConfigData[currentApproveType.value] = approverList.value.map((item, index) => ({ |
| | | ...item, |
| | | id: item.id || Date.now() + index, |
| | | sortOrder: index + 1, |
| | | })); |
| | | originalList.value = JSON.parse(JSON.stringify(mockConfigData[currentApproveType.value])); |
| | | approverList.value = JSON.parse(JSON.stringify(originalList.value)); |
| | | ElMessage.success('ä¿åæå'); |
| | | saveLoading.value = false; |
| | | }, 500); |
| | | }; |
| | | |
| | | // éç½® |
| | | 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(() => { |
| | | 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> |