PerReviewDetail.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. <template>
  2. <el-container style="height: 100%;">
  3. <el-main v-if="reviewInfo">
  4. <el-card style="height: 10%;margin-bottom: 20px;">
  5. <el-row type="flex" justify="space-between" align="center">
  6. <el-col>
  7. <WaStstistics
  8. title="考核表"
  9. :value="reviewInfo.title"
  10. />
  11. </el-col>
  12. <el-col>
  13. <WaStstistics
  14. title="周期种类"
  15. :value="cycleMap[reviewInfo.cycleType] || '--'"
  16. />
  17. </el-col>
  18. <el-col>
  19. <WaStstistics
  20. title="考核时间"
  21. :value="timeScope"
  22. />
  23. </el-col>
  24. <el-col>
  25. <WaStstistics
  26. title="考核状态"
  27. :value="statusMap[reviewInfo.status] || '--'"
  28. />
  29. </el-col>
  30. <el-col>
  31. <WaStstistics
  32. title="评分"
  33. :value="reviewInfo.score === null ? '--' : reviewInfo.score"
  34. />
  35. </el-col>
  36. <el-col>
  37. <WaStstistics
  38. title="考核中的指标"
  39. :value="reviewInfo.indicators.length > 0 ? (reviewInfo.indicators.length - indicatorEnd) : '--'"
  40. />
  41. </el-col>
  42. <el-col>
  43. <WaStstistics
  44. title="考核完成的指标"
  45. :value="indicatorEnd"
  46. />
  47. </el-col>
  48. <el-col
  49. v-if="reviewInfo.interviews && reviewInfo.interviews.length > 0"
  50. >
  51. <el-dropdown trigger="click">
  52. <el-button
  53. type="text"
  54. >
  55. 面谈记录
  56. </el-button>
  57. <el-dropdown-menu>
  58. <el-dropdown-item
  59. v-for="interview in reviewInfo.interviews"
  60. :key="interview.interviewId"
  61. >
  62. <span @click="openInterview(interview.interviewId)">
  63. {{ interview.createTime }}
  64. </span>
  65. </el-dropdown-item>
  66. </el-dropdown-menu>
  67. </el-dropdown>
  68. </el-col>
  69. </el-row>
  70. </el-card>
  71. <el-card style="height: calc(90% - 20px); position: relative; " >
  72. <Seal
  73. v-if="reviewInfo.levelName"
  74. :text="reviewInfo.levelName"
  75. :size="100"
  76. font-size="24px"
  77. :top="10"
  78. :left="20"
  79. :rotate="-30"
  80. :z-index="100"
  81. />
  82. <div class="flex-box-end" style="margin-bottom: 10px;">
  83. <el-switch
  84. v-model="showNode"
  85. active-text="显示考核节点"
  86. inactive-text="隐藏考核节点"
  87. />
  88. </div>
  89. <el-table
  90. :data="indicators"
  91. >
  92. <el-table-column
  93. prop="title"
  94. label="指标"
  95. fixed="left"
  96. show-overflow-tooltip
  97. align="center"
  98. />
  99. <el-table-column
  100. prop="content"
  101. label="规则"
  102. show-overflow-tooltip
  103. align="center"
  104. />
  105. <el-table-column
  106. prop="target"
  107. label="目标"
  108. align="center"
  109. >
  110. <template slot-scope="scope">
  111. {{ scope.row.target === null ? '--' : (scope.row.unit ? `${scope.row.target} ${scope.row.unit}` : scope.row.target) }}
  112. </template>
  113. </el-table-column>
  114. <el-table-column
  115. prop="result"
  116. label="结果"
  117. align="center"
  118. >
  119. <template slot-scope="scope">
  120. {{ scope.row.result === null ? '--' : (scope.row.unit ? `${scope.row.result} ${scope.row.unit}` : scope.row.result) }}
  121. </template>
  122. </el-table-column>
  123. <el-table-column
  124. prop="weight"
  125. label="权重"
  126. align="center"
  127. >
  128. <template slot-scope="scope">
  129. {{ scope.row.weight ? `${scope.row.weight} %` : '--' }}
  130. </template>
  131. </el-table-column>
  132. <el-table-column
  133. prop="businessStatus"
  134. label="考核状态"
  135. align="center"
  136. >
  137. <template slot-scope="scope">
  138. <el-link
  139. :type="scope.row.businessStatus === 'end' ? 'primary' : 'warning'"
  140. >
  141. {{ indicatorStatusMap[scope.row.businessStatus] || '--' }}
  142. </el-link>
  143. </template>
  144. </el-table-column>
  145. <el-table-column
  146. prop="score"
  147. label="得分"
  148. align="center"
  149. />
  150. <el-table-column
  151. v-if="showNode"
  152. prop="targetConfirms"
  153. label="目标确认"
  154. align="center"
  155. >
  156. <template slot-scope="scope">
  157. <el-link
  158. v-if="!scope.row.targetConfirms.enable"
  159. type="info"
  160. >
  161. 禁用
  162. </el-link>
  163. <el-link
  164. v-else-if="scope.row.targetConfirms.tasks.length <= 0"
  165. type="info"
  166. >
  167. 未开始
  168. </el-link>
  169. <template v-else >
  170. <div @click="openTasks(scope.row.targetConfirms.tasks,'目标确认任务',scope.row.unit)">
  171. <el-link
  172. v-if="scope.row.targetConfirms.finishCount > 0"
  173. type="primary"
  174. style="margin-right: 5px;"
  175. icon="el-icon-check"
  176. >
  177. {{ scope.row.targetConfirms.finishCount }}
  178. </el-link>
  179. <el-link
  180. v-if="scope.row.targetConfirms.runningCount > 0"
  181. type="warning"
  182. icon="el-icon-warning"
  183. >
  184. {{ scope.row.targetConfirms.runningCount }}
  185. </el-link>
  186. </div>
  187. </template>
  188. </template>
  189. </el-table-column>
  190. <el-table-column
  191. v-if="showNode"
  192. prop="resultInput"
  193. label="结果录入"
  194. >
  195. <template slot-scope="scope">
  196. <el-link
  197. v-if="!scope.row.resultInput.enable"
  198. type="info"
  199. >
  200. 禁用
  201. </el-link>
  202. <el-link
  203. v-else-if="scope.row.resultInput.tasks.length <= 0"
  204. type="info"
  205. >
  206. 未开始
  207. </el-link>
  208. <template v-else >
  209. <div @click="openTasks(scope.row.resultInput.tasks,'结果录入任务',scope.row.unit)">
  210. <el-link
  211. v-if="scope.row.resultInput.finishCount > 0"
  212. type="primary"
  213. style="margin-right: 5px;"
  214. icon="el-icon-check"
  215. >
  216. {{ scope.row.resultInput.finishCount }}
  217. </el-link>
  218. <el-link
  219. v-if="scope.row.resultInput.runningCount > 0"
  220. type="warning"
  221. icon="el-icon-warning"
  222. >
  223. {{ scope.row.resultInput.runningCount }}
  224. </el-link>
  225. </div>
  226. </template>
  227. </template>
  228. </el-table-column>
  229. <el-table-column
  230. v-if="showNode"
  231. prop="scoreSelf"
  232. label="自评"
  233. >
  234. <template slot-scope="scope">
  235. <el-link
  236. v-if="!scope.row.scoreSelf.enable"
  237. type="info"
  238. >
  239. 禁用
  240. </el-link>
  241. <el-link
  242. v-else-if="scope.row.scoreSelf.tasks.length <= 0"
  243. type="info"
  244. >
  245. 未开始
  246. </el-link>
  247. <template v-else >
  248. <div @click="openTasks(scope.row.scoreSelf.tasks,'自评任务',scope.row.unit)">
  249. <el-link
  250. v-if="scope.row.scoreSelf.finishCount > 0"
  251. type="primary"
  252. style="margin-right: 5px;"
  253. icon="el-icon-check"
  254. >
  255. {{ scope.row.scoreSelf.finishCount }}
  256. </el-link>
  257. <el-link
  258. v-if="scope.row.scoreSelf.runningCount > 0"
  259. type="warning"
  260. icon="el-icon-warning"
  261. >
  262. {{ scope.row.scoreSelf.runningCount }}
  263. </el-link>
  264. </div>
  265. </template>
  266. </template>
  267. </el-table-column>
  268. <el-table-column
  269. v-if="showNode"
  270. prop="scoreEachOther"
  271. label="互评"
  272. >
  273. <template slot-scope="scope">
  274. <el-link
  275. v-if="!scope.row.scoreEachOther.enable"
  276. type="info"
  277. >
  278. 禁用
  279. </el-link>
  280. <el-link
  281. v-else-if="scope.row.scoreEachOther.tasks.length <= 0"
  282. type="info"
  283. >
  284. 未开始
  285. </el-link>
  286. <template v-else >
  287. <div @click="openTasks(scope.row.scoreEachOther.tasks,'互评任务',scope.row.unit)">
  288. <el-link
  289. v-if="scope.row.scoreEachOther.finishCount > 0"
  290. type="primary"
  291. style="margin-right: 5px;"
  292. icon="el-icon-check"
  293. >
  294. {{ scope.row.scoreEachOther.finishCount }}
  295. </el-link>
  296. <el-link
  297. v-if="scope.row.scoreEachOther.runningCount > 0"
  298. type="warning"
  299. icon="el-icon-warning"
  300. >
  301. {{ scope.row.scoreEachOther.runningCount }}
  302. </el-link>
  303. </div>
  304. </template>
  305. </template>
  306. </el-table-column>
  307. <el-table-column
  308. v-if="showNode"
  309. prop="scores"
  310. label="评分"
  311. >
  312. <template slot-scope="scope">
  313. <el-link
  314. v-if="!scope.row.scores.enable"
  315. type="info"
  316. >
  317. 禁用
  318. </el-link>
  319. <el-link
  320. v-else-if="scope.row.scores.tasks.length <= 0"
  321. type="info"
  322. >
  323. 未开始
  324. </el-link>
  325. <template v-else >
  326. <div @click="openTasks(scope.row.scores.tasks,'评分任务',scope.row.unit)">
  327. <el-link
  328. v-if="scope.row.scores.finishCount > 0"
  329. type="primary"
  330. style="margin-right: 5px;"
  331. icon="el-icon-check"
  332. >
  333. {{ scope.row.scores.finishCount }}
  334. </el-link>
  335. <el-link
  336. v-if="scope.row.scores.runningCount > 0"
  337. type="warning"
  338. icon="el-icon-warning"
  339. >
  340. {{ scope.row.scores.runningCount }}
  341. </el-link>
  342. </div>
  343. </template>
  344. </template>
  345. </el-table-column>
  346. <el-table-column
  347. v-if="showNode"
  348. prop="reviews"
  349. label="审批"
  350. >
  351. <template slot-scope="scope">
  352. <el-link
  353. v-if="!scope.row.reviews.enable"
  354. type="info"
  355. >
  356. 禁用
  357. </el-link>
  358. <el-link
  359. v-else-if="scope.row.reviews.tasks.length <= 0"
  360. type="info"
  361. >
  362. 未开始
  363. </el-link>
  364. <template v-else >
  365. <div @click="openTasks(scope.row.reviews.tasks,'审批任务',scope.row.unit)">
  366. <el-link
  367. v-if="scope.row.reviews.finishCount > 0"
  368. type="primary"
  369. style="margin-right: 5px;"
  370. icon="el-icon-check"
  371. >
  372. {{ scope.row.reviews.finishCount }}
  373. </el-link>
  374. <el-link
  375. v-if="scope.row.reviews.runningCount > 0"
  376. type="warning"
  377. icon="el-icon-warning"
  378. >
  379. {{ scope.row.reviews.runningCount }}
  380. </el-link>
  381. </div>
  382. </template>
  383. </template>
  384. </el-table-column>
  385. </el-table>
  386. </el-card>
  387. </el-main>
  388. <el-dialog
  389. v-if="tasksInfo.title"
  390. :visible.sync="showTasks"
  391. :title="tasksInfo.title"
  392. center
  393. append-to-body
  394. >
  395. <div style="max-height: 600px; overflow-y: auto;">
  396. <el-card
  397. v-if="tasksInfo.tasks"
  398. v-for="task in tasksInfo.tasks"
  399. :key="task.taskId"
  400. style="margin-bottom: 20px;"
  401. >
  402. <el-descriptions
  403. border
  404. :column="3"
  405. :title="task.assigneeName"
  406. size="mini"
  407. direction="vertical"
  408. >
  409. <el-descriptions-item label="任务状态">{{ task.state === 'completed' ? '已处理' : '待处理' }}</el-descriptions-item>
  410. <el-descriptions-item
  411. v-if="task.score !== null"
  412. label="评分"
  413. >
  414. {{ task.score }}
  415. </el-descriptions-item>
  416. <el-descriptions-item
  417. v-if="task.result !== null"
  418. label="结果"
  419. >
  420. {{ tasksInfo.unit ? `${task.result} ${tasksInfo.unit}` : task.result }}
  421. </el-descriptions-item>
  422. <el-descriptions-item
  423. label="评论"
  424. >
  425. {{ task.comment }}
  426. </el-descriptions-item>
  427. </el-descriptions>
  428. </el-card>
  429. </div>
  430. </el-dialog>
  431. </el-container>
  432. </template>
  433. <script>
  434. import WaStstistics from "./tool/WaStstistics.vue";
  435. import moment from "moment";
  436. import Template from "../../examine/components/Template.vue";
  437. import Seal from "./tool/Seal.vue";
  438. import PerInterview from "./PerInterview.vue";
  439. export default {
  440. name: "PerReviewDetail",
  441. components: {PerInterview, Seal, Template, WaStstistics},
  442. props: {
  443. reviewId:{
  444. type: Number,
  445. required: true
  446. }
  447. },
  448. data(){
  449. return {
  450. userInfo:this.$userInfo(),
  451. loading:false,
  452. reviewInfo:null,
  453. cycleMap:{
  454. 0:'未定义',
  455. 1:'年度',
  456. 2:'半年度',
  457. 3:'季度',
  458. 4:'月度',
  459. },
  460. statusMap:{
  461. 0:'考核中',
  462. 1:'已结束',
  463. 2:'面谈',
  464. },
  465. indicatorStatusMap:{
  466. start:'进入考核',
  467. target_confirm:'目标确认中',
  468. result_input:'结果录入中',
  469. score_self:'自评中',
  470. score_each_other:'互评中',
  471. score:'评分中',
  472. review:'审批中',
  473. cc:'抄送',
  474. end:'已结束',
  475. },
  476. showNode:false,
  477. showTasks:false,
  478. tasks:[],
  479. tasksInfo:{
  480. title:'',
  481. unit:'',
  482. tasks:[]
  483. },
  484. }
  485. },
  486. computed:{
  487. timeScope(){
  488. if (!this.reviewInfo || !this.reviewInfo.startTime || !this.reviewInfo.endTime) return '--';
  489. return moment(this.reviewInfo.startTime).format('YY/MM/DD') + ' ~ ' + moment(this.reviewInfo.endTime).format('YY/MM/DD');
  490. },
  491. indicatorEnd(){
  492. if (!this.reviewInfo || this.reviewInfo.indicators.length <= 0) return '--';
  493. return this.reviewInfo.indicators.filter(indicator => indicator.businessStatus === 'end').length;
  494. },
  495. indicators(){
  496. return !this.reviewInfo ? [] : this.reviewInfo.indicators.map(item => {
  497. const indicator = {
  498. reviewIndicatorId:item.reviewIndicatorId,
  499. reviewTitle:item.reviewTitle,
  500. title:item.title,
  501. content:item.content,
  502. target:item.target,
  503. result:item.result,
  504. score:item.score,
  505. unit:item.unit,
  506. weight:item.weight,
  507. businessStatus:item.businessStatus,
  508. targetConfirms:null,
  509. resultInput:null,
  510. scoreSelf:null,
  511. scoreEachOther:null,
  512. scores:null,
  513. reviews:null,
  514. cc:null,
  515. };
  516. item.flow.nodes.forEach(node => {
  517. switch (node.type){
  518. case 'targetConfirms':
  519. const targetConfirms = {
  520. enable:node.enable,
  521. tasks:[],
  522. runningCount:0,
  523. finishCount:0,
  524. }
  525. if (node.children && node.children.length > 0){
  526. node.children.forEach(child => {
  527. if (child.tasks && child.tasks.length > 0) {
  528. child.tasks.forEach(task => {
  529. targetConfirms.tasks.push(task);
  530. if (task.state === 'completed'){
  531. targetConfirms.finishCount ++;
  532. }else {
  533. targetConfirms.runningCount ++;
  534. }
  535. })
  536. }
  537. })
  538. }
  539. indicator.targetConfirms = targetConfirms;
  540. break;
  541. case 'resultInput':
  542. const resultInput = {
  543. enable:node.enable,
  544. tasks:[],
  545. runningCount:0,
  546. finishCount:0,
  547. }
  548. if (node.tasks && node.tasks.length > 0){
  549. node.tasks.forEach(task => {
  550. resultInput.tasks.push(task);
  551. if (task.state === 'completed'){
  552. resultInput.finishCount ++;
  553. }else {
  554. resultInput.runningCount ++;
  555. }
  556. })
  557. }
  558. indicator.resultInput = resultInput;
  559. break;
  560. case 'scoreSelf':
  561. const scoreSelf = {
  562. enable:node.enable,
  563. tasks:[],
  564. runningCount:0,
  565. finishCount:0,
  566. }
  567. if (node.tasks && node.tasks.length > 0){
  568. node.tasks.forEach(task => {
  569. scoreSelf.tasks.push(task);
  570. if (task.state === 'completed'){
  571. scoreSelf.finishCount ++;
  572. }else {
  573. scoreSelf.runningCount ++;
  574. }
  575. })
  576. }
  577. indicator.scoreSelf = scoreSelf;
  578. break;
  579. case 'scoreEachOther':
  580. const scoreEachOther = {
  581. enable:node.enable,
  582. tasks:[],
  583. runningCount:0,
  584. finishCount:0,
  585. }
  586. if (node.tasks && node.tasks.length > 0){
  587. node.tasks.forEach(task => {
  588. scoreEachOther.tasks.push(task);
  589. if (task.state === 'completed'){
  590. scoreEachOther.finishCount ++;
  591. }else {
  592. scoreEachOther.runningCount ++;
  593. }
  594. })
  595. }
  596. indicator.scoreEachOther = scoreEachOther;
  597. break;
  598. case 'scores':
  599. const scores = {
  600. enable:node.enable,
  601. tasks:[],
  602. runningCount:0,
  603. finishCount:0,
  604. }
  605. if (node.children && node.children.length > 0){
  606. node.children.forEach(child => {
  607. if (child.tasks && child.tasks.length > 0) {
  608. child.tasks.forEach(task => {
  609. scores.tasks.push(task);
  610. if (task.state === 'completed'){
  611. scores.finishCount ++;
  612. }else {
  613. scores.runningCount ++;
  614. }
  615. })
  616. }
  617. })
  618. }
  619. indicator.scores = scores;
  620. break;
  621. case 'reviews':
  622. const reviews = {
  623. enable:node.enable,
  624. tasks:[],
  625. runningCount:0,
  626. finishCount:0,
  627. }
  628. if (node.children && node.children.length > 0){
  629. node.children.forEach(child => {
  630. if (child.tasks && child.tasks.length > 0) {
  631. child.tasks.forEach(task => {
  632. reviews.tasks.push(task);
  633. if (task.state === 'completed'){
  634. reviews.finishCount ++;
  635. }else {
  636. reviews.runningCount ++;
  637. }
  638. })
  639. }
  640. })
  641. }
  642. indicator.reviews = reviews;
  643. break;
  644. case 'cc':
  645. const cc = {
  646. enable:node.enable,
  647. tasks:[],
  648. runningCount:0,
  649. finishCount:0,
  650. }
  651. indicator.cc = cc;
  652. break;
  653. }
  654. });
  655. return indicator;
  656. });
  657. }
  658. },
  659. watch:{
  660. reviewId(v){
  661. if (v) this.initData();
  662. }
  663. },
  664. methods:{
  665. initData(){
  666. if (this.loading || !this.reviewId) return;
  667. this.loading = true;
  668. this.showNode = false;
  669. this.reviewInfo = null;
  670. Promise.all([
  671. this.$axiosUser('get',`/performance/statistics/review/info/${this.userInfo.site_id}/${this.reviewId}`)
  672. ])
  673. .then(([reviewRes]) =>{
  674. if (reviewRes.data.code !== 1) throw new Error(reviewRes.data.message);
  675. this.reviewInfo = reviewRes.data.data || null;
  676. })
  677. .catch(err => {
  678. console.warn(err);
  679. })
  680. .finally(() => {
  681. this.loading = false;
  682. })
  683. },
  684. openTasks(tasks,title,unit){
  685. if (!tasks || tasks.length <= 0) return;
  686. this.tasksInfo.tasks = tasks;
  687. this.tasksInfo.title = title || '任务信息';
  688. this.tasksInfo.unit = unit || '';
  689. this.showTasks = true;
  690. },
  691. openInterview(interviewId){
  692. if (!interviewId || !this.reviewInfo) return;
  693. window.open(`#/per/interview/${this.reviewId}/${interviewId}`);
  694. },
  695. },
  696. mounted() {
  697. this.initData();
  698. }
  699. }
  700. </script>
  701. <style scoped lang="scss">
  702. </style>