ScanRateTable.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  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: "totalOutRate",
  193. title: "出库扫码率",
  194. unit: "%",
  195. },
  196. {
  197. key: "totalInRate",
  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. if (!item[col.key]) return "--";
  417. const reg = /rate/i;
  418. if (reg.test(col.key))
  419. return Math.floor(Number(item[col.key])) + (col.unit || "");
  420. return item[col.key];
  421. },
  422. initRegionList() {
  423. this.regionList = this.filteredRegions.slice(0, 10);
  424. },
  425. loadMoreRegions(e) {
  426. if (this.regionList.length >= this.filteredRegions.length) return;
  427. const currentLen = this.regionList.length;
  428. const more = this.filteredRegions.slice(currentLen, currentLen + 10);
  429. this.regionList = this.regionList.concat(more);
  430. },
  431. onRegionSearch() {
  432. this.initRegionList();
  433. },
  434. initProductList() {
  435. this.productList = this.filteredProducts.slice(0, 10);
  436. },
  437. loadMoreProducts() {
  438. if (this.productList.length >= this.filteredProducts.length) return;
  439. const currentLen = this.productList.length;
  440. const more = this.filteredProducts.slice(currentLen, currentLen + 10);
  441. this.productList = this.productList.concat(more);
  442. },
  443. onProductSearch() {
  444. this.initProductList();
  445. },
  446. getUid,
  447. getSummary(col, ci) {
  448. if (ci == 0) return "合计";
  449. return Math.floor(Number(this.summaryData[ci])) + (col.unit || "");
  450. },
  451. closeAllTooltip() {
  452. this.headerTooltip = {};
  453. },
  454. toggleHeaderTooltip(i) {
  455. const value = !this.headerTooltip[i];
  456. this.headerTooltip = {};
  457. this.$set(this.headerTooltip, i, value);
  458. },
  459. // getUid(len = 16) {
  460. // const ts = Date.now().toString(36);
  461. // const rand = Math.random().toString(36).slice(2);
  462. // return (ts + rand).slice(0, len);
  463. // },
  464. // getProviceList() {
  465. // request('/common/getProviceList', {
  466. // path: 'customerScanningRate/wigets/ScanRateTable.vue',
  467. // }).then(res => {
  468. // if (res.code == 200) {
  469. // const _data = res.data;
  470. // this.regions = _data;
  471. // this.region = this.regions[0];
  472. // }
  473. // })
  474. // },
  475. // getDrugInfoName() {
  476. // request('/bills/getDrugInfoName', {
  477. // path: 'customerScanningRate/wigets/ScanRateTable.vue',
  478. // }).then(res => {
  479. // if (res.code == 200) {
  480. // const _data = res.data;
  481. // this.products = _data;
  482. // this.product = this.products[0];
  483. // }
  484. // })
  485. // },
  486. toggleDropdown() {
  487. if (this.fetchLoading) return;
  488. const next = !this.dropdownOpen;
  489. if (next) this.$emit("dropdown-open", this);
  490. this.dropdownOpen = next;
  491. },
  492. toggleDropdownRegion() {
  493. if (this.fetchLoading) return;
  494. const next = !this.dropdownRegionOpen;
  495. if (next) this.$emit("dropdown-open", this);
  496. this.dropdownRegionOpen = next;
  497. },
  498. selectProduct(type, p) {
  499. // this.$emit("change-product", p);
  500. if (type === "product") {
  501. this.product = p;
  502. this.dropdownOpen = false;
  503. } else if (type === "region") {
  504. this.region = p;
  505. this.dropdownRegionOpen = false;
  506. }
  507. },
  508. closeDropdown() {
  509. this.dropdownOpen = false;
  510. this.dropdownRegionOpen = false;
  511. },
  512. // genRows(n) {
  513. // const regions = ["广东", "广西", "湖南", "湖北", "四川", "浙江", "江苏"];
  514. // const res = [];
  515. // for (let i = 0; i < n; i++) {
  516. // const r = regions[i % regions.length];
  517. // res.push({
  518. // regionName: r,
  519. // outScanRate: `${(95 + (i % 5) * 0.5).toFixed(1)}%`,
  520. // inScanRate: `${(94 + (i % 5) * 0.6).toFixed(1)}%`,
  521. // });
  522. // }
  523. // return res;
  524. // },
  525. onScrollToLower(e) {
  526. if (e?.detail?.direction === "right") return;
  527. // if (!Array.isArray(this.list)) this.list = [];
  528. // if (this.list.length - 1 >= this.totalCount) return;
  529. // if (this.isLoading) return;
  530. // this.isLoading = true;
  531. // const remain = this.totalCount - this.list.length;
  532. // const toAdd = Math.min(7, remain);
  533. // setTimeout(() => {
  534. // if (this.list.length < this.totalCount) {
  535. // const more = this.genRows(toAdd);
  536. // this.list = this.list.concat(more);
  537. // }
  538. // this.isLoading = false;
  539. // }, 1000);
  540. this.fetchList();
  541. },
  542. resetFetch() {
  543. this.tablePage = 1;
  544. this.list = [];
  545. this.loading = true;
  546. this.hasmore = true;
  547. this.fetchList();
  548. },
  549. fetchList() {
  550. if (this._fetchDebounceTimer) clearTimeout(this._fetchDebounceTimer);
  551. const delay = 500;
  552. this._fetchDebounceTimer = setTimeout(() => {
  553. if (!this.hasmore) {
  554. setTimeout(() => {
  555. this.fetchLoading = false;
  556. this.loading = false;
  557. }, 500);
  558. return;
  559. }
  560. if (this.fetchLoading) return;
  561. this.fetchLoading = true;
  562. request(this.api, {
  563. physicName: this.product
  564. ? this.product?.physicName == "全品种"
  565. ? ""
  566. : this.product?.physicName
  567. : "",
  568. regionCode: this.region ? this.region?.regionCode || "" : "",
  569. pageNum: this.tablePage,
  570. pageSize: this.tablePageSize,
  571. path: "customerScanningRate/wigets/ScanRateTable.vue",
  572. ...(this.params || {}),
  573. }).then((res) => {
  574. if (res.code == 200) {
  575. const _data = res.data || {};
  576. let pageInfo = _data || {};
  577. if (this.tableType !== "variety") {
  578. this.summaryData = [];
  579. this.summaryData[0] = undefined;
  580. this.summaryStructure.forEach((item) => {
  581. this.summaryData.push(_data[item.key] || "--");
  582. });
  583. pageInfo = _data?.pageInfo || {};
  584. }
  585. const { list, total } = pageInfo;
  586. this.totalCount = total || 0;
  587. if (this.list.length >= this.totalCount) {
  588. this.fetchLoading = false;
  589. this.hasmore = false;
  590. this.loading = false;
  591. return;
  592. }
  593. this.list = Array.isArray(list)
  594. ? [...this.list, ...list]
  595. : [...this.list];
  596. if (this.list.length < this.totalCount) {
  597. this.hasmore = true;
  598. this.tablePage++;
  599. } else {
  600. this.hasmore = false;
  601. }
  602. // this.list.push(this.list[0]);
  603. // this.list.push(this.list[0]);
  604. // this.list.push(this.list[0]);
  605. // this.list.push(this.list[0]);
  606. // this.list.push(this.list[0]);
  607. // this.list.push(this.list[0]);
  608. // this.list.push(this.list[0]);
  609. // this.list.push(this.list[0]);
  610. // this.list.push(this.list[0]);
  611. // this.list.push(this.list[0]);
  612. // this.list.push(this.list[0]);
  613. }
  614. this.fetchLoading = false;
  615. this.loading = false;
  616. });
  617. }, delay);
  618. },
  619. headerCellStyle(ci, col) {
  620. const base = this.cellBaseStyle(ci, col);
  621. base.height = this.cellHeight;
  622. base.fontSize = this.headerFontSize;
  623. base.position = "sticky";
  624. base.zIndex = 2;
  625. base.top = "0rpx";
  626. base.background = "#eaf2ff";
  627. // base.display = 'flex';
  628. // base.alignItems = 'center';
  629. // base.justifyContent = 'center';
  630. if (ci === 0) {
  631. base.zIndex = 3;
  632. }
  633. if (col.fixed) {
  634. base.left = this.stickyLeft(ci);
  635. }
  636. return base;
  637. },
  638. bodyCellStyle(ci, col) {
  639. const base = this.cellBaseStyle(ci, col);
  640. // base.lineHeight = this.cellHeight;
  641. base.padding = "12rpx";
  642. base.fontSize = this.bodyFontSize;
  643. if (this.bodyFontColor) {
  644. base.color = this.bodyFontColor;
  645. }
  646. if (col.fixed) {
  647. base.position = "sticky";
  648. base.left = this.stickyLeft(ci);
  649. base.zIndex = 2;
  650. }
  651. return base;
  652. },
  653. cellBaseStyle(ci, col) {
  654. const style = {};
  655. // style.height = this.cellHeight;
  656. if (col && col.width) {
  657. style.width = col.width;
  658. style.flex = `0 0 ${col.width}`;
  659. } else {
  660. style.flex = 1;
  661. }
  662. return style;
  663. },
  664. stickyLeft(ci) {
  665. const parts = [];
  666. for (let i = 0; i < ci; i++) {
  667. const c = this.columns[i];
  668. if (c && c.fixed && c.width) parts.push(c.width);
  669. }
  670. if (!parts.length) return "0rpx";
  671. if (parts.length === 1) return parts[0];
  672. return `calc(${parts.join(" + ")})`;
  673. },
  674. isPlainTitle(t) {
  675. return typeof t === "string" ? t.indexOf("<") === -1 : true;
  676. },
  677. titleNodes(t) {
  678. if (typeof t !== "string") return t;
  679. let html = String(t);
  680. html = html
  681. .replace(/<\s*view/g, "<div")
  682. .replace(/<\/\s*view\s*>/g, "</div>");
  683. html = html.replace(
  684. /:style=\"\{\s*textAlign:\s*'center'\s*\}\"/g,
  685. 'style="text-align:center;"',
  686. );
  687. return html;
  688. },
  689. handleClick(data, col) {
  690. if (
  691. this.tableType === "variety" ||
  692. this.tableType === "customerDetail" ||
  693. col.key !== "regionName"
  694. )
  695. return;
  696. uni.navigateTo({
  697. url:
  698. "/traceCodePackages/traceabilityReport/pages/customerScanningRate/detail/index?regionCode=" +
  699. data.regionCode +
  700. "&regionName=" +
  701. encodeURIComponent(data.regionName) +
  702. "&drugEntBaseInfoId=" +
  703. (data.druGentBaseInfoId || "") +
  704. "&physicName=" +
  705. encodeURIComponent(this.product?.physicName || "") +
  706. "&scanType=" +
  707. this.scanType +
  708. (this.params.type ? "&type=" + this.params.type : ""),
  709. });
  710. },
  711. },
  712. };
  713. </script>
  714. <style scoped>
  715. .section {
  716. margin-top: 24rpx;
  717. transform: translate3d(0, 0, 0);
  718. }
  719. .section-header {
  720. position: relative;
  721. display: flex;
  722. align-items: center;
  723. justify-content: space-between;
  724. padding: 8rpx 8rpx;
  725. }
  726. .section-title {
  727. position: relative;
  728. font-size: 30rpx;
  729. font-weight: bold;
  730. color: #2c69ff;
  731. }
  732. .section-title::after {
  733. content: "";
  734. position: absolute;
  735. left: -20rpx;
  736. bottom: 13rpx;
  737. width: 8rpx;
  738. height: 50%;
  739. background: #2c69ff;
  740. border-radius: 10px;
  741. }
  742. .selector-wrap {
  743. position: relative;
  744. margin-right: 40rpx;
  745. }
  746. .selector {
  747. position: relative;
  748. display: flex;
  749. align-items: center;
  750. color: #2c69ff;
  751. padding-right: 30rpx;
  752. }
  753. .selector-text {
  754. font-size: 30rpx;
  755. max-width: 180rpx;
  756. overflow: hidden;
  757. white-space: nowrap;
  758. text-overflow: ellipsis;
  759. display: inline-block;
  760. }
  761. .selector-arrow {
  762. position: absolute;
  763. right: 0rpx;
  764. bottom: 14rpx;
  765. font-size: 30rpx;
  766. display: inline-block;
  767. width: 15rpx;
  768. height: 15rpx;
  769. border: 5rpx solid #2c69ff;
  770. border-top: none;
  771. border-left: none;
  772. transform-origin: 50% 50%;
  773. transform: rotate(45deg);
  774. transition: transform 0.2s;
  775. }
  776. .selector-arrow.open {
  777. bottom: 7rpx;
  778. transform: rotate(225deg);
  779. }
  780. .dropdown {
  781. position: absolute;
  782. right: 0rpx;
  783. top: 54rpx;
  784. width: 310rpx;
  785. max-height: 380rpx;
  786. background: #fff;
  787. border: 1rpx solid #eef0f4;
  788. border-radius: 12rpx;
  789. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
  790. z-index: 93;
  791. overflow: hidden;
  792. }
  793. .dropdown-search-bar {
  794. padding: 10rpx;
  795. border-bottom: 1rpx solid #f0f2f5;
  796. background: #fff;
  797. z-index: 94;
  798. }
  799. .dropdown-search-input {
  800. width: 100%;
  801. height: 60rpx;
  802. background: #f5f7fa;
  803. border-radius: 8rpx;
  804. padding: 0 20rpx;
  805. font-size: 28rpx;
  806. box-sizing: border-box;
  807. }
  808. .dropdown-scroll-view {
  809. flex: 1;
  810. width: 100%;
  811. max-height: 260rpx;
  812. /* Adjusted height to fit search bar */
  813. overflow-y: auto;
  814. }
  815. .dropdown-item {
  816. padding: 16rpx 24rpx;
  817. font-size: 28rpx;
  818. color: #333;
  819. text-wrap: wrap;
  820. }
  821. .dropdown-item+.dropdown-item {
  822. border-top: 1rpx solid #f0f2f5;
  823. }
  824. .table-scroll-x {
  825. position: relative;
  826. margin-top: 12rpx;
  827. width: 100%;
  828. min-height: 120rpx;
  829. border-radius: 16rpx;
  830. overflow: visible;
  831. }
  832. .card {
  833. background: #fff;
  834. font-size: 32rpx;
  835. }
  836. .table-header {
  837. display: flex;
  838. background: #eaf2ff;
  839. font-weight: bold;
  840. color: #2c69ff;
  841. border-top-left-radius: 16rpx;
  842. border-top-right-radius: 16rpx;
  843. }
  844. /* .table-body {
  845. margin-top: 8rpx;
  846. } */
  847. .row {
  848. display: flex;
  849. align-items: stretch;
  850. border-bottom: 1rpx solid #eef0f4;
  851. }
  852. .row:last-child,
  853. .row:nth-last-child(2) {
  854. border-bottom: none;
  855. }
  856. .loading-row {
  857. justify-content: flex-start;
  858. }
  859. .loading-wrapper {
  860. width: calc(100vw - 60rpx);
  861. height: 76rpx;
  862. display: flex;
  863. align-items: center;
  864. justify-content: center;
  865. position: sticky;
  866. top: 50%;
  867. left: 0;
  868. }
  869. .loading-icon {
  870. width: 40rpx;
  871. height: 40rpx;
  872. animation: spin 1s linear infinite;
  873. }
  874. @keyframes spin {
  875. from {
  876. transform: rotate(0deg);
  877. }
  878. to {
  879. transform: rotate(360deg);
  880. }
  881. }
  882. .cell {
  883. flex: 1;
  884. box-sizing: border-box;
  885. width: 0;
  886. box-sizing: border-box;
  887. display: flex;
  888. align-items: center;
  889. justify-content: center;
  890. /* text-align: center; */
  891. word-break: break-all;
  892. }
  893. .table-body .cell {
  894. background-color: #fff;
  895. text-align: center;
  896. }
  897. .table-body .striped {
  898. background: #f7f7f7;
  899. }
  900. .underline {
  901. text-decoration: underline;
  902. color: #2c69ff;
  903. }
  904. /* hide scrollbars for H5/App while preserving scroll behavior */
  905. .no-scrollbar {
  906. position: relative;
  907. -ms-overflow-style: none;
  908. scrollbar-width: none;
  909. z-index: 2;
  910. }
  911. ::v-deep .no-scrollbar::-webkit-scrollbar {
  912. display: none !important;
  913. width: 0 !important;
  914. height: 0 !important;
  915. -webkit-appearance: none;
  916. background: transparent;
  917. }
  918. .header-tooltip-wrap {
  919. position: absolute;
  920. top: 60rpx;
  921. right: 0rpx;
  922. z-index: 93;
  923. }
  924. .header-tooltip {
  925. background: #000;
  926. color: #fff;
  927. border-radius: 8rpx;
  928. padding: 8rpx 12rpx;
  929. font-size: 24rpx;
  930. width: 270rpx;
  931. }
  932. .header-tooltip-arrow {
  933. position: absolute;
  934. top: -11rpx;
  935. right: 20rpx;
  936. width: 0;
  937. height: 0;
  938. border-left: 10rpx solid transparent;
  939. border-right: 10rpx solid transparent;
  940. border-bottom: 12rpx solid #000;
  941. margin: 0 auto;
  942. }
  943. .no-data-placeholder {
  944. opacity: 0;
  945. }
  946. .no-data {
  947. position: absolute;
  948. top: 76rpx;
  949. left: 0;
  950. width: 100%;
  951. z-index: 1;
  952. }
  953. </style>