index.vue 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. <template>
  2. <!-- 激光测距仪页面 -->
  3. <div class="global-variable">
  4. <el-card class="box-card">
  5. <div>
  6. <el-steps :active="stepsActive">
  7. <el-step
  8. title="导入数据"
  9. :icon="UploadFilled"
  10. @click="() => (stepsActive = 0)"
  11. >
  12. </el-step>
  13. <el-step
  14. title="数据计算"
  15. :icon="Setting"
  16. @click="() => (stepsActive = 1)"
  17. ></el-step>
  18. <el-step
  19. title="查看结果"
  20. :icon="Histogram"
  21. @click="() => (stepsActive = 2)"
  22. ></el-step>
  23. </el-steps>
  24. </div>
  25. <div class="stepOne" v-if="stepsActive === 0">
  26. <el-upload
  27. ref="uploadRef"
  28. class="upload-demo"
  29. drag
  30. action=""
  31. show-file-list
  32. :accept="'.csv, .xlsx, .xls'"
  33. :auto-upload="false"
  34. limit="1"
  35. :disabled="true"
  36. :before-upload="() => false"
  37. >
  38. <div class="iconText" @click="validateAndHandleChange">
  39. <app-icon icon="UploadFilled" size="50px" />
  40. <div class="el-upload__text">
  41. 导入测量数据 <em>点击选择文件</em>
  42. </div>
  43. </div>
  44. <template #tip>
  45. <div class="el-upload__tip">
  46. 导入测量数据 ,文件格式只能上传Xlsx/Xls、Excel、CSV 格式
  47. </div>
  48. </template>
  49. </el-upload>
  50. <el-upload
  51. class="upload-demo"
  52. drag
  53. action=""
  54. show-file-list
  55. :accept="'.csv, .xlsx, .xls'"
  56. :auto-upload="false"
  57. limit="1"
  58. >
  59. <div class="iconText">
  60. <app-icon icon="UploadFilled" size="50px" />
  61. <div class="el-upload__text">
  62. 导入定位数据 <em>点击选择文件</em>
  63. </div>
  64. </div>
  65. <template #tip>
  66. <div class="el-upload__tip">
  67. 导入定位数据 ,文件格式只能上传Xlsx/Xls、Excel、CSV 格式
  68. </div>
  69. </template>
  70. </el-upload>
  71. <!-- <el-button @click="validateAndHandleChange" type="primary"
  72. >导入测量数据</el-button
  73. > -->
  74. <!-- <el-button @click="validateAndHandleChange" type="primary"
  75. >导入定位数据</el-button
  76. > -->
  77. <el-button type="primary" @click="submitPy">数据测量分析</el-button>
  78. </div>
  79. <div class="stepTwo" v-if="stepsActive === 1">
  80. <el-empty
  81. :image-size="300"
  82. v-loading="true"
  83. element-loading-text="数据测量中..."
  84. element-loading-background="rgba(255, 255, 255, 0.3)"
  85. class="loadingView"
  86. description=" "
  87. />
  88. </div>
  89. <div class="stepThree" v-if="stepsActive === 2">
  90. <div class="searchbox">
  91. <el-collapse
  92. v-model="activeNames"
  93. v-if="tabActiveName !== 'copy' && tabActiveName !== 'init'"
  94. >
  95. <el-collapse-item title="数据筛选" name="1">
  96. <template #title>
  97. <div class="titleLeft">数据筛选</div>
  98. <div class="titleRight">
  99. <el-button type="primary" @click.stop="getTableData">
  100. 查询
  101. </el-button>
  102. <el-button
  103. v-if="tabActiveName === 'list'"
  104. type="primary"
  105. @click.stop="onSubmit"
  106. >
  107. 导出
  108. </el-button>
  109. </div>
  110. </template>
  111. <el-form
  112. :inline="true"
  113. :model="formInline"
  114. class="demo-form-inline"
  115. >
  116. <el-row :gutter="10">
  117. <el-col :xs="16" :sm="8" :md="7" :lg="6" :xl="4">
  118. <el-form-item label="场站">
  119. <selecttree
  120. style="width: 220px"
  121. placeholder="请选择场站"
  122. :list="parentOpt"
  123. type="1"
  124. v-model="formInline.companyCode"
  125. @change="parentChange"
  126. :defaultParentProps="{
  127. children: 'children',
  128. label: 'companyName',
  129. value: 'codeNumber',
  130. }"
  131. >
  132. </selecttree>
  133. </el-form-item>
  134. </el-col>
  135. <el-col :xs="16" :sm="8" :md="7" :lg="6" :xl="4">
  136. <el-form-item label="风机">
  137. <el-select
  138. v-model="formInline.unitvalue"
  139. placeholder="请选择"
  140. @change="handleWindTuebineData"
  141. >
  142. <el-option
  143. v-for="item in unitoptions"
  144. :key="item.engineCode"
  145. :label="item.engineName"
  146. :value="item.engineCode"
  147. >
  148. </el-option>
  149. </el-select>
  150. </el-form-item>
  151. </el-col>
  152. <el-col :xs="24" :sm="14" :md="12" :lg="10" :xl="6">
  153. <el-form-item label="时间">
  154. <el-date-picker
  155. v-model="formInline.timevalue"
  156. type="daterange"
  157. range-separator="至"
  158. start-placeholder="开始日期"
  159. end-placeholder="结束日期"
  160. >
  161. </el-date-picker>
  162. </el-form-item>
  163. </el-col>
  164. </el-row>
  165. </el-form>
  166. </el-collapse-item>
  167. </el-collapse>
  168. </div>
  169. <div class="main-body">
  170. <div class="data-map">
  171. <el-tabs
  172. type="border-card"
  173. v-model="tabActiveName"
  174. @tab-click="handleTabChange"
  175. >
  176. <el-tab-pane label="原始图" name="init">
  177. <template #label>
  178. <img
  179. style="width: 30px; height: 20px; display: inline-block"
  180. src="@/assets/analyse/04.png"
  181. alt=""
  182. />
  183. 原始图
  184. </template>
  185. <div class="boxContent">
  186. <div class="left" v-if="tabActiveName === 'init'">
  187. <el-empty
  188. description="暂无数据,请先进行数据筛选"
  189. :image-size="200"
  190. v-if="currentInitRow === null"
  191. ></el-empty>
  192. <div
  193. v-else-if="
  194. currentInitRow !== null && tabActiveName === 'init'
  195. "
  196. >
  197. <InitCharts
  198. ref="initCharts"
  199. :type="tabActiveName"
  200. :key="chartKey"
  201. ></InitCharts>
  202. <CylinderOfTower
  203. ref="waveformDiagram"
  204. type="Waveformdiagram"
  205. :key="chartKey"
  206. ></CylinderOfTower>
  207. <CylinderOfTower
  208. ref="spectrogram"
  209. type="spectrogram"
  210. :key="chartKey"
  211. ></CylinderOfTower>
  212. </div>
  213. </div>
  214. <div class="right">
  215. <DescrBox
  216. type="init"
  217. :tableData="tableData"
  218. ref="initTable"
  219. :windName="windName"
  220. :windTurbineName="windTurbineName"
  221. :currentIndex="currentInitIndex"
  222. :currentRow="currentInitRow"
  223. @handleInitCurrentChange="handleInitCurrentChange"
  224. ></DescrBox>
  225. </div>
  226. </div>
  227. </el-tab-pane>
  228. <el-tab-pane label="拟合图" name="copy">
  229. <template #label>
  230. <img
  231. style="width: 25px; height: 20px; display: inline-block"
  232. src="@/assets/analyse/01.png"
  233. alt=""
  234. />
  235. 拟合图
  236. </template>
  237. <div class="boxContent" v-if="tabActiveName === 'copy'">
  238. <div class="left">
  239. <el-empty
  240. description="暂无数据,请先进行数据筛选"
  241. :image-size="200"
  242. v-if="currentCopyRow === null"
  243. ></el-empty>
  244. <div
  245. v-else-if="
  246. currentCopyRow !== null && tabActiveName === 'copy'
  247. "
  248. >
  249. <PlotOfFit
  250. type="LeafRootOutline"
  251. :key="chartKey"
  252. ></PlotOfFit>
  253. <PlotOfFit
  254. type="LeafRootRelativeOutline"
  255. :key="chartKey"
  256. ></PlotOfFit>
  257. <PlotOfFit
  258. type="LeafTipProfile"
  259. :key="chartKey"
  260. ></PlotOfFit>
  261. <PlotOfFit
  262. type="LeafTipRelativeProfile"
  263. :key="chartKey"
  264. ></PlotOfFit>
  265. </div>
  266. </div>
  267. <div class="right">
  268. <DescrBox
  269. type="copy"
  270. ref="copyTable"
  271. :windName="windName"
  272. :windTurbineName="windTurbineName"
  273. :tableData="tableData"
  274. :currentRow="currentCopyRow"
  275. :currentIndex="currentCopyIndex"
  276. @handleCopyCurrentChange="handleCopyCurrentChange"
  277. ></DescrBox>
  278. </div>
  279. </div>
  280. </el-tab-pane>
  281. <el-tab-pane label="数据列表" name="list">
  282. <template #label>
  283. <img
  284. style="width: 20px; height: 20px; display: inline-block"
  285. src="@/assets/analyse/dataList1.png"
  286. alt=""
  287. />
  288. 数据列表
  289. </template>
  290. <div>
  291. <MultilevelTable
  292. ref="multilevelTable"
  293. :windName="windName"
  294. :windTurbineName="windTurbineName"
  295. :tableData="tableData"
  296. ></MultilevelTable>
  297. </div>
  298. </el-tab-pane>
  299. </el-tabs>
  300. </div>
  301. </div>
  302. </div>
  303. </el-card>
  304. </div>
  305. </template>
  306. <script>
  307. import {
  308. getSysOrganizationAuthTreeByRoleId,
  309. windEngineGrouPage,
  310. getLaserData,
  311. } from "@/api/ledger";
  312. import DescrBox from "./components/descrBox.vue";
  313. import selecttree from "../../components/selecttree.vue";
  314. import MultilevelTable from "./components/MultilevelTable.vue";
  315. import InitCharts from "./components/initCharts.vue";
  316. import CylinderOfTower from "./components/CylinderOfTower.vue";
  317. import PlotOfFit from "./components/PlotOfFit.vue";
  318. import axios from "axios";
  319. import path from "path";
  320. export default {
  321. data() {
  322. return {
  323. stepsActive: 0,
  324. pathFileName: "",
  325. chartKey: 0, // 用于强制重新渲染子组件
  326. activeNames: ["1"],
  327. tabActiveName: "init",
  328. formInline: {
  329. companyCode: "", //单位
  330. unitvalue: "", //风机
  331. timevalue: "", //时间
  332. },
  333. fileList: [],
  334. companyCode: "", //单位表格中展示
  335. parentOpt: [], //风场
  336. tableData: [], // 假设是来自父组件的数据
  337. company: "",
  338. unitoptions: [], //风机list
  339. fourList: [],
  340. currentInitRow: null, // 原始图用于存储当前选中的行
  341. currentInitIndex: 0,
  342. currentCopyRow: null, //拟合图用于存储当前选中的行
  343. currentCopyIndex: 0,
  344. windName: "",
  345. windTurbineName: "",
  346. };
  347. },
  348. components: {
  349. DescrBox,
  350. PlotOfFit,
  351. MultilevelTable,
  352. selecttree,
  353. InitCharts,
  354. CylinderOfTower,
  355. },
  356. created() {
  357. // this.GETtree();
  358. // 获取 VITE_NODE_ENV
  359. console.log("环境变量", import.meta.env.VITE_NODE_ENV);
  360. console.log("process.env.NODE_ENV", process.env.NODE_ENV);
  361. },
  362. methods: {
  363. // 验证并处理文件变化
  364. async validateAndHandleChange() {
  365. // 直接获取文件路径(通常无效)
  366. try {
  367. const absolutePath = await window.electronAPI.getFilePath(); // ✅ 修正:不传 file
  368. if (absolutePath) {
  369. console.log("文件绝对路径:", absolutePath);
  370. this.pathFileName = absolutePath;
  371. // this.fetchA2(absolutePath);
  372. } else {
  373. console.log("未选择文件或路径获取失败");
  374. }
  375. } catch (error) {
  376. console.error("获取文件路径失败:", error);
  377. }
  378. // ✅ 手动触发 input[type="file"]
  379. this.$nextTick(() => {
  380. const inputEl =
  381. this.$refs.uploadRef.$el.querySelector("input[type='file']");
  382. // if (inputEl) {
  383. // inputEl.click();
  384. // }
  385. });
  386. },
  387. submitPy() {
  388. if (this.pathFileName !== "") {
  389. this.fetchA1(this.pathFileName);
  390. } else {
  391. this.$message({
  392. message: "请先选择文件",
  393. type: "warning",
  394. });
  395. }
  396. },
  397. async fetchA1(path) {
  398. try {
  399. const result = await window.electronAPI.callPythonAPI("a1", path);
  400. console.log("A1 API 响应:", result);
  401. } catch (error) {
  402. console.error("A1 API 失败:", error);
  403. }
  404. },
  405. async fetchA2(path) {
  406. try {
  407. const result = await window.electronAPI.callPythonAPI("a2", path);
  408. console.log("A2 API 响应:", result);
  409. } catch (error) {
  410. console.error("A2 API 失败:", error);
  411. }
  412. },
  413. handleTabChange() {
  414. // 每次切换 tab 或其他事件发生时,增加 key 值,强制子组件重新渲染
  415. this.chartKey += 1;
  416. },
  417. //获取表格数据接口
  418. async getTableData() {
  419. try {
  420. // companyCode: "", //单位
  421. // unitvalue: "", //风机
  422. const params = {
  423. startTime: this.$formatDateTWO(this.formInline.timevalue[0]),
  424. endTime: this.$formatDateTWO(this.formInline.timevalue[1]),
  425. // windCode: this.formInline.companyCode,
  426. windCode: this.maplist.codeNumber,
  427. windTurbineNumber: this.formInline.unitvalue,
  428. };
  429. const res = await getLaserData(params);
  430. if (res.code === 200) {
  431. this.tableData = res.datas;
  432. } else {
  433. this.$message.warning(res.message);
  434. }
  435. } catch (err) {
  436. this.$message.error(err + "请检查网络");
  437. }
  438. },
  439. // 获取风场
  440. async GETtree() {
  441. try {
  442. const res = await getSysOrganizationAuthTreeByRoleId();
  443. if (res.code !== 200) {
  444. this.$message.error(res.msg);
  445. return;
  446. }
  447. const treedata = res.data;
  448. const processedData = this.processTreeData(treedata);
  449. this.parentOpt = processedData;
  450. this.defaultdata = res.data[0];
  451. } catch (err) {
  452. this.$message.error(err);
  453. }
  454. },
  455. processTreeData(treeData) {
  456. const processedData = [];
  457. function processNode(node) {
  458. if (node.codeType === "field") {
  459. node.companyName = node.fieldName;
  460. }
  461. if (node.children && node.children.length > 0) {
  462. node.children.forEach((child) => {
  463. processNode(child);
  464. });
  465. }
  466. }
  467. treeData.forEach((root) => {
  468. processNode(root);
  469. processedData.push(root);
  470. });
  471. return processedData;
  472. },
  473. handleWindTuebineData(data) {},
  474. parentChange(data) {
  475. this.windName = data && data.companyName;
  476. this.maplist = data;
  477. this.maplistArr = data;
  478. let paramsData = {
  479. fieldCode: this.maplist.codeNumber,
  480. pageNum: 1,
  481. pageSize: 99,
  482. };
  483. // this.unitvalue = "";
  484. // 获取风机
  485. windEngineGrouPage(paramsData).then((res) => {
  486. this.unitoptions = res.data.list;
  487. });
  488. if (data.codeType === "field") {
  489. if (this.parseCoordinates(data.longitudeAndLatitudeString).length > 0) {
  490. return;
  491. }
  492. } else {
  493. const dataMapList = data.children;
  494. dataMapList.forEach((element) => {
  495. console.log(element);
  496. if (
  497. this.parseCoordinates(element.longitudeAndLatitudeString).length >
  498. 0 &&
  499. element.codeType === "field"
  500. ) {
  501. return;
  502. }
  503. });
  504. }
  505. },
  506. parseCoordinates(input) {
  507. if (input && typeof input === "string") {
  508. return input.split(",").map(Number);
  509. }
  510. // debugger;
  511. return [];
  512. },
  513. handleInitCurrentChange(val, ind) {
  514. this.currentInitRow = val; // 处理当前选中行
  515. this.currentInitIndex = ind; // 更新当前索引
  516. },
  517. handleCopyCurrentChange(val, ind) {
  518. this.currentCopyRow = val; // 处理当前选中行
  519. this.currentCopyIndex = ind; // 更新当前索引
  520. },
  521. onSubmit() {
  522. if (this.tableData.length > 0) {
  523. this.$refs.multilevelTable.outputFile();
  524. } else {
  525. this.$message.warning("请先查询到想要的数据范围后再进行导出");
  526. }
  527. },
  528. },
  529. };
  530. </script>
  531. <style lang="scss" scoped>
  532. .global-variable {
  533. width: 100%;
  534. height: 100%;
  535. .box-card {
  536. width: 100%;
  537. height: 100%;
  538. :deep(.el-card__body) {
  539. height: 100% !important;
  540. width: 100%;
  541. // background-color: aqua;
  542. }
  543. .stepOne {
  544. margin-top: 30px;
  545. height: calc(100% - 150px);
  546. // height: 100%;
  547. width: 100%;
  548. display: flex !important;
  549. justify-content: space-around;
  550. .upload-demo {
  551. width: 48%;
  552. height: 100%;
  553. :deep(.el-upload) {
  554. width: 100%;
  555. height: 80%;
  556. }
  557. :deep(.el-upload-dragger) {
  558. width: 100%;
  559. height: 100%;
  560. display: flex;
  561. justify-content: center;
  562. align-items: center;
  563. }
  564. }
  565. }
  566. .stepTwo {
  567. display: flex;
  568. justify-content: center;
  569. align-items: center;
  570. height: calc(100% - 100px);
  571. width: 100%;
  572. }
  573. }
  574. }
  575. .searchbox {
  576. p {
  577. margin-right: 20px;
  578. }
  579. .el-select {
  580. width: 180px;
  581. }
  582. ::v-deep .el-collapse-item__header {
  583. position: relative !important;
  584. }
  585. .titleLeft {
  586. font-size: 16px;
  587. font-weight: bold;
  588. }
  589. .titleRight {
  590. display: flex;
  591. position: absolute;
  592. right: 50px;
  593. }
  594. }
  595. .dialog-actions {
  596. text-align: right;
  597. padding: 0 10px;
  598. display: flex;
  599. justify-content: space-between;
  600. position: relative;
  601. }
  602. .subject {
  603. height: 280px;
  604. transition: all 0.3s ease;
  605. padding: 0 10px;
  606. p {
  607. font-size: 10px;
  608. }
  609. }
  610. .main-body {
  611. display: flex;
  612. justify-content: space-between;
  613. .data-map {
  614. width: 100%;
  615. // height: 620px;
  616. overflow-y: auto;
  617. .chart-area {
  618. margin-bottom: 10px;
  619. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
  620. }
  621. img {
  622. margin: 0 10px;
  623. }
  624. .boxContent {
  625. display: flex;
  626. justify-content: space-between;
  627. .left {
  628. width: 60%;
  629. }
  630. .right {
  631. width: 39%;
  632. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
  633. }
  634. }
  635. .el-tabs--border-card {
  636. height: 100%;
  637. }
  638. ::v-deep .el-tabs--border-card > .el-tabs__content {
  639. padding: 0 !important;
  640. }
  641. }
  642. .data-map::-webkit-scrollbar {
  643. display: none; /* 隐藏滚动条 */
  644. }
  645. }
  646. .subject.minimized {
  647. height: 0px; /* Adjust height when minimized */
  648. overflow: hidden;
  649. }
  650. #main {
  651. width: 100%;
  652. height: 280px;
  653. }
  654. </style>