Quellcode durchsuchen

feat(portraitManagement): 完善各层级人事画像大屏功能

1. 为SearchBar新增deptType参数,适配不同层级部门搜索选择
2. 重构站点画像运行数据页面,移除静态模拟数据改为动态接口获取
3. 迁移部门/团队/小组画像的统计组件到运行数据页,精简profile组件代码
4. 新增部门/团队/小组画像的动态数据获取逻辑
huoyi vor 3 Wochen
Ursprung
Commit
e52f230ec8

+ 152 - 85
src/views/portraitManagement/components/SearchBar.vue

@@ -1,44 +1,40 @@
1 1
 <template>
2 2
   <div class="ep-topbar" @click="visible = false">
3 3
     <div class="time-btns">
4
-      <div :class="currentTime==='week' ? 'primary' : 'default'" @click="selectTime('week')">近一周</div>
5
-      <div :class="currentTime==='month' ? 'primary' : 'default'" @click="selectTime('month')">近一月</div>
6
-      <div :class="currentTime==='quarter' ? 'primary' : 'default'" @click="selectTime('quarter')">近三月</div>
7
-      <div :class="currentTime==='year' ? 'primary' : 'default'" @click="selectTime('year')">近一年</div>
8
-      <div :class="currentTime==='custom' ? 'primary' : 'default'" @click="selectTime('custom')">自定义时间范围</div>
9
-      <div class="custom-style-date-picker-wrap" v-if="currentTime==='custom'">
10
-        <el-date-picker
11
-          class="custom-style-date-picker"
12
-          v-model="dateRange"
13
-          type="daterange"
14
-          range-separator="至"
15
-          start-placeholder="开始"
16
-          end-placeholder="结束"
17
-          style="width:320px"
18
-          @change="() => searchHandler()"
19
-        />
4
+      <div :class="currentTime === 'week' ? 'primary' : 'default'" @click="selectTime('week')">近一周</div>
5
+      <div :class="currentTime === 'month' ? 'primary' : 'default'" @click="selectTime('month')">近一月</div>
6
+      <div :class="currentTime === 'quarter' ? 'primary' : 'default'" @click="selectTime('quarter')">近三月</div>
7
+      <div :class="currentTime === 'year' ? 'primary' : 'default'" @click="selectTime('year')">近一年</div>
8
+      <div :class="currentTime === 'custom' ? 'primary' : 'default'" @click="selectTime('custom')">自定义时间范围</div>
9
+      <div class="custom-style-date-picker-wrap" v-if="currentTime === 'custom'">
10
+        <el-date-picker class="custom-style-date-picker" v-model="dateRange" type="daterange" range-separator="至"
11
+          start-placeholder="开始" end-placeholder="结束" style="width:320px" @change="() => searchHandler()" />
20 12
       </div>
21 13
     </div>
22 14
     <el-popover class="popover" title="" :visible="visible" placement="bottom-start" trigger="click" width="45vw">
23 15
       <template #reference>
24
-        <div class="primary" style="border-radius: 6px;" @click.stop="visible = !visible">组织架构/模糊搜索</div>
16
+        <div class="primary" style="border-radius: 6px;" @click.stop="visible = !visible">{{ props.deptType === 'user' ?
17
+          '组织架构/模糊搜索' : '部门选择' }}</div>
25 18
       </template>
26 19
 
27 20
       <div class="custom-el-style">
28 21
         <div>
29
-          <el-autocomplete v-model="personName" :fetch-suggestions="queryUsers" placeholder="搜索员工/团队画像" style="width:320px;" clearable>
30
-            <template #suffix
31
-              ><el-icon><Search /></el-icon
32
-            ></template>
22
+          <el-autocomplete v-model="personName" :fetch-suggestions="props.deptType === 'user' ? queryUsers : queryDept"
23
+            :placeholder="props.deptType === 'user' ? '搜索员工/团队画像' : '搜索部门'" style="width:320px;" clearable>
24
+            <template #suffix><el-icon>
25
+                <Search />
26
+              </el-icon></template>
33 27
             <template #default="{ item }">
34
-              <span>{{ item.nickName }}</span>
35
-              <span v-if="item.deptName" style="font-size:12px;color:#999;margin-left:8px">{{ item.deptName }}</span>
28
+              <span>{{ item.nickName || item.deptName }}</span>
29
+              <span v-if="item.deptName && item.nickName" style="font-size:12px;color:#999;margin-left:8px">{{
30
+                item.deptName }}</span>
36 31
             </template>
37 32
           </el-autocomplete>
38 33
           <div class="primary" style="border-radius: 6px;" @click="() => searchHandler()">模糊搜索</div>
39 34
         </div>
40 35
         <div style="margin-top: 20px;">
41
-          <el-tree :data="departments" :default-expanded-keys="[100]" :props="{ value: 'id', showPrefix: false }" accordion @node-click="handleNodeClick" />
36
+          <el-tree :data="departments" :default-expanded-keys="[100]" :props="{ value: 'id', showPrefix: false }"
37
+            accordion @node-click="handleNodeClick" />
42 38
         </div>
43 39
       </div>
44 40
     </el-popover>
@@ -48,7 +44,15 @@
48 44
 <script setup>
49 45
 import { onMounted, onUnmounted } from 'vue'
50 46
 import { searchPortraitUsers } from '@/api/score/index'
51
-import { getDeptUserTree } from '@/api/item/items'
47
+import { getDeptList } from '@/api/item/items'
48
+import { listDept } from '@/api/system/dept'
49
+
50
+const props = defineProps({
51
+  deptType: {
52
+    type: String,
53
+    default: 'user'
54
+  }
55
+})
52 56
 
53 57
 const visible = defineModel('visible', false)
54 58
 const emit = defineEmits(['search'])
@@ -65,6 +69,31 @@ const queryUsers = async (query, cb) => {
65 69
   } catch (_) { cb([]) }
66 70
 }
67 71
 
72
+const queryDept = async (query, cb) => {
73
+  if (!query?.trim()) { cb([]); return }
74
+  try {
75
+    const res = await listDept({ deptName: query.trim(), deptType: props.deptType })
76
+    cb((res.data || []).map(d => ({ ...d, value: d.deptName })))
77
+  } catch (_) { cb([]) }
78
+}
79
+
80
+const filterDeptTree = (data, targetType) => {
81
+  const filterNode = (node) => {
82
+    if (node.deptType === targetType) {
83
+      return { ...node, children: undefined }
84
+    }
85
+    if (node.children && node.children.length > 0) {
86
+      const filteredChildren = node.children.map(child => filterNode(child)).filter(Boolean)
87
+      if (filteredChildren.length > 0) {
88
+        return { ...node, children: filteredChildren }
89
+      }
90
+    }
91
+    return null
92
+  }
93
+
94
+  return data.map(node => filterNode(node)).filter(Boolean)
95
+}
96
+
68 97
 const getTimeRange = () => {
69 98
   if (currentTime.value === 'custom') {
70 99
     if (dateRange.value?.length === 2) {
@@ -73,16 +102,16 @@ const getTimeRange = () => {
73 102
     return {}
74 103
   }
75 104
   const end = new Date(), begin = new Date()
76
-  if (currentTime.value === 'week') begin.setDate(end.getDate()-7)
77
-  else if (currentTime.value === 'month') begin.setMonth(end.getMonth()-1)
78
-  else if (currentTime.value === 'quarter') begin.setMonth(end.getMonth()-3)
79
-  else begin.setFullYear(end.getFullYear()-1)
105
+  if (currentTime.value === 'week') begin.setDate(end.getDate() - 7)
106
+  else if (currentTime.value === 'month') begin.setMonth(end.getMonth() - 1)
107
+  else if (currentTime.value === 'quarter') begin.setMonth(end.getMonth() - 3)
108
+  else begin.setFullYear(end.getFullYear() - 1)
80 109
   return { beginTime: formatDate(begin), endTime: formatDate(end) }
81 110
 }
82 111
 
83 112
 const formatDate = (d) => {
84 113
   const dt = new Date(d)
85
-  return `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`
114
+  return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}`
86 115
 }
87 116
 
88 117
 const selectTime = (t) => {
@@ -91,15 +120,37 @@ const selectTime = (t) => {
91 120
 }
92 121
 
93 122
 const searchHandler = (query = {}) => {
94
-  const queryParams = { ...getTimeRange(), personName: personName.value, ...query }
95
-  if (queryParams.personName) {
123
+  const queryParams = { ...getTimeRange(), ...query }
124
+  if (props.deptType === 'user') {
125
+    if (queryParams.personName) {
126
+      emit('search', queryParams)
127
+    }
128
+  } else {
96 129
     emit('search', queryParams)
97 130
   }
98 131
 }
99 132
 
100 133
 const handleNodeClick = (node) => {
101
-  if (node.nodeType === 'user') {
102
-    searchHandler({ personName: node.label })
134
+  if (props.deptType === 'user') {
135
+    if (node.nodeType === 'user') {
136
+      personName.value = node.label
137
+      searchHandler({ personName: node.label })
138
+    }
139
+  } else {
140
+    if (node.deptType === props.deptType) {
141
+      personName.value = node.deptName || node.name || node.label;
142
+      let obj = {}
143
+      if(props.deptType === 'BRIGADE') {
144
+        obj = { deptId: node.deptId || node.id }
145
+      } 
146
+      if(props.deptType === 'MANAGER') {
147
+        obj = { teamId: node.deptId || node.id }
148
+      }
149
+      if(props.deptType === 'TEAMS') {
150
+        obj = { groupId: node.deptId || node.id }
151
+      }
152
+      searchHandler(obj)
153
+    }
103 154
   }
104 155
 }
105 156
 
@@ -126,17 +177,20 @@ const resetStyle = () => {
126 177
 
127 178
 onMounted(async () => {
128 179
   setStyle()
129
-  const res = await getDeptUserTree()
130
-  departments.value = res.data
180
+  const res = await getDeptList({ deptType: props.deptType })
181
+  if (props.deptType === 'user') {
182
+    departments.value = res.data
183
+  } else {
184
+    departments.value = filterDeptTree(res.data, props.deptType)
185
+  }
131 186
 })
132 187
 onUnmounted(() => {
133 188
   resetStyle()
134 189
 })
135 190
 
136 191
 defineExpose({
137
-  getDefQuery () {
192
+  getDefQuery() {
138 193
     return {
139
-      personName: '',
140 194
       ...getTimeRange()
141 195
     }
142 196
   }
@@ -160,21 +214,25 @@ defineExpose({
160 214
     gap: 10px;
161 215
     flex-wrap: wrap;
162 216
     margin-right: 40px;
163
-    & > div {
217
+
218
+    &>div {
164 219
       padding: 8px 24px;
165 220
       border-radius: 28px;
166 221
       font-weight: 500;
167 222
     }
168 223
   }
224
+
169 225
   .primary {
170 226
     background: linear-gradient(125deg, #2AB3E6, #9605FC);
171 227
     color: #fff;
172 228
     padding: 8px 24px;
173 229
   }
230
+
174 231
   .default {
175 232
     background: #1C1936;
176 233
     color: #4C4C93;
177 234
     position: relative;
235
+
178 236
     &::before {
179 237
       content: '';
180 238
       position: absolute;
@@ -186,12 +244,14 @@ defineExpose({
186 244
       opacity: 0.5;
187 245
     }
188 246
   }
247
+
189 248
   .custom-style-date-picker-wrap {
190 249
     position: relative;
191 250
     border-radius: 19px;
192 251
     height: 38px;
193 252
     padding: 0 !important;
194 253
     --el-text-color-primary: #fff;
254
+
195 255
     &::before {
196 256
       content: '';
197 257
       position: absolute;
@@ -207,53 +267,60 @@ defineExpose({
207 267
 </style>
208 268
 
209 269
 <style lang="scss">
210
-  .custom-el-style {
211
-    width:100%;
212
-    padding: 10px;
213
-    position: relative;
214
-    background: #1c1936;
215
-    border-radius: 8px;
216
-    --el-text-color-regular: #fff;
217
-    --el-fill-color-blank: #17122A;
218
-    --el-border-color: linear-gradient(150deg, #0f46fa 0%, #bd03fb 100%);
219
-    --el-border-color-hover: linear-gradient(150deg, #0f46fa 0%, #bd03fb 100%);
220
-    .primary {
221
-      background: linear-gradient(125deg, #2AB3E6, #9605FC);
222
-      color: #fff;
223
-      padding: 6px 20px;
224
-      display: inline-block;
225
-      margin-left: 15px;
226
-    }
227
-    &::before {
228
-      content: '';
229
-      position: absolute;
230
-      inset: -2px;
231
-      border-radius: 10px;
232
-      background: linear-gradient(150deg, #0f46fa 0%, #0f46fa 10%, #020E15, #020E15, #020E15, #bd03fb 90%, #bd03fb 100%);
233
-      filter: brightness(2) contrast(150%);
234
-      z-index: -2;
235
-      opacity: 0.5;
236
-    }
237
-    .el-input__wrapper {
238
-    }
239
-    .el-tree {
240
-      background-color: #1c1936;
241
-      color: #fff;
242
-      .el-tree-node__content {
243
-        --el-tree-node-hover-bg-color: #5c676d;
244
-      }
245
-    }
246
-  }
247
-  .el-autocomplete-suggestion {
248
-    background: #17122A;
270
+.custom-el-style {
271
+  width: 100%;
272
+  padding: 10px;
273
+  position: relative;
274
+  background: #1c1936;
275
+  border-radius: 8px;
276
+  --el-text-color-regular: #fff;
277
+  --el-fill-color-blank: #17122A;
278
+  --el-border-color: linear-gradient(150deg, #0f46fa 0%, #bd03fb 100%);
279
+  --el-border-color-hover: linear-gradient(150deg, #0f46fa 0%, #bd03fb 100%);
280
+
281
+  .primary {
282
+    background: linear-gradient(125deg, #2AB3E6, #9605FC);
283
+    color: #fff;
284
+    padding: 6px 20px;
285
+    display: inline-block;
286
+    margin-left: 15px;
249 287
   }
250
-  .custom-style-date-picker {
251
-    background: #1C1936 !important;
252
-    box-shadow: none !important;
253
-    height: 38px !important;
254
-    border-radius: 19px;
288
+
289
+  &::before {
290
+    content: '';
291
+    position: absolute;
292
+    inset: -2px;
293
+    border-radius: 10px;
294
+    background: linear-gradient(150deg, #0f46fa 0%, #0f46fa 10%, #020E15, #020E15, #020E15, #bd03fb 90%, #bd03fb 100%);
295
+    filter: brightness(2) contrast(150%);
296
+    z-index: -2;
297
+    opacity: 0.5;
255 298
   }
256
-  .el-popper {
257
-    --el-popover-padding: 0px;
299
+
300
+  .el-input__wrapper {}
301
+
302
+  .el-tree {
303
+    background-color: #1c1936;
304
+    color: #fff;
305
+
306
+    .el-tree-node__content {
307
+      --el-tree-node-hover-bg-color: #5c676d;
308
+    }
258 309
   }
310
+}
311
+
312
+.el-autocomplete-suggestion {
313
+  background: #17122A;
314
+}
315
+
316
+.custom-style-date-picker {
317
+  background: #1C1936 !important;
318
+  box-shadow: none !important;
319
+  height: 38px !important;
320
+  border-radius: 19px;
321
+}
322
+
323
+.el-popper {
324
+  --el-popover-padding: 0px;
325
+}
259 326
 </style>

+ 0 - 52
src/views/portraitManagement/deptProfile/component/profile.vue

@@ -26,33 +26,6 @@
26 26
           :chartData3="postData"
27 27
         />
28 28
       </div>
29
-
30
-      <div class="content-row">
31
-        <ProfilePassRate
32
-          :chartData1="passRatePassData"
33
-          :chartData2="passRateLineData"
34
-        />
35
-      </div>
36
-
37
-      <div class="content-row">
38
-        <ProfileSeizedInfo :chartData1="seizedTotal" />
39
-        <ProfileSeizedDistribution :chartData1="seizedDistributionData" />
40
-      </div>
41
-
42
-      <div class="content-row">
43
-        <ProfileDailySeizedLine :chartData1="dailyLineData" />
44
-        <ProfileDailySeizedArea
45
-          :chartData1="dailyAreaPersonA"
46
-          :chartData2="dailyAreaPersonB"
47
-          :chartData3="dailyAreaPersonC"
48
-          :chartData4="dailyAreaPersonD"
49
-          :chartData5="dailyAreaPersonE"
50
-        />
51
-      </div>
52
-
53
-      <div class="content-row">
54
-        <ProfileDailySeizedBar :chartData1="dailyBarData" />
55
-      </div>
56 29
     </div>
57 30
   </div>
58 31
 </template>
@@ -63,12 +36,6 @@ import ProfileRadar from '../../components/ProfileRadar.vue'
63 36
 import ProfileMembers from '../../components/ProfileMembers.vue'
64 37
 import ProfileBasicDistribution from '../../components/ProfileBasicDistribution.vue'
65 38
 import ProfilePositionDistribution from '../../components/ProfilePositionDistribution.vue'
66
-import ProfilePassRate from '../../components/ProfilePassRate.vue'
67
-import ProfileSeizedInfo from '../../components/ProfileSeizedInfo.vue'
68
-import ProfileSeizedDistribution from '../../components/ProfileSeizedDistribution.vue'
69
-import ProfileDailySeizedLine from '../../components/ProfileDailySeizedLine.vue'
70
-import ProfileDailySeizedArea from '../../components/ProfileDailySeizedArea.vue'
71
-import ProfileDailySeizedBar from '../../components/ProfileDailySeizedBar.vue'
72 39
 
73 40
 const props = defineProps({
74 41
   queryParams: {
@@ -159,25 +126,6 @@ const postData = ref({
159 126
   colors: ['#ff6b6b', '#ee5a24']
160 127
 })
161 128
 
162
-const passRatePassData = ref([370, 352, 330, 387, 426, 342, 325, 384, 445, 407, 382, 401, 380, 464, 342, 443, 305, 394, 334, 430, 364, 362, 400, 420])
163
-const passRateLineData = ref([4.5, 5, 4, 6, 7, 5, 4, 6, 8, 7, 6, 7, 6, 8, 5, 7, 4, 6, 5, 7, 6, 6, 6.5, 7])
164
-
165
-const seizedTotal = ref(1658)
166
-
167
-const seizedDistributionData = ref([
168
-  { value: 20, name: '打火机', itemStyle: { color: '#ff9f43' } },
169
-  { value: 30, name: '管制刀具', itemStyle: { color: '#ff6b6b' } },
170
-  { value: 50, name: '其他违禁品', itemStyle: { color: '#4da6ff' } }
171
-])
172
-
173
-const dailyLineData = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
174
-const dailyAreaPersonA = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
175
-const dailyAreaPersonB = ref([220, 182, 191, 234, 290, 330, 310, 123, 144, 210, 182, 191])
176
-const dailyAreaPersonC = ref([150, 232, 201, 154, 190, 330, 410, 201, 154, 190, 330, 410])
177
-const dailyAreaPersonD = ref([320, 332, 301, 334, 390, 330, 320, 332, 301, 334, 390, 330])
178
-const dailyAreaPersonE = ref([820, 932, 901, 934, 1290, 1330, 1320, 820, 932, 901, 934, 1290])
179
-const dailyBarData = ref([120, 200, 150, 80, 70, 110, 130, 90, 160, 140, 180])
180
-
181 129
 watch(() => props.queryParams, (newParams) => {
182 130
   fetchData(newParams)
183 131
 }, { deep: true })

+ 52 - 0
src/views/portraitManagement/deptProfile/component/runData.vue

@@ -1,6 +1,33 @@
1 1
 <template>
2 2
   <div class="operation-data">
3 3
     <div class="row">
4
+      <ProfilePassRate
5
+        :chartData1="passRatePassData"
6
+        :chartData2="passRateLineData"
7
+      />
8
+    </div>
9
+
10
+    <div class="row">
11
+      <ProfileSeizedInfo :chartData1="seizedTotal" />
12
+      <ProfileSeizedDistribution :chartData1="seizedDistributionData" />
13
+    </div>
14
+
15
+    <div class="row">
16
+      <ProfileDailySeizedLine :chartData1="dailyLineData" />
17
+      <ProfileDailySeizedArea
18
+        :chartData1="dailyAreaPersonA"
19
+        :chartData2="dailyAreaPersonB"
20
+        :chartData3="dailyAreaPersonC"
21
+        :chartData4="dailyAreaPersonD"
22
+        :chartData5="dailyAreaPersonE"
23
+      />
24
+    </div>
25
+
26
+    <div class="row">
27
+      <ProfileDailySeizedBar :chartData1="dailyBarData" />
28
+    </div>
29
+
30
+    <div class="row">
4 31
       <ChannelCheckChart :chartsData="chartsData" />
5 32
     </div>
6 33
     <div class="row">
@@ -70,7 +97,32 @@ import TestResult from '../../components/TestResult.vue';
70 97
 import TestArea from '../../components/TestArea.vue';
71 98
 import SupervisionDistribution from '../../components/SupervisionDistribution.vue';
72 99
 import InterceptionDistribution from '../../components/InterceptionDistribution.vue';
100
+import ProfilePassRate from '../../components/ProfilePassRate.vue';
101
+import ProfileSeizedInfo from '../../components/ProfileSeizedInfo.vue';
102
+import ProfileSeizedDistribution from '../../components/ProfileSeizedDistribution.vue';
103
+import ProfileDailySeizedLine from '../../components/ProfileDailySeizedLine.vue';
104
+import ProfileDailySeizedArea from '../../components/ProfileDailySeizedArea.vue';
105
+import ProfileDailySeizedBar from '../../components/ProfileDailySeizedBar.vue';
106
+
107
+
108
+const passRatePassData = ref([370, 352, 330, 387, 426, 342, 325, 384, 445, 407, 382, 401, 380, 464, 342, 443, 305, 394, 334, 430, 364, 362, 400, 420])
109
+const passRateLineData = ref([4.5, 5, 4, 6, 7, 5, 4, 6, 8, 7, 6, 7, 6, 8, 5, 7, 4, 6, 5, 7, 6, 6, 6.5, 7])
110
+
111
+const seizedTotal = ref(1658)
112
+
113
+const seizedDistributionData = ref([
114
+  { value: 20, name: '打火机', itemStyle: { color: '#ff9f43' } },
115
+  { value: 30, name: '管制刀具', itemStyle: { color: '#ff6b6b' } },
116
+  { value: 50, name: '其他违禁品', itemStyle: { color: '#4da6ff' } }
117
+])
73 118
 
119
+const dailyLineData = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
120
+const dailyAreaPersonA = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
121
+const dailyAreaPersonB = ref([220, 182, 191, 234, 290, 330, 310, 123, 144, 210, 182, 191])
122
+const dailyAreaPersonC = ref([150, 232, 201, 154, 190, 330, 410, 201, 154, 190, 330, 410])
123
+const dailyAreaPersonD = ref([320, 332, 301, 334, 390, 330, 320, 332, 301, 334, 390, 330])
124
+const dailyAreaPersonE = ref([820, 932, 901, 934, 1290, 1330, 1320, 820, 932, 901, 934, 1290])
125
+const dailyBarData = ref([120, 200, 150, 80, 70, 110, 130, 90, 160, 140, 180])
74 126
 
75 127
 const chartsData = ref([
76 128
   {

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

@@ -1,7 +1,7 @@
1 1
 <template>
2 2
   <div class="group-profile-page">
3 3
     <Page title="安检人事管理可视化大屏" :tabs="['能力画像', '运行数据']" @tab-change="handleTabChange">
4
-      <SearchBar v-model:visible="searchVisible" @search="handleSearch" />
4
+      <SearchBar v-model:visible="searchVisible" @search="handleSearch" :deptType="'BRIGADE'"/>
5 5
       <PentagonGroup :items="pentagonItems" />
6 6
       <Profile v-if="activeTab === 0" :query-params="queryParams" />
7 7
       <RunData v-else />

+ 0 - 52
src/views/portraitManagement/groupProfile/component/profile.vue

@@ -26,33 +26,6 @@
26 26
           :chartData3="postData"
27 27
         />
28 28
       </div>
29
-
30
-      <div class="content-row">
31
-        <ProfilePassRate
32
-          :chartData1="passRatePassData"
33
-          :chartData2="passRateLineData"
34
-        />
35
-      </div>
36
-
37
-      <div class="content-row">
38
-        <ProfileSeizedInfo :chartData1="seizedTotal" />
39
-        <ProfileSeizedDistribution :chartData1="seizedDistributionData" />
40
-      </div>
41
-
42
-      <div class="content-row">
43
-        <ProfileDailySeizedLine :chartData1="dailyLineData" />
44
-        <ProfileDailySeizedArea
45
-          :chartData1="dailyAreaPersonA"
46
-          :chartData2="dailyAreaPersonB"
47
-          :chartData3="dailyAreaPersonC"
48
-          :chartData4="dailyAreaPersonD"
49
-          :chartData5="dailyAreaPersonE"
50
-        />
51
-      </div>
52
-
53
-      <div class="content-row">
54
-        <ProfileDailySeizedBar :chartData1="dailyBarData" />
55
-      </div>
56 29
     </div>
57 30
   </div>
58 31
 </template>
@@ -63,12 +36,6 @@ import ProfileRadar from '../../components/ProfileRadar.vue'
63 36
 import ProfileMembers from '../../components/ProfileMembers.vue'
64 37
 import ProfileBasicDistribution from '../../components/ProfileBasicDistribution.vue'
65 38
 import ProfilePositionDistribution from '../../components/ProfilePositionDistribution.vue'
66
-import ProfilePassRate from '../../components/ProfilePassRate.vue'
67
-import ProfileSeizedInfo from '../../components/ProfileSeizedInfo.vue'
68
-import ProfileSeizedDistribution from '../../components/ProfileSeizedDistribution.vue'
69
-import ProfileDailySeizedLine from '../../components/ProfileDailySeizedLine.vue'
70
-import ProfileDailySeizedArea from '../../components/ProfileDailySeizedArea.vue'
71
-import ProfileDailySeizedBar from '../../components/ProfileDailySeizedBar.vue'
72 39
 
73 40
 const props = defineProps({
74 41
   queryParams: {
@@ -159,25 +126,6 @@ const postData = ref({
159 126
   colors: ['#ff6b6b', '#ee5a24']
160 127
 })
161 128
 
162
-const passRatePassData = ref([370, 352, 330, 387, 426, 342, 325, 384, 445, 407, 382, 401, 380, 464, 342, 443, 305, 394, 334, 430, 364, 362, 400, 420])
163
-const passRateLineData = ref([4.5, 5, 4, 6, 7, 5, 4, 6, 8, 7, 6, 7, 6, 8, 5, 7, 4, 6, 5, 7, 6, 6, 6.5, 7])
164
-
165
-const seizedTotal = ref(1658)
166
-
167
-const seizedDistributionData = ref([
168
-  { value: 20, name: '打火机', itemStyle: { color: '#ff9f43' } },
169
-  { value: 30, name: '管制刀具', itemStyle: { color: '#ff6b6b' } },
170
-  { value: 50, name: '其他违禁品', itemStyle: { color: '#4da6ff' } }
171
-])
172
-
173
-const dailyLineData = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
174
-const dailyAreaPersonA = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
175
-const dailyAreaPersonB = ref([220, 182, 191, 234, 290, 330, 310, 123, 144, 210, 182, 191])
176
-const dailyAreaPersonC = ref([150, 232, 201, 154, 190, 330, 410, 201, 154, 190, 330, 410])
177
-const dailyAreaPersonD = ref([320, 332, 301, 334, 390, 330, 320, 332, 301, 334, 390, 330])
178
-const dailyAreaPersonE = ref([820, 932, 901, 934, 1290, 1330, 1320, 820, 932, 901, 934, 1290])
179
-const dailyBarData = ref([120, 200, 150, 80, 70, 110, 130, 90, 160, 140, 180])
180
-
181 129
 watch(() => props.queryParams, (newParams) => {
182 130
   fetchData(newParams)
183 131
 }, { deep: true })

+ 102 - 2
src/views/portraitManagement/groupProfile/component/runData.vue

@@ -1,6 +1,33 @@
1 1
 <template>
2 2
   <div class="operation-data">
3 3
     <div class="row">
4
+      <ProfilePassRate
5
+        :chartData1="passRatePassData"
6
+        :chartData2="passRateLineData"
7
+      />
8
+    </div>
9
+
10
+    <div class="row">
11
+      <ProfileSeizedInfo :chartData1="seizedTotal" />
12
+      <ProfileSeizedDistribution :chartData1="seizedDistributionData" />
13
+    </div>
14
+
15
+    <div class="row">
16
+      <ProfileDailySeizedLine :chartData1="dailyLineData" />
17
+      <ProfileDailySeizedArea
18
+        :chartData1="dailyAreaPersonA"
19
+        :chartData2="dailyAreaPersonB"
20
+        :chartData3="dailyAreaPersonC"
21
+        :chartData4="dailyAreaPersonD"
22
+        :chartData5="dailyAreaPersonE"
23
+      />
24
+    </div>
25
+
26
+    <div class="row">
27
+      <ProfileDailySeizedBar :chartData1="dailyBarData" />
28
+    </div>
29
+
30
+    <div class="row">
4 31
       <ChannelCheckChart :chartsData="chartsData" />
5 32
     </div>
6 33
     <div class="row">
@@ -46,16 +73,17 @@
46 73
     </div>
47 74
     <div class="row">
48 75
       <div class="col">
49
-        <SupervisionDistribution :chartsData="chartsData"/>
76
+        <SupervisionDistribution :chartsData="supervisionData"/>
50 77
       </div>
51 78
       <div class="col">
52
-        <InterceptionDistribution :chartsData="chartsData" />
79
+        <InterceptionDistribution :chartsData="interceptionData" />
53 80
       </div>
54 81
     </div>
55 82
   </div>
56 83
 </template>
57 84
 
58 85
 <script setup>
86
+import { watch } from 'vue'
59 87
 import ChannelCheckChart from '../../components/ChannelCheckChart.vue';
60 88
 import SeizedInfo from '../../components/SeizedInfo.vue';
61 89
 import SeizedItems from '../../components/SeizedItems.vue';
@@ -70,7 +98,39 @@ import TestResult from '../../components/TestResult.vue';
70 98
 import TestArea from '../../components/TestArea.vue';
71 99
 import SupervisionDistribution from '../../components/SupervisionDistribution.vue';
72 100
 import InterceptionDistribution from '../../components/InterceptionDistribution.vue';
101
+import ProfilePassRate from '../../components/ProfilePassRate.vue';
102
+import ProfileSeizedInfo from '../../components/ProfileSeizedInfo.vue';
103
+import ProfileSeizedDistribution from '../../components/ProfileSeizedDistribution.vue';
104
+import ProfileDailySeizedLine from '../../components/ProfileDailySeizedLine.vue';
105
+import ProfileDailySeizedArea from '../../components/ProfileDailySeizedArea.vue';
106
+import ProfileDailySeizedBar from '../../components/ProfileDailySeizedBar.vue';
107
+import { realtimeInterceptionItem, supervisionProblemPosition } from '@/api/portraitManagement/portraitManagement';
108
+
109
+const props = defineProps({
110
+  queryParams: {
111
+    type: Object,
112
+    default: () => ({})
113
+  }
114
+})
115
+
116
+const passRatePassData = ref([370, 352, 330, 387, 426, 342, 325, 384, 445, 407, 382, 401, 380, 464, 342, 443, 305, 394, 334, 430, 364, 362, 400, 420])
117
+const passRateLineData = ref([4.5, 5, 4, 6, 7, 5, 4, 6, 8, 7, 6, 7, 6, 8, 5, 7, 4, 6, 5, 7, 6, 6, 6.5, 7])
73 118
 
119
+const seizedTotal = ref(1658)
120
+
121
+const seizedDistributionData = ref([
122
+  { value: 20, name: '打火机', itemStyle: { color: '#ff9f43' } },
123
+  { value: 30, name: '管制刀具', itemStyle: { color: '#ff6b6b' } },
124
+  { value: 50, name: '其他违禁品', itemStyle: { color: '#4da6ff' } }
125
+])
126
+
127
+const dailyLineData = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
128
+const dailyAreaPersonA = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
129
+const dailyAreaPersonB = ref([220, 182, 191, 234, 290, 330, 310, 123, 144, 210, 182, 191])
130
+const dailyAreaPersonC = ref([150, 232, 201, 154, 190, 330, 410, 201, 154, 190, 330, 410])
131
+const dailyAreaPersonD = ref([320, 332, 301, 334, 390, 330, 320, 332, 301, 334, 390, 330])
132
+const dailyAreaPersonE = ref([820, 932, 901, 934, 1290, 1330, 1320, 820, 932, 901, 934, 1290])
133
+const dailyBarData = ref([120, 200, 150, 80, 70, 110, 130, 90, 160, 140, 180])
74 134
 
75 135
 const chartsData = ref([
76 136
   {
@@ -119,6 +179,46 @@ const chartsData = ref([
119 179
     name: '烟火'
120 180
   },
121 181
 ])
182
+
183
+const supervisionData = ref([])
184
+const interceptionData = ref([])
185
+
186
+const fetchSupervisionData = async () => {
187
+  try {
188
+    const res = await supervisionProblemPosition(props.queryParams)
189
+    if (res.code === 200 && res.data) {
190
+      supervisionData.value = res.data.map(item => ({
191
+        num: item.total,
192
+        name: item.name
193
+      }))
194
+    }
195
+  } catch (error) {
196
+    console.error('获取各岗位监察问题分布数据失败', error)
197
+  }
198
+}
199
+
200
+const fetchInterceptionData = async () => {
201
+  try {
202
+    const res = await realtimeInterceptionItem(props.queryParams)
203
+    if (res.code === 200 && res.data) {
204
+      interceptionData.value = res.data.map(item => ({
205
+        num: item.total,
206
+        name: item.name
207
+      }))
208
+    }
209
+  } catch (error) {
210
+    console.error('获取实时质控拦截物品分布数据失败', error)
211
+  }
212
+}
213
+
214
+const fetchData = () => {
215
+  fetchSupervisionData()
216
+  fetchInterceptionData()
217
+}
218
+
219
+watch(() => props.queryParams, () => {
220
+  fetchData()
221
+}, { deep: true, immediate: true })
122 222
   
123 223
 
124 224
 </script>

+ 1 - 1
src/views/portraitManagement/groupProfile/index.vue

@@ -1,7 +1,7 @@
1 1
 <template>
2 2
   <div class="group-profile-page">
3 3
     <Page title="安检人事管理可视化大屏" :tabs="['能力画像', '运行数据']" @tab-change="handleTabChange">
4
-      <SearchBar v-model:visible="searchVisible" @search="handleSearch" />
4
+      <SearchBar v-model:visible="searchVisible" @search="handleSearch"  :deptType="'TEAMS'" />
5 5
       <PentagonGroup :items="pentagonItems" />
6 6
       <Profile v-if="activeTab === 0" :query-params="queryParams" />
7 7
       <RunData v-else />

+ 33 - 13
src/views/portraitManagement/stationProfile/component/runData.vue

@@ -87,7 +87,7 @@ import EventWorkArea from '../../components/EventWorkArea.vue'
87 87
 import TestItems from '../../components/TestItems.vue'
88 88
 import TestResult from '../../components/TestResult.vue'
89 89
 import TestArea from '../../components/TestArea.vue'
90
-import { securityTestItemClassification } from '@/api/portraitManagement/portraitManagement'
90
+import { securityTestItemClassification, securityTestPassingStatus, securityTestRegion } from '@/api/portraitManagement/portraitManagement'
91 91
 
92 92
 const props = defineProps({
93 93
   queryParams: {
@@ -157,9 +157,41 @@ const fetchSeizedItemsData = async () => {
157 157
     console.error('获取安保测试物品分类数据失败', error)
158 158
   }
159 159
 }
160
+const securityTestPassData = ref([])
161
+const securityTestAreaData = ref([])
162
+
163
+const fetchSecurityTestPassData = async () => {
164
+  try {
165
+    const res = await securityTestPassingStatus(props.queryParams)
166
+    if (res.code === 200 && res.data) {
167
+      securityTestPassData.value = res.data.map(item => ({
168
+        num: item.total,
169
+        name: item.name
170
+      }))
171
+    }
172
+  } catch (error) {
173
+    console.error('获取安保测试通过情况数据失败', error)
174
+  }
175
+}
176
+
177
+const fetchSecurityTestAreaData = async () => {
178
+  try {
179
+    const res = await securityTestRegion(props.queryParams)
180
+    if (res.code === 200 && res.data) {
181
+      securityTestAreaData.value = res.data.map(item => ({
182
+        num: item.total,
183
+        name: item.name
184
+      }))
185
+    }
186
+  } catch (error) {
187
+    console.error('获取安保测试区域情况数据失败', error)
188
+  }
189
+}
160 190
 
161 191
 const fetchData = () => {
162 192
   fetchSeizedItemsData()
193
+  fetchSecurityTestPassData()
194
+  fetchSecurityTestAreaData()
163 195
 }
164 196
 
165 197
 watch(() => props.queryParams, () => {
@@ -216,19 +248,7 @@ const unsafePostDistData = [
216 248
 
217 249
 
218 250
 
219
-const securityTestPassData = [
220
-  { num: 44, name: '通过' },
221
-  { num: 4, name: '未通过' }
222
-]
223 251
 
224
-const securityTestAreaData = [
225
-  { num: 100, name: 'T3国内出发(4层)' },
226
-  { num: 246, name: 'T3(8层出发)' },
227
-  { num: 160, name: 'T3国际出发' },
228
-  { num: 300, name: 'T3国际转国内' },
229
-  { num: 100, name: 'T1要客服务区西' },
230
-  { num: 167, name: 'T1要客服务区东' }
231
-]
232 252
 
233 253
 const hourlyPassChartRef = ref(null)
234 254
 let hourlyPassChart = null

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

@@ -1,7 +1,7 @@
1 1
 <template>
2 2
   <div class="station-profile-page">
3 3
     <Page title="旅检部综合信息展示大屏" :tabs="['站点画像', '运行数据']" @tab-change="handleTabChange">
4
-      <SearchBar v-model:visible="searchVisible" @search="handleSearch" />
4
+      <SearchBar v-model:visible="searchVisible" @search="handleSearch" :deptType="'STATION'" />
5 5
       <PentagonGroup :items="pentagonItems" />
6 6
       <Profile v-if="activeTab === 0" :query-params="queryParams" />
7 7
       <RunData v-else :query-params="queryParams" />

+ 0 - 52
src/views/portraitManagement/teamProfile/component/profile.vue

@@ -26,33 +26,6 @@
26 26
           :chartData3="postData"
27 27
         />
28 28
       </div>
29
-
30
-      <div class="content-row">
31
-        <ProfilePassRate
32
-          :chartData1="passRatePassData"
33
-          :chartData2="passRateLineData"
34
-        />
35
-      </div>
36
-
37
-      <div class="content-row">
38
-        <ProfileSeizedInfo :chartData1="seizedTotal" />
39
-        <ProfileSeizedDistribution :chartData1="seizedDistributionData" />
40
-      </div>
41
-
42
-      <div class="content-row">
43
-        <ProfileDailySeizedLine :chartData1="dailyLineData" />
44
-        <ProfileDailySeizedArea
45
-          :chartData1="dailyAreaPersonA"
46
-          :chartData2="dailyAreaPersonB"
47
-          :chartData3="dailyAreaPersonC"
48
-          :chartData4="dailyAreaPersonD"
49
-          :chartData5="dailyAreaPersonE"
50
-        />
51
-      </div>
52
-
53
-      <div class="content-row">
54
-        <ProfileDailySeizedBar :chartData1="dailyBarData" />
55
-      </div>
56 29
     </div>
57 30
   </div>
58 31
 </template>
@@ -63,12 +36,6 @@ import ProfileRadar from '../../components/ProfileRadar.vue'
63 36
 import ProfileMembers from '../../components/ProfileMembers.vue'
64 37
 import ProfileBasicDistribution from '../../components/ProfileBasicDistribution.vue'
65 38
 import ProfilePositionDistribution from '../../components/ProfilePositionDistribution.vue'
66
-import ProfilePassRate from '../../components/ProfilePassRate.vue'
67
-import ProfileSeizedInfo from '../../components/ProfileSeizedInfo.vue'
68
-import ProfileSeizedDistribution from '../../components/ProfileSeizedDistribution.vue'
69
-import ProfileDailySeizedLine from '../../components/ProfileDailySeizedLine.vue'
70
-import ProfileDailySeizedArea from '../../components/ProfileDailySeizedArea.vue'
71
-import ProfileDailySeizedBar from '../../components/ProfileDailySeizedBar.vue'
72 39
 
73 40
 const props = defineProps({
74 41
   queryParams: {
@@ -159,25 +126,6 @@ const postData = ref({
159 126
   colors: ['#ff6b6b', '#ee5a24']
160 127
 })
161 128
 
162
-const passRatePassData = ref([370, 352, 330, 387, 426, 342, 325, 384, 445, 407, 382, 401, 380, 464, 342, 443, 305, 394, 334, 430, 364, 362, 400, 420])
163
-const passRateLineData = ref([4.5, 5, 4, 6, 7, 5, 4, 6, 8, 7, 6, 7, 6, 8, 5, 7, 4, 6, 5, 7, 6, 6, 6.5, 7])
164
-
165
-const seizedTotal = ref(1658)
166
-
167
-const seizedDistributionData = ref([
168
-  { value: 20, name: '打火机', itemStyle: { color: '#ff9f43' } },
169
-  { value: 30, name: '管制刀具', itemStyle: { color: '#ff6b6b' } },
170
-  { value: 50, name: '其他违禁品', itemStyle: { color: '#4da6ff' } }
171
-])
172
-
173
-const dailyLineData = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
174
-const dailyAreaPersonA = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
175
-const dailyAreaPersonB = ref([220, 182, 191, 234, 290, 330, 310, 123, 144, 210, 182, 191])
176
-const dailyAreaPersonC = ref([150, 232, 201, 154, 190, 330, 410, 201, 154, 190, 330, 410])
177
-const dailyAreaPersonD = ref([320, 332, 301, 334, 390, 330, 320, 332, 301, 334, 390, 330])
178
-const dailyAreaPersonE = ref([820, 932, 901, 934, 1290, 1330, 1320, 820, 932, 901, 934, 1290])
179
-const dailyBarData = ref([120, 200, 150, 80, 70, 110, 130, 90, 160, 140, 180])
180
-
181 129
 watch(() => props.queryParams, (newParams) => {
182 130
   fetchData(newParams)
183 131
 }, { deep: true })

+ 52 - 0
src/views/portraitManagement/teamProfile/component/runData.vue

@@ -1,6 +1,33 @@
1 1
 <template>
2 2
   <div class="operation-data">
3 3
     <div class="row">
4
+      <ProfilePassRate
5
+        :chartData1="passRatePassData"
6
+        :chartData2="passRateLineData"
7
+      />
8
+    </div>
9
+
10
+    <div class="row">
11
+      <ProfileSeizedInfo :chartData1="seizedTotal" />
12
+      <ProfileSeizedDistribution :chartData1="seizedDistributionData" />
13
+    </div>
14
+
15
+    <div class="row">
16
+      <ProfileDailySeizedLine :chartData1="dailyLineData" />
17
+      <ProfileDailySeizedArea
18
+        :chartData1="dailyAreaPersonA"
19
+        :chartData2="dailyAreaPersonB"
20
+        :chartData3="dailyAreaPersonC"
21
+        :chartData4="dailyAreaPersonD"
22
+        :chartData5="dailyAreaPersonE"
23
+      />
24
+    </div>
25
+
26
+    <div class="row">
27
+      <ProfileDailySeizedBar :chartData1="dailyBarData" />
28
+    </div>
29
+
30
+    <div class="row">
4 31
       <ChannelCheckChart :chartsData="chartsData" />
5 32
     </div>
6 33
     <div class="row">
@@ -70,7 +97,32 @@ import TestResult from '../../components/TestResult.vue';
70 97
 import TestArea from '../../components/TestArea.vue';
71 98
 import SupervisionDistribution from '../../components/SupervisionDistribution.vue';
72 99
 import InterceptionDistribution from '../../components/InterceptionDistribution.vue';
100
+import ProfilePassRate from '../../components/ProfilePassRate.vue';
101
+import ProfileSeizedInfo from '../../components/ProfileSeizedInfo.vue';
102
+import ProfileSeizedDistribution from '../../components/ProfileSeizedDistribution.vue';
103
+import ProfileDailySeizedLine from '../../components/ProfileDailySeizedLine.vue';
104
+import ProfileDailySeizedArea from '../../components/ProfileDailySeizedArea.vue';
105
+import ProfileDailySeizedBar from '../../components/ProfileDailySeizedBar.vue';
106
+
107
+
108
+const passRatePassData = ref([370, 352, 330, 387, 426, 342, 325, 384, 445, 407, 382, 401, 380, 464, 342, 443, 305, 394, 334, 430, 364, 362, 400, 420])
109
+const passRateLineData = ref([4.5, 5, 4, 6, 7, 5, 4, 6, 8, 7, 6, 7, 6, 8, 5, 7, 4, 6, 5, 7, 6, 6, 6.5, 7])
110
+
111
+const seizedTotal = ref(1658)
112
+
113
+const seizedDistributionData = ref([
114
+  { value: 20, name: '打火机', itemStyle: { color: '#ff9f43' } },
115
+  { value: 30, name: '管制刀具', itemStyle: { color: '#ff6b6b' } },
116
+  { value: 50, name: '其他违禁品', itemStyle: { color: '#4da6ff' } }
117
+])
73 118
 
119
+const dailyLineData = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
120
+const dailyAreaPersonA = ref([120, 132, 101, 134, 90, 230, 210, 182, 191, 234, 290, 330])
121
+const dailyAreaPersonB = ref([220, 182, 191, 234, 290, 330, 310, 123, 144, 210, 182, 191])
122
+const dailyAreaPersonC = ref([150, 232, 201, 154, 190, 330, 410, 201, 154, 190, 330, 410])
123
+const dailyAreaPersonD = ref([320, 332, 301, 334, 390, 330, 320, 332, 301, 334, 390, 330])
124
+const dailyAreaPersonE = ref([820, 932, 901, 934, 1290, 1330, 1320, 820, 932, 901, 934, 1290])
125
+const dailyBarData = ref([120, 200, 150, 80, 70, 110, 130, 90, 160, 140, 180])
74 126
 
75 127
 const chartsData = ref([
76 128
   {

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

@@ -1,7 +1,7 @@
1 1
 <template>
2 2
   <div class="group-profile-page">
3 3
     <Page title="安检人事管理可视化大屏" :tabs="['能力画像', '运行数据']" @tab-change="handleTabChange">
4
-      <SearchBar v-model:visible="searchVisible" @search="handleSearch" />
4
+      <SearchBar v-model:visible="searchVisible" @search="handleSearch" :deptType="'MANAGER'"/>
5 5
       <PentagonGroup :items="pentagonItems" />
6 6
       <Profile v-if="activeTab === 0" :query-params="queryParams" />
7 7
       <RunData v-else />