index.vue 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  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>
  158. <el-form ref="formRef" :model="form" label-width="100px">
  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('')
  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 queryDeptOptions = ref([])
  319. const queryTeamOptions = ref([])
  320. const queryGroupOptions = ref([])
  321. const queryPersonOptions = ref([])
  322. const regionalOptions = ref([])
  323. const channelOptions = ref([])
  324. const postTreeData = ref([])
  325. 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 })
  326. 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 })
  327. const formChannelOptions = ref([])
  328. const rules = computed(() => {
  329. return {
  330. org: [{ required: true, message: '请选择配分层级', trigger: 'submit' }],
  331. dimensionId: [{ required: true, message: '请选择维度', trigger: 'submit' }],
  332. eventTime: [{ required: true, message: '请选择事件时间', trigger: 'submit' }],
  333. scoreValue: [{ required: true, message: '请输入分值', trigger: 'submit' }],
  334. deptId: form.org === '1' ? [{ required: true, message: '请选择部门', trigger: 'submit' }] : [],
  335. teamId: form.org === '2' ? [{ required: true, message: '请选择队室/班组', trigger: 'submit' }] : [],
  336. groupId: form.org === '3' ? [{ required: true, message: '请选择通道/小组', trigger: 'submit' }] : [],
  337. personId: form.org === '4' ? [{ required: true, message: '请选择责任人', trigger: 'submit' }] : [],
  338. }
  339. })
  340. // 搜索表单责任人选项:未选择部门/班组/小组时使用全量人员,否则使用过滤后的人员
  341. const searchPersonOptions = computed(() => {
  342. if (queryParams.deptId || queryParams.teamId || queryParams.groupId) {
  343. return queryPersonOptions.value
  344. }
  345. return personOptions.value
  346. })
  347. async function loadDimensions() {
  348. const r = await allDimension()
  349. dimensionOptions.value = r.data || []
  350. }
  351. async function handleLevelChange() {
  352. queryParams.dimensionId = null
  353. queryParams.deptId = null; queryParams.teamId = null; queryParams.groupId = null; queryParams.personId = null
  354. queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []
  355. const r = await getDimensionAll({ pageNum: 1, pageSize: 100, org: queryParams.org })
  356. dimensionOptions.value = r.data || []
  357. }
  358. async function handleFormLevelChange() {
  359. form.dimensionId = null
  360. form.level2Id = null; form.level2Name = ''; form.level3Id = null; form.level3Name = ''; form.level4Id = null; form.level4Name = ''
  361. form.deptId = null; form.teamId = null; form.groupId = null; form.personId = null
  362. indicatorTree.value = []
  363. level2Options.value = []; level3Options.value = []; level4Options.value = []
  364. teamOptions.value = []; groupOptions.value = []; personOptions.value = []
  365. const org = form.org
  366. const r = await getDimensionAll({ pageNum: 1, pageSize: 100, org })
  367. dimensionOptions.value = r.data || []
  368. // if (org === '2') {
  369. await loadAllTeams()
  370. // } else if (org === '3') {
  371. await loadAllGroups()
  372. // } else if (org === '4') {
  373. await loadAllPersons()
  374. // }
  375. }
  376. async function loadAllTeams() {
  377. const r = await listDept({ deptType: 'MANAGER' })
  378. teamOptions.value = (r.data || []).map(d => ({ id: d.deptId, label: d.deptName }))
  379. }
  380. async function loadDepts() {
  381. const r = await listDept()
  382. deptOptions.value = (r.data || []).filter(d => d.deptType === 'BRIGADE')
  383. }
  384. async function loadAllGroups() {
  385. const r = await listDept({ deptType: 'TEAMS' })
  386. groupOptions.value = (r.data || []).map(d => ({ id: d.deptId, label: d.deptName }))
  387. }
  388. async function loadAllPersons() {
  389. const r = await listUser({ pageSize: 9999 })
  390. personOptions.value = r.rows || []
  391. }
  392. async function loadRegions() {
  393. const r = await listPosition({ positionType: 'REGIONAL' })
  394. regionalOptions.value = r.data || []
  395. }
  396. async function handleRegionalChange(val) {
  397. queryParams.channelId = null
  398. channelOptions.value = []
  399. if (val) {
  400. const r = await listPosition({ parentId: val })
  401. channelOptions.value = r.data || []
  402. }
  403. }
  404. async function handleFormRegionalChange(val) {
  405. form.channelId = null
  406. formChannelOptions.value = []
  407. if (val) {
  408. const r = await listPosition({ parentId: val })
  409. formChannelOptions.value = r.data || []
  410. }
  411. }
  412. function handleBrigadeChange() {
  413. }
  414. async function handleDepartmentChange(val) {
  415. form.deptId = null
  416. if (val) {
  417. const r = await getDeptOrgInfo({ deptId: val })
  418. if (r.data) {
  419. form.deptId = r.data.brigadeId
  420. }
  421. }
  422. }
  423. async function handleGroupChange(val) {
  424. form.deptId = null; form.teamId = null
  425. if (val) {
  426. const r = await getDeptOrgInfo({ deptId: val })
  427. if (r.data) {
  428. form.deptId = r.data.brigadeId
  429. form.teamId = r.data.departmentId
  430. }
  431. }
  432. }
  433. async function handlePersonChange(val) {
  434. form.deptId = null; form.teamId = null; form.groupId = null
  435. if (val) {
  436. const r = await getUserOrgInfo({ userId: val })
  437. if (r.data) {
  438. form.deptId = r.data.deptInfo.brigadeId
  439. form.teamId = r.data.deptInfo.departmentId
  440. form.groupId = r.data.deptInfo.teamId
  441. }
  442. }
  443. }
  444. async function loadQueryDepts() {
  445. const r = await listDept()
  446. queryDeptOptions.value = (r.data || []).filter(d => d.deptType === 'BRIGADE')
  447. }
  448. async function handleQueryDeptChange(deptId) {
  449. queryParams.teamId = null; queryParams.groupId = null; queryParams.personId = null
  450. queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []
  451. if (deptId) {
  452. const r = await deptTreeSelect({ parentId: deptId })
  453. queryTeamOptions.value = r.data || []
  454. }
  455. }
  456. async function handleQueryTeamChange(val) {
  457. queryParams.groupId = null; queryParams.personId = null
  458. queryPersonOptions.value = []
  459. const team = queryTeamOptions.value.find(t => t.id === val)
  460. if (team) {
  461. const r = await deptTreeSelect({ parentId: team.id })
  462. queryGroupOptions.value = r.data || []
  463. } else {
  464. queryGroupOptions.value = []
  465. }
  466. }
  467. async function handleQueryGroupChange(val) {
  468. queryParams.personId = null
  469. const group = queryGroupOptions.value.find(g => g.id === val)
  470. if (group) {
  471. const r = await listUser({ deptId: group.id, pageSize: 9999 })
  472. queryPersonOptions.value = r.rows || []
  473. } else {
  474. queryPersonOptions.value = []
  475. }
  476. }
  477. function getList() {
  478. loading.value = true
  479. const p = { ...queryParams }
  480. if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
  481. listScoreEvent(p).then(r => { list.value = r.rows; total.value = r.total }).finally(() => loading.value = false)
  482. }
  483. function handleQuery() { queryParams.pageNum = 1; getList() }
  484. function resetQuery() {
  485. dateRange.value = []
  486. Object.assign(queryParams, {
  487. pageNum: 1,
  488. pageSize: 10,
  489. personId: null,
  490. personName: '',
  491. deptId: null,
  492. deptName: '',
  493. teamId: null,
  494. teamName: '',
  495. groupId: null,
  496. groupName: '',
  497. dimensionId: null,
  498. sourceType: '',
  499. org: '',
  500. scoreType: null,
  501. regionalId: null,
  502. channelId: null,
  503. postId: null
  504. })
  505. queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []; channelOptions.value = []
  506. queryRef.value?.resetFields()
  507. handleQuery()
  508. }
  509. function handleSelectionChange(sel) { ids.value = sel.map(s => s.id); single.value = sel.length !== 1; multiple.value = !sel.length }
  510. function resetForm() {
  511. 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 })
  512. level2Options.value = []; level3Options.value = []; level4Options.value = []
  513. teamOptions.value = []; groupOptions.value = []; personOptions.value = []; formChannelOptions.value = []
  514. }
  515. function handleAdd() { resetForm(); dialogTitle.value = '新增配分事项'; dialogVisible.value = true }
  516. async function handleUpdate(row) {
  517. resetForm()
  518. const record = row?.id ? row : list.value.find(r => r.id === ids.value[0])
  519. if (record) {
  520. Object.assign(form, record)
  521. const org = String(record.org)
  522. form.org = org
  523. // 加载区域和工作点
  524. if (record.regionalId) {
  525. await handleFormRegionalChange(record.regionalId)
  526. }
  527. // 加载队室/班组和通道/小组
  528. if (record.deptId) {
  529. // 先加载队室/班组
  530. const r = await deptTreeSelect({ parentId: record.deptId })
  531. teamOptions.value = r.data || []
  532. if (record.teamId) {
  533. // 再加载通道/小组
  534. const r2 = await deptTreeSelect({ parentId: record.teamId })
  535. groupOptions.value = r2.data || []
  536. // 最后加载责任人
  537. if (record.groupId) {
  538. const r3 = await listUser({ deptId: record.groupId })
  539. personOptions.value = r3.rows || []
  540. }
  541. }
  542. } else if (org === '2') {
  543. await loadAllTeams()
  544. } else if (org === '3') {
  545. await loadAllGroups()
  546. } else if (org === '4') {
  547. await loadAllPersons()
  548. }
  549. if (org === '1') {
  550. form.deptId = record.deptId || null
  551. } else if (org === '2') {
  552. form.teamId = record.teamId || null
  553. if (form.teamId) await handleDepartmentChange(form.teamId)
  554. } else if (org === '3') {
  555. form.groupId = record.groupId || null
  556. if (form.groupId) await handleGroupChange(form.groupId)
  557. } else if (org === '4') {
  558. form.personId = record.personId || null
  559. if (form.personId) await handlePersonChange(form.personId)
  560. }
  561. if (record.dimensionId) await onDimensionChange(record.dimensionId, true)
  562. if (record.level2Id) onLevel2Change(record.level2Id, true)
  563. if (record.level3Id) onLevel3Change(record.level3Id, true)
  564. if (record.level4Id) onLevel4Change(record.level4Id, true)
  565. }
  566. dialogTitle.value = '修改配分事项'; dialogVisible.value = true
  567. }
  568. async function onDimensionChange(dimId, preserveSelection) {
  569. const dim = dimensionOptions.value.find(d => d.id === dimId)
  570. if (dim) form.dimensionName = dim.name
  571. const r = await treeIndicator({ dimensionId: dimId, org: form.org })
  572. indicatorTree.value = r.data || []
  573. level2Options.value = indicatorTree.value.filter(item => item.type !== '3').map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue }))
  574. if (!preserveSelection) { form.level2Id = null; form.level2Name = ''; form.level3Id = null; form.level3Name = ''; form.level4Id = null; form.level4Name = ''; form.scoreValue = 0 }
  575. level3Options.value = []; level4Options.value = []
  576. if (preserveSelection && form.level2Id) onLevel2Change(form.level2Id, true)
  577. }
  578. function onLevel2Change(val, preserveSelection) {
  579. const node = indicatorTree.value.find(n => n.id === val)
  580. if (node) {
  581. form.level2Name = node.name
  582. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  583. }
  584. level3Options.value = node ? (node.children || []).map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue })) : []
  585. if (!preserveSelection) { form.level3Id = null; form.level3Name = ''; form.level4Id = null; form.level4Name = ''; if (node && node.scoreValue === undefined) form.scoreValue = 0 }
  586. level4Options.value = []
  587. if (preserveSelection && form.level3Id) onLevel3Change(form.level3Id, true)
  588. }
  589. function onLevel3Change(val, preserveSelection) {
  590. const level2Node = indicatorTree.value.find(n => n.id === form.level2Id)
  591. const node = level2Node ? (level2Node.children || []).find(n => n.id === val) : null
  592. if (node) {
  593. form.level3Name = node.name
  594. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  595. }
  596. level4Options.value = node ? (node.children || []).map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue })) : []
  597. if (!preserveSelection) { form.level4Id = null; form.level4Name = ''; if (node && node.scoreValue === undefined) form.scoreValue = 0 }
  598. }
  599. function onLevel4Change(val) {
  600. const level2Node = indicatorTree.value.find(n => n.id === form.level2Id)
  601. const level3Node = level2Node ? (level2Node.children || []).find(n => n.id === form.level3Id) : null
  602. const node = level3Node ? (level3Node.children || []).find(n => n.id === val) : null
  603. if (node) {
  604. form.level4Name = node.name
  605. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  606. }
  607. }
  608. // 递归查找树形结构中的节点
  609. const findTreeNode = (nodes, targetId, idKey, nameKey) => {
  610. for (const node of nodes) {
  611. if (node[idKey] === targetId) {
  612. return node
  613. }
  614. if (node.children) {
  615. const found = findTreeNode(node.children, targetId, idKey, nameKey)
  616. if (found) return found
  617. }
  618. }
  619. return null
  620. }
  621. async function submitForm() {
  622. await formRef.value.validate()
  623. let copyForm = { ...form }
  624. if (copyForm.personId) {
  625. const p = personOptions.value.find(p => p.userId === copyForm.personId)
  626. if (p) copyForm.personName = p.nickName
  627. }
  628. if (copyForm.deptId) {
  629. const d = deptOptions.value.find(d => d.deptId === copyForm.deptId)
  630. if (d) copyForm.deptName = d.deptName
  631. }
  632. if (copyForm.teamId) {
  633. const t = teamOptions.value.find(t => t.id === copyForm.teamId)
  634. if (t) copyForm.teamName = t.label
  635. }
  636. if (copyForm.groupId) {
  637. const g = groupOptions.value.find(g => g.id === copyForm.groupId)
  638. if (g) copyForm.groupName = g.label
  639. }
  640. if (copyForm.regionalId) {
  641. const r = regionalOptions.value.find(r => r.id === copyForm.regionalId)
  642. if (r) copyForm.regionalName = r.name
  643. }
  644. if (copyForm.channelId) {
  645. const c = formChannelOptions.value.find(c => c.id === copyForm.channelId)
  646. if (c) copyForm.channelName = c.name || c.positionName
  647. }
  648. if (copyForm.postId) {
  649. const postNode = findTreeNode(postTreeData.value, copyForm.postId, 'postId', 'postName')
  650. if (postNode) copyForm.postName = postNode.postName
  651. }
  652. if (form.id) { await updateScoreEvent(copyForm); ElMessage.success('修改成功') }
  653. else { await addScoreEvent(copyForm); ElMessage.success('新增成功') }
  654. dialogVisible.value = false; getList()
  655. }
  656. function handleDelete(row) {
  657. const delIds = row?.id ? [row.id] : ids.value
  658. ElMessageBox.confirm('确认删除选中的配分事项?', '提示', { type: 'warning' }).then(() => {
  659. delScoreEvent(delIds.join(',')).then(() => { ElMessage.success('删除成功'); getList() })
  660. })
  661. }
  662. function handleExport() {
  663. const p = { ...queryParams }
  664. if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
  665. // exportScoreEvent(p)
  666. proxy.download('score/event/export', {
  667. ...p
  668. }, `配分事项_${new Date().getTime()}.xlsx`)
  669. }
  670. async function handleImportFile(file) {
  671. try {
  672. const r = await importScoreEvent(file.raw)
  673. ElMessage.success(r.msg || '导入成功')
  674. getList()
  675. } catch (e) {
  676. ElMessage.error('导入失败')
  677. }
  678. }
  679. const route = useRoute()
  680. let isInit = true
  681. // 监听路由参数,从员工画像跳转时回填查询条件
  682. watch(() => route.query, (query) => {
  683. if (query.id) {
  684. queryParams.personId = Number(query.id)
  685. }
  686. if (query.startDate && query.endDate) {
  687. dateRange.value = [query.startDate, query.endDate]
  688. }
  689. // 非初始化时(即路由参数后续变化),主动刷新数据
  690. if (!isInit && (query.id || query.startDate || query.endDate)) {
  691. handleQuery()
  692. }
  693. isInit = false
  694. }, { immediate: true })
  695. // 递归处理岗位树,禁用第一级
  696. const processPostTree = (nodes, isFirstLevel = true) => {
  697. return nodes.map(node => {
  698. const newNode = { ...node, disabled: isFirstLevel }
  699. if (node.children) {
  700. newNode.children = processPostTree(node.children, false)
  701. }
  702. return newNode
  703. })
  704. }
  705. onMounted(() => {
  706. loadDimensions();
  707. loadDepts();
  708. loadQueryDepts();
  709. loadAllPersons();
  710. loadRegions();
  711. listAllTree().then(res => {
  712. postTreeData.value = processPostTree(res.data || [])
  713. });
  714. getList()
  715. })
  716. </script>
  717. <style scoped lang="less">
  718. /* Element Plus表格横向滚动配置 */
  719. .table-container :deep(.el-table) {
  720. width: 100%;
  721. min-width: 100%;
  722. }
  723. .table-container :deep(.el-table .el-table__header-wrapper),
  724. .table-container :deep(.el-table .el-table__body-wrapper) {
  725. overflow-x: auto !important;
  726. }
  727. </style>