index.vue 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709
  1. <template>
  2. <div class="app-container">
  3. <!-- 顶部切换 -->
  4. <div class="tab-container">
  5. <el-radio-group v-model="currentTab" size="large">
  6. <el-radio-button value="non-cadre">非干部</el-radio-button>
  7. <el-radio-button value="cadre">干部</el-radio-button>
  8. </el-radio-group>
  9. </div>
  10. <!-- 查询条件 -->
  11. <div class="filter-container">
  12. <el-form :model="queryParams" ref="queryFormRef" :inline="true" class="search-form">
  13. <el-form-item label="姓名" prop="userName">
  14. <el-input v-model="queryParams.userName" placeholder="请输入姓名" clearable style="width: 200px" />
  15. </el-form-item>
  16. <el-form-item label="考核月份" prop="assessmentMonth">
  17. <el-date-picker v-model="queryParams.assessmentMonth" type="month" placeholder="请选择考核月份"
  18. value-format="YYYY-MM" style="width: 200px" />
  19. </el-form-item>
  20. <template v-if="currentTab === 'non-cadre'">
  21. <el-form-item label="岗位" prop="post">
  22. <el-select v-model="queryParams.post" placeholder="请选择岗位" clearable style="width: 200px">
  23. <el-option v-for="item in post" :key="item.value" :label="item.label" :value="item.value" />
  24. </el-select>
  25. </el-form-item>
  26. <el-form-item label="考核组" prop="assessmentTeam">
  27. <el-select v-model="queryParams.assessmentTeam" placeholder="请选择考核组" clearable style="width: 200px">
  28. <el-option v-for="item in assessment_team" :key="item.value" :label="item.label" :value="item.value" />
  29. </el-select>
  30. </el-form-item>
  31. <el-form-item label="考核结果" prop="assessmentResult">
  32. <el-input v-model="queryParams.assessmentResult" placeholder="请输入考核结果" clearable style="width: 200px" />
  33. </el-form-item>
  34. <el-form-item label="豁免" prop="exemption">
  35. <el-input v-model="queryParams.exemption" placeholder="请输入豁免" clearable style="width: 200px" />
  36. </el-form-item>
  37. </template>
  38. <el-form-item>
  39. <el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
  40. <el-button icon="Refresh" @click="resetQuery">重置</el-button>
  41. </el-form-item>
  42. </el-form>
  43. </div>
  44. <!-- 操作按钮 -->
  45. <div class="operation-container">
  46. <!-- 非干部操作按钮 -->
  47. <div v-if="currentTab === 'non-cadre'" class="left-buttons">
  48. <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
  49. <el-button type="danger" plain icon="Delete" :disabled="selectedIds.length === 0"
  50. @click="handleBatchDelete">删除</el-button>
  51. <el-button type="warning" plain icon="Download" @click="handleExport">导出</el-button>
  52. </div>
  53. <!-- 干部模式时显示空白占位 -->
  54. <div v-else class="left-buttons"></div>
  55. <div class="right-buttons">
  56. <el-button type="primary" :disabled="!queryParams.assessmentMonth"
  57. @click="generateMonthlyAssessment">生成本月考核表</el-button>
  58. </div>
  59. </div>
  60. <!-- 非干部数据表格 -->
  61. <div v-if="currentTab === 'non-cadre'">
  62. <el-table v-loading="loading" :data="nonCadreList" border fit highlight-current-row
  63. style="width: 100%; margin-top: 20px;" @selection-change="handleSelectionChange">
  64. <el-table-column type="selection" width="55" align="center" />
  65. <el-table-column label="用工形式" prop="employmentType" align="center" min-width="100">
  66. <template #default="scope">
  67. <dict-tag :options="employment_type" :value="scope.row.employmentType" />
  68. </template>
  69. </el-table-column>
  70. <el-table-column label="岗位" prop="post" align="center" min-width="120">
  71. <template #default="scope">
  72. <dict-tag :options="post" :value="scope.row.post" />
  73. </template>
  74. </el-table-column>
  75. <el-table-column label="员工姓名" prop="userName" align="center" min-width="120" />
  76. <el-table-column label="考核组" prop="assessmentTeam" align="center" min-width="100">
  77. <template #default="scope">
  78. <dict-tag :options="assessment_team" :value="scope.row.assessmentTeam" />
  79. </template>
  80. </el-table-column>
  81. <el-table-column label="分管班组长" prop="deputyTeamLeaderName" align="center" min-width="120" />
  82. <el-table-column label="分管主管" prop="deputySupervisorName" align="center" min-width="120" />
  83. <el-table-column label="分管经理" prop="deputyManagerName" align="center" min-width="120" />
  84. <el-table-column label="红线指标触发次数" prop="redLineIndexTriggerCount" align="center" min-width="140" />
  85. <el-table-column label="红线指标依据" prop="redLineIndexAccordList" align="center" min-width="120"
  86. show-overflow-tooltip />
  87. <el-table-column label="核心指标分值" prop="coreIndexScore" align="center" min-width="120" />
  88. <el-table-column label="核心指标依据" prop="coreIndexAccordList" align="center" min-width="120"
  89. show-overflow-tooltip />
  90. <el-table-column label="其他指标分值" prop="otherIndexScore" align="center" min-width="120" />
  91. <el-table-column label="其他指标依据" prop="otherIndexAccordList" align="center" min-width="120"
  92. show-overflow-tooltip />
  93. <el-table-column label="其他指标中的安全(仅含SOC/站品控检查扣分)分值" prop="otherIndexSafetyScoreWithSocStationQcDeduction"
  94. align="center" min-width="200" show-overflow-tooltip />
  95. <el-table-column label="其他指标中的安全(仅含SOC/站品控检查扣分)依据" prop="otherIndexSafetyScoreWithSocStationQcAccordList"
  96. align="center" min-width="200" show-overflow-tooltip />
  97. <el-table-column label="其他指标中的安全(不含SOC/站品控检查扣分)分值" prop="otherIndexSafetyScoreWithoutSocStationQcDeduction"
  98. align="center" min-width="200" show-overflow-tooltip />
  99. <el-table-column label="其他指标中的安全(不含SOC/站品控检查扣分)依据" prop="otherIndexSafetyScoreWithoutSocStationQcAccordList"
  100. align="center" min-width="200" show-overflow-tooltip />
  101. <el-table-column label="其他指标中的非安全指标扣分" prop="otherIndexNonSafetyDeduction" align="center" min-width="160"
  102. show-overflow-tooltip />
  103. <el-table-column label="其他指标中的非安全指标扣分依据" prop="otherIndexNonSafetyAccordList" align="center" min-width="160"
  104. show-overflow-tooltip />
  105. <el-table-column label="非核心安全+核心扣分" prop="nonCoreSafetyPlusCoreDeduction" align="center" min-width="140" />
  106. <el-table-column label="SOC/站品控检查的涉及核心、安全指标扣分" prop="socStationQcInvolvedCoreSafetyDeduction" align="center"
  107. min-width="200" show-overflow-tooltip />
  108. <el-table-column label="SOC/站品控检查的涉及核心、安全指标扣分依据" prop="socStationQcInvolvedCoreSafetyAccordList" align="center"
  109. min-width="200" show-overflow-tooltip />
  110. <el-table-column label="分管员工数量" prop="inChargeEmployeeCount" align="center" min-width="120" />
  111. <el-table-column label="扣分平均值" prop="deductionAverage" align="center" min-width="100" />
  112. <el-table-column label="总分" prop="totalScore" align="center" min-width="100" sortable />
  113. <el-table-column label="奖励明细" prop="rewardAccordList" align="center" min-width="120" show-overflow-tooltip />
  114. <el-table-column label="惩罚明细" prop="punishmentAccordList" align="center" min-width="120"
  115. show-overflow-tooltip />
  116. <el-table-column label="奖励(元)" prop="rewardAmount" align="center" min-width="100" />
  117. <el-table-column label="扣罚(元)" prop="punishmentAmount" align="center" min-width="100" />
  118. <el-table-column label="考核结果" prop="assessmentResult" align="center" min-width="100" />
  119. <el-table-column label="考核结果备注" prop="assessmentResultRemark" align="center" min-width="140"
  120. show-overflow-tooltip />
  121. <el-table-column label="应用方式" prop="applicationMethod" align="center" min-width="100" />
  122. <el-table-column label="应用方式备注" prop="applicationMethodRemark" align="center" min-width="140"
  123. show-overflow-tooltip />
  124. <el-table-column label="是否豁免" prop="exemption" align="center" min-width="100">
  125. <template #default="scope">
  126. {{ scope.row.exemption }}
  127. </template>
  128. </el-table-column>
  129. <el-table-column label="是否豁免备注" prop="exemptionReasonRemark" align="center" min-width="140"
  130. show-overflow-tooltip />
  131. <el-table-column label="考核月份" prop="assessmentMonth" align="center" min-width="120" />
  132. <el-table-column label="操作" align="center" width="150" fixed="right">
  133. <template #default="scope">
  134. <el-button link type="primary" icon="Edit" @click="handleEdit(scope.row, 'non-cadre')">修改</el-button>
  135. <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
  136. </template>
  137. </el-table-column>
  138. </el-table>
  139. </div>
  140. <!-- 干部数据表格 -->
  141. <div v-else>
  142. <el-table v-loading="loading" :data="cadreList" border fit highlight-current-row
  143. style="width: 100%; margin-top: 20px;" @selection-change="handleSelectionChange">
  144. <el-table-column type="selection" width="55" align="center" />
  145. <el-table-column label="姓名" prop="name" align="center" min-width="120" />
  146. <el-table-column label="部门" prop="deptName" align="center" min-width="120" />
  147. <el-table-column label="岗位" prop="post" align="center" min-width="120">
  148. <template #default="scope">
  149. <dict-tag :options="post" :value="scope.row.post" />
  150. </template>
  151. </el-table-column>
  152. <el-table-column label="区域" prop="area" align="center" min-width="100">
  153. <template #default="scope">
  154. <dict-tag :options="work_area" :value="scope.row.area" />
  155. </template>
  156. </el-table-column>
  157. <el-table-column label="红线扣分" prop="redLineDeduction" align="center" min-width="100" />
  158. <el-table-column label="违规排名扣分" prop="violationRankingDeduction" align="center" min-width="140" />
  159. <el-table-column label="技能排名扣分" prop="skillRankingDeduction" align="center" min-width="140" />
  160. <el-table-column label="技能排名扣分豁免情况" prop="skillExemptionStatus" align="center" min-width="180"
  161. show-overflow-tooltip />
  162. <el-table-column label="总分" prop="totalScore" align="center" min-width="100" sortable />
  163. <el-table-column label="考核结果" prop="assessmentResult" align="center" min-width="120" />
  164. <el-table-column label="考核结果备注" prop="assessmentRemark" align="center" min-width="140" show-overflow-tooltip />
  165. <el-table-column label="考核月份" align="center" min-width="120">
  166. <template #default="scope">
  167. {{ scope.row.year }}-{{ String(scope.row.month).padStart(2, '0') }}
  168. </template>
  169. </el-table-column>
  170. <el-table-column label="应用方式" prop="applicationMethod" align="center" min-width="120" />
  171. <el-table-column label="应用方式备注" prop="applicationRemark" align="center" min-width="160" show-overflow-tooltip />
  172. </el-table>
  173. </div>
  174. <!-- 分页 -->
  175. <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
  176. v-model:limit="queryParams.pageSize" @pagination="getList" />
  177. <!-- 编辑/新增弹窗 -->
  178. <el-dialog :title="dialog.title" v-model="dialog.visible" width="80%" :close-on-click-modal="false"
  179. destroy-on-close>
  180. <!-- 非干部表单 -->
  181. <el-form v-loading="dialog.loading" v-if="dialog.type === 'non-cadre'" :model="nonCadreForm" ref="formRef"
  182. :rules="nonCadreRules" label-width="380px" class="form-container">
  183. <!-- 第一部分:基础信息 -->
  184. <el-row :gutter="20">
  185. <el-col :span="8">
  186. <el-form-item label="姓名" prop="userId" label-width="100px">
  187. <el-select v-model="nonCadreForm.userId" placeholder="请选择姓名" style="width: 100%" clearable filterable
  188. @change="handleUserChange">
  189. <el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
  190. </el-select>
  191. </el-form-item>
  192. </el-col>
  193. <el-col :span="8">
  194. <el-form-item label="考核月份" prop="assessmentMonth" label-width="100px">
  195. <el-date-picker v-model="nonCadreForm.assessmentMonth" type="month" placeholder="请选择考核月份"
  196. value-format="YYYY-MM" style="width: 100%" @change="handleAssessmentMonthChange" />
  197. </el-form-item>
  198. </el-col>
  199. <el-col :span="8">
  200. <div class="score-display">
  201. <span class="score-label">总分:</span>
  202. <span class="score-value">{{ nonCadreForm.totalScore }}</span>
  203. </div>
  204. </el-col>
  205. </el-row>
  206. <el-row :gutter="20">
  207. <el-col :span="8">
  208. <el-form-item label="用工形式" prop="employmentType" label-width="100px">
  209. <el-select v-model="nonCadreForm.employmentType" placeholder="请选择用工形式" style="width: 100%" disabled>
  210. <el-option v-for="item in employment_type" :key="item.value" :label="item.label" :value="item.value" />
  211. </el-select>
  212. </el-form-item>
  213. </el-col>
  214. <el-col :span="8">
  215. <el-form-item label="角色" prop="roleName" label-width="100px">
  216. <el-input v-model="nonCadreForm.roleName" disabled placeholder="角色" />
  217. </el-form-item>
  218. </el-col>
  219. <el-col :span="8">
  220. <el-form-item label="分管班组长" prop="deputyTeamLeaderId" label-width="100px">
  221. <el-select v-model="nonCadreForm.deputyTeamLeaderId" placeholder="请选择分管班组长" style="width: 100%" clearable>
  222. <el-option v-for="leader in teamLeaderOptions" :key="leader.userId" :label="leader.nickName"
  223. :value="leader.userId" />
  224. </el-select>
  225. </el-form-item>
  226. </el-col>
  227. </el-row>
  228. <el-row :gutter="20">
  229. <el-col :span="8">
  230. <el-form-item label="分管主管" prop="deputySupervisorId" label-width="100px">
  231. <el-select v-model="nonCadreForm.deputySupervisorId" placeholder="请选择分管主管" style="width: 100%" clearable>
  232. <el-option v-for="supervisor in supervisorOptions" :key="supervisor.userId" :label="supervisor.nickName"
  233. :value="supervisor.userId" />
  234. </el-select>
  235. </el-form-item>
  236. </el-col>
  237. <el-col :span="8">
  238. <el-form-item label="分管经理" prop="deputyManagerId" label-width="100px">
  239. <el-select v-model="nonCadreForm.deputyManagerId" placeholder="请选择分管经理" style="width: 100%" clearable>
  240. <el-option v-for="manager in managerOptions" :key="manager.userId" :label="manager.nickName"
  241. :value="manager.userId" />
  242. </el-select>
  243. </el-form-item>
  244. </el-col>
  245. <el-col :span="8">
  246. <el-form-item label="考核组" prop="assessmentTeam" label-width="100px">
  247. <el-select v-model="nonCadreForm.assessmentTeam" placeholder="请选择考核组" style="width: 100%">
  248. <el-option v-for="item in assessment_team" :key="item.value" :label="item.label" :value="item.value" />
  249. </el-select>
  250. </el-form-item>
  251. </el-col>
  252. <el-col :span="8">
  253. <el-form-item label="岗位" prop="post" label-width="100px">
  254. <el-select v-model="nonCadreForm.post" placeholder="请选择岗位" style="width: 100%">
  255. <el-option v-for="item in post" :key="item.value" :label="item.label" :value="item.value" />
  256. </el-select>
  257. </el-form-item>
  258. </el-col>
  259. </el-row>
  260. <!-- 考核指标区域 -->
  261. <div class="section-title blue" style="cursor: pointer;" @click="addIndicator">考核指标+</div>
  262. <div class="indicators-box" v-if="nonCadreForm.indicatorGroups.length > 0">
  263. <div v-for="(group, groupIndex) in nonCadreForm.indicatorGroups" :key="groupIndex" class="indicator-group">
  264. <div class="indicator-group-title">{{ group.title }}</div>
  265. <div v-for="(item, itemIndex) in group.items" :key="itemIndex" class="indicator-item">
  266. <div class="indicator-name">{{ item.indicatorName }}</div>
  267. <div class="indicator-value" v-if="item.categoryNameOne != '红线指标'">{{ item.score > 0 ? '+' : '' }}{{ item.score
  268. }}/次
  269. </div>
  270. <div class="indicator-count">{{ item.occurCount }}次</div>
  271. <div class="indicator-total">{{ item.scoreResult > 0 ? '+' : '' }}{{ item.scoreResult }}</div>
  272. <div class="indicator-actions">
  273. <el-button type="primary" link icon="Edit" @click="editIndicator(groupIndex, itemIndex)"></el-button>
  274. <el-button type="danger" link icon="Delete" @click="deleteIndicator(groupIndex, itemIndex)"></el-button>
  275. </div>
  276. </div>
  277. <!-- <el-button type="primary" plain icon="Plus" @click="addIndicator" style="margin-top: 10px;">添加指标</el-button> -->
  278. </div>
  279. </div>
  280. <!-- 第二部分:详细信息 -->
  281. <div style="margin-top: 30px;">
  282. <el-row :gutter="20">
  283. <el-col :span="12">
  284. <el-form-item label="红线指标触发次数">
  285. <el-input v-model="nonCadreForm.redLineIndexTriggerCount" disabled placeholder="不可编辑,红线明细的次数之和" />
  286. </el-form-item>
  287. </el-col>
  288. <el-col :span="12">
  289. <el-form-item label="红线指标依据">
  290. <span class="detail-link"
  291. @click="showDetailModal('红线指标依据', formatAccordList(nonCadreForm.redLineIndexAccordList))">
  292. 查看详情
  293. </span>
  294. </el-form-item>
  295. </el-col>
  296. </el-row>
  297. <el-row :gutter="20">
  298. <el-col :span="12">
  299. <el-form-item label="核心指标分值">
  300. <el-input v-model="nonCadreForm.coreIndexScore" disabled placeholder="不可编辑,各个明细的集合" />
  301. </el-form-item>
  302. </el-col>
  303. <el-col :span="12">
  304. <el-form-item label="核心指标依据">
  305. <span class="detail-link"
  306. @click="showDetailModal('核心指标依据', formatAccordList(nonCadreForm.coreIndexAccordList))">
  307. 查看详情
  308. </span>
  309. </el-form-item>
  310. </el-col>
  311. </el-row>
  312. <el-row :gutter="20">
  313. <el-col :span="12">
  314. <el-form-item label="其他指标中的安全指标(仅含SOC/站品控检查扣分)分值">
  315. <el-input v-model="nonCadreForm.otherIndexSafetyScoreWithSocStationQcDeduction" disabled
  316. placeholder="不可编辑,各个明细的集合" />
  317. </el-form-item>
  318. </el-col>
  319. <el-col :span="12">
  320. <el-form-item label="其他指标中的安全指标(仅含SOC/站品控检查扣分)依据">
  321. <span class="detail-link"
  322. @click="showDetailModal('其他指标中的安全指标依据', formatAccordList(nonCadreForm.otherIndexSafetyScoreWithSocStationQcAccordList))">
  323. 查看详情
  324. </span>
  325. </el-form-item>
  326. </el-col>
  327. </el-row>
  328. <el-row :gutter="20">
  329. <el-col :span="12">
  330. <el-form-item label="其他指标中的非安全指标分值">
  331. <el-input v-model="nonCadreForm.otherIndexNonSafetyDeduction" disabled placeholder="不可编辑,各个明细的集合" />
  332. </el-form-item>
  333. </el-col>
  334. <el-col :span="12">
  335. <el-form-item label="其他指标中的非安全指标依据">
  336. <span class="detail-link"
  337. @click="showDetailModal('其他指标中的非安全指标依据', formatAccordList(nonCadreForm.otherIndexNonSafetyAccordList))">
  338. 查看详情
  339. </span>
  340. </el-form-item>
  341. </el-col>
  342. </el-row>
  343. <el-row :gutter="20">
  344. <el-col :span="12">
  345. <el-form-item label="SOC/站品控检查的涉及核心、安全指标扣分">
  346. <el-input v-model="nonCadreForm.socStationQcInvolvedCoreSafetyDeduction" disabled
  347. placeholder="不可编辑,各个明细的集合" />
  348. </el-form-item>
  349. </el-col>
  350. <el-col :span="12">
  351. <el-form-item label="SOC/站品控检查的涉及核心、安全指标扣分依据">
  352. <span class="detail-link"
  353. @click="showDetailModal('SOC/站品控检查的涉及核心、安全指标扣分依据', formatAccordList(nonCadreForm.socStationQcInvolvedCoreSafetyAccordList))">
  354. 查看详情
  355. </span>
  356. </el-form-item>
  357. </el-col>
  358. </el-row>
  359. <el-row :gutter="20">
  360. <el-col :span="12">
  361. <el-form-item label="非核心安全+核心扣分">
  362. <el-input v-model="nonCadreForm.nonCoreSafetyPlusCoreDeduction" disabled placeholder="不可编辑,各个明细的集合" />
  363. </el-form-item>
  364. </el-col>
  365. <el-col :span="12">
  366. <el-form-item label="分管员工数量" prop="inChargeEmployeeCount">
  367. <el-input-number v-model="nonCadreForm.inChargeEmployeeCount" :min="0" style="width: 100%" />
  368. </el-form-item>
  369. </el-col>
  370. </el-row>
  371. <el-row :gutter="20">
  372. <el-col :span="12">
  373. <el-form-item label="扣分平均值">
  374. <el-input v-model="nonCadreForm.deductionAverage" disabled placeholder="不可编辑,计算" />
  375. </el-form-item>
  376. </el-col>
  377. <el-col :span="12">
  378. <el-form-item label="总分">
  379. <el-input v-model="nonCadreForm.totalScore" disabled placeholder="不可编辑" />
  380. </el-form-item>
  381. </el-col>
  382. </el-row>
  383. <el-row :gutter="20">
  384. <el-col :span="12">
  385. <el-form-item label="奖励明细">
  386. <span class="detail-link"
  387. @click="showDetailModal('奖励明细', formatAccordList(nonCadreForm.rewardAccordList))">
  388. 查看详情
  389. </span>
  390. </el-form-item>
  391. </el-col>
  392. <el-col :span="12">
  393. <el-form-item label="惩罚明细">
  394. <span class="detail-link"
  395. @click="showDetailModal('惩罚明细', formatAccordList(nonCadreForm.punishmentAccordList))">
  396. 查看详情
  397. </span>
  398. </el-form-item>
  399. </el-col>
  400. </el-row>
  401. <el-row :gutter="20">
  402. <el-col :span="12">
  403. <el-form-item label="奖励汇总" prop="rewardAmount">
  404. <div class="input-with-unit">
  405. <el-input-number v-model="nonCadreForm.rewardAmount" :min="0" :precision="2" disabled
  406. style="width: calc(100% - 40px)" />
  407. <span class="unit">元</span>
  408. </div>
  409. </el-form-item>
  410. </el-col>
  411. <el-col :span="12">
  412. <el-form-item label="扣罚汇总" prop="penaltyAmount">
  413. <div class="input-with-unit">
  414. <el-input-number v-model="nonCadreForm.penaltyAmount" :min="0" :precision="2" disabled
  415. style="width: calc(100% - 40px)" />
  416. <span class="unit">元</span>
  417. </div>
  418. </el-form-item>
  419. </el-col>
  420. </el-row>
  421. <el-row :gutter="20">
  422. <el-col :span="12">
  423. <el-form-item label="是否豁免" prop="exemption">
  424. <el-input v-model="nonCadreForm.exemption" placeholder="请输入是否豁免" disabled />
  425. </el-form-item>
  426. </el-col>
  427. <el-col :span="12">
  428. <el-form-item label="豁免备注" prop="exemptionReasonRemark">
  429. <el-input v-model="nonCadreForm.exemptionReasonRemark" type="textarea" :rows="1" placeholder="请输入豁免备注"
  430. disabled />
  431. </el-form-item>
  432. </el-col>
  433. </el-row>
  434. <el-row :gutter="20">
  435. <el-col :span="12">
  436. <el-form-item label="考核结果" prop="assessmentResult" disabled>
  437. <el-input v-model="nonCadreForm.assessmentResult" placeholder="请输入考核结果" disabled />
  438. </el-form-item>
  439. </el-col>
  440. <el-col :span="12">
  441. <el-form-item label="考核结果备注" prop="assessmentResultRemark">
  442. <el-input v-model="nonCadreForm.assessmentResultRemark" type="textarea" :rows="1"
  443. placeholder="请输入考核结果备注" disabled />
  444. </el-form-item>
  445. </el-col>
  446. </el-row>
  447. <el-row :gutter="20">
  448. <el-col :span="12">
  449. <el-form-item label="应用方式" prop="applicationMethod">
  450. <el-input v-model="nonCadreForm.applicationMethod" placeholder="请输入应用方式" disabled />
  451. </el-form-item>
  452. </el-col>
  453. <el-col :span="12">
  454. <el-form-item label="应用备注" prop="applicationMethodRemark">
  455. <el-input v-model="nonCadreForm.applicationMethodRemark" type="textarea" :rows="1" disabled
  456. placeholder="请输入应用备注" />
  457. </el-form-item>
  458. </el-col>
  459. </el-row>
  460. </div>
  461. </el-form>
  462. <template #footer>
  463. <div class="dialog-footer">
  464. <el-button @click="dialog.visible = false">取消</el-button>
  465. <el-button type="primary" @click="submitForm">确定</el-button>
  466. </div>
  467. </template>
  468. </el-dialog>
  469. <!-- 第二个模态框:添加/编辑扣分指标 -->
  470. <el-dialog :title="indicatorDialog.title" v-model="indicatorDialog.visible" width="60%">
  471. <el-form label-width="150px" class="indicator-form" :model="indicatorDialog.form" :rules="indicatorDialog.rules">
  472. <el-form-item label="指标名称" prop="indicatorId" required>
  473. <el-cascader v-model="indicatorDialog.form.indicatorId" placeholder="搜索指标名称" filterable clearable
  474. :options="indicatorDialog.cascaderOptions" :props="indicatorDialog.cascaderProps" style="flex: 1;"
  475. @change="onIndicatorCascaderChange">
  476. </el-cascader>
  477. </el-form-item>
  478. <el-form-item label="分值/单位" v-if="indicatorDialog.form.categoryNameOne != '红线指标'">
  479. <div style="display: flex; align-items: center; gap: 10px;">
  480. <span style="font-size: 24px; font-weight: bold;">{{ indicatorDialog.form.score > 0 ? '+' : '' }}{{
  481. indicatorDialog.form.score }}/次</span>
  482. </div>
  483. </el-form-item>
  484. <el-form-item label="发生次数">
  485. <div style="display: flex; align-items: center; gap: 10px;">
  486. <el-input-number v-model="indicatorDialog.form.occurCount" :min="1" style="width: 200px;"
  487. @change="updateTotal" />
  488. </div>
  489. </el-form-item>
  490. <template
  491. v-for="(detail, index) in (indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList || [])"
  492. :key="index">
  493. <div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">
  494. <h4 style="margin-left: 80px;">明细{{ index + 1 }}</h4>
  495. <el-button type="danger" size="small" @click="removeRewardPunishmentDetail(index)">删除</el-button>
  496. </div>
  497. <el-form-item label="检查部门">
  498. <div style="display: flex; align-items: center; gap: 10px; width: 100%;">
  499. <el-select v-model="detail.qcDeptType" placeholder="请选择" style="width: 200px;">
  500. <el-option v-for="item in base_performance_indicator_qc_dept_type" :key="item.value" :label="item.label"
  501. :value="item.value" />
  502. </el-select>
  503. </div>
  504. </el-form-item>
  505. <el-form-item v-if="indicatorDialog.form.score > 0" label="奖励明细">
  506. <el-input v-model="detail.rewardPunishmentDetail" type="textarea" :rows="2" />
  507. </el-form-item>
  508. <el-form-item v-if="indicatorDialog.form.score > 0" label="奖励">
  509. <div style="display: flex; align-items: center; gap: 10px;">
  510. <el-input-number v-model="detail.amount" :min="0" :precision="2" style="width: 200px;"
  511. @change="updateTotal" />
  512. <span>元</span>
  513. </div>
  514. </el-form-item>
  515. <el-form-item v-if="indicatorDialog.form.score < 0" label="惩罚明细">
  516. <el-input v-model="detail.rewardPunishmentDetail" type="textarea" :rows="2" />
  517. </el-form-item>
  518. <el-form-item v-if="indicatorDialog.form.score < 0" label="扣罚">
  519. <div style="display: flex; align-items: center; gap: 10px;">
  520. <el-input-number v-model="detail.amount" :min="0" :precision="2" style="width: 200px;"
  521. @change="updateTotal" />
  522. <span>元</span>
  523. </div>
  524. </el-form-item>
  525. </template>
  526. </el-form>
  527. <template #footer>
  528. <div class="dialog-footer">
  529. <el-button @click="indicatorDialog.visible = false">取消</el-button>
  530. <el-button type="primary" @click="saveIndicator">确定</el-button>
  531. </div>
  532. </template>
  533. </el-dialog>
  534. <!-- 详情模态框 -->
  535. <el-dialog :title="detailModal.title" v-model="detailModal.visible" width="60%">
  536. <div class="detail-content">
  537. {{ detailModal?.content || '暂无内容' }}
  538. </div>
  539. <template #footer>
  540. <div class="dialog-footer">
  541. <el-button @click="detailModal.visible = false">关闭</el-button>
  542. </div>
  543. </template>
  544. </el-dialog>
  545. </div>
  546. </template>
  547. <script setup>
  548. import { ref, reactive, onMounted, getCurrentInstance, watch, nextTick } from 'vue'
  549. import { ElMessage, ElMessageBox } from 'element-plus'
  550. // API导入(需要根据实际API路径调整)
  551. import { listCadreAssessment, generateCadreAssessment, listNonCadreAssessment, addNonCadreAssessment, updateNonCadreAssessment, deleteNonCadreAssessment, exportNonCadreAssessment, generateNonCadreAssessment, getNonCadreAssessment, deleteCadreAssessment } from '@/api/performance/monthlyAssess.js'
  552. import { selectUserLeaderListByCondition, listUserPerformance } from '@/api/system/user.js'
  553. import { listIndicator } from '@/api/system/classificationAssess.js'
  554. import { queryAssessCategoryTreeAndIndicator } from '@/api/system/classificationAssessIndicator.js'
  555. const { proxy } = getCurrentInstance()
  556. const { post, work_area, employment_type, assessment_team, base_performance_indicator_qc_dept_type } = proxy.useDict('post', 'work_area', 'employment_type', 'assessment_team', 'base_performance_indicator_qc_dept_type')
  557. // 响应式数据
  558. const loading = ref(false)
  559. const total = ref(0)
  560. const queryFormRef = ref()
  561. const formRef = ref()
  562. const currentTab = ref('non-cadre')
  563. const selectedIds = ref([])
  564. // 监听tab切换
  565. watch(() => currentTab.value, () => {
  566. getList()
  567. })
  568. // 查询参数
  569. const queryParams = reactive({
  570. pageNum: 1,
  571. pageSize: 10,
  572. userName: '',
  573. assessmentMonth: '',
  574. post: '',
  575. assessmentTeam: '',
  576. assessmentResult: '',
  577. exemption: ''
  578. })
  579. const currentMonthDefault = `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`
  580. const getEmptyNonCadreForm = () => ({
  581. id: '',
  582. userId: '',
  583. userName: '',
  584. roleKey: '',
  585. roleId: '',
  586. roleName: '',
  587. employmentType: '',
  588. post: '',
  589. assessmentMonth: currentMonthDefault,
  590. assessmentTeam: '',
  591. deputyTeamLeaderId: '',
  592. deputyTeamLeaderName: '',
  593. deputySupervisorId: '',
  594. deputySupervisorName: '',
  595. deputyManagerId: '',
  596. deputyManagerName: '',
  597. inChargeEmployeeCount: 0,
  598. totalScore: 0,
  599. assessmentResult: '',
  600. applicationMethod: '',
  601. exemption: '',
  602. exemptionReasonRemark: '',
  603. rewardAmount: 0,
  604. penaltyAmount: 0,
  605. punishmentAmount: 0,
  606. assessmentRemark: '',
  607. applicationMethodRemark: '',
  608. indicatorGroups: [],
  609. redLineTriggerCount: '',
  610. coreIndicatorScore: '',
  611. safetyWithSocScore: '',
  612. nonSafetyIndicatorScore: '',
  613. socSafetyCoreDeduction: '',
  614. nonCoreSafetyCoreDeduction: '',
  615. deductionAverage: '',
  616. rewardDetailsSummary: '',
  617. penaltyDetailsSummary: '',
  618. redLineIndexAccordList: [],
  619. coreIndexAccordList: [],
  620. otherIndexSafetyScoreWithSocStationQcAccordList: [],
  621. otherIndexNonSafetyAccordList: [],
  622. socStationQcInvolvedCoreSafetyAccordList: [],
  623. rewardAccordList: [],
  624. punishmentAccordList: []
  625. })
  626. const nonCadreForm = reactive(getEmptyNonCadreForm())
  627. watch(() => nonCadreForm.assessmentMonth, (newVal) => {
  628. if (newVal) {
  629. const month = newVal.replace('-', '')
  630. loadUserList(month)
  631. }
  632. })
  633. watch(() => nonCadreForm.deputyTeamLeaderId, (newVal) => {
  634. if (newVal) {
  635. const leader = teamLeaderOptions.value.find(u => u.userId === newVal)
  636. nonCadreForm.deputyTeamLeaderName = leader?.nickName || ''
  637. } else {
  638. nonCadreForm.deputyTeamLeaderName = ''
  639. }
  640. })
  641. watch(() => nonCadreForm.deputySupervisorId, (newVal) => {
  642. if (newVal) {
  643. const supervisor = supervisorOptions.value.find(u => u.userId === newVal)
  644. nonCadreForm.deputySupervisorName = supervisor?.nickName || ''
  645. } else {
  646. nonCadreForm.deputySupervisorName = ''
  647. }
  648. })
  649. watch(() => nonCadreForm.deputyManagerId, (newVal) => {
  650. if (newVal) {
  651. const manager = managerOptions.value.find(u => u.userId === newVal)
  652. nonCadreForm.deputyManagerName = manager?.nickName || ''
  653. } else {
  654. nonCadreForm.deputyManagerName = ''
  655. }
  656. })
  657. function addIndicatorToGroup(item) {
  658. const categoryName = item.categoryNameOne || '未分类'
  659. let group = nonCadreForm.indicatorGroups.length == 0 ? null : nonCadreForm.indicatorGroups.find(g => g.title === categoryName);
  660. if (!group) {
  661. group = {
  662. title: categoryName,
  663. items: []
  664. }
  665. nonCadreForm.indicatorGroups.push(group)
  666. }
  667. group.items.push(item)
  668. }
  669. // 第二个模态框状态
  670. const indicatorDialog = reactive({
  671. visible: false,
  672. title: '',
  673. mode: 'add',
  674. groupIndex: null,
  675. itemIndex: null,
  676. loading: false,
  677. indicatorOptions: [],
  678. cascaderOptions: [],
  679. cascaderProps: {
  680. value: 'id',
  681. label: 'name',
  682. children: 'indicatorList',
  683. checkStrictly: false,
  684. emitPath: false
  685. },
  686. rules: {
  687. indicatorId: [
  688. { required: true, message: '请选择指标名称', trigger: 'change' }
  689. ]
  690. },
  691. form: {
  692. indicatorId: '',
  693. name: '',
  694. score: 0,
  695. occurCount: 1,
  696. qcDeptType: '',
  697. scoreResult: 0,
  698. amountResult: 0,
  699. rewardDetails: '',
  700. reward: 0,
  701. penaltyDetails: '',
  702. penalty: 0,
  703. rewardPunishmentType: '',
  704. personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
  705. }
  706. })
  707. async function searchIndicators(query) {
  708. if (!query) {
  709. indicatorDialog.indicatorOptions = []
  710. return
  711. }
  712. indicatorDialog.loading = true
  713. try {
  714. const res = await listIndicator({ name: query })
  715. indicatorDialog.indicatorOptions = res.rows || []
  716. } catch (error) {
  717. console.error('搜索指标失败:', error)
  718. indicatorDialog.indicatorOptions = []
  719. } finally {
  720. indicatorDialog.loading = false
  721. }
  722. }
  723. async function loadIndicatorCascaderOptions() {
  724. try {
  725. const res = await queryAssessCategoryTreeAndIndicator({})
  726. const treeData = res.data || []
  727. indicatorDialog.cascaderOptions = transformIndicatorTree(treeData)
  728. } catch (error) {
  729. console.error('加载指标树失败:', error)
  730. indicatorDialog.cascaderOptions = []
  731. }
  732. }
  733. function transformIndicatorTree(treeData) {
  734. return treeData.map(node => {
  735. const transformed = {
  736. id: node.id,
  737. name: node.name,
  738. indicatorList: []
  739. }
  740. if (node.children && node.children.length > 0) {
  741. transformed.indicatorList = node.children.map(child => {
  742. const transformedChild = {
  743. id: child.id,
  744. name: child.name,
  745. indicatorList: []
  746. }
  747. if (child.indicatorList && child.indicatorList.length > 0) {
  748. transformedChild.indicatorList = child.indicatorList.map(indicator => ({
  749. ...indicator, name: indicator.name, id: indicator.id, indicatorId: indicator.id, indicatorName: indicator.name, occurCount: 1, personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
  750. }))
  751. }
  752. return transformedChild
  753. })
  754. }
  755. if (node.indicatorList && node.indicatorList.length > 0) {
  756. transformed.indicatorList = node.indicatorList.map(indicator => ({
  757. ...indicator, name: indicator.name, id: indicator.id, indicatorId: indicator.id, indicatorName: indicator.name, occurCount: 1, personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
  758. }))
  759. }
  760. return transformed
  761. })
  762. }
  763. function onIndicatorCascaderChange(value) {
  764. const selected = findIndicatorById(indicatorDialog.cascaderOptions, value)
  765. if (selected) {
  766. indicatorDialog.form = {
  767. ...selected,
  768. name: selected.name,
  769. id: selected.id,
  770. indicatorId: selected.id,
  771. indicatorName: selected.name,
  772. occurCount: 1,
  773. personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
  774. }
  775. updateTotal()
  776. }
  777. }
  778. function findIndicatorById(tree, targetId) {
  779. for (const node of tree) {
  780. if (node.id === targetId) {
  781. return node
  782. }
  783. if (node.indicatorList && node.indicatorList.length > 0) {
  784. const found = findIndicatorById(node.indicatorList, targetId)
  785. if (found) return found
  786. }
  787. }
  788. return null
  789. }
  790. // 弹窗配置
  791. const dialog = reactive({
  792. visible: false,
  793. title: '',
  794. type: 'non-cadre',
  795. loading: false
  796. })
  797. // 详情模态框
  798. const detailModal = reactive({
  799. visible: false,
  800. title: '',
  801. content: ''
  802. })
  803. // 显示详情模态框
  804. const showDetailModal = (title, content) => {
  805. detailModal.title = title
  806. detailModal.content = content
  807. detailModal.visible = true
  808. }
  809. const formatAccordList = (list) => {
  810. if (!list || !Array.isArray(list) || list.length === 0) return ''
  811. return list.map(item => {
  812. const name = item.indicatorName || item.name || ''
  813. const score = item.score !== undefined ? item.score : ''
  814. const count = item.occurCount !== undefined ? item.occurCount : ''
  815. return `${name} ${score}分 x ${count}次`
  816. }).join('\n')
  817. }
  818. // 数据列表
  819. const nonCadreList = ref([])
  820. const cadreList = ref([])
  821. const userList = ref([])
  822. const teamLeaderOptions = ref([])
  823. const supervisorOptions = ref([])
  824. const managerOptions = ref([])
  825. // 非干部表单验证规则
  826. const nonCadreRules = {
  827. userId: [{ required: true, message: '姓名不能为空', trigger: 'change' }]
  828. }
  829. async function loadUserList(month) {
  830. dialog.loading = true
  831. try {
  832. const res = await listUserPerformance({ month })
  833. userList.value = res.data || []
  834. } catch (error) {
  835. console.error('获取员工列表失败:', error)
  836. } finally {
  837. dialog.loading = false
  838. }
  839. }
  840. async function loadTeamLeaderOptions(userId) {
  841. try {
  842. const res = await selectUserLeaderListByCondition({ roleKeyList: ['banzuzhang'], userId })
  843. teamLeaderOptions.value = res.data || []
  844. } catch (error) {
  845. console.error('获取班组长列表失败:', error)
  846. }
  847. }
  848. async function loadSupervisorOptions(userId) {
  849. try {
  850. const res = await selectUserLeaderListByCondition({ roleKeyList: ['kezhang'], userId })
  851. supervisorOptions.value = res.data || []
  852. } catch (error) {
  853. console.error('获取主管列表失败:', error)
  854. }
  855. }
  856. async function loadManagerOptions(userId) {
  857. try {
  858. const res = await selectUserLeaderListByCondition({ roleKeyList: ['jingli'], userId })
  859. managerOptions.value = res.data || []
  860. } catch (error) {
  861. console.error('获取经理列表失败:', error)
  862. }
  863. }
  864. async function handleUserChange(userId) {
  865. if (!userId) {
  866. teamLeaderOptions.value = []
  867. supervisorOptions.value = []
  868. managerOptions.value = []
  869. nonCadreForm.deputyTeamLeaderId = ''
  870. nonCadreForm.deputyTeamLeaderName = ''
  871. nonCadreForm.deputySupervisorId = ''
  872. nonCadreForm.deputySupervisorName = ''
  873. nonCadreForm.deputyManagerId = ''
  874. nonCadreForm.deputyManagerName = ''
  875. checkAndLoadExistingAssessment()
  876. return
  877. }
  878. const user = userList.value.find(u => u.userId === userId)
  879. if (user && user.nickName) {
  880. await Promise.all([
  881. loadTeamLeaderOptions(user.userId),
  882. loadSupervisorOptions(user.userId),
  883. loadManagerOptions(user.userId)
  884. ])
  885. const role = user.roles && user.roles[0] || {}
  886. nonCadreForm.userName = user.nickName
  887. nonCadreForm.roleName = role?.roleName
  888. nonCadreForm.roleKey = role?.roleKey
  889. nonCadreForm.roleId = role?.roleId
  890. nonCadreForm.employmentType = user.employmentType
  891. nonCadreForm.post = user.post
  892. nonCadreForm.assessmentTeam = user.assessmentTeam
  893. nonCadreForm.assessmentTeamDesc = user.assessmentTeamDesc
  894. nonCadreForm.deputyTeamLeaderId = ''
  895. nonCadreForm.deputyTeamLeaderName = ''
  896. nonCadreForm.deputySupervisorId = ''
  897. nonCadreForm.deputySupervisorName = ''
  898. nonCadreForm.deputyManagerId = ''
  899. nonCadreForm.deputyManagerName = ''
  900. }
  901. checkAndLoadExistingAssessment()
  902. }
  903. async function handleAssessmentMonthChange() {
  904. checkAndLoadExistingAssessment()
  905. }
  906. async function checkAndLoadExistingAssessment() {
  907. if (!nonCadreForm.userId || !nonCadreForm.assessmentMonth) {
  908. return
  909. }
  910. try {
  911. const assessmentMonth = nonCadreForm.assessmentMonth.replace('-', '')
  912. const res = await listNonCadreAssessment({
  913. userId: nonCadreForm.userId,
  914. assessmentMonth: assessmentMonth
  915. })
  916. const rows = res.rows || []
  917. if (rows.length > 0) {
  918. const assessmentId = rows[0].id
  919. await loadAssessmentDetail(assessmentId)
  920. }
  921. } catch (error) {
  922. console.error('检查已有考核数据失败:', error)
  923. }
  924. }
  925. async function loadAssessmentDetail(id) {
  926. try {
  927. const res = await getNonCadreAssessment(id)
  928. const detailList = res.data.personnelMonthlyAssessmentIndicatorDetailList || []
  929. const indicatorGroupsMap = {}
  930. detailList.forEach(item => {
  931. const categoryKey = item.categoryCodeOne || item.categoryNameOne || '未分类'
  932. if (!indicatorGroupsMap[categoryKey]) {
  933. indicatorGroupsMap[categoryKey] = {
  934. title: item.categoryNameOne || '未分类',
  935. items: []
  936. }
  937. }
  938. indicatorGroupsMap[categoryKey].items.push({
  939. ...item
  940. })
  941. })
  942. const indicatorGroups = Object.values(indicatorGroupsMap)
  943. Object.keys(res.data).forEach(key => {
  944. if (key === 'assessmentMonth' && res.data[key]) {
  945. const val = res.data[key]
  946. if (typeof val === 'string' && val.length === 6 && /^\d+$/.test(val)) {
  947. nonCadreForm[key] = val.substring(0, 4) + '-' + val.substring(4, 6)
  948. } else {
  949. nonCadreForm[key] = val
  950. }
  951. } else if (key !== 'personnelMonthlyAssessmentIndicatorDetailList') {
  952. nonCadreForm[key] = res.data[key]
  953. }
  954. })
  955. nonCadreForm.indicatorGroups = indicatorGroups
  956. calculateRewardPenaltyTotal()
  957. } catch (error) {
  958. console.error('获取考核详情失败:', error)
  959. ElMessage.error('获取考核详情失败')
  960. }
  961. }
  962. // 获取数据列表
  963. const getList = async () => {
  964. loading.value = true
  965. try {
  966. // 将 assessmentMonth 拆分为 year 和 month
  967. let params = { ...queryParams }
  968. let params1 = { ...queryParams }
  969. if (params.assessmentMonth) {
  970. const [year, month] = params.assessmentMonth.split('-')
  971. params.year = parseInt(year)
  972. params.month = parseInt(month)
  973. delete params.assessmentMonth
  974. }
  975. if (params1.assessmentMonth) {
  976. const [year, month] = params1.assessmentMonth.split('-')
  977. params1.assessmentMonth = `${year}${month}`
  978. }
  979. if (currentTab.value === 'non-cadre') {
  980. const res = await listNonCadreAssessment(params1)
  981. nonCadreList.value = res.rows || []
  982. total.value = res.total || 0
  983. } else {
  984. // 干部数据API
  985. const res = await listCadreAssessment(params)
  986. cadreList.value = res.rows || []
  987. total.value = res.total || 0
  988. }
  989. } catch (error) {
  990. console.error('获取数据失败:', error)
  991. ElMessage.error('获取数据失败')
  992. } finally {
  993. loading.value = false
  994. }
  995. }
  996. // 查询
  997. const handleQuery = () => {
  998. queryParams.pageNum = 1
  999. getList()
  1000. }
  1001. // 重置查询
  1002. const resetQuery = () => {
  1003. queryFormRef.value?.resetFields()
  1004. queryParams.pageNum = 1
  1005. getList()
  1006. }
  1007. // 批量选择
  1008. const handleSelectionChange = (selection) => {
  1009. selectedIds.value = selection.map(item => item.id)
  1010. }
  1011. // 批量删除
  1012. const handleBatchDelete = () => {
  1013. if (selectedIds.value.length === 0) {
  1014. return
  1015. }
  1016. proxy.$modal.confirm(`是否确认删除选中的 ${selectedIds.value.length} 条数据?`).then(async () => {
  1017. try {
  1018. if (currentTab.value === 'non-cadre') {
  1019. await deleteNonCadreAssessment(selectedIds.value.join(','))
  1020. } else {
  1021. await deleteCadreAssessment(selectedIds.value.join(','))
  1022. }
  1023. ElMessage.success('删除成功')
  1024. selectedIds.value = []
  1025. getList()
  1026. } catch (error) {
  1027. console.error('删除失败:', error)
  1028. }
  1029. }).catch(() => { })
  1030. }
  1031. // 新增
  1032. const handleAdd = () => {
  1033. dialog.visible = true
  1034. dialog.type = currentTab.value
  1035. dialog.title = currentTab.value === 'non-cadre' ? '新增非干部月度考核' : '新增干部月度考核'
  1036. if (currentTab.value === 'non-cadre') {
  1037. const emptyForm = getEmptyNonCadreForm()
  1038. Object.keys(emptyForm).forEach(key => {
  1039. nonCadreForm[key] = emptyForm[key]
  1040. })
  1041. const month = nonCadreForm.assessmentMonth.replace('-', '')
  1042. loadUserList(month)
  1043. teamLeaderOptions.value = []
  1044. supervisorOptions.value = []
  1045. managerOptions.value = []
  1046. }
  1047. }
  1048. // 编辑
  1049. const handleEdit = async (row, type) => {
  1050. dialog.visible = true
  1051. dialog.type = type
  1052. dialog.title = '编辑非干部月度考核'
  1053. if (type === 'non-cadre') {
  1054. const month = nonCadreForm.assessmentMonth.replace('-', '')
  1055. loadUserList(month)
  1056. loadTeamLeaderOptions(row.userId)
  1057. loadSupervisorOptions(row.userId)
  1058. loadManagerOptions(row.userId)
  1059. try {
  1060. const res = await getNonCadreAssessment(row.id)
  1061. const detailList = res.data.personnelMonthlyAssessmentIndicatorDetailList || []
  1062. const indicatorGroupsMap = {}
  1063. detailList.forEach(item => {
  1064. const categoryKey = item.categoryCodeOne || item.categoryNameOne || '未分类'
  1065. if (!indicatorGroupsMap[categoryKey]) {
  1066. indicatorGroupsMap[categoryKey] = {
  1067. title: item.categoryNameOne || '未分类',
  1068. items: []
  1069. }
  1070. }
  1071. indicatorGroupsMap[categoryKey].items.push({
  1072. ...item
  1073. })
  1074. })
  1075. const indicatorGroups = Object.values(indicatorGroupsMap)
  1076. Object.keys(res.data).forEach(key => {
  1077. if (key === 'assessmentMonth' && res.data[key]) {
  1078. const val = res.data[key]
  1079. if (typeof val === 'string' && val.length === 6 && /^\d+$/.test(val)) {
  1080. nonCadreForm[key] = val.substring(0, 4) + '-' + val.substring(4, 6)
  1081. } else {
  1082. nonCadreForm[key] = val
  1083. }
  1084. } else if (key !== 'personnelMonthlyAssessmentIndicatorDetailList') {
  1085. nonCadreForm[key] = res.data[key]
  1086. }
  1087. })
  1088. nonCadreForm.indicatorGroups = indicatorGroups
  1089. } catch (error) {
  1090. console.error('获取详情失败:', error)
  1091. ElMessage.error('获取详情失败')
  1092. }
  1093. }
  1094. }
  1095. // 删除
  1096. const handleDelete = async (row) => {
  1097. try {
  1098. await ElMessageBox.confirm('确认删除该考核记录吗?', '提示', {
  1099. confirmButtonText: '确定',
  1100. cancelButtonText: '取消',
  1101. type: 'warning'
  1102. })
  1103. await deleteNonCadreAssessment(row.id)
  1104. ElMessage.success('删除成功')
  1105. getList()
  1106. } catch (error) {
  1107. if (error !== 'cancel') {
  1108. ElMessage.error('删除失败')
  1109. }
  1110. }
  1111. }
  1112. // 提交表单
  1113. const submitForm = async () => {
  1114. const valid = await formRef.value?.validate()
  1115. if (!valid) return
  1116. try {
  1117. if (dialog.type === 'non-cadre') {
  1118. const submitData = { ...nonCadreForm }
  1119. if (submitData.assessmentMonth) {
  1120. submitData.assessmentMonth = submitData.assessmentMonth.replace('-', '')
  1121. }
  1122. if (submitData.indicatorGroups && submitData.indicatorGroups.length > 0) {
  1123. submitData.personnelMonthlyAssessmentIndicatorDetailList = submitData.indicatorGroups.flatMap(group =>
  1124. group.items.map(item => ({
  1125. ...item,
  1126. indicatorName: item.name || item.indicatorName,
  1127. indicatorId: item.id || item.indicatorId,
  1128. indicatorCode: item.code || item.indicatorCode,
  1129. }))
  1130. )
  1131. delete submitData.indicatorGroups
  1132. }
  1133. if (dialog.title === '新增非干部月度考核') {
  1134. await addNonCadreAssessment(submitData)
  1135. ElMessage.success('新增成功')
  1136. } else {
  1137. await updateNonCadreAssessment(submitData)
  1138. ElMessage.success('更新成功')
  1139. }
  1140. } else {
  1141. ElMessage.success('操作成功')
  1142. }
  1143. dialog.visible = false
  1144. getList()
  1145. } catch (error) {
  1146. ElMessage.error('操作失败')
  1147. }
  1148. }
  1149. // 导出
  1150. const handleExport = async () => {
  1151. try {
  1152. let params = { ...queryParams }
  1153. if (params.assessmentMonth) {
  1154. const [year, month] = params.assessmentMonth.split('-')
  1155. params.year = parseInt(year)
  1156. params.month = parseInt(month)
  1157. delete params.assessmentMonth
  1158. }
  1159. Object.keys(params).forEach(key => {
  1160. if (params[key] === null || params[key] === undefined || params[key] === '') {
  1161. delete params[key]
  1162. }
  1163. })
  1164. proxy.download('personnel/assessment/export', params, `非干部月度考核_${new Date().getTime()}.xlsx`)
  1165. } catch (error) {
  1166. ElMessage.error('导出失败')
  1167. }
  1168. }
  1169. // 生成本月考核表
  1170. const generateMonthlyAssessment = async () => {
  1171. try {
  1172. const tabText = currentTab.value === 'non-cadre' ? '非干部' : '干部'
  1173. ElMessageBox.confirm(`是否生成${queryParams.assessmentMonth}考核数据?`, '提示', {
  1174. confirmButtonText: '确定',
  1175. cancelButtonText: '取消',
  1176. type: 'warning'
  1177. }).then(async () => {
  1178. loading.value = true
  1179. const queryMonth = queryParams.assessmentMonth.replace('-', '')
  1180. const queryYear = parseInt(queryMonth.substring(0, 4))
  1181. const queryMonthNum = parseInt(queryMonth.substring(4, 6))
  1182. if (currentTab.value === 'non-cadre') {
  1183. const res = await generateNonCadreAssessment({ month: queryMonth })
  1184. ElMessage.success('生成成功')
  1185. } else {
  1186. const params = {
  1187. year: queryYear,
  1188. month: queryMonthNum
  1189. }
  1190. const res = await generateCadreAssessment(params)
  1191. ElMessage.success('生成成功')
  1192. }
  1193. getList()
  1194. }).catch(() => { })
  1195. } catch (error) {
  1196. console.error('生成失败:', error)
  1197. ElMessage.error('生成失败')
  1198. } finally {
  1199. loading.value = false
  1200. }
  1201. }
  1202. // 获取考核结果文本
  1203. const getResultText = (value) => {
  1204. const map = {
  1205. 'excellent': '优秀',
  1206. 'good': '良好',
  1207. 'qualified': '合格',
  1208. 'unqualified': '不称职'
  1209. }
  1210. return map[value] || ''
  1211. }
  1212. // 添加指标 - 打开新增模态框
  1213. const addIndicator = () => {
  1214. indicatorDialog.visible = true
  1215. indicatorDialog.title = '添加扣分指标'
  1216. indicatorDialog.mode = 'add'
  1217. indicatorDialog.groupIndex = null
  1218. indicatorDialog.itemIndex = null
  1219. indicatorDialog.form = {
  1220. name: '',
  1221. score: 0,
  1222. occurCount: 1,
  1223. qcDeptType: '',
  1224. scoreResult: 0,
  1225. amountResult: 0,
  1226. rewardPunishmentDetail: '',
  1227. amount: 0,
  1228. personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: []
  1229. }
  1230. loadIndicatorCascaderOptions()
  1231. }
  1232. // 编辑指标 - 打开编辑模态框
  1233. const editIndicator = async (groupIndex, itemIndex) => {
  1234. const item = nonCadreForm.indicatorGroups[groupIndex].items[itemIndex]
  1235. indicatorDialog.visible = true
  1236. indicatorDialog.title = '编辑扣分指标'
  1237. indicatorDialog.mode = 'edit'
  1238. indicatorDialog.groupIndex = groupIndex
  1239. indicatorDialog.itemIndex = itemIndex
  1240. await loadIndicatorCascaderOptions()
  1241. await nextTick()
  1242. indicatorDialog.form = { ...item }
  1243. if (!indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList) {
  1244. indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList = []
  1245. }
  1246. }
  1247. // 删除指标
  1248. const deleteIndicator = (groupIndex, itemIndex) => {
  1249. ElMessageBox.confirm('确认删除该指标吗?', '提示', {
  1250. confirmButtonText: '确定',
  1251. cancelButtonText: '取消',
  1252. type: 'warning'
  1253. }).then(() => {
  1254. nonCadreForm.indicatorGroups[groupIndex].items.splice(itemIndex, 1)
  1255. calculateRewardPenaltyTotal()
  1256. ElMessage.success('删除成功')
  1257. }).catch(() => { })
  1258. }
  1259. // 指标名称变化时更新分值
  1260. const onIndicatorNameChange = (value) => {
  1261. console.log(value, indicatorDialog.indicatorOptions, "indicatorDialog.indicatorOptions")
  1262. const selected = indicatorDialog.indicatorOptions.find(item => item.id === value)
  1263. if (selected) {
  1264. indicatorDialog.form = { ...selected, indicatorId: selected.id, indicatorName: selected.name, occurCount: 1, personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList: [] }
  1265. updateTotal()
  1266. }
  1267. }
  1268. // 更新总分
  1269. const updateTotal = () => {
  1270. indicatorDialog.form.scoreResult = indicatorDialog.form.score * indicatorDialog.form.occurCount
  1271. const list = indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList
  1272. if (!list) {
  1273. indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList = []
  1274. indicatorDialog.form.amountResult = 0
  1275. return
  1276. }
  1277. const targetCount = indicatorDialog.form.occurCount
  1278. if (list.length < targetCount) {
  1279. for (let i = list.length; i < targetCount; i++) {
  1280. list.push({
  1281. qcDeptType: '',
  1282. rewardPunishmentDetail: '',
  1283. amount: 0
  1284. })
  1285. }
  1286. }
  1287. indicatorDialog.form.amountResult = list.reduce((sum, item) => sum + (Number(item.amount) || 0), 0)
  1288. }
  1289. // 保存指标
  1290. const saveIndicator = () => {
  1291. if (!indicatorDialog.form.indicatorId) {
  1292. ElMessage.error('请选择指标名称')
  1293. return
  1294. }
  1295. const list = indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList || []
  1296. if (list.length !== indicatorDialog.form.occurCount) {
  1297. ElMessage.error(`发生次数为 ${indicatorDialog.form.occurCount},但明细数量为 ${list.length},请删除多余的明细`)
  1298. return
  1299. }
  1300. list.forEach(detail => {
  1301. detail.rewardPunishmentType = indicatorDialog.form.rewardPunishmentType;
  1302. detail.score = indicatorDialog.form.score
  1303. })
  1304. if (indicatorDialog.mode === 'add') {
  1305. addIndicatorToGroup({ ...indicatorDialog.form })
  1306. ElMessage.success('添加成功')
  1307. } else {
  1308. if (indicatorDialog.groupIndex !== null && indicatorDialog.itemIndex !== null) {
  1309. Object.assign(nonCadreForm.indicatorGroups[indicatorDialog.groupIndex].items[indicatorDialog.itemIndex], indicatorDialog.form)
  1310. ElMessage.success('修改成功')
  1311. }
  1312. }
  1313. indicatorDialog.visible = false
  1314. calculateRewardPenaltyTotal()
  1315. }
  1316. // 计算奖励和扣罚汇总
  1317. const calculateRewardPenaltyTotal = () => {
  1318. let rewardTotal = 0
  1319. let penaltyTotal = 0
  1320. if (!nonCadreForm.indicatorGroups) {
  1321. nonCadreForm.rewardAmount = 0
  1322. nonCadreForm.penaltyAmount = 0
  1323. return
  1324. }
  1325. nonCadreForm.indicatorGroups.forEach(group => {
  1326. if (group.items && Array.isArray(group.items)) {
  1327. group.items.forEach(item => {
  1328. const detailList = item.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList || []
  1329. detailList.forEach(detail => {
  1330. if (detail.rewardPunishmentType === 'PUNISHMENT') {
  1331. penaltyTotal += Number(detail.amount) || 0
  1332. } else {
  1333. rewardTotal += Number(detail.amount) || 0
  1334. }
  1335. })
  1336. })
  1337. }
  1338. })
  1339. nonCadreForm.rewardAmount = rewardTotal
  1340. nonCadreForm.penaltyAmount = penaltyTotal
  1341. }
  1342. // 删除奖励/惩罚明细
  1343. const removeRewardPunishmentDetail = (index) => {
  1344. indicatorDialog.form.personnelMonthlyAssessmentIndicatorRewardPunishmentDetailList.splice(index, 1)
  1345. updateTotal()
  1346. }
  1347. // 监听Tab切换
  1348. const handleTabChange = () => {
  1349. queryParams.pageNum = 1
  1350. getList()
  1351. }
  1352. onMounted(() => {
  1353. getList()
  1354. })
  1355. </script>
  1356. <style lang="less" scoped>
  1357. .app-container {
  1358. padding: 20px;
  1359. }
  1360. .tab-container {
  1361. margin-bottom: 20px;
  1362. text-align: left;
  1363. }
  1364. .filter-container {
  1365. // margin-bottom: 20px;
  1366. }
  1367. .search-form {
  1368. display: flex;
  1369. flex-wrap: wrap;
  1370. gap: 10px;
  1371. }
  1372. .operation-container {
  1373. margin-bottom: 20px;
  1374. display: flex;
  1375. justify-content: space-between;
  1376. align-items: center;
  1377. }
  1378. .left-buttons {
  1379. display: flex;
  1380. gap: 10px;
  1381. }
  1382. .right-buttons {
  1383. display: flex;
  1384. gap: 10px;
  1385. }
  1386. .form-container {
  1387. max-height: 70vh;
  1388. overflow-y: auto;
  1389. padding-right: 10px;
  1390. }
  1391. .el-divider {
  1392. margin: 20px 0;
  1393. }
  1394. :deep(.el-form-item__label) {
  1395. font-weight: 500;
  1396. }
  1397. /* 新增样式 */
  1398. .score-display {
  1399. display: flex;
  1400. align-items: center;
  1401. gap: 10px;
  1402. height: 32px;
  1403. line-height: 32px;
  1404. padding: 0 10px;
  1405. justify-content: center;
  1406. }
  1407. .score-label,
  1408. .result-label {
  1409. font-weight: bold;
  1410. font-size: 16px;
  1411. }
  1412. .score-value {
  1413. font-size: 20px;
  1414. font-weight: bold;
  1415. }
  1416. .result-value {
  1417. font-size: 20px;
  1418. font-weight: bold;
  1419. }
  1420. .text-danger {
  1421. color: #f56c6c;
  1422. }
  1423. .section-title {
  1424. font-size: 22px;
  1425. font-weight: bold;
  1426. margin: 25px 0 15px 0;
  1427. padding-left: 5px;
  1428. }
  1429. .section-title.blue {
  1430. color: #409eff;
  1431. // border-left: 4px solid #409eff;
  1432. }
  1433. .indicators-box {
  1434. border: 1px solid #dcdfe6;
  1435. border-radius: 4px;
  1436. padding: 15px;
  1437. }
  1438. .indicator-group {
  1439. padding: 10px 0;
  1440. }
  1441. .indicator-group-title {
  1442. font-size: 18px;
  1443. font-weight: bold;
  1444. color: #303133;
  1445. margin-bottom: 15px;
  1446. }
  1447. .indicator-item {
  1448. display: flex;
  1449. align-items: center;
  1450. gap: 10px;
  1451. margin-bottom: 10px;
  1452. padding: 10px;
  1453. background: #f5f7fa;
  1454. border-radius: 4px;
  1455. }
  1456. .indicator-name {
  1457. flex: 2;
  1458. font-size: 16px;
  1459. }
  1460. .indicator-value,
  1461. .indicator-count,
  1462. .indicator-qcDeptType,
  1463. .indicator-total {
  1464. flex: 1;
  1465. font-size: 16px;
  1466. text-align: center;
  1467. }
  1468. .indicator-actions {
  1469. display: flex;
  1470. gap: 5px;
  1471. }
  1472. .input-with-unit {
  1473. display: flex;
  1474. align-items: center;
  1475. gap: 10px;
  1476. }
  1477. .unit {
  1478. font-size: 14px;
  1479. color: #606266;
  1480. }
  1481. .exemption-container {
  1482. display: flex;
  1483. align-items: center;
  1484. gap: 15px;
  1485. }
  1486. .exemption-hint {
  1487. font-size: 12px;
  1488. }
  1489. .indicator-form {
  1490. padding: 20px 0;
  1491. }
  1492. .indicator-form :deep(.el-form-item__label) {
  1493. font-weight: 500;
  1494. color: #303133;
  1495. }
  1496. .detail-link {
  1497. color: #409eff;
  1498. cursor: pointer;
  1499. text-decoration: underline;
  1500. display: inline-block;
  1501. width: 100%;
  1502. padding: 5px 0;
  1503. }
  1504. .detail-link:hover {
  1505. color: #66b1ff;
  1506. }
  1507. .detail-content {
  1508. padding: 20px;
  1509. line-height: 1.8;
  1510. font-size: 14px;
  1511. color: #303133;
  1512. min-height: 100px;
  1513. max-height: 500px;
  1514. overflow-y: auto;
  1515. white-space: pre-wrap;
  1516. word-break: break-all;
  1517. }
  1518. </style>