浏览代码

Merge branch 'employeeScreen'

huoyi 1 周之前
父节点
当前提交
ac17edc212

+ 2 - 2
src/api/ledger/index.js

@@ -1,8 +1,8 @@
1 1
 import request from '@/utils/request'
2 2
 
3 3
 // ===== 台账一键全量导入(多Sheet合并Excel)=====
4
-export function importCombinedLedger(data) {
5
-  return request({ url: '/ledger/import/combined', method: 'post', data })
4
+export function importCombinedLedger(data, timeout = 300000) {
5
+  return request({ url: '/ledger/import/combined', method: 'post', data, timeout })
6 6
 }
7 7
 
8 8
 // ===== 台账数据清理(按导入时间范围)=====

+ 10 - 10
src/router/index.js

@@ -107,17 +107,17 @@ export const constantRoutes = [
107 107
       //   meta: { title: '机场安检员考勤管理系统', icon: 'dashboard' }
108 108
       // },
109 109
       // {
110
-	    //   path: '/examQuestionStatistics',
111
-	    //   component: () => import('@/views/dataBigScreen/examQuestionStatistics'),
112
-	    //   name: 'examQuestionStatistics',
113
-	    //   meta: { title: '答题统计', icon: 'dashboard' }
114
-	    // },
110
+      //   path: '/examQuestionStatistics',
111
+      //   component: () => import('@/views/dataBigScreen/examQuestionStatistics'),
112
+      //   name: 'examQuestionStatistics',
113
+      //   meta: { title: '答题统计', icon: 'dashboard' }
114
+      // },
115 115
       // {
116
-	    //   path: '/abilityPortrait',
117
-	    //   component: () => import('@/views/dataBigScreen/abilityPortrait'),
118
-	    //   name: 'abilityPortrait',
119
-	    //   meta: { title: '能力画像显示大屏', icon: 'dashboard' }
120
-	    // }
116
+      //   path: '/abilityPortrait',
117
+      //   component: () => import('@/views/dataBigScreen/abilityPortrait'),
118
+      //   name: 'abilityPortrait',
119
+      //   meta: { title: '能力画像显示大屏', icon: 'dashboard' }
120
+      // }
121 121
     ]
122 122
   },
123 123
 ]

+ 50 - 91
src/views/portraitManagement/components/SearchBar.vue

@@ -52,7 +52,7 @@
52 52
 </template>
53 53
 
54 54
 <script setup>
55
-import { ref, nextTick, onMounted, onUnmounted } from 'vue'
55
+import { ref, nextTick, onMounted } from 'vue'
56 56
 import { searchPortraitUsers } from '@/api/score/index'
57 57
 import { getDeptList, getDeptUserTree } from '@/api/item/items'
58 58
 import { listDept } from '@/api/system/dept'
@@ -74,46 +74,36 @@ const treeRef = ref(null)
74 74
 const currentKey = ref(null)
75 75
 const curQuery = ref({})
76 76
 
77
-const defaultIdMap = {
78
-  user: 13,
79
-  BRIGADE: 103,
80
-  STATION: 100,
81
-  MANAGER: 104,
82
-  TEAMS: 110
83
-}
84
-
85
-const findNodeById = (nodes, id) => {
86
-  if (!nodes) return null
87
-  for (const node of nodes) {
88
-    if (node.id === id || node.deptId === id) return node
89
-    if (node.children) {
90
-      const found = findNodeById(node.children, id)
91
-      if (found) return found
92
-    }
77
+const findFirstNodeByDeptType = (nodes, targetType) => {
78
+  if (!nodes || nodes.length === 0) return null
79
+  const first = nodes[0]
80
+  if (first.deptType === targetType || first.nodeType === targetType) return first
81
+  if (first.children && first.children.length > 0) {
82
+    return findFirstNodeByDeptType(first.children, targetType)
93 83
   }
94 84
   return null
95 85
 }
96 86
 
97
-const buildPathForNode = (nodes, targetId, path = []) => {
98
-  if (!nodes) return path
87
+const buildPathForNode = (nodes, targetId) => {
88
+  if (!nodes) return null
99 89
   for (const node of nodes) {
100 90
     if (node.id === targetId || node.deptId === targetId) {
101 91
       // 如果是用户节点,不把用户名字加入 path
102 92
       if (node.deptType === 'user' || node.nodeType === 'user') {
103
-        return path
93
+        return []
104 94
       }
105 95
       const name = node.nickName || node.deptName || node.name || node.label
106
-      return [...path, name]
96
+      return [name]
107 97
     }
108
-    if (node.children) {
109
-      const name = node.deptName || node.name || node.label
110
-      const found = buildPathForNode(node.children, targetId, [...path, name])
111
-      if (found.length > path.length) {
112
-        return found
98
+    if (node.children && node.children.length > 0) {
99
+      const childPath = buildPathForNode(node.children, targetId)
100
+      if (childPath !== null) {
101
+        const name = node.deptName || node.name || node.label
102
+        return [name, ...childPath]
113 103
       }
114 104
     }
115 105
   }
116
-  return path
106
+  return null
117 107
 }
118 108
 
119 109
 const queryUsers = async (query, cb) => {
@@ -123,7 +113,7 @@ const queryUsers = async (query, cb) => {
123 113
     const mapped = (res.data || []).map(u => {
124 114
       let path = []
125 115
       if (u.deptId) {
126
-        path = buildPathForNode(departments.value, u.deptId)
116
+        path = buildPathForNode(departments.value, u.deptId) || []
127 117
       }
128 118
       return { ...u, value: u.nickName, path }
129 119
     })
@@ -138,7 +128,7 @@ const queryDept = async (query, cb) => {
138 128
     const mapped = (res.data || []).map(d => {
139 129
       let path = []
140 130
       if (d.id || d.deptId) {
141
-        path = buildPathForNode(departments.value, d.deptId || d.id)
131
+        path = buildPathForNode(departments.value, d.deptId || d.id) || []
142 132
       }
143 133
       return { ...d, value: d.deptName, path }
144 134
     })
@@ -149,14 +139,14 @@ const queryDept = async (query, cb) => {
149 139
 const handleSelect = (item) => {
150 140
   
151 141
   if (props.deptType === 'user') {
152
-    const path = item.path && item.path.length > 0 ? item.path : (item.deptId ? buildPathForNode(departments.value, item.deptId) : [])
142
+    const path = item.path && item.path.length > 0 ? item.path : (item.deptId ? buildPathForNode(departments.value, item.deptId) || [] : [])
153 143
     const name = item.nickName
154 144
 
155 145
     personName.value = path.length > 0 ? `${path.join(' / ')} / ${name}` : name
156 146
     curQuery.value = { personName: item.nickName,id:item.id }
157 147
     searchHandler(curQuery.value)
158 148
   } else {
159
-    const path = item.path && item.path.length > 0 ? item.path : ((item.id || item.deptId) ? buildPathForNode(departments.value, item.deptId || item.id) : [])
149
+    const path = item.path && item.path.length > 0 ? item.path : ((item.id || item.deptId) ? buildPathForNode(departments.value, item.deptId || item.id) || [] : [])
160 150
     const name = item.deptName || item.name || item.label
161 151
     personName.value = path.length > 0 ? path.join(' / ') : name
162 152
     if (props.deptType === 'BRIGADE' || props.deptType === 'STATION') {
@@ -239,7 +229,7 @@ const handleNodeClick = (node) => {
239 229
   if (props.deptType === 'user') {
240 230
     if (node.nodeType === 'user') {
241 231
    
242
-      const path = buildPathForNode(departments.value, node.deptId || node.id)
232
+      const path = buildPathForNode(departments.value, node.deptId || node.id) || []
243 233
       const name = node.label
244 234
       
245 235
       personName.value = path.length > 0 ? `${path.join(' / ')} / ${name}` : name
@@ -248,7 +238,7 @@ const handleNodeClick = (node) => {
248 238
     }
249 239
   } else {
250 240
     if (node.deptType === props.deptType) {
251
-      const path = buildPathForNode(departments.value, node.deptId || node.id)
241
+      const path = buildPathForNode(departments.value, node.deptId || node.id) || []
252 242
       const name = node.deptName || node.name || node.label;
253 243
       personName.value = path.length > 0 ? path.join(' / ') : name
254 244
       if (props.deptType === 'BRIGADE' || props.deptType === 'STATION') {
@@ -265,32 +255,7 @@ const handleNodeClick = (node) => {
265 255
   }
266 256
 }
267 257
 
268
-const setStyle = () => {
269
-  const root = document.querySelector(':root')
270
-  root.style.setProperty('--el-bg-color-overlay', '#1c1936')
271
-  root.style.setProperty('--el-border-color-extra-light', '#5c676d')
272
-  root.style.setProperty('--el-border-color-light', 'transparent')
273
-  root.style.setProperty('--el-popover-padding', '0px')
274
-  root.style.setProperty('--el-text-color-regular', '#fff')
275
-  root.style.setProperty('--el-fill-color-light', '#5c676d')
276
-  root.style.setProperty('--el-text-color-primary', '#fff')
277
-}
278
-
279
-const resetStyle = () => {
280
-  const root = document.querySelector(':root')
281
-  root.style.setProperty('--el-bg-color-overlay', '#ffffff')
282
-  root.style.setProperty('--el-border-color-extra-light', '#f2f6fc')
283
-  root.style.setProperty('--el-border-color-light', '#e4e7ed')
284
-  root.style.setProperty('--el-popover-padding', '12px')
285
-  root.style.setProperty('--el-text-color-regular', '#606266')
286
-  root.style.setProperty('--el-fill-color-light', '#f5f7fa')
287
-  root.style.setProperty('--el-text-color-primary', '#303133')
288
-
289
-}
290
-
291 258
 onMounted(async () => {
292
-  setStyle()
293
-
294 259
   if (props.deptType === 'user') {
295 260
     const res = await getDeptUserTree()
296 261
     departments.value = res.data
@@ -299,48 +264,42 @@ onMounted(async () => {
299 264
     departments.value = filterDeptTree(res.data, props.deptType)
300 265
   }
301 266
 
302
-  const defaultId = defaultIdMap[props.deptType]
303
-  if (defaultId) {
304
-    const defaultNode = findNodeById(departments.value, defaultId)
305
-    if (defaultNode) {
306
-      currentKey.value = defaultId
307
-      if (props.deptType === 'user') {
308
-        const path = buildPathForNode(departments.value, defaultNode.deptId || defaultNode.id)
309
-        const name = defaultNode.label
310
-        personName.value = path.length > 0 ? `${path.join(' / ')} / ${name}` : name
311
-        curQuery.value = { personName: defaultNode.label,id:defaultNode.id }
312
-        searchHandler(curQuery.value)
313
-      } else {
314
-        const path = buildPathForNode(departments.value, defaultNode.deptId || defaultNode.id)
315
-        const name = defaultNode.deptName || defaultNode.name || defaultNode.label
316
-        personName.value = path.length > 0 ? path.join(' / ') : name
317
-        if (props.deptType === 'BRIGADE' || props.deptType === 'STATION') {
318
-          curQuery.value = { deptId: defaultNode.deptId || defaultNode.id }
319
-        }
320
-        if (props.deptType === 'MANAGER') {
321
-          curQuery.value = { teamId: defaultNode.deptId || defaultNode.id, deptId: defaultNode.deptId || defaultNode.id }
322
-        }
323
-        if (props.deptType === 'TEAMS') {
324
-          curQuery.value = { groupId: defaultNode.deptId || defaultNode.id, deptId: defaultNode.deptId || defaultNode.id }
325
-        }
326
-        searchHandler(curQuery.value)
267
+  const targetType = props.deptType === 'user' ? 'user' : props.deptType
268
+  const defaultNode = findFirstNodeByDeptType(departments.value, targetType)
269
+  if (defaultNode) {
270
+    const nodeId = defaultNode.deptId || defaultNode.id
271
+    currentKey.value = nodeId
272
+    if (props.deptType === 'user') {
273
+      const path = buildPathForNode(departments.value, nodeId) || []
274
+      const name = defaultNode.label
275
+      personName.value = path.length > 0 ? `${path.join(' / ')} / ${name}` : name
276
+      curQuery.value = { personName: defaultNode.label, id: defaultNode.id }
277
+      searchHandler(curQuery.value)
278
+    } else {
279
+      const path = buildPathForNode(departments.value, nodeId) || []
280
+      const name = defaultNode.deptName || defaultNode.name || defaultNode.label
281
+      personName.value = path.length > 0 ? path.join(' / ') : name
282
+      if (props.deptType === 'BRIGADE' || props.deptType === 'STATION') {
283
+        curQuery.value = { deptId: nodeId }
284
+      }
285
+      if (props.deptType === 'MANAGER') {
286
+        curQuery.value = { teamId: nodeId, deptId: nodeId }
327 287
       }
328
-      nextTick(() => {
329
-        treeRef.value?.setCurrentKey(defaultId)
330
-      })
288
+      if (props.deptType === 'TEAMS') {
289
+        curQuery.value = { groupId: nodeId, deptId: nodeId }
290
+      }
291
+      searchHandler(curQuery.value)
331 292
     }
293
+    nextTick(() => {
294
+      treeRef.value?.setCurrentKey(nodeId)
295
+    })
332 296
   }
333 297
 })
334
-onUnmounted(() => {
335
-  resetStyle()
336
-})
337 298
 
338 299
 defineExpose({
339 300
   getDefQuery() {
340
-
341 301
     return {
342 302
       ...getTimeRange(),
343
-
344 303
     }
345 304
   }
346 305
 })

+ 4 - 3
src/views/portraitManagement/components/TestResult.vue

@@ -31,9 +31,10 @@ const setChartsOptionsBar = computed(() => {
31 31
       textStyle: { color: '#fff' }
32 32
     },
33 33
     legend: {
34
-      top: '10%',
35
-      left: '0%',
36
-      orient: 'vertical',
34
+      type: 'scroll',
35
+      top: '0%',
36
+      left: 'center',
37
+      orient: 'horizontal',
37 38
       textStyle: { color: '#fff' }
38 39
     },
39 40
     series: [{

+ 2 - 1
src/views/portraitManagement/deptProfile/index.vue

@@ -31,9 +31,10 @@ const queryParams = ref({})
31 31
 
32 32
 const pentagonItems = ref([])
33 33
 const invokerCountDeptTeamStats = (params) => {
34
+  console.log(params,999)
34 35
   const copyParams = {
35 36
     deptId: params.deptId,
36
-
37
+    ...params
37 38
   }
38 39
   countDeptTeamStats(copyParams).then(res => {
39 40
     if (res.code === 200 && res.data) {

+ 25 - 7
src/views/portraitManagement/employeeProfile/index.vue

@@ -187,9 +187,9 @@
187 187
         <!-- 加分区域 -->
188 188
         <div class="tooltip-section">
189 189
           <div class="section-list">
190
-            <div v-if="addList.length" class="list-item" v-for="(item, i) in addList" :key="i">
191
-              <span>{{ item.level3Name }}:</span>
192
-              <span class="add-text">{{ item.totalScore }}分</span>
190
+            <div v-if="addGroupList.length" class="list-item" v-for="(item, i) in addGroupList" :key="i">
191
+              <span>{{ item.name }}:</span>
192
+              <span class="add-text">+{{ item.total }}分</span>
193 193
             </div>
194 194
             <div v-else class="p-empty">暂无加分记录</div>
195 195
           </div>
@@ -201,11 +201,11 @@
201 201
         <!-- 扣分区域 -->
202 202
         <div class="tooltip-section">
203 203
           <div class="section-list">
204
-            <div v-if="deductList.length" class="list-item" v-for="(item, i) in deductList" :key="i">
205
-              <span>{{ item.level3Name }}:</span>
206
-              <span class="deduct-text">{{ item.totalScore }}分</span>
204
+            <div v-if="deductGroupList.length" class="list-item" v-for="(item, i) in deductGroupList" :key="i">
205
+              <span>{{ item.name }}:</span>
206
+              <span class="deduct-text">{{ item.total }}分</span>
207 207
             </div>
208
-            <div v-if="!deductList.length" class="p-empty">暂无扣分记录</div>
208
+            <div v-if="!deductGroupList.length" class="p-empty">暂无扣分记录</div>
209 209
           </div>
210 210
           <div class="section-total deduct">
211 211
             <span>合计扣分:</span>
@@ -229,6 +229,8 @@ import { useECharts } from '@/hooks/useEcharts'
229 229
 import useUserStore from '@/store/modules/user'
230 230
 import { useRouter } from 'vue-router'
231 231
 
232
+defineOptions({ name: 'ProfileEmployeeProfile' })
233
+
232 234
 const router = useRouter()
233 235
 let radarChartInstance = null
234 236
 
@@ -252,6 +254,22 @@ const deductList = computed(() => {
252 254
   const all = scoreDetails.value.filter(d => d.totalScore != null && Number(d.totalScore) < 0)
253 255
   return activeDimName.value ? all.filter(d => d.dimensionName === activeDimName.value) : all
254 256
 })
257
+const addGroupList = computed(() => {
258
+  const groups = {}
259
+  addList.value.forEach(d => {
260
+    const key = d.level2Name || '其他'
261
+    groups[key] = (groups[key] || 0) + Number(d.totalScore)
262
+  })
263
+  return Object.entries(groups).map(([name, total]) => ({ name, total: total.toFixed(2) }))
264
+})
265
+const deductGroupList = computed(() => {
266
+  const groups = {}
267
+  deductList.value.forEach(d => {
268
+    const key = d.level2Name || '其他'
269
+    groups[key] = (groups[key] || 0) + Number(d.totalScore)
270
+  })
271
+  return Object.entries(groups).map(([name, total]) => ({ name, total: total.toFixed(2) }))
272
+})
255 273
 const addTotal = computed(() => {
256 274
   const s = addList.value.reduce((acc, d) => acc + Number(d.totalScore), 0)
257 275
   return (s > 0 ? '+' : '') + s.toFixed(2)

+ 1 - 1
src/views/portraitManagement/employeeProfile/indexOld.vue

@@ -380,7 +380,7 @@ import { searchPortraitUsers, getEmployeePortrait } from '@/api/score/index'
380 380
 import { getDeptUserTree } from '@/api/item/items'
381 381
 import { useDict } from '@/utils/dict'
382 382
 
383
-defineOptions({ name: 'EmployeeProfile' })
383
+// defineOptions({ name: 'EmployeeProfile' })
384 384
 
385 385
 const route = useRoute()
386 386
 const currentTime = ref('year')

+ 3 - 1
src/views/portraitManagement/groupProfile/index.vue

@@ -33,7 +33,9 @@ const queryParams = ref({})
33 33
 const pentagonItems = ref([])
34 34
 const invokerCountDeptTeamStats = (params) => {
35 35
   const copyParams = {
36
-    deptId: params.groupId
36
+    deptId: params.groupId,
37
+    startDate: params.startDate,
38
+    endDate: params.endDate
37 39
   }
38 40
   countDeptTeamStats(copyParams).then(res => {
39 41
     if (res.code === 200 && res.data) {

+ 3 - 1
src/views/portraitManagement/teamProfile/index.vue

@@ -31,7 +31,9 @@ const queryParams = ref({})
31 31
 const pentagonItems = ref([])
32 32
 const invokerCountDeptTeamStats = (params) => {
33 33
   const copyParams = {
34
-    deptId: params.teamId
34
+    deptId: params.teamId,
35
+    startDate: params.startDate,
36
+    endDate: params.endDate
35 37
   }
36 38
   countDeptTeamStats(copyParams).then(res => {
37 39
     if (res.code === 200 && res.data) {

+ 222 - 58
src/views/score/event/index.vue

@@ -45,6 +45,27 @@
45 45
         <el-date-picker v-model="dateRange" type="daterange" value-format="YYYY-MM-DD" range-separator="-"
46 46
           start-placeholder="开始日期" end-placeholder="结束日期" clearable />
47 47
       </el-form-item>
48
+      <el-form-item label="扣分类型" prop="scoreType">
49
+        <el-select v-model="queryParams.scoreType" placeholder="全部类型" clearable style="width:150px">
50
+          <el-option v-for="dict in score_type" :key="dict.value" :label="dict.label" :value="dict.value" />
51
+        </el-select>
52
+      </el-form-item>
53
+      <el-form-item label="区域" prop="regionalId">
54
+        <el-select v-model="queryParams.regionalId" placeholder="请选择区域" clearable filterable style="width:200px"
55
+          @change="handleRegionalChange">
56
+          <el-option v-for="item in regionalOptions" :key="item.id" :label="item.name" :value="item.id" />
57
+        </el-select>
58
+      </el-form-item>
59
+      <el-form-item label="工作点" prop="channelId">
60
+        <el-select v-model="queryParams.channelId" placeholder="请选择工作点" clearable filterable style="width:150px">
61
+          <el-option v-for="item in channelOptions" :key="item.id" :label="item.positionName" :value="item.id" />
62
+        </el-select>
63
+      </el-form-item>
64
+      <el-form-item label="岗位" prop="postId">
65
+        <el-tree-select v-model="queryParams.postId" :data="postTreeData"
66
+          :props="{ value: 'postId', label: 'postName', children: 'children' }" clearable filterable placeholder="请选择岗位"
67
+          check-strictly />
68
+      </el-form-item>
48 69
       <el-form-item>
49 70
         <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
50 71
         <el-button icon="Refresh" @click="resetQuery">重置</el-button>
@@ -67,36 +88,40 @@
67 88
         <el-button type="warning" plain icon="Download" @click="handleExport"
68 89
           v-hasPermi="['score:event:export']">导出</el-button>
69 90
       </el-col>
70
-      <el-col :span="1.5">
91
+      <!-- <el-col :span="1.5">
71 92
         <el-upload :show-file-list="false" accept=".xlsx,.xls" :auto-upload="false" :on-change="handleImportFile">
72 93
           <el-button type="info" plain icon="Upload" v-hasPermi="['score:event:import']">导入</el-button>
73 94
         </el-upload>
74
-      </el-col>
95
+      </el-col> -->
75 96
       <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
76 97
     </el-row>
77 98
 
78
-    <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
79
-      <el-table-column type="selection" width="55" align="center" />
80
-      <el-table-column label="配分层级" align="center" prop="org" width="110">
99
+    <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange" style="width: 100%;" fit="true"
100
+      :scrollbar-always-on="true">
101
+      <el-table-column type="selection" width="55" align="center" resizable />
102
+      <el-table-column label="配分层级" align="center" prop="org" width="110" resizable>
81 103
         <template #default="{ row }"><dict-tag :options="score_level" :value="row.org" /></template>
82 104
       </el-table-column>
83
-      <el-table-column label="事件时间" align="center" prop="eventTime" width="120">
105
+      <el-table-column label="事件时间" align="center" prop="eventTime" width="120" resizable>
84 106
         <template #default="{ row }">{{ parseTime(row.eventTime, '{y}-{m}-{d}') }}</template>
85 107
       </el-table-column>
86
-      <el-table-column label="维度" align="center" prop="dimensionName" width="110" />
87
-      <el-table-column label="二级指标" align="center" prop="level2Name" show-overflow-tooltip />
88
-      <el-table-column label="三级指标" align="center" prop="level3Name" show-overflow-tooltip />
89
-      <el-table-column label="责任人" align="center" prop="personName" width="80" />
90
-      <el-table-column label="部门" align="center" prop="deptName" />
91
-      <el-table-column label="班组" align="center" prop="teamName" />
92
-      <el-table-column label="基础分值" align="center" prop="scoreValue" width="90">
108
+      <el-table-column label="维度" align="center" prop="dimensionName" width="150" resizable />
109
+      <el-table-column label="二级指标" align="center" prop="level2Name" min-width="250" show-overflow-tooltip resizable />
110
+      <el-table-column label="三级指标" align="center" prop="level3Name" min-width="250" show-overflow-tooltip resizable />
111
+      <el-table-column label="责任人" align="center" prop="personName" width="80" resizable />
112
+      <el-table-column label="部门" align="center" prop="deptName" min-width="120" resizable />
113
+      <el-table-column label="班组" align="center" prop="teamName" min-width="120" resizable />
114
+      <el-table-column label="区域" align="center" prop="regionalName" min-width="200" resizable />
115
+      <el-table-column label="工作点" align="center" prop="channelName" min-width="100" resizable />
116
+      <el-table-column label="岗位" align="center" prop="postName" min-width="190" resizable />
117
+      <el-table-column label="基础分值" align="center" prop="scoreValue" width="90" resizable>
93 118
         <template #default="{ row }">
94 119
           <span :style="{ color: row.scoreValue > 0 ? '#67c23a' : row.scoreValue < 0 ? '#f56c6c' : '' }">
95 120
             {{ row.scoreValue > 0 ? '+' : '' }}{{ row.scoreValue }}
96 121
           </span>
97 122
         </template>
98 123
       </el-table-column>
99
-      <el-table-column label="叠加分" align="center" prop="cascadeScore" width="80">
124
+      <el-table-column label="叠加分" align="center" prop="cascadeScore" width="80" resizable>
100 125
         <template #default="{ row }">
101 126
           <span v-if="row.cascadeScore" :style="{ color: row.cascadeScore > 0 ? '#67c23a' : '#f56c6c' }">
102 127
             {{ row.cascadeScore > 0 ? '+' : '' }}{{ row.cascadeScore }}
@@ -104,22 +129,22 @@
104 129
           <span v-else style="color:#c0c4cc">-</span>
105 130
         </template>
106 131
       </el-table-column>
107
-      <el-table-column label="总分值" align="center" prop="totalScore" width="90">
132
+      <el-table-column label="总分值" align="center" prop="totalScore" width="90" resizable>
108 133
         <template #default="{ row }">
109 134
           <strong :style="{ color: row.totalScore > 0 ? '#67c23a' : row.totalScore < 0 ? '#f56c6c' : '' }">
110 135
             {{ row.totalScore > 0 ? '+' : '' }}{{ row.totalScore }}
111 136
           </strong>
112 137
         </template>
113 138
       </el-table-column>
114
-      <el-table-column label="来源" align="center" width="90">
139
+      <el-table-column label="来源" align="center" width="90" resizable>
115 140
         <template #default="{ row }">
116 141
           <el-tag :type="row.sourceType === '1' ? 'primary' : 'warning'" size="small">
117 142
             {{ row.sourceType === '1' ? '手动录入' : '台账同步' }}
118 143
           </el-tag>
119 144
         </template>
120 145
       </el-table-column>
121
-      <el-table-column label="事件描述" align="center" prop="eventDesc" show-overflow-tooltip />
122
-      <el-table-column label="操作" align="center" width="180">
146
+      <el-table-column label="事件描述" min-width="450" align="center" prop="eventDesc" show-overflow-tooltip resizable />
147
+      <el-table-column label="操作" align="center" width="180" resizable fixed="right">
123 148
         <template #default="{ row }">
124 149
           <el-button link type="primary" icon="Edit" @click="handleUpdate(row)"
125 150
             v-hasPermi="['score:event:edit']">修改</el-button>
@@ -180,8 +205,18 @@
180 205
             </el-form-item>
181 206
           </el-col>
182 207
           <el-col :span="12">
183
-            <el-form-item label="位置">
184
-              <el-input v-model="form.location" placeholder="区域/通道" />
208
+            <el-form-item label="区域">
209
+              <el-select v-model="form.regionalId" placeholder="请选择区域" clearable filterable style="width:100%"
210
+                @change="handleFormRegionalChange">
211
+                <el-option v-for="item in regionalOptions" :key="item.id" :label="item.name" :value="item.id" />
212
+              </el-select>
213
+            </el-form-item>
214
+          </el-col>
215
+          <el-col :span="12">
216
+            <el-form-item label="工作点">
217
+              <el-select v-model="form.channelId" placeholder="请选择工作点" clearable filterable style="width:100%">
218
+                <el-option v-for="item in formChannelOptions" :key="item.id" :label="item.name" :value="item.id" />
219
+              </el-select>
185 220
             </el-form-item>
186 221
           </el-col>
187 222
           <el-col :span="12">
@@ -217,6 +252,13 @@
217 252
             </el-form-item>
218 253
           </el-col>
219 254
           <el-col :span="12">
255
+            <el-form-item label="岗位">
256
+              <el-tree-select v-model="form.postId" :data="postTreeData"
257
+                :props="{ value: 'postId', label: 'postName', children: 'children' }" clearable filterable
258
+                placeholder="请选择岗位" style="width:100%" check-strictly />
259
+            </el-form-item>
260
+          </el-col>
261
+          <el-col :span="12">
220 262
             <el-form-item label="基础分值" prop="scoreValue">
221 263
               <el-input-number v-model="form.scoreValue" :precision="2" style="width:100%" placeholder="正数=加分 负数=扣分" />
222 264
             </el-form-item>
@@ -251,6 +293,8 @@ import { ref, reactive, computed, watch, onMounted, getCurrentInstance } from 'v
251 293
 import { ElMessage, ElMessageBox } from 'element-plus'
252 294
 import { useRoute } from 'vue-router'
253 295
 import { listUser, getDeptOrgInfo, getUserOrgInfo } from '@/api/system/user'
296
+import { listPosition } from '@/api/system/position'
297
+import { listAllTree } from '@/api/system/post'
254 298
 import {
255 299
   listScoreEvent, addScoreEvent, updateScoreEvent, delScoreEvent,
256 300
   exportScoreEvent, importScoreEvent
@@ -263,7 +307,7 @@ import { parseTime } from '@/utils/ruoyi'
263 307
 defineOptions({ name: 'ScoreEvent' })
264 308
 
265 309
 const { proxy } = getCurrentInstance()
266
-const { score_level } = proxy.useDict('score_level')
310
+const { score_level, score_type } = proxy.useDict('score_level', 'score_type')
267 311
 
268 312
 const loading = ref(false), list = ref([]), total = ref(0), showSearch = ref(true)
269 313
 const dateRange = ref([]), queryRef = ref(null), formRef = ref(null)
@@ -282,9 +326,13 @@ const queryDeptOptions = ref([])
282 326
 const queryTeamOptions = ref([])
283 327
 const queryGroupOptions = ref([])
284 328
 const queryPersonOptions = ref([])
329
+const regionalOptions = ref([])
330
+const channelOptions = ref([])
331
+const postTreeData = ref([])
285 332
 
286
-const queryParams = reactive({ pageNum: 1, pageSize: 10, personId: null, personName: '', deptId: null, deptName: '', teamId: null, teamName: '', groupId: null, groupName: '', dimensionId: null, sourceType: '', org: '' })
287
-const form = reactive({ id: null, dimensionId: null, dimensionName: '', indicatorId: null, level2Id: null, level2Name: '', level3Id: null, level3Name: '', level4Id: null, level4Name: '', eventTime: '', location: '', personId: null, deptName: '', deptId: null, teamId: null, groupId: null, scoreValue: 0, cascadeScore: 0, eventDesc: '', remark: '', org: '' })
333
+const queryParams = reactive({ pageNum: 1, pageSize: 10, personId: null, personName: '', deptId: null, deptName: '', teamId: null, teamName: '', groupId: null, groupName: '', dimensionId: null, sourceType: '', org: '', scoreType: null, regionalId: null, channelId: null, postId: null })
334
+const form = reactive({ id: null, dimensionId: null, dimensionName: '', indicatorId: null, level2Id: null, level2Name: '', level3Id: null, level3Name: '', level4Id: null, level4Name: '', eventTime: '', location: '', personId: null, deptName: '', deptId: null, teamId: null, groupId: null, scoreValue: 0, cascadeScore: 0, eventDesc: '', remark: '', org: '', regionalId: null, channelId: null, postId: null })
335
+const formChannelOptions = ref([])
288 336
 const rules = computed(() => {
289 337
   return {
290 338
     org: [{ required: true, message: '请选择配分层级', trigger: 'submit' }],
@@ -300,6 +348,7 @@ const rules = computed(() => {
300 348
 
301 349
 // 搜索表单责任人选项:未选择部门/班组/小组时使用全量人员,否则使用过滤后的人员
302 350
 const searchPersonOptions = computed(() => {
351
+
303 352
   if (queryParams.deptId || queryParams.teamId || queryParams.groupId) {
304 353
     return queryPersonOptions.value
305 354
   }
@@ -328,6 +377,10 @@ async function handleFormLevelChange() {
328 377
   teamOptions.value = []; groupOptions.value = []; personOptions.value = []
329 378
 
330 379
   const org = form.org
380
+
381
+
382
+  const r = await getDimensionAll({ pageNum: 1, pageSize: 100, org })
383
+  dimensionOptions.value = r.data || []
331 384
   // if (org === '2') {
332 385
   await loadAllTeams()
333 386
   // } else if (org === '3') {
@@ -335,9 +388,6 @@ async function handleFormLevelChange() {
335 388
   // } else if (org === '4') {
336 389
   await loadAllPersons()
337 390
   // }
338
-
339
-  const r = await getDimensionAll({ pageNum: 1, pageSize: 100, org })
340
-  dimensionOptions.value = r.data || []
341 391
 }
342 392
 
343 393
 async function loadAllTeams() {
@@ -357,9 +407,33 @@ async function loadAllGroups() {
357 407
 
358 408
 async function loadAllPersons() {
359 409
   const r = await listUser({ pageSize: 9999 })
410
+
360 411
   personOptions.value = r.rows || []
361 412
 }
362 413
 
414
+async function loadRegions() {
415
+  const r = await listPosition({ positionType: 'REGIONAL' })
416
+  regionalOptions.value = r.data || []
417
+}
418
+
419
+async function handleRegionalChange(val) {
420
+  queryParams.channelId = null
421
+  channelOptions.value = []
422
+  if (val) {
423
+    const r = await listPosition({ parentId: val })
424
+    channelOptions.value = r.data || []
425
+  }
426
+}
427
+
428
+async function handleFormRegionalChange(val) {
429
+  form.channelId = null
430
+  formChannelOptions.value = []
431
+  if (val) {
432
+    const r = await listPosition({ parentId: val })
433
+    formChannelOptions.value = r.data || []
434
+  }
435
+}
436
+
363 437
 function handleBrigadeChange() {
364 438
 }
365 439
 
@@ -443,60 +517,90 @@ function getList() {
443 517
 function handleQuery() { queryParams.pageNum = 1; getList() }
444 518
 function resetQuery() {
445 519
   dateRange.value = []
446
-  Object.assign(queryParams, { 
447
-    pageNum: 1, 
448
-    pageSize: 10, 
449
-    personId: null, 
450
-    personName: '', 
451
-    deptId: null, 
452
-    deptName: '', 
453
-    teamId: null, 
454
-    teamName: '', 
455
-    groupId: null, 
456
-    groupName: '', 
457
-    dimensionId: null, 
458
-    sourceType: '', 
459
-    org: '' 
520
+  Object.assign(queryParams, {
521
+    pageNum: 1,
522
+    pageSize: 10,
523
+    personId: null,
524
+    personName: '',
525
+    deptId: null,
526
+    deptName: '',
527
+    teamId: null,
528
+    teamName: '',
529
+    groupId: null,
530
+    groupName: '',
531
+    dimensionId: null,
532
+    sourceType: '',
533
+    org: '',
534
+    scoreType: null,
535
+    regionalId: null,
536
+    channelId: null,
537
+    postId: null
460 538
   })
461
-  queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []
539
+  queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []; channelOptions.value = []
462 540
   queryRef.value?.resetFields()
463 541
   handleQuery()
464 542
 }
465 543
 function handleSelectionChange(sel) { ids.value = sel.map(s => s.id); single.value = sel.length !== 1; multiple.value = !sel.length }
466 544
 
467 545
 function resetForm() {
468
-  Object.assign(form, { id: null, dimensionId: null, dimensionName: '', indicatorId: null, level2Id: null, level2Name: '', level3Id: null, level3Name: '', level4Id: null, level4Name: '', eventTime: '', location: '', personId: null, deptName: '', deptId: null, teamId: null, groupId: null, scoreValue: 0, cascadeScore: 0, eventDesc: '', remark: '', org: '' })
546
+  Object.assign(form, { id: null, dimensionId: null, dimensionName: '', indicatorId: null, level2Id: null, level2Name: '', level3Id: null, level3Name: '', level4Id: null, level4Name: '', eventTime: '', location: '', personId: null, deptName: '', deptId: null, teamId: null, groupId: null, scoreValue: 0, cascadeScore: 0, eventDesc: '', remark: '', org: '', regionalId: null, channelId: null, postId: null })
469 547
   level2Options.value = []; level3Options.value = []; level4Options.value = []
470
-  teamOptions.value = []; groupOptions.value = []; personOptions.value = []
548
+  teamOptions.value = []; groupOptions.value = []; personOptions.value = []; formChannelOptions.value = []
471 549
 }
472 550
 
473 551
 function handleAdd() { resetForm(); dialogTitle.value = '新增配分事项'; dialogVisible.value = true }
474
-function handleUpdate(row) {
552
+async function handleUpdate(row) {
475 553
   resetForm()
476 554
   const record = row?.id ? row : list.value.find(r => r.id === ids.value[0])
477 555
   if (record) {
478 556
     Object.assign(form, record)
479 557
     const org = String(record.org)
480 558
     form.org = org
559
+
560
+    // 加载区域和工作点
561
+    if (record.regionalId) {
562
+      await handleFormRegionalChange(record.regionalId)
563
+    }
564
+
565
+    // 加载队室/班组和通道/小组
566
+    if (record.deptId) {
567
+      // 先加载队室/班组
568
+      const r = await deptTreeSelect({ parentId: record.deptId })
569
+      teamOptions.value = r.data || []
570
+
571
+      if (record.teamId) {
572
+        // 再加载通道/小组
573
+        const r2 = await deptTreeSelect({ parentId: record.teamId })
574
+        groupOptions.value = r2.data || []
575
+
576
+        // 最后加载责任人
577
+        if (record.groupId) {
578
+          const r3 = await listUser({ deptId: record.groupId })
579
+          personOptions.value = r3.rows || []
580
+        }
581
+      }
582
+    } else if (org === '2') {
583
+      await loadAllTeams()
584
+    } else if (org === '3') {
585
+      await loadAllGroups()
586
+    } else if (org === '4') {
587
+      await loadAllPersons()
588
+    }
589
+
481 590
     if (org === '1') {
482 591
       form.deptId = record.deptId || null
483 592
     } else if (org === '2') {
484
-      loadAllTeams().then(() => {
485
-        form.teamId = record.teamId || null
486
-        if (form.teamId) handleDepartmentChange(form.teamId)
487
-      })
593
+      form.teamId = record.teamId || null
594
+      if (form.teamId) await handleDepartmentChange(form.teamId)
488 595
     } else if (org === '3') {
489
-      loadAllGroups().then(() => {
490
-        form.groupId = record.groupId || null
491
-        if (form.groupId) handleGroupChange(form.groupId)
492
-      })
596
+      form.groupId = record.groupId || null
597
+      if (form.groupId) await handleGroupChange(form.groupId)
493 598
     } else if (org === '4') {
494
-      loadAllPersons().then(() => {
495
-        form.personId = record.personId || null
496
-        if (form.personId) handlePersonChange(form.personId)
497
-      })
599
+      form.personId = record.personId || null
600
+      if (form.personId) await handlePersonChange(form.personId)
498 601
     }
499
-    if (record.dimensionId) onDimensionChange(record.dimensionId, true)
602
+
603
+    if (record.dimensionId) await onDimensionChange(record.dimensionId, true)
500 604
     if (record.level2Id) onLevel2Change(record.level2Id, true)
501 605
     if (record.level3Id) onLevel3Change(record.level3Id, true)
502 606
     if (record.level4Id) onLevel4Change(record.level4Id, true)
@@ -548,6 +652,20 @@ function onLevel4Change(val) {
548 652
   }
549 653
 }
550 654
 
655
+// 递归查找树形结构中的节点
656
+const findTreeNode = (nodes, targetId, idKey, nameKey) => {
657
+  for (const node of nodes) {
658
+    if (node[idKey] === targetId) {
659
+      return node
660
+    }
661
+    if (node.children) {
662
+      const found = findTreeNode(node.children, targetId, idKey, nameKey)
663
+      if (found) return found
664
+    }
665
+  }
666
+  return null
667
+}
668
+
551 669
 async function submitForm() {
552 670
   await formRef.value.validate()
553 671
   let copyForm = { ...form }
@@ -568,6 +686,18 @@ async function submitForm() {
568 686
     const g = groupOptions.value.find(g => g.id === copyForm.groupId)
569 687
     if (g) copyForm.groupName = g.label
570 688
   }
689
+  if (copyForm.regionalId) {
690
+    const r = regionalOptions.value.find(r => r.id === copyForm.regionalId)
691
+    if (r) copyForm.regionalName = r.name
692
+  }
693
+  if (copyForm.channelId) {
694
+    const c = formChannelOptions.value.find(c => c.id === copyForm.channelId)
695
+    if (c) copyForm.channelName = c.name || c.positionName
696
+  }
697
+  if (copyForm.postId) {
698
+    const postNode = findTreeNode(postTreeData.value, copyForm.postId, 'postId', 'postName')
699
+    if (postNode) copyForm.postName = postNode.postName
700
+  }
571 701
 
572 702
   if (form.id) { await updateScoreEvent(copyForm); ElMessage.success('修改成功') }
573 703
   else { await addScoreEvent(copyForm); ElMessage.success('新增成功') }
@@ -619,5 +749,39 @@ watch(() => route.query, (query) => {
619 749
   isInit = false
620 750
 }, { immediate: true })
621 751
 
622
-onMounted(() => { loadDimensions(); loadDepts(); loadQueryDepts(); loadAllPersons(); getList() })
752
+// 递归处理岗位树,禁用第一级
753
+const processPostTree = (nodes, isFirstLevel = true) => {
754
+  return nodes.map(node => {
755
+    const newNode = { ...node, disabled: isFirstLevel }
756
+    if (node.children) {
757
+      newNode.children = processPostTree(node.children, false)
758
+    }
759
+    return newNode
760
+  })
761
+}
762
+
763
+onMounted(() => {
764
+  loadDimensions();
765
+  loadDepts();
766
+  loadQueryDepts();
767
+  loadAllPersons();
768
+  loadRegions();
769
+  listAllTree().then(res => {
770
+    postTreeData.value = processPostTree(res.data || [])
771
+  });
772
+  getList()
773
+})
623 774
 </script>
775
+
776
+<style scoped lang="less">
777
+/* Element Plus表格横向滚动配置 */
778
+.table-container :deep(.el-table) {
779
+  width: 100%;
780
+  min-width: 100%;
781
+}
782
+
783
+.table-container :deep(.el-table .el-table__header-wrapper),
784
+.table-container :deep(.el-table .el-table__body-wrapper) {
785
+  overflow-x: auto !important;
786
+}
787
+</style>