Sfoglia il codice sorgente

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

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
simonlll 1 mese fa
parent
commit
2237a00595
1 ha cambiato i file con 95 aggiunte e 38 eliminazioni
  1. 95 38
      src/views/portraitManagement/employeeProfile/index.vue

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

@@ -130,39 +130,44 @@
130 130
           <div class="card radar-card">
131 131
             <div class="sec-title">● 个人能力</div>
132 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 146
                   </div>
144
-                  <div v-if="!deductList.length" class="p-empty">暂无扣分记录</div>
145 147
                 </div>
146
-              </div>
148
+              </transition>
147 149
 
148 150
               <!-- 雷达图 -->
149 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 166
                   </div>
162
-                  <div v-if="!addList.length" class="p-empty">暂无加分记录</div>
163 167
                 </div>
164
-              </div>
168
+              </transition>
165 169
             </div>
170
+            <div class="radar-hint" v-if="!activeDimName">点击雷达图维度查看明细</div>
166 171
           </div>
167 172
         </div>
168 173
 
@@ -341,14 +346,18 @@ const positionList = computed(() => {
341 346
   return pos.split(/[,,、/]/).map(s => s.trim()).filter(Boolean)
342 347
 })
343 348
 
349
+const activeDimName = ref(null)
350
+
344 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 361
 const addTotal = computed(() => {
353 362
   const s = addList.value.reduce((acc, d) => acc + Number(d.totalScore), 0)
354 363
   return (s > 0 ? '+' : '') + s.toFixed(2)
@@ -388,7 +397,6 @@ const initChart = () => {
388 397
   const c = radarColor.value
389 398
   chart.setOption({
390 399
     radar: {
391
-      // 指标名显示加权前分值
392 400
       indicator: dims.map(d => ({ name: d.name + '\n' + d.score, max: 100 })),
393 401
       center: ['50%', '52%'],
394 402
       radius: '65%',
@@ -419,9 +427,29 @@ const initChart = () => {
419 427
     }],
420 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 454
 const handleResize = () => { chart?.resize() }
427 455
 
@@ -775,7 +803,8 @@ onBeforeUnmount(() => {
775 803
   }
776 804
 
777 805
   .chart-box {
778
-    flex: 1;
806
+    width: 100%;
807
+    height: 100%;
779 808
     min-height: 400px;
780 809
   }
781 810
 }
@@ -943,24 +972,30 @@ onBeforeUnmount(() => {
943 972
 /* ── 雷达图主体(含两侧浮窗) ── */
944 973
 .radar-body {
945 974
   flex: 1;
946
-  display: flex;
947
-  gap: 8px;
975
+  position: relative;
948 976
   min-height: 0;
949 977
 }
950 978
 
951
-/* ── 加/扣分浮窗 ── */
979
+/* ── 加/扣分浮窗(绝对定位,叠在 chart 上方) ── */
952 980
 .score-panel {
981
+  position: absolute;
982
+  top: 0;
953 983
   width: 180px;
954
-  flex-shrink: 0;
984
+  max-height: 100%;
955 985
   display: flex;
956 986
   flex-direction: column;
957 987
   border-radius: 12px;
958 988
   overflow: hidden;
959 989
   border: 1px solid #e0e7f0;
960 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 999
 .panel-header {
965 1000
   padding: 6px 10px;
966 1001
   font-size: 13px;
@@ -972,12 +1007,34 @@ onBeforeUnmount(() => {
972 1007
 .deduct-header { background: rgba(254,233,232,1); color: #e03030; }
973 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 1021
 .panel-total {
976 1022
   font-size: 12px;
977 1023
   font-weight: bold;
978 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 1038
 .panel-list {
982 1039
   flex: 1;
983 1040
   overflow-y: auto;