开发钱包相关页面,调试提现接口

This commit is contained in:
2025-09-27 23:08:32 +08:00
parent a1be43e02b
commit d6fb08eee4
13 changed files with 972 additions and 207 deletions

View File

@@ -132,3 +132,39 @@
}
}
}
// 过滤弹窗
.filter_popup {
padding: 20px;
.popup_content {
.form_section {
.form_item {
margin-bottom: 20px;
.form_label {
display: inline-block;
font-family: PingFang SC;
font-weight: 600;
font-style: Semibold;
font-size: 16px;
margin-bottom: 20px;
}
.options_wrapper {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
.option_item {
background-color: #0000000D;
text-align: center;
padding: 8px;
border-radius: 4px;
&.active {
background-color: #000000;
color: #fff;
}
}
}
}
}
}
}

View File

@@ -1,20 +1,48 @@
import React, { useState, useEffect } from "react";
import { View, Text, Button } from "@tarojs/components";
import { View, Text, Button, Input } from "@tarojs/components";
import Taro, { useDidShow } from "@tarojs/taro";
import dayjs from "dayjs";
import "./index.scss";
import { DialogCalendarCard } from "@/components/index";
// import { CalendarUI } from "@/components";
import { CommonPopup } from "@/components";
export enum TransactionSubType {
All = "",
GameActivity = "game_activity",
Withdrawal = "withdrawal",
Refund = "refund",
Compensation = "compensation",
}
export enum TransactionType {
All = "",
Income = "income",
Expense = "expense",
}
interface Option<T> {
label: string;
value: T;
}
interface TransactionLoadParams {
page: number;
limit: number;
type: TransactionType;
transaction_sub_type: TransactionSubType;
keyword?: string;
date?: string;
}
const DownloadBill: React.FC = () => {
const [dateRange, setDateRange] = useState({ start: "", end: "" });
const [dateType, setDateType] = useState("week");
const [visible, setVisible] = useState(false);
const [show_download_popup, set_show_download_popup] = useState(false);
const [isFocus, setIsFocus] = useState(false);
const [password, setPassword] = useState<string[]>(new Array(6).fill(""));
useEffect(() => {
culculateDateRange(dateType);
}, []);
const [showFilterPopup, setShowFilterPopup] = useState(false);
const culculateDateRange = (dateType: string) => {
const today = new Date();
const year = today.getFullYear();
@@ -59,7 +87,6 @@ const DownloadBill: React.FC = () => {
case "custom":
setDateType("custom");
setDateRange({ start: "", end: "" });
setVisible(true);
break;
}
};
@@ -74,6 +101,69 @@ const DownloadBill: React.FC = () => {
end: dayjs(end).format("YYYY-MM-DD"),
});
};
const handlePasswordInput = (e: any) => {
const value = e.detail.value;
const [one = "", two = "", three = "", four = "", five = "", six = ""] = value.split("");
setPassword([one, two, three, four, five, six]);
if (value.length === 6) {
// const timer = setTimeout(() => {
// // TODO 校验密码
// if (false) {
// set_show_download_popup(false);
// Taro.showModal({
// content: "支付密码错误,请重试",
// cancelText: "忘记密码",
// confirmText: "重试",
// cancelColor: "#000",
// confirmColor: "#fff",
// }).then((res) => {
// if (res.confirm) {
// set_show_download_popup(true);
// } else if (res.cancel) {
// Taro.navigateTo({
// url: "/user_pages/validPhone/index"
// });
// }
// }).finally(() => {
// clearTimeout(timer);
// });
// } else {
// // TODO 下载账单
// }
// }, 100);
}
}
const transaction_type_options: Option<TransactionSubType>[] = [
{
label: "全部",
value: TransactionSubType.All,
},
{
label: "组织活动",
value: TransactionSubType.GameActivity,
},
{
label: "提现",
value: TransactionSubType.Withdrawal,
},
{
label: "退款",
value: TransactionSubType.Refund,
},
{
label: "企业赔付",
value: TransactionSubType.Compensation,
},
];
const [load_transactions_params, set_load_transactions_params] =
useState<TransactionLoadParams>({
page: 1,
limit: 20,
type: TransactionType.All,
transaction_sub_type: TransactionSubType.All,
keyword: "",
date: "",
});
return (
<View className="download_bill_page">
<View className="hint_content">
@@ -81,13 +171,13 @@ const DownloadBill: React.FC = () => {
<Text className="button_text"></Text>
</View>
<View className="form_container">
<View className="form_item">
{/* <View className="form_item">
<Text className="title_text">接收方式</Text>
<View className="value_content arrow">
<Text>小程序消息</Text>
</View>
</View>
<View className="form_item">
</View> */}
<View className="form_item" onClick={() => { setShowFilterPopup(true) }}>
<Text className="title_text"></Text>
<View className="value_content arrow">
<Text></Text>
@@ -105,9 +195,8 @@ const DownloadBill: React.FC = () => {
</View>
<View
className={`option_button ${
dateType === "month" ? "active" : ""
}`}
className={`option_button ${dateType === "month" ? "active" : ""
}`}
onClick={() => {
selectDateRange("month");
}}
@@ -115,9 +204,8 @@ const DownloadBill: React.FC = () => {
</View>
<View
className={`option_button ${
dateType === "custom" ? "active" : ""
}`}
className={`option_button ${dateType === "custom" ? "active" : ""
}`}
onClick={() => {
selectDateRange("custom");
}}
@@ -126,11 +214,21 @@ const DownloadBill: React.FC = () => {
</View>
</View>
</View>
{dateRange.start && dateRange.end && (
{dateRange.start && dateRange.end && dateType !== "custom" && (
<View className="time_box">
<Text>{dateRange.start}</Text> <Text>{dateRange.end}</Text>
</View>
)}
{
dateType === "custom" && (
<View className="form_item" onClick={() => { setVisible(true); }}>
<Text className="title_text"></Text>
<View className="value_content arrow">
<Text>{dateRange.start && dateRange.end ? `${dateRange.start}${dateRange.end}` : "请选择账单时间"}</Text>
</View>
</View>
)
}
</View>
<View className="button_container">
<Text
@@ -152,6 +250,71 @@ const DownloadBill: React.FC = () => {
onClose={() => setVisible(false)}
/>
)}
{/* 下载账单输入密码弹窗 */}
<CommonPopup
visible={show_download_popup}
onClose={() => set_show_download_popup(false)}
title="提现"
className="withdraw_popup"
hideFooter={true}
>
<View className="popup_content">
<View className="popup_text">{`支付账单流水文件(文件名).xlsx`}</View>
<View className="popup_text">{`文件大小7KB`}</View>
<View className="popup_text">{`请输入交易密码`}</View>
<View className="password_container">
{
password.map((item, index) => (
<View key={index} className="password_item">
<Text className="password_text">{item}</Text>
</View>
))
}
</View>
<Input focus={isFocus} type="number" style={{ width: "0", height: "0", opacity: "0" }} value={password.filter(item => item !== "").join("")} maxlength={6} onInput={handlePasswordInput} />
</View>
</CommonPopup>
{/* 筛选账单弹窗 */}
<CommonPopup
visible={showFilterPopup}
onClose={() => setShowFilterPopup(false)}
onConfirm={() => { }}
title="选择筛选项"
className="filter_popup"
>
<View className="popup_content">
<View className="form_section">
<View className="form_item">
<Text className="form_label"></Text>
<View className="options_wrapper">
{transaction_type_options.map(
(option: Option<TransactionSubType>) => (
<View
className={
load_transactions_params.transaction_sub_type ===
option.value
? "option_item active"
: "option_item"
}
key={option.value}
onClick={() => {
set_load_transactions_params({
...load_transactions_params,
transaction_sub_type: option.value,
});
}}
>
{option.label}
</View>
)
)}
</View>
</View>
</View>
</View>
</CommonPopup>
</View>
);
};

View File

@@ -0,0 +1,64 @@
.download-bill-records-page {
color: #3C3C4399;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
padding: 20px;
.records-container {
.record-item {
padding: 16px 0;
&+.record-item {
border-top: 1px solid #0000000D;
}
.title-text {
font-size: 16px;
color: #000;
margin-bottom: 8px;
}
.info-item {
display: flex;
gap: 20px;
&+.info-item {
margin-top: 8px;
}
Text {
&:first-child {
width: 64px;
}
&:last-child {
flex: 1;
color: #000;
&.btn {
color: #007AFF;
flex: unset;
width: fit-content;
}
}
}
}
}
}
.tips {
font-size: 12px;
text-align: center;
position: fixed;
bottom: 40px;
width: calc(100% - 40px);
}
}

View File

@@ -1,9 +1,45 @@
import React, { useState, useEffect } from "react";
import { View } from "@tarojs/components";
import { View, Text } from "@tarojs/components";
import "./index.scss";
const DownloadBillRecords: React.FC = () => {
return (
<View></View>
<View className="download-bill-records-page">
<View className="records-container">
<View className="record-item">
<View className="title-text"></View>
<View className="info-item">
<Text></Text>
<Text>2025912 19:03:06</Text>
</View>
<View className="info-item">
<Text></Text>
<Text>2025912 19:03:06 2025912 19:03:06</Text>
</View>
<View className="info-item">
<Text></Text>
<Text className="btn"></Text>
</View>
</View>
<View className="record-item">
<View className="title-text"></View>
<View className="info-item">
<Text></Text>
<Text>2025912 19:03:06</Text>
</View>
<View className="info-item">
<Text></Text>
<Text>2025912 19:03:06 2025912 19:03:06</Text>
</View>
<View className="info-item">
<Text></Text>
<Text className="btn"></Text>
</View>
</View>
</View>
<View className="tips">7</View>
</View>
);
};

View File

@@ -1,197 +1,206 @@
.listSearchContainer {
padding: 0 15px;
padding-top: 16px;
padding: 0 15px;
padding-top: 16px;
.icon16 {
width: 16px;
height: 16px;
}
.topSearch {
padding: 10px 16px 5px 12px;
display: flex;
align-items: center;
height: 44px;
box-sizing: border-box;
gap: 10px;
border-radius: 44px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
background: #fff;
box-shadow: 0 4px 48px 0 rgba(0, 0, 0, 0.08);
.nut-input {
padding: 0;
height: 100%;
}
}
.searchRight {
display: flex;
align-items: center;
gap: 12px;
.searchLine {
width: 1px;
height: 20px;
border-radius: 20px;
background: rgba(0, 0, 0, 0.06);
.icon16 {
width: 16px;
height: 16px;
}
.searchText {
color: #000000;
font-size: 16px;
font-weight: 600;
line-height: 20px;
}
}
.topSearch {
padding: 5px 16px 5px 12px;
display: flex;
align-items: center;
height: 44px;
box-sizing: border-box;
gap: 10px;
border-radius: 44px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
background: #fff;
box-shadow: 0 4px 48px 0 rgba(0, 0, 0, 0.08);
.searchIcon {
width: 20px;
height: 20px;
}
.historySearchTitleWrapper {
display: flex;
padding: 12px 15px;
justify-content: space-between;
align-items: flex-end;
align-self: stretch;
.historySearchTitle,
.historySearchClear {
color: #000;
font-size: 14px;
font-weight: 600;
line-height: 20px;
.nut-input {
padding: 0;
height: 100%;
}
}
.historySearchClear {
color: #9a9a9a;
display: flex;
align-items: center;
gap: 4px;
}
}
.historySearchList {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
.historySearchItem {
flex-shrink: 0;
flex-grow: 0;
display: flex;
height: 28px;
padding: 4px 12px;
justify-content: center;
align-items: center;
gap: 2px;
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
background: rgba(0, 0, 0, 0.03);
}
}
.searchSuggestion {
padding: 6px 0;
.searchSuggestionItem {
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
.searchSuggestionItemLeft {
.searchRight {
display: flex;
align-items: center;
gap: 12px;
color: rgba(60, 60, 67, 0.6);
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.highlight {
color: #000000;
}
}
}
.transaction_list {
.loading_state,
.empty_state {
padding: 40px 20px;
text-align: center;
.loading_text,
.empty_text {
font-size: 14px;
color: #999;
}
}
.transaction_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 20px;
.transaction_left {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
.transaction_title {
font-size: 12px;
font-weight: 600;
color: #000;
line-height: 1.5;
text-align: left;
.searchLine {
width: 1px;
height: 20px;
border-radius: 20px;
background: rgba(0, 0, 0, 0.06);
}
.transaction_time {
display: flex;
align-items: center;
align-self: stretch;
gap: 4px;
.transaction_date,
.transaction_clock {
font-size: 10px;
font-weight: 400;
color: rgba(60, 60, 67, 0.6);
line-height: 1.2;
text-align: left;
}
.searchText {
color: #000000;
font-size: 16px;
font-weight: 600;
line-height: 20px;
}
}
}
.transaction_right {
.searchIcon {
width: 20px;
height: 20px;
}
.historySearchTitleWrapper {
display: flex;
flex-direction: column;
padding: 12px 0;
justify-content: space-between;
align-items: flex-end;
gap: 4px;
width: 68px;
align-self: stretch;
.transaction_amount {
font-size: 12px;
font-weight: 600;
color: #000;
line-height: 1.5;
text-align: right;
.historySearchTitle,
.historySearchClear {
color: #000;
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
.balance_info {
font-size: 10px;
font-weight: 400;
color: rgba(60, 60, 67, 0.6);
line-height: 1.2;
text-align: right;
.historySearchClear {
color: #9a9a9a;
display: flex;
align-items: center;
gap: 4px;
}
}
}
}
}
.historySearchList {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
.historySearchItem {
color: #3C3C4399;
font-size: 12px;
flex-shrink: 0;
flex-grow: 0;
display: flex;
padding: 4px 12px;
justify-content: center;
align-items: center;
gap: 2px;
border-radius: 999px;
border: 0.5px solid rgba(0, 0, 0, 0.06);
background: rgba(0, 0, 0, 0.03);
}
}
.searchSuggestion {
padding: 6px 0;
.searchSuggestionItem {
padding: 10px 20px;
display: flex;
align-items: center;
justify-content: space-between;
.searchSuggestionItemLeft {
display: flex;
align-items: center;
gap: 12px;
color: rgba(60, 60, 67, 0.6);
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.highlight {
color: #000000;
}
}
}
.transaction_list {
.loading_state,
.empty_state {
padding: 40px 20px;
text-align: center;
.loading_text,
.empty_text {
font-size: 14px;
color: #999;
}
}
.transaction_item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
.transaction_left {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
.transaction_title {
font-size: 12px;
font-weight: 600;
color: #000;
line-height: 1.5;
text-align: left;
}
.transaction_time {
display: flex;
align-items: center;
align-self: stretch;
gap: 4px;
.transaction_date,
.transaction_clock {
font-size: 10px;
font-weight: 400;
color: rgba(60, 60, 67, 0.6);
line-height: 1.2;
text-align: left;
}
}
}
.transaction_right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
width: 68px;
.transaction_amount {
font-size: 12px;
font-weight: 600;
color: #000;
line-height: 1.5;
text-align: right;
}
.balance_info {
font-size: 10px;
font-weight: 400;
color: rgba(60, 60, 67, 0.6);
line-height: 1.2;
text-align: right;
}
}
}
}
.tips_text {
font-size: 12px;
text-align: center;
color: #3C3C4399;
margin-top: 20px;
}
}

View File

@@ -341,6 +341,11 @@ const QueryTransactions = () => {
</View>
)}
</View>
{
transactions.length > 0 && (
<View className="tips_text">202491</View>
)
}
</View>
</>
);

View File

@@ -15,7 +15,7 @@ interface FormFields {
const SetTransactionPassword: React.FC = () => {
const [handleType, setHandleType] = useState("set");
const router = useRouter();
const { type } = router.params;
const { type, phone, sms_code } = router.params;
useEffect(() => {
if (type) {
@@ -60,9 +60,9 @@ const SetTransactionPassword: React.FC = () => {
return;
}
} else if (handleType === "reset") {
const { old_password } = formData;
// const { old_password } = formData;
try {
await httpService.post("/wallet/change_payment_password", { old_password, new_password });
await httpService.post("/wallet/reset_payment_password", { phone, new_password, sms_code });
Taro.showToast({
title: "修改交易密码成功",
icon: "success",
@@ -81,12 +81,12 @@ const SetTransactionPassword: React.FC = () => {
return (
<View className="set-transaction-password-page">
{
handleType === "reset" && (
<View className="form-item">
<Text className="form-label"></Text>
<Input placeholder="请输入旧密码" password type="number" maxlength={6} onInput={(e) => { handleInput(e, "old_password") }}></Input>
</View>
)
// handleType === "reset" && (
// <View className="form-item">
// <Text className="form-label">旧密码</Text>
// <Input placeholder="请输入旧密码" password type="number" maxlength={6} onInput={(e) => { handleInput(e, "old_password") }}></Input>
// </View>
// )
}
<View className="form-item">
<Text className="form-label"></Text>

View File

@@ -4,6 +4,7 @@ import { View, Text, Input, Button } from "@tarojs/components";
import "./index.scss";
import httpService from "@/services/httpService";
import { useUserInfo } from "@/store/userStore";
interface FormFields {
phone?: string;
@@ -11,8 +12,9 @@ interface FormFields {
}
const ValidPhone: React.FC = () => {
const userInfo = useUserInfo();
const [formData, setFormData] = useState<FormFields>({
phone: "",
phone: userInfo.phone || "",
sms_code: "",
});
@@ -22,21 +24,32 @@ const ValidPhone: React.FC = () => {
const handleConfirm = async () => {
// TODO: 校验验证码
Taro.navigateTo({ url: "/user_pages/setTransactionPassword/index?type=find" });
Taro.navigateTo({ url: `/user_pages/setTransactionPassword/index?type=reset&phone=${formData.phone}&sms_code=${formData.sms_code}` });
};
const getSMSCode = async () => {
const { phone } = formData;
try {
await httpService.post("/wallet/send_reset_password_sms", { phone });
Taro.showToast({ title: "验证码已发送", icon: "none" });
} catch (error) {
console.log(error);
Taro.showToast({ title: "获取验证码失败", icon: "none" });
}
};
return (
<View className="set-transaction-password-page">
<View className="form-item">
<Text className="form-label"></Text>
<Input defaultValue={"15708469466"} type="number" disabled></Input>
<Input defaultValue={formData.phone} type="number" disabled></Input>
</View>
<View className="form-item">
<Text className="form-label"></Text>
<Input placeholder="请输入验证码" type="number" onInput={(e) => { handleInput(e, "sms_code") }}></Input>
<Button className="btn" ></Button>
<Button className="btn" onClick={getSMSCode}></Button>
</View>
<Button className="btn bottom-btn" onClick={handleConfirm}></Button>
<Button className="btn bottom-btn" disabled={!formData.sms_code} onClick={handleConfirm}></Button>
</View>
);
};

View File

@@ -228,14 +228,20 @@ const WalletPage: React.FC = () => {
};
const navigateToSetTransactionPassword = (type: "set" | "reset") => {
let url = ""
if (type === "set") {
url = `/user_pages/setTransactionPassword/index?type=${type}`
} else if (type === "reset") {
url = `/user_pages/validPhone/index`
}
Taro.navigateTo({
url: `/user_pages/setTransactionPassword/index?type=${type}`,
url,
});
};
// 处理提现
const handle_withdraw = () => {
if (!password_status) {
if (password_status) {
navigateToSetTransactionPassword("set");
return;
}
@@ -247,7 +253,10 @@ const WalletPage: React.FC = () => {
});
return;
}
set_show_withdraw_popup(true);
Taro.navigateTo({
url: "/user_pages/withdrawal/index",
});
// set_show_withdraw_popup(true);
};
// 提交提现申请

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '钱包',
})

View File

@@ -0,0 +1,144 @@
.withdrawal-page {
height: 100vh;
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
color: #3C3C4399;
font-size: 12px;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
.withdrawal-container {
margin: 20px 5px 0;
border-radius: 20px;
padding-top: 12px;
padding-right: 20px;
padding-bottom: 24px;
padding-left: 20px;
gap: 16px;
border: 0.5px solid #EBEBEB;
box-shadow: 0px 4px 36px 0px #0000000D;
.title-text {
color: #000;
}
.input-container {
font-weight: bold;
color: #000;
display: flex;
align-items: flex-end;
gap: 8px;
border-bottom: 0.5px solid var(--Fills-Tertiary, #7878801F);
margin: 12px 0;
padding-bottom: 12px;
.symbol {
font-size: 20px;
}
Input {
font-size: 32px;
overflow: unset;
text-overflow: unset;
height: 32px;
line-height: 32px;
margin: 0;
padding: 0;
border: none;
background: none;
color: inherit;
outline: none;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
flex: 1;
}
.btn {
height: 24px;
border: 1px solid rgba(0, 0, 0, 0.06);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
background: #000;
box-shadow: 0 8px 64px 0 rgba(0, 0, 0, 0.1);
backdrop-filter: blur(16px);
font-feature-settings: "liga" off, "clig" off;
font-family: "PingFang SC";
font-size: 9.6px;
font-style: normal;
line-height: normal;
border-radius: 8px;
margin-right: 0;
}
}
.tips-text {
color: #F3334A;
}
.btn-container {
display: flex;
justify-content: space-between;
.btn {
color: #007AFF;
}
}
}
.tips-container {
padding: 20px 20px 0;
.title-text {
font-weight: 600;
margin-bottom: 12px;
}
.tips-text {
display: flex;
flex-direction: column;
}
}
}
.withdraw_popup {
.popup_content {
display: flex;
flex-direction: column;
align-items: center;
.popup_text {
font-family: DingTalk JinBuTi;
font-weight: 400;
font-style: Regular;
font-size: 20px;
line-height: 16px;
margin: 20px 0;
}
.password_container {
display: flex;
gap: 4px;
align-items: center;
.password_item {
width: 32px;
height: 32px;
border-radius: 4px;
background-color: #7878801F;
display: flex;
justify-content: center;
align-items: center;
-webkit-text-security: disc;
}
}
}
}

View File

@@ -0,0 +1,282 @@
import React, { useState, useEffect } from "react";
import { View, Text, Input, Button } from "@tarojs/components";
import Taro, { useDidShow } from "@tarojs/taro";
import httpService from "@/services/httpService";
import "./index.scss";
import { CommonPopup } from "@/components";
interface WalletInfo {
balance: string;
frozen_balance?: string;
total_balance?: string;
total_income?: string;
total_withdraw?: string;
}
const Withdrawal: React.FC = () => {
const [showTips, setShowTips] = useState(false);
const [tipsText, setTipsText] = useState<string>("");
const [inputValue, setInputValue] = useState<string>("0.00");
const [walletInfo, setWalletInfo] = useState<WalletInfo>({
balance: "0.00",
});
const [isFocus, setIsFocus] = useState(false);
const [show_withdraw_popup, set_show_withdraw_popup] = useState(false);
const [password, setPassword] = useState<string[]>(new Array(6).fill(""));
const toastConfig = {
aa: {
title: "已超单日限额",
content: "您今日提现已超过支付通道2000元单日限额建议您明日再发起提现资金仍安全存放在钱包余额中。"
},
bb: {
title: "已超单日限额",
content: "今日提现通道额度已用完,暂时无法提现;您的余额安全存放在钱包中,我们会在 次日0点恢复 提现服务;如有紧急情况,请联系客服。"
}
}
useDidShow(() => {
load_wallet_data();
});
useEffect(() => {
if (show_withdraw_popup) {
setIsFocus(true);
} else {
setPassword(new Array(6).fill(""));
setIsFocus(false);
}
}, [show_withdraw_popup]);
const validateWithdrawAmount = (amount: string) => {
if (Number(amount) > Number(walletInfo.balance)) {
setShowTips(true);
setTipsText("输入金额超过钱包余额");
} else if (Number(amount) > 200) {
setShowTips(true);
setTipsText("单笔提现金额不能超过 200元");
}
};
const withdrawAll = () => {
setInputValue(walletInfo.balance);
validateWithdrawAmount(walletInfo.balance);
};
const handleInput = (e: any) => {
console.log(e);
const value = e.detail.value;
setInputValue(value);
validateWithdrawAmount(value);
};
// 加载钱包数据
const load_wallet_data = async () => {
try {
const response = await httpService.post("/wallet/balance");
const {
balance,
frozen_balance,
total_balance,
total_income,
total_withdraw,
} = response.data;
setWalletInfo({
balance,
frozen_balance,
total_balance,
total_income,
total_withdraw,
});
} catch (error: any) {
console.error("加载钱包数据失败:", error);
let errorMessage = "加载失败,请重试";
if (
error &&
error.response &&
error.response.data &&
error.response.data.message
) {
errorMessage = error.response.data.message;
} else if (error && error.data && error.data.message) {
errorMessage = error.data.message;
}
Taro.showToast({
title: errorMessage,
icon: "error",
duration: 2000,
});
}
};
const handleWithdraw = async () => {
// TODO 校验提现状态
// if (true) {
// Taro.showToast({
// title: "您今日已累计提现 10次达到每日次数上限",
// icon: "none",
// });
// }
// const { aa, bb } = toastConfig;
// Taro.showModal({
// title: aa.title,
// content: aa.content,
// showCancel: false,
// confirmColor: "#000",
// });
// Taro.showModal({
// title: bb.title,
// content: bb.content,
// showCancel: false,
// confirmColor: "#000",
// });
set_show_withdraw_popup(true);
};
const submit_withdraw = async () => {
// 先调用后端接口获取提现参数
const response = await httpService.post("/wallet/withdraw", {
amount: parseFloat(inputValue),
transfer_remark: "用户申请提现",
});
// 根据后端返回的数据结构解析参数
const { mch_id, app_id, package_info, open_id } = response.data;
console.log("/wallet/withdraw:", response.data);
set_show_withdraw_popup(false);
// 调用微信商户转账接口
(Taro as any).requestMerchantTransfer({
mchId: mch_id,
appId: app_id,
package: package_info,
openId: open_id,
success: (res) => {
console.log("微信转账成功:", res);
Taro.showToast({
title: "提现成功",
icon: "success",
duration: 2000,
});
// 关闭弹窗并重置状态
set_show_withdraw_popup(false);
setInputValue("0.00");
// 重新加载数据
load_wallet_data();
},
fail: (res) => {
console.log("微信转账失败:", res);
},
});
}
const handlePasswordInput = (e: any) => {
const value = e.detail.value;
const [one = "", two = "", three = "", four = "", five = "", six = ""] = value.split("");
setPassword([one, two, three, four, five, six]);
if (value.length === 6) {
const timer = setTimeout(() => {
// TODO 校验密码
if (false) {
set_show_withdraw_popup(false);
Taro.showModal({
content: "支付密码错误,请重试",
cancelText: "忘记密码",
confirmText: "重试",
cancelColor: "#000",
confirmColor: "#fff",
}).then((res) => {
if (res.confirm) {
set_show_withdraw_popup(true);
} else if (res.cancel) {
Taro.navigateTo({
url: "/user_pages/validPhone/index"
});
}
}).finally(() => {
clearTimeout(timer);
});
} else {
submit_withdraw();
}
}, 100);
}
}
return (
<View className="withdrawal-page" >
<View className="withdrawal-container">
<Text className="title-text"></Text>
<View className="input-container">
<Text className="symbol">¥</Text>
<Input type="digit" placeholder="0.00" cursorColor="#000" value={inputValue} onInput={handleInput} />
{
!showTips && (Number(inputValue) !== 0) && (
<Button className="btn" onClick={handleWithdraw}></Button>
)
}
</View>
<View className="btn-container">
<View>
<Text>{`我的余额:¥${walletInfo.balance}`}</Text>
<Text>¥5000.00</Text>
</View>
<Text className="btn" onClick={withdrawAll}></Text>
</View>
{
showTips && (
<View className="tips-text">{tipsText}</View>
)
}
</View>
<View className="tips-container">
<View className="title-text"></View>
<View className="tips-text">
<Text>1. </Text>
<Text>2. </Text>
<Text>3. </Text>
<Text>4. </Text>
<Text>5. </Text>
<Text>6. </Text>
<Text>7. </Text>
</View>
</View>
<View className="tips-container">
<View className="title-text"></View>
<View className="tips-text">
<Text>1. </Text>
<Text>2. </Text>
<Text>3. </Text>
<Text>4. 使</Text>
</View>
</View>
{/* 提现输入密码弹窗 */}
<CommonPopup
visible={show_withdraw_popup}
onClose={() => set_show_withdraw_popup(false)}
title="提现"
className="withdraw_popup"
hideFooter={true}
>
<View className="popup_content">
<View className="popup_text">{`¥${inputValue}`}</View>
<View className="password_container">
{
password.map((item, index) => (
<View key={index} className="password_item">
<Text className="password_text">{item}</Text>
</View>
))
}
</View>
<Input focus={isFocus} type="number" style={{ width: "0", height: "0", opacity: "0" }} value={password.filter(item => item !== "").join("")} maxlength={6} onInput={handlePasswordInput} />
</View>
</CommonPopup>
</View >
);
};
export default Withdrawal;