index.vue 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
  4. <el-form-item label="配分层级" prop="org">
  5. <el-select v-model="queryParams.org" placeholder="请选择层级" style="width:200px" clearable
  6. @change="handleLevelChange">
  7. <el-option v-for="dict in score_level" :key="dict.value" :label="dict.label" :value="dict.value" />
  8. </el-select>
  9. </el-form-item>
  10. <el-form-item label="部门名称" prop="deptId">
  11. <el-select v-model="queryParams.deptId" placeholder="请选择部门" style="width:150px" clearable filterable
  12. @change="handleQueryDeptChange">
  13. <el-option v-for="d in queryDeptOptions" :key="d.deptId" :label="d.deptName" :value="d.deptId" />
  14. </el-select>
  15. </el-form-item>
  16. <el-form-item label="队室/班组" prop="teamId">
  17. <el-select v-model="queryParams.teamId" placeholder="请选择队室/班组" style="width:150px" clearable filterable
  18. @change="handleQueryTeamChange">
  19. <el-option v-for="t in queryTeamOptions" :key="t.id" :label="t.label" :value="t.id" />
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item label="通道/小组" prop="groupId">
  23. <el-select v-model="queryParams.groupId" placeholder="请选择通道/小组" style="width:150px" clearable filterable
  24. @change="handleQueryGroupChange">
  25. <el-option v-for="g in queryGroupOptions" :key="g.id" :label="g.label" :value="g.id" />
  26. </el-select>
  27. </el-form-item>
  28. <el-form-item label="责任人" prop="personId">
  29. <el-select v-model="queryParams.personId" placeholder="请选择责任人" style="width:150px" clearable filterable>
  30. <el-option v-for="p in searchPersonOptions" :key="p.userId" :label="p.nickName" :value="p.userId" />
  31. </el-select>
  32. </el-form-item>
  33. <el-form-item label="维度" prop="dimensionId">
  34. <el-select v-model="queryParams.dimensionId" placeholder="全部维度" clearable style="width:150px">
  35. <el-option v-for="d in dimensionOptions" :key="d.id" :label="d.name" :value="d.id" />
  36. </el-select>
  37. </el-form-item>
  38. <el-form-item label="来源" prop="sourceType">
  39. <el-select v-model="queryParams.sourceType" placeholder="全部来源" clearable style="width:120px">
  40. <el-option label="手动录入" value="1" />
  41. <el-option label="台账同步" value="2" />
  42. </el-select>
  43. </el-form-item>
  44. <el-form-item label="事件时间">
  45. <el-date-picker v-model="dateRange" type="daterange" value-format="YYYY-MM-DD" range-separator="-"
  46. start-placeholder="开始日期" end-placeholder="结束日期" clearable />
  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>
  69. <el-form-item>
  70. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  71. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  72. </el-form-item>
  73. </el-form>
  74. <el-row :gutter="10" class="mb8">
  75. <el-col :span="1.5">
  76. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['score:event:add']">新增</el-button>
  77. </el-col>
  78. <el-col :span="1.5">
  79. <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate"
  80. v-hasPermi="['score:event:edit']">修改</el-button>
  81. </el-col>
  82. <el-col :span="1.5">
  83. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
  84. v-hasPermi="['score:event:remove']">删除</el-button>
  85. </el-col>
  86. <el-col :span="1.5">
  87. <el-button type="warning" plain icon="Download" @click="handleExport"
  88. v-hasPermi="['score:event:export']">导出</el-button>
  89. </el-col>
  90. <!-- <el-col :span="1.5">
  91. <el-upload :show-file-list="false" accept=".xlsx,.xls" :auto-upload="false" :on-change="handleImportFile">
  92. <el-button type="info" plain icon="Upload" v-hasPermi="['score:event:import']">导入</el-button>
  93. </el-upload>
  94. </el-col> -->
  95. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
  96. </el-row>
  97. <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange" style="width: 100%;" fit="true"
  98. :scrollbar-always-on="true">
  99. <el-table-column type="selection" width="55" align="center" resizable />
  100. <el-table-column label="配分层级" align="center" prop="org" width="110" resizable>
  101. <template #default="{ row }"><dict-tag :options="score_level" :value="row.org" /></template>
  102. </el-table-column>
  103. <el-table-column label="事件时间" align="center" prop="eventTime" width="120" resizable>
  104. <template #default="{ row }">{{ parseTime(row.eventTime, '{y}-{m}-{d}') }}</template>
  105. </el-table-column>
  106. <el-table-column label="维度" align="center" prop="dimensionName" width="150" resizable />
  107. <el-table-column label="二级指标" align="center" prop="level2Name" min-width="250" show-overflow-tooltip resizable />
  108. <el-table-column label="三级指标" align="center" prop="level3Name" min-width="250" show-overflow-tooltip resizable />
  109. <el-table-column label="责任人" align="center" prop="personName" width="80" resizable />
  110. <el-table-column label="部门" align="center" prop="deptName" min-width="120" resizable />
  111. <el-table-column label="班组" align="center" prop="teamName" min-width="120" resizable />
  112. <el-table-column label="区域" align="center" prop="regionalName" min-width="200" resizable />
  113. <el-table-column label="工作点" align="center" prop="channelName" min-width="100" resizable />
  114. <el-table-column label="岗位" align="center" prop="postName" min-width="190" resizable />
  115. <el-table-column label="基础分值" align="center" prop="scoreValue" width="90" resizable>
  116. <template #default="{ row }">
  117. <span :style="{ color: row.scoreValue > 0 ? '#67c23a' : row.scoreValue < 0 ? '#f56c6c' : '' }">
  118. {{ row.scoreValue > 0 ? '+' : '' }}{{ row.scoreValue }}
  119. </span>
  120. </template>
  121. </el-table-column>
  122. <el-table-column label="叠加分" align="center" prop="cascadeScore" width="80" resizable>
  123. <template #default="{ row }">
  124. <span v-if="row.cascadeScore" :style="{ color: row.cascadeScore > 0 ? '#67c23a' : '#f56c6c' }">
  125. {{ row.cascadeScore > 0 ? '+' : '' }}{{ row.cascadeScore }}
  126. </span>
  127. <span v-else style="color:#c0c4cc">-</span>
  128. </template>
  129. </el-table-column>
  130. <el-table-column label="总分值" align="center" prop="totalScore" width="90" resizable>
  131. <template #default="{ row }">
  132. <strong :style="{ color: row.totalScore > 0 ? '#67c23a' : row.totalScore < 0 ? '#f56c6c' : '' }">
  133. {{ row.totalScore > 0 ? '+' : '' }}{{ row.totalScore }}
  134. </strong>
  135. </template>
  136. </el-table-column>
  137. <el-table-column label="来源" align="center" width="90" resizable>
  138. <template #default="{ row }">
  139. <el-tag :type="row.sourceType === '1' ? 'primary' : 'warning'" size="small">
  140. {{ row.sourceType === '1' ? '手动录入' : '台账同步' }}
  141. </el-tag>
  142. </template>
  143. </el-table-column>
  144. <el-table-column label="事件描述" min-width="450" align="center" prop="eventDesc" show-overflow-tooltip resizable />
  145. <el-table-column label="操作" align="center" width="180" resizable fixed="right">
  146. <template #default="{ row }">
  147. <el-button link type="primary" icon="Edit" @click="handleUpdate(row)"
  148. v-hasPermi="['score:event:edit']">修改</el-button>
  149. <el-button link type="danger" icon="Delete" @click="handleDelete(row)"
  150. v-hasPermi="['score:event:remove']">删除</el-button>
  151. </template>
  152. </el-table-column>
  153. </el-table>
  154. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
  155. v-model:limit="queryParams.pageSize" @pagination="getList" />
  156. <!-- 新增/修改对话框 -->
  157. <el-dialog :title="dialogTitle" v-model="dialogVisible" width="680px" append-to-body element-loading-text="加载中...">
  158. <el-form ref="formRef" :model="form" label-width="100px" v-loading="dialogLoading">
  159. <el-row :gutter="16">
  160. <el-col :span="12">
  161. <el-form-item label="配分层级" prop="org" :rules="rules.org">
  162. <el-select v-model="form.org" placeholder="请选择层级" style="width:100%" @change="handleFormLevelChange">
  163. <el-option v-for="dict in score_level" :key="dict.value" :label="dict.label" :value="dict.value" />
  164. </el-select>
  165. </el-form-item>
  166. </el-col>
  167. <el-col :span="12">
  168. <el-form-item label="维度" prop="dimensionId" :rules="rules.dimensionId">
  169. <el-select v-model="form.dimensionId" placeholder="请选择维度" style="width:100%" @change="onDimensionChange">
  170. <el-option v-for="d in dimensionOptions" :key="d.id" :label="d.name" :value="d.id" />
  171. </el-select>
  172. </el-form-item>
  173. </el-col>
  174. <el-col :span="12">
  175. <el-form-item label="二级指标" prop="level2Id">
  176. <el-select v-model="form.level2Id" placeholder="请选择" style="width:100%" clearable
  177. @change="onLevel2Change">
  178. <el-option v-for="n in level2Options" :key="n.id" :label="n.name" :value="n.id" />
  179. </el-select>
  180. </el-form-item>
  181. </el-col>
  182. <el-col :span="12">
  183. <el-form-item label="三级指标" prop="level3Id">
  184. <el-select v-model="form.level3Id" placeholder="请选择" style="width:100%" clearable
  185. @change="onLevel3Change">
  186. <el-option v-for="n in level3Options" :key="n.id" :label="n.name" :value="n.id" />
  187. </el-select>
  188. </el-form-item>
  189. </el-col>
  190. <el-col :span="12">
  191. <el-form-item label="四级指标">
  192. <el-select v-model="form.level4Id" placeholder="请选择" style="width:100%" clearable
  193. @change="onLevel4Change">
  194. <el-option v-for="n in level4Options" :key="n.id" :label="n.name" :value="n.id" />
  195. </el-select>
  196. </el-form-item>
  197. </el-col>
  198. <el-col :span="12">
  199. <el-form-item label="事件时间" prop="eventTime" :rules="rules.eventTime">
  200. <el-date-picker v-model="form.eventTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
  201. placeholder="请选择事件时间" style="width:100%" />
  202. </el-form-item>
  203. </el-col>
  204. <el-col :span="12">
  205. <el-form-item label="区域">
  206. <el-select v-model="form.regionalId" placeholder="请选择区域" clearable filterable style="width:100%"
  207. @change="handleFormRegionalChange">
  208. <el-option v-for="item in regionalOptions" :key="item.id" :label="item.name" :value="item.id" />
  209. </el-select>
  210. </el-form-item>
  211. </el-col>
  212. <el-col :span="12">
  213. <el-form-item label="工作点">
  214. <el-select v-model="form.channelId" placeholder="请选择工作点" clearable filterable style="width:100%">
  215. <el-option v-for="item in formChannelOptions" :key="item.id" :label="item.name" :value="item.id" />
  216. </el-select>
  217. </el-form-item>
  218. </el-col>
  219. <el-col :span="12">
  220. <el-form-item label="部门名称" prop="deptId" :rules="rules.deptId">
  221. <el-select v-model="form.deptId" placeholder="请选择部门" style="width:100%" clearable filterable
  222. @change="handleBrigadeChange">
  223. <el-option v-for="d in deptOptions" :key="d.deptId" :label="d.deptName" :value="d.deptId" />
  224. </el-select>
  225. </el-form-item>
  226. </el-col>
  227. <el-col :span="12">
  228. <el-form-item label="队室/班组" prop="teamId" :rules="rules.teamId">
  229. <el-select v-model="form.teamId" placeholder="请选择队室/班组" style="width:100%" clearable filterable
  230. @change="handleDepartmentChange">
  231. <el-option v-for="t in teamOptions" :key="t.id" :label="t.label" :value="t.id" />
  232. </el-select>
  233. </el-form-item>
  234. </el-col>
  235. <el-col :span="12">
  236. <el-form-item label="通道/小组" prop="groupId" :rules="rules.groupId">
  237. <el-select v-model="form.groupId" placeholder="请选择通道/小组" style="width:100%" clearable filterable
  238. @change="handleGroupChange">
  239. <el-option v-for="g in groupOptions" :key="g.id" :label="g.label" :value="g.id" />
  240. </el-select>
  241. </el-form-item>
  242. </el-col>
  243. <el-col :span="12">
  244. <el-form-item label="责任人" prop="personId" :rules="rules.personId">
  245. <el-select v-model="form.personId" placeholder="请选择责任人" style="width:100%" clearable filterable
  246. @change="handlePersonChange">
  247. <el-option v-for="p in personOptions" :key="p.userId" :label="p.nickName" :value="p.userId" />
  248. </el-select>
  249. </el-form-item>
  250. </el-col>
  251. <el-col :span="12">
  252. <el-form-item label="岗位">
  253. <el-tree-select v-model="form.postId" :data="postTreeData"
  254. :props="{ value: 'postId', label: 'postName', children: 'children' }" clearable filterable
  255. placeholder="请选择岗位" style="width:100%" check-strictly />
  256. </el-form-item>
  257. </el-col>
  258. <el-col :span="12">
  259. <el-form-item label="基础分值" prop="scoreValue">
  260. <el-input-number v-model="form.scoreValue" :precision="2" style="width:100%" placeholder="正数=加分 负数=扣分" />
  261. </el-form-item>
  262. </el-col>
  263. <el-col :span="12">
  264. <el-form-item label="叠加分值">
  265. <el-input-number v-model="form.cascadeScore" :precision="2" style="width:100%" placeholder="叠加后果产生的分值" />
  266. </el-form-item>
  267. </el-col>
  268. <el-col :span="24">
  269. <el-form-item label="事件描述" prop="eventDesc">
  270. <el-input v-model="form.eventDesc" type="textarea" :rows="3" placeholder="请输入事件描述" />
  271. </el-form-item>
  272. </el-col>
  273. <el-col :span="24">
  274. <el-form-item label="备注">
  275. <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
  276. </el-form-item>
  277. </el-col>
  278. </el-row>
  279. </el-form>
  280. <template #footer>
  281. <el-button @click="dialogVisible = false">取消</el-button>
  282. <el-button type="primary" @click="submitForm">确定</el-button>
  283. </template>
  284. </el-dialog>
  285. </div>
  286. </template>
  287. <script setup>
  288. import { ref, reactive, computed, watch, onMounted, getCurrentInstance } from 'vue'
  289. import { ElMessage, ElMessageBox } from 'element-plus'
  290. import { useRoute } from 'vue-router'
  291. import { listUser, getDeptOrgInfo, getUserOrgInfo } from '@/api/system/user'
  292. import { listPosition } from '@/api/system/position'
  293. import { listAllTree } from '@/api/system/post'
  294. import {
  295. listScoreEvent, addScoreEvent, updateScoreEvent, delScoreEvent,
  296. exportScoreEvent, importScoreEvent
  297. } from '@/api/score/index'
  298. import { allDimension, treeIndicator, getDimensionAll } from '@/api/score/index'
  299. import { listDept } from '@/api/system/dept'
  300. import { deptTreeSelect } from '@/api/system/user'
  301. import { parseTime } from '@/utils/ruoyi'
  302. defineOptions({ name: 'ScoreEvent' })
  303. const { proxy } = getCurrentInstance()
  304. const { score_level, score_type } = proxy.useDict('score_level', 'score_type')
  305. const loading = ref(false), list = ref([]), total = ref(0), showSearch = ref(true)
  306. const dateRange = ref([]), queryRef = ref(null), formRef = ref(null)
  307. const dialogVisible = ref(false), dialogTitle = ref(''), dialogLoading = ref(false)
  308. const single = ref(true), multiple = ref(true), ids = ref([])
  309. const dimensionOptions = ref([])
  310. const indicatorTree = ref([]) // 当前维度的指标树(扁平+嵌套)
  311. const level2Options = ref([])
  312. const level3Options = ref([])
  313. const level4Options = ref([])
  314. const deptOptions = ref([])
  315. const teamOptions = ref([])
  316. const groupOptions = ref([])
  317. const personOptions = ref([])
  318. const allPersonOptions = ref([]) // 缓存全部人员,避免重复请求
  319. const queryDeptOptions = ref([])
  320. const queryTeamOptions = ref([])
  321. const queryGroupOptions = ref([])
  322. const queryPersonOptions = ref([])
  323. const regionalOptions = ref([])
  324. const channelOptions = ref([])
  325. const postTreeData = ref([])
  326. 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 })
  327. 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 })
  328. const formChannelOptions = ref([])
  329. const rules = computed(() => {
  330. return {
  331. org: [{ required: true, message: '请选择配分层级', trigger: 'submit' }],
  332. dimensionId: [{ required: true, message: '请选择维度', trigger: 'submit' }],
  333. eventTime: [{ required: true, message: '请选择事件时间', trigger: 'submit' }],
  334. scoreValue: [{ required: true, message: '请输入分值', trigger: 'submit' }],
  335. deptId: form.org === '1' ? [{ required: true, message: '请选择部门', trigger: 'submit' }] : [],
  336. teamId: form.org === '2' ? [{ required: true, message: '请选择队室/班组', trigger: 'submit' }] : [],
  337. groupId: form.org === '3' ? [{ required: true, message: '请选择通道/小组', trigger: 'submit' }] : [],
  338. personId: form.org === '4' ? [{ required: true, message: '请选择责任人', trigger: 'submit' }] : [],
  339. }
  340. })
  341. // 搜索表单责任人选项:未选择部门/班组/小组时使用全量人员,否则使用过滤后的人员
  342. const searchPersonOptions = computed(() => {
  343. if (queryParams.deptId || queryParams.teamId || queryParams.groupId) {
  344. return queryPersonOptions.value
  345. }
  346. return personOptions.value
  347. })
  348. async function loadDimensions() {
  349. const r = await allDimension()
  350. dimensionOptions.value = r.data || []
  351. }
  352. async function handleLevelChange() {
  353. queryParams.dimensionId = null
  354. queryParams.deptId = null; queryParams.teamId = null; queryParams.groupId = null; queryParams.personId = null
  355. queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []
  356. const r = await getDimensionAll({ pageNum: 1, pageSize: 100, org: queryParams.org })
  357. dimensionOptions.value = r.data || []
  358. }
  359. async function handleFormLevelChange() {
  360. form.dimensionId = null
  361. form.level2Id = null; form.level2Name = ''; form.level3Id = null; form.level3Name = ''; form.level4Id = null; form.level4Name = ''
  362. form.deptId = null; form.teamId = null; form.groupId = null; form.personId = null
  363. indicatorTree.value = []
  364. level2Options.value = []; level3Options.value = []; level4Options.value = []
  365. teamOptions.value = []; groupOptions.value = []; personOptions.value = []
  366. const org = form.org
  367. const r = await getDimensionAll({ pageNum: 1, pageSize: 100, org })
  368. dimensionOptions.value = r.data || []
  369. // if (org === '2') {
  370. await loadAllTeams()
  371. // } else if (org === '3') {
  372. await loadAllGroups()
  373. // } else if (org === '4') {
  374. await loadAllPersons()
  375. // }
  376. }
  377. async function loadAllTeams() {
  378. const r = await listDept({ deptType: 'MANAGER' })
  379. teamOptions.value = (r.data || []).map(d => ({ id: d.deptId, label: d.deptName }))
  380. }
  381. async function loadDepts() {
  382. const r = await listDept()
  383. deptOptions.value = (r.data || []).filter(d => d.deptType === 'BRIGADE')
  384. }
  385. async function loadAllGroups() {
  386. const r = await listDept({ deptType: 'TEAMS' })
  387. groupOptions.value = (r.data || []).map(d => ({ id: d.deptId, label: d.deptName }))
  388. }
  389. async function loadAllPersons() {
  390. const r = await listUser({ pageSize: 9999 })
  391. const rows = r.rows || []
  392. allPersonOptions.value = rows
  393. personOptions.value = rows
  394. }
  395. async function loadRegions() {
  396. const r = await listPosition({ positionType: 'REGIONAL' })
  397. regionalOptions.value = r.data || []
  398. }
  399. async function handleRegionalChange(val) {
  400. queryParams.channelId = null
  401. channelOptions.value = []
  402. if (val) {
  403. const r = await listPosition({ parentId: val })
  404. channelOptions.value = r.data || []
  405. }
  406. }
  407. async function handleFormRegionalChange(val) {
  408. form.channelId = null
  409. formChannelOptions.value = []
  410. if (val) {
  411. const r = await listPosition({ parentId: val })
  412. formChannelOptions.value = r.data || []
  413. }
  414. }
  415. async function handleBrigadeChange(val) {
  416. form.teamId = null; form.groupId = null; form.personId = null
  417. teamOptions.value = []; groupOptions.value = []; personOptions.value = []
  418. if (val) {
  419. const r = await deptTreeSelect({ parentId: val })
  420. teamOptions.value = r.data || []
  421. }
  422. }
  423. async function handleDepartmentChange(val) {
  424. form.groupId = null; form.personId = null
  425. groupOptions.value = []; personOptions.value = []
  426. if (val) {
  427. const r = await deptTreeSelect({ parentId: val })
  428. groupOptions.value = r.data || []
  429. }
  430. }
  431. async function handleGroupChange(val) {
  432. form.personId = null
  433. personOptions.value = []
  434. if (val) {
  435. const r = await listUser({ deptId: val, pageSize: 9999 })
  436. personOptions.value = r.rows || []
  437. } else {
  438. personOptions.value = [...allPersonOptions.value]
  439. }
  440. }
  441. async function handlePersonChange(val) {
  442. dialogLoading.value = true
  443. form.deptId = null; form.teamId = null; form.groupId = null
  444. teamOptions.value = []; groupOptions.value = [];
  445. if (val) {
  446. const r = await getUserOrgInfo({ userId: val })
  447. if (r.data) {
  448. form.deptId = r.data.deptInfo.brigadeId
  449. form.teamId = r.data.deptInfo.departmentId
  450. form.groupId = r.data.deptInfo.teamId
  451. // 根据部门加载队室/班组选项
  452. if (form.deptId) {
  453. const r1 = await deptTreeSelect({ parentId: form.deptId })
  454. teamOptions.value = r1.data || []
  455. }
  456. // 根据队室/班组加载通道/小组选项
  457. if (form.teamId) {
  458. const r2 = await deptTreeSelect({ parentId: form.teamId })
  459. groupOptions.value = r2.data || []
  460. }
  461. }
  462. }
  463. dialogLoading.value = false
  464. }
  465. async function loadQueryDepts() {
  466. const r = await listDept()
  467. queryDeptOptions.value = (r.data || []).filter(d => d.deptType === 'BRIGADE')
  468. }
  469. async function handleQueryDeptChange(deptId) {
  470. queryParams.teamId = null; queryParams.groupId = null; queryParams.personId = null
  471. queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []
  472. if (deptId) {
  473. const r = await deptTreeSelect({ parentId: deptId })
  474. queryTeamOptions.value = r.data || []
  475. }
  476. }
  477. async function handleQueryTeamChange(val) {
  478. queryParams.groupId = null; queryParams.personId = null
  479. queryPersonOptions.value = []
  480. const team = queryTeamOptions.value.find(t => t.id === val)
  481. if (team) {
  482. const r = await deptTreeSelect({ parentId: team.id })
  483. queryGroupOptions.value = r.data || []
  484. } else {
  485. queryGroupOptions.value = []
  486. }
  487. }
  488. async function handleQueryGroupChange(val) {
  489. queryParams.personId = null
  490. const group = queryGroupOptions.value.find(g => g.id === val)
  491. if (group) {
  492. const r = await listUser({ deptId: group.id, pageSize: 9999 })
  493. queryPersonOptions.value = r.rows || []
  494. } else {
  495. queryPersonOptions.value = []
  496. }
  497. }
  498. function getList() {
  499. loading.value = true
  500. const p = { ...queryParams }
  501. if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
  502. listScoreEvent(p).then(r => { list.value = r.rows; total.value = r.total }).finally(() => loading.value = false)
  503. }
  504. function handleQuery() { queryParams.pageNum = 1; getList() }
  505. function resetQuery() {
  506. dateRange.value = []
  507. Object.assign(queryParams, {
  508. pageNum: 1,
  509. pageSize: 10,
  510. personId: null,
  511. personName: '',
  512. deptId: null,
  513. deptName: '',
  514. teamId: null,
  515. teamName: '',
  516. groupId: null,
  517. groupName: '',
  518. dimensionId: null,
  519. sourceType: '',
  520. org: '',
  521. scoreType: null,
  522. regionalId: null,
  523. channelId: null,
  524. postId: null
  525. })
  526. queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []; channelOptions.value = []
  527. queryRef.value?.resetFields()
  528. handleQuery()
  529. }
  530. function handleSelectionChange(sel) { ids.value = sel.map(s => s.id); single.value = sel.length !== 1; multiple.value = !sel.length }
  531. function resetForm() {
  532. 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 })
  533. level2Options.value = []; level3Options.value = []; level4Options.value = []
  534. teamOptions.value = []; groupOptions.value = []; personOptions.value = []; formChannelOptions.value = []
  535. }
  536. function handleAdd() { resetForm(); dialogTitle.value = '新增配分事项'; dialogVisible.value = true }
  537. async function handleUpdate(row) {
  538. dialogVisible.value = true
  539. resetForm()
  540. const record = row?.id ? row : list.value.find(r => r.id === ids.value[0])
  541. if (record) {
  542. Object.assign(form, record)
  543. const org = String(record.org)
  544. form.org = org
  545. // 加载区域和工作点
  546. if (record.regionalId) {
  547. await handleFormRegionalChange(record.regionalId)
  548. }
  549. // 加载队室/班组和通道/小组
  550. if (record.deptId) {
  551. // 先加载队室/班组
  552. const r = await deptTreeSelect({ parentId: record.deptId })
  553. teamOptions.value = r.data || []
  554. if (record.teamId) {
  555. // 再加载通道/小组
  556. const r2 = await deptTreeSelect({ parentId: record.teamId })
  557. groupOptions.value = r2.data || []
  558. // 最后加载责任人
  559. if (record.groupId) {
  560. const r3 = await listUser({ deptId: record.groupId })
  561. personOptions.value = r3.rows || []
  562. }
  563. }
  564. } else if (org === '2') {
  565. await loadAllTeams()
  566. } else if (org === '3') {
  567. await loadAllGroups()
  568. } else if (org === '4') {
  569. await loadAllPersons()
  570. }
  571. if (org === '1') {
  572. form.deptId = record.deptId || null
  573. } else if (org === '2') {
  574. form.teamId = record.teamId || null
  575. if (form.teamId) await handleDepartmentChange(form.teamId)
  576. } else if (org === '3') {
  577. form.groupId = record.groupId || null
  578. if (form.groupId) await handleGroupChange(form.groupId)
  579. } else if (org === '4') {
  580. form.personId = record.personId || null
  581. if (form.personId) await handlePersonChange(form.personId)
  582. }
  583. if (record.dimensionId) await onDimensionChange(record.dimensionId, true)
  584. if (record.level2Id) onLevel2Change(record.level2Id, true)
  585. if (record.level3Id) onLevel3Change(record.level3Id, true)
  586. if (record.level4Id) onLevel4Change(record.level4Id, true)
  587. }
  588. dialogTitle.value = '修改配分事项'
  589. }
  590. async function onDimensionChange(dimId, preserveSelection) {
  591. dialogLoading.value = true
  592. const dim = dimensionOptions.value.find(d => d.id === dimId)
  593. if (dim) form.dimensionName = dim.name
  594. const r = await treeIndicator({ dimensionId: dimId, org: form.org })
  595. indicatorTree.value = r.data || []
  596. level2Options.value = indicatorTree.value.filter(item => item.type !== '3').map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue }))
  597. if (!preserveSelection) { form.level2Id = null; form.level2Name = ''; form.level3Id = null; form.level3Name = ''; form.level4Id = null; form.level4Name = ''; form.scoreValue = 0 }
  598. level3Options.value = []; level4Options.value = []
  599. if (preserveSelection && form.level2Id) onLevel2Change(form.level2Id, true)
  600. dialogLoading.value = false
  601. }
  602. function onLevel2Change(val, preserveSelection) {
  603. const node = indicatorTree.value.find(n => n.id === val)
  604. if (node) {
  605. form.level2Name = node.name
  606. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  607. }
  608. level3Options.value = node ? (node.children || []).map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue })) : []
  609. if (!preserveSelection) { form.level3Id = null; form.level3Name = ''; form.level4Id = null; form.level4Name = ''; if (node && node.scoreValue === undefined) form.scoreValue = 0 }
  610. level4Options.value = []
  611. if (preserveSelection && form.level3Id) onLevel3Change(form.level3Id, true)
  612. }
  613. function onLevel3Change(val, preserveSelection) {
  614. const level2Node = indicatorTree.value.find(n => n.id === form.level2Id)
  615. const node = level2Node ? (level2Node.children || []).find(n => n.id === val) : null
  616. if (node) {
  617. form.level3Name = node.name
  618. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  619. }
  620. level4Options.value = node ? (node.children || []).map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue })) : []
  621. if (!preserveSelection) { form.level4Id = null; form.level4Name = ''; if (node && node.scoreValue === undefined) form.scoreValue = 0 }
  622. }
  623. function onLevel4Change(val) {
  624. const level2Node = indicatorTree.value.find(n => n.id === form.level2Id)
  625. const level3Node = level2Node ? (level2Node.children || []).find(n => n.id === form.level3Id) : null
  626. const node = level3Node ? (level3Node.children || []).find(n => n.id === val) : null
  627. if (node) {
  628. form.level4Name = node.name
  629. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  630. }
  631. }
  632. // 递归查找树形结构中的节点
  633. const findTreeNode = (nodes, targetId, idKey, nameKey) => {
  634. for (const node of nodes) {
  635. if (node[idKey] === targetId) {
  636. return node
  637. }
  638. if (node.children) {
  639. const found = findTreeNode(node.children, targetId, idKey, nameKey)
  640. if (found) return found
  641. }
  642. }
  643. return null
  644. }
  645. async function submitForm() {
  646. await formRef.value.validate()
  647. let copyForm = { ...form }
  648. if (copyForm.personId) {
  649. const p = personOptions.value.find(p => p.userId === copyForm.personId)
  650. if (p) copyForm.personName = p.nickName
  651. }
  652. if (copyForm.deptId) {
  653. const d = deptOptions.value.find(d => d.deptId === copyForm.deptId)
  654. if (d) copyForm.deptName = d.deptName
  655. }
  656. if (copyForm.teamId) {
  657. const t = teamOptions.value.find(t => t.id === copyForm.teamId)
  658. if (t) copyForm.teamName = t.label
  659. }
  660. if (copyForm.groupId) {
  661. const g = groupOptions.value.find(g => g.id === copyForm.groupId)
  662. if (g) copyForm.groupName = g.label
  663. }
  664. if (copyForm.regionalId) {
  665. const r = regionalOptions.value.find(r => r.id === copyForm.regionalId)
  666. if (r) copyForm.regionalName = r.name
  667. }
  668. if (copyForm.channelId) {
  669. const c = formChannelOptions.value.find(c => c.id === copyForm.channelId)
  670. if (c) copyForm.channelName = c.name || c.positionName
  671. }
  672. if (copyForm.postId) {
  673. const postNode = findTreeNode(postTreeData.value, copyForm.postId, 'postId', 'postName')
  674. if (postNode) copyForm.postName = postNode.postName
  675. }
  676. if (form.id) { await updateScoreEvent(copyForm); ElMessage.success('修改成功') }
  677. else { await addScoreEvent(copyForm); ElMessage.success('新增成功') }
  678. dialogVisible.value = false; getList()
  679. }
  680. function handleDelete(row) {
  681. const delIds = row?.id ? [row.id] : ids.value
  682. ElMessageBox.confirm('确认删除选中的配分事项?', '提示', { type: 'warning' }).then(() => {
  683. delScoreEvent(delIds.join(',')).then(() => { ElMessage.success('删除成功'); getList() })
  684. })
  685. }
  686. function handleExport() {
  687. const p = { ...queryParams }
  688. if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
  689. // exportScoreEvent(p)
  690. proxy.download('score/event/export', {
  691. ...p
  692. }, `配分事项_${new Date().getTime()}.xlsx`)
  693. }
  694. async function handleImportFile(file) {
  695. try {
  696. const r = await importScoreEvent(file.raw)
  697. ElMessage.success(r.msg || '导入成功')
  698. getList()
  699. } catch (e) {
  700. ElMessage.error('导入失败')
  701. }
  702. }
  703. const route = useRoute()
  704. let isInit = true
  705. // 监听路由参数,从员工画像跳转时回填查询条件
  706. watch(() => route.query, (query) => {
  707. if (query.id) {
  708. queryParams.personId = Number(query.id)
  709. }
  710. if (query.startDate && query.endDate) {
  711. dateRange.value = [query.startDate, query.endDate]
  712. }
  713. // 非初始化时(即路由参数后续变化),主动刷新数据
  714. if (!isInit && (query.id || query.startDate || query.endDate)) {
  715. handleQuery()
  716. }
  717. isInit = false
  718. }, { immediate: true })
  719. // 递归处理岗位树,禁用第一级
  720. const processPostTree = (nodes, isFirstLevel = true) => {
  721. return nodes.map(node => {
  722. const newNode = { ...node, disabled: isFirstLevel }
  723. if (node.children) {
  724. newNode.children = processPostTree(node.children, false)
  725. }
  726. return newNode
  727. })
  728. }
  729. // 监听通道/小组变化:无值时责任人显示全部人员
  730. watch(() => form.groupId, (newVal) => {
  731. if (!newVal) {
  732. personOptions.value = [...allPersonOptions.value]
  733. }
  734. })
  735. onMounted(() => {
  736. loadDimensions();
  737. loadDepts();
  738. loadQueryDepts();
  739. loadAllPersons();
  740. loadRegions();
  741. listAllTree().then(res => {
  742. postTreeData.value = processPostTree(res.data || [])
  743. });
  744. getList()
  745. })
  746. </script>
  747. <style scoped lang="less">
  748. /* Element Plus表格横向滚动配置 */
  749. .table-container :deep(.el-table) {
  750. width: 100%;
  751. min-width: 100%;
  752. }
  753. .table-container :deep(.el-table .el-table__header-wrapper),
  754. .table-container :deep(.el-table .el-table__body-wrapper) {
  755. overflow-x: auto !important;
  756. }
  757. </style>