Browse Source

Merge branch 'employeeScreen' of https://git.sundot.cn/chongqing/chongqing-web into employeeScreen

lixiangrui 4 weeks ago
parent
commit
ea2600b323

BIN
src/assets/icons/portrait/nianling_icon.png


BIN
src/assets/icons/portrait/renyuan_icon.png


BIN
src/assets/icons/portrait/siling_icon.png


BIN
src/assets/icons/portrait/zonghe_icon.png


BIN
src/assets/icons/portrait/zu01_icon.png


BIN
src/assets/icons/portrait/zu02_icon.png


+ 58 - 104
src/views/portraitManagement/components/PentagonGroup.vue

@@ -1,11 +1,6 @@
1 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
-    >
2
+  <div class="pentagon-group" :class="{ 'vertical': vertical }">
3
+    <div v-for="(item, index) in items" :key="index" class="pentagon-item">
9 4
       <svg class="pentagon-svg" viewBox="0 0 300 200" preserveAspectRatio="none">
10 5
         <defs>
11 6
           <linearGradient :id="`bgGradient-${index}`" x1="0%" y1="0%" x2="100%" y2="100%">
@@ -25,25 +20,21 @@
25 20
             </feMerge>
26 21
           </filter>
27 22
         </defs>
28
-        <path
29
-          :d="getQuadPath(item.vertices, item.isMiddle, index)"
30
-          :fill="`url(#bgGradient-${index})`"
31
-        />
32
-        <path
33
-          :d="getQuadPath(item.vertices, item.isMiddle, index)"
34
-          fill="none"
35
-          :stroke="`url(#borderGradient-${index})`"
36
-          stroke-width="3"
37
-          :filter="`url(#glow-${index})`"
38
-        />
23
+        <path :d="getQuadPath(item.vertices, item.cornerRadius, index)" :fill="`url(#bgGradient-${index})`" />
24
+        <path :d="getQuadPath(item.vertices, item.cornerRadius, index)" fill="none"
25
+          :stroke="`url(#borderGradient-${index})`" stroke-width="1.5" :filter="`url(#glow-${index})`" />
39 26
       </svg>
40
-      <div class="card-content">
41
-        <div class="icon-wrapper" :class="{ 'large': item.isMiddle }">
42
-          <span v-if="item.icon" class="icon-emoji">{{ item.icon }}</span>
27
+      <div class="card-content" 
28
+        :class="{ 'vertical': item.vertical || vertical }"
29
+        :style="item.cardContentStyle">
30
+        <div class="icon-wrapper" :style="item.iconStyle">
31
+          <img v-if="item.iconPath" :src="item.iconPath" class="icon-image" :alt="item.label" />
32
+          <span v-else-if="item.icon" class="icon-emoji">{{ item.icon }}</span>
43 33
         </div>
44
-        <div class="text-wrapper">
45
-          <div class="value">{{ item.value }}</div>
34
+        <div class="content-wrapper">
35
+          <div class="value" :style="item.valueStyle">{{ item.value }}</div>
46 36
           <div class="label" v-if="item.label">{{ item.label }}</div>
37
+          <slot name="content" :item="item" v-if="$slots.content"></slot>
47 38
         </div>
48 39
       </div>
49 40
     </div>
@@ -55,55 +46,38 @@ const props = defineProps({
55 46
   items: {
56 47
     type: Array,
57 48
     default: () => []
49
+  },
50
+  vertical: {
51
+    type: Boolean,
52
+    default: false
58 53
   }
59 54
 })
60 55
 
61
-const getVertices = (vertices, isMiddle, index) => {
56
+const getVertices = (vertices, index) => {
62 57
   if (vertices) {
63 58
     return vertices
64
-  } else if (isMiddle) {
65
-    return {
66
-      topLeft: { x: 20, y: 0 },
67
-      topRight: { x: 280, y: 0 },
68
-      bottomRight: { x: 300, y: 200 },
69
-      bottomLeft: { x: 0, y: 200 }
70
-    }
71
-  } else {
72
-    if (index === 0) {
73
-      return {
74
-        topLeft: { x: 0, y: 0 },
75
-        topRight: { x: 300, y: 0 },
76
-        bottomRight: { x: 280, y: 200 },
77
-        bottomLeft: { x: 0, y: 200 }
78
-      }
79
-    } else if (index === 1) {
80
-      return {
81
-        topLeft: { x: 20, y: 0 },
82
-        topRight: { x: 320, y: 0 },
83
-        bottomRight: { x: 300, y: 200 },
84
-        bottomLeft: { x: 0, y: 200 }
85
-      }
86
-    } else if (index === 3) {
87
-      return {
88
-        topLeft: { x: -20, y: 0 },
89
-        topRight: { x: 300, y: 0 },
90
-        bottomRight: { x: 320, y: 200 },
91
-        bottomLeft: { x: 0, y: 200 }
92
-      }
93
-    } else {
94
-      return {
95
-        topLeft: { x: 20, y: 0 },
96
-        topRight: { x: 300, y: 0 },
97
-        bottomRight: { x: 300, y: 200 },
98
-        bottomLeft: { x: 40, y: 200 }
99
-      }
100
-    }
59
+  }
60
+  return {
61
+    topLeft: { x: 0, y: 0 },
62
+    topRight: { x: 300, y: 0 },
63
+    bottomRight: { x: 300, y: 200 },
64
+    bottomLeft: { x: 0, y: 200 }
101 65
   }
102 66
 }
103 67
 
104
-const getQuadPath = (vertices, isMiddle, index) => {
105
-  const v = getVertices(vertices, isMiddle, index)
106
-  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`
68
+const getQuadPath = (vertices, cornerRadius, index) => {
69
+  const v = getVertices(vertices, index)
70
+  const r = cornerRadius || 0
71
+
72
+  return `M ${v.topLeft.x + r} ${v.topLeft.y} 
73
+          Q ${v.topLeft.x} ${v.topLeft.y} ${v.topLeft.x} ${v.topLeft.y + r}
74
+          L ${v.bottomLeft.x} ${v.bottomLeft.y - r}
75
+          Q ${v.bottomLeft.x} ${v.bottomLeft.y} ${v.bottomLeft.x + r} ${v.bottomLeft.y}
76
+          L ${v.bottomRight.x - r} ${v.bottomRight.y}
77
+          Q ${v.bottomRight.x} ${v.bottomRight.y} ${v.bottomRight.x} ${v.bottomRight.y - r}
78
+          L ${v.topRight.x} ${v.topRight.y + r}
79
+          Q ${v.topRight.x} ${v.topRight.y} ${v.topRight.x - r} ${v.topRight.y}
80
+          Z`
107 81
 }
108 82
 </script>
109 83
 
@@ -117,6 +91,10 @@ const getQuadPath = (vertices, isMiddle, index) => {
117 91
   position: relative;
118 92
   z-index: 10;
119 93
   width: 100%;
94
+
95
+  &.vertical {
96
+    flex-direction: column;
97
+  }
120 98
 }
121 99
 
122 100
 .pentagon-item {
@@ -128,34 +106,6 @@ const getQuadPath = (vertices, isMiddle, index) => {
128 106
   align-items: center;
129 107
   justify-content: center;
130 108
 
131
-  &.middle {
132
-    flex: 1.3;
133
-    height: 200px;
134
-    z-index: 5;
135
-    margin: 0 -5px;
136
-
137
-    .card-content {
138
-      flex-direction: column;
139
-
140
-      .icon-wrapper {
141
-        margin-right: 0;
142
-        margin-bottom: 15px;
143
-      }
144
-
145
-      .text-wrapper {
146
-        align-items: center;
147
-      }
148
-
149
-      .value {
150
-        font-size: 32px;
151
-      }
152
-
153
-      .label {
154
-        font-size: 18px;
155
-      }
156
-    }
157
-  }
158
-
159 109
   .pentagon-svg {
160 110
     position: absolute;
161 111
     width: 100%;
@@ -176,41 +126,45 @@ const getQuadPath = (vertices, isMiddle, index) => {
176 126
     padding: 15px 25px;
177 127
     gap: 15px;
178 128
 
129
+    &.vertical {
130
+      flex-direction: column;
131
+      text-align: center;
132
+    }
133
+
179 134
     .icon-wrapper {
180 135
       width: 50px;
181 136
       height: 50px;
182 137
       border-radius: 50%;
183
-      background: linear-gradient(135deg, #0f46fa, #bd03fb);
184 138
       display: flex;
185 139
       align-items: center;
186 140
       justify-content: center;
187
-      box-shadow: 0 0 15px rgba(15, 70, 250, 0.5);
188 141
       flex-shrink: 0;
189 142
 
190
-      &.large {
191
-        width: 80px;
192
-        height: 80px;
143
+      .icon-image {
144
+        width: 100px;
145
+        height: 100px;
146
+        object-fit: contain;
193 147
       }
194 148
 
195 149
       .icon-emoji {
196 150
         font-size: 24px;
197 151
         line-height: 1;
198
-
199
-        .large & {
200
-          font-size: 36px;
201
-        }
202 152
       }
203 153
     }
204 154
 
205
-    .text-wrapper {
155
+    .content-wrapper {
206 156
       display: flex;
207 157
       flex-direction: column;
208
-      align-items: flex-start;
158
+      align-items: center;
159
+
160
+      .card-content.vertical & {
161
+        align-items: center;
162
+      }
209 163
     }
210 164
 
211 165
     .value {
212 166
       font-size: 40px;
213
-      font-weight: bold;
167
+     
214 168
       color: #fff;
215 169
       text-shadow: 0 0 12px rgba(15, 70, 250, 0.8);
216 170
       line-height: 1;

+ 3 - 74
src/views/portraitManagement/groupProfile/component/profile.vue

@@ -1,6 +1,6 @@
1 1
 <template>
2 2
   <div class="main-content-wrapper">
3
-    <PentagonGroup :items="pentagonItems" />
3
+  
4 4
 
5 5
     <div class="main-content">
6 6
       <div class="content-row">
@@ -145,7 +145,7 @@
145 145
 import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue'
146 146
 import * as echarts from 'echarts'
147 147
 import InfoCard from '../../components/card.vue'
148
-import PentagonGroup from '../../components/PentagonGroup.vue'
148
+
149 149
 
150 150
 const props = defineProps({
151 151
   queryParams: {
@@ -154,78 +154,7 @@ const props = defineProps({
154 154
   }
155 155
 })
156 156
 
157
-const pentagonItems = ref([
158
-  {
159
-    value: '88',
160
-    label: '综合得分',
161
-    icon: '🎖️',
162
-    isMiddle: false,
163
-    edgeGradients: {
164
-      top: { start: '#0f46fa', end: '#bd03fb' },
165
-      right: { start: '#bd03fb', end: '#7eff7e' },
166
-      bottom: { start: '#7eff7e', end: '#bd03fb' },
167
-      left: { start: '#bd03fb', end: '#0f46fa' }
168
-    },
169
-    bgGradientStart: 'rgba(33,33,58,0.95)',
170
-    bgGradientEnd: 'rgba(15,70,250,0.2)'
171
-  },
172
-  {
173
-    value: '10',
174
-    label: '人员数量',
175
-    icon: '👥',
176
-    isMiddle: false,
177
-    edgeGradients: {
178
-      top: { start: '#bd03fb', end: '#0f46fa' },
179
-      right: { start: '#0f46fa', end: '#7eff7e' },
180
-      bottom: { start: '#7eff7e', end: '#bd03fb' },
181
-      left: { start: '#bd03fb', end: '#0f46fa' }
182
-    },
183
-    bgGradientStart: 'rgba(33,33,58,0.95)',
184
-    bgGradientEnd: 'rgba(189,3,251,0.15)'
185
-  },
186
-  {
187
-    value: '周游波小组',
188
-    label: '',
189
-    icon: '👨‍👩‍👧‍👦',
190
-    isMiddle: true,
191
-    edgeGradients: {
192
-      top: { start: '#0f46fa', end: '#bd03fb' },
193
-      right: { start: '#bd03fb', end: '#7eff7e' },
194
-      bottom: { start: '#7eff7e', end: '#0f46fa' },
195
-      left: { start: '#0f46fa', end: '#bd03fb' }
196
-    },
197
-    bgGradientStart: 'rgba(33,33,58,0.98)',
198
-    bgGradientEnd: 'rgba(189,3,251,0.25)'
199
-  },
200
-  {
201
-    value: '28',
202
-    label: '平均年龄',
203
-    icon: '🎂',
204
-    isMiddle: false,
205
-    edgeGradients: {
206
-      top: { start: '#7eff7e', end: '#0f46fa' },
207
-      right: { start: '#0f46fa', end: '#bd03fb' },
208
-      bottom: { start: '#bd03fb', end: '#7eff7e' },
209
-      left: { start: '#7eff7e', end: '#0f46fa' }
210
-    },
211
-    bgGradientStart: 'rgba(33,33,58,0.95)',
212
-    bgGradientEnd: 'rgba(126,255,126,0.15)'
213
-  },
214
-  {
215
-    value: '3.5',
216
-    label: '平均司龄',
217
-    icon: '📊',
218
-    isMiddle: false,
219
-    edgeGradients: {
220
-      top: { start: '#0f46fa', end: '#7eff7e' },
221
-      right: { start: '#7eff7e', end: '#bd03fb' },
222
-      bottom: { start: '#bd03fb', end: '#0f46fa' },
223
-      left: { start: '#0f46fa', end: '#7eff7e' }
224
-    },
225
-    bgGradientStart: 'rgba(33,33,58,0.95)',
226
-    bgGradientEnd: 'rgba(15,70,250,0.2)'
227
-  }
228
-])
157
+
229 158
 
230 159
 const radarData = ref([
231 160
   { name: '通道安全防控力', value: 86 },

+ 134 - 0
src/views/portraitManagement/groupProfile/index.vue

@@ -2,6 +2,7 @@
2 2
   <div class="group-profile-page">
3 3
     <Page title="安检人事管理可视化大屏" :tabs="['能力画像', '运行数据']" @tab-change="handleTabChange">
4 4
       <SearchBar v-model:visible="searchVisible" @search="handleSearch" />
5
+      <PentagonGroup :items="pentagonItems" />
5 6
       <Profile v-if="activeTab === 0" :query-params="queryParams" />
6 7
       <RunData v-else />
7 8
     </Page>
@@ -15,11 +16,144 @@ import Page from '../components/page.vue'
15 16
 import SearchBar from '../components/SearchBar.vue'
16 17
 import Profile from './component/profile.vue'
17 18
 import RunData from './component/runData.vue'
19
+import PentagonGroup from '../components/PentagonGroup.vue'
20
+import zongheIcon from '@/assets/icons/portrait/zonghe_icon.png'
21
+import renyuanIcon from '@/assets/icons/portrait/renyuan_icon.png'
22
+import zu02Icon from '@/assets/icons/portrait/zu02_icon.png'
23
+import nianlingIcon from '@/assets/icons/portrait/nianling_icon.png'
24
+import silingIcon from '@/assets/icons/portrait/siling_icon.png'
18 25
 
19 26
 const activeTab = ref(0)
20 27
 const searchVisible = ref(false)
21 28
 const queryParams = ref({})
22 29
 
30
+const pentagonItems = ref([
31
+  {
32
+    value: '88',
33
+    label: '综合得分',
34
+    iconPath: zongheIcon,
35
+    cornerRadius: 20,
36
+    vertices: {
37
+      topLeft: { x: 0, y: 0 },
38
+      topRight: { x: 300, y: 0 },
39
+      bottomRight: { x: 260, y: 200 },
40
+      bottomLeft: { x: 0, y: 200 }
41
+    },
42
+    cardContentStyle: {
43
+      gap: '100px'
44
+    },
45
+    edgeGradients: {
46
+      top: { start: '#0f46fa', end: 'transparent' },
47
+      right: { start: 'transparent', end: '#9903C1' },
48
+      bottom: { start: '#9903C1', end: 'transparent' },
49
+      left: { start: 'transparent', end: '#0f46fa' }
50
+    },
51
+    bgGradientStart: 'rgba(33,33,58,0.95)',
52
+    bgGradientEnd: 'rgba(15,70,250,0.2)'
53
+  },
54
+  {
55
+    value: '10',
56
+    label: '人员数量',
57
+    iconPath: renyuanIcon,
58
+    cornerRadius: 20,
59
+    vertices: {
60
+      topLeft: { x: 10, y: 0 },
61
+      topRight: { x: 300, y: 0 },
62
+      bottomRight: { x: 260, y: 200 },
63
+      bottomLeft: { x: -30, y: 200 }
64
+    },cardContentStyle: {
65
+      gap: '100px'
66
+    },
67
+    edgeGradients: {
68
+      top: { start: '#0f46fa', end: 'transparent' },
69
+      right: { start: 'transparent', end: '#9903C1' },
70
+      bottom: { start: '#9903C1', end: 'transparent' },
71
+      left: { start: 'transparent', end: '#0f46fa' }
72
+    },
73
+    bgGradientStart: 'rgba(33,33,58,0.95)',
74
+    bgGradientEnd: 'rgba(15,70,250,0.2)'
75
+  },
76
+  {
77
+    value: '周游波小组',
78
+    label: '',
79
+    iconPath: zu02Icon,
80
+    iconStyle: {
81
+      width: '200px',
82
+      height: '200px'
83
+    },
84
+    valueStyle: {
85
+      fontSize: '48px'
86
+    },
87
+    vertical: true,
88
+    cornerRadius: 20,
89
+    vertices: {
90
+      topLeft: { x: 10, y: 0 },
91
+      topRight: { x: 280, y: 0 },
92
+      bottomRight: { x: 320, y: 200 },
93
+      bottomLeft: { x: -30, y: 200 }
94
+    },
95
+    cardContentStyle: {
96
+      gap: '0px',
97
+      position: 'relative',
98
+      bottom: '65px'
99
+    },
100
+    edgeGradients: {
101
+      top: { start: '#0f46fa', end: 'transparent' },
102
+      right: { start: 'transparent', end: '#9903C1' },
103
+      bottom: { start: '#9903C1', end: 'transparent' },
104
+      left: { start: 'transparent', end: '#0f46fa' }
105
+    },
106
+    bgGradientStart: 'rgba(33,33,58,0.95)',
107
+    bgGradientEnd: 'rgba(15,70,250,0.2)'
108
+  },
109
+  {
110
+    value: '28',
111
+    label: '平均年龄',
112
+    iconPath: nianlingIcon,
113
+    cornerRadius: 20,
114
+    vertices: {
115
+      topLeft: { x: -10, y: 0 },
116
+      topRight: { x: 290, y: 0 },
117
+      bottomRight: { x: 330, y: 200 },
118
+      bottomLeft: { x: 30, y: 200 }
119
+    },
120
+    cardContentStyle: {
121
+      gap: '100px'
122
+    },
123
+    edgeGradients: {
124
+      top: { start: '#0f46fa', end: 'transparent' },
125
+      right: { start: 'transparent', end: '#9903C1' },
126
+      bottom: { start: '#9903C1', end: 'transparent' },
127
+      left: { start: 'transparent', end: '#0f46fa' }
128
+    },
129
+    bgGradientStart: 'rgba(33,33,58,0.95)',
130
+    bgGradientEnd: 'rgba(15,70,250,0.2)'
131
+  },
132
+  {
133
+    value: '3.5',
134
+    label: '平均司龄',
135
+    iconPath: silingIcon,
136
+    cornerRadius: 20,
137
+    vertices: {
138
+      topLeft: { x: 0, y: 0 },
139
+      topRight: { x: 300, y: 0 },
140
+      bottomRight: { x: 300, y: 200 },
141
+      bottomLeft: { x: 40, y: 200 }
142
+    },
143
+    cardContentStyle: {
144
+      gap: '100px'
145
+    },
146
+    edgeGradients: {
147
+      top: { start: '#0f46fa', end: 'transparent' },
148
+      right: { start: 'transparent', end: '#9903C1' },
149
+      bottom: { start: '#9903C1', end: 'transparent' },
150
+      left: { start: 'transparent', end: '#0f46fa' }
151
+    },
152
+    bgGradientStart: 'rgba(33,33,58,0.95)',
153
+    bgGradientEnd: 'rgba(15,70,250,0.2)'
154
+  }
155
+])
156
+
23 157
 const handleSearch = (params) => {
24 158
   queryParams.value = params
25 159
 }

+ 449 - 0
src/views/portraitManagement/stationProfile/component/profile.vue

@@ -0,0 +1,449 @@
1
+<template>
2
+  <div class="station-content-wrapper">
3
+  
4
+
5
+    <div class="station-content">
6
+  
7
+
8
+      <div class="content-row">
9
+        <InfoCard title="团队成员">
10
+          <div class="team-members-table">
11
+            <table>
12
+              <thead>
13
+                <tr>
14
+                  <th>部门</th>
15
+                  <th>员工数量</th>
16
+                  <th>党员数量</th>
17
+                  <th>平均年龄</th>
18
+                  <th>平均工龄</th>
19
+                  <th>职业资格证书等级</th>
20
+                  <th>平均升级年龄</th>
21
+                  <th>综合得分</th>
22
+                </tr>
23
+              </thead>
24
+              <tbody>
25
+                <tr>
26
+                  <td>旅检一部</td>
27
+                  <td>800</td>
28
+                  <td>7</td>
29
+                  <td>25</td>
30
+                  <td>3</td>
31
+                  <td>16</td>
32
+                  <td>3</td>
33
+                  <td>90</td>
34
+                </tr>
35
+                <tr>
36
+                  <td>旅检二部</td>
37
+                  <td>756</td>
38
+                  <td>2</td>
39
+                  <td>28</td>
40
+                  <td>8</td>
41
+                  <td>18</td>
42
+                  <td>5</td>
43
+                  <td>88</td>
44
+                </tr>
45
+                <tr>
46
+                  <td>旅检三部</td>
47
+                  <td>708</td>
48
+                  <td>7</td>
49
+                  <td>25</td>
50
+                  <td>3</td>
51
+                  <td>23</td>
52
+                  <td>3</td>
53
+                  <td>86</td>
54
+                </tr>
55
+              </tbody>
56
+            </table>
57
+          </div>
58
+        </InfoCard>
59
+      </div>
60
+
61
+      <div class="content-row">
62
+        <InfoCard title="成员基本情况分布">
63
+          <div class="member-distribution">
64
+            <div class="dist-item">
65
+              <h4>性别分布</h4>
66
+              <div ref="genderDistChartRef" class="dist-chart"></div>
67
+            </div>
68
+            <div class="dist-item">
69
+              <h4>民族分布</h4>
70
+              <div ref="nationDistChartRef" class="dist-chart"></div>
71
+            </div>
72
+            <div class="dist-item">
73
+              <h4>政治面貌分布</h4>
74
+              <div ref="politicalDistChartRef" class="dist-chart"></div>
75
+            </div>
76
+          </div>
77
+        </InfoCard>
78
+      </div>
79
+
80
+      <div class="content-row">
81
+        <InfoCard title="成员职位情况分布">
82
+          <div class="position-distribution">
83
+            <div class="dist-item">
84
+              <h4>职业资格等级分布</h4>
85
+              <div ref="qualificationLevelChartRef" class="dist-chart"></div>
86
+            </div>
87
+            <div class="dist-item">
88
+              <h4>开机年限分布</h4>
89
+              <div ref="experienceYearsChartRef" class="dist-chart"></div>
90
+            </div>
91
+            <div class="dist-item">
92
+              <h4>岗位资质分布</h4>
93
+              <div ref="positionQualificationChartRef" class="dist-chart"></div>
94
+            </div>
95
+          </div>
96
+        </InfoCard>
97
+      </div>
98
+    </div>
99
+  </div>
100
+</template>
101
+
102
+<script setup>
103
+import { ref, onMounted, onUnmounted, nextTick } from 'vue'
104
+import * as echarts from 'echarts'
105
+import InfoCard from '../../components/card.vue'
106
+
107
+
108
+
109
+const props = defineProps({
110
+  queryParams: {
111
+    type: Object,
112
+    default: () => ({})
113
+  }
114
+})
115
+
116
+
117
+const genderDistChartRef = ref(null)
118
+let genderDistChart = null
119
+const nationDistChartRef = ref(null)
120
+let nationDistChart = null
121
+const politicalDistChartRef = ref(null)
122
+let politicalDistChart = null
123
+const qualificationLevelChartRef = ref(null)
124
+let qualificationLevelChart = null
125
+const experienceYearsChartRef = ref(null)
126
+let experienceYearsChart = null
127
+const positionQualificationChartRef = ref(null)
128
+let positionQualificationChart = null
129
+
130
+const initGenderDistChart = () => {
131
+  if (!genderDistChartRef.value) return
132
+  genderDistChart = echarts.init(genderDistChartRef.value)
133
+  const option = {
134
+    series: [{
135
+      type: 'pie',
136
+      radius: ['50%', '70%'],
137
+      data: [
138
+        { value: 650, name: '女', itemStyle: { color: '#ff9f9f' } },
139
+        { value: 750, name: '男', itemStyle: { color: '#7effc4' } }
140
+      ],
141
+      label: { show: true, color: '#fff', fontSize: 11 },
142
+      labelLine: { show: true }
143
+    }]
144
+  }
145
+  genderDistChart.setOption(option)
146
+}
147
+
148
+const initNationDistChart = () => {
149
+  if (!nationDistChartRef.value) return
150
+  nationDistChart = echarts.init(nationDistChartRef.value)
151
+  const option = {
152
+    series: [{
153
+      type: 'pie',
154
+      radius: ['50%', '70%'],
155
+      data: [
156
+        { value: 615, name: '汉族', itemStyle: { color: '#7effc4' } },
157
+        { value: 484, name: '回族', itemStyle: { color: '#4da6ff' } },
158
+        { value: 156, name: '其他', itemStyle: { color: '#ffd93d' } }
159
+      ],
160
+      label: { show: true, color: '#fff', fontSize: 11 },
161
+      labelLine: { show: true }
162
+    }]
163
+  }
164
+  nationDistChart.setOption(option)
165
+}
166
+
167
+const initPoliticalDistChart = () => {
168
+  if (!politicalDistChartRef.value) return
169
+  politicalDistChart = echarts.init(politicalDistChartRef.value)
170
+  const option = {
171
+    series: [{
172
+      type: 'pie',
173
+      radius: ['50%', '70%'],
174
+      data: [
175
+        { value: 410, name: '群众', itemStyle: { color: '#ffd93d' } },
176
+        { value: 540, name: '共青团员', itemStyle: { color: '#4da6ff' } },
177
+        { value: 270, name: '中共党员', itemStyle: { color: '#ff6b6b' } },
178
+        { value: 138, name: '预备党员', itemStyle: { color: '#7effc4' } }
179
+      ],
180
+      label: { show: true, color: '#fff', fontSize: 11 },
181
+      labelLine: { show: true }
182
+    }]
183
+  }
184
+  politicalDistChart.setOption(option)
185
+}
186
+
187
+const initQualificationLevelChart = () => {
188
+  if (!qualificationLevelChartRef.value) return
189
+  qualificationLevelChart = echarts.init(qualificationLevelChartRef.value)
190
+  const option = {
191
+    tooltip: {
192
+      trigger: 'axis',
193
+      backgroundColor: 'rgba(13,80,122,0.95)',
194
+      borderColor: '#70CFE7',
195
+      textStyle: { color: '#fff' }
196
+    },
197
+    legend: {
198
+      data: ['人数'],
199
+      textStyle: { color: '#a0c4ff' },
200
+      top: 0
201
+    },
202
+    grid: {
203
+      left: '10%',
204
+      right: '10%',
205
+      bottom: '15%',
206
+      containLabel: true
207
+    },
208
+    xAxis: {
209
+      type: 'category',
210
+      data: ['等级1', '等级2', '等级3', '等级4', '等级5'],
211
+      axisLabel: { color: '#a0c4ff', fontSize: 10 },
212
+      axisLine: { lineStyle: { color: 'rgba(15,70,250,0.3)' } }
213
+    },
214
+    yAxis: {
215
+      type: 'value',
216
+      axisLabel: { color: '#a0c4ff' },
217
+      axisLine: { lineStyle: { color: 'rgba(15,70,250,0.3)' } },
218
+      splitLine: { lineStyle: { color: 'rgba(15,70,250,0.2)' } }
219
+    },
220
+    series: [{
221
+      name: '人数',
222
+      type: 'bar',
223
+      data: [450, 650, 900, 750, 550],
224
+      itemStyle: {
225
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
226
+          { offset: 0, color: '#ff6b9d' },
227
+          { offset: 1, color: '#ff6b6b' }
228
+        ])
229
+      },
230
+      barWidth: '50%'
231
+    }]
232
+  }
233
+  qualificationLevelChart.setOption(option)
234
+}
235
+
236
+const initExperienceYearsChart = () => {
237
+  if (!experienceYearsChartRef.value) return
238
+  experienceYearsChart = echarts.init(experienceYearsChartRef.value)
239
+  const option = {
240
+    tooltip: {
241
+      trigger: 'axis',
242
+      backgroundColor: 'rgba(13,80,122,0.95)',
243
+      borderColor: '#70CFE7',
244
+      textStyle: { color: '#fff' }
245
+    },
246
+    legend: {
247
+      data: ['人数'],
248
+      textStyle: { color: '#a0c4ff' },
249
+      top: 0
250
+    },
251
+    grid: {
252
+      left: '10%',
253
+      right: '10%',
254
+      bottom: '15%',
255
+      containLabel: true
256
+    },
257
+    xAxis: {
258
+      type: 'category',
259
+      data: ['0-3', '4-7', '8-11', '12-15', '16-19'],
260
+      axisLabel: { color: '#a0c4ff', fontSize: 10 },
261
+      axisLine: { lineStyle: { color: 'rgba(15,70,250,0.3)' } }
262
+    },
263
+    yAxis: {
264
+      type: 'value',
265
+      axisLabel: { color: '#a0c4ff' },
266
+      axisLine: { lineStyle: { color: 'rgba(15,70,250,0.3)' } },
267
+      splitLine: { lineStyle: { color: 'rgba(15,70,250,0.2)' } }
268
+    },
269
+    series: [{
270
+      name: '人数',
271
+      type: 'bar',
272
+      data: [650, 500, 400, 300, 550],
273
+      itemStyle: {
274
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
275
+          { offset: 0, color: '#a55eea' },
276
+          { offset: 1, color: '#bd03fb' }
277
+        ])
278
+      },
279
+      barWidth: '50%'
280
+    }]
281
+  }
282
+  experienceYearsChart.setOption(option)
283
+}
284
+
285
+const initPositionQualificationChart = () => {
286
+  if (!positionQualificationChartRef.value) return
287
+  positionQualificationChart = echarts.init(positionQualificationChartRef.value)
288
+  const option = {
289
+    tooltip: {
290
+      trigger: 'axis',
291
+      backgroundColor: 'rgba(13,80,122,0.95)',
292
+      borderColor: '#70CFE7',
293
+      textStyle: { color: '#fff' }
294
+    },
295
+    legend: {
296
+      data: ['人数'],
297
+      textStyle: { color: '#a0c4ff' },
298
+      top: 0
299
+    },
300
+    grid: {
301
+      left: '10%',
302
+      right: '10%',
303
+      bottom: '15%',
304
+      containLabel: true
305
+    },
306
+    xAxis: {
307
+      type: 'category',
308
+      data: ['仿伪', '炸探', '人身', '开包', '开检后', '开机'],
309
+      axisLabel: { color: '#a0c4ff', fontSize: 10 },
310
+      axisLine: { lineStyle: { color: 'rgba(15,70,250,0.3)' } }
311
+    },
312
+    yAxis: {
313
+      type: 'value',
314
+      axisLabel: { color: '#a0c4ff' },
315
+      axisLine: { lineStyle: { color: 'rgba(15,70,250,0.3)' } },
316
+      splitLine: { lineStyle: { color: 'rgba(15,70,250,0.2)' } }
317
+    },
318
+    series: [{
319
+      name: '人数',
320
+      type: 'bar',
321
+      data: [450, 600, 180, 150, 650, 550],
322
+      itemStyle: {
323
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
324
+          { offset: 0, color: '#ffd93d' },
325
+          { offset: 1, color: '#ffd9b3' }
326
+        ])
327
+      },
328
+      barWidth: '50%'
329
+    }]
330
+  }
331
+  positionQualificationChart.setOption(option)
332
+}
333
+
334
+const handleResize = () => {
335
+  if (genderDistChart) genderDistChart.resize()
336
+  if (nationDistChart) nationDistChart.resize()
337
+  if (politicalDistChart) politicalDistChart.resize()
338
+  if (qualificationLevelChart) qualificationLevelChart.resize()
339
+  if (experienceYearsChart) experienceYearsChart.resize()
340
+  if (positionQualificationChart) positionQualificationChart.resize()
341
+}
342
+
343
+onMounted(() => {
344
+  nextTick(() => {
345
+    setTimeout(() => {
346
+      initGenderDistChart()
347
+      initNationDistChart()
348
+      initPoliticalDistChart()
349
+      initQualificationLevelChart()
350
+      initExperienceYearsChart()
351
+      initPositionQualificationChart()
352
+      window.addEventListener('resize', handleResize)
353
+    }, 100)
354
+  })
355
+})
356
+
357
+onUnmounted(() => {
358
+  window.removeEventListener('resize', handleResize)
359
+  if (genderDistChart) genderDistChart.dispose()
360
+  if (nationDistChart) nationDistChart.dispose()
361
+  if (politicalDistChart) politicalDistChart.dispose()
362
+  if (qualificationLevelChart) qualificationLevelChart.dispose()
363
+  if (experienceYearsChart) experienceYearsChart.dispose()
364
+  if (positionQualificationChart) positionQualificationChart.dispose()
365
+})
366
+</script>
367
+
368
+<style lang="scss" scoped>
369
+.station-content-wrapper {
370
+  .station-content {
371
+    display: flex;
372
+    flex-direction: column;
373
+    gap: 20px;
374
+    padding: 0 20px 40px;
375
+  }
376
+  
377
+  .content-row {
378
+    display: flex;
379
+    gap: 20px;
380
+    
381
+    > .info-card {
382
+      flex: 1;
383
+      min-width: 0;
384
+    }
385
+  }
386
+
387
+  .team-members-table {
388
+    width: 100%;
389
+    overflow-x: auto;
390
+
391
+    table {
392
+      width: 100%;
393
+      border-collapse: collapse;
394
+      color: #fff;
395
+
396
+      th, td {
397
+        padding: 12px 15px;
398
+        text-align: center;
399
+        border: 1px solid rgba(15, 70, 250, 0.3);
400
+      }
401
+
402
+      th {
403
+        background: rgba(15, 70, 250, 0.2);
404
+        color: #a0c4ff;
405
+        font-weight: 600;
406
+      }
407
+
408
+      td {
409
+        background: rgba(33, 33, 58, 0.5);
410
+      }
411
+
412
+      tbody tr:hover td {
413
+        background: rgba(15, 70, 250, 0.3);
414
+      }
415
+    }
416
+  }
417
+
418
+  .member-distribution,
419
+  .position-distribution {
420
+    display: flex;
421
+    gap: 30px;
422
+    justify-content: space-around;
423
+    flex-wrap: wrap;
424
+
425
+    .dist-item {
426
+      flex: 1;
427
+      min-width: 200px;
428
+      text-align: center;
429
+
430
+      h4 {
431
+        color: #a0c4ff;
432
+        margin-bottom: 15px;
433
+        font-size: 16px;
434
+      }
435
+
436
+      .dist-chart {
437
+        width: 100%;
438
+        height: 200px;
439
+      }
440
+    }
441
+  }
442
+
443
+  .position-distribution {
444
+    .dist-chart {
445
+      height: 280px;
446
+    }
447
+  }
448
+}
449
+</style>

File diff suppressed because it is too large
+ 1240 - 0
src/views/portraitManagement/stationProfile/component/runData.vue


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

@@ -0,0 +1,187 @@
1
+<template>
2
+  <div class="station-profile-page">
3
+    <Page title="旅检部综合信息展示大屏" :tabs="['站点画像', '运行数据']" @tab-change="handleTabChange">
4
+      <SearchBar v-model:visible="searchVisible" @search="handleSearch" />
5
+      <PentagonGroup :items="pentagonItems" />
6
+      <Profile v-if="activeTab === 0" :query-params="queryParams" />
7
+      <RunData v-else :query-params="queryParams" />
8
+    </Page>
9
+  </div>
10
+</template>
11
+
12
+<script setup>
13
+import { ref } from 'vue'
14
+import Page from '../components/page.vue'
15
+import SearchBar from '../components/SearchBar.vue'
16
+import Profile from './component/profile.vue'
17
+import RunData from './component/runData.vue'
18
+import PentagonGroup from '../components/PentagonGroup.vue'
19
+import zu02Icon from '@/assets/icons/portrait/zu02_icon.png'
20
+
21
+const activeTab = ref(0)
22
+const searchVisible = ref(false)
23
+const queryParams = ref({})
24
+
25
+const pentagonItems = ref([
26
+  {
27
+    value: '120',
28
+    label: '主班在岗|旅检一部',
29
+    iconPath: zu02Icon,
30
+    vertical: true,
31
+    cornerRadius: 20,
32
+    vertices: {
33
+      topLeft: { x: 0, y: 0 },
34
+      topRight: { x: 320, y: 0 },
35
+      bottomRight: { x: 280, y: 200 },
36
+      bottomLeft: { x: 0, y: 200 }
37
+    },
38
+    cardContentStyle: {
39
+      gap: '10px'
40
+    },
41
+    edgeGradients: {
42
+      top: { start: '#0f46fa', end: 'transparent' },
43
+      right: { start: 'transparent', end: '#9903C1' },
44
+      bottom: { start: '#9903C1', end: 'transparent' },
45
+      left: { start: 'transparent', end: '#0f46fa' }
46
+    },
47
+    bgGradientStart: 'rgba(33,33,58,0.95)',
48
+    bgGradientEnd: 'rgba(15,70,250,0.2)'
49
+  },
50
+  {
51
+    value: '130',
52
+    label: '主班在岗|旅检二部',
53
+    iconPath: zu02Icon,
54
+    vertical: true,
55
+    cornerRadius: 20,
56
+    vertices: {
57
+      topLeft: { x: 30, y: 0 },
58
+      topRight: { x: 300, y: 0 },
59
+      bottomRight: { x: 260, y: 200 },
60
+      bottomLeft: { x: -10, y: 200 }
61
+    },
62
+    cardContentStyle: {
63
+      gap: '10px'
64
+    },
65
+    edgeGradients: {
66
+      top: { start: '#0f46fa', end: 'transparent' },
67
+      right: { start: 'transparent', end: '#9903C1' },
68
+      bottom: { start: '#9903C1', end: 'transparent' },
69
+      left: { start: 'transparent', end: '#0f46fa' }
70
+    },
71
+    bgGradientStart: 'rgba(33,33,58,0.95)',
72
+    bgGradientEnd: 'rgba(189,3,251,0.15)'
73
+  },
74
+  {
75
+    value: '150',
76
+    label: '主班在岗|旅检三部',
77
+    iconPath: zu02Icon,
78
+    vertical: true,
79
+    cornerRadius: 20,
80
+    vertices: {
81
+      topLeft: { x: 10, y: 0 },
82
+      topRight: { x: 270, y: 0 },
83
+      bottomRight: { x: 270, y: 200 },
84
+      bottomLeft: { x: -30, y: 200 }
85
+    },
86
+    cardContentStyle: {
87
+      gap: '10px'
88
+    },
89
+    edgeGradients: {
90
+      top: { start: '#0f46fa', end: 'transparent' },
91
+      right: { start: 'transparent', end: '#9903C1' },
92
+      bottom: { start: '#9903C1', end: 'transparent' },
93
+      left: { start: 'transparent', end: '#0f46fa' }
94
+    },
95
+    bgGradientStart: 'rgba(33,33,58,0.98)',
96
+    bgGradientEnd: 'rgba(189,3,251,0.25)'
97
+  },
98
+  {
99
+    value: '80',
100
+    label: '备勤在岗|旅检一部',
101
+    iconPath: zu02Icon,
102
+    vertical: true,
103
+    cornerRadius: 20,
104
+    vertices: {
105
+      topLeft: { x: -20, y: 0 },
106
+      topRight: { x: 300, y: 0 },
107
+      bottomRight: { x: 320, y: 200 },
108
+      bottomLeft: { x: 0, y: 200 }
109
+    },
110
+    cardContentStyle: {
111
+      gap: '10px'
112
+    },
113
+    edgeGradients: {
114
+      top: { start: '#7eff7e', end: 'transparent' },
115
+      right: { start: 'transparent', end: '#9903C1' },
116
+      bottom: { start: '#9903C1', end: 'transparent' },
117
+      left: { start: 'transparent', end: '#7eff7e' }
118
+    },
119
+    bgGradientStart: 'rgba(33,33,58,0.95)',
120
+    bgGradientEnd: 'rgba(126,255,126,0.15)'
121
+  },
122
+  {
123
+    value: '70',
124
+    label: '备勤在岗|旅检二部',
125
+    iconPath: zu02Icon,
126
+    vertical: true,
127
+    cornerRadius: 20,
128
+    vertices: {
129
+      topLeft: { x: 20, y: 0 },
130
+      topRight: { x: 300, y: 0 },
131
+      bottomRight: { x: 300, y: 200 },
132
+      bottomLeft: { x: 40, y: 200 }
133
+    },
134
+    cardContentStyle: {
135
+      gap: '10px'
136
+    },
137
+    edgeGradients: {
138
+      top: { start: '#0f46fa', end: 'transparent' },
139
+      right: { start: 'transparent', end: '#9903C1' },
140
+      bottom: { start: '#9903C1', end: 'transparent' },
141
+      left: { start: 'transparent', end: '#0f46fa' }
142
+    },
143
+    bgGradientStart: 'rgba(33,33,58,0.95)',
144
+    bgGradientEnd: 'rgba(15,70,250,0.2)'
145
+  },
146
+  {
147
+    value: '65',
148
+    label: '备勤在岗|旅检三部',
149
+    iconPath: zu02Icon,
150
+    vertical: true,
151
+    cornerRadius: 20,
152
+    vertices: {
153
+      topLeft: { x: 0, y: 0 },
154
+      topRight: { x: 300, y: 0 },
155
+      bottomRight: { x: 300, y: 200 },
156
+      bottomLeft: { x: 0, y: 200 }
157
+    },
158
+    cardContentStyle: {
159
+      gap: '10px'
160
+    },
161
+    edgeGradients: {
162
+      top: { start: '#0f46fa', end: 'transparent' },
163
+      right: { start: 'transparent', end: '#9903C1' },
164
+      bottom: { start: '#9903C1', end: 'transparent' },
165
+      left: { start: 'transparent', end: '#0f46fa' }
166
+    },
167
+    bgGradientStart: 'rgba(33,33,58,0.95)',
168
+    bgGradientEnd: 'rgba(15,70,250,0.2)'
169
+  }
170
+])
171
+
172
+const handleSearch = (params) => {
173
+  queryParams.value = params
174
+}
175
+
176
+const handleTabChange = (index) => {
177
+  activeTab.value = index
178
+}
179
+</script>
180
+
181
+<style lang="scss" scoped>
182
+.station-profile-page {
183
+  width: 100%;
184
+  min-height: 100vh;
185
+  overflow-y: auto;
186
+}
187
+</style>