index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. <template>
  2. <view class="detail-page">
  3. <view class="tabs">
  4. <view class="tab" v-for="(t, i) in tabs" :key="t.label" :class="{ active: category == t.value }"
  5. :style="{ opacity: loading ? 0.5 : 1 }" @click="selectTab(t)">
  6. {{ t.label }}
  7. </view>
  8. </view>
  9. <view class="card" :style="{ height: tableBodyHeight + 'rpx' }">
  10. <scroll-view class="grid-scroll" scroll-y="true" scroll-x="true" :show-scrollbar="false"
  11. :style="{ maxHeight: tableBodyHeight + 'rpx' }" enhanced @scrolltolower="onTableScrollToLower">
  12. <view :style="{
  13. width: '1296px',
  14. }">
  15. <view class="grid-header">
  16. <text class="hcell hcell-sticky-left">查询客户</text>
  17. <text class="hcell">社会信用代码</text>
  18. <text class="hcell">加入时间</text>
  19. <text class="hcell">品种</text>
  20. <text class="hcell">上游客户</text>
  21. <text class="hcell">货源片区</text>
  22. <text class="hcell">数量</text>
  23. <text class="hcell">批号</text>
  24. <text class="hcell">监管码样本(抽样)</text>
  25. <text class="hcell">终端到达数量</text>
  26. </view>
  27. <view v-if="!loading && rows.length > 0" class="grid-body">
  28. <view v-for="(row, idx) in rows" :key="idx + '-' + (row.produceBatchNo || '')" class="grow">
  29. <view v-if="row.flags.entName" class="gcell gcol-1 gspan" :class="['sticky-left']"
  30. :style="{ gridRowEnd: 'span ' + row.spans.entName }">{{ row.entName }}</view>
  31. <view v-if="row.flags.entName" class="gcell gcol-2 gspan"
  32. :style="{ gridRowEnd: 'span ' + row.spans.entName }">{{ row.orgCode }}</view>
  33. <view v-if="row.flags.entName" class="gcell gcol-3 gspan"
  34. :style="{ gridRowEnd: 'span ' + row.spans.entName }">{{ formatDate(row.time,
  35. 'YYYY-MM-DD') || '--' }}</view>
  36. <view v-if="row.flags.physicName" class="gcell gcol-4 gspan"
  37. :style="{ gridRowEnd: 'span ' + row.spans.physicName }">{{ row.physicName }}</view>
  38. <view v-if="row.flags.currentENTName" class="gcell gcol-5 gspan"
  39. :style="{ gridRowEnd: 'span ' + row.spans.currentENTName }">{{ row.currentENTName }}
  40. </view>
  41. <view class="gcell gcol-6">{{ row.fromRegionName }}</view>
  42. <view class="gcell gcol-7">{{ row.fromQuantity }}</view>
  43. <view class="gcell gcol-8">{{ row.produceBatchNo }}</view>
  44. <view class="gcell gcol-9">{{ row.tracCode }}</view>
  45. <view class="gcell gcol-10">{{ row.toQuantity }}</view>
  46. </view>
  47. <view class="gcell gcell-full loading-row" v-if="hasMore">
  48. <view class="loading-wrapper">
  49. <image class="loading-icon" src="../../../static/images/loading.png" />
  50. </view>
  51. </view>
  52. </view>
  53. </view>
  54. </scroll-view>
  55. <view v-if="loading" class="loading-wrap">
  56. <view class="loading-row">
  57. <view class="loading-wrapper">
  58. <image class="loading-icon" src="../../../static/images/loading.png" />
  59. </view>
  60. </view>
  61. </view>
  62. <view v-if="showEmptyData" class="empty-data">
  63. <EmptyView text="无相关数据" />
  64. </view>
  65. </view>
  66. <view class="fab-btn" @click="downloadTable">
  67. <text class="fab-text">下载表格</text>
  68. </view>
  69. </view>
  70. </template>
  71. <script>
  72. import EmptyView from "../../../wigets/empty.vue";
  73. import request, { downloadFile } from '../../../request/index.js'
  74. import { formatDate } from '../../../utils/utils.js'
  75. export default {
  76. components: {
  77. EmptyView,
  78. },
  79. data() {
  80. return {
  81. isLoading: false,
  82. tableBodyHeight: 0,
  83. baseRowHeight: 76,
  84. tabs: [
  85. {
  86. label: "药品购进",
  87. value: "1",
  88. },
  89. {
  90. label: "药品销售",
  91. value: "2",
  92. },
  93. ],
  94. category: "1",
  95. loading: true,
  96. rows: [],
  97. totalCount: 0,
  98. fetchLoading: false,
  99. hasMore: true,
  100. pageNum: 1,
  101. pageSize: 15,
  102. };
  103. },
  104. created() {
  105. this.tableBodyHeight = 14 * 80;
  106. this.fetchList();
  107. },
  108. computed: {
  109. showEmptyData() {
  110. if (this.loading) return false;
  111. if (this.rows.length === 0) {
  112. return true;
  113. }
  114. return false;
  115. },
  116. },
  117. methods: {
  118. formatDate,
  119. selectTab(t) {
  120. if (this.loading) return;
  121. this.category = t.value;
  122. this.resetFetch();
  123. },
  124. computeBlackLeafCount(node) {
  125. if (!node || !node.dataList || !node.dataList.length) return 1;
  126. let sum = 0;
  127. node.dataList.forEach((c) => {
  128. sum += this.computeBlackLeafCount(c);
  129. });
  130. node.leafCount = sum;
  131. return sum;
  132. },
  133. buildBlackRows(data) {
  134. const res = [];
  135. (data || []).forEach((companyNode) => {
  136. const cLeaves = this.computeBlackLeafCount(companyNode);
  137. let companyStarted = false;
  138. (companyNode.dataList || []).forEach((expNode) => {
  139. const eLeaves = this.computeBlackLeafCount(expNode);
  140. let exporterStarted = false;
  141. const groups = {};
  142. (expNode.dataList || []).forEach((item) => {
  143. const key = item.currentENTName || "";
  144. if (!groups[key]) groups[key] = [];
  145. groups[key].push(item);
  146. });
  147. Object.keys(groups).forEach((prodName) => {
  148. const group = groups[prodName];
  149. let productStarted = false;
  150. group.forEach((batch) => {
  151. res.push({
  152. entName: companyNode.entName,
  153. orgCode: companyNode.orgCode,
  154. time: batch.time,
  155. physicName: expNode.physicName,
  156. currentENTName: prodName,
  157. fromRegionName: batch.fromRegionName,
  158. fromQuantity: batch.fromQuantity,
  159. produceBatchNo: batch.produceBatchNo,
  160. tracCode: batch.tracCode,
  161. toQuantity: batch.toQuantity,
  162. spans: {
  163. entName: companyStarted ? 0 : cLeaves,
  164. physicName: exporterStarted ? 0 : eLeaves,
  165. currentENTName: productStarted ? 0 : group.length,
  166. },
  167. flags: {
  168. entName: !companyStarted,
  169. physicName: !exporterStarted,
  170. currentENTName: !productStarted,
  171. },
  172. });
  173. companyStarted = true;
  174. exporterStarted = true;
  175. productStarted = true;
  176. });
  177. });
  178. });
  179. });
  180. return res;
  181. },
  182. onTableScrollToLower(e) {
  183. if (e?.detail?.direction === 'right') return
  184. // if (this.isLoading) return;
  185. // if (this.rows.length >= this.totalCount) return;
  186. // this.isLoading = true;
  187. // const remain = this.totalCount - this.rows.length;
  188. // const toAdd = Math.min(15, remain);
  189. // setTimeout(() => {
  190. // const more = this.rows.slice(0, toAdd);
  191. // this.rows = this.rows.concat(more);
  192. // this.isLoading = false;
  193. // }, 800);
  194. this.fetchList();
  195. },
  196. resetFetch() {
  197. this.loading = true;
  198. this.rows = [];
  199. this.totalCount = 0;
  200. this.fetchLoading = false;
  201. this.hasMore = true;
  202. this.pageNum = 1;
  203. this.fetchList();
  204. },
  205. fetchList() {
  206. if (this.fetchLoading || !this.hasMore) return;
  207. this.fetchLoading = true;
  208. let url = `/report/getBlacklistReport`;
  209. try {
  210. request(url, {
  211. pageNum: this.pageNum,
  212. pageSize: this.pageSize,
  213. path: '/blacklist/index.vue',
  214. }).then(res => {
  215. if (res.code == 200) {
  216. const { total, list } = res.data || {};
  217. this.totalCount = total || 0;
  218. let _list = [];
  219. if (Array.isArray(list)) {
  220. _list = this.buildBlackRows(list);
  221. }
  222. this.rows = [...this.rows, ..._list];
  223. if (this.rows.length < this.totalCount) {
  224. this.hasMore = true;
  225. this.pageNum++;
  226. } else {
  227. this.hasMore = false;
  228. }
  229. }
  230. this.isLoading = false;
  231. this.loading = false;
  232. this.fetchLoading = false;
  233. }).catch(() => {
  234. this.isLoading = false;
  235. this.loading = false;
  236. this.fetchLoading = false;
  237. })
  238. } catch (res) {
  239. this.isLoading = false;
  240. this.loading = false;
  241. this.fetchLoading = false;
  242. }
  243. },
  244. downloadTable() {
  245. downloadFile('', {
  246. id: '',
  247. path: '/blacklist/index.vue',
  248. })
  249. },
  250. },
  251. };
  252. </script>
  253. <style scoped>
  254. .detail-page {
  255. box-sizing: border-box;
  256. padding: 24rpx;
  257. position: relative;
  258. min-height: 100vh;
  259. background: #f3f6f9;
  260. padding-bottom: calc(50rpx + env(safe-area-inset-bottom));
  261. }
  262. .tabs {
  263. display: flex;
  264. align-items: center;
  265. background: #fff;
  266. border-radius: 90rpx;
  267. padding: 10rpx;
  268. margin-bottom: 30rpx;
  269. }
  270. .tab {
  271. flex: 1;
  272. height: 70rpx;
  273. display: flex;
  274. align-items: center;
  275. justify-content: center;
  276. padding: 0 24rpx;
  277. border-radius: 70rpx;
  278. font-size: 28rpx;
  279. color: #333;
  280. }
  281. .tab+.tab {
  282. margin-left: 12rpx;
  283. }
  284. .tab.active {
  285. background: #2c69ff;
  286. color: #fff;
  287. }
  288. .card {
  289. position: relative;
  290. margin-top: 12rpx;
  291. font-size: 30rpx;
  292. overflow: hidden;
  293. }
  294. .grid-header {
  295. position: sticky;
  296. top: 0;
  297. z-index: 10;
  298. display: grid;
  299. grid-template-columns: 340rpx 300rpx 220rpx 220rpx 220rpx 220rpx 220rpx 220rpx 340rpx 200rpx;
  300. }
  301. .grid-header .hcell {
  302. background: #eaf2ff;
  303. font-weight: bold;
  304. color: #2c69ff;
  305. }
  306. .hcell {
  307. height: 76rpx;
  308. display: flex;
  309. align-items: center;
  310. justify-content: center;
  311. }
  312. .hcell:last-child {
  313. border-right: none;
  314. }
  315. .hcell-sticky-left {
  316. position: sticky;
  317. left: 0;
  318. z-index: 11;
  319. }
  320. .grid-scroll {
  321. border-radius: 16rpx;
  322. margin-top: 8rpx;
  323. overflow: hidden;
  324. }
  325. .grid-body {
  326. background: #fff;
  327. display: grid;
  328. grid-template-columns: 340rpx 300rpx 220rpx 220rpx 220rpx 220rpx 220rpx 220rpx 340rpx 200rpx;
  329. grid-auto-rows: auto;
  330. }
  331. .grow {
  332. display: contents;
  333. }
  334. .gcell {
  335. display: flex;
  336. align-items: center;
  337. justify-content: center;
  338. border-bottom: 1rpx solid #eef0f4;
  339. border-right: 1rpx solid #eef0f4;
  340. background: #fff;
  341. color: #333;
  342. padding: 0 20rpx;
  343. text-align: center;
  344. line-height: 50rpx;
  345. }
  346. .gcell:last-child {
  347. border-right: none;
  348. }
  349. .gcell.gcell-full {
  350. grid-column: 1 / -1;
  351. background: transparent;
  352. border: none;
  353. }
  354. .gspan {
  355. background: #fff;
  356. }
  357. .gcol-1 {
  358. grid-column: 1;
  359. }
  360. .gcol-2 {
  361. grid-column: 2;
  362. }
  363. .gcol-3 {
  364. grid-column: 3;
  365. }
  366. .gcol-4 {
  367. grid-column: 4;
  368. }
  369. .gcol-5 {
  370. grid-column: 5;
  371. }
  372. .gcol-6 {
  373. grid-column: 6;
  374. }
  375. .gcol-7 {
  376. grid-column: 7;
  377. }
  378. .gcol-8 {
  379. grid-column: 8;
  380. }
  381. .gcol-9 {
  382. grid-column: 9;
  383. }
  384. .gcol-10 {
  385. grid-column: 10;
  386. }
  387. .sticky-left {
  388. position: sticky;
  389. left: 0;
  390. z-index: 9;
  391. background: #fff;
  392. }
  393. .loading-row {
  394. justify-content: flex-start;
  395. }
  396. .loading-wrapper {
  397. position: sticky;
  398. top: 50%;
  399. left: 0;
  400. width: calc(100vw - 48rpx);
  401. height: 76rpx;
  402. display: flex;
  403. align-items: center;
  404. justify-content: center;
  405. }
  406. .loading-icon {
  407. width: 40rpx;
  408. height: 40rpx;
  409. animation: spin 1s linear infinite;
  410. }
  411. @keyframes spin {
  412. from {
  413. transform: rotate(0deg);
  414. }
  415. to {
  416. transform: rotate(360deg);
  417. }
  418. }
  419. .empty-data,
  420. .loading-wrap {
  421. position: absolute;
  422. left: 0;
  423. top: 85rpx;
  424. z-index: 999;
  425. width: 100%;
  426. background-color: #fff;
  427. border-bottom-left-radius: 16rpx;
  428. border-bottom-right-radius: 16rpx;
  429. }
  430. .fab-btn {
  431. position: fixed;
  432. bottom: calc(50rpx + env(safe-area-inset-bottom));
  433. right: 30rpx;
  434. background-color: #007aff;
  435. color: #ffffff;
  436. padding: 20rpx 40rpx;
  437. border-radius: 50rpx;
  438. box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2);
  439. z-index: 100;
  440. }
  441. .fab-text {
  442. font-size: 28rpx;
  443. font-weight: bold;
  444. }
  445. </style>