From 7881574a654c70b04b4e42092b3a4c4ab21dd879 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期二, 19 五月 2026 13:19:46 +0800
Subject: [PATCH] Merge branch 'dev_NEW_pro' of http://114.132.189.42:9002/r/product-inventory-management into dev_NEW_pro

---
 src/views/systemArchitecture/index.vue |  548 ++++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 385 insertions(+), 163 deletions(-)

diff --git a/src/views/systemArchitecture/index.vue b/src/views/systemArchitecture/index.vue
index 1eb37bb..c3c6c49 100644
--- a/src/views/systemArchitecture/index.vue
+++ b/src/views/systemArchitecture/index.vue
@@ -1,8 +1,13 @@
 <template>
   <div class="architecture-page">
+    <div class="page-header">
+      <span class="header-line"></span>
+      <h1 class="page-title">绯荤粺鏋舵瀯鍥�</h1>
+    </div>
+
     <section ref="canvasRef" class="canvas">
       <svg
-        v-if="linkPath"
+        v-if="svgSize.width && svgSize.height"
         class="link-overlay"
         :width="svgSize.width"
         :height="svgSize.height"
@@ -14,15 +19,24 @@
             markerWidth="10"
             markerHeight="10"
             refX="7"
-            refY="3.5"
+            refY="4.5"
             orient="auto"
             markerUnits="strokeWidth"
           >
-            <path d="M0,0 L0,7 L7,3.5 z" fill="#94a3b8" />
+            <path d="M0,0 L0,7 L7,3.5 z" fill="#a7bdda" />
           </marker>
         </defs>
+
         <path
-          :d="linkPath"
+          v-for="path in aiPaths"
+          :key="path.key"
+          :d="path.d"
+          class="link-overlay__path link-overlay__path--ai"
+        />
+
+        <path
+          v-if="flowPath"
+          :d="flowPath"
           class="link-overlay__path"
           marker-end="url(#erp-link-arrow)"
         />
@@ -34,31 +48,51 @@
         class="lane"
       >
         <aside class="lane__aside">
-          <svg-icon :icon-class="row.icon" class="lane__icon" />
-          <h2>{{ row.label }}</h2>
-          <span class="lane__arrow"></span>
+          <div class="lane__aside-card" :class="`lane__aside-card--${row.key}`">
+            <div class="lane__aside-icon">
+              <svg-icon :icon-class="row.icon" class="lane__icon" />
+            </div>
+            <div class="lane__aside-content">
+              <h2>{{ row.label }}</h2>
+            </div>
+          </div>
         </aside>
 
-        <div class="lane__flow">
-          <div
-            v-for="(item, index) in row.items"
-            :key="item.name"
-            class="node-wrap"
-          >
-            <article
-              class="node"
-              :class="{ 'node--accent': item.accent }"
-              :ref="setNodeRef(item)"
+        <div class="lane__flow" :class="`lane__flow--${row.key}`">
+          <template v-for="(item, index) in row.items" :key="item.name">
+            <div
+              class="node-wrap"
+              :class="{ 'node-wrap--spacer': item.spacer }"
             >
-              <span class="node__mark">
-                <span class="node__cap"></span>
-                <span class="node__base"></span>
-                <svg-icon :icon-class="item.icon" class="node__icon" />
-              </span>
-              <h3>{{ item.name }}</h3>
-            </article>
-            <span v-if="index < row.items.length - 1" class="flow-arrow"></span>
-          </div>
+              <template v-if="item.isCore">
+                <article ref="coreRef" class="ai-core">
+                  <div class="ai-core__halo"></div>
+                  <div class="ai-core__brain">
+                    <img :src="aiHead" alt="AI 鏍稿績" class="ai-core__head-image" />
+                  </div>
+                </article>
+              </template>
+
+              <template v-else>
+                <article
+                  class="node"
+                  :class="{ 'node--accent': item.accent, 'node--small-gap': item.compact }"
+                  :ref="setNodeRef(item)"
+                >
+                  <div class="node__icon-wrapper">
+                    <svg-icon :icon-class="item.icon" class="node__icon" />
+                  </div>
+                  <h3>{{ item.name }}</h3>
+                </article>
+              </template>
+
+              <span
+                v-if="index < row.items.length - 1"
+                class="flow-arrow"
+                :class="{ 'flow-arrow--hidden': item.hideArrowAfter }"
+              ></span>
+            </div>
+          </template>
         </div>
       </section>
     </section>
@@ -67,11 +101,13 @@
 
 <script setup>
 import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue'
+import aiHead from '@/assets/images/head.svg'
 
 const architectureRows = [
   {
     key: 'basic',
     label: '鍩虹閰嶇疆',
+    description: '绯荤粺鍩虹淇℃伅绠$悊',
     icon: 'system',
     items: [
       { name: '瑙掕壊鐢ㄦ埛绠$悊', icon: 'user' },
@@ -81,39 +117,44 @@
   },
   {
     key: 'sale',
-    label: '閿�鍞�',
+    label: '閿�鍞厤缃�',
+    description: '瀹㈡埛涓庨攢鍞鐞�',
     icon: 'chart',
     items: [
       { name: '瀹㈡埛妗f', icon: 'peoples' },
       { name: '閿�鍞姤浠�', icon: 'form' },
+      { name: '閿�鍞姤浠�', icon: 'chart' },
       { name: '閿�鍞彴璐�', icon: 'table' },
-      { name: '鍙戣揣鍙拌处', icon: 'clipboard', accent: true, linkSource: true },
-      { name: '閿�鍞��璐�', icon: 'nested' },
+      { name: '鍙戣揣鍙拌处', icon: 'clipboard', accent: true, linkSource: true, aiLink: true },
+      { name: '閿�鍞��璐�', icon: 'nested', accent: true, aiLink: true },
       { name: '瀹㈡埛寰�鏉�', icon: 'money' },
-      { name: '鎸囨爣缁熻', icon: 'chart' }
+      { name: '鎸囨爣缁熻', icon: 'pie-chart' }
     ]
   },
   {
     key: 'purchase',
-    label: '閲囪喘',
+    label: '閲囪喘閰嶇疆',
+    description: '閲囪喘涓庝緵搴斿晢绠$悊',
     icon: 'shopping',
     items: [
       { name: '渚涘簲鍟嗘。妗�', icon: 'people' },
-      { name: '閲囪喘鍙拌处', icon: 'table', accent: true },
-      { name: '閲囪喘閫�璐�', icon: 'nested' },
+      { name: '閲囪喘鍙拌处', icon: 'table' },
+      { name: '閲囪喘閫�璐�', icon: 'nested', accent: true, aiLink: true },
+      { isCore: true, hideArrowAfter: true },
       { name: '渚涘簲鍟嗗線鏉�', icon: 'money' },
       { name: '閲囪喘鎶ヨ〃', icon: 'chart' }
     ]
   },
   {
     key: 'produce',
-    label: '鐢熶骇',
+    label: '鐢熶骇閰嶇疆',
+    description: '鐢熶骇杩囩▼绠$悊',
     icon: 'build',
     items: [
       { name: '宸ュ簭', icon: 'tree' },
       { name: 'BOM', icon: 'list' },
-      { name: '宸ヨ壓璺嚎', icon: 'guide' },
-      { name: '鐢熶骇璁㈠崟', icon: 'form' },
+      { name: '宸ヨ壓璺嚎', icon: 'guide', accent: true },
+      { name: '鐢熶骇璁㈠崟', icon: 'peoples', aiLink: true },
       { name: '鐢熶骇鎺掍骇', icon: 'date' },
       { name: '鐢熶骇鎶ュ伐', icon: 'edit' },
       { name: '鎶ュ伐鍙拌处', icon: 'clipboard' },
@@ -123,60 +164,126 @@
   {
     key: 'store',
     label: '浠撳偍鐗╂祦',
+    description: '搴撳瓨涓庡嚭鍏ュ簱绠$悊',
     icon: 'redis',
     items: [
-      { name: '鍏ュ簱绠$悊', icon: 'download', accent: true },
-      { name: '鍑哄簱绠$悊', icon: 'upload', accent: true, linkTarget: true },
+      { name: '鍏ュ簱绠$悊', icon: 'download' },
+      { name: '鍑哄簱绠$悊', icon: 'upload', linkTarget: true },
       { name: '搴撳瓨绠$悊', icon: 'redis-list' }
     ]
   }
 ]
 
 const canvasRef = ref(null)
+const coreRef = ref(null)
 const sourceNodeRef = ref(null)
 const targetNodeRef = ref(null)
-const linkPath = ref('')
 const svgSize = ref({ width: 0, height: 0 })
+const flowPath = ref('')
+const aiPaths = ref([])
+const aiNodeRefs = []
 let resizeObserver = null
 
 function setNodeRef(item) {
   return (el) => {
     if (item.linkSource) sourceNodeRef.value = el
     if (item.linkTarget) targetNodeRef.value = el
+
+    if (item.aiLink) {
+      if (el && !aiNodeRefs.some((entry) => entry.key === item.name && entry.el === el)) {
+        aiNodeRefs.push({ key: item.name, el })
+      }
+    }
   }
 }
 
-function resetLine() {
-  linkPath.value = ''
+function resetLines() {
+  flowPath.value = ''
+  aiPaths.value = []
 }
 
-function updateLinkLine() {
-  if (!canvasRef.value || !sourceNodeRef.value || !targetNodeRef.value || window.innerWidth <= 768) {
-    resetLine()
+function getCenter(rect) {
+  return {
+    x: rect.left + rect.width / 2,
+    y: rect.top + rect.height / 2
+  }
+}
+
+function getEdgePoint(sourceRect, targetRect) {
+  const sourceCenter = getCenter(sourceRect)
+  const targetCenter = getCenter(targetRect)
+  const deltaX = targetCenter.x - sourceCenter.x
+  const deltaY = targetCenter.y - sourceCenter.y
+
+  if (Math.abs(deltaX) > Math.abs(deltaY)) {
+    return {
+      x: deltaX > 0 ? sourceRect.right : sourceRect.left,
+      y: sourceCenter.y
+    }
+  }
+
+  return {
+    x: sourceCenter.x,
+    y: deltaY > 0 ? sourceRect.bottom : sourceRect.top
+  }
+}
+
+function updateLinkLines() {
+  if (!canvasRef.value) {
+    resetLines()
     return
   }
-
-  const canvasRect = canvasRef.value.getBoundingClientRect()
-  const sourceRect = sourceNodeRef.value.getBoundingClientRect()
-  const targetRect = targetNodeRef.value.getBoundingClientRect()
 
   svgSize.value = {
     width: Math.ceil(canvasRef.value.scrollWidth),
     height: Math.ceil(canvasRef.value.scrollHeight)
   }
 
+  if (window.innerWidth <= 1200 || !coreRef.value) {
+    resetLines()
+    return
+  }
+
+  const canvasRect = canvasRef.value.getBoundingClientRect()
+  const coreRect = coreRef.value.getBoundingClientRect()
+  const validAiNodes = aiNodeRefs.filter((entry) => entry.el?.isConnected)
+
+  aiPaths.value = validAiNodes.map((entry) => {
+    const nodeRect = entry.el.getBoundingClientRect()
+    const corePoint = getEdgePoint(coreRect, nodeRect)
+    const nodePoint = getEdgePoint(nodeRect, coreRect)
+    const startX = nodePoint.x - canvasRect.left
+    const startY = nodePoint.y - canvasRect.top
+    const endX = corePoint.x - canvasRect.left
+    const endY = corePoint.y - canvasRect.top
+    const controlY = startY < endY ? startY + (endY - startY) * 0.45 : startY - (startY - endY) * 0.45
+    const controlX = (startX + endX) / 2
+
+    return {
+      key: entry.key,
+      d: `M ${startX} ${startY} C ${controlX} ${controlY}, ${controlX} ${controlY}, ${endX} ${endY}`
+    }
+  })
+
+  if (!sourceNodeRef.value || !targetNodeRef.value) {
+    flowPath.value = ''
+    return
+  }
+
+  const sourceRect = sourceNodeRef.value.getBoundingClientRect()
+  const targetRect = targetNodeRef.value.getBoundingClientRect()
   const startX = sourceRect.left + sourceRect.width / 2 - canvasRect.left
   const startY = sourceRect.bottom - canvasRect.top + 2
   const endX = targetRect.left + targetRect.width / 2 - canvasRect.left
-  const endY = targetRect.top - canvasRect.top - 6
-  const middleY = startY + Math.max(24, (endY - startY) / 2)
+  const endY = targetRect.top - canvasRect.top - 4
+  const middleY = startY + (endY - startY) / 2
 
-  linkPath.value = `M ${startX} ${startY} L ${startX} ${middleY} L ${endX} ${middleY} L ${endX} ${endY}`
+  flowPath.value = `M ${startX} ${startY} C ${startX} ${middleY}, ${endX} ${middleY}, ${endX} ${endY}`
 }
 
 function handleResize() {
   requestAnimationFrame(() => {
-    requestAnimationFrame(updateLinkLine)
+    requestAnimationFrame(updateLinkLines)
   })
 }
 
@@ -203,153 +310,199 @@
 <style scoped>
 .architecture-page {
   min-height: calc(100vh - 84px);
-  padding: 18px;
-  background: #f6f7f9;
+  padding: 24px;
+  background: #f0f4fb;
+}
+
+.page-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 24px;
+}
+
+.header-line {
+  width: 4px;
+  height: 20px;
+  background: #2563eb;
+  border-radius: 2px;
+}
+
+.page-title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 700;
+  color: #1f2937;
 }
 
 .canvas {
   position: relative;
-  display: grid;
-  gap: 10px;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+  padding: 28px 24px;
+  background: #fff;
+  border: 1px solid rgba(219, 234, 254, 0.9);
+  border-radius: 18px;
+  box-shadow: 0 10px 30px rgba(30, 64, 175, 0.06);
 }
 
 .lane {
   display: grid;
-  grid-template-columns: 94px 1fr;
-  gap: 10px;
+  grid-template-columns: 190px 1fr;
+  gap: 18px;
   align-items: stretch;
 }
 
-.lane__aside {
+.lane__aside-card {
+  height: 100%;
+  min-height: 138px;
+  padding: 22px 20px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 14px;
+  background: linear-gradient(180deg, #f8fbff 0%, #eef5ff 100%);
+  border: 1px solid rgba(219, 234, 254, 0.95);
+  border-radius: 18px;
+  box-shadow: inset 0 1px 8px rgba(255, 255, 255, 0.9);
+}
+
+.lane__aside-card--sale .lane__aside-icon {
+  background: linear-gradient(180deg, #f3e8ff 0%, #e9d5ff 100%);
+  color: #9333ea;
+}
+
+.lane__aside-card--purchase .lane__aside-icon {
+  background: linear-gradient(180deg, #e0f2fe 0%, #bae6fd 100%);
+  color: #0284c7;
+}
+
+.lane__aside-card--produce .lane__aside-icon {
+  background: linear-gradient(180deg, #ecfdf5 0%, #d1fae5 100%);
+  color: #16a34a;
+}
+
+.lane__aside-card--store .lane__aside-icon {
+  background: linear-gradient(180deg, #eef2ff 0%, #dbeafe 100%);
+  color: #2563eb;
+}
+
+.lane__aside-icon {
+  width: 44px;
+  height: 44px;
+  flex-shrink: 0;
   display: flex;
   align-items: center;
   justify-content: center;
-  gap: 8px;
-  padding: 8px 6px;
+  border-radius: 14px;
+  background: linear-gradient(180deg, #e0ecff 0%, #dbeafe 100%);
+  color: #2563eb;
+  box-shadow: 0 8px 18px rgba(37, 99, 235, 0.12);
 }
 
 .lane__icon {
-  font-size: 18px;
-  color: #2563eb;
-  flex-shrink: 0;
+  font-size: 24px;
 }
 
-.lane__aside h2 {
+.lane__aside-content h2 {
   margin: 0;
-  width: 34px;
-  font-size: 12px;
-  line-height: 1.15;
-  font-weight: 600;
-  color: #1f2937;
+  font-size: 18px;
+  font-weight: 700;
+  color: #1d4ed8;
+  line-height: 1.2;
   text-align: center;
 }
 
-.lane__arrow {
-  position: relative;
-  width: 16px;
-  height: 1px;
-  background: #cbd5e1;
-  flex-shrink: 0;
-}
-
-.lane__arrow::after {
-  content: '';
-  position: absolute;
-  top: 50%;
-  right: -1px;
-  width: 6px;
-  height: 6px;
-  border-top: 1px solid #cbd5e1;
-  border-right: 1px solid #cbd5e1;
-  transform: translateY(-50%) rotate(45deg);
+.lane__aside-content p {
+  margin: 0;
+  font-size: 13px;
+  color: #7c8aa5;
 }
 
 .lane__flow {
   position: relative;
   z-index: 2;
   display: flex;
-  flex-wrap: wrap;
   align-items: center;
-  gap: 8px;
-  padding: 12px 14px;
-  background: #f1f3f5;
+  gap: 12px;
+  min-height: 138px;
+  padding: 18px 22px;
+  background: linear-gradient(180deg, #ffffff 0%, #fbfdff 100%);
+  border: 1px solid rgba(219, 234, 254, 0.9);
+  border-radius: 18px;
+  box-shadow: inset 0 1px 10px rgba(255, 255, 255, 0.92);
+}
+
+.lane__flow--purchase,
+.lane__flow--produce {
+  padding-left: 28px;
+  padding-right: 28px;
 }
 
 .node-wrap {
   display: flex;
   align-items: center;
-  gap: 8px;
-  position: relative;
+  gap: 12px;
+  flex-shrink: 0;
 }
 
 .node {
-  width: 100px;
-  min-height: 72px;
-  padding: 4px 2px;
+  width: 96px;
+  min-height: 86px;
+  padding: 8px 6px;
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  gap: 6px;
+  gap: 8px;
+  background: transparent;
 }
 
-.node__mark {
-  position: relative;
-  width: 34px;
-  height: 28px;
+.node__icon-wrapper {
+  width: 58px;
+  height: 58px;
   display: flex;
-  align-items: flex-start;
+  align-items: center;
   justify-content: center;
-}
-
-.node__cap {
-  position: absolute;
-  top: 0;
-  width: 18px;
-  height: 14px;
-  border-radius: 4px 4px 3px 3px;
-  background: linear-gradient(180deg, #60a5fa, #2563eb);
-  box-shadow: 0 3px 6px rgba(37, 99, 235, 0.16);
-}
-
-.node__base {
-  position: absolute;
-  bottom: 0;
-  width: 30px;
-  height: 12px;
-  border-radius: 4px;
-  background: linear-gradient(180deg, #ffffff, #e8edf3);
-  border: 1px solid #d7dee7;
+  border-radius: 16px;
+  background: linear-gradient(180deg, #ffffff 0%, #f2f7ff 100%);
+  border: 1px solid rgba(191, 219, 254, 0.95);
+  box-shadow:
+    0 8px 18px rgba(37, 99, 235, 0.08),
+    inset 0 1px 8px rgba(255, 255, 255, 0.95);
 }
 
 .node__icon {
-  position: relative;
-  z-index: 1;
-  margin-top: 2px;
-  font-size: 15px;
-  color: #ffffff;
+  font-size: 26px;
+  color: #2563eb;
 }
 
-.node--accent .node__cap {
-  background: linear-gradient(180deg, #2dd4bf, #0f766e);
-  box-shadow: 0 3px 6px rgba(15, 118, 110, 0.16);
+.node--accent .node__icon-wrapper {
+  background: linear-gradient(180deg, #ffffff 0%, #eef4ff 100%);
 }
 
 .node h3 {
   margin: 0;
   font-size: 12px;
-  line-height: 1.3;
+  line-height: 1.35;
   font-weight: 600;
-  color: #111827;
+  color: #334155;
   text-align: center;
 }
 
 .flow-arrow {
-  width: 18px;
+  width: 36px;
   height: 1px;
-  background: #b6c1ce;
   position: relative;
-  flex-shrink: 0;
+  background: repeating-linear-gradient(
+    to right,
+    #bfd7ff 0,
+    #bfd7ff 4px,
+    transparent 4px,
+    transparent 8px
+  );
 }
 
 .flow-arrow::after {
@@ -359,77 +512,146 @@
   right: -1px;
   width: 6px;
   height: 6px;
-  border-top: 1px solid #94a3b8;
-  border-right: 1px solid #94a3b8;
+  border-top: 1px solid #9ec5ff;
+  border-right: 1px solid #9ec5ff;
   transform: translateY(-50%) rotate(45deg);
+}
+
+.flow-arrow--hidden {
+  visibility: hidden;
+}
+
+.ai-core {
+  position: relative;
+  width: 252px;
+  height: 176px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.ai-core__halo {
+  position: absolute;
+  inset: 14px 28px 32px;
+  border-radius: 50%;
+  background: radial-gradient(circle, rgba(96, 165, 250, 0.12) 0%, rgba(96, 165, 250, 0) 72%);
+  transform: scale(1.08);
+}
+
+.ai-core__brain {
+  position: absolute;
+  inset: 6px 0 0;
+  left: 50%;
+  width: 232px;
+  height: 156px;
+  transform: translateX(-50%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  filter: drop-shadow(0 10px 18px rgba(96, 165, 250, 0.18));
+}
+
+.ai-core__head-image {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
 }
 
 .link-overlay {
   position: absolute;
   inset: 0;
-  z-index: 20;
+  z-index: 3;
   pointer-events: none;
-  overflow: visible;
 }
 
 .link-overlay__path {
   fill: none;
-  stroke: #94a3b8;
-  stroke-width: 1;
-  stroke-dasharray: 4 4;
+  stroke: #bfd2ea;
+  stroke-width: 1.2;
+  stroke-dasharray: 4 5;
   stroke-linecap: round;
   stroke-linejoin: round;
 }
 
-@media (max-width: 1180px) {
-  .lane {
-    grid-template-columns: 1fr;
-    gap: 6px;
+.link-overlay__path--ai {
+  stroke: #bfd7ff;
+  stroke-width: 1.4;
+}
+
+@media (max-width: 1200px) {
+  .architecture-page {
+    padding: 16px;
   }
 
-  .lane__aside {
-    justify-content: flex-start;
+  .lane {
+    grid-template-columns: 1fr;
+  }
+
+  .lane__aside-card,
+  .lane__flow {
+    min-height: auto;
+  }
+
+  .lane__flow {
+    flex-wrap: wrap;
+  }
+
+  .ai-core {
+    order: 99;
+    width: 100%;
+    max-width: 268px;
+    margin: 0 auto;
+  }
+
+  .link-overlay {
+    display: none;
   }
 }
 
 @media (max-width: 768px) {
-  .architecture-page {
-    padding: 12px;
+  .canvas {
+    padding: 16px;
+    gap: 16px;
+  }
+
+  .lane__aside-card {
+    min-height: auto;
+    padding: 18px 16px;
   }
 
   .lane__flow {
+    padding: 16px;
     display: grid;
-    gap: 8px;
-    padding: 12px;
+    grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
+    gap: 12px;
   }
 
   .node-wrap {
-    display: grid;
+    flex-direction: column;
     gap: 8px;
   }
 
   .node {
     width: 100%;
-    min-height: 52px;
-    flex-direction: row;
-    justify-content: flex-start;
-    gap: 10px;
-  }
-
-  .node h3 {
-    text-align: left;
   }
 
   .flow-arrow {
     width: 1px;
     height: 16px;
     margin: 0 auto;
+    background: repeating-linear-gradient(
+      to bottom,
+      #bfd7ff 0,
+      #bfd7ff 4px,
+      transparent 4px,
+      transparent 8px
+    );
   }
 
   .flow-arrow::after {
     top: auto;
-    bottom: -1px;
     right: 50%;
+    bottom: -1px;
     transform: translateX(50%) rotate(135deg);
   }
 }

--
Gitblit v1.9.3