ccfData.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. <template>
  2. <div class="CCF-container target-container" id="box" v-if="canView">
  3. <span
  4. v-show="!isLeftWrapShow"
  5. class="slide-btn-icon slide-right"
  6. @click="isLeftWrapShow = !isLeftWrapShow"
  7. >
  8. <i :class="{'el-icon-d-arrow-left':isLeftWrapShow,'el-icon-d-arrow-right':!isLeftWrapShow}"></i>
  9. </span>
  10. <div class="left-cont minHeight" id="left" v-show="isLeftWrapShow">
  11. <span
  12. v-show="isLeftWrapShow"
  13. class="slide-btn-icon slide-left"
  14. @click="isLeftWrapShow = !isLeftWrapShow"
  15. >
  16. <i :class="{'el-icon-d-arrow-left':isLeftWrapShow,'el-icon-d-arrow-right':!isLeftWrapShow}"></i>
  17. </span>
  18. <div class="left-top">
  19. <el-button
  20. v-permission="permissionBtn.dataSourcePermission.ccfData_exportExcel"
  21. style="width: 100%"
  22. type="primary"
  23. plain
  24. size="medium"
  25. @click="exportClick"
  26. :loading="btnload"
  27. :disabled="!select_classify&&!leftSearchTradeCode"
  28. ><!-- 导出Excel -->{{$t('Common.exp_excel')}}</el-button
  29. >
  30. <el-autocomplete
  31. style="margin: 20px 0; width: 100%"
  32. prefix-icon="el-icon-search"
  33. v-model="leftSearchVal"
  34. :fetch-suggestions="handleLeftSearch"
  35. :trigger-on-focus="false"
  36. :placeholder="$t('Edb.InputHolderAll.input_name_orid')"
  37. @select="handleSelectLeftSearchval"
  38. popper-class="el-autocomplete-suggestion-data-entry"
  39. clearable
  40. @clear="clearSearch"
  41. >
  42. <template slot-scope="scope">
  43. <div v-if="scope.item.nodata" style="text-align: center">
  44. <!-- 暂无数据 -->{{$t('Table.prompt_slogan')}}
  45. </div>
  46. <div v-else>
  47. {{ scope.item.IndexName }}
  48. </div>
  49. </template>
  50. </el-autocomplete>
  51. </div>
  52. <div class="scroll-wrap">
  53. <el-tree
  54. ref="treeRef"
  55. class="target_tree word-wrap"
  56. :data="classifyList"
  57. node-key="ClassifyId"
  58. :props="{
  59. label: 'ClassifyName',
  60. children: 'Child',
  61. }"
  62. :current-node-key="select_classify"
  63. check-strictly
  64. highlight-current
  65. :default-expanded-keys="defaultShowNodes"
  66. @node-expand="handleNodeExpand"
  67. @node-collapse="handleNodeCollapse"
  68. :empty-text="$t('Common.no_classify_msg')"
  69. @current-change="nodeChangeHandle"
  70. >
  71. </el-tree>
  72. </div>
  73. <span
  74. class="move-btn resize"
  75. v-drag
  76. id="resize"
  77. >
  78. </span>
  79. </div>
  80. <div
  81. class="right-cont minHeight"
  82. id="right"
  83. v-loading="dataloading"
  84. :element-loading-text="$t('Table.data_loading')"
  85. >
  86. <template v-if="rightShow">
  87. <div class="right-box" @scroll="scrollHandle">
  88. <div class="data-header">
  89. <lz-table
  90. :tableOption="tableOption"
  91. tableType="header"
  92. ref="table"
  93. source="ccf"
  94. />
  95. </div>
  96. <div class="data-cont" v-if="dateArr.length">
  97. <lz-table
  98. :tableOption="tableOption"
  99. tableType="data"
  100. :dateArr="dateArr"
  101. source="ccf"
  102. />
  103. </div>
  104. </div>
  105. </template>
  106. <div v-else class="nodata-cont">
  107. <tableNoData :text="$t('Table.prompt_slogan')"/>
  108. </div>
  109. </div>
  110. </div>
  111. </template>
  112. <script>
  113. import lzTable from "@/components/lzTable.vue";
  114. import { ccfDataInterface } from "@/api/api.js";
  115. export default {
  116. name: "ccfData",
  117. components: { lzTable },
  118. directives: {
  119. drag(el, bindings) {
  120. el.onmousedown = function (e) {
  121. var init = e.clientX;
  122. // console.log(init);
  123. var box = $('#box')[0];
  124. // console.log(box.clientWidth)
  125. let total_wid = box.offsetWidth;
  126. var left = $('#left')[0];
  127. var right = $('#right')[0];
  128. var initWidth = left.offsetWidth;
  129. document.onmousemove = function (e) {
  130. var end = e.clientX;
  131. var newWidth = end - init + initWidth;
  132. left.style.width = newWidth + 'px';
  133. right.style.width = newWidth > 320 ? total_wid - newWidth + 'px' : total_wid - 320 + 'px';
  134. };
  135. document.onmouseup = function () {
  136. document.onmousemove = document.onmouseup = null;
  137. e.releaseCapture && e.releaseCapture();
  138. };
  139. e.setCapture && e.setCapture();
  140. return false;
  141. };
  142. }
  143. },
  144. data() {
  145. return {
  146. isLeftWrapShow:true,
  147. exportBase:process.env.VUE_APP_API_ROOT + "/datamanage/ccf/export", //CCF数据导出接口
  148. dataloading: false,
  149. rightShow: false,
  150. select_classify: 0,
  151. classifyList: [],
  152. tableOption: [],
  153. dateArr: [], //最长的日期数组
  154. btnload: false,
  155. page_no: 1,
  156. page_size: 20,
  157. havemore: true, //是否还有数据
  158. leftSearchVal: "", //左侧搜索值
  159. leftSearchTradeCode: "", //如果是搜索选择的 则有此code
  160. isShowSingleData: false, //右侧是否展示的是单个指标数据
  161. defaultShowNodes: [], //展开的节点
  162. };
  163. },
  164. methods: {
  165. /* 获取分类 */
  166. getClassify() {
  167. ccfDataInterface.classifyList().then((res) => {
  168. if (res.Ret !== 200) return;
  169. this.classifyList = res.Data || [];
  170. this.select_classify = this.classifyList[0].Child[0].ClassifyId
  171. this.getDataList();
  172. this.$nextTick(()=>{
  173. this.orientationNode(this.select_classify)
  174. })
  175. });
  176. },
  177. /* 获取数据 */
  178. getDataList: _.throttle(function () {
  179. this.isShowSingleData = false;
  180. this.dataloading = true;
  181. ccfDataInterface
  182. .dataList({
  183. ClassifyId: this.select_classify,
  184. PageSize: this.page_size,
  185. CurrentIndex: this.page_no,
  186. })
  187. .then((res) => {
  188. this.rightShow = true;
  189. if (res.Ret !== 200) return;
  190. // 找出最多的页码 判断是否还有数据
  191. let page_arrs = res.Data.map((item) => item.Paging.Pages);
  192. let totalPage = Math.max.apply(Math, page_arrs);
  193. this.havemore = this.page_no < totalPage ? true : false;
  194. // 合并数据
  195. if (this.page_no === 1) {
  196. this.tableOption = res.Data;
  197. } else {
  198. this.tableOption.forEach((item) => {
  199. res.Data.forEach((_item) => {
  200. if (item.IndexCode === _item.IndexCode) {
  201. item.DataList = item.DataList.concat(_item.DataList);
  202. }
  203. });
  204. });
  205. }
  206. // 合并所有指标中的日期 作为日期数组
  207. let arr = res.Data.map((item) => {
  208. return item.DataList;
  209. });
  210. let obj = [];
  211. for (let i of arr) {
  212. for (let j of i) {
  213. obj.push(j.DataTime);
  214. }
  215. }
  216. let arr2 = [...new Set(obj)].sort().reverse();
  217. let concatArr = [...new Set([...this.dateArr, ...arr2])]
  218. .sort()
  219. .reverse();
  220. this.dateArr = this.page_no === 1 ? arr2 : concatArr;
  221. /* 不满6个追加6个空的显示一排 别问 问就是为了美观 */
  222. if (this.tableOption.length < 7)
  223. for (let i = 0; i < 7; i++) {
  224. this.tableOption.push({
  225. DataList: [],
  226. });
  227. if (this.tableOption.length >= 7) break;
  228. }
  229. //数据最大长度小于12个 追加数据满12个 别问 问就是为了美观
  230. if (this.dateArr.length < 12)
  231. for (let i = 0; i < 12; i++) {
  232. this.dateArr.push("");
  233. if (this.dateArr.length >= 12) break;
  234. }
  235. this.dataloading = false;
  236. this.page_no === 1 &&
  237. this.$nextTick(() => {
  238. this.rightShow && this.initWidth();
  239. });
  240. });
  241. }, 200),
  242. // 获取单个指标数据
  243. async getTargetDataList(code) {
  244. this.isShowSingleData = true;
  245. this.dataloading = true;
  246. try {
  247. const res = await ccfDataInterface.getTargetDataList({
  248. IndexCode: code,
  249. });
  250. this.rightShow = true;
  251. if (res.Ret !== 200) return;
  252. const DataList = res.Data.Data || [];
  253. // 合并数据
  254. this.tableOption = [
  255. {
  256. DataList: DataList,
  257. ...res.Data,
  258. },
  259. ];
  260. this.orientationNode(res.Data ? res.Data.ClassifyId:-1)
  261. // 这里是单个指标所以不用合并日期
  262. const arr = DataList.map((item) => item.DataTime);
  263. this.dateArr = [...new Set(arr)].sort().reverse();
  264. /* 不满6个追加6个空的显示一排 别问 问就是为了美观 */
  265. for (let i = 0; i < 7; i++) {
  266. this.tableOption.push({
  267. DataList: [],
  268. });
  269. if (this.tableOption.length >= 7) break;
  270. }
  271. //数据最大长度小于12个 追加数据满12个 别问 问就是为了美观
  272. if (this.dateArr.length < 12)
  273. for (let i = 0; i < 12; i++) {
  274. this.dateArr.push("");
  275. if (this.dateArr.length >= 12) break;
  276. }
  277. this.dataloading = false;
  278. this.rightShow && this.initWidth();
  279. } catch (err) {
  280. console.log(err);
  281. }
  282. },
  283. // 左侧目录定位
  284. orientationNode(Id){
  285. this.select_classify = Id;
  286. this.defaultShowNodes.push(Id)
  287. this.$refs.treeRef.setCurrentKey(Id);
  288. this.$nextTick(() => {
  289. setTimeout(()=>{
  290. let parent = document.getElementsByClassName('scroll-wrap')[0]
  291. let node = parent.querySelector('.is-current')
  292. parent.scrollTo({
  293. top: node.offsetTop-200
  294. })
  295. },300)
  296. })
  297. },
  298. // 树节点展开
  299. handleNodeExpand(data) {
  300. // 保存当前展开的节点
  301. let flag = this.defaultShowNodes.some((item) => item === data.ClassifyId);
  302. if (!flag) {
  303. // 不存在则存到数组里
  304. this.defaultShowNodes.push(data.ClassifyId);
  305. }
  306. },
  307. // 树节点关闭
  308. handleNodeCollapse(data) {
  309. let shouldCollapseIds=data.Child ? [...data.Child.map(it => it.ClassifyId),data.ClassifyId] : [data.ClassifyId]
  310. this.defaultShowNodes = this.defaultShowNodes.filter(it =>{
  311. return !shouldCollapseIds.includes(it)
  312. })
  313. },
  314. initWidth() {
  315. this.$nextTick(() => {
  316. $(".right-box")[0].style.width =
  317. this.$refs.table.$el.clientWidth + 5 + "px";
  318. $(".right-box")[0].scrollTop = 0;
  319. $(".right-box")[0].scrollLeft = 0;
  320. });
  321. },
  322. /* 无频度的异常显示处理 7*12*/
  323. nodataDeal() {
  324. this.tableOption = [];
  325. this.dateArr = [];
  326. for (let i = 0; i < 7; i++) {
  327. this.tableOption.push({
  328. DataList: [],
  329. });
  330. if (this.tableOption.length >= 7) break;
  331. }
  332. for (let i = 0; i < 12; i++) {
  333. this.dateArr.push("");
  334. if (this.dateArr.length >= 12) break;
  335. }
  336. },
  337. /* 滚动加载 */
  338. scrollHandle(e) {
  339. if (this.isShowSingleData) return;
  340. const dom = e.target;
  341. let total = dom.scrollTop + dom.clientHeight;
  342. if (total >= dom.scrollHeight && this.havemore) {
  343. this.page_no++;
  344. console.log("load下一页");
  345. this.getDataList();
  346. }
  347. },
  348. /* 数据导出 */
  349. exportClick() {
  350. this.btnload = true;
  351. const link = document.createElement("a");
  352. link.href = this.exportDataUrl;
  353. link.download = "";
  354. link.click();
  355. setTimeout(() => {
  356. this.btnload = false;
  357. }, 5000);
  358. },
  359. //左侧搜索
  360. async handleLeftSearch(query, cb) {
  361. cb([]);
  362. if (!query) return;
  363. const res = await ccfDataInterface.getTargetListByName({
  364. Keyword: query,
  365. });
  366. if (res.Ret === 200) {
  367. let arr = res.Data || [];
  368. if (!arr.length) {
  369. cb([{ nodata: true }]);
  370. } else {
  371. cb(arr);
  372. }
  373. }
  374. },
  375. // 选中左侧搜索值
  376. handleSelectLeftSearchval(e) {
  377. if (!e.IndexCode) return;
  378. this.leftSearchTradeCode = e.IndexCode;
  379. this.leftSearchVal = e.IndexName;
  380. // 获取单独指标数据
  381. this.getTargetDataList(e.IndexCode);
  382. },
  383. // 对[#,;]转义
  384. escapeStr(str) {
  385. return str.replace(/#/g, escape("#")).replace(/;/g, escape(";"));
  386. },
  387. //改变选中节点
  388. nodeChangeHandle(data, node) {
  389. if (data.Level==1) return;
  390. this.select_classify = data.ClassifyId;
  391. this.leftSearchVal=''
  392. this.page_no = 1;
  393. this.page_size = 20;
  394. this.getDataList()
  395. },
  396. clearSearch(){
  397. this.page_no=1
  398. this.leftSearchTradeCode=''
  399. this.getDataList();
  400. }
  401. },
  402. computed: {
  403. exportDataUrl() {
  404. // 数据导出接口
  405. let urlStr = this.exportBase;
  406. // token
  407. urlStr += `?${localStorage.getItem("auth") || ""}`;
  408. if (this.isShowSingleData) {
  409. // 指标id
  410. urlStr += `&IndexCode=${
  411. this.isShowSingleData ? this.leftSearchTradeCode : ""
  412. }`;
  413. } else {
  414. // 目录id
  415. urlStr += `&ClassifyId=${
  416. this.isShowSingleData ? "" : this.select_classify
  417. }`;
  418. }
  419. return this.escapeStr(urlStr);
  420. },
  421. canView(){
  422. return this.permissionBtn.isShowBtn('dataSourcePermission','ccfData_view')
  423. }
  424. },
  425. mounted() {
  426. this.canView && this.getClassify();
  427. },
  428. };
  429. </script>
  430. <style lang="scss" scoped>
  431. @import "../css/customtree.scss";
  432. @import "../css/baseTargetPage.scss";
  433. .CCF-container {
  434. display: flex;
  435. * {
  436. box-sizing: border-box;
  437. }
  438. .minHeight {
  439. height: calc(100vh - 120px);
  440. background-color: #fff;
  441. box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.05);
  442. border-radius: 4px;
  443. }
  444. .left-cont {
  445. min-width: 300px;
  446. width: 300px;
  447. margin-right: 20px;
  448. padding: 30px 0;
  449. overflow: hidden;
  450. position: relative;
  451. .left-top {
  452. padding: 0 20px;
  453. }
  454. .scroll-wrap {
  455. padding: 0 20px;
  456. height: calc(100vh - 280px);
  457. overflow-y: auto;
  458. margin-right: 20px;
  459. .target_tree {
  460. color: #333;
  461. }
  462. }
  463. .move-btn {
  464. height: 100%;
  465. width: 4px;
  466. position: absolute;
  467. right: 0px;
  468. top: 0;
  469. &:hover {
  470. cursor: col-resize;
  471. }
  472. }
  473. }
  474. .right-cont {
  475. width: 82%;
  476. padding: 30px;
  477. .right-box {
  478. max-width: 100%;
  479. max-height: calc(100vh - 180px);
  480. border-left: 1px solid #dcdfe6;
  481. border-right: 1px solid #dcdfe6;
  482. overflow: auto;
  483. .data-header {
  484. width: 100%;
  485. position: sticky;
  486. top: 0;
  487. z-index: 2;
  488. }
  489. }
  490. .nodata-cont {
  491. width: 200px !important;
  492. text-align: center;
  493. color: #666;
  494. font-size: 16px;
  495. margin: 0 auto;
  496. }
  497. }
  498. }
  499. </style>