ScanRateTable.vue 27 KB

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