ScanRateTable.vue 26 KB

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