index.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <template>
  2. <div class="sponsors-wrap">
  3. <h1 class="txt">
  4. 截止至{{ onMountedTime }},已收到:{{ receiveMoney / 100 }}元赞助~
  5. </h1>
  6. <div class="pay-list">
  7. <div
  8. v-for="(item, index) in payList"
  9. :key="index"
  10. class="item"
  11. >
  12. <div class="time">发起时间:{{ item.created_at }},</div>
  13. <div class="user">
  14. <template v-if="item.user">
  15. <img
  16. :src="item.user.avatar"
  17. class="avatar"
  18. alt=""
  19. />
  20. <span class="username">{{ item.user.username }}</span>
  21. </template>
  22. <span v-else>游客</span>,
  23. </div>
  24. <div class="account">支付宝账号:{{ item.buyer_logon_id }},</div>
  25. <div class="gift">
  26. 赞助了:{{ item.subject }}({{ item.total_amount }}元),
  27. </div>
  28. <div class="status">
  29. 状态:{{
  30. item.trade_status === PayStatusEnum.WAIT_BUYER_PAY
  31. ? '支付中'
  32. : '已支付'
  33. }},
  34. </div>
  35. <div class="time">支付时间:{{ item.send_pay_date || '-' }}</div>
  36. </div>
  37. </div>
  38. <h2>开始赞助(支付宝)</h2>
  39. <div class="gift-list">
  40. <div
  41. v-for="(item, index) in giftList"
  42. :key="index"
  43. class="item"
  44. @click="startPay(item)"
  45. >
  46. {{ item.subject }}({{ item.total_amount }}元)
  47. </div>
  48. </div>
  49. <div class="qrcode-wrap">
  50. <img
  51. v-if="aliPayBase64 !== ''"
  52. class="qrcode"
  53. :src="aliPayBase64"
  54. alt=""
  55. />
  56. <template v-if="currentPayStatus !== PayStatusEnum.error">
  57. <div class="mask">
  58. <div class="txt">
  59. {{
  60. currentPayStatus === PayStatusEnum.TRADE_SUCCESS
  61. ? '支付成功'
  62. : '等待支付'
  63. }}
  64. </div>
  65. </div>
  66. </template>
  67. </div>
  68. <div v-if="aliPayBase64 !== ''">
  69. <div class="bottom">
  70. <div class="sao">打开支付宝扫一扫</div>
  71. <div class="expr">有效期5分钟({{ formatDownTime(downTime) }})</div>
  72. </div>
  73. </div>
  74. <h3 v-if="currentPayStatus === PayStatusEnum.WAIT_BUYER_PAY">
  75. ps:支付宝标题显示:东圃牛杂档,是正常的~
  76. </h3>
  77. <div
  78. v-if="payOk"
  79. class="bottom"
  80. >
  81. <h2>感谢您的赞助~</h2>
  82. </div>
  83. </div>
  84. </template>
  85. <script lang="ts" setup>
  86. import { hrefToTarget, isMobile } from 'billd-utils';
  87. import QRCode from 'qrcode';
  88. import { onMounted, onUnmounted, ref } from 'vue';
  89. import { fetchAliPay, fetchAliPayStatus, fetchOrderList } from '@/api/order';
  90. import { IOrder, PayStatusEnum } from '@/interface';
  91. const payOk = ref(false);
  92. const onMountedTime = ref('');
  93. const aliPayBase64 = ref('');
  94. const payStatusTimer = ref();
  95. const downTimer = ref();
  96. const receiveMoney = ref(0);
  97. const downTime = ref();
  98. const downTimeEnd = ref();
  99. const payList = ref<IOrder[]>([]);
  100. const currentPayStatus = ref(PayStatusEnum.error);
  101. const giftList = ref([
  102. {
  103. total_amount: '0.10',
  104. subject: '一根辣条',
  105. body: 'aaa',
  106. },
  107. {
  108. total_amount: '1.00',
  109. subject: '一根烤肠',
  110. body: 'bbb',
  111. },
  112. {
  113. total_amount: '5.00',
  114. subject: '一瓶奶茶',
  115. body: 'ccc',
  116. },
  117. {
  118. total_amount: '10.00',
  119. subject: '一杯咖啡',
  120. body: 'ddd',
  121. },
  122. {
  123. total_amount: '50.00',
  124. subject: '一顿麦当劳',
  125. body: 'eee',
  126. },
  127. {
  128. total_amount: '100.00',
  129. subject: '一顿海底捞',
  130. body: 'fff',
  131. },
  132. ]);
  133. onUnmounted(() => {
  134. clearInterval(payStatusTimer.value);
  135. clearInterval(downTimer.value);
  136. });
  137. onMounted(() => {
  138. onMountedTime.value = new Date().toLocaleString();
  139. getPayList();
  140. });
  141. function formatDownTime(startTime: number) {
  142. const time2 = downTimeEnd.value - startTime;
  143. const ms = 1;
  144. const second = ms * 1000;
  145. const minute = second * 60;
  146. const hour = minute * 60;
  147. const day = hour * 24;
  148. if (time2 > day) {
  149. const res = (time2 / day).toFixed(4).split('.');
  150. return `${res[0]}天${Math.ceil(Number(`0.${res[1]}`) * 24)}时`;
  151. } else if (time2 > hour) {
  152. const res = (time2 / hour).toFixed(4).split('.');
  153. return `${res[0]}时${Math.ceil(Number(`0.${res[1]}`) * 60)}分`;
  154. } else if (time2 > minute) {
  155. const res = (time2 / minute).toFixed(4).split('.');
  156. return `${res[0]}分${Math.ceil(Number(`0.${res[1]}`) * 60)}秒`;
  157. } else {
  158. const res = (time2 / second).toFixed(4).split('.');
  159. return `${res[0]}秒`;
  160. }
  161. }
  162. async function generateQR(text) {
  163. let base64 = '';
  164. try {
  165. base64 = await QRCode.toDataURL(text, {
  166. margin: 1,
  167. });
  168. } catch (err) {
  169. console.error('生成二维码失败!', err);
  170. }
  171. return base64;
  172. }
  173. function handleDownTime() {
  174. clearInterval(downTimer.value);
  175. downTimeEnd.value = +new Date() + 1000 * 60 * 5;
  176. downTime.value = +new Date();
  177. downTimer.value = setInterval(() => {
  178. downTime.value = +new Date();
  179. }, 1000);
  180. }
  181. async function getPayList() {
  182. try {
  183. const res = await fetchOrderList({
  184. trade_status: PayStatusEnum.TRADE_SUCCESS,
  185. });
  186. if (res.code === 200) {
  187. payList.value = res.data.rows;
  188. receiveMoney.value = payList.value.reduce(
  189. (pre, item) => pre + Number(item.total_amount) * 100,
  190. 0
  191. );
  192. }
  193. } catch (error) {
  194. console.log(error);
  195. }
  196. }
  197. async function startPay(item) {
  198. currentPayStatus.value = PayStatusEnum.error;
  199. payOk.value = false;
  200. clearInterval(payStatusTimer.value);
  201. clearInterval(downTimer.value);
  202. try {
  203. const res = await fetchAliPay({
  204. total_amount: item.total_amount,
  205. subject: item.subject,
  206. body: item.body,
  207. });
  208. if (res.code === 200) {
  209. if (isMobile()) {
  210. hrefToTarget(res.data.qr_code);
  211. return;
  212. }
  213. const base64 = await generateQR(res.data.qr_code);
  214. aliPayBase64.value = base64;
  215. getPayStatus(res.data.out_trade_no);
  216. handleDownTime();
  217. }
  218. } catch (error) {
  219. console.log(error);
  220. }
  221. }
  222. function getPayStatus(outTradeNo: string) {
  223. clearInterval(payStatusTimer.value);
  224. payStatusTimer.value = setInterval(async () => {
  225. try {
  226. const res = await fetchAliPayStatus({
  227. out_trade_no: outTradeNo,
  228. });
  229. if (res.data.tradeStatus === PayStatusEnum.WAIT_BUYER_PAY) {
  230. currentPayStatus.value = PayStatusEnum.WAIT_BUYER_PAY;
  231. console.log('等待支付');
  232. }
  233. if (res.data.tradeStatus === PayStatusEnum.TRADE_SUCCESS) {
  234. currentPayStatus.value = PayStatusEnum.TRADE_SUCCESS;
  235. clearInterval(downTimer.value);
  236. clearInterval(payStatusTimer.value);
  237. console.log('支付成功!');
  238. payOk.value = true;
  239. getPayList();
  240. }
  241. } catch (error) {
  242. console.log(error);
  243. }
  244. }, 1000);
  245. }
  246. </script>
  247. <style lang="scss" scoped>
  248. .sponsors-wrap {
  249. text-align: center;
  250. .pay-list {
  251. display: flex;
  252. overflow: scroll;
  253. align-items: center;
  254. flex-direction: column;
  255. box-sizing: border-box;
  256. margin-bottom: 20px;
  257. padding: 10px;
  258. width: 100%;
  259. height: 200px;
  260. background-color: papayawhip;
  261. .item {
  262. display: inline-flex;
  263. flex-wrap: wrap;
  264. justify-content: center;
  265. margin-bottom: 4px;
  266. width: 100%;
  267. text-align: left;
  268. .user {
  269. width: 120px;
  270. padding: 0 10px;
  271. display: flex;
  272. align-items: center;
  273. justify-content: center;
  274. .avatar {
  275. width: 30px;
  276. height: 30px;
  277. border-radius: 50%;
  278. }
  279. .username {
  280. @extend %singleEllipsis;
  281. }
  282. }
  283. .account {
  284. width: 250px;
  285. }
  286. .gift {
  287. width: 260px;
  288. }
  289. .status {
  290. width: 120px;
  291. text-align: left;
  292. }
  293. .time {
  294. width: 280px;
  295. }
  296. }
  297. }
  298. .gift-list {
  299. display: flex;
  300. align-items: center;
  301. flex-wrap: wrap;
  302. justify-content: center;
  303. .item {
  304. margin: 5px;
  305. padding: 5px 10px;
  306. border-radius: 4px;
  307. background-color: skyblue;
  308. cursor: pointer;
  309. }
  310. }
  311. .qrcode-wrap {
  312. position: relative;
  313. display: flex;
  314. align-items: center;
  315. justify-content: center;
  316. box-sizing: border-box;
  317. margin: 20px auto 0;
  318. width: 140px;
  319. height: 140px;
  320. .mask {
  321. position: absolute !important;
  322. display: flex;
  323. align-items: center;
  324. justify-content: center;
  325. @extend %maskBg;
  326. .txt {
  327. color: white;
  328. font-weight: bold;
  329. }
  330. }
  331. }
  332. .bottom {
  333. margin-top: 2px;
  334. width: 100%;
  335. text-align: center;
  336. font-size: 14px;
  337. }
  338. }
  339. </style>