Parcourir la source

feat(员工档案): 新增员工档案页面及功能

- 添加员工档案页面,包含基本信息、综合评价、工作履历等模块
- 实现时间范围切换和组织选择功能
- 使用echarts展示个人能力雷达图
- 更新环境配置和端口设置
- 修改token key以支持多项目区分
huoyi il y a 1 mois
Parent
commit
36346003d2

+ 3 - 0
.env.development

@@ -1,6 +1,9 @@
1 1
 # 页面标题
2 2
 VITE_APP_TITLE = 安检分级质控系统-管理端
3 3
 
4
+# 项目标识(用于区分不同项目的登录状态)
5
+VITE_APP_TOKEN_KEY = 'chongqing'
6
+
4 7
 # 开发环境配置
5 8
 VITE_APP_ENV = 'development'
6 9
 

+ 4 - 1
.env.production

@@ -1,6 +1,9 @@
1 1
 # 页面标题
2 2
 VITE_APP_TITLE = 安检分级质控系统-管理端
3 3
 
4
+# 项目标识(用于区分不同项目的登录状态)
5
+VITE_APP_TOKEN_KEY = 'chongqing'
6
+
4 7
 # 生产环境配置
5 8
 VITE_APP_ENV = 'production'
6 9
 
@@ -8,4 +11,4 @@ VITE_APP_ENV = 'production'
8 11
 VITE_APP_BASE_API = '/prod-api'
9 12
 
10 13
 # 是否在打包时开启压缩,支持 gzip 和 brotli
11
-VITE_BUILD_COMPRESS = gzip
14
+VITE_BUILD_COMPRESS = gzip

+ 4 - 1
.env.staging

@@ -1,6 +1,9 @@
1 1
 # 页面标题
2 2
 VITE_APP_TITLE = 安检分级质控系统-管理端
3 3
 
4
+# 项目标识(用于区分不同项目的登录状态)
5
+VITE_APP_TOKEN_KEY = 'chongqing'
6
+
4 7
 # 生产环境配置
5 8
 VITE_APP_ENV = 'staging'
6 9
 
@@ -8,4 +11,4 @@ VITE_APP_ENV = 'staging'
8 11
 VITE_APP_BASE_API = '/stage-api'
9 12
 
10 13
 # 是否在打包时开启压缩,支持 gzip 和 brotli
11
-VITE_BUILD_COMPRESS = gzip
14
+VITE_BUILD_COMPRESS = gzip

+ 1 - 1
src/utils/auth.js

@@ -1,6 +1,6 @@
1 1
 import Cookies from 'js-cookie'
2 2
 
3
-const TokenKey = 'Admin-Token'
3
+const TokenKey = 'Admin-Token-' + import.meta.env.VITE_APP_TOKEN_KEY
4 4
 
5 5
 export function getToken() {
6 6
   return Cookies.get(TokenKey)

+ 532 - 0
src/views/portraitManagement/employeeProfile/index.vue

@@ -0,0 +1,532 @@
1
+<template>
2
+  <div class="employee-profile">
3
+    <div class="top-bar">
4
+      <div class="time-selector">
5
+        <span class="label">切换时间范围</span>
6
+        <el-button size="small" @click="selectTime('week')" :type="currentTime === 'week' ? 'primary' : 'default'">近一周</el-button>
7
+        <el-button size="small" @click="selectTime('month')" :type="currentTime === 'month' ? 'primary' : 'default'">近一月</el-button>
8
+        <el-button size="small" @click="selectTime('quarter')" :type="currentTime === 'quarter' ? 'primary' : 'default'">近三月</el-button>
9
+        <el-button size="small" @click="selectTime('year')" :type="currentTime === 'year' ? 'primary' : 'default'">近一年</el-button>
10
+        <el-button size="small" type="default">自定义时间范围</el-button>
11
+        <el-date-picker
12
+          v-model="dateRange"
13
+          type="daterange"
14
+          range-separator="至"
15
+          start-placeholder="开始时间"
16
+          end-placeholder="结束时间"
17
+          size="small"
18
+          style="margin-left: 10px;"
19
+        />
20
+      </div>
21
+      <div class="org-selector">
22
+        <el-select v-model="selectedOrg" placeholder="请选择组织" size="small" style="width: 200px;">
23
+          <el-option label="旅检一科" value="dept1" />
24
+          <el-option label="旅检二科" value="dept2" />
25
+          <el-option label="旅检三科" value="dept3" />
26
+        </el-select>
27
+      </div>
28
+    </div>
29
+
30
+    <div class="main-content">
31
+      <div class="row">
32
+        <div class="card basic-info-card">
33
+          <div class="avatar-section">
34
+            <img class="avatar" src="https://trae-api-cn.mchost.guru/api/ide/v1/text_to_image?prompt=portrait%20of%20asian%20male%20in%20business%20suit&image_size=square" alt="头像" />
35
+          </div>
36
+          <div class="info-section">
37
+            <h2 class="name">陈雨桐</h2>
38
+            <div class="info-grid">
39
+              <div class="info-item">
40
+                <span class="label">所属部门及队:</span>
41
+                <span class="value">旅检一科/雨桐班组/鹏飞小组</span>
42
+              </div>
43
+              <div class="info-item">
44
+                <span class="label">出生日期:</span>
45
+                <span class="value">1990年10月18日</span>
46
+              </div>
47
+              <div class="info-item">
48
+                <span class="label">技能等级:</span>
49
+                <span class="value">二级</span>
50
+              </div>
51
+              <div class="info-item">
52
+                <span class="label">学历:</span>
53
+                <span class="value">本科</span>
54
+              </div>
55
+              <div class="info-item">
56
+                <span class="label">专业:</span>
57
+                <span class="value">物流运输</span>
58
+              </div>
59
+            </div>
60
+            <div class="tags">
61
+              <span class="label">标签:</span>
62
+              <el-tag size="small" type="primary">通道组长</el-tag>
63
+              <el-tag size="small" type="warning">旅检</el-tag>
64
+            </div>
65
+          </div>
66
+        </div>
67
+
68
+        <div class="card score-card">
69
+          <h3 class="card-title">综合评价</h3>
70
+          <div class="score-display">
71
+            <span class="score">78<span class="plus">+2</span></span>
72
+          </div>
73
+        </div>
74
+
75
+        <div class="card extra-info-card">
76
+          <h3 class="card-title">补充信息</h3>
77
+          <div class="extra-info-list">
78
+            <div class="extra-item"><span class="label">政治面貌:</span><span class="value">党员</span></div>
79
+            <div class="extra-item"><span class="label">性别:</span><span class="value">男</span></div>
80
+            <div class="extra-item"><span class="label">籍贯:</span><span class="value">重庆市</span></div>
81
+            <div class="extra-item"><span class="label">民族:</span><span class="value">汉族</span></div>
82
+            <div class="extra-item"><span class="label">年龄:</span><span class="value">28岁</span></div>
83
+            <div class="extra-item"><span class="label">司龄:</span><span class="value">6年</span></div>
84
+            <div class="extra-item"><span class="label">性格特征:</span><span class="value">检查员型</span></div>
85
+            <div class="extra-item"><span class="label">工作风格:</span><span class="value">尽责型</span></div>
86
+          </div>
87
+        </div>
88
+      </div>
89
+
90
+      <div class="row">
91
+        <div class="card work-experience-card">
92
+          <h3 class="card-title">工作履历</h3>
93
+          <div class="experience-list">
94
+            <div class="experience-item">2020.11入职/司龄6年/开机年限5年/现任班组长</div>
95
+            <div class="experience-item">2020.11入职/司龄6年/开机年限5年/现任班组长</div>
96
+          </div>
97
+        </div>
98
+
99
+        <div class="card ability-card">
100
+          <h3 class="card-title">个人能力</h3>
101
+          <div ref="abilityChart" class="chart-container"></div>
102
+        </div>
103
+
104
+        <div class="card award-card">
105
+          <h3 class="card-title">获奖记录</h3>
106
+          <table class="award-table">
107
+            <thead>
108
+              <tr>
109
+                <th>站级</th>
110
+                <th>个人荣誉</th>
111
+                <th>航站楼加分</th>
112
+                <th>8分</th>
113
+              </tr>
114
+            </thead>
115
+            <tbody>
116
+              <tr>
117
+                <td>站级</td>
118
+                <td>个人荣誉</td>
119
+                <td>小额奖励</td>
120
+                <td>100元</td>
121
+              </tr>
122
+              <tr>
123
+                <td>部门级</td>
124
+                <td>个人荣誉</td>
125
+                <td>小额奖励</td>
126
+                <td>100元</td>
127
+              </tr>
128
+              <tr>
129
+                <td>部门级</td>
130
+                <td>个人荣誉</td>
131
+                <td>小额奖励</td>
132
+                <td>100元</td>
133
+              </tr>
134
+            </tbody>
135
+          </table>
136
+        </div>
137
+      </div>
138
+
139
+      <div class="row">
140
+        <div class="card position-card">
141
+          <h3 class="card-title">业务岗位</h3>
142
+          <div class="position-list">
143
+            <div class="position-item">证件检查岗位</div>
144
+            <div class="position-item">防爆检查岗位</div>
145
+            <div class="position-item">人身检查岗位</div>
146
+            <div class="position-item">开包检查岗位</div>
147
+            <div class="position-item">X射线安检仪操作岗位</div>
148
+          </div>
149
+        </div>
150
+
151
+        <div class="card training-card">
152
+          <h3 class="card-title">培训情况</h3>
153
+          <div class="training-item">
154
+            <span class="item-label">季度考核:</span>
155
+            <span class="item-value">90分</span>
156
+            <span class="item-value">图像成绩 95分</span>
157
+          </div>
158
+        </div>
159
+      </div>
160
+    </div>
161
+  </div>
162
+</template>
163
+
164
+<script setup>
165
+import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
166
+import * as echarts from 'echarts';
167
+
168
+defineOptions({ name: 'EmployeeProfile' });
169
+
170
+const currentTime = ref('year');
171
+const dateRange = ref([]);
172
+const selectedOrg = ref('dept1');
173
+const abilityChart = ref(null);
174
+let chart = null;
175
+
176
+const selectTime = (time) => {
177
+  currentTime.value = time;
178
+};
179
+
180
+const initChart = () => {
181
+  if (!abilityChart.value) return;
182
+  chart = echarts.init(abilityChart.value);
183
+  const option = {
184
+    radar: {
185
+      indicator: [
186
+        { name: '安全防控能力', max: 100 },
187
+        { name: '服务响应能力', max: 100 },
188
+        { name: '业务实操能力', max: 100 },
189
+        { name: '作风践行能力', max: 100 },
190
+        { name: '身心调节能力', max: 100 }
191
+      ],
192
+      center: ['50%', '55%'],
193
+      radius: '70%',
194
+      splitNumber: 4,
195
+      axisLine: {
196
+        lineStyle: {
197
+          color: '#ddd'
198
+        }
199
+      },
200
+      splitArea: {
201
+        areaStyle: {
202
+          color: ['rgba(255,255,255,0)', 'rgba(255,255,255,0)']
203
+        }
204
+      },
205
+      splitLine: {
206
+        lineStyle: {
207
+          color: '#ddd'
208
+        }
209
+      },
210
+      name: {
211
+        textStyle: {
212
+          color: '#333',
213
+          fontSize: 12
214
+        }
215
+      }
216
+    },
217
+    series: [
218
+      {
219
+        type: 'radar',
220
+        data: [
221
+          {
222
+            value: [84, 75, 68, 74, 80],
223
+            name: '个人能力',
224
+            areaStyle: {
225
+              color: 'rgba(80, 180, 255, 0.4)'
226
+            },
227
+            lineStyle: {
228
+              color: '#50b4ff',
229
+              width: 2
230
+            },
231
+            itemStyle: {
232
+              color: '#50b4ff'
233
+            }
234
+          }
235
+        ],
236
+        symbol: 'circle',
237
+        symbolSize: 6
238
+      }
239
+    ]
240
+  };
241
+  chart.setOption(option);
242
+};
243
+
244
+const handleResize = () => {
245
+  if (chart) {
246
+    chart.resize();
247
+  }
248
+};
249
+
250
+onMounted(() => {
251
+  nextTick(() => {
252
+    initChart();
253
+    window.addEventListener('resize', handleResize);
254
+  });
255
+});
256
+
257
+onBeforeUnmount(() => {
258
+  window.removeEventListener('resize', handleResize);
259
+  if (chart) {
260
+    chart.dispose();
261
+    chart = null;
262
+  }
263
+});
264
+</script>
265
+
266
+<style scoped lang="scss">
267
+.employee-profile {
268
+  padding: 20px;
269
+  background: #f5f7fa;
270
+  min-height: 100vh;
271
+}
272
+
273
+.top-bar {
274
+  display: flex;
275
+  justify-content: space-between;
276
+  align-items: center;
277
+  background: #fff;
278
+  padding: 15px 20px;
279
+  border-radius: 8px;
280
+  margin-bottom: 20px;
281
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
282
+
283
+  .time-selector {
284
+    display: flex;
285
+    align-items: center;
286
+    gap: 10px;
287
+
288
+    .label {
289
+      font-size: 14px;
290
+      color: #666;
291
+      margin-right: 8px;
292
+    }
293
+  }
294
+}
295
+
296
+.main-content {
297
+  display: flex;
298
+  flex-direction: column;
299
+  gap: 20px;
300
+}
301
+
302
+.row {
303
+  display: flex;
304
+  gap: 20px;
305
+}
306
+
307
+.card {
308
+  background: #fff;
309
+  border-radius: 8px;
310
+  padding: 20px;
311
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
312
+  flex: 1;
313
+}
314
+
315
+.card-title {
316
+  font-size: 16px;
317
+  font-weight: bold;
318
+  color: #409eff;
319
+  margin: 0 0 20px 0;
320
+  padding-bottom: 10px;
321
+  border-bottom: 2px solid #409eff;
322
+  position: relative;
323
+
324
+  &::before {
325
+    content: '';
326
+    position: absolute;
327
+    left: 0;
328
+    top: -4px;
329
+    width: 8px;
330
+    height: 20px;
331
+    background: #409eff;
332
+    border-radius: 2px;
333
+  }
334
+}
335
+
336
+.basic-info-card {
337
+  display: flex;
338
+  gap: 20px;
339
+  flex: 1.5;
340
+
341
+  .avatar-section {
342
+    flex-shrink: 0;
343
+
344
+    .avatar {
345
+      width: 120px;
346
+      height: 120px;
347
+      border-radius: 50%;
348
+      border: 3px solid #409eff;
349
+      object-fit: cover;
350
+    }
351
+  }
352
+
353
+  .info-section {
354
+    flex: 1;
355
+
356
+    .name {
357
+      font-size: 24px;
358
+      font-weight: bold;
359
+      color: #333;
360
+      margin: 0 0 15px 0;
361
+    }
362
+
363
+    .info-grid {
364
+      display: grid;
365
+      grid-template-columns: repeat(2, 1fr);
366
+      gap: 10px;
367
+      margin-bottom: 15px;
368
+
369
+      .info-item {
370
+        font-size: 14px;
371
+        color: #666;
372
+
373
+        .label {
374
+          color: #999;
375
+        }
376
+        .value {
377
+          color: #333;
378
+          font-weight: 500;
379
+        }
380
+      }
381
+    }
382
+
383
+    .tags {
384
+      display: flex;
385
+      align-items: center;
386
+      gap: 8px;
387
+
388
+      .label {
389
+        font-size: 14px;
390
+        color: #999;
391
+      }
392
+    }
393
+  }
394
+}
395
+
396
+.score-card {
397
+  flex: 0.8;
398
+  display: flex;
399
+  flex-direction: column;
400
+  align-items: center;
401
+  justify-content: center;
402
+
403
+  .score-display {
404
+    flex: 1;
405
+    display: flex;
406
+    align-items: center;
407
+    justify-content: center;
408
+
409
+    .score {
410
+      font-size: 48px;
411
+      font-weight: bold;
412
+      color: #ff9900;
413
+    }
414
+    .plus {
415
+      font-size: 24px;
416
+      color: #67c23a;
417
+    }
418
+  }
419
+}
420
+
421
+.extra-info-card {
422
+  flex: 1;
423
+
424
+  .extra-info-list {
425
+    .extra-item {
426
+      padding: 8px 0;
427
+      font-size: 14px;
428
+      color: #666;
429
+      border-bottom: 1px dashed #eee;
430
+
431
+      &:last-child {
432
+        border-bottom: none;
433
+      }
434
+
435
+      .label {
436
+        color: #999;
437
+      }
438
+      .value {
439
+        color: #333;
440
+        font-weight: 500;
441
+      }
442
+    }
443
+  }
444
+}
445
+
446
+.work-experience-card {
447
+  flex: 1;
448
+
449
+  .experience-list {
450
+    .experience-item {
451
+      padding: 10px 15px;
452
+      background: #f0f7ff;
453
+      border-radius: 6px;
454
+      margin-bottom: 10px;
455
+      font-size: 14px;
456
+      color: #333;
457
+    }
458
+  }
459
+}
460
+
461
+.ability-card {
462
+  flex: 1.2;
463
+
464
+  .chart-container {
465
+    height: 300px;
466
+    width: 100%;
467
+  }
468
+}
469
+
470
+.award-card {
471
+  flex: 1;
472
+
473
+  .award-table {
474
+    width: 100%;
475
+    border-collapse: collapse;
476
+    font-size: 13px;
477
+
478
+    th, td {
479
+      padding: 10px 8px;
480
+      text-align: center;
481
+      border: 1px solid #ddd;
482
+    }
483
+
484
+    th {
485
+      background: #ffeae8;
486
+      color: #333;
487
+      font-weight: 500;
488
+    }
489
+
490
+    td {
491
+      background: #fff;
492
+      color: #666;
493
+    }
494
+  }
495
+}
496
+
497
+.position-card {
498
+  flex: 1;
499
+
500
+  .position-list {
501
+    .position-item {
502
+      padding: 12px 15px;
503
+      background: #fceee7;
504
+      border-radius: 8px;
505
+      margin-bottom: 8px;
506
+      font-size: 14px;
507
+      color: #333;
508
+      text-align: center;
509
+    }
510
+  }
511
+}
512
+
513
+.training-card {
514
+  flex: 1;
515
+
516
+  .training-item {
517
+    padding: 15px;
518
+    background: #f0f7ff;
519
+    border-radius: 8px;
520
+    font-size: 14px;
521
+
522
+    .item-label {
523
+      color: #666;
524
+    }
525
+    .item-value {
526
+      color: #333;
527
+      font-weight: 500;
528
+      margin-left: 8px;
529
+    }
530
+  }
531
+}
532
+</style>

+ 1 - 1
vite.config.js

@@ -42,7 +42,7 @@ export default defineConfig(({ mode, command }) => {
42 42
     },
43 43
     // vite 相关配置
44 44
     server: {
45
-      port: 8111,
45
+      port: 8113,
46 46
       host: true,
47 47
       open: true,
48 48
       proxy: {