NationalDataBase.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. <template>
  2. <!-- 国家统计局 -->
  3. <div class="national-data-base-wrap">
  4. <div class="nav-bar">
  5. <div class="nav-list">
  6. <el-menu
  7. default-active="hgyd"
  8. mode="horizontal"
  9. active-text-color="#409EFF"
  10. @select="menuSelect"
  11. >
  12. <template v-for="item in menuList">
  13. <template v-if="item.Children">
  14. <el-submenu
  15. :key="item.Dbcode"
  16. :index="item.Dbcode"
  17. :popper-append-to-body="true"
  18. >
  19. <template slot="title">{{ item.Dbname }}</template>
  20. <el-menu-item
  21. v-for="subItem in item.Children"
  22. :key="subItem.Dbcode"
  23. :index="subItem.Dbcode"
  24. >
  25. {{ subItem.Dbname }}
  26. </el-menu-item>
  27. </el-submenu>
  28. </template>
  29. <el-menu-item v-else :key="item.Dbcode" :index="item.Dbcode">
  30. {{ item.Dbname }}
  31. </el-menu-item>
  32. </template>
  33. </el-menu>
  34. </div>
  35. <el-button v-permission="permissionBtn.dataSourcePermission.gjtjjData_export"
  36. type="primary" :disabled="isDisabled" @click="handleExport"
  37. >导出</el-button
  38. >
  39. </div>
  40. <div class="data-show-cotainer" v-loading="searchLoading">
  41. <span
  42. class="slide-btn-icon"
  43. :class="{'slide-left':isLeftWrapShow,'slide-right':!isLeftWrapShow}"
  44. @click="isLeftWrapShow = !isLeftWrapShow"
  45. >
  46. <i :class="{'el-icon-d-arrow-left':isLeftWrapShow,'el-icon-d-arrow-right':!isLeftWrapShow}"></i>
  47. </span>
  48. <div class="data-list-wrap wrap" v-show="isLeftWrapShow">
  49. <p class="sub-menu-title" v-if="currentMenu">{{ currentMenu.label }}</p>
  50. <div class="search-box">
  51. <el-select
  52. filterable
  53. remote
  54. placeholder="指标名称/指标ID"
  55. v-model="searchValue"
  56. :remote-method="searchHandle"
  57. value-key="IndexCode"
  58. clearable
  59. >
  60. <i slot="prefix" class="el-input__icon el-icon-search"></i>
  61. <el-option
  62. v-for="item in searchOptions"
  63. :key="item.IndexCode"
  64. :label="item.IndexName"
  65. :value="item"
  66. />
  67. </el-select>
  68. </div>
  69. <div class="data-list">
  70. <el-tree
  71. lazy
  72. :load="loadTree"
  73. ref="dataTree"
  74. :data="treeData"
  75. :props="treeProps"
  76. node-key="ClassifyId"
  77. :default-expanded-keys="defaultShowNodes"
  78. highlight-current
  79. v-loading="treeDataLoading"
  80. @current-change="changeNode"
  81. >
  82. </el-tree>
  83. </div>
  84. </div>
  85. <div
  86. class="data-table-wrap wrap"
  87. v-if="tableShow"
  88. v-loading="tableLoading"
  89. >
  90. <div class="select-box" v-if="tableShow && areaSelect">
  91. <el-select v-model="areaSelect" @change="areaSelectChange">
  92. <el-option
  93. v-for="item in selectOptions"
  94. :key="item"
  95. :label="item"
  96. :value="item"
  97. />
  98. </el-select>
  99. </div>
  100. <div class="data-table">
  101. <div class="table-header">
  102. <lz-table
  103. tableType="header"
  104. :tableOption="tableOption"
  105. source="smm"
  106. />
  107. </div>
  108. <div class="table-container">
  109. <lz-table
  110. tableType="data"
  111. :dateArr="tableDate"
  112. :tableOption="tableOption"
  113. source="smm"
  114. />
  115. </div>
  116. </div>
  117. </div>
  118. <div class="data-table-wrap wrap nodata-cont" v-else>
  119. <tableNoData text="暂无数据"/>
  120. </div>
  121. </div>
  122. </div>
  123. </template>
  124. <script>
  125. import lzTable from "../../../components/lzTable.vue";
  126. import { nationalInterface } from "@/api/api.js";
  127. export default {
  128. components: { lzTable },
  129. data() {
  130. return {
  131. isLeftWrapShow:true,
  132. /* menu */
  133. menuList: [], //菜单列表
  134. currentMenu: null, //当前选择的菜单
  135. /* search */
  136. searchValue: null, //搜索选中值
  137. searchOptions: [], //搜索结果
  138. searchLoading: false,
  139. /* select */
  140. areaSelect: null, //地区选择值
  141. selectOptions: [], //地区选项
  142. /* tree */
  143. treeData: [], //树形结构数据
  144. treeProps: {
  145. label: "ClassifyName",
  146. children: "Children",
  147. isLeaf: "isLeaf", //懒加载需要显式定义叶子节点标识
  148. }, //覆盖默认的props
  149. currentNode: null, //当前选择的节点
  150. defaultShowNodes: [], //默认展开的节点列表
  151. searchTreeData: [], //暂时性存放指标列表数据
  152. treeDataLoading: false,
  153. /* lz-table */
  154. tableOption: [],
  155. tableDate: [],
  156. tableShow: false,
  157. tableLoading: false,
  158. };
  159. },
  160. watch: {
  161. //找到所有父级节点,展开并选中对应项
  162. async searchValue(newVal) {
  163. if (newVal && newVal instanceof Object) {
  164. this.searchLoading = true;
  165. const { ClassifyId, IndexId, Reg } = newVal;
  166. const { ParentIds } = this.getNodeData(ClassifyId);
  167. //获取指标列表
  168. const nodeDataList = await this.getIndexData(ClassifyId);
  169. //暂存指标列表
  170. this.searchTreeData = nodeDataList;
  171. const nodeData = Reg.length
  172. ? nodeDataList.find(
  173. (node) =>
  174. Number((node.ClassifyId + "").split("-")[0]) === ClassifyId &&
  175. (node.ChildrenIndexIds.includes(IndexId) ||
  176. node.IndexId === IndexId)
  177. )
  178. : nodeDataList.find((node) => node.IndexId === IndexId);
  179. this.defaultShowNodes = [...ParentIds, ClassifyId];
  180. this.$nextTick(() => {
  181. nodeData && this.$refs.dataTree.setCurrentKey(nodeData.ClassifyId);
  182. const node = this.$refs.dataTree.getNode(nodeData.ClassifyId);
  183. //改变区域
  184. this.areaSelect = Reg.length ? Reg : null;
  185. //改变节点会触发loadTree/getTableData
  186. node && this.changeNode(node.data, node);
  187. setTimeout(() => {
  188. //滚动到高亮节点位置
  189. const dom = document.querySelector(".el-tree-node.is-current");
  190. const parentDom = document.querySelector(".data-list");
  191. if (!dom || !parentDom) {
  192. this.searchLoading = false;
  193. return;
  194. }
  195. if (dom.offsetTop > parentDom.offsetHeight) {
  196. //parentDom.scrollTop = dom.offsetTop - parentDom.offsetHeight/2
  197. parentDom.scrollTo({
  198. top: dom.offsetTop - parentDom.offsetHeight / 2,
  199. left: 0,
  200. behavior: "smooth",
  201. });
  202. }
  203. this.searchLoading = false;
  204. }, 300);
  205. });
  206. this.searchOptions = [];
  207. }
  208. },
  209. //切换菜单
  210. currentMenu(newVal) {
  211. if (newVal) {
  212. this.treeDataLoading = true;
  213. this.getTreeData();
  214. }
  215. },
  216. },
  217. computed: {
  218. //当节点不为可导出数据时,按钮禁用
  219. isDisabled() {
  220. if (!this.currentNode) return true;
  221. const { isLeaf } = this.currentNode;
  222. const { IsParent } = this.currentNode.data;
  223. if (!isLeaf && IsParent !== 0) return true;
  224. if (!this.tableShow) return true;
  225. return false;
  226. },
  227. },
  228. methods: {
  229. //获取菜单
  230. getMenuList() {
  231. nationalInterface.getMenuList().then((res) => {
  232. if (res.Ret !== 200) return;
  233. this.menuList = res.Data
  234. ? res.Data.map((item) => {
  235. if (item.Dbname === "地区数据") {
  236. item.Dbcode = "dqsj";
  237. }
  238. if (item.Dbname === "国际数据") {
  239. item.Dbcode = "gjsj";
  240. }
  241. return item;
  242. })
  243. : [];
  244. this.currentMenu = {
  245. label: this.menuList[0].Dbname,
  246. key: this.menuList[0].Dbcode,
  247. };
  248. });
  249. },
  250. //切换菜单
  251. menuSelect(key, keyPath) {
  252. if (key === this.currentMenu.key) return;
  253. this.initOptions();
  254. const menu = this.menuList.find((m) => m.Dbcode === keyPath[0]);
  255. if (keyPath.length > 1) {
  256. const menuItem = menu.Children.find((i) => i.Dbcode === key);
  257. this.currentMenu = {
  258. label: menuItem.Dbname,
  259. key,
  260. };
  261. } else {
  262. this.currentMenu = {
  263. label: menu.Dbname,
  264. key,
  265. };
  266. }
  267. },
  268. //切换地区
  269. areaSelectChange(value) {
  270. //console.log('change')
  271. this.currentNode && this.getTableData(this.currentNode.data);
  272. },
  273. //重置配置项
  274. initOptions() {
  275. this.tableShow = false;
  276. this.searchValue = null;
  277. this.currentNode = null;
  278. this.areaSelect = null;
  279. },
  280. //获取指标分类列表
  281. getTreeData() {
  282. const { key } = this.currentMenu;
  283. nationalInterface
  284. .getClassifyList({
  285. Dbcode: key,
  286. })
  287. .then((res) => {
  288. if (res.Ret !== 200) return;
  289. this.treeData = res.Data;
  290. this.treeDataLoading = false;
  291. });
  292. },
  293. //搜索指标
  294. searchHandle(KeyWord) {
  295. //console.log('search',KeyWord)
  296. if (!KeyWord) return;
  297. nationalInterface
  298. .getIndexList({
  299. Dbcode: this.currentMenu.key,
  300. Keywords: KeyWord,
  301. })
  302. .then((res) => {
  303. if (res.Ret !== 200) return;
  304. if (res.Data) {
  305. this.searchOptions = res.Data.List || [];
  306. }
  307. });
  308. },
  309. //加载树形数据
  310. async loadTree(node, resolve) {
  311. if (node.data.IsParent === 0) {
  312. //调用接口加载指标 如果已有暂存的数据就不重复请求
  313. const treeData = this.searchTreeData.length
  314. ? this.searchTreeData
  315. : await this.getIndexData(node.data.ClassifyId);
  316. this.searchTreeData = [];
  317. node.data.isLoad = true;
  318. this.currentNode &&
  319. this.currentNode.data.ClassifyId === node.data.ClassifyId &&
  320. this.getTableData(node.data);
  321. return resolve(treeData);
  322. }
  323. if (node.isLeaf) {
  324. return resolve([]);
  325. }
  326. if (node.level === 0) {
  327. return resolve(this.treeData);
  328. }
  329. if (node.level > 0) {
  330. //根据ClassifyId加载对应分类数据
  331. const { nodeData } = this.getNodeData(node.data.ClassifyId);
  332. return resolve(nodeData);
  333. }
  334. },
  335. //根据ClassifyId获取对应层级数据
  336. getNodeData(ClassifyId) {
  337. let nodeData = [];
  338. let ParentIds = [];
  339. const getChildren = (data, id) => {
  340. data.map((item) => {
  341. if (item.ClassifyId === id) {
  342. nodeData = item.Children;
  343. ParentIds = item.ParentIds;
  344. } else {
  345. if (item.Children) {
  346. getChildren(item.Children, id);
  347. }
  348. }
  349. });
  350. };
  351. getChildren(this.treeData, ClassifyId);
  352. return { nodeData, ParentIds };
  353. },
  354. //根据ClassifyId获取指标列表及区域数据
  355. async getIndexData(ClassifyId) {
  356. const { key } = this.currentMenu;
  357. let indexData = [];
  358. const res = await nationalInterface.getIndexList({
  359. Dbcode: key,
  360. ClassifyId: Number((ClassifyId + "").split("-")[0]),
  361. });
  362. if (res.Ret !== 200) return [];
  363. if (res.Data) {
  364. const { List, RegList } = res.Data;
  365. indexData = List
  366. ? List.map((item) => {
  367. item.ClassifyName = item.IndexName;
  368. item.ClassifyId = item.ClassifyId + "-" + item.IndexId;
  369. item.isLeaf = true;
  370. return item;
  371. })
  372. : [];
  373. //地区数据
  374. this.selectOptions = RegList || [];
  375. this.areaSelect = this.selectOptions.length ? RegList[0] : null;
  376. }
  377. return indexData;
  378. },
  379. //获取指标/指标列表数据
  380. getTableData(data) {
  381. this.tableLoading = true;
  382. const { IndexId, ClassifyId } = data;
  383. nationalInterface
  384. .getIndexDetail({
  385. ClassifyId: Number((ClassifyId + "").split("-")[0]),
  386. IndexId: IndexId ? IndexId : "", //不加IndexId获取的是指标列表数据
  387. Reg: this.areaSelect ? this.areaSelect : "",
  388. })
  389. .then((res) => {
  390. if (res.Ret !== 200) return;
  391. //转换成lzTable的格式
  392. const { totalTableOption, totalTableDate } = this.formatTableListData(
  393. res.Data || []
  394. );
  395. this.tableOption = totalTableOption;
  396. this.tableDate = totalTableDate;
  397. this.tableShow = true;
  398. this.tableLoading = false;
  399. this.$nextTick(() => {
  400. //将table-wrap的scroll置顶
  401. const tableWrap = document.querySelector(".data-table");
  402. tableWrap.scrollTo({
  403. top: 0,
  404. left: 0,
  405. behavior: "smooth",
  406. });
  407. });
  408. });
  409. },
  410. //格式化单个指标数据
  411. formatTableData(data) {
  412. let tableOption = [],
  413. tableDate = [];
  414. tableOption = data;
  415. //获取data中的日期作为tableDate
  416. tableDate = data.DataList
  417. ? data.DataList.map((item) => {
  418. return item.DataTime || "";
  419. })
  420. : [];
  421. return { tableOption, tableDate };
  422. },
  423. //格式化指标分类数据
  424. formatTableListData(data) {
  425. let totalTableOption = [],
  426. totalTableDate = [];
  427. data.forEach((item) => {
  428. const { tableOption, tableDate } = this.formatTableData(item);
  429. totalTableOption.push(tableOption);
  430. totalTableDate = totalTableDate.concat(tableDate);
  431. });
  432. //totalTableDate 去重
  433. totalTableDate = [...new Set(totalTableDate)].sort().reverse();
  434. //数据长度补空
  435. //tableOption不满7个,补充至7个
  436. if (totalTableOption.length < 7) {
  437. const padding = 7 - totalTableOption.length;
  438. for (let i = 0; i < padding; i++) {
  439. totalTableOption.push({
  440. DataList: [],
  441. });
  442. }
  443. }
  444. //tableDate不满12个 补充至12个
  445. if (totalTableDate.length < 12) {
  446. const padding = 12 - totalTableDate.length;
  447. for (let i = 0; i < padding; i++) {
  448. totalTableDate.push("");
  449. }
  450. }
  451. return { totalTableOption, totalTableDate };
  452. },
  453. //切换选中节点
  454. async changeNode(data, node) {
  455. this.currentNode = node;
  456. if (node.isLeaf) {
  457. this.getTableData(data);
  458. }
  459. if (data.IsParent === 0) {
  460. //获取地区数据
  461. data.isLoad && (await this.getIndexData(data.ClassifyId));
  462. data.isLoad && this.getTableData(data);
  463. } else {
  464. this.tableShow = false;
  465. }
  466. },
  467. //处理导出指标的url
  468. handleExport() {
  469. const exportBase =
  470. process.env.VUE_APP_API_ROOT +
  471. "/datamanage/base_from_national_statistics/index_detail";
  472. const auth = localStorage.getItem("auth") || "";
  473. const { ClassifyId, IndexId } = this.currentNode.data;
  474. const classifyId = IndexId ? "" : ClassifyId;
  475. const indexId = IndexId ? IndexId : "";
  476. const reg = this.areaSelect ? this.areaSelect : "";
  477. const urlStr =
  478. `${exportBase}` +
  479. `?ClassifyId=${classifyId}` +
  480. `&IndexId=${indexId}` +
  481. `&Reg=${reg}` +
  482. `&IsExport=true` +
  483. `&${auth}`;
  484. console.log("check url", urlStr);
  485. this.exportData(urlStr);
  486. },
  487. //导出指标
  488. exportData(url) {
  489. const link = document.createElement("a");
  490. link.href = url;
  491. link.download = "";
  492. link.click();
  493. },
  494. },
  495. created() {
  496. this.getMenuList();
  497. },
  498. mounted() {},
  499. };
  500. </script>
  501. <style lang="scss">
  502. .national-data-base-wrap {
  503. .el-submenu {
  504. .el-submenu__title {
  505. border-bottom: none !important;
  506. transition: background-color 0.3s, color 0.3s;
  507. }
  508. &.is-active {
  509. i {
  510. color: #409eff;
  511. }
  512. }
  513. }
  514. .el-tree-node {
  515. .el-tree-node__children {
  516. .el-tree-node__content {
  517. white-space: normal;
  518. min-height: 26px;
  519. height: auto;
  520. margin-bottom: 5px;
  521. }
  522. }
  523. }
  524. }
  525. </style>
  526. <style scoped lang="scss">
  527. .national-data-base-wrap {
  528. .nav-bar {
  529. display: flex;
  530. background-color: white;
  531. border: 1px solid #dcdfe6;
  532. box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.08);
  533. border-radius: 4px;
  534. align-items: center;
  535. margin-bottom: 20px;
  536. .nav-list {
  537. margin-right: 30px;
  538. flex: 1;
  539. ul {
  540. border-bottom: none;
  541. li {
  542. border-bottom: none;
  543. }
  544. }
  545. }
  546. .el-button {
  547. width: 120px;
  548. height: 40px;
  549. margin-right: 20px;
  550. }
  551. }
  552. .data-show-cotainer {
  553. display: flex;
  554. position: relative;
  555. .slide-btn-icon{
  556. &.slide-left{
  557. left:385px;
  558. }
  559. &.slide-right{
  560. left: 0;
  561. }
  562. }
  563. .wrap {
  564. height: calc(100vh - 194px);
  565. background-color: white;
  566. border: 1px solid #dcdfe6;
  567. border-radius: 4px;
  568. padding: 20px;
  569. box-sizing: border-box;
  570. }
  571. .data-list-wrap {
  572. min-width: 400px;
  573. width: 400px;
  574. margin-right: 20px;
  575. .sub-menu-title {
  576. font-size: 16px;
  577. font-weight: bold;
  578. margin-bottom: 20px;
  579. }
  580. .search-box {
  581. .el-select {
  582. width: 100%;
  583. }
  584. }
  585. .data-list {
  586. margin-top: 20px;
  587. height: calc(100vh - 333px);
  588. overflow-y: auto;
  589. }
  590. }
  591. .data-table-wrap {
  592. flex: 1;
  593. .select-box {
  594. .el-select {
  595. width: 240px;
  596. }
  597. margin-bottom: 20px;
  598. }
  599. .data-table {
  600. overflow: auto;
  601. border-left: 1px solid #dcdfe6;
  602. border-right: 1px solid #dcdfe6;
  603. width: 100%;
  604. height: calc(100vh - 292px);
  605. .table-header,
  606. .table-container {
  607. width: 1000px; //让data-table出滚动条,只要小于7*179(lz-table-td min-width)
  608. }
  609. }
  610. &.nodata-cont {
  611. text-align: center;
  612. }
  613. }
  614. }
  615. }
  616. </style>