index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. <template>
  2. <basic-container class="supervision">
  3. <div class="supervision-info">
  4. <!-- <div class="supervision-item">
  5. <div class="txt">全部人员审核总数:{{ supervisionInfo.allOperatorSubtotal }}</div>
  6. <div class="progress">
  7. <el-progress type="dashboard" :percentage="supervisionInfo.allOperatorSupRate" />
  8. <span>全部人员审核百分比</span>
  9. </div>
  10. </div>
  11. <div class="supervision-item">
  12. <div class="txt">当前人员审核总数:{{ supervisionInfo.curOperatorSubtotal }}</div>
  13. <div class="progress">
  14. <el-progress type="dashboard" :percentage="supervisionInfo.curOperatorSupRate" />
  15. <span>当前人员审核百分比</span>
  16. </div>
  17. </div>
  18. <div class="supervision-item">
  19. <div class="txt">上季度积分包审核总数:{{ supervisionInfo.total }}</div>
  20. </div> -->
  21. </div>
  22. <avue-crud
  23. ref="crud"
  24. :page.sync="page"
  25. :data="tableData"
  26. :table-loading="tableLoading"
  27. :option="tableOption"
  28. :search="searchForm"
  29. @search-change="searchChange"
  30. @refresh-change="refreshChange"
  31. @search-reset="searchReset"
  32. @size-change="sizeChange"
  33. @current-change="currentChange"
  34. >
  35. <template slot="taskPeriodSearch">
  36. <ElQuarterPicker placeholder="请选择" :value="monthrange" v-model="monthrange" @change="monthrangeChange" class="w100" />
  37. </template>
  38. <template slot="menuRight">
  39. <div class="right-content">
  40. <div class="r-btn" v-if="userInfo?.roles?.includes(53)">
  41. <template v-if="exportDetailErrorMsg">
  42. <el-tooltip effect="dark" :content="exportDetailErrorMsg" placement="top-start">
  43. <el-button class="error-message" disabled>生成失败<i class="el-icon-warning-outline el-icon--right"></i></el-button>
  44. </el-tooltip>
  45. </template>
  46. <el-button v-if="exportDetailShow" :disabled="exportDetailDisabled" size="small" @click="downInfo">{{ exportBtnText }}</el-button>
  47. <el-button type="primary" size="small" @click="exportTask">监督任务数据导出</el-button>
  48. </div>
  49. <el-checkbox v-model="supervision" @change="checkBoxChange">只看待监督</el-checkbox>
  50. </div>
  51. </template>
  52. <!-- 操作栏 -->
  53. <template slot="menu" slot-scope="scope">
  54. <el-button v-if="scope.row.toCheck" type="text" size="small" icon="el-icon-edit-outline" @click="taskMonitorFn(scope.row, 0)">任务监督 </el-button>
  55. <el-button v-if="scope.row.checked" type="text" size="small" icon="el-icon-tickets" @click="taskMonitorFn(scope.row, 1)">监督详情 </el-button>
  56. <el-button v-if="scope.row.checked && IS_JDY" type="text" size="small" icon="el-icon-download" @click="exportFn(scope.row, 1)">监督详情 </el-button>
  57. <!-- <el-button v-if="scope.row.taskSupStat && getPercentage(scope.row) > 10" type="text" size="small" icon="el-icon-edit-outline" @click="monitorFn(scope.row)"
  58. >监督
  59. </el-button> -->
  60. </template>
  61. <!-- 进度 -->
  62. <template slot="progress" slot-scope="scope">
  63. <div>
  64. <el-progress :percentage="getPercentage(scope.row, 0)"></el-progress>
  65. </div>
  66. </template>
  67. <!-- 角色 -->
  68. <template slot="roleNumber" slot-scope="scope">
  69. <div class="role-number">
  70. <div style="color: #67c23a"><span>生产企业财务监督:</span>{{ scope.row.taskSupStat.mahFinaSupTotal }}</div>
  71. <div style="color: #e6a23c"><span>生产企业销售监督:</span>{{ scope.row.taskSupStat.mahSalesSupTotal }}</div>
  72. <div style="color: #f56c6c"><span>营销中心财务监督:</span>{{ scope.row.taskSupStat.optFinaSupTotal }}</div>
  73. <div style="color: #409eff"><span>营销中心合规监督:</span>{{ scope.row.taskSupStat.optLawSupTotal }}</div>
  74. </div>
  75. </template>
  76. <!-- 进度 -->
  77. <template slot="roleProgress" slot-scope="scope">
  78. <div class="role-progress">
  79. <div><el-progress :percentage="getPercentage(scope.row, 1)" color="#67C23A" text-color="#67C23A"></el-progress></div>
  80. <div><el-progress :percentage="getPercentage(scope.row, 2)" color="#E6A23C" text-color="#E6A23C"></el-progress></div>
  81. <div><el-progress :percentage="getPercentage(scope.row, 3)" color="#F56C6C" text-color="#F56C6C"></el-progress></div>
  82. <div><el-progress :percentage="getPercentage(scope.row, 4)" color="#409EFF" text-color="#409EFF"></el-progress></div>
  83. </div>
  84. </template>
  85. </avue-crud>
  86. <monitorDialog ref="monitorRef" @success="success" />
  87. <!-- 导出弹窗 -->
  88. <el-dialog title="导出报告" :close-on-click-modal="false" :visible.sync="exportDialog" width="50%" center :before-close="exportDialogHandleClose">
  89. <el-form label-width="100px">
  90. <el-form-item label="导出周期">
  91. <ElQuarterPicker placeholder="请选择" v-model="exportMonthrange" @change="exportMonthrangeChange" class="w100" />
  92. </el-form-item>
  93. </el-form>
  94. <span slot="footer" class="dialog-footer">
  95. <el-button @click="exportDialogHandleClose">取 消</el-button>
  96. <el-button type="primary" @click="exportBtn">确 定</el-button>
  97. </span>
  98. </el-dialog>
  99. <el-dialog title="提示" :visible.sync="exportDialogVisible" width="30%">
  100. <el-form ref="exportFormRef" :model="exportForm" label-width="80px">
  101. <el-form-item label="选择月份">
  102. <el-date-picker value-format="yyyy-MM-dd" v-model="exportForm.startDate" type="month" placeholder="选择月"> </el-date-picker>
  103. </el-form-item>
  104. </el-form>
  105. <span slot="footer" class="dialog-footer">
  106. <el-button @click="handleExportDialogClose">取 消</el-button>
  107. <el-button type="primary" @click="exportDialogFn">确 定</el-button>
  108. </span>
  109. </el-dialog>
  110. </basic-container>
  111. </template>
  112. <script>
  113. import { fetchList, supervisionPkgStatApi, reportExportApi, checkHisExport } from '@/api/riskEventAudit.js';
  114. import ElQuarterPicker from '@/components/ElQuarterPicker/index';
  115. import { tableOption } from '@/const/crud/riskEventAudit.js';
  116. import { getAreaTreeApi } from '@/api/areaTree';
  117. import monitorDialog from './components/monitorDialog.vue';
  118. import { mapGetters } from 'vuex';
  119. import dayjs from 'dayjs';
  120. import { getExportResult } from '@/api/admin/user';
  121. export default {
  122. components: { monitorDialog, ElQuarterPicker },
  123. data() {
  124. return {
  125. exportDialogVisible: false,
  126. exportUrl: '',
  127. exportForm: {
  128. startDate: ''
  129. },
  130. exportDetailShow: false,
  131. exportDetailDisabled: false,
  132. exportDetailErrorMsg: '',
  133. exportBtnText: '',
  134. tableLoading: false,
  135. tableOption: tableOption,
  136. page: {
  137. total: 0, // 总页数
  138. currentPage: 1, // 当前页数
  139. pageSize: 10 // 每页显示多少条
  140. },
  141. tableData: [],
  142. treeList: [],
  143. searchForm: {
  144. taskPeriod: []
  145. },
  146. supervision: false,
  147. searchFormAreaCodes: [],
  148. searchCascaderProps: {
  149. multiple: true,
  150. label: 'name',
  151. value: 'id'
  152. },
  153. supervisionInfo: {},
  154. IS_JDY: false,
  155. monthrange: '',
  156. exportDialog: false,
  157. exportMonthrange: '',
  158. exportPeriod: [],
  159. currRow: {}
  160. };
  161. },
  162. computed: {
  163. ...mapGetters(['userInfo'])
  164. },
  165. created() {
  166. const curr = this.getCurrentQuarterNumber();
  167. this.monthrange = curr;
  168. this.monthrangeChange(this.monthrange);
  169. this.getAreaTree();
  170. // 获取监督统计
  171. this.getSupervisionPkgStat();
  172. const roles = this.userInfo.roles;
  173. const rolesArr = [49, 52, 53, 56];
  174. this.IS_JDY = roles.some((item) => rolesArr.includes(item));
  175. this.getExportInfoStatus();
  176. //
  177. },
  178. activated() {
  179. this.getList(this.page);
  180. },
  181. methods: {
  182. getCurrentQuarterNumber() {
  183. const now = dayjs();
  184. const year = now.year();
  185. const month = now.month(); // 0-11
  186. const quarter = Math.floor(month / 3) + 1; // 1 到 4
  187. return `${year}-0${quarter}`;
  188. },
  189. exportDialogHandleClose() {
  190. this.exportMonthrange = '';
  191. this.exportPeriod = [];
  192. this.exportDialog = false;
  193. },
  194. monthrangeChange(e) {
  195. if (e) {
  196. let arr = this.getQuarterRange(e);
  197. this.searchForm.taskPeriod = arr;
  198. } else {
  199. this.monthrange = '';
  200. this.searchForm.taskPeriod = [];
  201. }
  202. },
  203. exportFn(row) {
  204. this.currRow = row;
  205. this.exportDialog = true;
  206. },
  207. exportMonthrangeChange(e) {
  208. if (e) {
  209. let arr = this.getQuarterRange(e);
  210. this.exportPeriod = arr;
  211. } else {
  212. this.exportMonthrange = '';
  213. this.exportPeriod = [];
  214. }
  215. },
  216. async exportBtn() {
  217. const obj = {
  218. entId: this.currRow.deptId,
  219. taskPeriod: this.exportPeriod
  220. };
  221. const res = await reportExportApi(obj);
  222. if (res.data.code === 0) {
  223. let url = res.data.data.url;
  224. const link = document.createElement('a');
  225. link.href = process.env.VUE_APP_URL + url;
  226. link.download = '详情.pdf';
  227. link.target = '_blank'; // 为了在某些浏览器中工作,可以设置为 "_blank"
  228. document.body.appendChild(link);
  229. link.click();
  230. document.body.removeChild(link);
  231. this.exportDialogHandleClose();
  232. }
  233. },
  234. getQuarterRange(dateString) {
  235. const [year, quarterCode] = dateString.split('-').map(Number);
  236. let startMonth, endMonth, endDay;
  237. switch (quarterCode) {
  238. case 1: // Q1
  239. startMonth = 1;
  240. endMonth = 3;
  241. endDay = 31;
  242. break;
  243. case 2: // Q2
  244. startMonth = 4;
  245. endMonth = 6;
  246. endDay = 30;
  247. break;
  248. case 3: // Q3
  249. startMonth = 7;
  250. endMonth = 9;
  251. endDay = 30;
  252. break;
  253. case 4: // Q4
  254. startMonth = 10;
  255. endMonth = 12;
  256. endDay = 31;
  257. break;
  258. default:
  259. throw new Error('Invalid quarter code');
  260. }
  261. const startDate = `${year}-${String(startMonth).padStart(2, '0')}-01`;
  262. const endDate = `${year}-${String(endMonth).padStart(2, '0')}-${endDay}`;
  263. return [startDate, endDate];
  264. },
  265. getTaskMonitor() {},
  266. getShowtaskMonitorBtn(row) {
  267. const roles = this.userInfo.roles;
  268. const rolesArr = [49, 52, 53, 56];
  269. let flag = roles.some((item) => rolesArr.includes(item));
  270. const supervision = row.supervision;
  271. const scorePackageStatus = row.scorePackageStatus;
  272. const flag2 = scorePackageStatus === '4' && (!supervision || supervision === 'REJECTED');
  273. return flag && flag2;
  274. },
  275. async getSupervisionPkgStat() {
  276. const res = await supervisionPkgStatApi();
  277. if (res.data.code === 0) {
  278. this.supervisionInfo = res.data.data;
  279. this.supervisionInfo.allOperatorSupRate = this.supervisionInfo.allOperatorSupRate * 100;
  280. this.supervisionInfo.curOperatorSupRate = this.supervisionInfo.curOperatorSupRate * 100;
  281. }
  282. },
  283. // 获取区域树列表
  284. async getAreaTree() {
  285. const res = await getAreaTreeApi();
  286. this.treeList = res.data.data;
  287. },
  288. async getList(page, params) {
  289. this.tableLoading = true;
  290. const obj = Object.assign(
  291. {
  292. current: page.currentPage,
  293. size: page.pageSize,
  294. supervision: this.supervision
  295. },
  296. params,
  297. this.searchForm
  298. );
  299. const res = await fetchList(obj);
  300. if (res.data.code === 0) {
  301. this.tableData = res.data.data.records;
  302. this.page.total = res.data.data.total;
  303. }
  304. this.tableLoading = false;
  305. },
  306. refreshChange() {
  307. this.getList(this.page);
  308. },
  309. checkBoxChange() {
  310. this.page.currentPage = 1;
  311. this.getList(this.page);
  312. },
  313. extractLastElements(array) {
  314. // 创建一个新的空数组来存储结果
  315. let result = [];
  316. // 遍历输入的二维数组
  317. for (let i = 0; i < array.length; i++) {
  318. // 检查内部数组是否非空
  319. if (array[i].length > 0) {
  320. // 将内部数组的最后一个元素添加到结果数组中
  321. result.push(array[i][array[i].length - 1]);
  322. }
  323. }
  324. // 返回结果数组
  325. return result;
  326. },
  327. searchChange(form, done) {
  328. this.searchForm = form;
  329. if (this.searchFormAreaCodes.length) {
  330. const codes = this.extractLastElements(this.searchFormAreaCodes);
  331. this.searchForm.areaCode = codes;
  332. }
  333. this.page.currentPage = 1;
  334. this.getList(this.page, form);
  335. done();
  336. },
  337. success() {
  338. this.getList(this.page);
  339. this.getSupervisionPkgStat();
  340. },
  341. searchReset() {
  342. this.searchForm = {};
  343. this.searchFormAreaCodes = [];
  344. this.monthrange = '';
  345. },
  346. sizeChange(pageSize) {
  347. this.page.pageSize = pageSize;
  348. this.getList(this.page);
  349. },
  350. currentChange(current) {
  351. this.page.currentPage = current;
  352. this.getList(this.page);
  353. },
  354. monitorFn(row) {
  355. this.$refs.monitorRef.showInfo(row);
  356. },
  357. taskMonitorFn(row, type) {
  358. console.log('this.monthrange', this.monthrange);
  359. this.$router.push({
  360. path: '/servicePackageMonitoring/riskEventAudit/taskMonitor?deptId=' + row.deptId + `&type=${type}&time=${this.monthrange}`,
  361. query: {
  362. name: '任务监督-' + row.deptName
  363. }
  364. });
  365. },
  366. getPercentage(row, type) {
  367. if (!row.taskSupStat) return 0;
  368. let num;
  369. switch (type) {
  370. case 0:
  371. num = row.taskSupStat.supTotal;
  372. break;
  373. case 1:
  374. num = row.taskSupStat.mahFinaSupTotal;
  375. break;
  376. case 2:
  377. num = row.taskSupStat.mahSalesSupTotal;
  378. break;
  379. case 3:
  380. num = row.taskSupStat.optFinaSupTotal;
  381. break;
  382. case 4:
  383. num = row.taskSupStat.optLawSupTotal;
  384. break;
  385. }
  386. if (Number(row.taskSupStat.total) && Number(num)) {
  387. let percentage = num / row.taskSupStat.total;
  388. percentage = percentage * 100;
  389. if (Number.isInteger(percentage)) {
  390. return percentage; // 如果是整数,直接转换为字符串
  391. } else {
  392. // 如果不是整数,保留两位小数并转换为字符串
  393. const num = percentage.toFixed(2);
  394. return Number(num);
  395. }
  396. }
  397. return 0;
  398. },
  399. exportTask() {
  400. this.exportDialogVisible = true;
  401. },
  402. getMonthRange(dateStr) {
  403. const start = dayjs(dateStr).startOf('month').format('YYYY-MM-DD');
  404. const end = dayjs(dateStr).endOf('month').format('YYYY-MM-DD');
  405. return [start, end];
  406. },
  407. async exportDialogFn() {
  408. if (!this.exportForm.startDate) {
  409. return this.$message.warning('请选择导出日期');
  410. }
  411. const taskPeriodResult = this.getMonthRange(this.exportForm.startDate);
  412. const res = await checkHisExport({
  413. taskPeriod: taskPeriodResult
  414. });
  415. if (res.data.code == 0) {
  416. this.getExportInfoStatus();
  417. }
  418. this.handleExportDialogClose();
  419. },
  420. // 获取导出状态 - 用户信息导出
  421. async getExportInfoStatus() {
  422. const checkStatus = async () => {
  423. const res = await getExportResult({ type: 'CHECK_SUP' });
  424. if (res.data.code === 0) {
  425. this.exportDetailShow = true;
  426. const status = res.data.data.status;
  427. if (status === 'GENERATING') {
  428. this.exportBtnText = '数据生成中';
  429. this.exportDetailDisabled = true;
  430. return false;
  431. } else if (status === 'GENERATED') {
  432. this.exportUrl = res.data.data.latestUrl;
  433. this.exportBtnText = '下载数据';
  434. this.exportDetailDisabled = false;
  435. return true;
  436. } else if (status === 'ERROR') {
  437. this.exportDetailErrorMsg = res.data.data.errorMsg || '导出失败';
  438. this.exportDetailDisabled = true;
  439. return true;
  440. } else {
  441. this.exportDetailShow = false;
  442. return true;
  443. }
  444. } else {
  445. this.exportBtnText = '获取状态失败';
  446. this.exportDetailDisabled = false;
  447. return true;
  448. }
  449. };
  450. // 先执行一次
  451. const stop = await checkStatus();
  452. // 如果还在生成中,启动轮询
  453. if (!stop) {
  454. const timer = setInterval(async () => {
  455. const stopLoop = await checkStatus();
  456. if (stopLoop) clearInterval(timer);
  457. }, 5000); // 每5秒轮询一次
  458. }
  459. },
  460. downInfo() {
  461. let link = document.createElement('a');
  462. link.style.display = 'none';
  463. link.href = process.env.VUE_APP_URL + this.exportUrl;
  464. link.setAttribute('download', '用户信息.xlsx');
  465. document.body.appendChild(link);
  466. link.click();
  467. document.body.removeChild(link);
  468. },
  469. handleExportDialogClose() {
  470. this.exportForm.startDate = '';
  471. this.exportDialogVisible = false;
  472. }
  473. }
  474. };
  475. </script>
  476. <style lang="scss" scoped>
  477. .supervision {
  478. height: calc(100vh - 180px);
  479. }
  480. .supervision-info {
  481. margin-bottom: 30px;
  482. padding-left: 50px;
  483. display: flex;
  484. .supervision-item {
  485. margin-right: 50px;
  486. width: 200px;
  487. display: flex;
  488. flex-direction: column;
  489. align-items: center;
  490. .txt {
  491. margin-bottom: 20px;
  492. width: 200px;
  493. text-align: center;
  494. }
  495. .progress {
  496. width: 200px;
  497. display: flex;
  498. flex-direction: column;
  499. align-items: center;
  500. }
  501. }
  502. }
  503. ::v-deep .el-progress-bar {
  504. width: 160px;
  505. }
  506. ::v-deep .el-progress__text {
  507. width: 60px;
  508. }
  509. .role-number {
  510. height: 140px;
  511. display: flex;
  512. flex-direction: column;
  513. justify-content: space-between;
  514. div {
  515. height: 25px;
  516. span {
  517. display: inline-block;
  518. width: 180px;
  519. text-align: right;
  520. }
  521. }
  522. }
  523. .role-progress {
  524. height: 140px;
  525. display: flex;
  526. flex-direction: column;
  527. justify-content: space-between;
  528. div {
  529. height: 25px;
  530. }
  531. }
  532. .right-content {
  533. display: flex;
  534. align-items: center;
  535. .r-btn {
  536. margin-right: 10px;
  537. }
  538. }
  539. .error-message {
  540. color: red;
  541. font-weight: 600;
  542. i {
  543. font-weight: 600;
  544. font-size: 14px;
  545. }
  546. &:hover {
  547. background-color: #fff !important;
  548. border-color: #ebeef5 !important;
  549. color: red !important;
  550. }
  551. }
  552. </style>