payment.vue 9.5 KB

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