From 34900348ac27a7eab1a1499da57635fc0552af7f Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 29 四月 2026 11:17:47 +0800
Subject: [PATCH] 进销存newpro 1.系统架构图前端初模型

---
 src/views/systemArchitecture/index.vue |  436 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/router/index.js                    |   14 +
 2 files changed, 450 insertions(+), 0 deletions(-)

diff --git a/src/router/index.js b/src/router/index.js
index 52c31d4..cc2b88c 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -47,6 +47,20 @@
     component: () => import("@/views/register"),
     hidden: true,
   },
+  // 绯荤粺鏋舵瀯鍥�
+  {
+    path: "/system-architecture",
+    component: Layout,
+    redirect: "/system-architecture/index",
+    children: [
+      {
+        path: "index",
+        component: () => import("@/views/systemArchitecture/index.vue"),
+        name: "SystemArchitecture",
+        meta: { title: "绯荤粺鏋舵瀯鍥�", icon: "tree" },
+      },
+    ],
+  },
   {
     path: "/:pathMatch(.*)*",
     component: () => import("@/views/error/404"),
diff --git a/src/views/systemArchitecture/index.vue b/src/views/systemArchitecture/index.vue
new file mode 100644
index 0000000..1eb37bb
--- /dev/null
+++ b/src/views/systemArchitecture/index.vue
@@ -0,0 +1,436 @@
+<template>
+  <div class="architecture-page">
+    <section ref="canvasRef" class="canvas">
+      <svg
+        v-if="linkPath"
+        class="link-overlay"
+        :width="svgSize.width"
+        :height="svgSize.height"
+        aria-hidden="true"
+      >
+        <defs>
+          <marker
+            id="erp-link-arrow"
+            markerWidth="10"
+            markerHeight="10"
+            refX="7"
+            refY="3.5"
+            orient="auto"
+            markerUnits="strokeWidth"
+          >
+            <path d="M0,0 L0,7 L7,3.5 z" fill="#94a3b8" />
+          </marker>
+        </defs>
+        <path
+          :d="linkPath"
+          class="link-overlay__path"
+          marker-end="url(#erp-link-arrow)"
+        />
+      </svg>
+
+      <section
+        v-for="row in architectureRows"
+        :key="row.key"
+        class="lane"
+      >
+        <aside class="lane__aside">
+          <svg-icon :icon-class="row.icon" class="lane__icon" />
+          <h2>{{ row.label }}</h2>
+          <span class="lane__arrow"></span>
+        </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)"
+            >
+              <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>
+        </div>
+      </section>
+    </section>
+  </div>
+</template>
+
+<script setup>
+import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref } from 'vue'
+
+const architectureRows = [
+  {
+    key: 'basic',
+    label: '鍩虹閰嶇疆',
+    icon: 'system',
+    items: [
+      { name: '瑙掕壊鐢ㄦ埛绠$悊', icon: 'user' },
+      { name: '浜у搧缁存姢', icon: 'monitor' },
+      { name: '瀹℃壒绠$悊', icon: 'tree', accent: true }
+    ]
+  },
+  {
+    key: 'sale',
+    label: '閿�鍞�',
+    icon: 'chart',
+    items: [
+      { name: '瀹㈡埛妗f', icon: 'peoples' },
+      { name: '閿�鍞姤浠�', icon: 'form' },
+      { name: '閿�鍞彴璐�', icon: 'table' },
+      { name: '鍙戣揣鍙拌处', icon: 'clipboard', accent: true, linkSource: true },
+      { name: '閿�鍞��璐�', icon: 'nested' },
+      { name: '瀹㈡埛寰�鏉�', icon: 'money' },
+      { name: '鎸囨爣缁熻', icon: 'chart' }
+    ]
+  },
+  {
+    key: 'purchase',
+    label: '閲囪喘',
+    icon: 'shopping',
+    items: [
+      { name: '渚涘簲鍟嗘。妗�', icon: 'people' },
+      { name: '閲囪喘鍙拌处', icon: 'table', accent: true },
+      { name: '閲囪喘閫�璐�', icon: 'nested' },
+      { name: '渚涘簲鍟嗗線鏉�', icon: 'money' },
+      { name: '閲囪喘鎶ヨ〃', icon: 'chart' }
+    ]
+  },
+  {
+    key: 'produce',
+    label: '鐢熶骇',
+    icon: 'build',
+    items: [
+      { name: '宸ュ簭', icon: 'tree' },
+      { name: 'BOM', icon: 'list' },
+      { name: '宸ヨ壓璺嚎', icon: 'guide' },
+      { name: '鐢熶骇璁㈠崟', icon: 'form' },
+      { name: '鐢熶骇鎺掍骇', icon: 'date' },
+      { name: '鐢熶骇鎶ュ伐', icon: 'edit' },
+      { name: '鎶ュ伐鍙拌处', icon: 'clipboard' },
+      { name: '鐢熶骇鏍哥畻', icon: 'money', accent: true }
+    ]
+  },
+  {
+    key: 'store',
+    label: '浠撳偍鐗╂祦',
+    icon: 'redis',
+    items: [
+      { name: '鍏ュ簱绠$悊', icon: 'download', accent: true },
+      { name: '鍑哄簱绠$悊', icon: 'upload', accent: true, linkTarget: true },
+      { name: '搴撳瓨绠$悊', icon: 'redis-list' }
+    ]
+  }
+]
+
+const canvasRef = ref(null)
+const sourceNodeRef = ref(null)
+const targetNodeRef = ref(null)
+const linkPath = ref('')
+const svgSize = ref({ width: 0, height: 0 })
+let resizeObserver = null
+
+function setNodeRef(item) {
+  return (el) => {
+    if (item.linkSource) sourceNodeRef.value = el
+    if (item.linkTarget) targetNodeRef.value = el
+  }
+}
+
+function resetLine() {
+  linkPath.value = ''
+}
+
+function updateLinkLine() {
+  if (!canvasRef.value || !sourceNodeRef.value || !targetNodeRef.value || window.innerWidth <= 768) {
+    resetLine()
+    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)
+  }
+
+  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)
+
+  linkPath.value = `M ${startX} ${startY} L ${startX} ${middleY} L ${endX} ${middleY} L ${endX} ${endY}`
+}
+
+function handleResize() {
+  requestAnimationFrame(() => {
+    requestAnimationFrame(updateLinkLine)
+  })
+}
+
+onMounted(async () => {
+  await nextTick()
+  handleResize()
+  window.addEventListener('resize', handleResize)
+  if (window.ResizeObserver && canvasRef.value) {
+    resizeObserver = new ResizeObserver(handleResize)
+    resizeObserver.observe(canvasRef.value)
+  }
+})
+
+onUpdated(() => {
+  nextTick(handleResize)
+})
+
+onBeforeUnmount(() => {
+  window.removeEventListener('resize', handleResize)
+  if (resizeObserver) resizeObserver.disconnect()
+})
+</script>
+
+<style scoped>
+.architecture-page {
+  min-height: calc(100vh - 84px);
+  padding: 18px;
+  background: #f6f7f9;
+}
+
+.canvas {
+  position: relative;
+  display: grid;
+  gap: 10px;
+}
+
+.lane {
+  display: grid;
+  grid-template-columns: 94px 1fr;
+  gap: 10px;
+  align-items: stretch;
+}
+
+.lane__aside {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 8px;
+  padding: 8px 6px;
+}
+
+.lane__icon {
+  font-size: 18px;
+  color: #2563eb;
+  flex-shrink: 0;
+}
+
+.lane__aside h2 {
+  margin: 0;
+  width: 34px;
+  font-size: 12px;
+  line-height: 1.15;
+  font-weight: 600;
+  color: #1f2937;
+  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__flow {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 8px;
+  padding: 12px 14px;
+  background: #f1f3f5;
+}
+
+.node-wrap {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  position: relative;
+}
+
+.node {
+  width: 100px;
+  min-height: 72px;
+  padding: 4px 2px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 6px;
+}
+
+.node__mark {
+  position: relative;
+  width: 34px;
+  height: 28px;
+  display: flex;
+  align-items: flex-start;
+  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;
+}
+
+.node__icon {
+  position: relative;
+  z-index: 1;
+  margin-top: 2px;
+  font-size: 15px;
+  color: #ffffff;
+}
+
+.node--accent .node__cap {
+  background: linear-gradient(180deg, #2dd4bf, #0f766e);
+  box-shadow: 0 3px 6px rgba(15, 118, 110, 0.16);
+}
+
+.node h3 {
+  margin: 0;
+  font-size: 12px;
+  line-height: 1.3;
+  font-weight: 600;
+  color: #111827;
+  text-align: center;
+}
+
+.flow-arrow {
+  width: 18px;
+  height: 1px;
+  background: #b6c1ce;
+  position: relative;
+  flex-shrink: 0;
+}
+
+.flow-arrow::after {
+  content: '';
+  position: absolute;
+  top: 50%;
+  right: -1px;
+  width: 6px;
+  height: 6px;
+  border-top: 1px solid #94a3b8;
+  border-right: 1px solid #94a3b8;
+  transform: translateY(-50%) rotate(45deg);
+}
+
+.link-overlay {
+  position: absolute;
+  inset: 0;
+  z-index: 20;
+  pointer-events: none;
+  overflow: visible;
+}
+
+.link-overlay__path {
+  fill: none;
+  stroke: #94a3b8;
+  stroke-width: 1;
+  stroke-dasharray: 4 4;
+  stroke-linecap: round;
+  stroke-linejoin: round;
+}
+
+@media (max-width: 1180px) {
+  .lane {
+    grid-template-columns: 1fr;
+    gap: 6px;
+  }
+
+  .lane__aside {
+    justify-content: flex-start;
+  }
+}
+
+@media (max-width: 768px) {
+  .architecture-page {
+    padding: 12px;
+  }
+
+  .lane__flow {
+    display: grid;
+    gap: 8px;
+    padding: 12px;
+  }
+
+  .node-wrap {
+    display: grid;
+    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;
+  }
+
+  .flow-arrow::after {
+    top: auto;
+    bottom: -1px;
+    right: 50%;
+    transform: translateX(50%) rotate(135deg);
+  }
+}
+</style>

--
Gitblit v1.9.3