From f38a383fee2a1484948d9c5a66078c31e0a5e43a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 19 五月 2026 15:51:13 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_NEW_pro' 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