|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+<template>
|
|
|
2
|
+ <div class="page-container">
|
|
|
3
|
+ <h1 class="page-title">员工维度评分明细</h1>
|
|
|
4
|
+
|
|
|
5
|
+ <div class="filter-bar">
|
|
|
6
|
+ <div class="filter-left">
|
|
|
7
|
+ <button class="time-btn" :class="{ active: activeRange === 'week' }"
|
|
|
8
|
+ @click="setActiveRange('week')">近一周</button>
|
|
|
9
|
+ <button class="time-btn" :class="{ active: activeRange === 'month' }"
|
|
|
10
|
+ @click="setActiveRange('month')">近一月</button>
|
|
|
11
|
+ <button class="time-btn" :class="{ active: activeRange === 'quarter' }"
|
|
|
12
|
+ @click="setActiveRange('quarter')">近三月</button>
|
|
|
13
|
+ <button class="time-btn" :class="{ active: activeRange === 'year' }"
|
|
|
14
|
+ @click="setActiveRange('year')">近一年</button>
|
|
|
15
|
+ <el-date-picker v-model="startDate" type="date" placeholder="开始日期" style="width: 150px;" />
|
|
|
16
|
+ <span style="margin: 0 8px;">至</span>
|
|
|
17
|
+ <el-date-picker v-model="endDate" type="date" placeholder="结束日期" style="width: 150px;" />
|
|
|
18
|
+ <el-tree-select v-model="selectedOrg" :data="cascadeOptions"
|
|
|
19
|
+ :props="{ label: 'label', value: 'value', children: 'children' }" node-key="value"
|
|
|
20
|
+ placeholder="组织架构/员工" clearable filterable check-strictly style="width: 200px;" />
|
|
|
21
|
+ <el-select v-model="selectedDimension" placeholder="维度" clearable style="width: 150px;">
|
|
|
22
|
+ <el-option v-for="item in dimensionOptions" :key="item.value" :label="item.label"
|
|
|
23
|
+ :value="item.value" />
|
|
|
24
|
+ </el-select>
|
|
|
25
|
+ </div>
|
|
|
26
|
+ <div class="filter-right">
|
|
|
27
|
+ <el-button type="primary" @click="handleSearch">搜索</el-button>
|
|
|
28
|
+ <el-button @click="handleReset">重置</el-button>
|
|
|
29
|
+ </div>
|
|
|
30
|
+ </div>
|
|
|
31
|
+
|
|
|
32
|
+ <div class="table-section">
|
|
|
33
|
+ <div class="section-title">
|
|
|
34
|
+ <span class="title-text">员工维度评分明细</span>
|
|
|
35
|
+ <span class="title-badge">评分依据: 员工配分表</span>
|
|
|
36
|
+ </div>
|
|
|
37
|
+ <el-table :data="tableData" style="width: 100%;" border stripe>
|
|
|
38
|
+ <el-table-column prop="userId" label="员工ID" />
|
|
|
39
|
+ <el-table-column prop="nickName" label="姓名" />
|
|
|
40
|
+ <el-table-column prop="deptName" label="部门" />
|
|
|
41
|
+ <el-table-column prop="teamName" label="班组" />
|
|
|
42
|
+ <el-table-column prop="groupName" label="小组" />
|
|
|
43
|
+ <el-table-column prop="dimName" label="维度名称" />
|
|
|
44
|
+ <el-table-column prop="dimScore" label="维度分值" />
|
|
|
45
|
+ </el-table>
|
|
|
46
|
+ <el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum"
|
|
|
47
|
+ v-model:page-size="queryParams.pageSize" :page-sizes="[10, 20, 50, 100]"
|
|
|
48
|
+ layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
|
|
|
49
|
+ @current-change="handlePageChange" style="margin-top: 16px; justify-content: flex-end;" />
|
|
|
50
|
+ </div>
|
|
|
51
|
+ </div>
|
|
|
52
|
+</template>
|
|
|
53
|
+
|
|
|
54
|
+<script setup>
|
|
|
55
|
+import { ref, reactive, onMounted, watch } from 'vue'
|
|
|
56
|
+import { useRoute } from 'vue-router'
|
|
|
57
|
+import { getDeptUserTree } from '@/api/item/items'
|
|
|
58
|
+import { getEmployeeDimensionPageData } from '@/api/warningManage/employeeDimensionDetails'
|
|
|
59
|
+
|
|
|
60
|
+const route = useRoute()
|
|
|
61
|
+
|
|
|
62
|
+const activeRange = ref('month')
|
|
|
63
|
+const startDate = ref(null)
|
|
|
64
|
+const endDate = ref(null)
|
|
|
65
|
+const selectedOrg = ref('')
|
|
|
66
|
+const selectedDimension = ref('')
|
|
|
67
|
+const cascadeOptions = ref([])
|
|
|
68
|
+
|
|
|
69
|
+const dimensionOptions = ref([
|
|
|
70
|
+ { label: '安全响应能力', value: '安全响应能力' },
|
|
|
71
|
+ { label: '服务响应能力', value: '服务响应能力' }
|
|
|
72
|
+])
|
|
|
73
|
+
|
|
|
74
|
+const tableData = ref([
|
|
|
75
|
+ { userId: 397, nickName: '胡婷婷', deptName: '旅检一部', teamName: '安平班组', groupName: '陈行小组', dimName: '安全响应能力', dimScore: 80.5 },
|
|
|
76
|
+ { userId: 437, nickName: '李璐', deptName: '旅检一部', teamName: '安平班组', groupName: '陈行小组', dimName: '服务响应能力', dimScore: 79.2 },
|
|
|
77
|
+ { userId: 564, nickName: '徐倩', deptName: '旅检一部', teamName: '安平班组', groupName: '陈行小组', dimName: '安全响应能力', dimScore: 81.3 },
|
|
|
78
|
+ { userId: 587, nickName: '余杭洋', deptName: '旅检一部', teamName: '安平班组', groupName: '陈行小组', dimName: '服务响应能力', dimScore: 78.6 },
|
|
|
79
|
+ { userId: 550, nickName: '肖垚', deptName: '旅检一部', teamName: '安平班组', groupName: '陈行小组', dimName: '安全响应能力', dimScore: 80.1 },
|
|
|
80
|
+ { userId: 107, nickName: '刘珊', deptName: '旅检三部', teamName: '安平班组', groupName: '陈行小组', dimName: '服务响应能力', dimScore: 79.8 },
|
|
|
81
|
+ { userId: 191, nickName: '王佐朝', deptName: '旅检三部', teamName: '安平班组', groupName: '陈行小组', dimName: '安全响应能力', dimScore: 82.0 },
|
|
|
82
|
+ { userId: 250, nickName: '张元媛', deptName: '旅检三部', teamName: '安平班组', groupName: '陈行小组', dimName: '服务响应能力', dimScore: 78.9 },
|
|
|
83
|
+ { userId: 293, nickName: '陈凯琳', deptName: '旅检三部', teamName: '安平班组', groupName: '陈行小组', dimName: '安全响应能力', dimScore: 80.7 },
|
|
|
84
|
+ { userId: 603, nickName: '张霞', deptName: '旅检一部', teamName: '安平班组', groupName: '陈行小组', dimName: '服务响应能力', dimScore: 79.5 }
|
|
|
85
|
+])
|
|
|
86
|
+const total = ref(972)
|
|
|
87
|
+const queryParams = reactive({
|
|
|
88
|
+ pageNum: 1,
|
|
|
89
|
+ pageSize: 10
|
|
|
90
|
+})
|
|
|
91
|
+
|
|
|
92
|
+const transformCascadeData = (nodes) => {
|
|
|
93
|
+ if (!nodes) return []
|
|
|
94
|
+ return nodes.map(node => {
|
|
|
95
|
+ const deptType = node.deptType || node.nodeType
|
|
|
96
|
+ const label = deptType === 'user'
|
|
|
97
|
+ ? (node.nickName || node.label)
|
|
|
98
|
+ : (node.deptName || node.name || node.label)
|
|
|
99
|
+
|
|
|
100
|
+ let value;
|
|
|
101
|
+ if (deptType === 'STATION') value = `station_${node.id}`;
|
|
|
102
|
+ else if (deptType === 'BRIGADE') value = `dept_${node.id}`
|
|
|
103
|
+ else if (deptType === 'MANAGER') value = `team_${node.id}`
|
|
|
104
|
+ else if (deptType === 'TEAMS') value = `group_${node.id}`
|
|
|
105
|
+ else value = `user_${node.id}`
|
|
|
106
|
+
|
|
|
107
|
+ const children = node.children && node.children.length > 0 ? transformCascadeData(node.children) : undefined
|
|
|
108
|
+
|
|
|
109
|
+ return { label, value, deptType, children }
|
|
|
110
|
+ })
|
|
|
111
|
+}
|
|
|
112
|
+
|
|
|
113
|
+const getSelectedInfo = (selectedValue) => {
|
|
|
114
|
+ if (!selectedValue) return null
|
|
|
115
|
+ const findNodeByValue = (nodes, value) => {
|
|
|
116
|
+ for (const node of nodes) {
|
|
|
117
|
+ if (node.value === value) return node
|
|
|
118
|
+ if (node.children) {
|
|
|
119
|
+ const found = findNodeByValue(node.children, value)
|
|
|
120
|
+ if (found) return found
|
|
|
121
|
+ }
|
|
|
122
|
+ }
|
|
|
123
|
+ return null
|
|
|
124
|
+ }
|
|
|
125
|
+ return findNodeByValue(cascadeOptions.value, selectedValue)
|
|
|
126
|
+}
|
|
|
127
|
+
|
|
|
128
|
+const formatDate = (d) => {
|
|
|
129
|
+ if (!d) return ''
|
|
|
130
|
+ const date = new Date(d)
|
|
|
131
|
+ const y = date.getFullYear()
|
|
|
132
|
+ const m = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
133
|
+ const day = String(date.getDate()).padStart(2, '0')
|
|
|
134
|
+ return `${y}-${m}-${day}`
|
|
|
135
|
+}
|
|
|
136
|
+
|
|
|
137
|
+const getDateRangeFromActive = () => {
|
|
|
138
|
+ const now = new Date()
|
|
|
139
|
+ let start = new Date(now)
|
|
|
140
|
+ if (activeRange.value === 'week') start.setDate(now.getDate() - 7)
|
|
|
141
|
+ else if (activeRange.value === 'month') start.setMonth(now.getMonth() - 1)
|
|
|
142
|
+ else if (activeRange.value === 'quarter') start.setMonth(now.getMonth() - 3)
|
|
|
143
|
+ else start.setFullYear(now.getFullYear() - 1)
|
|
|
144
|
+ return { startDate: formatDate(start), endDate: formatDate(now) }
|
|
|
145
|
+}
|
|
|
146
|
+
|
|
|
147
|
+const setActiveRange = (range) => {
|
|
|
148
|
+ activeRange.value = range
|
|
|
149
|
+ if (range !== 'custom') {
|
|
|
150
|
+ startDate.value = null
|
|
|
151
|
+ endDate.value = null
|
|
|
152
|
+ }
|
|
|
153
|
+}
|
|
|
154
|
+
|
|
|
155
|
+const fetchDimensionData = async () => {
|
|
|
156
|
+ // TODO: 接入真实接口
|
|
|
157
|
+ // let params = {
|
|
|
158
|
+ // pageNum: queryParams.pageNum,
|
|
|
159
|
+ // pageSize: queryParams.pageSize
|
|
|
160
|
+ // }
|
|
|
161
|
+ // if (startDate.value && endDate.value) {
|
|
|
162
|
+ // params.startDate = formatDate(startDate.value)
|
|
|
163
|
+ // params.endDate = formatDate(endDate.value)
|
|
|
164
|
+ // } else {
|
|
|
165
|
+ // const range = getDateRangeFromActive()
|
|
|
166
|
+ // params.startDate = range.startDate
|
|
|
167
|
+ // params.endDate = range.endDate
|
|
|
168
|
+ // }
|
|
|
169
|
+ // const selectedInfo = getSelectedInfo(selectedOrg.value)
|
|
|
170
|
+ // if (selectedInfo) {
|
|
|
171
|
+ // const rawId = Number(selectedInfo.value.split('_')[1])
|
|
|
172
|
+ // if (selectedInfo.deptType === 'BRIGADE') params.deptId = rawId
|
|
|
173
|
+ // else if (selectedInfo.deptType === 'MANAGER') params.teamId = rawId
|
|
|
174
|
+ // else if (selectedInfo.deptType === 'TEAMS') params.groupId = rawId
|
|
|
175
|
+ // else if (selectedInfo.deptType === 'user') params.userId = rawId
|
|
|
176
|
+ // }
|
|
|
177
|
+ // if (selectedDimension.value) {
|
|
|
178
|
+ // params.dimName = selectedDimension.value
|
|
|
179
|
+ // }
|
|
|
180
|
+ // try {
|
|
|
181
|
+ // const res = await getEmployeeDimensionPageData(params)
|
|
|
182
|
+ // if (res.data) {
|
|
|
183
|
+ // tableData.value = res.data.rows || res.data || []
|
|
|
184
|
+ // total.value = res.total || res.data?.total || 0
|
|
|
185
|
+ // }
|
|
|
186
|
+ // } catch (error) {
|
|
|
187
|
+ // console.error('获取员工维度评分明细失败:', error)
|
|
|
188
|
+ // }
|
|
|
189
|
+}
|
|
|
190
|
+
|
|
|
191
|
+const handleSearch = () => {
|
|
|
192
|
+ queryParams.pageNum = 1
|
|
|
193
|
+ fetchDimensionData()
|
|
|
194
|
+}
|
|
|
195
|
+
|
|
|
196
|
+const handleReset = () => {
|
|
|
197
|
+ startDate.value = null
|
|
|
198
|
+ endDate.value = null
|
|
|
199
|
+ activeRange.value = 'month'
|
|
|
200
|
+ selectedOrg.value = ''
|
|
|
201
|
+ selectedDimension.value = ''
|
|
|
202
|
+ queryParams.pageNum = 1
|
|
|
203
|
+ fetchDimensionData()
|
|
|
204
|
+}
|
|
|
205
|
+
|
|
|
206
|
+const handlePageChange = (newPage) => {
|
|
|
207
|
+ queryParams.pageNum = newPage
|
|
|
208
|
+ fetchDimensionData()
|
|
|
209
|
+}
|
|
|
210
|
+
|
|
|
211
|
+const handleSizeChange = (newSize) => {
|
|
|
212
|
+ queryParams.pageSize = newSize
|
|
|
213
|
+ queryParams.pageNum = 1
|
|
|
214
|
+ fetchDimensionData()
|
|
|
215
|
+}
|
|
|
216
|
+
|
|
|
217
|
+onMounted(async () => {
|
|
|
218
|
+ try {
|
|
|
219
|
+ const res = await getDeptUserTree()
|
|
|
220
|
+ if (res.data) {
|
|
|
221
|
+ cascadeOptions.value = transformCascadeData(res.data)
|
|
|
222
|
+ }
|
|
|
223
|
+ } catch (error) {
|
|
|
224
|
+ console.error('获取组织架构数据失败:', error)
|
|
|
225
|
+ }
|
|
|
226
|
+ fetchDimensionData()
|
|
|
227
|
+})
|
|
|
228
|
+
|
|
|
229
|
+watch(() => route.query, (query) => {
|
|
|
230
|
+ const { id, org, startDate: sd, endDate: ed, activeRange: ar, dimension } = query
|
|
|
231
|
+ if (id) {
|
|
|
232
|
+ selectedOrg.value = `user_${id}`
|
|
|
233
|
+ } else if (org) {
|
|
|
234
|
+ selectedOrg.value = org
|
|
|
235
|
+ } else {
|
|
|
236
|
+ selectedOrg.value = ''
|
|
|
237
|
+ }
|
|
|
238
|
+ if (sd && ed) {
|
|
|
239
|
+ startDate.value = sd
|
|
|
240
|
+ endDate.value = ed
|
|
|
241
|
+ activeRange.value = 'custom'
|
|
|
242
|
+ } else {
|
|
|
243
|
+ startDate.value = null
|
|
|
244
|
+ endDate.value = null
|
|
|
245
|
+ activeRange.value = ar || 'month'
|
|
|
246
|
+ }
|
|
|
247
|
+ if (dimension) {
|
|
|
248
|
+ selectedDimension.value = dimension
|
|
|
249
|
+ }
|
|
|
250
|
+ queryParams.pageNum = 1
|
|
|
251
|
+ fetchDimensionData()
|
|
|
252
|
+}, { immediate: true })
|
|
|
253
|
+</script>
|
|
|
254
|
+
|
|
|
255
|
+<style scoped>
|
|
|
256
|
+.page-container {
|
|
|
257
|
+ padding: 20px;
|
|
|
258
|
+ background: #f5f7fa;
|
|
|
259
|
+ min-height: calc(100vh - 90px);
|
|
|
260
|
+}
|
|
|
261
|
+
|
|
|
262
|
+.page-title {
|
|
|
263
|
+ font-size: 1.6rem;
|
|
|
264
|
+ font-weight: 700;
|
|
|
265
|
+ color: #1e3c72;
|
|
|
266
|
+ margin: 0 0 16px 0;
|
|
|
267
|
+}
|
|
|
268
|
+
|
|
|
269
|
+.filter-bar {
|
|
|
270
|
+ background: #fff;
|
|
|
271
|
+ border-radius: 12px;
|
|
|
272
|
+ padding: 12px 20px;
|
|
|
273
|
+ margin-bottom: 20px;
|
|
|
274
|
+ display: flex;
|
|
|
275
|
+ align-items: center;
|
|
|
276
|
+ justify-content: space-between;
|
|
|
277
|
+ gap: 12px;
|
|
|
278
|
+ flex-wrap: wrap;
|
|
|
279
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
|
|
280
|
+}
|
|
|
281
|
+
|
|
|
282
|
+.filter-left {
|
|
|
283
|
+ display: flex;
|
|
|
284
|
+ align-items: center;
|
|
|
285
|
+ gap: 8px;
|
|
|
286
|
+ flex-wrap: wrap;
|
|
|
287
|
+}
|
|
|
288
|
+
|
|
|
289
|
+.filter-right {
|
|
|
290
|
+ display: flex;
|
|
|
291
|
+ align-items: center;
|
|
|
292
|
+ gap: 8px;
|
|
|
293
|
+}
|
|
|
294
|
+
|
|
|
295
|
+.time-btn {
|
|
|
296
|
+ background: #f8fafc;
|
|
|
297
|
+ border: 1px solid #e2e8f0;
|
|
|
298
|
+ padding: 6px 18px;
|
|
|
299
|
+ border-radius: 6px;
|
|
|
300
|
+ font-size: 0.85rem;
|
|
|
301
|
+ font-weight: 500;
|
|
|
302
|
+ cursor: pointer;
|
|
|
303
|
+ transition: all 0.2s;
|
|
|
304
|
+ color: #1e293b;
|
|
|
305
|
+}
|
|
|
306
|
+
|
|
|
307
|
+.time-btn.active {
|
|
|
308
|
+ background: #2563eb;
|
|
|
309
|
+ border-color: #2563eb;
|
|
|
310
|
+ color: #fff;
|
|
|
311
|
+}
|
|
|
312
|
+
|
|
|
313
|
+.time-btn:hover:not(.active) {
|
|
|
314
|
+ background: #e2e8f0;
|
|
|
315
|
+}
|
|
|
316
|
+
|
|
|
317
|
+.table-section {
|
|
|
318
|
+ background: #fff;
|
|
|
319
|
+ border-radius: 12px;
|
|
|
320
|
+ padding: 20px;
|
|
|
321
|
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
|
|
322
|
+}
|
|
|
323
|
+
|
|
|
324
|
+.section-title {
|
|
|
325
|
+ display: flex;
|
|
|
326
|
+ align-items: center;
|
|
|
327
|
+ gap: 12px;
|
|
|
328
|
+ margin-bottom: 16px;
|
|
|
329
|
+}
|
|
|
330
|
+
|
|
|
331
|
+.title-text {
|
|
|
332
|
+ font-size: 1.1rem;
|
|
|
333
|
+ font-weight: 700;
|
|
|
334
|
+ color: #dc2626;
|
|
|
335
|
+}
|
|
|
336
|
+
|
|
|
337
|
+.title-badge {
|
|
|
338
|
+ font-size: 0.75rem;
|
|
|
339
|
+ background: #eef2ff;
|
|
|
340
|
+ color: #475569;
|
|
|
341
|
+ padding: 3px 12px;
|
|
|
342
|
+ border-radius: 20px;
|
|
|
343
|
+}
|
|
|
344
|
+</style>
|