ScanRateTable.vue 27 KB

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