index.vue 50 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691
  1. <template>
  2. <Water></Water>
  3. <scroll-view
  4. class="detail-page"
  5. :style="{ height: pageHeight }"
  6. :scroll-top="scrollTopValue"
  7. :scroll-into-view="scrollIntoViewId"
  8. scroll-y="true"
  9. scroll-with-animation
  10. lower-threshold="80"
  11. @scroll="onPageScroll"
  12. @scrolltolower="onTimelineScrollToLower"
  13. v-if="!!data && !loading"
  14. >
  15. <view id="detailPageTopAnchor"></view>
  16. <view
  17. class="detail-card detail-bg"
  18. :style="{ backgroundImage: 'url(' + bg + ')' }"
  19. >
  20. <view class="detail-drug-title">{{ data.physicName }}</view>
  21. <view class="detail-row detail-traceability">
  22. <view class="detail-label">追溯码:</view>
  23. <view class="detail-value detail-wrap">{{
  24. formatTraceCode(traceCode)
  25. }}</view>
  26. <view class="detail-copy-btn" @click="copy(traceCode)">
  27. <image
  28. src="../../../static/images/copy.png"
  29. mode="scaleToFill"
  30. :style="{ width: '30rpx', height: '30rpx' }"
  31. />
  32. </view>
  33. </view>
  34. <view class="detail-row">
  35. <view class="detail-label">生产企业:</view>
  36. <view
  37. class="detail-value detail-wrap detail-company-value"
  38. :class="{ 'detail-clamp': !companyExpanded }"
  39. :style="{ marginBottom: needCompanyToggle ? '40rpx' : undefined }"
  40. id="companyValue"
  41. >
  42. <text>{{ data.produceEntName }}</text>
  43. </view>
  44. <text
  45. v-if="needCompanyToggle"
  46. class="detail-expand-tag"
  47. @click="toggleCompanyExpand"
  48. >{{ companyExpanded ? "收起" : "展开" }}</text
  49. >
  50. </view>
  51. <view class="detail-row">
  52. <view class="detail-label">批准文号:</view>
  53. <view class="detail-value">{{ data.approveNo }}</view>
  54. </view>
  55. <view class="detail-row">
  56. <view class="detail-label">生产日期:</view>
  57. <view class="detail-value">{{ data.produceDate }}</view>
  58. </view>
  59. <view class="detail-row" v-show="cardExpanded">
  60. <view class="detail-label">产品批号:</view>
  61. <view class="detail-value">{{ data.produceBatchNo }}</view>
  62. </view>
  63. <view class="detail-row" v-show="cardExpanded">
  64. <view class="detail-label">
  65. 剂<text :style="{ opacity: 0 }">剂型</text>型:</view
  66. >
  67. <view class="detail-value">{{ data.prepnDesc }}</view>
  68. </view>
  69. <view class="detail-row" v-show="cardExpanded">
  70. <view class="detail-label">包装规格:</view>
  71. <view class="detail-value">{{ data.prepnSpec }}</view>
  72. </view>
  73. <view class="detail-row" v-show="cardExpanded">
  74. <view class="detail-label">有效期至:</view>
  75. <view class="detail-value">{{ data.validEndDate }}</view>
  76. </view>
  77. <view class="detail-card-toggle" @click="toggleCardExpand">
  78. <uni-icons
  79. :type="cardExpanded ? 'up' : 'down'"
  80. size="18"
  81. color="#9aa4b2"
  82. ></uni-icons>
  83. </view>
  84. </view>
  85. <view
  86. class="detail-timeline detail-bg"
  87. :style="{ backgroundImage: 'url(' + bg + ')' }"
  88. >
  89. <view class="detail-section-header">
  90. <view class="detail-section-title">扫码跟踪</view>
  91. <!-- <view class="detail-section-actions">
  92. <view class="detail-section-action" @tap="toggleTimelineView">
  93. <uni-icons
  94. :type="timelineView === 'text' ? 'image' : 'list'"
  95. size="20"
  96. color="#59a8f2"
  97. ></uni-icons>
  98. <text>{{ timelineView === "text" ? "查看图形" : "查看文字" }}</text>
  99. </view>
  100. <view class="detail-section-action" @tap="onSetDefaultView">
  101. <uni-icons type="gear" size="20" color="#59a8f2"></uni-icons>
  102. <text>设置默认</text>
  103. </view>
  104. </view> -->
  105. </view>
  106. <!-- <view class="detail-graph-panel" v-if="timelineView === 'graph'">
  107. <view class="graph-mode-row">
  108. <view class="graph-mode-left">
  109. <view class="graph-mode-title">货权流向模式</view>
  110. <view class="graph-mode-desc"
  111. >开启后则会按照码的实际实物货权关系,将委托企业(若有)还原到收发货企业上并绘制流向图。</view
  112. >
  113. </view>
  114. <switch
  115. :checked="ownershipFlowMode"
  116. color="#2c69ff"
  117. @change="onOwnershipModeChange"
  118. />
  119. </view>
  120. <view
  121. :style="{
  122. width: graphViewWidth + 'px',
  123. height: graphViewHeight + 'px',
  124. }"
  125. v-if="isShowGraph"
  126. >
  127. <l-echart
  128. ref="chartRef"
  129. :beforeDelay="180"
  130. :lStyle="`width:${graphViewWidth}px;height:${graphViewHeight}px;`"
  131. @finished="initChart"
  132. ></l-echart>
  133. </view>
  134. <view
  135. :style="{
  136. width: graphViewWidth + 'px',
  137. height: graphViewHeight + 'px',
  138. display: 'flex',
  139. alignItems: 'center',
  140. justifyContent: 'center',
  141. }"
  142. v-else
  143. >
  144. 链路过长无法展示
  145. </view>
  146. </view> -->
  147. <view class="detail-timeline-toolbar" v-if="timelineView === 'text'">
  148. <view class="detail-toolbar-btn" @tap.stop="setAllTimelineDetail(true)"
  149. >全部展示详情</view
  150. >
  151. <view class="detail-toolbar-btn" @tap.stop="setAllTimelineDetail(false)"
  152. >全部展示概要</view
  153. >
  154. </view>
  155. <view
  156. v-if="timelineView === 'text'"
  157. class="detail-timeline-item"
  158. :class="{ 'detail-timeline-item-first': i === 0 }"
  159. v-for="(value, i) in visibleBillDetailList"
  160. :key="i"
  161. >
  162. <view class="detail-left">
  163. <view class="detail-dot" :class="{ 'detail-fill': false }"></view>
  164. <view
  165. class="detail-line"
  166. v-if="i < visibleBillDetailList.length - 1 || hasMoreTimelineItems"
  167. ></view>
  168. </view>
  169. <view class="detail-right">
  170. <view class="detail-title-row">
  171. <view class="detail-title" :class="{ 'detail-bold': true }">
  172. {{ value.billTypeName || "--" }} {{ value.billCode || "" }}
  173. </view>
  174. <view class="detail-item-toggle" @tap.stop="toggleTimelineItem(i)">
  175. {{ value._showDetail ? "概要" : "详情" }}
  176. <uni-icons
  177. :type="value._showDetail ? 'up' : 'down'"
  178. size="14"
  179. color="#888"
  180. ></uni-icons>
  181. </view>
  182. </view>
  183. <view class="detail-info-row">
  184. <text class="detail-info-label">发货企业:</text>
  185. <text class="detail-info-value">{{
  186. value.fromEntName || "--"
  187. }}</text>
  188. </view>
  189. <view class="detail-tag-row">
  190. <text class="detail-tag" v-if="value.fromCustomerNature">{{
  191. value.fromCustomerNature
  192. }}</text>
  193. <text class="detail-tag" v-if="value.fromCustomerLevel">{{
  194. value.fromCustomerLevel
  195. }}</text>
  196. <text class="detail-tag" v-if="value.fromResponsibleManager">{{
  197. value.fromResponsibleManager
  198. }}</text>
  199. </view>
  200. <view class="detail-tag-row" v-if="value._showDetail">
  201. <text
  202. class="detail-tag"
  203. :style="getTagColorStyle(value.fromEntTypeName)"
  204. v-if="value.fromEntTypeName"
  205. >{{ value.fromEntTypeName }}</text
  206. >
  207. <text
  208. class="detail-tag detail-tag-region"
  209. v-if="getRegionByPrefix(value, 'from')"
  210. >{{ getRegionByPrefix(value, "from") }}</text
  211. >
  212. </view>
  213. <view class="detail-info-row">
  214. <text class="detail-info-label">收货企业:</text>
  215. <text class="detail-info-value">{{ value.toEntName || "--" }}</text>
  216. </view>
  217. <view class="detail-tag-row">
  218. <text class="detail-tag" v-if="value.toCustomerNature">{{
  219. value.toCustomerNature
  220. }}</text>
  221. <text class="detail-tag" v-if="value.toCustomerLevel">{{
  222. value.toCustomerLevel
  223. }}</text>
  224. <text class="detail-tag" v-if="value.toResponsibleManager">{{
  225. value.toResponsibleManager
  226. }}</text>
  227. </view>
  228. <view class="detail-tag-row" v-if="value._showDetail">
  229. <text
  230. class="detail-tag"
  231. :style="getTagColorStyle(value.toEntTypeName)"
  232. v-if="value.toEntTypeName"
  233. >{{ value.toEntTypeName }}</text
  234. >
  235. <text
  236. class="detail-tag detail-tag-region"
  237. v-if="getRegionByPrefix(value, 'to')"
  238. >{{ getRegionByPrefix(value, "to") }}</text
  239. >
  240. </view>
  241. <view class="detail-info-row">
  242. <text class="detail-info-label">入出库时间:</text>
  243. <text class="detail-info-value">{{ value.billTime || "--" }}</text>
  244. </view>
  245. <view
  246. class="detail-info-row"
  247. v-if="value._showDetail && value.uploadEntName"
  248. >
  249. <text class="detail-info-label">上传企业:</text>
  250. <text class="detail-info-value">{{ value.uploadEntName }}</text>
  251. </view>
  252. <view
  253. class="detail-tag-row"
  254. v-if="value._showDetail && value.uploadEntName"
  255. >
  256. <text class="detail-tag" v-if="value.uploadCustomerNature">{{
  257. value.uploadCustomerNature
  258. }}</text>
  259. <text class="detail-tag" v-if="value.uploadCustomerLevel">{{
  260. value.uploadCustomerLevel
  261. }}</text>
  262. <text class="detail-tag" v-if="value.uploadResponsibleManager">{{
  263. value.uploadResponsibleManager
  264. }}</text>
  265. </view>
  266. <view
  267. class="detail-tag-row"
  268. v-if="value._showDetail && value.uploadEntName"
  269. >
  270. <text
  271. class="detail-tag"
  272. :style="getTagColorStyle(value.uploadEntTypeName)"
  273. v-if="value.uploadEntTypeName"
  274. >{{ value.uploadEntTypeName }}</text
  275. >
  276. <text
  277. class="detail-tag detail-tag-region"
  278. v-if="getRegionByPrefix(value, 'upload')"
  279. >{{ getRegionByPrefix(value, "upload") }}</text
  280. >
  281. </view>
  282. <view
  283. class="detail-info-row"
  284. v-if="value._showDetail && value.uploadEntName"
  285. >
  286. <text class="detail-info-label">上传时间:</text>
  287. <text class="detail-info-value">{{
  288. value.uploadTime || "--"
  289. }}</text>
  290. </view>
  291. <view
  292. class="detail-info-row"
  293. v-if="value._showDetail && value.disEntName"
  294. >
  295. <text class="detail-info-label">配送企业:</text>
  296. <text class="detail-info-value">{{ value.disEntName }}</text>
  297. </view>
  298. <view
  299. class="detail-tag-row"
  300. v-if="
  301. value._showDetail &&
  302. value.disEntName &&
  303. !value.disEntName.includes(',')
  304. "
  305. >
  306. <text class="detail-tag" v-if="value.disCustomerNature">{{
  307. value.disCustomerNature
  308. }}</text>
  309. <text class="detail-tag" v-if="value.disCustomerLevel">{{
  310. value.disCustomerLevel
  311. }}</text>
  312. <text class="detail-tag" v-if="value.disResponsibleManager">{{
  313. value.disResponsibleManager
  314. }}</text>
  315. </view>
  316. <view
  317. class="detail-tag-row"
  318. v-if="
  319. value._showDetail &&
  320. value.disEntName &&
  321. !value.disEntName.includes(',')
  322. "
  323. >
  324. <text
  325. class="detail-tag"
  326. :style="getTagColorStyle(value.disEntTypeName)"
  327. v-if="value.disEntTypeName"
  328. >{{ value.disEntTypeName }}</text
  329. >
  330. <text
  331. class="detail-tag detail-tag-region"
  332. v-if="getRegionByPrefix(value, 'dis')"
  333. >{{ getRegionByPrefix(value, "dis") }}</text
  334. >
  335. </view>
  336. <view
  337. class="detail-info-row"
  338. v-if="value._showDetail && value.assEntName"
  339. >
  340. <text class="detail-info-label">委托企业:</text>
  341. <text class="detail-info-value">{{ value.assEntName }}</text>
  342. </view>
  343. <view
  344. class="detail-tag-row"
  345. v-if="value._showDetail && value.assEntName"
  346. >
  347. <text class="detail-tag" v-if="value.assCustomerNature">{{
  348. value.assCustomerNature
  349. }}</text>
  350. <text class="detail-tag" v-if="value.assCustomerLevel">{{
  351. value.assCustomerLevel
  352. }}</text>
  353. <text class="detail-tag" v-if="value.assResponsibleManager">{{
  354. value.assResponsibleManager
  355. }}</text>
  356. </view>
  357. <view
  358. class="detail-tag-row"
  359. v-if="value._showDetail && value.assEntName"
  360. >
  361. <text
  362. class="detail-tag"
  363. :style="getTagColorStyle(value.assEntTypeName)"
  364. v-if="value.assEntTypeName"
  365. >{{ value.assEntTypeName }}</text
  366. >
  367. <text
  368. class="detail-tag detail-tag-region"
  369. v-if="getRegionByPrefix(value, 'ass')"
  370. >{{ getRegionByPrefix(value, "ass") }}</text
  371. >
  372. </view>
  373. </view>
  374. </view>
  375. <view
  376. v-if="timelineView === 'text' && hasMoreTimelineItems"
  377. class="detail-timeline-loadmore"
  378. >
  379. 上拉加载更多({{ visibleBillDetailList.length }}/{{
  380. timelineTotalCount
  381. }})
  382. </view>
  383. </view>
  384. </scroll-view>
  385. <transition name="back-top-fade">
  386. <view
  387. v-if="!!data && !loading && showBackTopButton"
  388. class="detail-back-top-btn"
  389. @tap="backToTop"
  390. >
  391. <view class="detail-back-top-arrow"></view>
  392. </view>
  393. </transition>
  394. <view
  395. class="detail-page-empty"
  396. :style="{ height: pageHeight }"
  397. v-if="!data && !loading"
  398. >
  399. <image
  400. src="../../../static/images/empty.png"
  401. mode="scaleToFill"
  402. :style="{ width: '264rpx', height: '200rpx' }"
  403. />
  404. <text
  405. :style="{
  406. fontSize: '24rpx',
  407. color: '#99a1ad',
  408. marginTop: '60rpx',
  409. textAlign: 'center',
  410. }"
  411. >所查询药品所在生产企业尚未纳入扫码范围,后续将进一步扩大,敬请期待</text
  412. >
  413. </view>
  414. <view
  415. class="detail-page-empty"
  416. :style="{ height: pageHeight }"
  417. v-if="loading"
  418. >
  419. <image
  420. src="../../../static/images/loading.png"
  421. mode="scaleToFill"
  422. :style="{ width: '50rpx', height: '50rpx' }"
  423. class="detail-loading"
  424. />
  425. </view>
  426. </template>
  427. <script>
  428. import bg from "../../../static/images/bg.png";
  429. import { formatDate } from "../../../utils/utils.js";
  430. import request from "../../../request/index.js";
  431. import Water from "@/components/water/water.vue";
  432. // import lEchart from "../../../uni_modules/lime-echart/components/l-echart/l-echart.vue";
  433. // const echarts = require("../../../static/js/echarts.min.js");
  434. export default {
  435. components: {
  436. Water,
  437. // lEchart,
  438. },
  439. data() {
  440. return {
  441. data: null,
  442. loading: true,
  443. statusBarHeight: 20,
  444. traceCode: "",
  445. pageHeight: 0,
  446. bg,
  447. companyExpanded: true,
  448. needCompanyToggle: false,
  449. cardExpanded: false,
  450. allTimelineDetail: true,
  451. timelineView: "text",
  452. defaultTimelineViewKey:
  453. "traceabilityCodeQuery.detail.timelineDefaultView",
  454. imageData: [],
  455. graphViewWidth: 300,
  456. graphViewHeight: 480,
  457. chartInstance: null,
  458. chartOption: {},
  459. ownershipFlowMode: false,
  460. timelinePageSize: 20,
  461. timelineVisibleCount: 20,
  462. scrollTopValue: 0,
  463. scrollIntoViewId: "",
  464. backTopThreshold: 0,
  465. showBackTopButton: false,
  466. backTopHideTimer: null,
  467. };
  468. },
  469. onLoad(options) {
  470. const info = uni.getSystemInfoSync();
  471. this.statusBarHeight = info.statusBarHeight || 20;
  472. this.pageHeight = (info.windowHeight && info.windowHeight + "px") || "100%";
  473. this.backTopThreshold = info.windowHeight || 667;
  474. this.graphViewWidth = Math.max((info.windowWidth || 375) - 36, 280);
  475. this.graphViewHeight = Math.max((info.windowHeight || 667) - 200, 420);
  476. if (options.traceCode) {
  477. this.traceCode = options.traceCode;
  478. }
  479. // const defaultView = uni.getStorageSync(this.defaultTimelineViewKey);
  480. // if (defaultView === "graph" || defaultView === "text") {
  481. // this.timelineView = defaultView;
  482. // }
  483. // this.getData(options.traceCode);
  484. this.getData2(options.traceCode);
  485. },
  486. onReady() {
  487. this.$nextTick(() => {
  488. const info = uni.getSystemInfoSync();
  489. const lineHeightPx = (info.windowWidth / 750) * 40;
  490. this.companyExpanded = true; // 展开测量高度
  491. uni
  492. .createSelectorQuery()
  493. .in(this)
  494. .select("#companyValue")
  495. .boundingClientRect((rect) => {
  496. if (!rect || !lineHeightPx) return;
  497. const lines = Math.round(rect.height / lineHeightPx);
  498. this.needCompanyToggle = lines > 2;
  499. this.companyExpanded = !this.needCompanyToggle;
  500. })
  501. .exec();
  502. });
  503. },
  504. onReachBottom() {
  505. if (this.timelineView !== "text") return;
  506. this.loadMoreTimelineItems();
  507. },
  508. // 页面卸载时销毁图表实例
  509. // beforeUnmount() {
  510. // if (this.backTopHideTimer) {
  511. // clearTimeout(this.backTopHideTimer);
  512. // this.backTopHideTimer = null;
  513. // }
  514. // if (this.$refs.chartRef) {
  515. // this.$refs.chartRef.dispose();
  516. // }
  517. // },
  518. computed: {
  519. isShowGraph() {
  520. return this.data.billDetailList.length < 30;
  521. },
  522. timelineTotalCount() {
  523. if (!this.data || !Array.isArray(this.data.billDetailList)) return 0;
  524. return this.data.billDetailList.length;
  525. },
  526. visibleBillDetailList() {
  527. if (!this.data || !Array.isArray(this.data.billDetailList)) return [];
  528. const maxCount = Math.min(
  529. this.timelineVisibleCount,
  530. this.timelineTotalCount,
  531. );
  532. return this.data.billDetailList.slice(0, maxCount);
  533. },
  534. hasMoreTimelineItems() {
  535. return this.visibleBillDetailList.length < this.timelineTotalCount;
  536. },
  537. },
  538. methods: {
  539. getTagColorStyle(tag) {
  540. let color = "#57bfe1";
  541. if (tag.includes("生产")) color = "#f5c269";
  542. if (tag.includes("零售")) color = "#91c76d";
  543. return {
  544. color,
  545. borderColor: color + "80",
  546. };
  547. },
  548. // 初始化图表
  549. // async initChart() {
  550. // if (
  551. // !this.$refs.chartRef ||
  552. // !this.isShowGraph ||
  553. // this.timelineView !== "graph"
  554. // )
  555. // return;
  556. // let option = {};
  557. // const data = [];
  558. // const links = [];
  559. // const selfCurveness = 0.5;
  560. // const curveness = 0.7;
  561. // const lineWidth = 2;
  562. // const length = this.imageData.length;
  563. // const x = 350;
  564. // const offset = 200;
  565. // let i = 0;
  566. // this.imageData.forEach((item, index) => {
  567. // const { name, to } = item;
  568. // const toLen = to.length;
  569. // const y = index * 250 + 100;
  570. // const color = name.includes("生产")
  571. // ? "#fde58f"
  572. // : name.includes("零售")
  573. // ? "#b5ea8e"
  574. // : name.includes("消费者")
  575. // ? "#e0d3fe"
  576. // : "#a3e6ff";
  577. // data.push({
  578. // name,
  579. // x,
  580. // y,
  581. // itemStyle: {
  582. // color,
  583. // },
  584. // });
  585. // to.forEach((toItem, toIndex) => {
  586. // const { name: toName, text: toText } = toItem;
  587. // if (name.includes(toName)) {
  588. // data.push({
  589. // name: toText,
  590. // x,
  591. // y: y - 200,
  592. // itemStyle: {
  593. // color: "rgba(0,0,0,0)",
  594. // },
  595. // label: {
  596. // show: true,
  597. // color: "#c6c6c6",
  598. // },
  599. // tooltip: {
  600. // show: false,
  601. // },
  602. // });
  603. // links.push(
  604. // {
  605. // source: name,
  606. // target: toText,
  607. // label: {
  608. // show: false,
  609. // },
  610. // lineStyle: {
  611. // color: "#c6c6c6",
  612. // width: lineWidth,
  613. // curveness: selfCurveness,
  614. // },
  615. // },
  616. // {
  617. // source: toText,
  618. // target: name,
  619. // label: {
  620. // show: false,
  621. // },
  622. // lineStyle: {
  623. // color: "#c6c6c6",
  624. // width: lineWidth,
  625. // curveness: selfCurveness,
  626. // },
  627. // },
  628. // );
  629. // } else {
  630. // const isOut = toText.includes("出库");
  631. // links.push({
  632. // source: name,
  633. // target: toName,
  634. // label: {
  635. // show: true,
  636. // formatter: (params) => {
  637. // const text = toText;
  638. // if (!text) return "";
  639. // return text.split(/\s+/).filter(Boolean).join("\n");
  640. // },
  641. // color: isOut ? "#80a6fd" : "#aaaaaa",
  642. // fontSize: 12,
  643. // rotate: 0,
  644. // width: 100,
  645. // overflow: "breakAll",
  646. // },
  647. // lineStyle: {
  648. // color: isOut ? "#80a6fd" : "#aaaaaa",
  649. // width: lineWidth,
  650. // curveness: isOut ? curveness : -curveness,
  651. // },
  652. // });
  653. // }
  654. // });
  655. // });
  656. // console.log("this.imageData", this.imageData);
  657. // console.log("data", data);
  658. // console.log("links", links);
  659. // option = {
  660. // tooltip: {},
  661. // animationDurationUpdate: 1500,
  662. // animationEasingUpdate: "quinticInOut",
  663. // series: [
  664. // {
  665. // type: "graph",
  666. // layout: "none",
  667. // symbolSize: 80,
  668. // roam: true,
  669. // label: {
  670. // show: true,
  671. // formatter: (params) => {
  672. // const text = String((params && params.name) || "");
  673. // if (!text) return "";
  674. // return text.split(/\s+/).filter(Boolean).join("\n");
  675. // },
  676. // },
  677. // edgeSymbol: ["circle", "arrow"],
  678. // edgeSymbolSize: [4, 10],
  679. // data,
  680. // links,
  681. // lineStyle: {
  682. // opacity: 0.9,
  683. // width: 2,
  684. // curveness: 0,
  685. // },
  686. // },
  687. // ],
  688. // };
  689. // try {
  690. // await this.$nextTick();
  691. // // if (this.chartInstance) {
  692. // // this.chartInstance.setOption(option, true);
  693. // // return;
  694. // // }
  695. // this.chartInstance = await this.$refs.chartRef.init(echarts);
  696. // this.chartInstance.setOption(option, true);
  697. // } catch (error) {
  698. // console.error("图表初始化失败:", error);
  699. // }
  700. // },
  701. // 更新图表数据
  702. updateChart(newOption) {
  703. if (this.chartInstance) {
  704. this.chartInstance.setOption(newOption);
  705. } else if (this.$refs.chartRef) {
  706. this.$refs.chartRef.setOption(newOption);
  707. }
  708. },
  709. // 调整图表大小
  710. resizeChart() {
  711. if (this.$refs.chartRef) {
  712. this.$refs.chartRef.resize();
  713. }
  714. },
  715. formatDate(date) {
  716. return formatDate({ date }, "YYYY-MM-DD");
  717. },
  718. getRegion(value = {}) {
  719. const { provinceDesc, cityDesc, areaDesc } = value;
  720. return (
  721. [provinceDesc, cityDesc, areaDesc].filter(Boolean).join(",") || ""
  722. );
  723. },
  724. getRegionByPrefix(value = {}, prefix = "from") {
  725. const province = value[`${prefix}ProvinceDesc`];
  726. const city = value[`${prefix}CityDesc`];
  727. const area = value[`${prefix}AreaDesc`];
  728. return [province, city, area].filter(Boolean).join("-") || "";
  729. },
  730. initTimelineVisibleCount() {
  731. this.timelineVisibleCount = this.timelinePageSize;
  732. },
  733. onPageScroll(e) {
  734. const top = Number((e && e.detail && e.detail.scrollTop) || 0);
  735. if (top < this.backTopThreshold) {
  736. this.showBackTopButton = false;
  737. if (this.backTopHideTimer) {
  738. clearTimeout(this.backTopHideTimer);
  739. this.backTopHideTimer = null;
  740. }
  741. return;
  742. }
  743. this.showBackTopButton = true;
  744. if (this.backTopHideTimer) {
  745. clearTimeout(this.backTopHideTimer);
  746. }
  747. this.backTopHideTimer = setTimeout(() => {
  748. this.showBackTopButton = false;
  749. this.backTopHideTimer = null;
  750. }, 3000);
  751. },
  752. backToTop() {
  753. this.scrollTopValue = 0;
  754. this.scrollIntoViewId = "detailPageTopAnchor";
  755. this.showBackTopButton = false;
  756. if (this.backTopHideTimer) {
  757. clearTimeout(this.backTopHideTimer);
  758. this.backTopHideTimer = null;
  759. }
  760. this.$nextTick(() => {
  761. this.scrollIntoViewId = "";
  762. });
  763. },
  764. onTimelineScrollToLower() {
  765. if (this.timelineView !== "text") return;
  766. this.loadMoreTimelineItems();
  767. },
  768. loadMoreTimelineItems() {
  769. if (!this.hasMoreTimelineItems) return;
  770. this.timelineVisibleCount = Math.min(
  771. this.timelineVisibleCount + this.timelinePageSize,
  772. this.timelineTotalCount,
  773. );
  774. },
  775. toggleTimelineItem(index) {
  776. if (!this.data || !Array.isArray(this.data.billDetailList)) return;
  777. const item = this.data.billDetailList[index];
  778. if (!item) return;
  779. this.$set(item, "_showDetail", !item._showDetail);
  780. },
  781. setAllTimelineDetail(showDetail) {
  782. this.allTimelineDetail = !!showDetail;
  783. if (!this.data || !Array.isArray(this.data.billDetailList)) return;
  784. this.data.billDetailList.forEach((item) => {
  785. if (item) this.$set(item, "_showDetail", !!showDetail);
  786. });
  787. },
  788. toggleTimelineView() {
  789. this.timelineView = this.timelineView === "text" ? "graph" : "text";
  790. if (this.timelineView === "text") {
  791. this.initTimelineVisibleCount();
  792. }
  793. if (
  794. this.timelineView === "graph" &&
  795. this.data &&
  796. Array.isArray(this.data.billDetailList)
  797. ) {
  798. this.imageData = this.ownershipFlowMode
  799. ? this.buildImageData2(this.data.billDetailList)
  800. : this.buildImageData(this.data.billDetailList);
  801. this.updateGraphCanvasSize();
  802. this.$nextTick(() => this.initChart());
  803. }
  804. },
  805. onOwnershipModeChange(e) {
  806. this.ownershipFlowMode = !!(e && e.detail && e.detail.value);
  807. if (!this.data || !Array.isArray(this.data.billDetailList)) return;
  808. this.imageData = this.ownershipFlowMode
  809. ? this.buildImageData2(this.data.billDetailList)
  810. : this.buildImageData(this.data.billDetailList);
  811. this.updateGraphCanvasSize();
  812. if (this.timelineView === "graph") {
  813. this.$nextTick(() => this.initChart());
  814. }
  815. },
  816. onSetDefaultView() {
  817. uni.setStorageSync(this.defaultTimelineViewKey, this.timelineView);
  818. uni.showToast({
  819. title: "已设为默认",
  820. icon: "none",
  821. });
  822. },
  823. buildImageData2(data) {
  824. if (!data || !Array.isArray(data)) return [];
  825. const list = [];
  826. const buildTime = (time) => {
  827. return time.split(" ")[0];
  828. };
  829. const buildTo = (to, item) => {
  830. const hasAss = !!item.assEntName;
  831. const hasType = item.billTypeName && item.billTypeName !== "****";
  832. if (item.billTypeName === "使用出库") {
  833. to.push({
  834. name: "消费者",
  835. text: !hasType
  836. ? "****"
  837. : buildTime(item.billTime) + " " + item.billTypeName,
  838. });
  839. } else if (item.toEntName) {
  840. const address = [
  841. item.toProvinceDesc,
  842. item.toCityDesc,
  843. item.toAreaDesc,
  844. ]
  845. .filter(Boolean)
  846. .join(",");
  847. to.push({
  848. name: item.toEntTypeName + " " + item.toEntName + " " + address,
  849. text: !hasType
  850. ? "****"
  851. : buildTime(item.billTime) + " " + item.billTypeName,
  852. });
  853. if (hasAss && hasType) {
  854. to[to.length - 1].text += " 被委托:" + item.fromEntName;
  855. }
  856. }
  857. return to;
  858. };
  859. data.forEach((item, i) => {
  860. const hasAss = !!item.assEntName;
  861. const index = list.findIndex((i) =>
  862. i.name.includes(hasAss ? item.assEntName : item.fromEntName),
  863. );
  864. let to = [];
  865. if (index !== -1) {
  866. to = list[index].to;
  867. }
  868. to = buildTo(to, item);
  869. if (index === -1) {
  870. const address = (
  871. hasAss
  872. ? [item.assProvinceDesc, item.assCityDesc, item.assAreaDesc]
  873. : [item.fromProvinceDesc, item.fromCityDesc, item.fromAreaDesc]
  874. )
  875. .filter(Boolean)
  876. .join(",");
  877. const name = hasAss
  878. ? item.assEntTypeName + " " + item.assEntName + " " + address
  879. : item.fromEntTypeName + " " + item.fromEntName + " " + address;
  880. list.push({
  881. num: i,
  882. name,
  883. to,
  884. });
  885. } else {
  886. list[index].to = to;
  887. }
  888. if (
  889. item.toEntName &&
  890. list.findIndex((i) => i.name.includes(item.toEntName)) === -1
  891. ) {
  892. const address = [
  893. item.toProvinceDesc,
  894. item.toCityDesc,
  895. item.toAreaDesc,
  896. ]
  897. .filter(Boolean)
  898. .join(",");
  899. list.push({
  900. num: i + 9999,
  901. name: item.toEntTypeName + " " + item.toEntName + " " + address,
  902. to: [],
  903. });
  904. }
  905. });
  906. const lastItem = list[list.length - 1];
  907. if (lastItem?.to?.[0]?.name === "消费者") {
  908. list.push({
  909. num: 99999,
  910. name: "消费者",
  911. to: [],
  912. });
  913. }
  914. list.sort((a, b) => a.num - b.num);
  915. return list;
  916. },
  917. buildImageData(data) {
  918. if (!data || !Array.isArray(data)) return [];
  919. const list = [];
  920. const buildTime = (time) => {
  921. return time.split(" ")[0];
  922. };
  923. const buildTo = (to, item) => {
  924. const hasType = item.billTypeName && item.billTypeName !== "****";
  925. if (item.billTypeName === "使用出库") {
  926. to.push({
  927. name: "消费者",
  928. text: !hasType
  929. ? "****"
  930. : buildTime(item.billTime) + " " + item.billTypeName,
  931. });
  932. } else if (item.toEntName) {
  933. const address = [
  934. item.toProvinceDesc,
  935. item.toCityDesc,
  936. item.toAreaDesc,
  937. ]
  938. .filter(Boolean)
  939. .join(",");
  940. to.push({
  941. name: item.toEntTypeName + " " + item.toEntName + " " + address,
  942. text: !hasType
  943. ? "****"
  944. : buildTime(item.billTime) + " " + item.billTypeName,
  945. });
  946. if (item.assEntName && hasType) {
  947. to[to.length - 1].text += " 委托:" + item.assEntName;
  948. }
  949. }
  950. return to;
  951. };
  952. data.forEach((item, i) => {
  953. const index = list.findIndex((i) => i.name.includes(item.fromEntName));
  954. let to = [];
  955. if (index !== -1) {
  956. to = list[index].to;
  957. }
  958. to = buildTo(to, item);
  959. if (index === -1) {
  960. const address = [
  961. item.fromProvinceDesc,
  962. item.fromCityDesc,
  963. item.fromAreaDesc,
  964. ]
  965. .filter(Boolean)
  966. .join(",");
  967. list.push({
  968. num: i,
  969. name: item.fromEntTypeName + " " + item.fromEntName + " " + address,
  970. to,
  971. });
  972. } else {
  973. list[index].to = to;
  974. }
  975. if (
  976. item.toEntName &&
  977. list.findIndex((i) => i.name.includes(item.toEntName)) === -1
  978. ) {
  979. const address = [
  980. item.toProvinceDesc,
  981. item.toCityDesc,
  982. item.toAreaDesc,
  983. ]
  984. .filter(Boolean)
  985. .join(",");
  986. list.push({
  987. num: i + 9999,
  988. name: item.toEntTypeName + " " + item.toEntName + " " + address,
  989. to: [],
  990. });
  991. }
  992. });
  993. const lastItem = list[list.length - 1];
  994. if (lastItem?.to?.[0]?.name === "消费者") {
  995. list.push({
  996. num: 99999,
  997. name: "消费者",
  998. to: [],
  999. });
  1000. }
  1001. list.sort((a, b) => a.num - b.num);
  1002. return list;
  1003. },
  1004. updateGraphCanvasSize() {
  1005. if (!this.isShowGraph) {
  1006. this.graphViewHeight = 50;
  1007. return;
  1008. }
  1009. const list = this.imageData || [];
  1010. const top = 90;
  1011. const bottom = 90;
  1012. const vGap = 170;
  1013. const nodeCount = list.length || 1;
  1014. this.graphViewHeight = Math.max(
  1015. 520,
  1016. top + bottom + (nodeCount - 1) * vGap,
  1017. );
  1018. },
  1019. getData2(traceCode) {
  1020. request("/bills/scanCode2", {
  1021. tracCode: traceCode,
  1022. path: "/traceabilityCodeQuery/pages/detail/index.vue",
  1023. }).then((res) => {
  1024. if (res.code == 200) {
  1025. const _data = res.data;
  1026. this.data = _data || null;
  1027. if (this.data && Array.isArray(this.data.billDetailList)) {
  1028. this.data.billDetailList = this.data.billDetailList.map((item) => ({
  1029. ...item,
  1030. _showDetail: !!this.allTimelineDetail,
  1031. }));
  1032. // this.initTimelineVisibleCount();
  1033. // if (this.isShowGraph) {
  1034. // this.imageData = this.ownershipFlowMode
  1035. // ? this.buildImageData2(this.data.billDetailList)
  1036. // : this.buildImageData(this.data.billDetailList);
  1037. // }
  1038. // this.updateGraphCanvasSize();
  1039. // this.initChart();
  1040. }
  1041. if (
  1042. typeof _data === "object" &&
  1043. Object.values(_data).filter((i) => !!i).length == 0
  1044. ) {
  1045. this.data = null;
  1046. }
  1047. this.loading = false;
  1048. } else {
  1049. uni.showToast({ title: "查询失败", icon: "none" });
  1050. }
  1051. });
  1052. },
  1053. getData(traceCode) {
  1054. request("/bills/scanCode", {
  1055. tracCode: traceCode,
  1056. path: "/traceabilityCodeQuery/pages/detail/index.vue",
  1057. }).then((res) => {
  1058. if (res.code == 200) {
  1059. // const _data = res.data;
  1060. // this.data = _data || null;
  1061. // const defaultData = [
  1062. // {
  1063. // billTypeName: "生产入库",
  1064. // billCode: "SYS_IN_2025041410194699357",
  1065. // fromEntId: "00000000000000959808",
  1066. // fromEntName: "惠州市九惠制药股份有限公司",
  1067. // fromEntType: "1",
  1068. // fromEntTypeName: "生产企业",
  1069. // fromProvinceDesc: "广东省",
  1070. // fromCityDesc: "惠州市",
  1071. // fromAreaDesc: "惠城区",
  1072. // toEntId: "00000000000000959808",
  1073. // toEntName: "惠州市九惠制药股份有限公司",
  1074. // toEntType: "1",
  1075. // toEntTypeName: "生产企业",
  1076. // toProvinceDesc: "广东省",
  1077. // toCityDesc: "惠州市",
  1078. // toAreaDesc: "惠城区",
  1079. // billTime: "2025-04-12 00:00:00",
  1080. // uploadEntId: "00000000000000959808",
  1081. // uploadEntName: "惠州市九惠制药股份有限公司",
  1082. // uploadEntType: "1",
  1083. // uploadEntTypeName: "生产企业",
  1084. // uploadProvinceDesc: "广东省",
  1085. // uploadCityDesc: "惠州市",
  1086. // uploadAreaDesc: "惠城区",
  1087. // uploadTime: "2025-04-14 10:19:47",
  1088. // },
  1089. // {
  1090. // billTypeName: "销售出库",
  1091. // billCode: "20250414093811017995",
  1092. // fromEntId: "00000000000000959808",
  1093. // fromEntName: "惠州市九惠制药股份有限公司",
  1094. // fromEntType: "1",
  1095. // fromEntTypeName: "生产企业",
  1096. // fromProvinceDesc: "广东省",
  1097. // fromCityDesc: "惠州市",
  1098. // fromAreaDesc: "惠城区",
  1099. // toEntId: "00000000000012618117",
  1100. // toEntName: "深圳华润三九医药贸易有限公司",
  1101. // toEntType: "2",
  1102. // toEntTypeName: "批发企业",
  1103. // toProvinceDesc: "广东省",
  1104. // toCityDesc: "深圳市",
  1105. // toAreaDesc: "龙华区",
  1106. // billTime: "2025-04-14 09:38:11",
  1107. // uploadEntId: "00000000000000592642",
  1108. // uploadEntName: "华润三九医药股份有限公司",
  1109. // uploadEntType: "1",
  1110. // uploadEntTypeName: "生产企业",
  1111. // uploadProvinceDesc: "广东省",
  1112. // uploadCityDesc: "深圳市",
  1113. // uploadAreaDesc: "龙华区",
  1114. // uploadTime: "2025-10-31 17:29:36",
  1115. // },
  1116. // {
  1117. // billTypeName: "销售出库",
  1118. // billCode: "2504032G-2",
  1119. // fromEntId: "00000000000012618117",
  1120. // fromEntName: "深圳华润三九医药贸易有限公司",
  1121. // fromEntType: "2",
  1122. // fromEntTypeName: "批发企业",
  1123. // fromProvinceDesc: "广东省",
  1124. // fromCityDesc: "深圳市",
  1125. // fromAreaDesc: "龙华区",
  1126. // toEntId: "8b63aa19a9a94cb6aa472ee69d309bb5",
  1127. // toEntName: "国药控股福建有限公司",
  1128. // toEntType: "5",
  1129. // toEntTypeName: "物流企业",
  1130. // toProvinceDesc: "福建省",
  1131. // toCityDesc: "厦门市",
  1132. // toAreaDesc: "思明区",
  1133. // billTime: "2025-09-27 00:00:00",
  1134. // uploadEntId: "00000000000012618117",
  1135. // uploadEntName: "深圳华润三九医药贸易有限公司",
  1136. // uploadEntType: "2",
  1137. // uploadEntTypeName: "批发企业",
  1138. // uploadProvinceDesc: "广东省",
  1139. // uploadCityDesc: "深圳市",
  1140. // uploadAreaDesc: "龙华区",
  1141. // uploadTime: "2025-09-28 10:04:12",
  1142. // },
  1143. // {
  1144. // billTypeName: "采购入库",
  1145. // billCode: "2504032G-1",
  1146. // fromEntId: "00000000000000592642",
  1147. // fromEntName: "华润三九医药股份有限公司",
  1148. // fromEntType: "1",
  1149. // fromEntTypeName: "生产企业",
  1150. // fromProvinceDesc: "广东省",
  1151. // fromCityDesc: "深圳市",
  1152. // fromAreaDesc: "龙华区",
  1153. // toEntId: "00000000000012618117",
  1154. // toEntName: "深圳华润三九医药贸易有限公司",
  1155. // toEntType: "2",
  1156. // toEntTypeName: "批发企业",
  1157. // toProvinceDesc: "广东省",
  1158. // toCityDesc: "深圳市",
  1159. // toAreaDesc: "龙华区",
  1160. // billTime: "2025-09-27 00:00:00",
  1161. // uploadEntId: "00000000000000592642",
  1162. // uploadEntName: "华润三九医药股份有限公司",
  1163. // uploadEntType: "1",
  1164. // uploadEntTypeName: "生产企业",
  1165. // uploadProvinceDesc: "广东省",
  1166. // uploadCityDesc: "深圳市",
  1167. // uploadAreaDesc: "龙华区",
  1168. // uploadTime: "2025-09-28 10:04:06",
  1169. // },
  1170. // {
  1171. // billTypeName: "采购入库",
  1172. // billCode: "FJASNP2509280580393",
  1173. // fromEntId: "00000000000012618117",
  1174. // fromEntName: "深圳华润三九医药贸易有限公司",
  1175. // fromEntType: "2",
  1176. // fromEntTypeName: "批发企业",
  1177. // fromProvinceDesc: "广东省",
  1178. // fromCityDesc: "深圳市",
  1179. // fromAreaDesc: "龙华区",
  1180. // toEntId: "8b63aa19a9a94cb6aa472ee69d309bb5",
  1181. // toEntName: "国药控股福建有限公司",
  1182. // toEntType: "5",
  1183. // toEntTypeName: "物流企业",
  1184. // toProvinceDesc: "福建省",
  1185. // toCityDesc: "厦门市",
  1186. // toAreaDesc: "思明区",
  1187. // billTime: "2025-10-30 00:00:00",
  1188. // uploadEntId: "00000000000012618117",
  1189. // uploadEntName: "深圳华润三九医药贸易有限公司",
  1190. // uploadEntType: "2",
  1191. // uploadEntTypeName: "批发企业",
  1192. // uploadProvinceDesc: "广东省",
  1193. // uploadCityDesc: "深圳市",
  1194. // uploadAreaDesc: "龙华区",
  1195. // uploadTime: "2025-10-30 17:05:35",
  1196. // },
  1197. // {
  1198. // billTypeName: "销售出库",
  1199. // billCode: "FJS0CFFPHL",
  1200. // fromEntId: "8b63aa19a9a94cb6aa472ee69d309bb5",
  1201. // fromEntName: "国药控股福建有限公司",
  1202. // fromEntType: "5",
  1203. // fromEntTypeName: "物流企业",
  1204. // fromProvinceDesc: "福建省",
  1205. // fromCityDesc: "厦门市",
  1206. // fromAreaDesc: "思明区",
  1207. // toEntId: "00000000000001312166",
  1208. // toEntName: "国药控股厦门有限公司",
  1209. // toEntType: "2",
  1210. // toEntTypeName: "批发企业",
  1211. // toProvinceDesc: "福建省",
  1212. // toCityDesc: "厦门市",
  1213. // toAreaDesc: "同安区",
  1214. // billTime: "2025-10-31 00:00:00",
  1215. // uploadEntId: "8b63aa19a9a94cb6aa472ee69d309bb5",
  1216. // uploadEntName: "国药控股福建有限公司",
  1217. // uploadEntType: "5",
  1218. // uploadEntTypeName: "物流企业",
  1219. // uploadProvinceDesc: "福建省",
  1220. // uploadCityDesc: "厦门市",
  1221. // uploadAreaDesc: "思明区",
  1222. // uploadTime: "2025-11-01 00:03:29",
  1223. // },
  1224. // {
  1225. // billTypeName: "采购入库",
  1226. // billCode: "XMASNP2510310469890",
  1227. // fromEntId: "8b63aa19a9a94cb6aa472ee69d309bb5",
  1228. // fromEntName: "国药控股福建有限公司",
  1229. // fromEntType: "5",
  1230. // fromEntTypeName: "物流企业",
  1231. // fromProvinceDesc: "福建省",
  1232. // fromCityDesc: "厦门市",
  1233. // fromAreaDesc: "思明区",
  1234. // toEntId: "00000000000001312166",
  1235. // toEntName: "国药控股厦门有限公司",
  1236. // toEntType: "2",
  1237. // toEntTypeName: "批发企业",
  1238. // toProvinceDesc: "福建省",
  1239. // toCityDesc: "厦门市",
  1240. // toAreaDesc: "同安区",
  1241. // billTime: "2025-11-03 00:00:00",
  1242. // uploadEntId: "8b63aa19a9a94cb6aa472ee69d309bb5",
  1243. // uploadEntName: "国药控股福建有限公司",
  1244. // uploadEntType: "5",
  1245. // uploadEntTypeName: "物流企业",
  1246. // uploadProvinceDesc: "福建省",
  1247. // uploadCityDesc: "厦门市",
  1248. // uploadAreaDesc: "思明区",
  1249. // uploadTime: "2025-11-03 17:14:48",
  1250. // },
  1251. // ];
  1252. // this.imageData = this.buildImageData(defaultData);
  1253. // this.updateGraphCanvasSize();
  1254. // this.initChart();
  1255. // if (
  1256. // typeof _data === "object" &&
  1257. // Object.values(_data).filter((i) => !!i).length == 0
  1258. // ) {
  1259. // this.data = null;
  1260. // }
  1261. // this.loading = false;
  1262. // } else {
  1263. // uni.showToast({ title: "查询失败", icon: "none" });
  1264. // }
  1265. }
  1266. });
  1267. },
  1268. copy(text) {
  1269. const data = String(text || this.traceCode);
  1270. uni.setClipboardData({
  1271. data,
  1272. success: () => {
  1273. console.log(
  1274. "packages/traceabilityCodeQuery/pages/detail/index.vue" +
  1275. "storage" +
  1276. "clipboardData",
  1277. );
  1278. uni.showToast({
  1279. title: "已复制追溯码",
  1280. icon: "none",
  1281. duration: 2000,
  1282. });
  1283. },
  1284. });
  1285. },
  1286. toggleCompanyExpand() {
  1287. if (!this.needCompanyToggle) return;
  1288. this.companyExpanded = !this.companyExpanded;
  1289. },
  1290. toggleCardExpand() {
  1291. this.cardExpanded = !this.cardExpanded;
  1292. },
  1293. formatTraceCode(traceCode) {
  1294. return String(traceCode || "")
  1295. .replace(/(.{5})/g, "$1 ")
  1296. .trim();
  1297. },
  1298. },
  1299. };
  1300. </script>
  1301. <style>
  1302. .detail-bg {
  1303. background-position: center top;
  1304. background-size: 100% auto;
  1305. }
  1306. .detail-page {
  1307. box-sizing: border-box;
  1308. background: rgb(226 226 226);
  1309. padding-bottom: 50rpx;
  1310. overflow: auto;
  1311. }
  1312. .detail-page-empty {
  1313. box-sizing: border-box;
  1314. display: flex;
  1315. flex-direction: column;
  1316. align-items: center;
  1317. justify-content: center;
  1318. padding: 0 136rpx 280rpx;
  1319. }
  1320. .detail-back-top-btn {
  1321. position: fixed;
  1322. right: 28rpx;
  1323. bottom: 110rpx;
  1324. width: 84rpx;
  1325. height: 84rpx;
  1326. border-radius: 50%;
  1327. background: #fff;
  1328. display: flex;
  1329. align-items: center;
  1330. justify-content: center;
  1331. box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.18);
  1332. z-index: 999;
  1333. }
  1334. .back-top-fade-enter-active,
  1335. .back-top-fade-leave-active {
  1336. transition:
  1337. opacity 0.28s ease,
  1338. transform 0.28s ease;
  1339. }
  1340. .back-top-fade-enter-from,
  1341. .back-top-fade-leave-to {
  1342. opacity: 0;
  1343. transform: translateY(16rpx) scale(0.92);
  1344. }
  1345. .detail-back-top-arrow {
  1346. width: 20rpx;
  1347. height: 20rpx;
  1348. border-top: 4rpx solid #5f6773;
  1349. border-left: 4rpx solid #5f6773;
  1350. transform: rotate(45deg);
  1351. margin-top: 8rpx;
  1352. }
  1353. .detail-card {
  1354. padding: 40rpx;
  1355. }
  1356. .detail-drug-title {
  1357. font-size: 45rpx;
  1358. font-weight: 800;
  1359. color: #000;
  1360. margin-bottom: 16rpx;
  1361. }
  1362. .detail-row {
  1363. position: relative;
  1364. display: flex;
  1365. margin-top: 12rpx;
  1366. align-items: baseline;
  1367. }
  1368. .detail-label {
  1369. flex-shrink: 0;
  1370. display: inline-block;
  1371. vertical-align: baseline;
  1372. width: 150rpx;
  1373. font-size: 28rpx;
  1374. }
  1375. .detail-value {
  1376. display: inline-block;
  1377. vertical-align: baseline;
  1378. font-size: 28rpx;
  1379. }
  1380. .detail-company-value {
  1381. line-height: 40rpx;
  1382. }
  1383. .detail-company-value.detail-clamp {
  1384. display: -webkit-box;
  1385. -webkit-box-orient: vertical;
  1386. -webkit-line-clamp: 2;
  1387. overflow: hidden;
  1388. }
  1389. .detail-expand-tag {
  1390. position: absolute;
  1391. left: 150rpx;
  1392. bottom: 0;
  1393. color: #2c69ff;
  1394. font-size: 28rpx;
  1395. }
  1396. .detail-value.detail-wrap {
  1397. word-break: break-all;
  1398. white-space: normal;
  1399. }
  1400. .detail-copy-btn {
  1401. margin-left: 16rpx;
  1402. display: flex;
  1403. align-items: center;
  1404. color: #2c69ff;
  1405. }
  1406. .detail-copy-text {
  1407. margin-left: 6rpx;
  1408. font-size: 26rpx;
  1409. }
  1410. .detail-traceability {
  1411. font-size: 24rpx;
  1412. margin: -10rpx 0 45rpx;
  1413. }
  1414. .detail-card-toggle {
  1415. display: flex;
  1416. justify-content: center;
  1417. align-items: center;
  1418. padding-top: 18rpx;
  1419. }
  1420. .detail-traceability .detail-label {
  1421. width: auto;
  1422. }
  1423. .detail-section-header {
  1424. padding: 30rpx 24rpx 20rpx 46rpx;
  1425. border-bottom: 1rpx solid #55555526;
  1426. display: flex;
  1427. justify-content: space-between;
  1428. align-items: center;
  1429. }
  1430. .detail-section-title {
  1431. font-size: 35rpx;
  1432. font-weight: 900;
  1433. }
  1434. .detail-section-actions {
  1435. display: flex;
  1436. align-items: center;
  1437. gap: 12rpx;
  1438. }
  1439. .detail-section-action {
  1440. font-size: 26rpx;
  1441. color: #59a8f2;
  1442. padding: 4rpx 6rpx;
  1443. display: flex;
  1444. align-items: center;
  1445. gap: 6rpx;
  1446. }
  1447. .detail-section-action.active {
  1448. color: #2c69ff;
  1449. font-weight: 600;
  1450. }
  1451. .detail-timeline {
  1452. margin-top: 20rpx;
  1453. padding-bottom: 50rpx;
  1454. }
  1455. .detail-graph-placeholder {
  1456. margin: 16rpx 20rpx 0;
  1457. background: #fff;
  1458. border-radius: 14rpx;
  1459. font-size: 24rpx;
  1460. text-align: center;
  1461. color: #97a0ae;
  1462. padding: 120rpx 20rpx;
  1463. }
  1464. .detail-graph-panel {
  1465. margin: 16rpx 20rpx 0;
  1466. background: #fff;
  1467. border-radius: 14rpx;
  1468. padding: 16rpx;
  1469. }
  1470. .graph-mode-row {
  1471. display: flex;
  1472. align-items: flex-start;
  1473. justify-content: space-between;
  1474. gap: 18rpx;
  1475. margin-bottom: 18rpx;
  1476. padding: 4rpx 6rpx 0;
  1477. }
  1478. .graph-mode-left {
  1479. flex: 1;
  1480. }
  1481. .graph-mode-title {
  1482. font-size: 30rpx;
  1483. color: #333;
  1484. font-weight: 600;
  1485. }
  1486. .graph-mode-desc {
  1487. margin-top: 8rpx;
  1488. font-size: 24rpx;
  1489. color: #8d96a0;
  1490. line-height: 1.45;
  1491. }
  1492. .detail-graph-canvas {
  1493. width: 100%;
  1494. height: 920rpx;
  1495. background: #f8fafc;
  1496. border-radius: 10rpx;
  1497. }
  1498. .detail-timeline-toolbar {
  1499. display: flex;
  1500. gap: 16rpx;
  1501. padding: 20rpx 32rpx 10rpx;
  1502. }
  1503. .detail-toolbar-btn {
  1504. padding: 8rpx 18rpx;
  1505. border: 1rpx solid #d8dbe0;
  1506. border-radius: 999rpx;
  1507. color: #666;
  1508. font-size: 24rpx;
  1509. background: #fff;
  1510. }
  1511. .detail-timeline-item {
  1512. position: relative;
  1513. display: flex;
  1514. margin-left: 36rpx;
  1515. }
  1516. .detail-timeline-item-first {
  1517. margin-top: 20rpx;
  1518. }
  1519. .detail-timeline-loadmore {
  1520. text-align: center;
  1521. color: #8f98a3;
  1522. font-size: 24rpx;
  1523. padding: 18rpx 0 8rpx;
  1524. }
  1525. .detail-left {
  1526. position: absolute;
  1527. top: 34rpx;
  1528. left: 0;
  1529. height: 100%;
  1530. display: flex;
  1531. align-items: center;
  1532. flex-direction: column;
  1533. }
  1534. .detail-dot {
  1535. width: 16rpx;
  1536. height: 16rpx;
  1537. border-radius: 50%;
  1538. border: 2rpx solid #2c69ff;
  1539. }
  1540. .detail-dot.detail-fill {
  1541. background: #2c69ff;
  1542. border-color: #2c69ff;
  1543. }
  1544. .detail-line {
  1545. width: 2rpx;
  1546. flex: 1;
  1547. background: #8face68f;
  1548. margin-right: 2rpx;
  1549. }
  1550. .detail-right {
  1551. flex: 1;
  1552. padding: 20rpx 0;
  1553. margin-left: 45rpx;
  1554. border-bottom: 1rpx solid #55555526;
  1555. }
  1556. .detail-title-row {
  1557. display: flex;
  1558. justify-content: space-between;
  1559. align-items: flex-start;
  1560. gap: 16rpx;
  1561. }
  1562. .detail-title {
  1563. font-size: 32rpx;
  1564. color: #333;
  1565. }
  1566. .detail-title.detail-bold {
  1567. font-weight: 800;
  1568. }
  1569. .detail-item-toggle {
  1570. flex-shrink: 0;
  1571. padding: 6rpx 12rpx;
  1572. border: 1rpx solid #d8dbe0;
  1573. border-radius: 999rpx;
  1574. font-size: 22rpx;
  1575. color: #666;
  1576. display: flex;
  1577. align-items: center;
  1578. gap: 6rpx;
  1579. margin-right: 16rpx;
  1580. }
  1581. .detail-info-row {
  1582. display: flex;
  1583. margin-top: 10rpx;
  1584. font-size: 28rpx;
  1585. color: #444;
  1586. }
  1587. .detail-info-label {
  1588. color: #666;
  1589. flex-shrink: 0;
  1590. }
  1591. .detail-info-value {
  1592. color: #333;
  1593. word-break: break-all;
  1594. }
  1595. .detail-tag-row {
  1596. display: flex;
  1597. flex-wrap: wrap;
  1598. gap: 10rpx;
  1599. margin-top: 8rpx;
  1600. padding-left: 130rpx;
  1601. }
  1602. .detail-tag {
  1603. color: #57bfe1;
  1604. border-radius: 8rpx;
  1605. padding: 5rpx 14rpx;
  1606. font-size: 22rpx;
  1607. border: 1rpx solid #57bfe180;
  1608. }
  1609. .detail-tag-region {
  1610. color: #999;
  1611. border: 1rpx solid #d8dbe0;
  1612. }
  1613. .detail-text-red {
  1614. color: red;
  1615. }
  1616. .detail-loading {
  1617. animation: spin 1s linear infinite;
  1618. }
  1619. @keyframes spin {
  1620. from {
  1621. transform: rotate(0deg);
  1622. }
  1623. to {
  1624. transform: rotate(360deg);
  1625. }
  1626. }
  1627. </style>