Bladeren bron

feat:提现与领红包流程接口对接

qianxinyu 4 maanden geleden
bovenliggende
commit
80a0a35d99

+ 1 - 1
components/SwipeAction/SwipeAction.vue

@@ -48,7 +48,7 @@ export default {
       default: false,
     },
     pIndex: {
-      type: Number,
+      type: String || Number,
       default: 0,
     },
   },

+ 6 - 2
pages.json

@@ -189,7 +189,9 @@
     {
       "path": "pages/balance/index",
       "style": {
-        "navigationBarTitleText": "余额明细"
+        "navigationBarTitleText": "余额明细",
+        "enablePullDownRefresh": true,
+        "onReachBottomDistance": 50
       }
     },
     {
@@ -201,7 +203,9 @@
     {
       "path": "pages/redpacket/list",
       "style": {
-        "navigationBarTitleText": "红包列表"
+        "navigationBarTitleText": "红包列表",
+        "enablePullDownRefresh": true,
+        "onReachBottomDistance": 50
       }
     }
   ],

+ 25 - 13
pages/balance/detail.vue

@@ -2,39 +2,51 @@
   <view class="container">
     <!-- 金额显示 -->
     <view class="amount-section">
-      <text class="amount">+0.50</text>
-      <text class="description">签到打卡</text>
+      <text class="amount">{{ balanceDetail.prefix == 1 ? "+" : "-" }}{{ balanceDetail.amount }}</text>
+      <text class="description">{{ balanceDetail.description }}</text>
     </view>
 
     <!-- 交易信息 -->
     <view class="details-section">
       <view class="detail-item">
         <text class="label">交易类型</text>
-        <text class="value">收入</text>
+        <text class="value">{{ balanceDetail.prefix == 1 ? "收入" : "支出" }}</text>
       </view>
       <view class="detail-item">
         <text class="label">交易时间</text>
-        <text class="value">2025-01-10 13:12:12</text>
+        <text class="value">{{ common.timestampToString(balanceDetail.insert_time) }}</text>
       </view>
       <view class="detail-item">
         <text class="label">记录编码</text>
-        <text class="value">klpt000000036</text>
+        <text class="value">{{ balanceDetail.id_code }}</text>
       </view>
       <view class="detail-item">
         <text class="label">交易后余额</text>
-        <text class="value">78.78</text>
+        <text class="value">¥{{ balanceDetail.balance }}</text>
       </view>
     </view>
   </view>
 </template>
 
-<script>
-export default {
-  methods: {
-    goBack() {
-      uni.navigateBack();
-    },
-  },
+<script setup>
+import { ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+
+import http from "@/utils/request";
+import common from "@/utils/common";
+
+const balanceDetail = ref({});
+
+onLoad((options) => {
+  // 获取传递的参数
+  _getBalanceDetail(options.record_id);
+});
+
+const _getBalanceDetail = async (record_id) => {
+  const callback = await http.request("api/custom_amount/get_record_info", { record_id });
+  if (callback.code == "success") {
+    balanceDetail.value = callback.data;
+  }
 };
 </script>
 

+ 65 - 13
pages/balance/index.vue

@@ -1,41 +1,86 @@
 <template>
   <view class="balance">
-    <view class="date_picker">
+    <!-- <view class="date_picker">
       <picker mode="date" :value="selectedDate" fields="month" @change="_bindDateChange">
         <view>{{ formattedDate }}</view>
       </picker>
-    </view>
+    </view> -->
     <view class="balance_list">
-      <view class="balance_date">2025年1月</view>
-      <view class="balance_item" @click="_goDetail">
+      <!-- <view class="balance_date">2025年1月</view> -->
+      <view class="balance_item" @click="_goDetail(item.id)" v-for="(item, index) in balanceList" :key="item.id">
         <view class="balance_item_left">
-          <view style="margin-bottom: 10rpx">签到打卡</view>
-          <view>2025-01-01 12:00</view>
+          <view style="margin-bottom: 10rpx">{{ item.type_state }}</view>
+          <view>{{ item.insert_time }}</view>
         </view>
         <view class="balance_item_right">
-          <view class="price">+1.00</view>
-          <view>余额:78.78</view>
+          <view class="price">{{ item.prefix == 1 ? "+" : "-" }}{{ item.amount }}</view>
+          <view>余额:{{ item.balance }}</view>
         </view>
       </view>
     </view>
+    <Empty v-if="balanceList.length == 0 && !isReqing" text="----- 还没有记录啦 -----" />
+    <view class="to_bottom" v-if="isLast && balanceList.length"> -----到底啦-----</view>
   </view>
 </template>
 
 <script setup>
-import { ref, computed } from "vue";
+import { ref, computed, onMounted } from "vue";
+import { onReachBottom, onPullDownRefresh } from "@dcloudio/uni-app";
+import Empty from "@/components/Empty/Empty.vue";
+import http from "@/utils/request";
 
 const selectedDate = ref("请选择日期");
+const balanceList = ref([]);
+const page = ref(1);
+const pageSize = ref(15);
+const isLast = ref(false);
+const isReqing = ref(false);
 
 const _bindDateChange = (e) => {
   selectedDate.value = e.detail.value;
 };
 
-const _goDetail = () => {
+onMounted(() => {
+  _getBalacnelist();
+});
+
+const _goDetail = (record_id) => {
   uni.navigateTo({
-    url: "/pages/balance/detail",
+    url: `/pages/balance/detail?record_id=${record_id}`,
   });
 };
 
+const _getBalacnelist = async () => {
+  if (isReqing.value) return;
+  isReqing.value = true;
+
+  try {
+    const callback = await http.request("api/custom_amount/get_record_list", { page: page.value, limit: pageSize.value });
+    if (callback.code == "success") {
+      if (callback.data.last_page <= page.value) isLast.value = true;
+      const balanceListData = callback.data.data || [];
+      balanceList.value = [...balanceList.value, ...balanceListData];
+    }
+  } finally {
+    isReqing.value = false;
+  }
+};
+
+onReachBottom(async () => {
+  console.log("上拉加载");
+  if (isLast.value || isReqing.value) return;
+  page.value += 1;
+  await _getBalacnelist();
+});
+
+onPullDownRefresh(async () => {
+  page.value = 1;
+  balanceList.value = [];
+  isLast.value = false;
+  await _getBalacnelist();
+  uni.stopPullDownRefresh();
+});
+
 const formattedDate = computed(() => {
   if (selectedDate.value === "请选择日期") {
     return selectedDate.value;
@@ -65,22 +110,29 @@ const formattedDate = computed(() => {
       border-bottom: 2rpx solid #ddd;
     }
     .balance_item {
-      margin-bottom: 20rpx;
+      padding-bottom: 20rpx;
       display: flex;
       justify-content: space-between;
       align-items: center;
+      border-bottom: 1px solid #ddd;
       .balance_item_left,
       .balance_item_right {
         display: flex;
         flex-direction: column;
         font-size: 26rpx;
-        margin-top: 20rpx;
+        padding-top: 20rpx;
       }
       .balance_item_right {
         align-items: flex-end;
         > .price {
           font-weight: bold;
           margin-bottom: 10rpx;
+          &.red {
+            color: #ff0000;
+          }
+          &.green {
+            color: #00ff00;
+          }
         }
       }
     }

+ 41 - 7
pages/redPacket/index.vue

@@ -3,8 +3,8 @@
     <image src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/red_packet_background.jpg" class="read_packet_background" />
     <view class="rule" @click="showRule">活动规则</view>
     <view class="get_content" v-if="!showReward">
-      <view class="custom_button" @click="showRectangle"> 立即领取 </view>
-      <text>点击领取后,红包将自动到账余额</text>
+      <view class="custom_button" :class="{ disabled: packetItem.status !== 0 }" @click="showRectangle"> 立即领取 </view>
+      <text>{{ packetItem.status !== 0 ? "红包已领取或已失效" : "点击领取后,红包将自动到账余额" }}</text>
     </view>
     <uni-popup ref="lotteryRule" type="center">
       <view class="lottery_rule_box">
@@ -13,14 +13,16 @@
           <view class="close_btn" @click="closeRule"> X </view>
         </view>
         <scroll-view class="lottery_rule_info" scroll-y="true">
-          <rich-text nodes="lotteryInfo.rule" class="rich_text"></rich-text>
+          <rich-text :nodes="packetItem.active_rule" class="rich_text"></rich-text>
         </scroll-view>
       </view>
     </uni-popup>
     <div :class="['rectangle', { show: showReward }]">
       <image src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/red_packet_bottom.png" class="read_packet_background" />
       <view class="text_content">
-        <view> <text style="font-size: 50rpx; font-weight: bold">8.88 </text>元 </view>
+        <view>
+          <text style="font-size: 50rpx; font-weight: bold">{{ packetItem.money }} </text>元
+        </view>
         <view style="margin-top: 20rpx; color: #02a7f0" @click="_goWithdraw">已存入余额,去提现</view>
       </view>
     </div>
@@ -29,9 +31,19 @@
 
 <script setup>
 import { ref } from "vue";
+import { onLoad } from "@dcloudio/uni-app";
+import common from "@/utils/common";
+
+import http from "@/utils/request";
 
 const lotteryRule = ref();
 const showReward = ref(false);
+const packetItem = ref({});
+
+onLoad((options) => {
+  // 获取传递的参数
+  _getPacketlist(options.packet_id);
+});
 
 const closeRule = () => {
   lotteryRule.value.close();
@@ -41,15 +53,28 @@ const showRule = () => {
   lotteryRule.value.open("center");
 };
 
-const showRectangle = () => {
-  showReward.value = true;
-};
+const showRectangle = common.debounce(() => {
+  if (!packetItem.value.custom_redpacket_id) return;
+  http.request("/api/redpacket/get_redpacket", { custom_redpacket_id: packetItem.value.custom_redpacket_id }).then((callback) => {
+    if (callback.code == "success") {
+      showReward.value = true;
+    }
+  });
+}, 1000);
 
 const _goWithdraw = () => {
   uni.redirectTo({
     url: "/pages/user/withdraw",
   });
 };
+
+const _getPacketlist = (packet_id) => {
+  http.request("api/redpacket/get_info", { custom_redpacket_id: packet_id }, "POST").then((callback) => {
+    if (callback.code == "success") {
+      packetItem.value = callback.data;
+    }
+  });
+};
 </script>
 
 <style lang="less" scoped>
@@ -99,6 +124,15 @@ const _goWithdraw = () => {
     border: 2px solid transparent;
     text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
     position: relative;
+    &.disabled {
+      background-color: #ccc;
+      color: #666;
+      &:before,
+      &:after {
+        background-color: #ccc;
+        border-color: #ccc;
+      }
+    }
     &:before,
     &:after {
       content: "";

+ 54 - 10
pages/redPacket/list.vue

@@ -1,32 +1,76 @@
 <template>
   <view class="red_packet_list">
-    <view v-for="(packet, index) in redpackets" :key="packet.id" class="red_packet_item" :class="{ disabled: index == 0 }" @click="handleClick(packet)">
+    <view v-for="(packet, index) in redpackets" :key="packet.id" class="red_packet_item" :class="{ disabled: packet.status !== 0 }" @click="handleClick(packet)">
       <img src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/packet_list_backerground.png" alt="" class="redpacket_background" />
       <view class="packet_header">
         <view class="packet_image">福</view>
         <view class="packet_info">
-          <h3 class="packet_title">{{ packet.title }}</h3>
-          <p class="packet_date">{{ packet.date }}</p>
+          <h3 class="packet_title">{{ packet.active_name }}</h3>
+          <p class="packet_date">{{ common.timestampToString(packet.start_time) }} - {{ common.timestampToString(packet.end_time) }}</p>
         </view>
       </view>
     </view>
+    <Empty v-if="redpackets.length == 0 && !isReqing" text="----- 还没有红包啦 -----" />
+    <view class="to_bottom" v-if="isLast && redpackets.length"> -----到底啦-----</view>
   </view>
 </template>
 
 <script setup>
-import { ref } from "vue";
+import { ref, onMounted } from "vue";
+import { onReachBottom, onPullDownRefresh } from "@dcloudio/uni-app";
+import Empty from "@/components/Empty/Empty.vue";
+import common from "@/utils/common";
+import http from "@/utils/request";
 
-const redpackets = ref([
-  { id: 1, title: "新年红包", date: "2023-01-01", amount: 88.88, image: "path/to/image1.jpg" },
-  { id: 2, title: "生日红包", date: "2023-02-14", amount: 66.66, image: "path/to/image2.jpg" },
-  { id: 3, title: "节日红包", date: "2023-03-08", amount: 99.99, image: "path/to/image3.jpg" },
-]);
+const redpackets = ref([]);
+const page = ref(1);
+const pageSize = ref(10);
+const isLast = ref(false);
+const isReqing = ref(false);
+
+onMounted(() => {
+  _getPacketlist();
+});
 
 const handleClick = (packet) => {
+  if (packet.status !== 0) return;
   uni.navigateTo({
-    url: "/pages/redpacket/index",
+    url: `/pages/redpacket/index?packet_id=${packet.custom_redpacket_id}`,
+  });
+};
+
+const _getPacketlist = () => {
+  http.request("api/redpacket/get_list", { page: page.value, limit: pageSize.value }).then((callback) => {
+    if (callback.code == "success") {
+      if (callback.data.last_page <= page.value) isLast.value = true;
+      const packetData = callback.data.data || [];
+      redpackets.value = [...redpackets.value, ...packetData];
+    }
   });
 };
+
+onReachBottom(() => {
+  // 如果页码是0,避免第一页重复
+  if (page.value < 1) return;
+
+  // 最后一页不请求
+  if (isLast.value) return;
+
+  isReqing.value = true;
+
+  page.value = page.value + 1;
+
+  _getPacketlist();
+});
+
+onPullDownRefresh(() => {
+  page.value = 1;
+  redpackets.value = [];
+  isReqing.value = true;
+  isLast.value = false;
+  _getPacketlist();
+  uni.stopPullDownRefresh();
+});
 </script>
 
 <style lang="less" scoped>

+ 30 - 5
pages/user/index.vue

@@ -20,8 +20,12 @@
     <view class="balance_content">
       <view class="balance_content_main">
         <view style="display: flex">
-          <view class="price_content" style="margin-right: 45rpx"> <text class="title">当前余额(元)</text> <text>0.00</text></view>
-          <view class="price_content"> <text class="title">已成功提现(元)</text> <text>0.00</text></view>
+          <view class="price_content" style="margin-right: 45rpx">
+            <text class="title">当前余额(元)</text> <text>{{ Number(userInfo.amount || 0).toFixed(2) }}</text></view
+          >
+          <view class="price_content">
+            <text class="title">已成功提现(元)</text> <text>{{ Number(userInfo.transfer_amount || 0).toFixed(2) }}</text></view
+          >
         </view>
         <view class="withdraw_btn" @click="_goWithdraw">提现</view>
       </view>
@@ -49,8 +53,8 @@
       </navigator>
     </view>
     <view class="alter_info">本程序暂不提供在线交易以及支付功能,您所提交的预约,我们将验证您的购药资质并交由有售卖药品资质的商业公司与您联系确认并提供线下后续服务。</view>
-    <view class="packet_content">
-      <view class="close_btn" @click="closeRule"> X </view>
+    <view class="packet_content" v-if="show_packet">
+      <view class="close_btn" @click="closePacket"> X </view>
       <image src="https://kailin-mp.oss-cn-shenzhen.aliyuncs.com/static/icon/red_packet.gif" class="red_packet" @click="_getredpacket" />
     </view>
   </view>
@@ -67,6 +71,8 @@ export default {
         status: 0,
         city_id: 0,
       },
+      packetList: [],
+      show_packet: false,
     };
   },
   onLoad(param) {
@@ -126,11 +132,16 @@ export default {
         uni.setStorageSync("userInfo", callback.data);
       }
     });
+    this._getPacketNum();
   },
   methods: {
     _getredpacket() {
+      let url = "/pages/redpacket/list";
+      if (this.packetList.length == 1) {
+        url = `/pages/redpacket/index?packet_id=${this.packetList[0].custom_redpacket_id}`;
+      }
       uni.navigateTo({
-        url: "/pages/redpacket/list",
+        url: url,
       });
     },
     _goWithdraw() {
@@ -143,6 +154,20 @@ export default {
         url: "/pages/balance/index",
       });
     },
+    //获取红包数量是否展示红包领取页面
+    _getPacketNum() {
+      this.$http.request("api/redpacket/get_list").then((callback) => {
+        if (callback.code == "success") {
+          this.packetList = callback.data.data || [];
+          if (callback.data.data.length > 0) {
+            this.show_packet = true;
+          }
+        }
+      });
+    },
+    closePacket() {
+      this.show_packet = false;
+    },
   },
 };
 </script>

+ 72 - 17
pages/user/withdraw.vue

@@ -4,7 +4,7 @@
       <view class="title">提现金额</view>
       <view class="content">
         <view>¥</view>
-        <input type="number" class="input" v-model="withdrawAmount" @blur="_valiteMoney" />
+        <input type="digit" class="input" v-model="withdrawAmount" @blur="_valiteMoney" />
       </view>
       <view class="bottom">
         <text>当前可提现余额{{ maxWithdrawAmount }}元</text>
@@ -18,15 +18,17 @@
 <script setup>
 import { ref, watch, onMounted } from "vue";
 import http from "@/utils/request";
+import common from "@/utils/common";
 
 const withdrawAmount = ref(0);
-const maxWithdrawAmount = ref(134.14);
+const maxWithdrawAmount = ref(0);
 const isDisabled = ref(true);
+const userInfo = ref({});
+const loading = ref(false);
 
 onMounted(() => {
-  http.request("api/custom/get_info").then((res) => {
-    console.log(res);
-  });
+  userInfo.value = uni.getStorageSync("userInfo");
+  maxWithdrawAmount.value = userInfo.value.amount;
 });
 
 const _valiteMoney = (e) => {
@@ -44,18 +46,71 @@ const _getAll = () => {
   withdrawAmount.value = maxWithdrawAmount.value;
 };
 
-const _getMoney = () => {
-  if (isDisabled.value) return;
-  uni.showModal({
-    title: "温馨提示",
-    content: "您已成功提现,请注意查看信息",
-    showCancel: false,
-    confirmText: "确认",
-    success: (res) => {
-      if (res.confirm) {
-        console.log("用户点击确认");
-      }
-    },
+const _getMoney = common.debounce(async () => {
+  if (isDisabled.value || loading.value) return;
+
+  if (withdrawAmount.value < 0.1) {
+    uni.showToast({ icon: "none", title: "提现金额不能小于0.1元" });
+    return;
+  }
+
+  loading.value = true;
+  uni.showLoading({ title: "正在发起提现..." });
+
+  try {
+    const loginRes = await uni.login({ provider: "weixin" });
+    const response = await http.request("/api/wechat_transfer/transfer", { code: loginRes.code, amount: withdrawAmount.value }, "POST");
+
+    if (response.code !== "success") {
+      throw new Error(response.msg);
+    }
+
+    const systemInfo = uni.getSystemInfoSync();
+    if (systemInfo.uniPlatform !== "mp-weixin" || !wx.canIUse("requestMerchantTransfer")) {
+      throw new Error("你的微信版本过低,请更新至最新版本。");
+    }
+
+    await new Promise((resolve, reject) => {
+      wx.requestMerchantTransfer({
+        mchId: "1612111355",
+        appId: wx.getAccountInfoSync().miniProgram.appId,
+        package: response.data.package_info,
+        success: resolve,
+        fail: reject,
+      });
+    });
+
+    uni.showModal({
+      title: "温馨提示",
+      content: "您已成功提现,请注意查看信息",
+      showCancel: false,
+      confirmText: "确认",
+      success: (res) => {
+        if (res.confirm) {
+          _getUserInfo();
+        }
+      },
+    });
+  } catch (error) {
+    uni.showModal({
+      title: "温馨提示",
+      content: error.message || "提现失败,请稍后再试",
+      showCancel: false,
+      confirmText: "确认",
+    });
+  } finally {
+    loading.value = false;
+    uni.hideLoading();
+  }
+}, 300);
+
+const _getUserInfo = () => {
+  http.request("api/custom/get_info").then((callback) => {
+    if (callback.code == "success") {
+      maxWithdrawAmount.value = callback.data.amount;
+      console.log("callback.data", callback.data.amount);
+      uni.setStorageSync("userInfo", callback.data);
+    }
   });
 };
 

+ 19 - 1
utils/common.js

@@ -1,8 +1,26 @@
 // 防抖函数
-export function debounce(func, wait) {
+function debounce(func, wait) {
   let timeout;
   return function (...args) {
     clearTimeout(timeout);
     timeout = setTimeout(() => func.apply(this, args), wait);
   };
 }
+
+/**
+ * 将时间戳转换为字符串格式 YYYY-MM-DD HH:mm:ss
+ * @param {number} timestamp - 时间戳(毫秒)
+ * @returns {string} 格式化后的日期字符串
+ */
+function timestampToString(timestamp) {
+  const date = new Date(timestamp * 1000);
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, "0");
+  const day = String(date.getDate()).padStart(2, "0");
+  const hours = String(date.getHours()).padStart(2, "0");
+  const minutes = String(date.getMinutes()).padStart(2, "0");
+  const seconds = String(date.getSeconds()).padStart(2, "0");
+  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+}
+
+export default { debounce, timestampToString };