payment.vue 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <template>
  2. <view class="container">
  3. <view v-if="loading" class="loading-wrap">
  4. <text class="loading-text">加载中...</text>
  5. </view>
  6. <scroll-view v-else scroll-y class="content">
  7. <!-- Header Section -->
  8. <view class="header-section">
  9. <text class="header-title">订单待支付</text>
  10. <text class="header-countdown" v-if="countdown > 0">
  11. 支付剩余时间: {{ formatCountdown }}
  12. </text>
  13. <text class="header-cancelled" v-else>
  14. 订单已取消
  15. </text>
  16. </view>
  17. <!-- Order Info Card -->
  18. <view class="section">
  19. <view class="info-row">
  20. <text class="info-label">订单编号</text>
  21. <text class="info-value">{{ orderNo }}</text>
  22. </view>
  23. <view class="info-row">
  24. <text class="info-label">订单金额</text>
  25. <text class="order-amount">{{ formatPriceWithSymbol(amount) }}</text>
  26. </view>
  27. </view>
  28. <!-- Payment Method -->
  29. <view class="section">
  30. <text class="section-title">支付方式</text>
  31. <view class="payment-method-row">
  32. <view class="payment-method-left">
  33. <text class="payment-icon">💳</text>
  34. <text class="payment-name">微信支付</text>
  35. </view>
  36. <text class="payment-checked">✓</text>
  37. </view>
  38. </view>
  39. <!-- Pay Button -->
  40. <view class="pay-section">
  41. <button
  42. :class="['pay-btn', (paying || countdown <= 0) ? 'disabled' : '']"
  43. :disabled="paying || countdown <= 0"
  44. @click="onPay"
  45. >
  46. {{ paying ? '支付中...' : '微信支付 ' + formatPriceWithSymbol(amount) }}
  47. </button>
  48. <text class="cancel-link" @click="onCancelOrder">取消订单</text>
  49. </view>
  50. </scroll-view>
  51. </view>
  52. </template>
  53. <script>
  54. import config from '@/config.js'
  55. export default {
  56. data() {
  57. return {
  58. orderNo: '',
  59. amount: 0,
  60. countdown: 900,
  61. paying: false,
  62. loading: false,
  63. timer: null
  64. }
  65. },
  66. computed: {
  67. formatCountdown() {
  68. var min = Math.floor(this.countdown / 60)
  69. var sec = this.countdown % 60
  70. return String(min).padStart(2, '0') + ':' + String(sec).padStart(2, '0')
  71. }
  72. },
  73. onLoad(options) {
  74. if (options.orderNo) {
  75. this.orderNo = options.orderNo
  76. }
  77. if (options.amount) {
  78. this.amount = parseFloat(options.amount)
  79. }
  80. this.startCountdown()
  81. },
  82. onUnload() {
  83. if (this.timer) {
  84. clearInterval(this.timer)
  85. this.timer = null
  86. }
  87. },
  88. methods: {
  89. startCountdown() {
  90. var that = this
  91. this.timer = setInterval(function() {
  92. if (that.countdown > 0) {
  93. that.countdown = that.countdown - 1
  94. } else {
  95. if (that.timer) {
  96. clearInterval(that.timer)
  97. that.timer = null
  98. }
  99. }
  100. }, 1000)
  101. },
  102. onPay() {
  103. if (this.countdown <= 0) {
  104. uni.showToast({ title: '订单已取消,无法支付', icon: 'none' })
  105. return
  106. }
  107. this.paying = true
  108. var that = this
  109. uni.request({
  110. url: config.api('/api/product/order/pay'),
  111. method: 'POST',
  112. data: { orderNo: this.orderNo },
  113. header: {
  114. 'Content-Type': 'application/json',
  115. 'Authorization': 'Bearer ' + uni.getStorageSync('token')
  116. },
  117. success: function(res) {
  118. that.paying = false
  119. if (res.data && res.data.code === 200) {
  120. var params = res.data.data && res.data.data.wechatPayParams
  121. if (params) {
  122. uni.requestPayment({
  123. timeStamp: params.timeStamp,
  124. nonceStr: params.nonceStr,
  125. package: params.package,
  126. signType: params.signType || 'RSA',
  127. paySign: params.paySign,
  128. success: function() {
  129. uni.showToast({ title: '支付成功', icon: 'success' })
  130. setTimeout(function() {
  131. uni.redirectTo({
  132. url: '/pages/shop/order-list/order-list?tab=paid'
  133. })
  134. }, 1500)
  135. },
  136. fail: function() {
  137. uni.showToast({ title: '支付失败', icon: 'none' })
  138. }
  139. })
  140. } else {
  141. uni.showToast({ title: '获取支付参数失败', icon: 'none' })
  142. }
  143. } else {
  144. uni.showToast({ title: res.data.message || '支付失败', icon: 'none' })
  145. }
  146. },
  147. fail: function() {
  148. that.paying = false
  149. uni.showToast({ title: '网络请求失败', icon: 'none' })
  150. }
  151. })
  152. },
  153. onCancelOrder() {
  154. var that = this
  155. uni.showModal({
  156. title: '确认取消',
  157. content: '确定取消该订单吗?',
  158. success: function(confirm) {
  159. if (confirm.confirm) {
  160. uni.request({
  161. url: config.api('/api/product/order/cancel'),
  162. method: 'POST',
  163. data: { orderNo: that.orderNo },
  164. header: {
  165. 'Content-Type': 'application/json',
  166. 'Authorization': 'Bearer ' + uni.getStorageSync('token')
  167. },
  168. success: function(res) {
  169. if (res.data && res.data.code === 200) {
  170. uni.showToast({ title: '订单已取消', icon: 'success' })
  171. setTimeout(function() {
  172. uni.navigateBack()
  173. }, 1500)
  174. } else {
  175. uni.showToast({ title: res.data.message || '取消失败', icon: 'none' })
  176. }
  177. }
  178. })
  179. }
  180. }
  181. })
  182. },
  183. formatPrice(price) {
  184. if (price === null || price === undefined) return '0.00'
  185. return (Number(price) / 100).toFixed(2)
  186. }
  187. }
  188. }
  189. </script>
  190. <style scoped>
  191. .container {
  192. min-height: 100vh;
  193. background: #f5f5f5;
  194. }
  195. .loading-wrap {
  196. display: flex;
  197. justify-content: center;
  198. padding-top: 200rpx;
  199. }
  200. .loading-text {
  201. font-size: 28rpx;
  202. color: #999;
  203. }
  204. .content {
  205. height: calc(100vh - 120rpx);
  206. }
  207. .header-section {
  208. background: linear-gradient(135deg, #F97316, #FB923C);
  209. padding: 50rpx 30rpx;
  210. display: flex;
  211. flex-direction: column;
  212. align-items: center;
  213. }
  214. .header-title {
  215. font-size: 36rpx;
  216. font-weight: bold;
  217. color: #fff;
  218. margin-bottom: 12rpx;
  219. }
  220. .header-countdown {
  221. font-size: 26rpx;
  222. color: rgba(255,255,255,0.9);
  223. }
  224. .header-cancelled {
  225. font-size: 26rpx;
  226. color: rgba(255,255,255,0.7);
  227. }
  228. .section {
  229. background: #fff;
  230. margin: 20rpx;
  231. border-radius: 16rpx;
  232. padding: 24rpx;
  233. }
  234. .section-title {
  235. font-size: 28rpx;
  236. font-weight: bold;
  237. color: #333;
  238. margin-bottom: 20rpx;
  239. display: block;
  240. }
  241. .info-row {
  242. display: flex;
  243. justify-content: space-between;
  244. align-items: center;
  245. padding: 16rpx 0;
  246. border-bottom: 1rpx solid #f5f5f5;
  247. }
  248. .info-row:last-child {
  249. border-bottom: none;
  250. }
  251. .info-label {
  252. font-size: 26rpx;
  253. color: #999;
  254. }
  255. .info-value {
  256. font-size: 26rpx;
  257. color: #333;
  258. }
  259. .order-amount {
  260. font-size: 40rpx;
  261. color: #F97316;
  262. font-weight: bold;
  263. }
  264. .payment-method-row {
  265. display: flex;
  266. align-items: center;
  267. justify-content: space-between;
  268. padding: 16rpx 0;
  269. }
  270. .payment-method-left {
  271. display: flex;
  272. align-items: center;
  273. }
  274. .payment-icon {
  275. font-size: 40rpx;
  276. margin-right: 16rpx;
  277. }
  278. .payment-name {
  279. font-size: 28rpx;
  280. color: #333;
  281. }
  282. .payment-checked {
  283. font-size: 32rpx;
  284. color: #52c41a;
  285. font-weight: bold;
  286. }
  287. .pay-section {
  288. display: flex;
  289. flex-direction: column;
  290. align-items: center;
  291. padding: 40rpx 30rpx;
  292. }
  293. .pay-btn {
  294. width: 100%;
  295. height: 88rpx;
  296. line-height: 88rpx;
  297. background: linear-gradient(135deg, #F97316, #FB923C);
  298. color: #fff;
  299. font-size: 32rpx;
  300. font-weight: bold;
  301. border-radius: 44rpx;
  302. border: none;
  303. text-align: center;
  304. }
  305. .pay-btn::after {
  306. border: none;
  307. }
  308. .pay-btn.disabled {
  309. background: #ddd;
  310. color: #999;
  311. }
  312. .cancel-link {
  313. margin-top: 24rpx;
  314. font-size: 26rpx;
  315. color: #999;
  316. text-decoration: underline;
  317. padding: 10rpx;
  318. }
  319. </style>