Ver código fonte

feat: 员工画像雷达图浮窗改为点击维度触发,修复布局偏移

- 浮窗默认隐藏,点击雷达图维度后按角度计算显示对应加/扣分明细
- 再次点击同一维度收起,切换维度自动更新内容
- 面板改为 position:absolute 绝对定位,避免占用 flex 空间导致雷达图偏移
- 扣分贴左、加分贴右,chart-box 铺满全宽保证居中
- 行格式改为 level3Name : score,面板标题显示维度名与合计
- 加入 panel-fade 过渡动画

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
simonlll 1 mês atrás
pai
commit
2237a00595
1 arquivos alterados com 95 adições e 38 exclusões
  1. 95 38
      src/views/portraitManagement/employeeProfile/index.vue

+ 95 - 38
src/views/portraitManagement/employeeProfile/index.vue

@@ -130,39 +130,44 @@
130
           <div class="card radar-card">
130
           <div class="card radar-card">
131
             <div class="sec-title">● 个人能力</div>
131
             <div class="sec-title">● 个人能力</div>
132
             <div class="radar-body">
132
             <div class="radar-body">
133
-              <!-- 扣分明细浮窗 -->
134
-              <div class="score-panel deduct-panel">
135
-                <div class="panel-header deduct-header">
136
-                  扣分明细&nbsp;<span class="panel-total">合计 {{ deductTotal }}</span>
137
-                </div>
138
-                <div class="panel-list">
139
-                  <div v-for="(item, i) in deductList" :key="i" class="panel-row">
140
-                    <span class="p-dim">{{ item.dimensionName }}</span>
141
-                    <span class="p-name">{{ item.level2Name }}{{ item.level3Name ? '·'+item.level3Name : '' }}</span>
142
-                    <span class="p-score deduct-score">{{ item.totalScore }}</span>
133
+              <!-- 扣分明细浮窗(点击维度后显示,绝对定位左侧) -->
134
+              <transition name="panel-fade">
135
+                <div class="score-panel deduct-panel" v-if="activeDimName">
136
+                  <div class="panel-header deduct-header">
137
+                    扣分明细<span class="panel-dim-name">{{ activeDimName }}</span>
138
+                    <span class="panel-total">合计 {{ deductTotal }}</span>
139
+                  </div>
140
+                  <div class="panel-list">
141
+                    <div v-for="(item, i) in deductList" :key="i" class="panel-row">
142
+                      <span class="p-name">{{ item.level3Name || item.level2Name }}</span>
143
+                      <span class="p-score deduct-score">{{ item.totalScore }}</span>
144
+                    </div>
145
+                    <div v-if="!deductList.length" class="p-empty">暂无扣分记录</div>
143
                   </div>
146
                   </div>
144
-                  <div v-if="!deductList.length" class="p-empty">暂无扣分记录</div>
145
                 </div>
147
                 </div>
146
-              </div>
148
+              </transition>
147
 
149
 
148
               <!-- 雷达图 -->
150
               <!-- 雷达图 -->
149
               <div ref="abilityChart" class="chart-box"></div>
151
               <div ref="abilityChart" class="chart-box"></div>
150
 
152
 
151
-              <!-- 加分明细浮窗 -->
152
-              <div class="score-panel add-panel">
153
-                <div class="panel-header add-header">
154
-                  加分明细&nbsp;<span class="panel-total">合计 {{ addTotal }}</span>
155
-                </div>
156
-                <div class="panel-list">
157
-                  <div v-for="(item, i) in addList" :key="i" class="panel-row">
158
-                    <span class="p-dim">{{ item.dimensionName }}</span>
159
-                    <span class="p-name">{{ item.level2Name }}{{ item.level3Name ? '·'+item.level3Name : '' }}</span>
160
-                    <span class="p-score add-score">+{{ item.totalScore }}</span>
153
+              <!-- 加分明细浮窗(点击维度后显示,绝对定位右侧) -->
154
+              <transition name="panel-fade">
155
+                <div class="score-panel add-panel" v-if="activeDimName">
156
+                  <div class="panel-header add-header">
157
+                    加分明细<span class="panel-dim-name">{{ activeDimName }}</span>
158
+                    <span class="panel-total">合计 {{ addTotal }}</span>
159
+                  </div>
160
+                  <div class="panel-list">
161
+                    <div v-for="(item, i) in addList" :key="i" class="panel-row">
162
+                      <span class="p-name">{{ item.level3Name || item.level2Name }}</span>
163
+                      <span class="p-score add-score">+{{ item.totalScore }}</span>
164
+                    </div>
165
+                    <div v-if="!addList.length" class="p-empty">暂无加分记录</div>
161
                   </div>
166
                   </div>
162
-                  <div v-if="!addList.length" class="p-empty">暂无加分记录</div>
163
                 </div>
167
                 </div>
164
-              </div>
168
+              </transition>
165
             </div>
169
             </div>
170
+            <div class="radar-hint" v-if="!activeDimName">点击雷达图维度查看明细</div>
166
           </div>
171
           </div>
167
         </div>
172
         </div>
168
 
173
 
@@ -341,14 +346,18 @@ const positionList = computed(() => {
341
   return pos.split(/[,,、/]/).map(s => s.trim()).filter(Boolean)
346
   return pos.split(/[,,、/]/).map(s => s.trim()).filter(Boolean)
342
 })
347
 })
343
 
348
 
349
+const activeDimName = ref(null)
350
+
344
 const scoreDetails = computed(() => portrait.value?.scoreDetails || [])
351
 const scoreDetails = computed(() => portrait.value?.scoreDetails || [])
345
 
352
 
346
-const addList = computed(() =>
347
-  scoreDetails.value.filter(d => d.totalScore != null && Number(d.totalScore) > 0)
348
-)
349
-const deductList = computed(() =>
350
-  scoreDetails.value.filter(d => d.totalScore != null && Number(d.totalScore) < 0)
351
-)
353
+const addList = computed(() => {
354
+  const all = scoreDetails.value.filter(d => d.totalScore != null && Number(d.totalScore) > 0)
355
+  return activeDimName.value ? all.filter(d => d.dimensionName === activeDimName.value) : all
356
+})
357
+const deductList = computed(() => {
358
+  const all = scoreDetails.value.filter(d => d.totalScore != null && Number(d.totalScore) < 0)
359
+  return activeDimName.value ? all.filter(d => d.dimensionName === activeDimName.value) : all
360
+})
352
 const addTotal = computed(() => {
361
 const addTotal = computed(() => {
353
   const s = addList.value.reduce((acc, d) => acc + Number(d.totalScore), 0)
362
   const s = addList.value.reduce((acc, d) => acc + Number(d.totalScore), 0)
354
   return (s > 0 ? '+' : '') + s.toFixed(2)
363
   return (s > 0 ? '+' : '') + s.toFixed(2)
@@ -388,7 +397,6 @@ const initChart = () => {
388
   const c = radarColor.value
397
   const c = radarColor.value
389
   chart.setOption({
398
   chart.setOption({
390
     radar: {
399
     radar: {
391
-      // 指标名显示加权前分值
392
       indicator: dims.map(d => ({ name: d.name + '\n' + d.score, max: 100 })),
400
       indicator: dims.map(d => ({ name: d.name + '\n' + d.score, max: 100 })),
393
       center: ['50%', '52%'],
401
       center: ['50%', '52%'],
394
       radius: '65%',
402
       radius: '65%',
@@ -419,9 +427,29 @@ const initChart = () => {
419
     }],
427
     }],
420
     tooltip: { trigger: 'item' }
428
     tooltip: { trigger: 'item' }
421
   })
429
   })
430
+
431
+  // 点击雷达图按角度判断最近维度
432
+  chart.getZr().on('click', (event) => {
433
+    if (!dims.length) return
434
+    const el = abilityChart.value
435
+    const cx = el.offsetWidth * 0.5
436
+    const cy = el.offsetHeight * 0.52
437
+    const dx = event.offsetX - cx
438
+    const dy = event.offsetY - cy
439
+    let angle = Math.atan2(dx, -dy) * 180 / Math.PI
440
+    if (angle < 0) angle += 360
441
+    const step = 360 / dims.length
442
+    const idx = Math.round(angle / step) % dims.length
443
+    const name = dims[idx]?.name
444
+    if (!name) return
445
+    activeDimName.value = activeDimName.value === name ? null : name
446
+  })
422
 }
447
 }
423
 
448
 
424
-watch(portrait, (val) => { if (val) nextTick(() => initChart()) })
449
+watch(portrait, (val) => {
450
+  activeDimName.value = null
451
+  if (val) nextTick(() => initChart())
452
+})
425
 
453
 
426
 const handleResize = () => { chart?.resize() }
454
 const handleResize = () => { chart?.resize() }
427
 
455
 
@@ -775,7 +803,8 @@ onBeforeUnmount(() => {
775
   }
803
   }
776
 
804
 
777
   .chart-box {
805
   .chart-box {
778
-    flex: 1;
806
+    width: 100%;
807
+    height: 100%;
779
     min-height: 400px;
808
     min-height: 400px;
780
   }
809
   }
781
 }
810
 }
@@ -943,24 +972,30 @@ onBeforeUnmount(() => {
943
 /* ── 雷达图主体(含两侧浮窗) ── */
972
 /* ── 雷达图主体(含两侧浮窗) ── */
944
 .radar-body {
973
 .radar-body {
945
   flex: 1;
974
   flex: 1;
946
-  display: flex;
947
-  gap: 8px;
975
+  position: relative;
948
   min-height: 0;
976
   min-height: 0;
949
 }
977
 }
950
 
978
 
951
-/* ── 加/扣分浮窗 ── */
979
+/* ── 加/扣分浮窗(绝对定位,叠在 chart 上方) ── */
952
 .score-panel {
980
 .score-panel {
981
+  position: absolute;
982
+  top: 0;
953
   width: 180px;
983
   width: 180px;
954
-  flex-shrink: 0;
984
+  max-height: 100%;
955
   display: flex;
985
   display: flex;
956
   flex-direction: column;
986
   flex-direction: column;
957
   border-radius: 12px;
987
   border-radius: 12px;
958
   overflow: hidden;
988
   overflow: hidden;
959
   border: 1px solid #e0e7f0;
989
   border: 1px solid #e0e7f0;
960
   font-size: 12px;
990
   font-size: 12px;
961
-  background: #fff;
991
+  background: rgba(255,255,255,0.92);
992
+  box-shadow: 0 2px 8px rgba(0,0,0,0.12);
993
+  z-index: 10;
962
 }
994
 }
963
 
995
 
996
+.deduct-panel { left: 0; }
997
+.add-panel    { right: 0; }
998
+
964
 .panel-header {
999
 .panel-header {
965
   padding: 6px 10px;
1000
   padding: 6px 10px;
966
   font-size: 13px;
1001
   font-size: 13px;
@@ -972,12 +1007,34 @@ onBeforeUnmount(() => {
972
 .deduct-header { background: rgba(254,233,232,1); color: #e03030; }
1007
 .deduct-header { background: rgba(254,233,232,1); color: #e03030; }
973
 .add-header    { background: rgba(206,248,228,1); color: #1a9944; }
1008
 .add-header    { background: rgba(206,248,228,1); color: #1a9944; }
974
 
1009
 
1010
+.panel-dim-name {
1011
+  font-size: 11px;
1012
+  font-weight: normal;
1013
+  margin-left: 4px;
1014
+  opacity: 0.8;
1015
+  max-width: 60px;
1016
+  overflow: hidden;
1017
+  text-overflow: ellipsis;
1018
+  white-space: nowrap;
1019
+}
1020
+
975
 .panel-total {
1021
 .panel-total {
976
   font-size: 12px;
1022
   font-size: 12px;
977
   font-weight: bold;
1023
   font-weight: bold;
978
   margin-left: auto;
1024
   margin-left: auto;
979
 }
1025
 }
980
 
1026
 
1027
+.radar-hint {
1028
+  text-align: center;
1029
+  font-size: 12px;
1030
+  color: #aaa;
1031
+  margin-top: 4px;
1032
+  flex-shrink: 0;
1033
+}
1034
+
1035
+.panel-fade-enter-active, .panel-fade-leave-active { transition: opacity 0.2s; }
1036
+.panel-fade-enter-from, .panel-fade-leave-to       { opacity: 0; }
1037
+
981
 .panel-list {
1038
 .panel-list {
982
   flex: 1;
1039
   flex: 1;
983
   overflow-y: auto;
1040
   overflow-y: auto;