refund-detail-dialog.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. <template>
  2. <ele-modal
  3. :width="1200"
  4. v-model="visible"
  5. title="退款详情"
  6. fullscreen
  7. :body-style="{ padding: '0 20px', height: 'calc(100vh - 120px)' }"
  8. >
  9. <div v-loading="loading" class="h-full flex flex-col">
  10. <!-- 顶部状态栏 -->
  11. <div class="status-bar mb-6">
  12. <div class="flex items-start mb-4">
  13. <div class="status-info">
  14. <div class="text-xl font-bold mb-2 flex items-center">
  15. <span>{{ getStatusText(form.status) }}</span>
  16. </div>
  17. <!-- 倒计时组件 -->
  18. <div
  19. v-if="deadline > 0"
  20. class="flex items-center text-red-500 text-sm mb-3"
  21. >
  22. <span class="mr-2">剩余</span>
  23. <el-countdown
  24. format="DD [天] HH [时] mm [分]"
  25. :value="deadline"
  26. :value-style="{
  27. fontSize: '14px',
  28. color: '#ef4444',
  29. lineHeight: '1'
  30. }"
  31. />
  32. </div>
  33. <div class="text-sm text-gray-500 space-y-1">
  34. <template
  35. v-if="['1', '3'].includes(String(form.status))"
  36. >
  37. <div>{{
  38. isRefundOnly
  39. ? '请及时联系买家协商退款事宜'
  40. : '请及时联系买家协商退货事宜'
  41. }}</div>
  42. <div v-if="form.refundType == 1"
  43. >该退款为卖家原因退款,若您同意买家退货,退货运费将由您承担。</div
  44. >
  45. </template>
  46. <template
  47. v-else-if="
  48. String(form.status) === '7' &&
  49. String(form.refundType) === '1'
  50. "
  51. >
  52. <div>买家已发货,请及时验货并处理退款</div>
  53. </template>
  54. <template v-else-if="String(form.status) === '7'">
  55. <div>买家已发货,请及时验货并处理退款</div>
  56. </template>
  57. <template v-else-if="String(form.status) === '2'">
  58. <div>买家修改退款申请后,需要您重新处理</div>
  59. <div
  60. >如果买家超时未响应,退款申请将自动关闭</div
  61. >
  62. </template>
  63. <template v-else-if="String(form.status) === '9'">
  64. <div>退款成功时间:{{ form.finishTime }}</div>
  65. <div
  66. >退款金额:¥
  67. {{
  68. form.refundMoneyFinal ||
  69. form.refundMoney
  70. }}元</div
  71. >
  72. <div>退款规则:符合未发货秒退 </div>
  73. </template>
  74. </div>
  75. </div>
  76. <div style="min-width: 600px" class="pt-2">
  77. <el-steps
  78. :active="currentStep"
  79. align-center
  80. finish-status="success"
  81. :process-status="processStatus"
  82. >
  83. <template v-if="isRefundOnly">
  84. <el-step
  85. :title="`买家申请${getRefundTypeText(form.refundType)}`"
  86. />
  87. <el-step
  88. :title="
  89. String(form.status) === '10'
  90. ? '退款已撤销'
  91. : '卖家处理退款申请'
  92. "
  93. />
  94. <el-step title="退款完毕" />
  95. </template>
  96. <template v-else>
  97. <el-step title="买家申请退货退款" />
  98. <el-step
  99. :title="
  100. String(form.status) === '10'
  101. ? '退款已撤销'
  102. : '卖家处理退货申请'
  103. "
  104. />
  105. <el-step title="买家退货" />
  106. <el-step title="退款完毕" />
  107. </template>
  108. </el-steps>
  109. </div>
  110. </div>
  111. </div>
  112. <div class="detail-container flex flex-1 min-h-0 overflow-hidden">
  113. <!-- 左侧:退款信息 -->
  114. <div class="section refund-info flex-1 overflow-y-auto">
  115. <div class="section-title">退款信息</div>
  116. <div class="info-item">
  117. <span class="label">退款金额:</span>
  118. <span class="value text-red-500 font-bold">
  119. ¥{{ form.refundMoney }}
  120. <span
  121. v-if="form.refundMoneyFinal"
  122. class="text-xs text-gray-500 ml-1"
  123. >
  124. (实退: ¥{{ form.refundMoneyFinal }})
  125. </span>
  126. </span>
  127. </div>
  128. <div class="info-item">
  129. <span class="label">退款原因:</span>
  130. <span class="value">{{ form.refundReason }}</span>
  131. </div>
  132. <div class="info-item">
  133. <span class="label">要求:</span>
  134. <span class="value">{{
  135. getRefundTypeText(form.refundType)
  136. }}</span>
  137. </div>
  138. <div class="info-item">
  139. <span class="label">货物状态:</span>
  140. <span class="value">{{
  141. getShopStatusText(form.shopStatus)
  142. }}</span>
  143. </div>
  144. <div class="info-item">
  145. <span class="label">买家留言:</span>
  146. <span class="value">{{ form.description || '-' }}</span>
  147. </div>
  148. <div class="info-item">
  149. <span class="label">退款编号:</span>
  150. <span class="value">{{ form.refundOrderId }}</span>
  151. <el-icon
  152. class="copy-icon"
  153. @click="handleCopy(form.refundOrderId)"
  154. >
  155. <CopyDocument />
  156. </el-icon>
  157. </div>
  158. </div>
  159. <!-- 中间:交易信息 -->
  160. <div
  161. class="section transaction-info flex-[1.2] overflow-y-auto"
  162. >
  163. <div class="section-title">交易信息</div>
  164. <div class="product-list mb-4">
  165. <div
  166. v-for="(prod, idx) in form.detailList"
  167. :key="idx"
  168. class="product-item flex mb-2"
  169. >
  170. <el-image
  171. :src="prod.cover"
  172. class="w-16 h-16 rounded mr-2"
  173. fit="cover"
  174. />
  175. <div class="flex-1">
  176. <div
  177. class="text-sm font-bold truncate-2-lines"
  178. >{{ prod.bookName }}</div
  179. >
  180. <div class="text-xs text-gray-500 mt-1">{{
  181. prod.isbn
  182. }}</div>
  183. <div class="text-xs text-gray-500 mt-1"
  184. >{{ prod.payPrice }} × {{ prod.num }}</div
  185. >
  186. </div>
  187. </div>
  188. </div>
  189. <div class="info-item">
  190. <span class="label">买家:</span>
  191. <div class="flex items-center">
  192. <el-avatar
  193. :size="20"
  194. :src="form.avatar"
  195. class="mr-2"
  196. />
  197. <span class="value text-blue-500"
  198. >{{ form.userNick }}/{{ form.userId }}</span
  199. >
  200. </div>
  201. </div>
  202. <div class="info-item">
  203. <span class="label">申请退款:</span>
  204. <span class="value"
  205. >¥{{ form.refundMoney || '0.00' }}</span
  206. >
  207. </div>
  208. <div class="info-item">
  209. <span class="label">邮费:</span>
  210. <span class="value"
  211. >¥{{ form.expressMoney || '0.00' }}</span
  212. >
  213. </div>
  214. <div class="info-item">
  215. <span class="label">订单标签:</span>
  216. <dict-data
  217. code="shop_refund_status"
  218. v-model="form.status"
  219. type="tag"
  220. />
  221. </div>
  222. <div class="info-item">
  223. <span class="label">成交时间:</span>
  224. <span class="value">{{ form.orderTime }}</span>
  225. </div>
  226. <div class="info-item">
  227. <span class="label">订单编号:</span>
  228. <span class="value text-blue-500">{{
  229. form.originOrderId
  230. }}</span>
  231. <el-icon
  232. class="copy-icon"
  233. @click="handleCopy(form.originOrderId)"
  234. >
  235. <CopyDocument />
  236. </el-icon>
  237. </div>
  238. <div class="logistics-info mt-4 p-3 bg-gray-50 rounded">
  239. <div class="font-bold mb-2">发货物流信息:</div>
  240. <div v-if="form.waybillCode">
  241. <div class="text-sm mb-1">
  242. {{ form.expressName }} {{ form.waybillCode }}
  243. <el-icon
  244. class="copy-icon"
  245. @click="handleCopy(form.waybillCode)"
  246. >
  247. <CopyDocument />
  248. </el-icon>
  249. </div>
  250. <div class="text-xs text-gray-500">
  251. 您已在云南财大南苑荟华1栋菜鸟驿站完成取件,感谢使用菜鸟驿站,期待再次为您服务。
  252. </div>
  253. </div>
  254. <div v-else class="text-sm text-gray-400"
  255. >暂无物流信息</div
  256. >
  257. </div>
  258. </div>
  259. <!-- 右侧:协商历史 -->
  260. <div
  261. class="section history-info flex-[1.5] flex flex-col overflow-hidden"
  262. >
  263. <div
  264. class="flex justify-between items-center mb-4 flex-shrink-0"
  265. >
  266. <div class="section-title mb-0">协商历史</div>
  267. <el-button
  268. link
  269. type="primary"
  270. @click="handleLeaveMessage"
  271. >我要留言</el-button
  272. >
  273. </div>
  274. <div class="history-list flex-1 overflow-y-auto pr-2">
  275. <div
  276. v-for="(activity, index) in historyList"
  277. :key="index"
  278. class="history-item flex mb-4 pb-4 border-b border-gray-100 last:border-0"
  279. >
  280. <div class="avatar-wrapper mr-3">
  281. <el-avatar
  282. :size="40"
  283. :src="activity.imgPath"
  284. shape="square"
  285. class="rounded-lg"
  286. />
  287. </div>
  288. <div class="content-wrapper flex-1">
  289. <div
  290. class="header flex justify-between items-start mb-1"
  291. >
  292. <span
  293. class="name font-bold text-gray-800"
  294. >{{
  295. activity.userName ||
  296. (activity.userType === '2'
  297. ? '客服'
  298. : '用户')
  299. }}</span
  300. >
  301. <span class="time text-xs text-gray-400">{{
  302. activity.createTime
  303. }}</span>
  304. </div>
  305. <div class="main-content text-sm text-gray-600">
  306. <div
  307. v-if="activity.title"
  308. class="font-bold mb-1 text-gray-900"
  309. >{{ activity.title }}
  310. </div>
  311. <div class="whitespace-pre-wrap mb-2">{{
  312. activity.content
  313. }}</div>
  314. <div
  315. v-if="
  316. activity.imgList &&
  317. activity.imgList.length
  318. "
  319. class="mt-2 flex flex-wrap gap-2"
  320. >
  321. <el-image
  322. v-for="(img, i) in activity.imgList"
  323. :key="i"
  324. :src="img"
  325. class="w-20 h-20 rounded border border-gray-200"
  326. :preview-src-list="activity.imgList"
  327. fit="cover"
  328. />
  329. </div>
  330. </div>
  331. </div>
  332. </div>
  333. </div>
  334. </div>
  335. </div>
  336. <div class="policy-tip mt-4 text-xs text-gray-400">
  337. <template v-if="isRefundOnly">
  338. · 如果您同意,请点击“同意退款”,退款将立即退到买家账户。<br />
  339. · 如果您拒绝,买家可以申请客服介入。<br />
  340. · 如果您逾期未处理申请,视作同意买家申请。<br />
  341. </template>
  342. <template v-else>
  343. · 如果您同意,请点击“同意退货”,将正确退货地址发给买家。<br />
  344. · 如果您拒绝,买家可以申请客服介入。<br />
  345. ·
  346. 如果您逾期未处理申请,视作同意买家申请。系统会自动将当前交易的退货地址发给买家<br />
  347. </template>
  348. </div>
  349. </div>
  350. <template #footer>
  351. <div class="flex justify-center gap-4">
  352. <el-button
  353. :disabled="
  354. !canOperate && !['4', '5'].includes(String(form.status))
  355. "
  356. >备注</el-button
  357. >
  358. <el-button @click="handleRefuse" :disabled="!canOperate">{{
  359. isRefundOnly ? '拒绝退款申请' : '拒绝退货申请'
  360. }}</el-button>
  361. <el-button @click="handleAgree" :disabled="!canOperate">{{
  362. isRefundOnly ? '同意退款' : '同意退货'
  363. }}</el-button>
  364. <el-button
  365. v-if="isRefundOnly"
  366. type="primary"
  367. @click="handleExpressIntercept"
  368. :disabled="form.interceptStatus === '1'"
  369. >快递拦截</el-button
  370. >
  371. <el-button
  372. v-if="isRefundOnly"
  373. type="primary"
  374. @click="handleNegotiateReturn"
  375. :disabled="
  376. !canOperate && !['4', '5'].includes(String(form.status))
  377. "
  378. >协商退货退款</el-button
  379. >
  380. <el-button
  381. type="primary"
  382. @click="handleNegotiate"
  383. :disabled="
  384. !canOperate && !['4', '5'].includes(String(form.status))
  385. "
  386. >与买家协商</el-button
  387. >
  388. </div>
  389. </template>
  390. <negotiation-apply-dialog
  391. v-model="negotiationDialogVisible"
  392. :refund-order-id="form.refundOrderId"
  393. :refund-type="Number(form.refundType)"
  394. :max-refund-amount="Number(form.refundMoney) || 0"
  395. @success="handleNegotiationSuccess"
  396. />
  397. <refuse-dialog
  398. ref="refuseDialogRef"
  399. @success="handleNegotiationSuccess"
  400. />
  401. <agree-dialog
  402. ref="agreeDialogRef"
  403. @success="handleNegotiationSuccess"
  404. />
  405. <confirm-refund-dialog
  406. ref="confirmRefundDialogRef"
  407. @success="handleNegotiationSuccess"
  408. />
  409. <negotiate-return-dialog
  410. ref="negotiateReturnDialogRef"
  411. @success="handleNegotiationSuccess"
  412. />
  413. <leave-message-dialog
  414. ref="leaveMessageDialogRef"
  415. @success="handleNegotiationSuccess"
  416. />
  417. </ele-modal>
  418. </template>
  419. <script setup>
  420. import { ref, computed } from 'vue';
  421. import { EleMessage } from 'ele-admin-plus/es';
  422. import { ElMessageBox } from 'element-plus';
  423. import { CopyDocument } from '@element-plus/icons-vue';
  424. import { useClipboard } from '@vueuse/core';
  425. import request from '@/utils/request';
  426. import NegotiationApplyDialog from './negotiation-apply-dialog.vue';
  427. import RefuseDialog from './refuse-dialog.vue';
  428. import AgreeDialog from './agree-dialog.vue';
  429. import ConfirmRefundDialog from './confirm-refund-dialog.vue';
  430. import NegotiateReturnDialog from './negotiate-return-dialog.vue';
  431. import LeaveMessageDialog from './leave-message-dialog.vue';
  432. const visible = defineModel({ type: Boolean });
  433. const loading = ref(false);
  434. const form = ref({});
  435. const historyList = ref([]);
  436. const { copy } = useClipboard();
  437. const negotiationDialogVisible = ref(false);
  438. const refuseDialogRef = ref(null);
  439. const agreeDialogRef = ref(null);
  440. const confirmRefundDialogRef = ref(null);
  441. const negotiateReturnDialogRef = ref(null);
  442. const leaveMessageDialogRef = ref(null);
  443. const deadline = ref(0);
  444. const isRefundOnly = computed(() =>
  445. ['0', '2', '3'].includes(String(form.value.refundType))
  446. );
  447. // 进度条状态
  448. const processStatus = computed(() => {
  449. const status = Number(form.value.status);
  450. if ([5, 6, 10].includes(status)) {
  451. return 'error';
  452. }
  453. return 'process';
  454. });
  455. // 当前步骤
  456. const currentStep = computed(() => {
  457. const status = Number(form.value.status);
  458. if (isRefundOnly.value) {
  459. // 1:申请, 2,3:协商中, 5:驳回, 6:超时关闭 -> 停在第2步(卖家处理)
  460. if ([1, 2, 3, 5, 6].includes(status)) return 1;
  461. // 4:审核通过, 7:买家已发货, 8:确认收货 -> 停在第3步(退款完毕前)
  462. if ([4, 7, 8].includes(status)) return 2;
  463. // 9:退款成功 -> 全绿(第3步完成)
  464. if (status === 9) return 3;
  465. // 10:退款已撤销 -> 停在第2步
  466. if (status === 10) return 1;
  467. return 1;
  468. }
  469. // 退货退款
  470. // 1:申请, 2,3:协商中, 5:驳回, 6:超时关闭 -> 停在第2步(卖家处理)
  471. if ([1, 2, 3, 5, 6].includes(status)) return 1;
  472. // 4:审核通过(商家同意退货,等待买家退货) -> 停在第3步(买家退货)
  473. if (status === 4) return 2;
  474. // 7:买家已发货, 8:确认收货 -> 停在第4步(退款完毕前)
  475. if ([7, 8].includes(status)) return 3;
  476. // 9:退款成功 -> 全绿(第4步完成)
  477. if (status === 9) return 4;
  478. // 10:退款已撤销 -> 停在第2步
  479. if (status === 10) return 1;
  480. return 1;
  481. });
  482. // 是否可操作主流程(拒绝、同意)
  483. const canOperate = computed(() => {
  484. return ['1', '3'].includes(String(form.value.status));
  485. });
  486. const handleOpen = (row) => {
  487. if (row && row.refundOrderId) {
  488. visible.value = true;
  489. loading.value = true;
  490. request
  491. .get(`/shop/shopOrder/getRefundInfo/${row.refundOrderId}`)
  492. .then((res) => {
  493. if (res.data.code === 200) {
  494. const data = res.data.data || {};
  495. form.value = data;
  496. if (!form.value.orderTime) {
  497. form.value.orderTime = form.value.createTime;
  498. }
  499. if (data.restSecond) {
  500. deadline.value =
  501. Date.now() + data.restSecond * 1000;
  502. } else {
  503. deadline.value = 0;
  504. }
  505. // 处理协商历史数据
  506. if (
  507. data.complaintsLogList &&
  508. data.complaintsLogList.length
  509. ) {
  510. historyList.value = data.complaintsLogList;
  511. } else {
  512. historyList.value = [];
  513. }
  514. } else {
  515. EleMessage.error(res.data.msg || '获取详情失败');
  516. fallbackToRow(row);
  517. }
  518. })
  519. .catch((e) => {
  520. console.error(e);
  521. EleMessage.error('获取详情失败');
  522. fallbackToRow(row);
  523. })
  524. .finally(() => {
  525. loading.value = false;
  526. });
  527. } else if (row) {
  528. visible.value = true;
  529. fallbackToRow(row);
  530. }
  531. };
  532. const fallbackToRow = (row) => {
  533. form.value = JSON.parse(JSON.stringify(row));
  534. if (!form.value.orderTime) {
  535. form.value.orderTime = form.value.createTime;
  536. }
  537. if (form.value.restSecond) {
  538. deadline.value = Date.now() + form.value.restSecond * 1000;
  539. } else {
  540. deadline.value = 0;
  541. }
  542. historyList.value = [];
  543. // Fallback时也添加初始申请记录
  544. const startEvent = {
  545. createTime: form.value.createTime,
  546. userName: form.value.userNick,
  547. userType: '1',
  548. imgPath: form.value.avatar || '',
  549. title: '发起了退款申请',
  550. content: `货物状态:${getShopStatusText(form.value.shopStatus)}\n原因:${form.value.refundReason}\n金额:¥${form.value.refundMoney}\n说明:${form.value.description || '无'}`,
  551. imgList: []
  552. };
  553. historyList.value.push(startEvent);
  554. };
  555. const handleCopy = async (text) => {
  556. try {
  557. await copy(text);
  558. EleMessage.success('复制成功');
  559. } catch (e) {
  560. EleMessage.error('复制失败');
  561. }
  562. };
  563. const getRefundTypeText = (type) => {
  564. const map = {
  565. 0: '极速退款',
  566. 1: '退货退款',
  567. 2: '仅退款',
  568. 3: '缺货退款'
  569. };
  570. return map[type] || type;
  571. };
  572. const getStatusText = (status) => {
  573. const key = String(status);
  574. const map = {
  575. 1: '申请退款',
  576. 2: '协商中待用户确认',
  577. 3: '协商中待商家确认',
  578. 4: '审核通过',
  579. 5: '审核驳回',
  580. 6: '超时关闭',
  581. 7: '买家已发货',
  582. 8: '确认收货',
  583. 9: '退款成功',
  584. 10: '退款已撤销'
  585. };
  586. return map[key] || status;
  587. };
  588. const getShopStatusText = (status) => {
  589. const map = { 1: '未收到货', 2: '已收到货' };
  590. return map[status] || '-';
  591. };
  592. const handleNegotiate = () => {
  593. negotiationDialogVisible.value = true;
  594. };
  595. const handleNegotiateReturn = () => {
  596. if (negotiateReturnDialogRef.value) {
  597. negotiateReturnDialogRef.value.open(form.value);
  598. }
  599. };
  600. const handleNegotiationSuccess = () => {
  601. handleOpen(form.value);
  602. };
  603. const handleExpressIntercept = () => {
  604. ElMessageBox.confirm('确定要拦截该快递吗?', '提示', {
  605. type: 'warning',
  606. confirmButtonText: '确定',
  607. cancelButtonText: '取消'
  608. })
  609. .then(() => {
  610. loading.value = true;
  611. request
  612. .post(
  613. `/shop/shopOrder/refundExpressIntercept/${form.value.refundOrderId}`
  614. )
  615. .then((res) => {
  616. if (res.data.code === 200) {
  617. EleMessage.success('快递拦截成功');
  618. handleOpen(form.value);
  619. } else {
  620. EleMessage.error(res.data.msg || '快递拦截失败');
  621. }
  622. })
  623. .catch((e) => {
  624. console.error(e);
  625. EleMessage.error('网络错误');
  626. })
  627. .finally(() => {
  628. loading.value = false;
  629. });
  630. })
  631. .catch(() => {});
  632. };
  633. const handleRefuse = () => {
  634. if (refuseDialogRef.value) {
  635. refuseDialogRef.value.open(form.value.refundOrderId, Number(form.value.refundType));
  636. }
  637. };
  638. const handleAgree = () => {
  639. if (isRefundOnly.value) {
  640. confirmRefundDialogRef.value?.open(
  641. form.value.refundOrderId,
  642. form.value.refundMoneyFinal || form.value.refundMoney
  643. );
  644. return;
  645. }
  646. agreeDialogRef.value?.open(form.value.refundOrderId);
  647. };
  648. const handleLeaveMessage = () => {
  649. if (leaveMessageDialogRef.value) {
  650. leaveMessageDialogRef.value.open(form.value.refundOrderId);
  651. }
  652. };
  653. defineExpose({
  654. handleOpen
  655. });
  656. </script>
  657. <style scoped lang="scss">
  658. .status-bar {
  659. background: #fff;
  660. padding: 15px;
  661. border-bottom: 1px solid #eee;
  662. }
  663. .detail-container {
  664. gap: 20px;
  665. .section {
  666. background: #fff;
  667. .section-title {
  668. font-size: 16px;
  669. font-weight: bold;
  670. margin-bottom: 15px;
  671. color: #333;
  672. padding-left: 10px;
  673. border-left: 3px solid #409eff;
  674. line-height: 1;
  675. }
  676. .info-item {
  677. margin-bottom: 10px;
  678. font-size: 13px;
  679. display: flex;
  680. .label {
  681. color: #999;
  682. width: 70px;
  683. flex-shrink: 0;
  684. }
  685. .value {
  686. color: #333;
  687. flex: 1;
  688. word-break: break-all;
  689. }
  690. .copy-icon {
  691. cursor: pointer;
  692. color: #409eff;
  693. margin-left: 5px;
  694. font-size: 14px;
  695. }
  696. }
  697. }
  698. .transaction-info {
  699. padding: 0 10px;
  700. border-left: 1px solid #eee;
  701. border-right: 1px solid #eee;
  702. }
  703. .history-info {
  704. padding-left: 10px;
  705. }
  706. }
  707. .history-card {
  708. background: #f9f9f9;
  709. padding: 10px;
  710. border-radius: 4px;
  711. .detail-rows {
  712. line-height: 1.6;
  713. }
  714. }
  715. .truncate-2-lines {
  716. display: -webkit-box;
  717. -webkit-line-clamp: 2;
  718. -webkit-box-orient: vertical;
  719. overflow: hidden;
  720. }
  721. </style>