index.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  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
  55. style="width:150px" @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
  61. style="width:150px">
  62. <el-option v-for="item in channelOptions" :key="item.id" :label="item.positionName" :value="item.id" />
  63. </el-select>
  64. </el-form-item>
  65. <el-form-item label="岗位" prop="postId">
  66. <el-tree-select v-model="queryParams.postId" :data="postTreeData"
  67. :props="{ value: 'postId', label: 'postName', children: 'children' }" clearable filterable
  68. placeholder="请选择岗位" check-strictly />
  69. </el-form-item>
  70. <el-form-item>
  71. <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
  72. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  73. </el-form-item>
  74. </el-form>
  75. <el-row :gutter="10" class="mb8">
  76. <el-col :span="1.5">
  77. <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['score:event:add']">新增</el-button>
  78. </el-col>
  79. <el-col :span="1.5">
  80. <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate"
  81. v-hasPermi="['score:event:edit']">修改</el-button>
  82. </el-col>
  83. <el-col :span="1.5">
  84. <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete"
  85. v-hasPermi="['score:event:remove']">删除</el-button>
  86. </el-col>
  87. <el-col :span="1.5">
  88. <el-button type="warning" plain icon="Download" @click="handleExport"
  89. v-hasPermi="['score:event:export']">导出</el-button>
  90. </el-col>
  91. <!-- <el-col :span="1.5">
  92. <el-upload :show-file-list="false" accept=".xlsx,.xls" :auto-upload="false" :on-change="handleImportFile">
  93. <el-button type="info" plain icon="Upload" v-hasPermi="['score:event:import']">导入</el-button>
  94. </el-upload>
  95. </el-col> -->
  96. <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
  97. </el-row>
  98. <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
  99. <el-table-column type="selection" width="55" align="center" />
  100. <el-table-column label="配分层级" align="center" prop="org" width="110">
  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">
  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="110" />
  107. <el-table-column label="二级指标" align="center" prop="level2Name" show-overflow-tooltip />
  108. <el-table-column label="三级指标" align="center" prop="level3Name" show-overflow-tooltip />
  109. <el-table-column label="责任人" align="center" prop="personName" width="80" />
  110. <el-table-column label="部门" align="center" prop="deptName" />
  111. <el-table-column label="班组" align="center" prop="teamName" />
  112. <el-table-column label="区域" align="center" prop="regionalName" />
  113. <el-table-column label="工作点" align="center" prop="channelName" />
  114. <el-table-column label="岗位" align="center" prop="postName" />
  115. <el-table-column label="基础分值" align="center" prop="scoreValue" width="90">
  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">
  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">
  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">
  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="事件描述" width="460" align="center" prop="eventDesc" show-overflow-tooltip />
  145. <el-table-column label="操作" align="center" width="180">
  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
  207. style="width:100%" @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
  215. style="width:100%">
  216. <el-option v-for="item in formChannelOptions" :key="item.id" :label="item.name" :value="item.id" />
  217. </el-select>
  218. </el-form-item>
  219. </el-col>
  220. <el-col :span="12">
  221. <el-form-item label="部门名称" prop="deptId" :rules="rules.deptId">
  222. <el-select v-model="form.deptId" placeholder="请选择部门" style="width:100%" clearable filterable
  223. @change="handleBrigadeChange">
  224. <el-option v-for="d in deptOptions" :key="d.deptId" :label="d.deptName" :value="d.deptId" />
  225. </el-select>
  226. </el-form-item>
  227. </el-col>
  228. <el-col :span="12">
  229. <el-form-item label="队室/班组" prop="teamId" :rules="rules.teamId">
  230. <el-select v-model="form.teamId" placeholder="请选择队室/班组" style="width:100%" clearable filterable
  231. @change="handleDepartmentChange">
  232. <el-option v-for="t in teamOptions" :key="t.id" :label="t.label" :value="t.id" />
  233. </el-select>
  234. </el-form-item>
  235. </el-col>
  236. <el-col :span="12">
  237. <el-form-item label="通道/小组" prop="groupId" :rules="rules.groupId">
  238. <el-select v-model="form.groupId" placeholder="请选择通道/小组" style="width:100%" clearable filterable
  239. @change="handleGroupChange">
  240. <el-option v-for="g in groupOptions" :key="g.id" :label="g.label" :value="g.id" />
  241. </el-select>
  242. </el-form-item>
  243. </el-col>
  244. <el-col :span="12">
  245. <el-form-item label="责任人" prop="personId" :rules="rules.personId">
  246. <el-select v-model="form.personId" placeholder="请选择责任人" style="width:100%" clearable filterable
  247. @change="handlePersonChange">
  248. <el-option v-for="p in personOptions" :key="p.userId" :label="p.nickName" :value="p.userId" />
  249. </el-select>
  250. </el-form-item>
  251. </el-col>
  252. <el-col :span="12">
  253. <el-form-item label="岗位">
  254. <el-tree-select v-model="form.postId" :data="postTreeData"
  255. :props="{ value: 'postId', label: 'postName', children: 'children' }" clearable filterable
  256. placeholder="请选择岗位" style="width:100%" check-strictly />
  257. </el-form-item>
  258. </el-col>
  259. <el-col :span="12">
  260. <el-form-item label="基础分值" prop="scoreValue">
  261. <el-input-number v-model="form.scoreValue" :precision="2" style="width:100%" placeholder="正数=加分 负数=扣分" />
  262. </el-form-item>
  263. </el-col>
  264. <el-col :span="12">
  265. <el-form-item label="叠加分值">
  266. <el-input-number v-model="form.cascadeScore" :precision="2" style="width:100%" placeholder="叠加后果产生的分值" />
  267. </el-form-item>
  268. </el-col>
  269. <el-col :span="24">
  270. <el-form-item label="事件描述" prop="eventDesc">
  271. <el-input v-model="form.eventDesc" type="textarea" :rows="3" placeholder="请输入事件描述" />
  272. </el-form-item>
  273. </el-col>
  274. <el-col :span="24">
  275. <el-form-item label="备注">
  276. <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
  277. </el-form-item>
  278. </el-col>
  279. </el-row>
  280. </el-form>
  281. <template #footer>
  282. <el-button @click="dialogVisible = false">取消</el-button>
  283. <el-button type="primary" @click="submitForm">确定</el-button>
  284. </template>
  285. </el-dialog>
  286. </div>
  287. </template>
  288. <script setup>
  289. import { ref, reactive, computed, watch, onMounted, getCurrentInstance } from 'vue'
  290. import { ElMessage, ElMessageBox } from 'element-plus'
  291. import { useRoute } from 'vue-router'
  292. import { listUser, getDeptOrgInfo, getUserOrgInfo } from '@/api/system/user'
  293. import { listPosition } from '@/api/system/position'
  294. import { listAllTree } from '@/api/system/post'
  295. import {
  296. listScoreEvent, addScoreEvent, updateScoreEvent, delScoreEvent,
  297. exportScoreEvent, importScoreEvent
  298. } from '@/api/score/index'
  299. import { allDimension, treeIndicator, getDimensionAll } from '@/api/score/index'
  300. import { listDept } from '@/api/system/dept'
  301. import { deptTreeSelect } from '@/api/system/user'
  302. import { parseTime } from '@/utils/ruoyi'
  303. defineOptions({ name: 'ScoreEvent' })
  304. const { proxy } = getCurrentInstance()
  305. const { score_level, score_type } = proxy.useDict('score_level', 'score_type')
  306. const loading = ref(false), list = ref([]), total = ref(0), showSearch = ref(true)
  307. const dateRange = ref([]), queryRef = ref(null), formRef = ref(null)
  308. const dialogVisible = ref(false), dialogTitle = ref('')
  309. const single = ref(true), multiple = ref(true), ids = ref([])
  310. const dimensionOptions = ref([])
  311. const indicatorTree = ref([]) // 当前维度的指标树(扁平+嵌套)
  312. const level2Options = ref([])
  313. const level3Options = ref([])
  314. const level4Options = ref([])
  315. const deptOptions = ref([])
  316. const teamOptions = ref([])
  317. const groupOptions = ref([])
  318. const personOptions = 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. // if (org === '2') {
  368. await loadAllTeams()
  369. // } else if (org === '3') {
  370. await loadAllGroups()
  371. // } else if (org === '4') {
  372. await loadAllPersons()
  373. // }
  374. const r = await getDimensionAll({ pageNum: 1, pageSize: 100, org })
  375. dimensionOptions.value = r.data || []
  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. personOptions.value = r.rows || []
  392. }
  393. async function loadRegions() {
  394. const r = await listPosition({ positionType: 'REGIONAL' })
  395. regionalOptions.value = r.data || []
  396. }
  397. async function handleRegionalChange(val) {
  398. queryParams.channelId = null
  399. channelOptions.value = []
  400. if (val) {
  401. const r = await listPosition({ parentId: val })
  402. channelOptions.value = r.data || []
  403. }
  404. }
  405. async function handleFormRegionalChange(val) {
  406. form.channelId = null
  407. formChannelOptions.value = []
  408. if (val) {
  409. const r = await listPosition({ parentId: val })
  410. formChannelOptions.value = r.data || []
  411. }
  412. }
  413. function handleBrigadeChange() {
  414. }
  415. async function handleDepartmentChange(val) {
  416. form.deptId = null
  417. if (val) {
  418. const r = await getDeptOrgInfo({ deptId: val })
  419. if (r.data) {
  420. form.deptId = r.data.brigadeId
  421. }
  422. }
  423. }
  424. async function handleGroupChange(val) {
  425. form.deptId = null; form.teamId = null
  426. if (val) {
  427. const r = await getDeptOrgInfo({ deptId: val })
  428. if (r.data) {
  429. form.deptId = r.data.brigadeId
  430. form.teamId = r.data.departmentId
  431. }
  432. }
  433. }
  434. async function handlePersonChange(val) {
  435. form.deptId = null; form.teamId = null; form.groupId = null
  436. if (val) {
  437. const r = await getUserOrgInfo({ userId: val })
  438. if (r.data) {
  439. form.deptId = r.data.deptInfo.brigadeId
  440. form.teamId = r.data.deptInfo.departmentId
  441. form.groupId = r.data.deptInfo.teamId
  442. }
  443. }
  444. }
  445. async function loadQueryDepts() {
  446. const r = await listDept()
  447. queryDeptOptions.value = (r.data || []).filter(d => d.deptType === 'BRIGADE')
  448. }
  449. async function handleQueryDeptChange(deptId) {
  450. queryParams.teamId = null; queryParams.groupId = null; queryParams.personId = null
  451. queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []
  452. if (deptId) {
  453. const r = await deptTreeSelect({ parentId: deptId })
  454. queryTeamOptions.value = r.data || []
  455. }
  456. }
  457. async function handleQueryTeamChange(val) {
  458. queryParams.groupId = null; queryParams.personId = null
  459. queryPersonOptions.value = []
  460. const team = queryTeamOptions.value.find(t => t.id === val)
  461. if (team) {
  462. const r = await deptTreeSelect({ parentId: team.id })
  463. queryGroupOptions.value = r.data || []
  464. } else {
  465. queryGroupOptions.value = []
  466. }
  467. }
  468. async function handleQueryGroupChange(val) {
  469. queryParams.personId = null
  470. const group = queryGroupOptions.value.find(g => g.id === val)
  471. if (group) {
  472. const r = await listUser({ deptId: group.id })
  473. queryPersonOptions.value = r.rows || []
  474. } else {
  475. queryPersonOptions.value = []
  476. }
  477. }
  478. function getList() {
  479. loading.value = true
  480. const p = { ...queryParams }
  481. if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
  482. listScoreEvent(p).then(r => { list.value = r.rows; total.value = r.total }).finally(() => loading.value = false)
  483. }
  484. function handleQuery() { queryParams.pageNum = 1; getList() }
  485. function resetQuery() {
  486. dateRange.value = []
  487. Object.assign(queryParams, {
  488. pageNum: 1,
  489. pageSize: 10,
  490. personId: null,
  491. personName: '',
  492. deptId: null,
  493. deptName: '',
  494. teamId: null,
  495. teamName: '',
  496. groupId: null,
  497. groupName: '',
  498. dimensionId: null,
  499. sourceType: '',
  500. org: '',
  501. scoreType: null,
  502. regionalId: null,
  503. channelId: null,
  504. postId: null
  505. })
  506. queryTeamOptions.value = []; queryGroupOptions.value = []; queryPersonOptions.value = []; channelOptions.value = []
  507. queryRef.value?.resetFields()
  508. handleQuery()
  509. }
  510. function handleSelectionChange(sel) { ids.value = sel.map(s => s.id); single.value = sel.length !== 1; multiple.value = !sel.length }
  511. function resetForm() {
  512. 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 })
  513. level2Options.value = []; level3Options.value = []; level4Options.value = []
  514. teamOptions.value = []; groupOptions.value = []; personOptions.value = []; formChannelOptions.value = []
  515. }
  516. function handleAdd() { resetForm(); dialogTitle.value = '新增配分事项'; dialogVisible.value = true }
  517. async function handleUpdate(row) {
  518. resetForm()
  519. const record = row?.id ? row : list.value.find(r => r.id === ids.value[0])
  520. if (record) {
  521. Object.assign(form, record)
  522. const org = String(record.org)
  523. form.org = org
  524. // 加载区域和工作点
  525. if (record.regionalId) {
  526. await handleFormRegionalChange(record.regionalId)
  527. }
  528. // 加载队室/班组和通道/小组
  529. if (record.deptId) {
  530. // 先加载队室/班组
  531. const r = await deptTreeSelect({ parentId: record.deptId })
  532. teamOptions.value = r.data || []
  533. if (record.teamId) {
  534. // 再加载通道/小组
  535. const r2 = await deptTreeSelect({ parentId: record.teamId })
  536. groupOptions.value = r2.data || []
  537. // 最后加载责任人
  538. if (record.groupId) {
  539. const r3 = await listUser({ deptId: record.groupId })
  540. personOptions.value = r3.rows || []
  541. }
  542. }
  543. } else if (org === '2') {
  544. await loadAllTeams()
  545. } else if (org === '3') {
  546. await loadAllGroups()
  547. } else if (org === '4') {
  548. await loadAllPersons()
  549. }
  550. if (org === '1') {
  551. form.deptId = record.deptId || null
  552. } else if (org === '2') {
  553. form.teamId = record.teamId || null
  554. if (form.teamId) await handleDepartmentChange(form.teamId)
  555. } else if (org === '3') {
  556. form.groupId = record.groupId || null
  557. if (form.groupId) await handleGroupChange(form.groupId)
  558. } else if (org === '4') {
  559. form.personId = record.personId || null
  560. if (form.personId) await handlePersonChange(form.personId)
  561. }
  562. if (record.dimensionId) await onDimensionChange(record.dimensionId, true)
  563. if (record.level2Id) onLevel2Change(record.level2Id, true)
  564. if (record.level3Id) onLevel3Change(record.level3Id, true)
  565. if (record.level4Id) onLevel4Change(record.level4Id, true)
  566. }
  567. dialogTitle.value = '修改配分事项'; dialogVisible.value = true
  568. }
  569. async function onDimensionChange(dimId, preserveSelection) {
  570. const dim = dimensionOptions.value.find(d => d.id === dimId)
  571. if (dim) form.dimensionName = dim.name
  572. const r = await treeIndicator({ dimensionId: dimId, org: form.org })
  573. indicatorTree.value = r.data || []
  574. level2Options.value = indicatorTree.value.filter(item => item.type !== '3').map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue }))
  575. if (!preserveSelection) { form.level2Id = null; form.level2Name = ''; form.level3Id = null; form.level3Name = ''; form.level4Id = null; form.level4Name = ''; form.scoreValue = 0 }
  576. level3Options.value = []; level4Options.value = []
  577. if (preserveSelection && form.level2Id) onLevel2Change(form.level2Id, true)
  578. }
  579. function onLevel2Change(val, preserveSelection) {
  580. const node = indicatorTree.value.find(n => n.id === val)
  581. if (node) {
  582. form.level2Name = node.name
  583. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  584. }
  585. level3Options.value = node ? (node.children || []).map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue })) : []
  586. if (!preserveSelection) { form.level3Id = null; form.level3Name = ''; form.level4Id = null; form.level4Name = ''; if (node && node.scoreValue === undefined) form.scoreValue = 0 }
  587. level4Options.value = []
  588. if (preserveSelection && form.level3Id) onLevel3Change(form.level3Id, true)
  589. }
  590. function onLevel3Change(val, preserveSelection) {
  591. const level2Node = indicatorTree.value.find(n => n.id === form.level2Id)
  592. const node = level2Node ? (level2Node.children || []).find(n => n.id === val) : null
  593. if (node) {
  594. form.level3Name = node.name
  595. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  596. }
  597. level4Options.value = node ? (node.children || []).map(n => ({ name: n.name, id: n.id, scoreValue: n.scoreValue })) : []
  598. if (!preserveSelection) { form.level4Id = null; form.level4Name = ''; if (node && node.scoreValue === undefined) form.scoreValue = 0 }
  599. }
  600. function onLevel4Change(val) {
  601. const level2Node = indicatorTree.value.find(n => n.id === form.level2Id)
  602. const level3Node = level2Node ? (level2Node.children || []).find(n => n.id === form.level3Id) : null
  603. const node = level3Node ? (level3Node.children || []).find(n => n.id === val) : null
  604. if (node) {
  605. form.level4Name = node.name
  606. if (node.scoreValue !== undefined) form.scoreValue = node.scoreValue
  607. }
  608. }
  609. // 递归查找树形结构中的节点
  610. const findTreeNode = (nodes, targetId, idKey, nameKey) => {
  611. for (const node of nodes) {
  612. if (node[idKey] === targetId) {
  613. return node
  614. }
  615. if (node.children) {
  616. const found = findTreeNode(node.children, targetId, idKey, nameKey)
  617. if (found) return found
  618. }
  619. }
  620. return null
  621. }
  622. async function submitForm() {
  623. await formRef.value.validate()
  624. let copyForm = { ...form }
  625. if (copyForm.personId) {
  626. const p = personOptions.value.find(p => p.userId === copyForm.personId)
  627. if (p) copyForm.personName = p.nickName
  628. }
  629. if (copyForm.deptId) {
  630. const d = deptOptions.value.find(d => d.deptId === copyForm.deptId)
  631. if (d) copyForm.deptName = d.deptName
  632. }
  633. if (copyForm.teamId) {
  634. const t = teamOptions.value.find(t => t.id === copyForm.teamId)
  635. if (t) copyForm.teamName = t.label
  636. }
  637. if (copyForm.groupId) {
  638. const g = groupOptions.value.find(g => g.id === copyForm.groupId)
  639. if (g) copyForm.groupName = g.label
  640. }
  641. if (copyForm.regionalId) {
  642. const r = regionalOptions.value.find(r => r.id === copyForm.regionalId)
  643. if (r) copyForm.regionalName = r.name
  644. }
  645. if (copyForm.channelId) {
  646. const c = formChannelOptions.value.find(c => c.id === copyForm.channelId)
  647. if (c) copyForm.channelName = c.name || c.positionName
  648. }
  649. if (copyForm.postId) {
  650. const postNode = findTreeNode(postTreeData.value, copyForm.postId, 'postId', 'postName')
  651. if (postNode) copyForm.postName = postNode.postName
  652. }
  653. if (form.id) { await updateScoreEvent(copyForm); ElMessage.success('修改成功') }
  654. else { await addScoreEvent(copyForm); ElMessage.success('新增成功') }
  655. dialogVisible.value = false; getList()
  656. }
  657. function handleDelete(row) {
  658. const delIds = row?.id ? [row.id] : ids.value
  659. ElMessageBox.confirm('确认删除选中的配分事项?', '提示', { type: 'warning' }).then(() => {
  660. delScoreEvent(delIds.join(',')).then(() => { ElMessage.success('删除成功'); getList() })
  661. })
  662. }
  663. function handleExport() {
  664. const p = { ...queryParams }
  665. if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
  666. // exportScoreEvent(p)
  667. proxy.download('score/event/export', {
  668. ...p
  669. }, `配分事项_${new Date().getTime()}.xlsx`)
  670. }
  671. async function handleImportFile(file) {
  672. try {
  673. const r = await importScoreEvent(file.raw)
  674. ElMessage.success(r.msg || '导入成功')
  675. getList()
  676. } catch (e) {
  677. ElMessage.error('导入失败')
  678. }
  679. }
  680. const route = useRoute()
  681. let isInit = true
  682. // 监听路由参数,从员工画像跳转时回填查询条件
  683. watch(() => route.query, (query) => {
  684. if (query.id) {
  685. queryParams.personId = Number(query.id)
  686. }
  687. if (query.startDate && query.endDate) {
  688. dateRange.value = [query.startDate, query.endDate]
  689. }
  690. // 非初始化时(即路由参数后续变化),主动刷新数据
  691. if (!isInit && (query.id || query.startDate || query.endDate)) {
  692. handleQuery()
  693. }
  694. isInit = false
  695. }, { immediate: true })
  696. // 递归处理岗位树,禁用第一级
  697. const processPostTree = (nodes, isFirstLevel = true) => {
  698. return nodes.map(node => {
  699. const newNode = { ...node, disabled: isFirstLevel }
  700. if (node.children) {
  701. newNode.children = processPostTree(node.children, false)
  702. }
  703. return newNode
  704. })
  705. }
  706. onMounted(() => {
  707. loadDimensions();
  708. loadDepts();
  709. loadQueryDepts();
  710. loadAllPersons();
  711. loadRegions();
  712. listAllTree().then(res => {
  713. postTreeData.value = processPostTree(res.data || [])
  714. });
  715. getList()
  716. })
  717. </script>