archives.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. <template>
  2. <div>
  3. <div class="searchBox">
  4. <div class="searchItem">
  5. <span>时段:</span>
  6. <el-radio-group v-model="radio" size="mini" @change="resetDate">
  7. <el-radio label="sy" border>上月</el-radio>
  8. <el-radio label="by" border>本月</el-radio>
  9. <el-radio label="bjd" border>本季度</el-radio>
  10. <el-radio label="jn" border>今年</el-radio>
  11. </el-radio-group>
  12. </div>
  13. <div class="searchItem">
  14. <span>日期:</span>
  15. <el-date-picker
  16. v-model="date"
  17. size="mini"
  18. type="daterange"
  19. range-separator="至"
  20. start-placeholder="开始日期"
  21. end-placeholder="结束日期"
  22. value-format="yyyy-MM-dd"
  23. @change="
  24. () => {
  25. this.radio = '';
  26. requestData();
  27. }
  28. "
  29. />
  30. </div>
  31. <div class="searchItem">
  32. <el-button size="mini" type="primary" @click="requestData"
  33. >搜索</el-button
  34. >
  35. </div>
  36. </div>
  37. <el-tabs v-model="tabActive" @tab-click="reLayoutTable">
  38. <el-tab-pane label="部门" name="bm">
  39. <div id="chartDom" style="width: 100%; height: 350px"></div>
  40. <div class="exportBox">
  41. <el-button size="small" @click="exportExcel">导出</el-button>
  42. </div>
  43. <el-table
  44. ref="archivesTableRef-bm"
  45. :data="bmTableData"
  46. border
  47. :height="tableHeight"
  48. style="width: 100%"
  49. >
  50. <el-table-column fixed="left" label="名称" align="center">
  51. <template slot-scope="scope">
  52. <el-button type="text" size="small" @click="viewWp(scope.row)">{{
  53. scope.row.deptName
  54. }}</el-button>
  55. </template>
  56. </el-table-column>
  57. <el-table-column
  58. prop="ecTotalUser"
  59. label="应培训人数"
  60. sortable
  61. align="center"
  62. />
  63. <el-table-column
  64. prop="ecActualUser"
  65. label="实培训人数"
  66. sortable
  67. align="center"
  68. />
  69. <el-table-column prop="pxl" label="培训率" sortable align="center" >
  70. <template slot-scope="scope">{{scope.row.pxl}}%</template>
  71. </el-table-column>
  72. <el-table-column prop="rjxs" label="人均学时" sortable align="center" >
  73. <template slot-scope="scope">{{scope.row.rjxs}}</template>
  74. </el-table-column>
  75. <el-table-column prop="ecTotalMin" sortable label="累计学时" align="center" />
  76. <el-table-column
  77. prop="eeTotalUser"
  78. label="应考试人数"
  79. align="center"
  80. sortable
  81. />
  82. <el-table-column
  83. prop="eeActualUser"
  84. label="实考试人数"
  85. align="center"
  86. sortable
  87. />
  88. <el-table-column prop="ksl" label="考试率" sortable align="center">
  89. <template slot-scope="scope">{{scope.row.ksl}}%</template>
  90. </el-table-column>
  91. <el-table-column prop="kshgl" label="考试合格率" sortable align="center">
  92. <template slot-scope="scope">{{scope.row.kshgl}}%</template>
  93. </el-table-column>
  94. <el-table-column
  95. fixed="right"
  96. label="操作"
  97. width="100"
  98. align="center"
  99. >
  100. <template slot-scope="scope">
  101. <el-button
  102. type="text"
  103. size="small"
  104. @click="viewBmDetail(scope.row, date)"
  105. >详情</el-button
  106. >
  107. </template>
  108. </el-table-column>
  109. </el-table>
  110. </el-tab-pane>
  111. <el-tab-pane
  112. :label="`职工${selectDeptName ? '(仅查看' + selectDeptName + ')' : ''}`"
  113. name="zg"
  114. >
  115. <div class="exportBox" style="margin: 10px">
  116. <el-button size="small" @click="exportUserExcel">导出</el-button>
  117. </div>
  118. <el-table
  119. ref="archivesTableRef-zg"
  120. :data="zgTableData"
  121. border
  122. height="660"
  123. style="width: 100%"
  124. >
  125. <el-table-column
  126. type="index"
  127. label="序号"
  128. width="50"
  129. align="center"
  130. />
  131. <el-table-column prop="realName" label="姓名" align="center" />
  132. <el-table-column prop="deptName" label="所属部门" align="center" />
  133. <el-table-column label="培训统计" align="center">
  134. <el-table-column
  135. prop="courseNum"
  136. label="参与课程数"
  137. align="center"
  138. sortable
  139. />
  140. <el-table-column prop="totalMin" label="总学时" sortable align="center" />
  141. <el-table-column prop="wcl" label="完成率" sortable align="center">
  142. <template slot-scope="scope">{{scope.row.wcl}}%</template>
  143. </el-table-column>
  144. </el-table-column>
  145. <el-table-column label="考试记录" align="center">
  146. <el-table-column
  147. prop="tryCount"
  148. label="参与考试次数"
  149. align="center"
  150. sortable
  151. />
  152. <el-table-column prop="passed" label="及格次数" sortable align="center" />
  153. <el-table-column prop="jgl" label="及格率" sortable align="center">
  154. <template slot-scope="scope">{{scope.row.jgl}}%</template>
  155. </el-table-column>
  156. </el-table-column>
  157. <el-table-column
  158. fixed="right"
  159. label="考培档案"
  160. width="100"
  161. align="center"
  162. >
  163. <template slot-scope="scope">
  164. <el-button
  165. type="text"
  166. size="small"
  167. @click="viewZgDetail(scope.row, date)"
  168. >详情</el-button
  169. >
  170. </template>
  171. </el-table-column>
  172. </el-table>
  173. </el-tab-pane>
  174. </el-tabs>
  175. </div>
  176. </template>
  177. <script>
  178. import * as echarts from "echarts";
  179. import {
  180. getDepartTotal,
  181. getUserTotal,
  182. exportBmExcel,
  183. } from "@/api/archives/index";
  184. import { utils, writeFileXLSX } from 'xlsx';
  185. import { Message } from "element-ui";
  186. export default {
  187. data() {
  188. return {
  189. radio: "by",
  190. date: [],
  191. tabActive: "bm",
  192. bmTableData: [],
  193. zgTableData: [],
  194. selectDeptCode: "",
  195. selectDeptName: "",
  196. tableHeight: 280,
  197. };
  198. },
  199. mounted() {
  200. this.resetDate(this.radio);
  201. window.addEventListener('resize', () => {
  202. this.tableHeight = (window.innerHeight - 657) <= 280 ? 280 : window.innerHeight - 657
  203. })
  204. },
  205. methods: {
  206. // 获取部门数据
  207. getDepartTotal() {
  208. getDepartTotal({
  209. statDateL: this.date[0],
  210. statDateR: this.date[1],
  211. }).then((res) => {
  212. let xAxisData = [];
  213. let seriesData = [[], []];
  214. res.data.forEach((ele) => {
  215. ele.pxl = this.renderNumber(ele.ecActualUser, ele.ecTotalUser, true);
  216. ele.ksl = this.renderNumber(ele.eeActualUser, ele.eeTotalUser, true);
  217. ele.rjxs = this.renderNumber(ele.ecActualUser, ele.ecTotalMin, true);
  218. ele.kshgl = this.renderNumber(ele.eePassUser, ele.eeActualUser, true);
  219. xAxisData.push(ele.deptName);
  220. seriesData[0].push(
  221. this.renderNumber(ele.ecActualUser, ele.ecTotalUser, true)
  222. );
  223. seriesData[1].push(
  224. this.renderNumber(ele.eePassUser, ele.eeActualUser, true)
  225. );
  226. });
  227. this.bmTableData = res.data;
  228. this.initChart(xAxisData, seriesData);
  229. });
  230. },
  231. // 获取职工数据
  232. getUserTotal() {
  233. getUserTotal({
  234. statDateL: this.date[0],
  235. statDateR: this.date[1],
  236. deptCode: "",
  237. realName: "",
  238. }).then((res) => {
  239. if (!this.selectDeptCode) {
  240. res.data.forEach((ele) => {
  241. ele.wcl = this.renderNumber(ele.learnCourse, ele.courseNum, true);
  242. ele.jgl = this.renderNumber(ele.passed, ele.tryCount, true);
  243. });
  244. this.zgTableData = res.data;
  245. } else {
  246. let zgTableData = [];
  247. res.data.forEach((ele) => {
  248. if (ele.deptCode === this.selectDeptCode) {
  249. ele.wcl = this.renderNumber(ele.learnCourse, ele.courseNum, true);
  250. ele.jgl = this.renderNumber(ele.passed, ele.tryCount, true);
  251. zgTableData.push(ele);
  252. }
  253. });
  254. this.zgTableData = zgTableData;
  255. }
  256. });
  257. },
  258. // 过滤数字,规避 0/0 的情况
  259. renderNumber(num1, num2, renderChart) {
  260. if (!num1 && !num2) {
  261. return renderChart ? 0 : "0%";
  262. } else {
  263. if (!(num1 / num2)) {
  264. return renderChart ? 0 : "0%";
  265. } else {
  266. return renderChart ? parseInt((num1 / num2) * 100) : ((num1 / num2) * 100).toFixed(2) + "%";
  267. }
  268. }
  269. },
  270. // 初始化图表
  271. initChart(xAxisData, seriesData) {
  272. let myChart = echarts.init(document.getElementById("chartDom"));
  273. let option;
  274. option = {
  275. xAxis: {
  276. type: "category",
  277. data: xAxisData,
  278. },
  279. yAxis: {
  280. type: "value",
  281. },
  282. legend: {
  283. show: true,
  284. x: "center",
  285. y: "top",
  286. itemWidth: 10,
  287. itemHeight: 10,
  288. data: ["部门培训率", "部门考试合格率"],
  289. textStyle: {
  290. fontSize: 10, //字体大小
  291. color: "#000", //字体颜色
  292. },
  293. },
  294. tooltip: {
  295. show: true,
  296. trigger: "axis",
  297. formatter(data) {
  298. let str = `${data[0].name}: <br />`;
  299. data.forEach((ele) => {
  300. let circle = `<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;left:5px;background-color:${ele.color}"></span>`;
  301. str += circle + `${ele.seriesName}: ${ele.value}% <br />`;
  302. });
  303. return str;
  304. },
  305. },
  306. grid: {
  307. show: true,
  308. left: "35",
  309. top: "23",
  310. right: "30",
  311. bottom: "23",
  312. },
  313. series: [
  314. {
  315. name: "部门培训率",
  316. data: seriesData[0],
  317. type: "bar",
  318. itemStyle: {
  319. color: "rgb(86,114,196)",
  320. },
  321. barMaxWidth: "25",
  322. },
  323. {
  324. name: "部门考试合格率",
  325. data: seriesData[1],
  326. type: "bar",
  327. itemStyle: {
  328. color: "rgb(149,204,122)",
  329. },
  330. barMaxWidth: "25",
  331. },
  332. ],
  333. };
  334. option && myChart.setOption(option);
  335. },
  336. // 点击时段
  337. resetDate(radio) {
  338. const date = new Date();
  339. const days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  340. if (radio === "sy") {
  341. const month = date.getMonth() - 1 < 0 ? 11 : date.getMonth() - 1;
  342. const day = days[month];
  343. this.date = [
  344. new Date(new Date() - 3600 * 1000 * 24 * 30).formatDate("yyyy-MM") +
  345. `-01`,
  346. new Date(new Date() - 3600 * 1000 * 24 * 30).formatDate("yyyy-MM") +
  347. `-${day}`,
  348. ];
  349. } else if (radio === "by") {
  350. const month = date.getMonth() - 1 < 0 ? 11 : date.getMonth() - 1;
  351. const day = days[month + 1];
  352. this.date = [
  353. new Date().formatDate("yyyy-MM") +
  354. "-01",
  355. new Date().formatDate("yyyy-MM") + `-${day}`,
  356. ];
  357. } else if (radio === "bjd") {
  358. const monthList = [1,4,7,10]
  359. const nowDate = new Date()
  360. let minMonth = 1;
  361. for(let i=0;i<monthList.length;i++){
  362. if(nowDate.getMonth()+1 >= monthList[i]){
  363. minMonth = monthList[i]
  364. }
  365. }
  366. const maxMonth = minMonth + 2
  367. nowDate.setMonth(maxMonth)
  368. nowDate.setDate(0)
  369. const day = nowDate.getDate()
  370. this.date = [
  371. nowDate.formatDate("yyyy")+ `-${(minMonth < 10 ? "0" + minMonth : maxMonth)}-01`,
  372. nowDate.formatDate("yyyy") +
  373. `-${(maxMonth < 10 ? "0" + maxMonth : maxMonth)}-${day}`,
  374. ];
  375. } else if (radio === "jn") {
  376. this.date = [
  377. new Date().formatDate("yyyy") + "-01-01",
  378. new Date().formatDate("yyyy") + "-12-31",
  379. ];
  380. }
  381. this.requestData();
  382. },
  383. // 请求数据
  384. requestData() {
  385. if (this.date && this.date.length) {
  386. this.getDepartTotal();
  387. this.getUserTotal();
  388. } else {
  389. Message.error("日期区间不可为空");
  390. }
  391. },
  392. // 点击场站
  393. viewWp(row) {
  394. if (this.date && this.date.length) {
  395. this.selectDeptCode = row.deptCode;
  396. this.selectDeptName = row.deptName;
  397. this.tabActive = "zg";
  398. this.getUserTotal();
  399. } else {
  400. Message.error("日期区间不可为空");
  401. }
  402. },
  403. // 查看部门详情
  404. viewBmDetail(params, date) {
  405. params._date = date;
  406. localStorage.setItem("examLocalStorage", JSON.stringify(params));
  407. this.$router.push({ name: "TrainDetailBm", params });
  408. },
  409. // 查看职工详情
  410. viewZgDetail(params, date) {
  411. params._date = date;
  412. localStorage.setItem("examLocalStorage", JSON.stringify(params));
  413. this.$router.push({ name: "TrainDetailZg", params });
  414. },
  415. // 导出 excel
  416. exportExcel() {
  417. if (this.date && this.date.length) {
  418. // exportBmExcel({
  419. // statDateL: this.date[0],
  420. // statDateR: this.date[1],
  421. // });
  422. getDepartTotal({
  423. statDateL: this.date[0],
  424. statDateR: this.date[1],
  425. }).then((res) => {
  426. res.data.forEach((ele) => {
  427. ele['部门编码'] = ele.deptCode
  428. ele['部门名称'] = ele.deptName
  429. ele['应培训人数'] = ele.ecTotalUser
  430. ele['实培训人数'] = ele.ecActualUser
  431. ele['培训率'] = this.renderNumber(ele.ecActualUser, ele.ecTotalUser)
  432. ele['人均学时'] = this.renderNumber(ele.ecActualUser, ele.ecTotalMin, true);
  433. ele['累积学时'] = ele.ecTotalMin
  434. ele['应考试人数'] = ele.eeTotalUser
  435. ele['实考试人数'] = ele.eeActualUser
  436. ele['考试率'] = this.renderNumber(ele.eeActualUser, ele.eeTotalUser);
  437. ele['考试合格率'] = this.renderNumber(ele.eePassUser, ele.eeActualUser);
  438. delete ele.deptName
  439. delete ele.deptCode
  440. delete ele.ecTotalUser
  441. delete ele.ecActualUser
  442. delete ele.ecTotalMin
  443. delete ele.ecPassUser
  444. delete ele.eeActualUser
  445. delete ele.eePassUser
  446. delete ele.eeTotalUser
  447. });
  448. const ws = utils.json_to_sheet(res.data);
  449. const wb = utils.book_new();
  450. utils.book_append_sheet(wb, ws, "sheet1");
  451. writeFileXLSX(wb, "部门统计-导出.xlsx");
  452. });
  453. } else {
  454. Message.error("搜索条件日期区间不可为空");
  455. }
  456. },
  457. // 导出 excel
  458. exportUserExcel() {
  459. if (this.date && this.date.length) {
  460. // exportBmExcel({
  461. // statDateL: this.date[0],
  462. // statDateR: this.date[1],
  463. // });
  464. getUserTotal({
  465. statDateL: this.date[0],
  466. statDateR: this.date[1],
  467. deptCode: "",
  468. realName: "",
  469. }).then((res) => {
  470. let zgTableData = [];
  471. if (!this.selectDeptCode) {
  472. res.data.forEach((ele) => {
  473. ele['姓名'] = ele.realName
  474. ele['所属部门'] = ele.deptName
  475. ele['参与课程数'] = ele.courseNum
  476. ele['总学时'] = ele.totalMin
  477. ele['完成率'] = this.renderNumber(ele.learnCourse, ele.courseNum);
  478. ele['参与考试次数'] = ele.tryCount
  479. ele['及格次数'] = ele.passed
  480. ele['及格率'] = this.renderNumber(ele.passed, ele.tryCount);
  481. delete ele.courseNum
  482. delete ele.deptCode
  483. delete ele.deptName
  484. delete ele.learnCourse
  485. delete ele.passed
  486. delete ele.realName
  487. delete ele.totalMin
  488. delete ele.tryCount
  489. delete ele.userName
  490. });
  491. zgTableData = res.data;
  492. } else {
  493. res.data.forEach((ele) => {
  494. if (ele.deptCode === this.selectDeptCode) {
  495. ele['姓名'] = ele.realName
  496. ele['所属部门'] = ele.deptName
  497. ele['参与课程数'] = ele.courseNum
  498. ele['总学时'] = ele.totalMin
  499. ele['完成率'] = this.renderNumber(ele.learnCourse, ele.courseNum);
  500. ele['参与考试次数'] = ele.tryCount
  501. ele['及格次数'] = ele.passed
  502. ele['及格率'] = this.renderNumber(ele.passed, ele.tryCount);
  503. delete ele.courseNum
  504. delete ele.deptCode
  505. delete ele.deptName
  506. delete ele.learnCourse
  507. delete ele.passed
  508. delete ele.realName
  509. delete ele.totalMin
  510. delete ele.tryCount
  511. delete ele.userName
  512. zgTableData.push(ele);
  513. }
  514. });
  515. }
  516. const ws = utils.json_to_sheet(zgTableData);
  517. const wb = utils.book_new();
  518. utils.book_append_sheet(wb, ws, "sheet1");
  519. writeFileXLSX(wb, "职工统计-导出.xlsx");
  520. });
  521. } else {
  522. Message.error("搜索条件日期区间不可为空");
  523. }
  524. },
  525. // 重置表格布局
  526. reLayoutTable() {
  527. if (this.selectDeptCode || this.selectDeptName) {
  528. if (this.date && this.date.length) {
  529. this.selectDeptCode = "";
  530. this.selectDeptName = "";
  531. this.getUserTotal();
  532. } else {
  533. Message.error("日期区间不可为空");
  534. }
  535. }
  536. this.$nextTick(() => {
  537. this.$refs[`archivesTableRef-${this.tabActive}`].doLayout();
  538. });
  539. },
  540. },
  541. };
  542. </script>
  543. <style lang="scss" scoped>
  544. .exportBox {
  545. width: calc(100% - 20px);
  546. margin: 20px 10px;
  547. display: flex;
  548. justify-content: flex-end;
  549. align-items: center;
  550. }
  551. .searchBox {
  552. width: 100%;
  553. display: flex;
  554. justify-content: flex-start;
  555. align-items: center;
  556. .searchItem {
  557. display: flex;
  558. justify-content: flex-start;
  559. align-items: center;
  560. margin-right: 20px;
  561. font-size: 14px;
  562. .el-radio-group {
  563. display: flex;
  564. justify-content: flex-start;
  565. align-items: center;
  566. label.el-radio {
  567. margin: 2px 5px 2px 0;
  568. &:last-child {
  569. margin-right: 0;
  570. }
  571. }
  572. }
  573. &:first-child {
  574. margin-left: 0;
  575. }
  576. }
  577. }
  578. </style>