Selaa lähdekoodia

feat: 个人画像

lixiangrui 4 viikkoa sitten
vanhempi
commit
a32aec927d

+ 18 - 4
src/hooks/useEcharts.js

@@ -17,13 +17,19 @@ export function useECharts(
17 17
 ) {
18 18
   const chartInstance = ref(null);
19 19
   const resizeObserver = ref(null);
20
+  let eventQueue = []
20 21
 
21 22
   // 初始化图表
22 23
   const init = () => {
23 24
     if (!containerRef.value) return;
24 25
     dispose();
25 26
     chartInstance.value = markRaw(echarts.init(containerRef.value, theme))
27
+    
26 28
     chartInstance.value.setOption(options.value);
29
+    eventQueue = eventQueue.filter((handler) => {
30
+      handler()
31
+      return false
32
+    })
27 33
     // 初始化 resize 监听
28 34
     if (autoResize) {
29 35
       setupResizeObserver();
@@ -71,6 +77,11 @@ export function useECharts(
71 77
     }
72 78
   };
73 79
 
80
+  const bindEvent = (eventName, callback) => {
81
+    eventQueue.push(() => chartInstance.value.getZr().on(eventName, callback))
82
+    return result
83
+  }
84
+
74 85
   // 监听 options 变化
75 86
   watch(options, (newOptions) => {
76 87
     if (chartInstance.value) {
@@ -79,14 +90,17 @@ export function useECharts(
79 90
   }, { deep: true, immediate: true });
80 91
 
81 92
   // 组件生命周期
82
-  onMounted(() => init());
93
+  onMounted(() => {
94
+    init()
95
+  });
83 96
   onBeforeUnmount(() => dispose());
84
-
85
-  return {
97
+  const result = {
86 98
     instance: chartInstance,
87 99
     init,
88 100
     dispose,
89 101
     reset,
90
-    resize
102
+    resize,
103
+    bindEvent
91 104
   };
105
+  return result
92 106
 }

+ 175 - 30
src/views/portraitManagement/components/SearchBar.vue

@@ -1,34 +1,32 @@
1 1
 <template>
2 2
   <div class="ep-topbar" @click="visible = false">
3 3
     <div class="time-btns">
4
-      <el-button size="small" :type="currentTime==='week'?'primary':'default'" @click="selectTime('week')">近一周</el-button>
5
-      <el-button size="small" :type="currentTime==='month'?'primary':'default'" @click="selectTime('month')">近一月</el-button>
6
-      <el-button size="small" :type="currentTime==='quarter'?'primary':'default'" @click="selectTime('quarter')">近三月</el-button>
7
-      <el-button size="small" :type="currentTime==='year'?'primary':'default'" @click="selectTime('year')">近一年</el-button>
8
-      <el-button size="small" :type="currentTime==='custom'?'primary':'default'" @click="selectTime('custom')">自定义时间范围</el-button>
9
-      <el-date-picker
10
-        v-if="currentTime==='custom'"
11
-        v-model="dateRange"
12
-        type="daterange"
13
-        range-separator="至"
14
-        start-placeholder="开始"
15
-        end-placeholder="结束"
16
-        size="small"
17
-        style="margin-left:8px;width:240px"
18
-        @change="() => searchHandler()" />
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
+        />
20
+      </div>
19 21
     </div>
20
-    <el-popover title="" :visible="visible" placement="bottom-end" trigger="click" width="60vw">
22
+    <el-popover class="popover" title="" :visible="visible" placement="bottom-start" trigger="click" width="45vw">
21 23
       <template #reference>
22
-        <el-button type="primary" @click.stop="visible = !visible">组织架构/模糊搜索</el-button>
24
+        <div class="primary" style="border-radius: 6px;" @click.stop="visible = !visible">组织架构/模糊搜索</div>
23 25
       </template>
24
-      <div style="width:100%; padding: 15px;">
26
+
27
+      <div class="custom-el-style">
25 28
         <div>
26
-          <el-autocomplete
27
-            v-model="personName"
28
-            :fetch-suggestions="queryUsers"
29
-            placeholder="搜索员工/团队画像"
30
-            style="width:320px"
31
-            clearable>
29
+          <el-autocomplete v-model="personName" :fetch-suggestions="queryUsers" placeholder="搜索员工/团队画像" style="width:320px;" clearable>
32 30
             <template #suffix
33 31
               ><el-icon><Search /></el-icon
34 32
             ></template>
@@ -37,10 +35,10 @@
37 35
               <span v-if="item.deptName" style="font-size:12px;color:#999;margin-left:8px">{{ item.deptName }}</span>
38 36
             </template>
39 37
           </el-autocomplete>
40
-          <el-button type="primary" style="margin-left: 30px;" @click="() => searchHandler()">模糊搜索</el-button>
38
+          <div class="primary" style="border-radius: 6px;" @click="() => searchHandler()">模糊搜索</div>
41 39
         </div>
42 40
         <div style="margin-top: 20px;">
43
-          <el-tree :data="departments" :props="{ value: 'id', showPrefix: false }" accordion @node-click="handleNodeClick" />
41
+          <el-tree :data="departments" :default-expanded-keys="[100]" :props="{ value: 'id', showPrefix: false }" accordion @node-click="handleNodeClick" />
44 42
         </div>
45 43
       </div>
46 44
     </el-popover>
@@ -48,10 +46,16 @@
48 46
 </template>
49 47
 
50 48
 <script setup>
49
+import { onMounted, onUnmounted } from 'vue'
50
+import { searchPortraitUsers } from '@/api/score/index'
51
+import { getDeptUserTree } from '@/api/item/items'
52
+
51 53
 const visible = defineModel('visible', false)
52 54
 const emit = defineEmits(['search'])
53 55
 const currentTime = ref('year')
54 56
 const personName = ref('')
57
+const departments = ref([])
58
+const dateRange = ref([])
55 59
 
56 60
 const queryUsers = async (query, cb) => {
57 61
   if (!query?.trim()) { cb([]); return }
@@ -76,13 +80,21 @@ const getTimeRange = () => {
76 80
   return { beginTime: formatDate(begin), endTime: formatDate(end) }
77 81
 }
78 82
 
83
+const formatDate = (d) => {
84
+  const dt = new Date(d)
85
+  return `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`
86
+}
87
+
79 88
 const selectTime = (t) => {
80 89
   currentTime.value = t
81 90
   if (t !== 'custom') searchHandler()
82 91
 }
83 92
 
84 93
 const searchHandler = (query = {}) => {
85
-  emit('search', { ...getTimeRange(), personName: personName.value }, ...query)
94
+  const queryParams = { ...getTimeRange(), personName: personName.value, ...query }
95
+  if (queryParams.personName) {
96
+    emit('search', queryParams)
97
+  }
86 98
 }
87 99
 
88 100
 const handleNodeClick = (node) => {
@@ -90,6 +102,45 @@ const handleNodeClick = (node) => {
90 102
     searchHandler({ personName: node.label })
91 103
   }
92 104
 }
105
+
106
+const setStyle = () => {
107
+  const root = document.querySelector(':root')
108
+  root.style.setProperty('--el-bg-color-overlay', '#1c1936')
109
+  root.style.setProperty('--el-border-color-extra-light', '#5c676d')
110
+  root.style.setProperty('--el-border-color-light', 'transparent')
111
+  root.style.setProperty('--el-popover-padding', '0px')
112
+  root.style.setProperty('--el-text-color-regular', '#fff')
113
+  root.style.setProperty('--el-fill-color-light', '#5c676d')
114
+}
115
+
116
+const resetStyle = () => {
117
+  const root = document.querySelector(':root')
118
+  root.style.setProperty('--el-bg-color-overlay', '#ffffff')
119
+  root.style.setProperty('--el-border-color-extra-light', '#f2f6fc')
120
+  root.style.setProperty('--el-border-color-light', '#e4e7ed')
121
+  root.style.setProperty('--el-popover-padding', '12px')
122
+  root.style.setProperty('--el-text-color-regular', '#606266')
123
+  root.style.setProperty('--el-fill-color-light', '#f5f7fa')
124
+
125
+}
126
+
127
+onMounted(async () => {
128
+  setStyle()
129
+  const res = await getDeptUserTree()
130
+  departments.value = res.data
131
+})
132
+onUnmounted(() => {
133
+  resetStyle()
134
+})
135
+
136
+defineExpose({
137
+  getDefQuery () {
138
+    return {
139
+      personName: '',
140
+      ...getTimeRange()
141
+    }
142
+  }
143
+})
93 144
 </script>
94 145
 
95 146
 <style lang="scss" scoped>
@@ -97,8 +148,8 @@ const handleNodeClick = (node) => {
97 148
   display: flex;
98 149
   align-items: center;
99 150
   justify-content: flex-start;
100
-  height: 90px;
101
-  padding: 15px;
151
+  height: 60px;
152
+  padding: 0 15px;
102 153
   box-sizing: border-box;
103 154
   gap: 12px;
104 155
   flex-wrap: wrap;
@@ -106,9 +157,103 @@ const handleNodeClick = (node) => {
106 157
   .time-btns {
107 158
     display: flex;
108 159
     align-items: center;
109
-    gap: 6px;
160
+    gap: 10px;
110 161
     flex-wrap: wrap;
111 162
     margin-right: 40px;
163
+    & > div {
164
+      padding: 8px 24px;
165
+      border-radius: 28px;
166
+      font-weight: 500;
167
+    }
168
+  }
169
+  .primary {
170
+    background: linear-gradient(125deg, #2AB3E6, #9605FC);
171
+    color: #fff;
172
+    padding: 8px 24px;
173
+  }
174
+  .default {
175
+    background: #1C1936;
176
+    color: #4C4C93;
177
+    position: relative;
178
+    &::before {
179
+      content: '';
180
+      position: absolute;
181
+      inset: -1px;
182
+      border-radius: 29px;
183
+      background: linear-gradient(150deg, #0f46fa 0%, #bd03fb 100%);
184
+      filter: brightness(2) contrast(150%);
185
+      z-index: -1;
186
+      opacity: 0.5;
187
+    }
188
+  }
189
+  .custom-style-date-picker-wrap {
190
+    position: relative;
191
+    border-radius: 19px;
192
+    height: 38px;
193
+    padding: 0 !important;
194
+    --el-text-color-primary: #fff;
195
+    &::before {
196
+      content: '';
197
+      position: absolute;
198
+      inset: -1px;
199
+      border-radius: 19px;
200
+      background: linear-gradient(150deg, #0f46fa 0%, #bd03fb 100%);
201
+      filter: brightness(2) contrast(150%);
202
+      z-index: -1;
203
+      opacity: 0.5;
204
+    }
112 205
   }
113 206
 }
114 207
 </style>
208
+
209
+<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;
249
+  }
250
+  .custom-style-date-picker {
251
+    background: #1C1936 !important;
252
+    box-shadow: none !important;
253
+    height: 38px !important;
254
+    border-radius: 19px;
255
+  }
256
+  .el-popper {
257
+    --el-popover-padding: 0px;
258
+  }
259
+</style>

+ 4 - 1
src/views/portraitManagement/components/card.vue

@@ -58,6 +58,9 @@ const props = defineProps({
58 58
     border-radius: 39px;
59 59
     padding: 15px 20px;
60 60
     height: 100%;
61
+    display: flex;
62
+    flex-direction: column;
63
+    row-gap: 10px;
61 64
   }
62 65
 
63 66
   /* 标题区域 */
@@ -88,6 +91,6 @@ const props = defineProps({
88 91
   .card-content {
89 92
     color: #fff;
90 93
     font-size: 16px;
91
-    height: 100%;
94
+    flex: 1;
92 95
   }
93 96
 </style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 572 - 1104
src/views/portraitManagement/employeeProfile/index.vue


+ 0 - 87
src/views/portraitManagement/employeeProfile/indexNew.vue

@@ -1,87 +0,0 @@
1
-<template>
2
-  <Page>
3
-    <div class="content">
4
-      <div class="content-search">
5
-        <SearchBar v-model:visible="visible" @search="searchHandler"/>
6
-      </div>
7
-      <div class="content-info">
8
-        <Card title="个人基本信息"> </Card>
9
-      </div>
10
-      <div class="content-bottom">
11
-        <div class="content-bottom-left">
12
-          <Card title="个人基本信息">
13
-            <div class="work-history">2020.1.1入职,司龄6年、开机年限5年,现任职班组长。</div>
14
-          </Card>
15
-          <Card title="个人基本信息"> </Card>
16
-        </div>
17
-        <div class="content-bottom-center">
18
-          <Card title="个人基本信息"></Card>
19
-        </div>
20
-        <div class="content-bottom-right">
21
-          <Card title="个人基本信息"></Card>
22
-        </div>
23
-      </div>
24
-    </div>
25
-  </Page>
26
-</template>
27
-
28
-<script setup>
29
-import Page from '../components/page.vue'
30
-import Card from '../components/card.vue'
31
-import SearchBar from '../components/SearchBar.vue'
32
-import { ref } from 'vue'
33
-const visible = ref(false)
34
-const searchHandler = () => {
35
-  
36
-}
37
-</script>
38
-
39
-<style lang="scss" scoped>
40
-
41
-.content {
42
-  height: 100%;
43
-  display: flex;
44
-  flex-direction: column;
45
-  row-gap: 15px;
46
-  padding: 15px;
47
-  .content-search {}
48
-  .content-info {
49
-    height: 230px;
50
-    & > * {
51
-        height: 100%;
52
-      }
53
-  }
54
-  .content-bottom {
55
-    flex: 1;
56
-    display: flex;
57
-    column-gap: 15px;
58
-    .content-bottom-left {
59
-      width: 380px;
60
-      display: flex;
61
-      flex-direction: column;
62
-      row-gap: 15px;
63
-      & > *:nth-child(1) {
64
-        flex: 1;
65
-        .work-history {
66
-          padding: 15px;
67
-        }
68
-      }
69
-      & > *:nth-child(2) {
70
-        flex: 1.6;
71
-      }
72
-    }
73
-    .content-bottom-center {
74
-      flex: 1;
75
-      & > * {
76
-        height: 100%;
77
-      }
78
-    }
79
-    .content-bottom-right {
80
-      width: 380px;
81
-      & > * {
82
-        height: 100%;
83
-      }
84
-    }
85
-  }
86
-}
87
-</style>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1306 - 0
src/views/portraitManagement/employeeProfile/indexOld.vue