ScanRateTable.vue 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108
  1. <template>
  2. <view
  3. class="section"
  4. :style="{
  5. position: 'relative',
  6. zIndex: dropdownOpen || dropdownRegionOpen ? 9 : 'auto',
  7. }"
  8. >
  9. <view v-if="showHeader" class="section-header">
  10. <text class="section-title">{{ title }}</text>
  11. <view :style="{ display: 'flex' }">
  12. <view
  13. v-if="showSelector && tableType === 'variety'"
  14. class="selector-wrap"
  15. >
  16. <view class="selector" @click.stop="toggleDropdownRegion">
  17. <text class="selector-text">{{
  18. region?.regionCode ? region.regionName : "选择片区"
  19. }}</text>
  20. <text
  21. class="selector-arrow"
  22. :class="{ open: dropdownRegionOpen }"
  23. ></text>
  24. </view>
  25. <view
  26. class="dropdown"
  27. v-show="dropdownRegionOpen"
  28. :style="
  29. dropdownDirection === 'top'
  30. ? { top: 'auto', bottom: '48rpx' }
  31. : {}
  32. "
  33. @click.stop
  34. >
  35. <view class="dropdown-search-bar">
  36. <input
  37. class="dropdown-search-input"
  38. v-model="regionSearchText"
  39. @input="onRegionSearch"
  40. placeholder="搜索..."
  41. />
  42. </view>
  43. <scroll-view
  44. scroll-y="true"
  45. class="dropdown-scroll-view"
  46. lower-threshold="80"
  47. @scrolltolower="loadMoreRegions"
  48. >
  49. <view
  50. class="dropdown-item"
  51. v-for="(p, i) in regionList"
  52. :key="p.regionCode || i"
  53. :style="
  54. p.regionCode === region?.regionCode ||
  55. (!region?.regionCode && i == 0)
  56. ? {
  57. backgroundColor: '#2c69ff',
  58. color: '#fff',
  59. }
  60. : {}
  61. "
  62. @click.stop="selectProduct('region', p)"
  63. >{{ p.regionName || p }}</view
  64. >
  65. <view
  66. v-if="regionList.length === 0"
  67. class="dropdown-item"
  68. style="text-align: center; color: #999"
  69. >暂无数据
  70. </view>
  71. </scroll-view>
  72. </view>
  73. </view>
  74. <view v-if="showSelector" class="selector-wrap">
  75. <view class="selector" @click.stop="toggleDropdown">
  76. <text class="selector-text">{{
  77. product?.drugEntBaseInfoId ? product.physicName : "选择品种"
  78. }}</text>
  79. <text class="selector-arrow" :class="{ open: dropdownOpen }"></text>
  80. </view>
  81. <view
  82. class="dropdown"
  83. v-show="dropdownOpen"
  84. :style="
  85. dropdownDirection === 'top'
  86. ? {
  87. top: 'auto',
  88. bottom: '48rpx',
  89. }
  90. : {}
  91. "
  92. @click.stop
  93. >
  94. <view class="dropdown-search-bar">
  95. <input
  96. class="dropdown-search-input"
  97. v-model="productSearchText"
  98. @input="onProductSearch"
  99. placeholder="搜索..."
  100. />
  101. </view>
  102. <scroll-view
  103. scroll-y="true"
  104. class="dropdown-scroll-view"
  105. lower-threshold="80"
  106. @scrolltolower="loadMoreProducts"
  107. >
  108. <view
  109. class="dropdown-item"
  110. v-for="(p, i) in productList"
  111. :key="p.drugEntBaseInfoId || i"
  112. :style="
  113. p.drugEntBaseInfoId === product?.drugEntBaseInfoId ||
  114. (!product?.drugEntBaseInfoId && i == 0)
  115. ? {
  116. backgroundColor: '#2c69ff',
  117. color: '#fff',
  118. }
  119. : {}
  120. "
  121. @click.stop="selectProduct('product', p)"
  122. >{{ p.physicName }}</view
  123. >
  124. <view
  125. v-if="productList.length === 0"
  126. class="dropdown-item"
  127. style="text-align: center; color: #999"
  128. >暂无数据
  129. </view>
  130. </scroll-view>
  131. </view>
  132. </view>
  133. </view>
  134. </view>
  135. <view
  136. class="table-scroll-x"
  137. :style="{
  138. height:
  139. tableHeightAuto || list.length < 7
  140. ? 'auto'
  141. : tableBodyHeight + 80 + 'rpx',
  142. maxHeight: tableHeightAuto,
  143. }"
  144. >
  145. <scroll-view
  146. class="no-scrollbar"
  147. :style="{ height: '100%' }"
  148. scroll-x="true"
  149. scroll-y="true"
  150. enhanced
  151. :show-scrollbar="false"
  152. @scrolltolower="onScrollToLower"
  153. >
  154. <view class="card" :style="{ width: tableWidth }">
  155. <view
  156. class="table-header"
  157. :style="{
  158. position: 'sticky',
  159. top: '0',
  160. zIndex: 10,
  161. fontWeight: headerBold ? 'bold' : 'normal',
  162. }"
  163. >
  164. <view
  165. v-for="(col, ci) in columnList"
  166. :key="getUid()"
  167. class="cell"
  168. :style="{
  169. ...headerCellStyle(ci, col),
  170. }"
  171. >
  172. <view
  173. v-if="isPlainTitle(col.title)"
  174. :style="{
  175. display: 'flex',
  176. alignItems: 'center',
  177. position: 'relative',
  178. }"
  179. >
  180. <text>{{ col.title }}</text>
  181. <uni-icons
  182. v-if="col.tooltip"
  183. type="help"
  184. size="16"
  185. color="#2c69ff"
  186. @tap.stop.prevent="toggleHeaderTooltip(ci)"
  187. ></uni-icons>
  188. <view
  189. v-if="headerTooltip[ci]"
  190. class="header-tooltip-wrap"
  191. @click.stop
  192. >
  193. <view class="header-tooltip">{{ col.tooltip }}</view>
  194. <view class="header-tooltip-arrow"></view>
  195. </view>
  196. </view>
  197. <rich-text v-else :nodes="titleNodes(col.title)" />
  198. </view>
  199. </view>
  200. <view v-if="!loading && list && list.length > 0" class="table-body">
  201. <template>
  202. <view class="row" v-for="(item, i) in list" :key="getUid()">
  203. <view
  204. v-for="(col, ci) in columnList"
  205. :key="getUid()"
  206. class="cell"
  207. :class="{
  208. underline: col.underline,
  209. striped: striped && (i + 1) % 2 === 0,
  210. }"
  211. :style="bodyCellStyle(ci, col)"
  212. @click="handleClick(item, col)"
  213. >{{ renderCell(item, col) }}</view
  214. >
  215. </view>
  216. <view class="row loading-row" v-if="hasmore">
  217. <view class="loading-wrapper">
  218. <image class="loading-icon" :src="loadingImg" />
  219. </view>
  220. </view>
  221. <view
  222. class="row"
  223. v-if="showSummary"
  224. :style="{
  225. position: 'sticky',
  226. bottom: '0',
  227. zIndex: 5,
  228. backgroundColor: '#fff',
  229. borderTop: '1rpx solid #eef0f4',
  230. color: summaryFontColor,
  231. }"
  232. >
  233. <view
  234. v-for="(col, ci) in columnList"
  235. :key="getUid()"
  236. class="cell"
  237. :style="{
  238. ...bodyCellStyle(ci, col),
  239. fontWeight: summaryBold ? 'bold' : 'normal',
  240. }"
  241. >{{ getSummary(col, ci) }}</view
  242. >
  243. </view>
  244. </template>
  245. </view>
  246. </view>
  247. <view
  248. v-if="loading"
  249. class="no-data-placeholder"
  250. :style="{ height: 'auto' }"
  251. >
  252. <view class="row loading-row">
  253. <view class="loading-wrapper">
  254. <image class="loading-icon" :src="loadingImg" />
  255. </view>
  256. </view>
  257. </view>
  258. <view
  259. v-if="!loading && (!list || list.length === 0)"
  260. class="no-data-placeholder"
  261. >
  262. <EmptyView />
  263. </view>
  264. </scroll-view>
  265. <view
  266. v-if="loading"
  267. class="no-data"
  268. :style="{ height: 'auto', top: cellHeight }"
  269. >
  270. <view class="row loading-row">
  271. <view class="loading-wrapper">
  272. <image class="loading-icon" :src="loadingImg" />
  273. </view>
  274. </view>
  275. </view>
  276. <view
  277. v-if="!loading && (!list || list.length === 0)"
  278. class="no-data"
  279. :style="{ top: cellHeight }"
  280. >
  281. <EmptyView />
  282. </view>
  283. </view>
  284. </view>
  285. </template>
  286. <script>
  287. import EmptyView from "../../../../wigets/empty.vue";
  288. import loadingImg from "../../../../static/images/loading.png";
  289. import { getUid } from "../../../../utils/utils.js";
  290. import request from "../../../../request/index.js";
  291. import Select from "../../../../wigets/select.vue";
  292. export default {
  293. name: "ScanRateTable",
  294. components: { EmptyView, Select },
  295. props: {
  296. params: {
  297. type: Object,
  298. default: null,
  299. },
  300. scanType: { type: String, default: "1" },
  301. api: {
  302. type: String,
  303. default: "",
  304. },
  305. tableType: { type: String, default: "customer" },
  306. title: { type: String, default: "客户扫码率" },
  307. showSelector: { type: Boolean, default: true },
  308. showHeader: { type: Boolean, default: true },
  309. dropdownDirection: { type: String, default: "bottom" },
  310. showSummary: { type: Boolean, default: true },
  311. striped: { type: Boolean, default: true },
  312. summaryStructure: {
  313. type: Array,
  314. default: () => [
  315. {
  316. key: "totalOutRate",
  317. title: "出库扫码率",
  318. unit: "%",
  319. },
  320. {
  321. key: "totalInRate",
  322. title: "入库扫码率",
  323. unit: "%",
  324. },
  325. {
  326. key: "totalSelfExaminationRate",
  327. title: "自验证率",
  328. unit: "%",
  329. },
  330. ],
  331. },
  332. summaryBold: { type: Boolean, default: false },
  333. summaryFontColor: {
  334. type: String,
  335. default: "#2c69ff",
  336. },
  337. initial: { type: Array, default: () => [] },
  338. products: { type: Array, default: () => [] },
  339. regions: { type: Array, default: () => [] },
  340. cellHeight: {
  341. type: String,
  342. default: "76rpx",
  343. },
  344. headerFontSize: {
  345. type: String,
  346. default: "32rpx",
  347. },
  348. headerBold: {
  349. type: Boolean,
  350. default: true,
  351. },
  352. bodyFontSize: {
  353. type: String,
  354. default: "32rpx",
  355. },
  356. bodyFontColor: {
  357. type: String,
  358. default: "",
  359. },
  360. //若需要横向滚动且左侧固定,则tableWidth需等于columns所有width之和(可以略小于)
  361. tableWidth: {
  362. type: String,
  363. default: "100%",
  364. },
  365. tableHeightAuto: {
  366. type: Boolean,
  367. default: false,
  368. },
  369. columns: {
  370. type: Array,
  371. default: () => [
  372. {
  373. key: "regionName",
  374. title: "区域",
  375. underline: true,
  376. fixed: true,
  377. width: "",
  378. },
  379. {
  380. key: "outScanRate",
  381. title: "出库扫码率",
  382. fixed: false,
  383. width: "",
  384. tooltip:
  385. "客户出库扫码量(与下游入库单中相同的追溯码)/下游企业入库扫码量",
  386. unit: "%",
  387. },
  388. {
  389. key: "inScanRate",
  390. title: "入库扫码率",
  391. fixed: false,
  392. width: "",
  393. tooltip:
  394. "客户入库扫码量(与上游出库单中相同的追溯码)/上游企业出库扫码量",
  395. unit: "%",
  396. },
  397. {
  398. key: "selfExaminationRate",
  399. title: "自验证率",
  400. fixed: false,
  401. width: "",
  402. tooltip: "客户出库扫码量(与入库单中相同的追溯码)/入库扫码量",
  403. unit: "%",
  404. },
  405. ],
  406. },
  407. },
  408. data() {
  409. return {
  410. dropdownOpen: false,
  411. dropdownRegionOpen: false,
  412. list: [],
  413. totalCount: 0,
  414. hasmore: true,
  415. loading: true,
  416. fetchLoading: false,
  417. loadingImg,
  418. product: null,
  419. region: null,
  420. tablePage: 1,
  421. tablePageSize: 10,
  422. summaryData: [],
  423. headerTooltip: {},
  424. regionSearchText: "",
  425. productSearchText: "",
  426. regionList: [],
  427. productList: [],
  428. };
  429. },
  430. computed: {
  431. filteredRegions() {
  432. if (!this.regionSearchText) return this.regions;
  433. const lower = this.regionSearchText.toLowerCase();
  434. return this.regions.filter(
  435. (r) =>
  436. (r.regionName || r.name || r || "")
  437. .toString()
  438. .toLowerCase()
  439. .indexOf(lower) > -1,
  440. );
  441. },
  442. filteredProducts() {
  443. if (!this.productSearchText) return this.products;
  444. const lower = this.productSearchText.toLowerCase();
  445. return this.products.filter(
  446. (p) => (p.physicName || "").toLowerCase().indexOf(lower) > -1,
  447. );
  448. },
  449. tableBodyHeight() {
  450. return 6 * 80;
  451. },
  452. columnList() {
  453. if (this.params) {
  454. const type = this.params?.type;
  455. if (type === "1" || type === "2")
  456. return this.columns.filter((i) => i.key !== "selfExaminationRate");
  457. }
  458. return this.columns;
  459. },
  460. },
  461. watch: {
  462. dropdownRegionOpen(val) {
  463. if (val) {
  464. this.regionSearchText = "";
  465. this.initRegionList();
  466. }
  467. },
  468. dropdownOpen(val) {
  469. if (val) {
  470. this.productSearchText = "";
  471. this.initProductList();
  472. }
  473. },
  474. product(val) {
  475. this.resetFetch();
  476. },
  477. region: {
  478. handler(val) {
  479. this.resetFetch();
  480. },
  481. deep: true,
  482. },
  483. params: {
  484. handler(newVal, oldVal) {
  485. if (
  486. newVal !== oldVal &&
  487. JSON.stringify(newVal) === JSON.stringify(oldVal)
  488. )
  489. return;
  490. this.resetFetch();
  491. },
  492. deep: true,
  493. },
  494. products: {
  495. handler(newVal, oldVal) {
  496. if (this.dropdownOpen) {
  497. this.initProductList();
  498. }
  499. },
  500. deep: true,
  501. },
  502. regions: {
  503. handler(newVal, oldVal) {
  504. if (this.dropdownRegionOpen) {
  505. this.initRegionList();
  506. }
  507. },
  508. deep: true,
  509. },
  510. },
  511. created() {
  512. // this.fetchLoading = false;
  513. // this.isLoading = false;
  514. // this.loading = false;
  515. // this.list =
  516. // this.initial && this.initial.length ? this.initial : this.genRows(7);
  517. // if (this.list.length > this.totalCount) {
  518. // this.list = this.list.slice(0, this.totalCount);
  519. // }
  520. // if (!this.showHeader) {
  521. // this.fetchList();
  522. // } else {
  523. // Promise.all([this.getProviceList(), this.getDrugInfoName()]).then(
  524. // () => { }
  525. // );
  526. // }
  527. this.fetchList();
  528. },
  529. methods: {
  530. renderCell(item, col) {
  531. if (!item[col.key]) return "--";
  532. const reg = /rate/i;
  533. if (reg.test(col.key))
  534. return Math.floor(Number(item[col.key])) + (col.unit || "");
  535. return item[col.key];
  536. },
  537. initRegionList() {
  538. this.regionList = this.filteredRegions.slice(0, 10);
  539. },
  540. loadMoreRegions(e) {
  541. if (this.regionList.length >= this.filteredRegions.length) return;
  542. const currentLen = this.regionList.length;
  543. const more = this.filteredRegions.slice(currentLen, currentLen + 10);
  544. this.regionList = this.regionList.concat(more);
  545. },
  546. onRegionSearch() {
  547. this.initRegionList();
  548. },
  549. initProductList() {
  550. this.productList = this.filteredProducts.slice(0, 10);
  551. },
  552. loadMoreProducts() {
  553. if (this.productList.length >= this.filteredProducts.length) return;
  554. const currentLen = this.productList.length;
  555. const more = this.filteredProducts.slice(currentLen, currentLen + 10);
  556. this.productList = this.productList.concat(more);
  557. },
  558. onProductSearch() {
  559. this.initProductList();
  560. },
  561. getUid,
  562. getSummary(col, ci) {
  563. if (ci == 0) return "合计";
  564. return Math.floor(Number(this.summaryData[ci])) + (col.unit || "");
  565. },
  566. closeAllTooltip() {
  567. this.headerTooltip = {};
  568. },
  569. toggleHeaderTooltip(i) {
  570. const value = !this.headerTooltip[i];
  571. this.headerTooltip = {};
  572. this.$set(this.headerTooltip, i, value);
  573. },
  574. // getUid(len = 16) {
  575. // const ts = Date.now().toString(36);
  576. // const rand = Math.random().toString(36).slice(2);
  577. // return (ts + rand).slice(0, len);
  578. // },
  579. // getProviceList() {
  580. // request('/common/getProviceList', {
  581. // path: 'customerScanningRate/wigets/ScanRateTable.vue',
  582. // }).then(res => {
  583. // if (res.code == 200) {
  584. // const _data = res.data;
  585. // this.regions = _data;
  586. // this.region = this.regions[0];
  587. // }
  588. // })
  589. // },
  590. // getDrugInfoName() {
  591. // request('/bills/getDrugInfoName', {
  592. // path: 'customerScanningRate/wigets/ScanRateTable.vue',
  593. // }).then(res => {
  594. // if (res.code == 200) {
  595. // const _data = res.data;
  596. // this.products = _data;
  597. // this.product = this.products[0];
  598. // }
  599. // })
  600. // },
  601. toggleDropdown() {
  602. if (this.fetchLoading) return;
  603. const next = !this.dropdownOpen;
  604. if (next) this.$emit("dropdown-open", this);
  605. this.dropdownOpen = next;
  606. },
  607. toggleDropdownRegion() {
  608. if (this.fetchLoading) return;
  609. const next = !this.dropdownRegionOpen;
  610. if (next) this.$emit("dropdown-open", this);
  611. this.dropdownRegionOpen = next;
  612. },
  613. selectProduct(type, p) {
  614. // this.$emit("change-product", p);
  615. if (type === "product") {
  616. this.product = p;
  617. this.dropdownOpen = false;
  618. } else if (type === "region") {
  619. this.region = p;
  620. this.dropdownRegionOpen = false;
  621. }
  622. },
  623. closeDropdown() {
  624. this.dropdownOpen = false;
  625. this.dropdownRegionOpen = false;
  626. },
  627. // genRows(n) {
  628. // const regions = ["广东", "广西", "湖南", "湖北", "四川", "浙江", "江苏"];
  629. // const res = [];
  630. // for (let i = 0; i < n; i++) {
  631. // const r = regions[i % regions.length];
  632. // res.push({
  633. // regionName: r,
  634. // outScanRate: `${(95 + (i % 5) * 0.5).toFixed(1)}%`,
  635. // inScanRate: `${(94 + (i % 5) * 0.6).toFixed(1)}%`,
  636. // });
  637. // }
  638. // return res;
  639. // },
  640. onScrollToLower(e) {
  641. if (e?.detail?.direction === "right") return;
  642. // if (!Array.isArray(this.list)) this.list = [];
  643. // if (this.list.length - 1 >= this.totalCount) return;
  644. // if (this.isLoading) return;
  645. // this.isLoading = true;
  646. // const remain = this.totalCount - this.list.length;
  647. // const toAdd = Math.min(7, remain);
  648. // setTimeout(() => {
  649. // if (this.list.length < this.totalCount) {
  650. // const more = this.genRows(toAdd);
  651. // this.list = this.list.concat(more);
  652. // }
  653. // this.isLoading = false;
  654. // }, 1000);
  655. this.fetchList();
  656. },
  657. resetFetch() {
  658. this.tablePage = 1;
  659. this.list = [];
  660. this.loading = true;
  661. this.hasmore = true;
  662. this.fetchList();
  663. },
  664. fetchList() {
  665. if (this._fetchDebounceTimer) clearTimeout(this._fetchDebounceTimer);
  666. const delay = 500;
  667. this._fetchDebounceTimer = setTimeout(() => {
  668. if (!this.hasmore) {
  669. setTimeout(() => {
  670. this.fetchLoading = false;
  671. this.loading = false;
  672. }, 500);
  673. return;
  674. }
  675. if (this.fetchLoading) return;
  676. this.fetchLoading = true;
  677. request(this.api, {
  678. physicName: this.product
  679. ? this.product?.physicName == "全品种"
  680. ? ""
  681. : this.product?.physicName
  682. : "",
  683. regionCode: this.region ? this.region?.regionCode || "" : "",
  684. pageNum: this.tablePage,
  685. pageSize: this.tablePageSize,
  686. path: "customerScanningRate/wigets/ScanRateTable.vue",
  687. ...(this.params || {}),
  688. }).then((res) => {
  689. if (res.code == 200) {
  690. const _data = res.data || {};
  691. let pageInfo = _data || {};
  692. if (this.tableType !== "variety") {
  693. this.summaryData = [];
  694. this.summaryData[0] = undefined;
  695. this.summaryStructure.forEach((item) => {
  696. this.summaryData.push(_data[item.key] || "--");
  697. });
  698. pageInfo = _data?.pageInfo || {};
  699. }
  700. const { list, total } = pageInfo;
  701. this.totalCount = total || 0;
  702. if (this.list.length >= this.totalCount) {
  703. this.fetchLoading = false;
  704. this.hasmore = false;
  705. this.loading = false;
  706. return;
  707. }
  708. this.list = Array.isArray(list)
  709. ? [...this.list, ...list]
  710. : [...this.list];
  711. if (this.list.length < this.totalCount) {
  712. this.hasmore = true;
  713. this.tablePage++;
  714. } else {
  715. this.hasmore = false;
  716. }
  717. // this.list.push(this.list[0]);
  718. // this.list.push(this.list[0]);
  719. // this.list.push(this.list[0]);
  720. // this.list.push(this.list[0]);
  721. // this.list.push(this.list[0]);
  722. // this.list.push(this.list[0]);
  723. // this.list.push(this.list[0]);
  724. // this.list.push(this.list[0]);
  725. // this.list.push(this.list[0]);
  726. // this.list.push(this.list[0]);
  727. // this.list.push(this.list[0]);
  728. }
  729. this.fetchLoading = false;
  730. this.loading = false;
  731. });
  732. }, delay);
  733. },
  734. headerCellStyle(ci, col) {
  735. const base = this.cellBaseStyle(ci, col);
  736. base.height = this.cellHeight;
  737. base.fontSize = this.headerFontSize;
  738. base.position = "sticky";
  739. base.zIndex = 2;
  740. base.top = "0rpx";
  741. base.background = "#eaf2ff";
  742. // base.display = 'flex';
  743. // base.alignItems = 'center';
  744. // base.justifyContent = 'center';
  745. if (ci === 0) {
  746. base.zIndex = 3;
  747. }
  748. if (col.fixed) {
  749. base.left = this.stickyLeft(ci);
  750. }
  751. return base;
  752. },
  753. bodyCellStyle(ci, col) {
  754. const base = this.cellBaseStyle(ci, col);
  755. // base.lineHeight = this.cellHeight;
  756. base.padding = "12rpx";
  757. base.fontSize = this.bodyFontSize;
  758. if (this.bodyFontColor) {
  759. base.color = this.bodyFontColor;
  760. }
  761. if (col.fixed) {
  762. base.position = "sticky";
  763. base.left = this.stickyLeft(ci);
  764. base.zIndex = 2;
  765. }
  766. return base;
  767. },
  768. cellBaseStyle(ci, col) {
  769. const style = {};
  770. // style.height = this.cellHeight;
  771. if (col && col.width) {
  772. style.width = col.width;
  773. style.flex = `0 0 ${col.width}`;
  774. } else {
  775. style.flex = 1;
  776. }
  777. return style;
  778. },
  779. stickyLeft(ci) {
  780. const parts = [];
  781. for (let i = 0; i < ci; i++) {
  782. const c = this.columns[i];
  783. if (c && c.fixed && c.width) parts.push(c.width);
  784. }
  785. if (!parts.length) return "0rpx";
  786. if (parts.length === 1) return parts[0];
  787. return `calc(${parts.join(" + ")})`;
  788. },
  789. isPlainTitle(t) {
  790. return typeof t === "string" ? t.indexOf("<") === -1 : true;
  791. },
  792. titleNodes(t) {
  793. if (typeof t !== "string") return t;
  794. let html = String(t);
  795. html = html
  796. .replace(/<\s*view/g, "<div")
  797. .replace(/<\/\s*view\s*>/g, "</div>");
  798. html = html.replace(
  799. /:style=\"\{\s*textAlign:\s*'center'\s*\}\"/g,
  800. 'style="text-align:center;"',
  801. );
  802. return html;
  803. },
  804. handleClick(data, col) {
  805. if (
  806. this.tableType === "variety" ||
  807. this.tableType === "customerDetail" ||
  808. col.key !== "regionName"
  809. )
  810. return;
  811. uni.navigateTo({
  812. url:
  813. "/traceCodePackages/traceabilityReport/pages/customerScanningRate/detail/index?regionCode=" +
  814. data.regionCode +
  815. "&regionName=" +
  816. encodeURIComponent(data.regionName) +
  817. "&drugEntBaseInfoId=" +
  818. (data.druGentBaseInfoId || "") +
  819. "&physicName=" +
  820. encodeURIComponent(this.product?.physicName || "") +
  821. "&scanType=" +
  822. this.scanType +
  823. (this.params.type ? "&type=" + this.params.type : ""),
  824. });
  825. },
  826. },
  827. };
  828. </script>
  829. <style scoped>
  830. .section {
  831. margin-top: 24rpx;
  832. transform: translate3d(0, 0, 0);
  833. }
  834. .section-header {
  835. position: relative;
  836. display: flex;
  837. align-items: center;
  838. justify-content: space-between;
  839. padding: 8rpx 8rpx;
  840. }
  841. .section-title {
  842. position: relative;
  843. font-size: 30rpx;
  844. font-weight: bold;
  845. color: #2c69ff;
  846. }
  847. .section-title::after {
  848. content: "";
  849. position: absolute;
  850. left: -20rpx;
  851. bottom: 13rpx;
  852. width: 8rpx;
  853. height: 50%;
  854. background: #2c69ff;
  855. border-radius: 10px;
  856. }
  857. .selector-wrap {
  858. position: relative;
  859. margin-right: 40rpx;
  860. }
  861. .selector {
  862. position: relative;
  863. display: flex;
  864. align-items: center;
  865. color: #2c69ff;
  866. padding-right: 30rpx;
  867. }
  868. .selector-text {
  869. font-size: 30rpx;
  870. max-width: 180rpx;
  871. overflow: hidden;
  872. white-space: nowrap;
  873. text-overflow: ellipsis;
  874. display: inline-block;
  875. }
  876. .selector-arrow {
  877. position: absolute;
  878. right: 0rpx;
  879. bottom: 14rpx;
  880. font-size: 30rpx;
  881. display: inline-block;
  882. width: 15rpx;
  883. height: 15rpx;
  884. border: 5rpx solid #2c69ff;
  885. border-top: none;
  886. border-left: none;
  887. transform-origin: 50% 50%;
  888. transform: rotate(45deg);
  889. transition: transform 0.2s;
  890. }
  891. .selector-arrow.open {
  892. bottom: 7rpx;
  893. transform: rotate(225deg);
  894. }
  895. .dropdown {
  896. position: absolute;
  897. right: 0rpx;
  898. top: 54rpx;
  899. width: 310rpx;
  900. max-height: 380rpx;
  901. background: #fff;
  902. border: 1rpx solid #eef0f4;
  903. border-radius: 12rpx;
  904. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
  905. z-index: 93;
  906. overflow: hidden;
  907. }
  908. .dropdown-search-bar {
  909. padding: 10rpx;
  910. border-bottom: 1rpx solid #f0f2f5;
  911. background: #fff;
  912. z-index: 94;
  913. }
  914. .dropdown-search-input {
  915. width: 100%;
  916. height: 60rpx;
  917. background: #f5f7fa;
  918. border-radius: 8rpx;
  919. padding: 0 20rpx;
  920. font-size: 28rpx;
  921. box-sizing: border-box;
  922. }
  923. .dropdown-scroll-view {
  924. flex: 1;
  925. width: 100%;
  926. max-height: 260rpx;
  927. /* Adjusted height to fit search bar */
  928. overflow-y: auto;
  929. }
  930. .dropdown-item {
  931. padding: 16rpx 24rpx;
  932. font-size: 28rpx;
  933. color: #333;
  934. text-wrap: wrap;
  935. }
  936. .dropdown-item + .dropdown-item {
  937. border-top: 1rpx solid #f0f2f5;
  938. }
  939. .table-scroll-x {
  940. position: relative;
  941. margin-top: 12rpx;
  942. width: 100%;
  943. min-height: 120rpx;
  944. border-radius: 16rpx;
  945. overflow: visible;
  946. }
  947. .card {
  948. background: #fff;
  949. font-size: 32rpx;
  950. }
  951. .table-header {
  952. display: flex;
  953. background: #eaf2ff;
  954. font-weight: bold;
  955. color: #2c69ff;
  956. border-top-left-radius: 16rpx;
  957. border-top-right-radius: 16rpx;
  958. }
  959. /* .table-body {
  960. margin-top: 8rpx;
  961. } */
  962. .row {
  963. display: flex;
  964. align-items: stretch;
  965. border-bottom: 1rpx solid #eef0f4;
  966. }
  967. .row:last-child,
  968. .row:nth-last-child(2) {
  969. border-bottom: none;
  970. }
  971. .loading-row {
  972. justify-content: flex-start;
  973. }
  974. .loading-wrapper {
  975. width: calc(100vw - 60rpx);
  976. height: 76rpx;
  977. display: flex;
  978. align-items: center;
  979. justify-content: center;
  980. position: sticky;
  981. top: 50%;
  982. left: 0;
  983. }
  984. .loading-icon {
  985. width: 40rpx;
  986. height: 40rpx;
  987. animation: spin 1s linear infinite;
  988. }
  989. @keyframes spin {
  990. from {
  991. transform: rotate(0deg);
  992. }
  993. to {
  994. transform: rotate(360deg);
  995. }
  996. }
  997. .cell {
  998. flex: 1;
  999. box-sizing: border-box;
  1000. width: 0;
  1001. box-sizing: border-box;
  1002. display: flex;
  1003. align-items: center;
  1004. justify-content: center;
  1005. /* text-align: center; */
  1006. word-break: break-all;
  1007. }
  1008. .table-body .cell {
  1009. background-color: #fff;
  1010. text-align: center;
  1011. }
  1012. .table-body .striped {
  1013. background: #f7f7f7;
  1014. }
  1015. .underline {
  1016. text-decoration: underline;
  1017. color: #2c69ff;
  1018. }
  1019. /* hide scrollbars for H5/App while preserving scroll behavior */
  1020. .no-scrollbar {
  1021. position: relative;
  1022. -ms-overflow-style: none;
  1023. scrollbar-width: none;
  1024. z-index: 2;
  1025. }
  1026. ::v-deep .no-scrollbar::-webkit-scrollbar {
  1027. display: none !important;
  1028. width: 0 !important;
  1029. height: 0 !important;
  1030. -webkit-appearance: none;
  1031. background: transparent;
  1032. }
  1033. .header-tooltip-wrap {
  1034. position: absolute;
  1035. top: 60rpx;
  1036. right: 0rpx;
  1037. z-index: 93;
  1038. }
  1039. .header-tooltip {
  1040. background: #000;
  1041. color: #fff;
  1042. border-radius: 8rpx;
  1043. padding: 8rpx 12rpx;
  1044. font-size: 24rpx;
  1045. width: 270rpx;
  1046. }
  1047. .header-tooltip-arrow {
  1048. position: absolute;
  1049. top: -11rpx;
  1050. right: 20rpx;
  1051. width: 0;
  1052. height: 0;
  1053. border-left: 10rpx solid transparent;
  1054. border-right: 10rpx solid transparent;
  1055. border-bottom: 12rpx solid #000;
  1056. margin: 0 auto;
  1057. }
  1058. .no-data-placeholder {
  1059. opacity: 0;
  1060. }
  1061. .no-data {
  1062. position: absolute;
  1063. top: 76rpx;
  1064. left: 0;
  1065. width: 100%;
  1066. z-index: 1;
  1067. }
  1068. </style>