ScanRateTable.vue 25 KB

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