Explorar el Código

feat(portraitManagement): add team profile visualization pages and optimize page component

1. 新增部门、班组、岗位画像页面骨架文件
2. 新增五边形统计卡片组件PentagonGroup.vue
3. 完善班组画像大屏页面,集成各类图表与统计组件
4. 优化page.vue布局样式,支持纵向滚动背景
huoyi hace 1 mes
padre
commit
04a788c836

+ 278 - 0
src/views/portraitManagement/components/PentagonGroup.vue

@@ -0,0 +1,278 @@
1
+<template>
2
+  <div class="pentagon-group">
3
+    <div
4
+      v-for="(item, index) in items"
5
+      :key="index"
6
+      class="pentagon-item"
7
+      :class="{ 'middle': item.isMiddle }"
8
+    >
9
+      <svg class="pentagon-svg" viewBox="0 0 300 200" preserveAspectRatio="none">
10
+        <defs>
11
+          <linearGradient :id="`topGradient-${index}`" x1="0%" y1="0%" x2="100%" y2="0%">
12
+            <stop offset="0%" :stop-color="item.edgeGradients?.top?.start || '#0f46fa'" />
13
+            <stop offset="100%" :stop-color="item.edgeGradients?.top?.end || '#bd03fb'" />
14
+          </linearGradient>
15
+          <linearGradient :id="`rightGradient-${index}`" x1="0%" y1="0%" x2="0%" y2="100%">
16
+            <stop offset="0%" :stop-color="item.edgeGradients?.right?.start || '#bd03fb'" />
17
+            <stop offset="100%" :stop-color="item.edgeGradients?.right?.end || '#0f46fa'" />
18
+          </linearGradient>
19
+          <linearGradient :id="`bottomGradient-${index}`" x1="100%" y1="0%" x2="0%" y2="0%">
20
+            <stop offset="0%" :stop-color="item.edgeGradients?.bottom?.start || '#0f46fa'" />
21
+            <stop offset="100%" :stop-color="item.edgeGradients?.bottom?.end || '#bd03fb'" />
22
+          </linearGradient>
23
+          <linearGradient :id="`leftGradient-${index}`" x1="0%" y1="100%" x2="0%" y2="0%">
24
+            <stop offset="0%" :stop-color="item.edgeGradients?.left?.start || '#bd03fb'" />
25
+            <stop offset="100%" :stop-color="item.edgeGradients?.left?.end || '#0f46fa'" />
26
+          </linearGradient>
27
+          <linearGradient :id="`bgGradient-${index}`" x1="0%" y1="0%" x2="100%" y2="100%">
28
+            <stop offset="0%" :stop-color="item.bgGradientStart || 'rgba(33,33,58,0.95)'" />
29
+            <stop offset="100%" :stop-color="item.bgGradientEnd || 'rgba(15,70,250,0.15)'" />
30
+          </linearGradient>
31
+          <filter :id="`glow-${index}`">
32
+            <feGaussianBlur stdDeviation="3" result="coloredBlur" />
33
+            <feMerge>
34
+              <feMergeNode in="coloredBlur" />
35
+              <feMergeNode in="SourceGraphic" />
36
+            </feMerge>
37
+          </filter>
38
+        </defs>
39
+        <path
40
+          :d="getQuadPath(item.vertices, item.isMiddle, index)"
41
+          :fill="`url(#bgGradient-${index})`"
42
+        />
43
+        <path
44
+          :d="getTopEdge(item.vertices, item.isMiddle, index)"
45
+          :fill="none"
46
+          :stroke="`url(#topGradient-${index})`"
47
+          stroke-width="3"
48
+          :filter="`url(#glow-${index})`"
49
+        />
50
+        <path
51
+          :d="getRightEdge(item.vertices, item.isMiddle, index)"
52
+          :fill="none"
53
+          :stroke="`url(#rightGradient-${index})`"
54
+          stroke-width="3"
55
+          :filter="`url(#glow-${index})`"
56
+        />
57
+        <path
58
+          :d="getBottomEdge(item.vertices, item.isMiddle, index)"
59
+          :fill="none"
60
+          :stroke="`url(#bottomGradient-${index})`"
61
+          stroke-width="3"
62
+          :filter="`url(#glow-${index})`"
63
+        />
64
+        <path
65
+          :d="getLeftEdge(item.vertices, item.isMiddle, index)"
66
+          :fill="none"
67
+          :stroke="`url(#leftGradient-${index})`"
68
+          stroke-width="3"
69
+          :filter="`url(#glow-${index})`"
70
+        />
71
+      </svg>
72
+      <div class="card-content">
73
+        <div class="icon-wrapper" :class="{ 'large': item.isMiddle }">
74
+          <span v-if="item.icon" class="icon-emoji">{{ item.icon }}</span>
75
+        </div>
76
+        <div class="text-wrapper">
77
+          <div class="value">{{ item.value }}</div>
78
+          <div class="label" v-if="item.label">{{ item.label }}</div>
79
+        </div>
80
+      </div>
81
+    </div>
82
+  </div>
83
+</template>
84
+
85
+<script setup>
86
+const props = defineProps({
87
+  items: {
88
+    type: Array,
89
+    default: () => []
90
+  }
91
+})
92
+
93
+const getVertices = (vertices, isMiddle, index) => {
94
+  if (vertices) {
95
+    return vertices
96
+  } else if (isMiddle) {
97
+    return {
98
+      topLeft: { x: 20, y: 0 },
99
+      topRight: { x: 280, y: 0 },
100
+      bottomRight: { x: 300, y: 200 },
101
+      bottomLeft: { x: 0, y: 200 }
102
+    }
103
+  } else {
104
+    if (index === 0) {
105
+      return {
106
+        topLeft: { x: 0, y: 0 },
107
+        topRight: { x: 300, y: 0 },
108
+        bottomRight: { x: 280, y: 200 },
109
+        bottomLeft: { x: 0, y: 200 }
110
+      }
111
+    } else if (index === 1) {
112
+      return {
113
+        topLeft: { x: 20, y: 0 },
114
+        topRight: { x: 320, y: 0 },
115
+        bottomRight: { x: 300, y: 200 },
116
+        bottomLeft: { x: 0, y: 200 }
117
+      }
118
+    } else if (index === 3) {
119
+      return {
120
+        topLeft: { x: -20, y: 0 },
121
+        topRight: { x: 300, y: 0 },
122
+        bottomRight: { x: 320, y: 200 },
123
+        bottomLeft: { x: 0, y: 200 }
124
+      }
125
+    } else {
126
+      return {
127
+        topLeft: { x: 20, y: 0 },
128
+        topRight: { x: 300, y: 0 },
129
+        bottomRight: { x: 300, y: 200 },
130
+        bottomLeft: { x: 40, y: 200 }
131
+      }
132
+    }
133
+  }
134
+}
135
+
136
+const getQuadPath = (vertices, isMiddle, index) => {
137
+  const v = getVertices(vertices, isMiddle, index)
138
+  return `M ${v.topLeft.x} ${v.topLeft.y} L ${v.topRight.x} ${v.topRight.y} L ${v.bottomRight.x} ${v.bottomRight.y} L ${v.bottomLeft.x} ${v.bottomLeft.y} Z`
139
+}
140
+
141
+const getTopEdge = (vertices, isMiddle, index) => {
142
+  const v = getVertices(vertices, isMiddle, index)
143
+  return `M ${v.topLeft.x} ${v.topLeft.y} L ${v.topRight.x} ${v.topRight.y}`
144
+}
145
+
146
+const getRightEdge = (vertices, isMiddle, index) => {
147
+  const v = getVertices(vertices, isMiddle, index)
148
+  return `M ${v.topRight.x} ${v.topRight.y} L ${v.bottomRight.x} ${v.bottomRight.y}`
149
+}
150
+
151
+const getBottomEdge = (vertices, isMiddle, index) => {
152
+  const v = getVertices(vertices, isMiddle, index)
153
+  return `M ${v.bottomRight.x} ${v.bottomRight.y} L ${v.bottomLeft.x} ${v.bottomLeft.y}`
154
+}
155
+
156
+const getLeftEdge = (vertices, isMiddle, index) => {
157
+  const v = getVertices(vertices, isMiddle, index)
158
+  return `M ${v.bottomLeft.x} ${v.bottomLeft.y} L ${v.topLeft.x} ${v.topLeft.y}`
159
+}
160
+</script>
161
+
162
+<style lang="scss" scoped>
163
+.pentagon-group {
164
+  display: flex;
165
+  justify-content: center;
166
+  align-items: stretch;
167
+  gap: 0;
168
+  padding: 20px;
169
+  position: relative;
170
+  z-index: 10;
171
+  width: 100%;
172
+}
173
+
174
+.pentagon-item {
175
+  position: relative;
176
+  flex: 1;
177
+  min-width: 0;
178
+  height: 160px;
179
+  display: flex;
180
+  align-items: center;
181
+  justify-content: center;
182
+
183
+  &.middle {
184
+    flex: 1.3;
185
+    height: 200px;
186
+    z-index: 5;
187
+    margin: 0 -5px;
188
+
189
+    .card-content {
190
+      flex-direction: column;
191
+
192
+      .icon-wrapper {
193
+        margin-right: 0;
194
+        margin-bottom: 15px;
195
+      }
196
+
197
+      .text-wrapper {
198
+        align-items: center;
199
+      }
200
+
201
+      .value {
202
+        font-size: 32px;
203
+      }
204
+
205
+      .label {
206
+        font-size: 18px;
207
+      }
208
+    }
209
+  }
210
+
211
+  .pentagon-svg {
212
+    position: absolute;
213
+    width: 100%;
214
+    height: 100%;
215
+    top: 0;
216
+    left: 0;
217
+    display: block;
218
+    overflow: visible;
219
+  }
220
+
221
+  .card-content {
222
+    position: relative;
223
+    z-index: 1;
224
+    display: flex;
225
+    flex-direction: row;
226
+    align-items: center;
227
+    justify-content: center;
228
+    padding: 15px 25px;
229
+    gap: 15px;
230
+
231
+    .icon-wrapper {
232
+      width: 50px;
233
+      height: 50px;
234
+      border-radius: 50%;
235
+      background: linear-gradient(135deg, #0f46fa, #bd03fb);
236
+      display: flex;
237
+      align-items: center;
238
+      justify-content: center;
239
+      box-shadow: 0 0 15px rgba(15, 70, 250, 0.5);
240
+      flex-shrink: 0;
241
+
242
+      &.large {
243
+        width: 80px;
244
+        height: 80px;
245
+      }
246
+
247
+      .icon-emoji {
248
+        font-size: 24px;
249
+        line-height: 1;
250
+
251
+        .large & {
252
+          font-size: 36px;
253
+        }
254
+      }
255
+    }
256
+
257
+    .text-wrapper {
258
+      display: flex;
259
+      flex-direction: column;
260
+      align-items: flex-start;
261
+    }
262
+
263
+    .value {
264
+      font-size: 40px;
265
+      font-weight: bold;
266
+      color: #fff;
267
+      text-shadow: 0 0 12px rgba(15, 70, 250, 0.8);
268
+      line-height: 1;
269
+    }
270
+
271
+    .label {
272
+      font-size: 14px;
273
+      color: #a0c4ff;
274
+      margin-top: 5px;
275
+    }
276
+  }
277
+}
278
+</style>

+ 6 - 4
src/views/portraitManagement/components/page.vue

@@ -22,10 +22,13 @@ const props = defineProps({
22 22
 
23 23
 <style lang="scss" scoped>
24 24
 .bg {
25
-  height: 100vh;
26 25
   width: 100%;
26
+  min-height: 100vh;
27 27
   position: relative;
28 28
   background-image: url('@/assets/dataBigScreen/bg.png');
29
+  background-repeat: repeat-y;
30
+  background-size: 100% auto;
31
+  background-position: top center;
29 32
   &::before {
30 33
     content: '';
31 34
     position: absolute;
@@ -35,12 +38,11 @@ const props = defineProps({
35 38
     z-index: 1;
36 39
   }
37 40
   .page {
38
-    content: '';
39
-    position: absolute;
40
-    inset: 0;
41
+    position: relative;
41 42
     z-index: 2;
42 43
     display: flex;
43 44
     flex-direction: column;
45
+    min-height: 100vh;
44 46
   }
45 47
   .title {
46 48
     height: 88px;

+ 0 - 0
src/views/portraitManagement/deptProfile/index.vue


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1177 - 0
src/views/portraitManagement/groupProfile/index.vue


+ 0 - 0
src/views/portraitManagement/stationProfile/index.vue


+ 0 - 0
src/views/portraitManagement/teamProfile/index.vue