psarReport.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. <template>
  2. <div class="reportBox">
  3. <div class="outlineBox">
  4. <div class="btnBox">
  5. <el-dropdown
  6. trigger="click"
  7. @command="callFn"
  8. popper-class="psarDropdown"
  9. >
  10. <el-button style="color: #05bb4c" size="small" type="text"
  11. >导出
  12. <el-icon><arrow-down /></el-icon>
  13. </el-button>
  14. <template #dropdown>
  15. <el-dropdown-menu>
  16. <el-dropdown-item command="exportPDF">PDF</el-dropdown-item>
  17. <el-dropdown-item command="exportWord">WORD</el-dropdown-item>
  18. </el-dropdown-menu>
  19. </template>
  20. </el-dropdown>
  21. <!-- <el-button type="success" size="mini" @click="exportPDF"
  22. >导出PDF</el-button
  23. > -->
  24. </div>
  25. <template v-if="reportTitle">
  26. <p
  27. class="songti font fw"
  28. style="
  29. width: 100%;
  30. padding: 12px 24px;
  31. color: #b3bdc0;
  32. background: rgba(96, 103, 105, 0.2);
  33. cursor: pointer;
  34. margin: 0;
  35. color: #05bb4c;
  36. "
  37. @click="
  38. nodeClick({
  39. label: `国家能源集团宁夏新能源开发有限公司${reportTitle}`,
  40. })
  41. "
  42. >
  43. {{ reportTitle }}
  44. </p>
  45. </template>
  46. <el-tree
  47. :data="treeData"
  48. :props="{
  49. children: 'children',
  50. label: 'label',
  51. }"
  52. style="height: calc(100% - 48px); overflow-y: scroll"
  53. @node-click="nodeClick"
  54. >
  55. <template #default="{ node, data }">
  56. <span class="font songti fw">{{ data.index }}{{ node.label }}</span>
  57. </template>
  58. </el-tree>
  59. </div>
  60. <div class="resizeLine">
  61. <el-divider direction="vertical" />
  62. <el-icon :size="24" color="#eee"><DCaret /></el-icon>
  63. </div>
  64. <div class="contentBox">
  65. <div class="pagePadding A4">
  66. <div class="page">
  67. <template v-if="reportTitle">
  68. <h1
  69. :id="`国家能源集团宁夏新能源开发有限公司${reportTitle}`"
  70. class="songti mainTitle fw"
  71. style="margin-bottom: 0"
  72. >
  73. 国家能源集团宁夏新能源开发有限公司
  74. </h1>
  75. <h1 :id="reportTitle" class="songti mainTitle fw">
  76. {{ reportTitle }}
  77. </h1>
  78. </template>
  79. <el-card
  80. :id="pItem.label"
  81. v-for="pItem in treeData"
  82. :key="pItem.index"
  83. >
  84. <p class="songti title fw">
  85. {{ pItem.index || "" }}{{ pItem.label }}
  86. </p>
  87. <p
  88. class="fangsong indent"
  89. style="margin-bottom: 5px"
  90. v-for="(cItem, cIndex) in pItem.content"
  91. :key="cIndex"
  92. >
  93. {{ cItem }}
  94. </p>
  95. </el-card>
  96. </div>
  97. </div>
  98. </div>
  99. </div>
  100. </template>
  101. <script>
  102. import Get_PDF from "@tools/fixGetPDF";
  103. import docxtemplater from "docxtemplater";
  104. import { saveAs } from "file-saver";
  105. import JSZipUtils from "jszip-utils";
  106. import PizZip from "pizzip";
  107. export default {
  108. components: {},
  109. data() {
  110. return {
  111. locationHref: "",
  112. reportTitle: "",
  113. treeData: [
  114. {
  115. label: "发电量完成情况",
  116. children: [],
  117. content: [],
  118. },
  119. {
  120. label: "资源及理论发电量平衡分析",
  121. children: [],
  122. content: [],
  123. },
  124. {
  125. label: "电网调度因素损失分析",
  126. children: [],
  127. content: [],
  128. },
  129. {
  130. label: "故障及损失分析",
  131. children: [],
  132. content: [],
  133. },
  134. {
  135. label: "计划检修损失分析",
  136. children: [],
  137. content: [],
  138. },
  139. {
  140. label: "性能损失分析",
  141. children: [],
  142. content: [],
  143. },
  144. {
  145. label: "厂用电量分析",
  146. children: [],
  147. content: [],
  148. },
  149. {
  150. label: "场站问题总结和建议",
  151. children: [],
  152. content: [],
  153. },
  154. ],
  155. sourceData: {},
  156. };
  157. },
  158. created() {
  159. this.locationHref = location.href;
  160. let treeData = this.initTreeData(this.BASE.deepCopy(this.treeData || []));
  161. this.treeData = treeData;
  162. },
  163. mounted() {
  164. this.$nextTick(() => {
  165. this.initMouseEvent();
  166. this.getContentData();
  167. if (this.$route.hash) {
  168. this.pageScroll(this.$route.hash.replace(/^\#/, ""));
  169. }
  170. });
  171. },
  172. methods: {
  173. // 初始化树形
  174. initTreeData(treeData, parentIdx = "") {
  175. treeData.forEach((ele, idx) => {
  176. ele.index = `${parentIdx}${idx + 1}.`;
  177. if (ele.children?.length) {
  178. return this.initTreeData(ele.children, ele.index);
  179. }
  180. });
  181. return treeData;
  182. },
  183. // 初始化鼠标事件
  184. initMouseEvent() {
  185. const resize = document.querySelector(".resizeLine");
  186. const left = document.querySelector(".outlineBox");
  187. const right = document.querySelector(".contentBox");
  188. const box = document.querySelector(".reportBox");
  189. const svgPath = resize.querySelector(".el-icon path");
  190. left.style.width = getComputedStyle(left, null).width;
  191. right.style.width = getComputedStyle(right, null).width;
  192. resize.onmousedown = (e) => {
  193. const startX = e.clientX;
  194. resize.left = resize.offsetLeft;
  195. svgPath.style.fill = "#05bb4c";
  196. svgPath.style.transform = "scale(1.5)";
  197. svgPath.style.transformOrigin = "center center";
  198. svgPath.style.transition = "0.2s";
  199. document.onmousemove = (e) => {
  200. var endX = e.clientX;
  201. const maxT = box.clientWidth - resize.offsetWidth;
  202. let moveLen = resize.left + (endX - startX);
  203. if (moveLen < 250) moveLen = 250;
  204. if (moveLen > maxT - 1000) moveLen = maxT - 1000;
  205. resize.style.left = moveLen;
  206. left.style.width = `${moveLen}px`;
  207. right.style.width = `${box.clientWidth - moveLen - 8}px`;
  208. };
  209. document.onmouseup = () => {
  210. svgPath.style.fill = "#eee";
  211. svgPath.style.transform = "scale(1)";
  212. svgPath.style.transformOrigin = "center center";
  213. svgPath.style.transition = "0.2s";
  214. document.onmousemove = null;
  215. document.onmouseup = null;
  216. resize.releaseCapture && resize.releaseCapture();
  217. };
  218. resize.setCapture && resize.setCapture();
  219. return false;
  220. };
  221. },
  222. // 树形点击
  223. nodeClick(node) {
  224. const label = node.label;
  225. location.hash = `${this.$route.path}#${label}`;
  226. this.pageScroll(label);
  227. },
  228. // 页面滚动至锚点
  229. pageScroll(id) {
  230. document.getElementById(id)?.scrollIntoView({ behavior: "smooth" });
  231. },
  232. // 数据请求
  233. getContentData() {
  234. const that = this;
  235. that.API.requestData({
  236. isMust: false, // 请求是否携带 token ,默认为 true ,可缺省
  237. showLoading: true, // 请求是否显示加载中遮罩层,默认 false ,可缺省
  238. method: "GET", // 请求方式,默认为 GET ,可缺省
  239. baseURL: "http://192.168.10.9:9001/", // 请求服务器地址 + 端口,可缺省
  240. subUrl: "economy/analysis/2023/3", // 请求接口地址,必传项
  241. timeout: 3000, // 请求超时时间,默认 3s ,可缺省
  242. success({ data }) {
  243. that.sourceData = data;
  244. that.treeData.forEach((pEle) => {
  245. const content = data[pEle.label] || [];
  246. if (content.length) {
  247. content.forEach((cEle) => {
  248. pEle.content.push(cEle);
  249. });
  250. }
  251. });
  252. that.reportTitle = data?.["标题"]?.[0] || "";
  253. },
  254. });
  255. },
  256. // 下拉列表调用函数
  257. callFn(command) {
  258. this[command]();
  259. },
  260. // 导出 PDF
  261. exportPDF() {
  262. this.BASE.showLoading({
  263. text: "正在导出...请稍后...",
  264. });
  265. setTimeout(() => {
  266. Get_PDF.getPdf(
  267. document.querySelector(".contentBox .page"),
  268. "2023年3月经济运行分析报告"
  269. );
  270. this.BASE.closeLoading();
  271. }, 50);
  272. },
  273. // 导出 WORD
  274. exportWord() {
  275. let wordData = {};
  276. for (let key in this.sourceData) {
  277. if (key === "标题") {
  278. wordData[key] = this.sourceData[key][0];
  279. } else {
  280. let dataItem = [];
  281. this.sourceData[key].forEach((content) => {
  282. dataItem.push({ content });
  283. });
  284. wordData[key] = dataItem;
  285. }
  286. }
  287. // 读取并获得模板文件的二进制内容
  288. JSZipUtils.getBinaryContent(
  289. "./static/template/wordTemplate.docx",
  290. function (error, content) {
  291. if (error) {
  292. throw error;
  293. }
  294. // 创建一个PizZip实例,内容为模板的内容
  295. let zip = new PizZip(content);
  296. // 创建并加载docxtemplater实例对象
  297. let doc = new docxtemplater(zip, { linebreaks: true });
  298. // doc.attachModule(new ImageModule(opts));
  299. doc.setData(wordData);
  300. try {
  301. // 用模板变量的值替换所有模板变量
  302. doc.render();
  303. } catch (error) {
  304. // 抛出异常
  305. let e = {
  306. message: error.message,
  307. name: error.name,
  308. stack: error.stack,
  309. properties: error.properties,
  310. };
  311. throw error;
  312. }
  313. // 生成一个代表docxtemplater对象的zip文件(不是一个真实的文件,而是在内存中的表示)
  314. let out = doc.getZip().generate({
  315. type: "blob",
  316. mimeType:
  317. "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  318. });
  319. // 将目标文件对象保存为目标类型的文件,并命名
  320. saveAs(out, "2023年3月经济运行分析报告.docx");
  321. }
  322. );
  323. },
  324. },
  325. watch: {},
  326. };
  327. </script>
  328. <style lang="less" scoped>
  329. .reportBox {
  330. width: 100%;
  331. height: 100%;
  332. overflow: hidden;
  333. .outlineBox {
  334. width: calc(25% - 8px);
  335. height: 100%;
  336. float: left;
  337. .btnBox {
  338. width: 97%;
  339. margin: 0 1.5% 12px 1.5%;
  340. display: flex;
  341. justify-content: flex-end;
  342. align-items: center;
  343. }
  344. }
  345. .resizeLine {
  346. width: 6px;
  347. height: 100%;
  348. cursor: w-resize;
  349. float: left;
  350. position: relative;
  351. .el-icon {
  352. position: absolute;
  353. left: calc(50% - 12px);
  354. top: calc(50% - 12px);
  355. transform: rotate(90deg);
  356. transition: 0.2s;
  357. }
  358. .el-divider--vertical {
  359. width: 2px;
  360. margin: 0 2px;
  361. height: 100%;
  362. transition: 0.2s;
  363. }
  364. &:active .el-divider--vertical {
  365. background: #05bb4c;
  366. transition: 0.2s;
  367. }
  368. }
  369. .contentBox {
  370. float: right;
  371. width: 75%;
  372. height: 100%;
  373. display: flex;
  374. justify-content: center;
  375. align-items: flex-start;
  376. overflow-y: scroll;
  377. .mainTitle {
  378. font-size: 30px;
  379. width: 90%;
  380. margin: 0 5% 20px 5%;
  381. text-align: center;
  382. color: #000;
  383. }
  384. .page {
  385. background: #fff;
  386. }
  387. .pagePadding {
  388. padding: 20mm;
  389. box-sizing: border-box;
  390. background: #fff;
  391. }
  392. .A4 {
  393. width: 210mm;
  394. }
  395. .el-card {
  396. border-color: transparent;
  397. color: #000;
  398. .el-card__body {
  399. p {
  400. margin: 0;
  401. }
  402. }
  403. }
  404. }
  405. }
  406. .font {
  407. width: 100%;
  408. overflow: hidden;
  409. text-overflow: ellipsis;
  410. white-space: nowrap;
  411. line-height: 1.5;
  412. }
  413. .fw {
  414. font-weight: 700;
  415. }
  416. .songti {
  417. font-family: SimSun, 宋体;
  418. font-size: 18.7px;
  419. line-height: 2;
  420. }
  421. .fangsong {
  422. font-family: FangSong, 仿宋;
  423. font-size: 18.7px;
  424. line-height: 2;
  425. }
  426. .indent {
  427. text-indent: 2em;
  428. }
  429. .title {
  430. font-size: 22px;
  431. line-height: 2;
  432. }
  433. </style>
  434. <style lang="less">
  435. .contentBox {
  436. .el-card {
  437. .el-card__body {
  438. padding: 0;
  439. }
  440. }
  441. .el-card.is-always-shadow {
  442. box-shadow: none;
  443. }
  444. }
  445. .el-popper.is-light.psarDropdown {
  446. background: rgb(22, 30, 30);
  447. border: 1px solid #05bb4c;
  448. .el-scrollbar__wrap > ul {
  449. padding: 0;
  450. }
  451. .el-dropdown-menu {
  452. padding: 0;
  453. background: none;
  454. }
  455. .el-dropdown-menu__item {
  456. color: #fff;
  457. }
  458. .el-dropdown-menu__item:hover {
  459. color: #05bb4c;
  460. }
  461. .el-popper__arrow {
  462. &::before {
  463. border-top-color: #05bb4c !important;
  464. border-left-color: #05bb4c !important;
  465. }
  466. }
  467. }
  468. </style>