gathering.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <template>
  2. <view class="gathering-container">
  3. <!-- 自定义导航栏 -->
  4. <view class="nav-bar">
  5. <view class="nav-back" @click="goBack">
  6. <text class="back-arrow">&lt;</text>
  7. </view>
  8. <view class="nav-title">待结账单</view>
  9. <view class="nav-placeholder"></view>
  10. </view>
  11. <!-- 头部占位,防止内容被导航栏遮挡 -->
  12. <view class="nav-bar-placeholder"></view>
  13. <!-- 供货商信息 Banner -->
  14. <view class="supplier-banner" v-if="ghsInfo.name">
  15. <image class="supplier-avatar" :src="ghsInfo.avatar || '/static/default-avatar.png'" mode="aspectFill" />
  16. <view class="supplier-details">
  17. <text class="supplier-name">{{ ghsInfo.name }}</text>
  18. <text class="supplier-desc">请选择需要结清的采购订单</text>
  19. </view>
  20. </view>
  21. <!-- 订单列表区域 -->
  22. <scroll-view scroll-y class="order-list-scroll">
  23. <view class="list-wrapper">
  24. <block v-if="detailsList && detailsList.length > 0">
  25. <view
  26. v-for="(item, index) in detailsList"
  27. :key="item.id"
  28. class="order-item-card"
  29. :class="{ 'item-checked': item.checked }"
  30. @click="toggleCheckItem(index)">
  31. <!-- 选择框 -->
  32. <view class="checkbox-wrapper">
  33. <view class="custom-checkbox" :class="{ 'checked': item.checked }">
  34. <view class="checkbox-inner" v-if="item.checked"></view>
  35. </view>
  36. </view>
  37. <!-- 订单内容 -->
  38. <view class="order-content">
  39. <view class="order-header">
  40. <text class="order-sn">采购单 {{ item.orderSn }}</text>
  41. <text class="order-time">{{ formatTime(item.addTime) }}</text>
  42. </view>
  43. <view class="order-price-info">
  44. <view class="price-row">
  45. <text class="price-label">待结金额:</text>
  46. <text class="price-value">¥{{ parseFloat(item.remainDebtPrice || item.debtPrice || 0).toFixed(2) }}</text>
  47. </view>
  48. <!-- 部分结清展示 -->
  49. <view class="partial-clear-info" v-if="item.showPartialClear">
  50. <text class="partial-text">已清 ¥{{ item.hasPayDebt }},原始欠款 ¥{{ parseFloat(item.debtPrice).toFixed(2) }}</text>
  51. </view>
  52. </view>
  53. </view>
  54. </view>
  55. </block>
  56. <!-- 空状态 -->
  57. <view class="empty-state" v-else-if="!loading">
  58. <image class="empty-img" src="/static/empty.png" mode="aspectFit" v-if="hasEmptyImg" />
  59. <view class="empty-icon" v-else>📝</view>
  60. <text class="empty-text">暂无待结订单</text>
  61. </view>
  62. <!-- 加载中 -->
  63. <view class="loading-state" v-if="loading">
  64. <text class="loading-text">加载中...</text>
  65. </view>
  66. </view>
  67. </scroll-view>
  68. <!-- 底部结算栏 -->
  69. <view class="bottom-action-bar">
  70. <view class="select-all-section" @click="toggleSelectAll">
  71. <view class="custom-checkbox" :class="{ 'checked': isAllChecked }">
  72. <view class="checkbox-inner" v-if="isAllChecked"></view>
  73. </view>
  74. <text class="select-all-text">{{ isAllChecked ? '全不选' : '全选' }}</text>
  75. </view>
  76. <view class="summary-section">
  77. <view class="summary-text">
  78. 已选 <text class="highlight-count">{{ selectList.length }}</text> 笔
  79. </view>
  80. <view class="total-amount">
  81. 合计: <text class="currency-symbol">¥</text><text class="amount-value">{{ selectTotalAmount }}</text>
  82. </view>
  83. </view>
  84. <button
  85. class="pay-submit-btn"
  86. :disabled="selectList.length === 0"
  87. :class="{ 'btn-disabled': selectList.length === 0 }"
  88. @click="batchConfirm">
  89. 付款结清
  90. </button>
  91. </view>
  92. </view>
  93. </template>
  94. <script>
  95. /**
  96. * 待结账单页面
  97. * 用途:展示花店向供货商采购的待结款订单,支持多选并一键跳转支付宝结账
  98. * 谁用:花店端用户
  99. */
  100. import { purchaseDebtList, ghsDetail, purchaseClearCreateOrder, purchaseClearAliPay } from '@/api/purchase/index';
  101. export default {
  102. name: 'gathering',
  103. data() {
  104. return {
  105. ghsId: 0,
  106. salt: '',
  107. ghsInfo: {},
  108. detailsList: [],
  109. loading: false,
  110. hasEmptyImg: false,
  111. page: 1,
  112. pageSize: 100
  113. };
  114. },
  115. computed: {
  116. // 已选订单列表
  117. selectList() {
  118. return this.detailsList.filter(item => item.checked);
  119. },
  120. // 已选订单总金额
  121. selectTotalAmount() {
  122. let total = 0;
  123. this.selectList.forEach(item => {
  124. total += Number(item.remainDebtPrice || 0);
  125. });
  126. return total.toFixed(2);
  127. },
  128. // 是否全选
  129. isAllChecked() {
  130. if (!this.detailsList || this.detailsList.length === 0) {
  131. return false;
  132. }
  133. return this.selectList.length === this.detailsList.length;
  134. }
  135. },
  136. onLoad(options) {
  137. // 解析路由参数 id/salt
  138. if (options.id) {
  139. this.ghsId = options.id;
  140. } else if (options.q) {
  141. // 支持小票二维码扫描打开
  142. const currentUrl = decodeURIComponent(options.q);
  143. const match = currentUrl.match(/[?&]id=(\d+)/);
  144. if (match && match[1]) {
  145. this.ghsId = match[1];
  146. }
  147. }
  148. if (options.salt) {
  149. this.salt = options.salt;
  150. }
  151. if (this.ghsId) {
  152. this.loadData();
  153. } else {
  154. uni.showToast({
  155. title: '参数错误,缺少供货商ID',
  156. icon: 'none'
  157. });
  158. }
  159. },
  160. methods: {
  161. // 返回上一页
  162. goBack() {
  163. uni.navigateBack({
  164. delta: 1
  165. });
  166. },
  167. // 格式化时间,截取月-日 时:分
  168. formatTime(timeStr) {
  169. if (!timeStr) return '';
  170. return timeStr.substring(5, 16);
  171. },
  172. // 加载供货商信息和待结订单列表
  173. async loadData() {
  174. this.loading = true;
  175. uni.showLoading({ title: '加载中...', mask: true });
  176. try {
  177. // 1. 获取供货商详情
  178. const ghsRes = await ghsDetail({ id: this.ghsId, salt: this.salt });
  179. if (ghsRes && ghsRes.code === 1) {
  180. this.ghsInfo = ghsRes.data;
  181. }
  182. // 2. 获取待结订单列表
  183. const listRes = await purchaseDebtList({
  184. id: this.ghsId,
  185. salt: this.salt,
  186. page: this.page,
  187. pageSize: this.pageSize
  188. });
  189. uni.hideLoading();
  190. this.loading = false;
  191. if (listRes && listRes.code === 1) {
  192. const rawList = listRes.data.list || [];
  193. // 映射数据,添加 checked 属性和部分结清判断
  194. this.detailsList = rawList.map(item => {
  195. const debtPrice = Number(item.debtPrice) || 0;
  196. const remainDebtPrice = Number(item.remainDebtPrice) || 0;
  197. let hasPayDebt = Number(item.hasPayDebt);
  198. if (isNaN(hasPayDebt)) {
  199. hasPayDebt = debtPrice > remainDebtPrice ? debtPrice - remainDebtPrice : 0;
  200. }
  201. return {
  202. ...item,
  203. checked: false,
  204. hasPayDebt: hasPayDebt.toFixed(2),
  205. showPartialClear: hasPayDebt > 0
  206. };
  207. });
  208. } else {
  209. uni.showToast({
  210. title: listRes.msg || '获取账单列表失败',
  211. icon: 'none'
  212. });
  213. }
  214. } catch (err) {
  215. uni.hideLoading();
  216. this.loading = false;
  217. uni.showToast({
  218. title: '请求失败,请检查网络',
  219. icon: 'none'
  220. });
  221. }
  222. },
  223. // 切换单项选择状态
  224. toggleCheckItem(index) {
  225. if (this.detailsList[index]) {
  226. this.$set(this.detailsList[index], 'checked', !this.detailsList[index].checked);
  227. }
  228. },
  229. // 切换全选/全不选
  230. toggleSelectAll() {
  231. const targetState = !this.isAllChecked;
  232. this.detailsList.forEach((item, index) => {
  233. this.$set(this.detailsList[index], 'checked', targetState);
  234. });
  235. },
  236. // 批量付款结清
  237. batchConfirm() {
  238. if (this.selectList.length === 0) {
  239. uni.showToast({
  240. title: '请选择订单',
  241. icon: 'none'
  242. });
  243. return;
  244. }
  245. const that = this;
  246. uni.showModal({
  247. title: '确认提交',
  248. content: `确认结清选中的 ${this.selectList.length} 笔订单,合计 ¥${this.selectTotalAmount} 吗?`,
  249. success: (res) => {
  250. if (res.confirm) {
  251. that.submitPayment();
  252. }
  253. }
  254. });
  255. },
  256. // 提交结账并跳转支付
  257. async submitPayment() {
  258. uni.showLoading({ title: '正在创建结账单...', mask: true });
  259. const purchaseIds = this.selectList.map(item => item.id).join(',');
  260. try {
  261. // 1. 创建结账订单
  262. const createRes = await purchaseClearCreateOrder({
  263. purchaseIds: purchaseIds,
  264. ghsId: this.ghsId
  265. });
  266. if (createRes && createRes.code === 1) {
  267. // 2. 获取支付宝支付链接
  268. const orderSn = createRes.data.orderSn;
  269. uni.showLoading({ title: '正在获取支付链接...', mask: true });
  270. const payRes = await purchaseClearAliPay({ orderSn: orderSn });
  271. uni.hideLoading();
  272. if (payRes && payRes.code === 1 && payRes.data.hasRenew === 1) {
  273. uni.showToast({
  274. title: '正在跳转支付...',
  275. icon: 'success',
  276. duration: 1500
  277. });
  278. // 延迟跳转,确保提示框可见
  279. setTimeout(() => {
  280. window.location.href = payRes.data.payUrl;
  281. }, 300);
  282. } else {
  283. uni.showToast({
  284. title: (payRes && payRes.msg) || '商家暂未开通此支付功能',
  285. icon: 'none',
  286. duration: 2000
  287. });
  288. }
  289. } else {
  290. uni.hideLoading();
  291. uni.showToast({
  292. title: (createRes && createRes.msg) || '创建结账单失败',
  293. icon: 'none',
  294. duration: 2000
  295. });
  296. }
  297. } catch (err) {
  298. uni.hideLoading();
  299. uni.showToast({
  300. title: '操作失败,请重试',
  301. icon: 'none'
  302. });
  303. }
  304. }
  305. }
  306. };
  307. </script>
  308. <style lang="scss" scoped>
  309. .gathering-container {
  310. min-height: 100vh;
  311. background-color: #f7f8fa;
  312. display: flex;
  313. flex-direction: column;
  314. position: relative;
  315. box-sizing: border-box;
  316. }
  317. /* 自定义导航栏样式 */
  318. .nav-bar {
  319. display: flex;
  320. align-items: center;
  321. justify-content: space-between;
  322. padding: 20upx 30upx;
  323. background: #fff;
  324. border-bottom: 1upx solid #eef0f2;
  325. position: fixed;
  326. top: 0;
  327. left: 0;
  328. width: 100%;
  329. height: 88upx;
  330. z-index: 100;
  331. box-sizing: border-box;
  332. .nav-back {
  333. width: 60upx;
  334. height: 60upx;
  335. display: flex;
  336. align-items: center;
  337. justify-content: center;
  338. border-radius: 50%;
  339. background: #f5f6f8;
  340. cursor: pointer;
  341. .back-arrow {
  342. font-size: 36upx;
  343. color: #333;
  344. font-weight: bold;
  345. }
  346. }
  347. .nav-title {
  348. color: #333;
  349. font-size: 34upx;
  350. font-weight: 600;
  351. }
  352. .nav-placeholder {
  353. width: 60upx;
  354. }
  355. }
  356. .nav-bar-placeholder {
  357. height: 88upx;
  358. width: 100%;
  359. }
  360. /* 供货商 Banner */
  361. .supplier-banner {
  362. display: flex;
  363. align-items: center;
  364. padding: 30upx;
  365. background: linear-gradient(135deg, #3385ff 0%, #5197ff 100%);
  366. color: #fff;
  367. margin-bottom: 20upx;
  368. box-shadow: 0 4upx 12upx rgba(51, 133, 255, 0.2);
  369. .supplier-avatar {
  370. width: 100upx;
  371. height: 100upx;
  372. border-radius: 50%;
  373. border: 4upx solid rgba(255, 255, 255, 0.6);
  374. margin-right: 24upx;
  375. background-color: rgba(255, 255, 255, 0.2);
  376. }
  377. .supplier-details {
  378. display: flex;
  379. flex-direction: column;
  380. .supplier-name {
  381. font-size: 34upx;
  382. font-weight: 600;
  383. margin-bottom: 8upx;
  384. }
  385. .supplier-desc {
  386. font-size: 24upx;
  387. color: rgba(255, 255, 255, 0.8);
  388. }
  389. }
  390. }
  391. /* 订单列表 */
  392. .order-list-scroll {
  393. flex: 1;
  394. height: 0; /* 必须设置,配合 flex: 1 才能在 scroll-view 中正常滚动 */
  395. }
  396. .list-wrapper {
  397. padding: 10upx 24upx 160upx; /* 底部预留空间给结算栏 */
  398. }
  399. .order-item-card {
  400. display: flex;
  401. align-items: center;
  402. padding: 24upx;
  403. background-color: #fff;
  404. border-radius: 16upx;
  405. margin-bottom: 20upx;
  406. box-shadow: 0 2upx 8upx rgba(0, 0, 0, 0.02);
  407. border: 2upx solid transparent;
  408. transition: all 0.2s ease;
  409. &.item-checked {
  410. border-color: rgba(51, 133, 255, 0.3);
  411. background-color: #fcfdff;
  412. }
  413. }
  414. /* 自定义复选框 */
  415. .checkbox-wrapper {
  416. margin-right: 24upx;
  417. }
  418. .custom-checkbox {
  419. width: 40upx;
  420. height: 40upx;
  421. border: 2upx solid #ccc;
  422. border-radius: 50%;
  423. display: flex;
  424. align-items: center;
  425. justify-content: center;
  426. transition: all 0.2s ease;
  427. box-sizing: border-box;
  428. &.checked {
  429. border-color: #3385ff;
  430. background-color: #3385ff;
  431. }
  432. .checkbox-inner {
  433. width: 16upx;
  434. height: 16upx;
  435. border-radius: 50%;
  436. background-color: #fff;
  437. }
  438. }
  439. /* 订单内容 */
  440. .order-content {
  441. flex: 1;
  442. display: flex;
  443. flex-direction: column;
  444. .order-header {
  445. display: flex;
  446. justify-content: space-between;
  447. align-items: center;
  448. margin-bottom: 16upx;
  449. .order-sn {
  450. font-size: 28upx;
  451. font-weight: 600;
  452. color: #333;
  453. }
  454. .order-time {
  455. font-size: 24upx;
  456. color: #999;
  457. }
  458. }
  459. .order-price-info {
  460. display: flex;
  461. flex-direction: column;
  462. .price-row {
  463. display: flex;
  464. align-items: baseline;
  465. .price-label {
  466. font-size: 26upx;
  467. color: #666;
  468. }
  469. .price-value {
  470. font-size: 32upx;
  471. font-weight: bold;
  472. color: #ff4d4f;
  473. }
  474. }
  475. .partial-clear-info {
  476. margin-top: 8upx;
  477. background-color: #fff1f0;
  478. border-radius: 8upx;
  479. padding: 8upx 16upx;
  480. display: inline-block;
  481. align-self: flex-start;
  482. .partial-text {
  483. font-size: 22upx;
  484. color: #ff4d4f;
  485. font-weight: 500;
  486. }
  487. }
  488. }
  489. }
  490. /* 状态样式 */
  491. .empty-state {
  492. display: flex;
  493. flex-direction: column;
  494. align-items: center;
  495. justify-content: center;
  496. padding: 120upx 0;
  497. .empty-icon {
  498. font-size: 80upx;
  499. margin-bottom: 20upx;
  500. }
  501. .empty-text {
  502. font-size: 28upx;
  503. color: #999;
  504. }
  505. }
  506. .loading-state {
  507. text-align: center;
  508. padding: 40upx 0;
  509. .loading-text {
  510. font-size: 26upx;
  511. color: #999;
  512. }
  513. }
  514. /* 底部结算栏 */
  515. .bottom-action-bar {
  516. position: fixed;
  517. bottom: 0;
  518. left: 0;
  519. width: 100%;
  520. height: 120upx;
  521. background-color: #fff;
  522. box-shadow: 0 -4upx 16upx rgba(0, 0, 0, 0.05);
  523. display: flex;
  524. align-items: center;
  525. padding: 0 30upx;
  526. box-sizing: border-box;
  527. z-index: 90;
  528. .select-all-section {
  529. display: flex;
  530. align-items: center;
  531. cursor: pointer;
  532. margin-right: 30upx;
  533. .select-all-text {
  534. font-size: 28upx;
  535. color: #333;
  536. margin-left: 12upx;
  537. font-weight: 500;
  538. }
  539. }
  540. .summary-section {
  541. flex: 1;
  542. display: flex;
  543. flex-direction: column;
  544. justify-content: center;
  545. .summary-text {
  546. font-size: 22upx;
  547. color: #666;
  548. margin-bottom: 4upx;
  549. .highlight-count {
  550. color: #3385ff;
  551. font-weight: bold;
  552. margin: 0 4upx;
  553. }
  554. }
  555. .total-amount {
  556. font-size: 26upx;
  557. color: #333;
  558. font-weight: 500;
  559. .currency-symbol {
  560. color: #ff4d4f;
  561. font-size: 24upx;
  562. margin-left: 6upx;
  563. }
  564. .amount-value {
  565. color: #ff4d4f;
  566. font-size: 36upx;
  567. font-weight: bold;
  568. }
  569. }
  570. }
  571. .pay-submit-btn {
  572. width: 220upx;
  573. height: 80upx;
  574. line-height: 80upx;
  575. background: linear-gradient(135deg, #3385ff 0%, #1e70eb 100%);
  576. color: #fff;
  577. font-size: 30upx;
  578. font-weight: bold;
  579. border-radius: 40upx;
  580. border: none;
  581. box-shadow: 0 4upx 12upx rgba(30, 112, 235, 0.3);
  582. transition: all 0.2s ease;
  583. display: flex;
  584. align-items: center;
  585. justify-content: center;
  586. &:active {
  587. transform: scale(0.97);
  588. }
  589. &.btn-disabled {
  590. background: #e1e4e8 !important;
  591. color: #969faf !important;
  592. box-shadow: none !important;
  593. }
  594. }
  595. }
  596. </style>