index.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. <template>
  2. <Water></Water>
  3. <view class="scan-range-page">
  4. <view class="loading-page" v-if="getLoading()">
  5. <image class="loading-icon" src="../../../static/images/loading.png" />
  6. </view>
  7. <scroll-view v-else class="content" scroll-y="true" @scrolltolower="onScrollToLower">
  8. <view class="item" v-for="(item, i) in companyList" :key="i" @click="toggle(i)">
  9. <view class="item-header">
  10. <text class="item-title">{{ i + 1 }}.&nbsp;{{ item.entName || "" }}</text>
  11. <uni-icons type="down" size="22" color="#999" :style="{
  12. transform: expanded[i] ? 'rotate(180deg)' : 'rotate(0deg)',
  13. transition: 'transform 0.3s ease-in-out',
  14. }"></uni-icons>
  15. </view>
  16. <scroll-view class="item-collapse" scroll-y="true" :style="{
  17. height: getCollapseHeight(i),
  18. }" @scrolltolower="getDrugInfoNameList(i)">
  19. <view class="cv-item" v-for="(d, di) in products[i]" :key="'drug-' + di">
  20. <view class="cv-index-wrap">
  21. <text class="cv-index">{{ di + 1 }}</text>
  22. </view>
  23. <view class="cv-body">
  24. <view class="cv-row">
  25. <text class="cv-label">药品名称:</text>
  26. <text class="cv-value">{{ d.physicName || "" }}</text>
  27. </view>
  28. <view class="cv-row">
  29. <text class="cv-label">药品规格:</text>
  30. <text class="cv-value">{{ d.pkgSpec || "" }}</text>
  31. </view>
  32. </view>
  33. </view>
  34. </scroll-view>
  35. </view>
  36. <view class="loading-row" v-if="companyHasMore">
  37. <view class="loading-wrapper">
  38. <image class="loading-icon" src="../../../static/images/loading.png" />
  39. </view>
  40. </view>
  41. </scroll-view>
  42. </view>
  43. </template>
  44. <script>
  45. import request from "../../../request/index.js";
  46. import { getPaginatedList } from "../../../utils/utils.js";
  47. import Water from "@/components/water/water.vue";
  48. export default {
  49. components: {
  50. Water,
  51. },
  52. data() {
  53. return {
  54. companyList: [],
  55. companyPageNum: 1,
  56. companyPageSize: 20,
  57. companyTotalCount: 0,
  58. companyHasMore: true,
  59. companyLoading: false,
  60. fetchedCount: 0,
  61. allCompanies: [],
  62. loading: true,
  63. expanded: {},
  64. products: {},
  65. productPages: [],
  66. canScanCompanyList: ["华润三九医药股份有限公司", '华润三九(枣庄)药业有限公司', '华润三九(南昌)药业有限公司', '合肥华润神鹿药业有限公司', '杭州华润老桐君药业有限公司', '吉林三九金复康药业有限公司', '华润三九(郴州)制药有限公司']
  67. };
  68. },
  69. onLoad() {
  70. this.fetchInitial();
  71. },
  72. computed: {
  73. /** 白名单规范化后的集合,与接口 entName 比对用 */
  74. canScanCompanySet() {
  75. return new Set(
  76. this.canScanCompanyList.map((s) => this.normalizeEntName(s))
  77. );
  78. },
  79. },
  80. methods: {
  81. normalizeEntName(s) {
  82. return String(s || "")
  83. .trim()
  84. .replace(/\(/g, "(")
  85. .replace(/\)/g, ")");
  86. },
  87. getDrugInfoNameList(i) {
  88. const res = getPaginatedList({
  89. page: this.productPages[i] || 0,
  90. initData: this.companyList[i]?.drugInfoNameList || [],
  91. data: this.products[i] || [],
  92. });
  93. if (!res || !Array.isArray(res.data)) return;
  94. this.$set(this.productPages, i, res.page || this.productPages[i] || 0);
  95. this.$set(this.products, i, res.data);
  96. },
  97. getCollapseHeight(i) {
  98. if (!this.expanded[i]) return "0";
  99. const n = this.companyList[i]?.drugInfoNameList?.length || 0;
  100. const perItemRpx = 132;
  101. const maxRpx = 560;
  102. return Math.min(maxRpx, n * perItemRpx) + "rpx";
  103. },
  104. getLoading() {
  105. return this.loading;
  106. },
  107. fetchInitial() {
  108. this.fetchCustomers();
  109. },
  110. fetchCustomers() {
  111. if (!this.companyHasMore || this.companyLoading) return;
  112. this.companyLoading = true;
  113. request("/customer/info/getEnterpriseData", {
  114. pageNum: this.companyPageNum,
  115. pageSize: this.companyPageSize,
  116. path: "/traceabilityCodeQuery/pages/scanRange/index.vue",
  117. }).then((res) => {
  118. if (res.code == 200) {
  119. const { total, list } = res.data || {};
  120. this.companyTotalCount = total || 0;
  121. const rawList = Array.isArray(list) ? list : [];
  122. this.fetchedCount = (this.fetchedCount || 0) + rawList.length;
  123. const allowed = this.canScanCompanySet;
  124. const filtered = rawList.filter((item) =>
  125. allowed.has(this.normalizeEntName(item.entName))
  126. );
  127. this.companyList = [...this.companyList, ...filtered];
  128. if (
  129. rawList.length > 0 &&
  130. this.fetchedCount < this.companyTotalCount
  131. ) {
  132. this.companyPageNum++;
  133. this.companyHasMore = true;
  134. } else {
  135. this.companyHasMore = false;
  136. }
  137. this.companyList.forEach((item, i) => {
  138. this.getDrugInfoNameList(i);
  139. });
  140. if (
  141. this.companyHasMore &&
  142. (filtered.length === 0 || this.companyList.length < 10)
  143. ) {
  144. this.companyLoading = false;
  145. this.fetchCustomers();
  146. return;
  147. }
  148. }
  149. this.companyLoading = false;
  150. this.loading = false;
  151. });
  152. },
  153. onScrollToLower() {
  154. this.fetchCustomers();
  155. },
  156. toggle(i) {
  157. this.$set(this.expanded, i, !this.expanded[i]);
  158. },
  159. },
  160. };
  161. </script>
  162. <style>
  163. .scan-range-page {
  164. box-sizing: border-box;
  165. padding: 24rpx;
  166. height: 100vh;
  167. background: #f3f6f9;
  168. }
  169. .content {
  170. max-height: calc(100vh - 48rpx - 92rpx - 20rpx - env(safe-area-inset-bottom));
  171. background: #fff;
  172. border-radius: 16rpx;
  173. }
  174. .item {
  175. padding: 10rpx 20rpx;
  176. border-bottom: 1rpx solid #eef0f4;
  177. }
  178. .item-header {
  179. display: flex;
  180. align-items: center;
  181. justify-content: space-between;
  182. line-height: 60rpx;
  183. }
  184. .item-title {
  185. font-size: 30rpx;
  186. color: #333;
  187. }
  188. .item-collapse {
  189. margin-top: 12rpx;
  190. height: 0;
  191. transition: height 0.3s ease-in-out;
  192. }
  193. .cv-item {
  194. box-sizing: border-box;
  195. padding: 20rpx 12rpx 20rpx 8rpx;
  196. display: flex;
  197. align-items: flex-start;
  198. color: #666;
  199. border-bottom: 1rpx solid #f0f2f5;
  200. }
  201. .cv-item:last-child {
  202. border-bottom: none;
  203. }
  204. .cv-index-wrap {
  205. flex-shrink: 0;
  206. padding-top: 6rpx;
  207. margin-right: 16rpx;
  208. }
  209. .cv-index {
  210. display: flex;
  211. align-items: center;
  212. justify-content: center;
  213. min-width: 25rpx;
  214. height: 36rpx;
  215. padding: 0 6rpx;
  216. line-height: 1;
  217. font-size: 20rpx;
  218. text-align: center;
  219. border-radius: 50%;
  220. border: 1rpx solid #999;
  221. }
  222. .cv-body {
  223. flex: 1;
  224. min-width: 0;
  225. }
  226. .cv-row {
  227. display: flex;
  228. align-items: flex-start;
  229. font-size: 28rpx;
  230. line-height: 1.5;
  231. }
  232. .cv-row+.cv-row {
  233. margin-top: 10rpx;
  234. }
  235. .cv-label {
  236. flex-shrink: 0;
  237. width: 150rpx;
  238. color: #727a86;
  239. }
  240. .cv-value {
  241. flex: 1;
  242. min-width: 0;
  243. color: #333;
  244. word-break: break-all;
  245. white-space: normal;
  246. }
  247. .loading-page {
  248. width: 100%;
  249. height: 200rpx;
  250. display: flex;
  251. align-items: center;
  252. justify-content: center;
  253. }
  254. .loading-row {
  255. display: flex;
  256. align-items: center;
  257. justify-content: center;
  258. padding: 20rpx;
  259. }
  260. .loading-wrapper {
  261. width: 100%;
  262. display: flex;
  263. align-items: center;
  264. justify-content: center;
  265. }
  266. .loading-icon {
  267. width: 50rpx;
  268. height: 50rpx;
  269. animation: spin 1s linear infinite;
  270. }
  271. @keyframes spin {
  272. from {
  273. transform: rotate(0deg);
  274. }
  275. to {
  276. transform: rotate(360deg);
  277. }
  278. }
  279. </style>