合并代码

This commit is contained in:
筱野
2025-09-28 23:00:57 +08:00
23 changed files with 1901 additions and 422 deletions

View File

@@ -30,6 +30,9 @@ export default defineAppConfig({
"downloadBill/index", // 下载账单
"downloadBillRecords/index", // 下载账单记录
"billDetail/index", // 账单详情
"setTransactionPassword/index", // 设置交易密码
"validPhone/index", // 验证手机号
"withdrawal/index", // 提现
],
},
// {

View File

@@ -17,7 +17,7 @@ interface PickerOption {
interface PickerProps {
visible: boolean;
setvisible: (visible: boolean) => void;
options?: PickerOption[][];
options?: PickerOption[][] | PickerOption[];
value?: (string | number)[];
type?: "month" | "day" | "hour" | "ntrp" | null;
img?: string;

View File

@@ -29,7 +29,9 @@ export const API_CONFIG = {
CREATE: '/game/create',
JOIN: '/game/join',
LEAVE: '/game/leave'
}
},
PROFESSIONS: '/professions/tree',
CITIS: '/admin/wch_cities/page'
};
// 请求拦截器配置

View File

@@ -40,6 +40,17 @@ interface UserDetailData {
};
}
export interface PickerOption {
text: string | number;
value: string | number;
children?: PickerOption[];
}
export interface Profession {
name: string;
children: Profession[] | [];
}
// 用户详细信息接口(从 loginService 移过来)
export interface UserInfoType {
id: number
@@ -119,6 +130,21 @@ interface BackendGameData {
}[];
}
const formatOptions = (data: Profession[]): PickerOption[] => {
return data.map((item: Profession) => {
const { name: text, children } = item;
const itm: PickerOption = {
text,
value: text,
children: children ? formatOptions(children) : []
}
if (!itm.children!.length) {
delete itm.children
}
return itm
})
}
// 用户服务类
export class UserService {
// 数据转换函数将后端数据转换为ListContainer期望的格式
@@ -206,7 +232,7 @@ export class UserService {
date_str = `明天(${weekday})`;
} else if (start_date.getTime() === day_after_tomorrow.getTime()) {
date_str = `后天(${weekday})`;
} else if(this.is_date_in_this_week(start_time)) {
} else if (this.is_date_in_this_week(start_time)) {
date_str = weekday;
} else {
date_str = `${start_time.getFullYear()}-${(start_time.getMonth() + 1).toString().padStart(2, '0')}-${start_time.getDate().toString().padStart(2, '0')}(${weekday})`;
@@ -238,7 +264,7 @@ export class UserService {
if (response.code === 0) {
const userData = response.data;
return {
id: userData.id || '',
id: userData.id || '',
nickname: userData.nickname || '',
avatar: userData.avatar_url || '',
join_date: userData.subscribe_time ? `${new Date(userData.subscribe_time).getFullYear()}${new Date(userData.subscribe_time).getMonth() + 1}月加入` : '',
@@ -492,6 +518,39 @@ export class UserService {
return '';
}
}
// 获取职业树
static async getProfessions(): Promise<[] | PickerOption[]> {
try {
const response = await httpService.post<any>(API_CONFIG.PROFESSIONS);
const { code, data, message } = response;
if (code === 0) {
return formatOptions(data || []);
} else {
throw new Error(message || '获取职业树失败');
}
} catch (error) {
console.error('获取职业树失败:', error);
return [];
}
}
// 获取城市树
static async getCities(): Promise<[] | PickerOption[]> {
try {
const response = await httpService.post<any>(API_CONFIG.CITIS);
const { code, data, message } = response;
if (code === 0) {
return formatOptions(data || []);
} else {
throw new Error(message || '获取城市树失败');
}
} catch (error) {
console.error('获取职业树失败:', error);
return [];
}
}
}
// 从 loginService 移过来的用户相关方法

View File

@@ -1,6 +1,10 @@
import React, { useEffect, useState } from "react";
import { View, Text, Input, Button, Image } from "@tarojs/components";
import { View, Text } from "@tarojs/components";
import { useRouter } from '@tarojs/taro';
import dayjs from 'dayjs';
import Taro from '@tarojs/taro';
import httpService from "@/services/httpService";
import { TransactionType, TransactionSubType } from "@/user_pages/wallet/index";
import "./index.scss";
@@ -10,51 +14,84 @@ enum FreezeActions {
}
interface BillDetail {
id: number;
transaction_type: TransactionType;
transaction_sub_type: TransactionSubType;
freeze_action: FreezeActions;
amount: number;
description: string;
related_id: number;
create_time: string;
order_no: string;
game_title: string;
order_amount: number;
type_text: string;
sub_type_text: string;
amount_yuan: string;
id?: number;
transaction_type?: TransactionType;
transaction_sub_type?: TransactionSubType;
freeze_action?: FreezeActions;
amount?: number;
description?: string;
related_id?: number;
create_time?: string;
order_no?: string;
game_title?: string;
order_amount?: number;
type_text?: string;
sub_type_text?: string;
amount_yuan?: string;
}
const BillDetail: React.FC = () => {
const [billDetail, setBillDetail] = useState<BillDetail | null>(null);
const router = useRouter();
const { id } = router.params;
const [billDetail, setBillDetail] = useState<BillDetail>({});
const getBillDetail = async () => {
try {
const res = await httpService.post<BillDetail>("/wallet/transaction_detail", { transaction_id: id })
if (res.code === 0) {
setBillDetail(res.data);
}
} catch (error) {
console.log(error);
}
};
const copyText = (text: string | undefined) => {
if (!text) {
return;
}
Taro.setClipboardData({
data: text,
success: () => {
Taro.showToast({
title: '复制成功',
icon: 'none'
});
}
});
};
useEffect(() => {
getBillDetail();
}, [id]);
return (
<View className="bill-detail-page">
<View className="title-text-box">
<View className="title-text"> ()</View>
<View className="amount-text">
<Text>+</Text>
<Text>65.00</Text>
<Text>{billDetail.transaction_type === 'expense' ? '-' : '+'}</Text>
<Text>{billDetail.amount_yuan}</Text>
</View>
</View>
<View className="detail-wrapper">
<View className="detail-item">
<Text></Text>
<Text>2025-02-16 12:21:54</Text>
<Text>{billDetail.create_time && dayjs(billDetail.create_time).format('YYYY-MM-DD HH:mm:ss')}</Text>
</View>
<View className="detail-item">
<Text></Text>
<Text></Text>
<Text>{billDetail.game_title}</Text>
</View>
<View className="detail-item">
<Text></Text>
<Text>¥3890.00</Text>
<Text>¥{billDetail.amount}</Text>
</View>
<View className="detail-item">
<Text></Text>
<Text></Text>
<View className="with-btn-box">
<Text>89172371293791273912</Text>
<Text className="btn"></Text>
<Text>{billDetail.order_no}</Text>
<Text className="btn" onClick={() => copyText(billDetail.order_no)}></Text>
</View>
</View>
</View>

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,47 @@
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";
import httpService from "@/services/httpService";
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 {
transaction_sub_type: TransactionSubType;
date_range?: string[];
}
const DownloadBill: React.FC = () => {
const [dateRange, setDateRange] = useState({ start: "", end: "" });
const [transactionSubType, setTransactionSubType] =
useState<TransactionSubType>(TransactionSubType.All);
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();
@@ -49,17 +76,18 @@ const DownloadBill: React.FC = () => {
}
switch (range) {
case "week":
setCurrentTimeValue(new Date());
setDateType("week");
culculateDateRange("week");
break;
case "month":
setCurrentTimeValue(new Date());
setDateType("month");
culculateDateRange("month");
break;
case "custom":
setDateType("custom");
setDateRange({ start: "", end: "" });
setVisible(true);
break;
}
};
@@ -74,23 +102,136 @@ 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>({
transaction_sub_type: TransactionSubType.All,
date_range: [],
});
const handleClose = () => {
setTransactionSubType(load_transactions_params.transaction_sub_type);
setShowFilterPopup(false);
};
const handleTypeConfirm = () => {
set_load_transactions_params((prev) => {
return { ...prev, transaction_sub_type: transactionSubType };
});
setShowFilterPopup(false);
};
const handleDownloadBill = async () => {
try {
const { transaction_sub_type } = load_transactions_params;
const { start, end } = dateRange;
const date_range = [start, end];
const res = await httpService.post("/wallet/download_bill", { transaction_sub_type, date_range });
const { fileUrl, fileName } = res.data;
// 调用下载文件接口
wx.downloadFile({
url: fileUrl, // 文件路径
success: function (res) {
// 只有200状态码表示下载成功
if (res.statusCode === 200) {
// 下载成功后可以使用res.tempFilePath访问临时文件路径
console.log('文件下载成功,临时路径为:', res.tempFilePath);
// 保存文件到本地
wx.openDocument({
filePath: res.tempFilePath,
showMenu: true // 显示保存菜单
});
}
},
fail: function (err) {
console.error('文件下载失败:', err);
}
});
} catch (error) {
console.error(error);
}
};
return (
<View className="download_bill_page">
<View className="hint_content">
<Text> </Text>
<Text className="button_text"></Text>
{/* <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>
<Text>
{
transaction_type_options.find(
(item) =>
item.value === load_transactions_params.transaction_sub_type
)?.label
}
</Text>
</View>
</View>
<View className="form_item">
@@ -105,9 +246,8 @@ const DownloadBill: React.FC = () => {
</View>
<View
className={`option_button ${
dateType === "month" ? "active" : ""
}`}
className={`option_button ${dateType === "month" ? "active" : ""
}`}
onClick={() => {
selectDateRange("month");
}}
@@ -115,9 +255,8 @@ const DownloadBill: React.FC = () => {
</View>
<View
className={`option_button ${
dateType === "custom" ? "active" : ""
}`}
className={`option_button ${dateType === "custom" ? "active" : ""
}`}
onClick={() => {
selectDateRange("custom");
}}
@@ -126,11 +265,28 @@ 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
@@ -141,7 +297,9 @@ const DownloadBill: React.FC = () => {
>
</Text>
<Button className="download_button"></Button>
<Button className="download_button" onClick={handleDownloadBill}>
</Button>
</View>
{visible && (
<DialogCalendarCard
@@ -152,6 +310,72 @@ 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={handleClose}
onConfirm={handleTypeConfirm}
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={
transactionSubType === option.value
? "option_item active"
: "option_item"
}
key={option.value}
onClick={() => {
setTransactionSubType(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,75 @@
import React, { useState, useEffect } from "react";
import { View } from "@tarojs/components";
import { View, Text } from "@tarojs/components";
import "./index.scss";
import httpService from "@/services/httpService";
import Taro from "@tarojs/taro";
interface BillRecord {
id: number;
file_name: string;
download_url: string;
file_size: number;
create_time: string;
expire_time: string;
bill_date_range_start: string;
bill_date_range_end: string;
bill_transaction_type: string;
bill_transaction_sub_type: string;
date_range_desc: string;
transaction_type_desc: string;
transaction_sub_type_desc: string;
}
const DownloadBillRecords: React.FC = () => {
const [records, setRecords] = useState<BillRecord[]>([]);
const [params, setParams] = useState({
page: 1,
limit: 20,
});
useEffect(() => {
fetchRecords();
}, []);
const fetchRecords = async () => {
try {
const response = await httpService.post<{ rows: BillRecord[] }>('/wallet/download_history', params);
setRecords(response.data.rows);
} catch (error) {
console.log(error);
Taro.showToast({
title: '获取账单记录失败',
icon: 'none',
duration: 2000,
});
}
};
return (
<View></View>
<View className="download-bill-records-page">
<View className="records-container">
{
records.map((record) => (
<View className="record-item" key={record.id}>
<View className="title-text">{record.file_name}</View>
<View className="info-item">
<Text></Text>
<Text>{record.create_time}</Text>
</View>
<View className="info-item">
<Text></Text>
<Text>{record.date_range_desc}</Text>
</View>
<View className="info-item">
<Text></Text>
<Text className="btn"></Text>
</View>
</View>
))
}
</View>
<View className="tips">7</View>
</View>
);
};

View File

@@ -1,50 +1,50 @@
import React, { useState, useEffect } from 'react';
import { View, Text, Image, ScrollView, Button } from '@tarojs/components';
import { PopupPicker } from '@/components/Picker/index'
import Taro from '@tarojs/taro';
import './index.scss';
import { UserInfo } from '@/components/UserInfo';
import { UserService } from '@/services/userService';
import { clear_login_state } from '@/services/loginService';
import { convert_db_gender_to_display } from '@/utils/genderUtils';
import { EditModal } from '@/components';
import React, { useState, useEffect } from "react";
import { View, Text, Image, ScrollView, Button } from "@tarojs/components";
import { PopupPicker } from "@/components/Picker/index";
import Taro from "@tarojs/taro";
import "./index.scss";
import { UserInfo } from "@/components/UserInfo";
import { UserService, PickerOption } from "@/services/userService";
import { clear_login_state } from "@/services/loginService";
import { convert_db_gender_to_display } from "@/utils/genderUtils";
import { EditModal } from "@/components";
import img from "@/config/images";
const EditProfilePage: React.FC = () => {
// 用户信息状态
const [user_info, setUserInfo] = useState<UserInfo>({
id: '1',
nickname: '加载中...',
avatar: require('@/static/userInfo/default_avatar.svg'),
join_date: '加载中...',
id: "1",
nickname: "加载中...",
avatar: require("@/static/userInfo/default_avatar.svg"),
join_date: "加载中...",
stats: {
following: 0,
friends: 0,
hosted: 0,
participated: 0
participated: 0,
},
personal_profile: '加载中...',
occupation: '加载中...',
ntrp_level: 'NTRP 3.0',
phone: '',
gender: '',
country: '',
province: '',
city: '',
personal_profile: "加载中...",
occupation: "加载中...",
ntrp_level: "NTRP 3.0",
phone: "",
gender: "",
country: "",
province: "",
city: "",
});
// 表单状态
const [form_data, setFormData] = useState({
nickname: '',
personal_profile: '',
occupation: '',
ntrp_level: '4.0',
phone: '',
gender: '',
birthday: '2000-01-01',
country: '',
province: '',
city: ''
nickname: "",
personal_profile: "",
occupation: "",
ntrp_level: "4.0",
phone: "",
gender: "",
birthday: "2000-01-01",
country: "",
province: "",
city: "",
});
// 加载状态
@@ -52,18 +52,44 @@ const EditProfilePage: React.FC = () => {
// 编辑弹窗状态
const [edit_modal_visible, setEditModalVisible] = useState(false);
const [editing_field, setEditingField] = useState<string>('');
const [editing_field, setEditingField] = useState<string>("");
const [gender_picker_visible, setGenderPickerVisible] = useState(false);
const [birthday_picker_visible, setBirthdayPickerVisible] = useState(false);
const [location_picker_visible, setLocationPickerVisible] = useState(false);
const [ntrp_picker_visible, setNtrpPickerVisible] = useState(false);
const [occupation_picker_visible, setOccupationPickerVisible] = useState(false);
const [occupation_picker_visible, setOccupationPickerVisible] =
useState(false);
// 职业数据
const [professions, setProfessions] = useState<PickerOption[]>([]);
// 城市数据
const [cities, setCities] = useState<PickerOption[]>([]);
// 页面加载时初始化数据
useEffect(() => {
load_user_info();
getProfessions();
getCities();
}, []);
const getProfessions = async () => {
try {
const res = await UserService.getProfessions();
setProfessions(res);
} catch (e) {
console.log("获取职业失败:", e);
}
};
const getCities = async () => {
try {
const res = await UserService.getCities();
setCities(res);
} catch (e) {
console.log("获取职业失败:", e);
}
};
// 加载用户信息
const load_user_info = async () => {
try {
@@ -71,23 +97,23 @@ const EditProfilePage: React.FC = () => {
const user_data = await UserService.get_user_info();
setUserInfo(user_data);
setFormData({
nickname: user_data.nickname || '',
personal_profile: user_data.personal_profile || '',
occupation: user_data.occupation || '',
ntrp_level: user_data.ntrp_level || 'NTRP 4.0',
phone: user_data.phone || '',
gender: user_data.gender || '',
birthday: user_data.birthday || '',
country: user_data.country || '',
province: user_data.province || '',
city: user_data.city || ''
nickname: user_data.nickname || "",
personal_profile: user_data.personal_profile || "",
occupation: user_data.occupation || "",
ntrp_level: user_data.ntrp_level || "NTRP 4.0",
phone: user_data.phone || "",
gender: user_data.gender || "",
birthday: user_data.birthday || "",
country: user_data.country || "",
province: user_data.province || "",
city: user_data.city || "",
});
} catch (error) {
console.error('加载用户信息失败:', error);
console.error("加载用户信息失败:", error);
Taro.showToast({
title: '加载用户信息失败',
icon: 'error',
duration: 2000
title: "加载用户信息失败",
icon: "error",
duration: 2000,
});
} finally {
setLoading(false);
@@ -98,51 +124,51 @@ const EditProfilePage: React.FC = () => {
const handle_avatar_upload = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
sizeType: ["compressed"],
sourceType: ["album", "camera"],
success: async (res) => {
const tempFilePath = res.tempFilePaths[0];
try {
const avatar_url = await UserService.upload_avatar(tempFilePath);
setUserInfo(prev => ({ ...prev, avatar: avatar_url }));
setUserInfo((prev) => ({ ...prev, avatar: avatar_url }));
Taro.showToast({
title: '头像上传成功',
icon: 'success'
title: "头像上传成功",
icon: "success",
});
} catch (error) {
console.error('头像上传失败:', error);
console.error("头像上传失败:", error);
Taro.showToast({
title: '头像上传失败',
icon: 'none'
title: "头像上传失败",
icon: "none",
});
}
}
},
});
};
// 处理编辑弹窗
const handle_open_edit_modal = (field: string) => {
if (field === 'gender') {
if (field === "gender") {
setGenderPickerVisible(true);
return;
}
if (field === 'birthday') {
if (field === "birthday") {
setBirthdayPickerVisible(true);
return;
}
if (field === 'location') {
if (field === "location") {
setLocationPickerVisible(true);
return;
}
if (field === 'ntrp_level') {
if (field === "ntrp_level") {
setNtrpPickerVisible(true);
return;
}
if (field === 'occupation') {
if (field === "occupation") {
setOccupationPickerVisible(true);
return;
}
if (field === 'nickname') {
if (field === "nickname") {
// 手动输入
setEditingField(field);
setEditModalVisible(true);
@@ -159,60 +185,67 @@ const EditProfilePage: React.FC = () => {
await UserService.update_user_info(update_data);
// 更新本地状态
setFormData(prev => ({ ...prev, [editing_field]: value }));
setUserInfo(prev => ({ ...prev, [editing_field]: value }));
setFormData((prev) => ({ ...prev, [editing_field]: value }));
setUserInfo((prev) => ({ ...prev, [editing_field]: value }));
// 关闭弹窗
setEditModalVisible(false);
setEditingField('');
setEditingField("");
// 显示成功提示
Taro.showToast({
title: '保存成功',
icon: 'success'
title: "保存成功",
icon: "success",
});
} catch (error) {
console.error('保存失败:', error);
console.error("保存失败:", error);
Taro.showToast({
title: '保存失败',
icon: 'error'
title: "保存失败",
icon: "error",
});
}
};
const handle_edit_modal_cancel = () => {
setEditModalVisible(false);
setEditingField('');
setEditingField("");
};
// 处理字段编辑
const handle_field_edit = async (field: string | { [key: string]: string }, value?: string) => {
const handle_field_edit = async (
field: string | { [key: string]: string },
value?: string
) => {
try {
if (typeof field === 'object' && field !== null && !Array.isArray(field)) {
if (
typeof field === "object" &&
field !== null &&
!Array.isArray(field)
) {
await UserService.update_user_info({ ...field });
// 更新本地状态
setFormData(prev => ({ ...prev, ...field }));
setUserInfo(prev => ({ ...prev, ...field }));
setFormData((prev) => ({ ...prev, ...field }));
setUserInfo((prev) => ({ ...prev, ...field }));
} else {
// 调用更新用户信息接口,只传递修改的字段
const update_data = { [field as string]: value };
await UserService.update_user_info(update_data);
// 更新本地状态
setFormData(prev => ({ ...prev, [field as string]: value }));
setUserInfo(prev => ({ ...prev, [field as string]: value }));
setFormData((prev) => ({ ...prev, [field as string]: value }));
setUserInfo((prev) => ({ ...prev, [field as string]: value }));
}
// 显示成功提示
Taro.showToast({
title: '保存成功',
icon: 'success'
title: "保存成功",
icon: "success",
});
} catch (error) {
console.error('保存失败:', error);
console.error("保存失败:", error);
Taro.showToast({
title: '保存失败',
icon: 'error'
title: "保存失败",
icon: "error",
});
}
};
@@ -220,13 +253,19 @@ const EditProfilePage: React.FC = () => {
// 处理性别选择
const handle_gender_change = (e: any) => {
const gender_value = e[0];
handle_field_edit('gender', gender_value);
handle_field_edit("gender", gender_value);
};
// 处理生日选择
const handle_birthday_change = (e: any) => {
const [year, month, day] = e;
handle_field_edit('birthday', `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`);
handle_field_edit(
"birthday",
`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(
2,
"0"
)}`
);
};
// 处理地区选择
@@ -238,61 +277,66 @@ const EditProfilePage: React.FC = () => {
// 处理NTRP水平选择
const handle_ntrp_level_change = (e: any) => {
const ntrp_level_value = e[0];
handle_field_edit('ntrp_level', ntrp_level_value);
handle_field_edit("ntrp_level", ntrp_level_value);
};
// 处理职业选择
const handle_occupation_change = (e: any) => {
const [country, province] = e;
handle_field_edit('occupation', `${country} ${province}`);
handle_field_edit("occupation", `${country} ${province}`);
};
// 处理退出登录
const handle_logout = () => {
Taro.showModal({
title: '确认退出',
content: '确定要退出登录吗?',
title: "确认退出",
content: "确定要退出登录吗?",
success: (res) => {
if (res.confirm) {
// 清除用户数据
clear_login_state();
Taro.reLaunch({
url: '/login_pages/index/index'
url: "/login_pages/index/index",
});
}
}
},
});
};
const onGetPhoneNumber = async (e) => {
if (!e.detail || !e.detail.code) {
Taro.showToast({
title: '获取手机号失败,请重试',
icon: 'none',
duration: 2000
title: "获取手机号失败,请重试",
icon: "none",
duration: 2000,
});
return;
}
try {
const phone = await UserService.parse_phone(e.detail.code);
handle_field_edit('phone', phone);
handle_field_edit("phone", phone);
} catch (e) {
console.error('解析手机号失败:', e);
console.error("解析手机号失败:", e);
Taro.showToast({
title: '解析手机号失败,请重试',
icon: 'none',
duration: 2000
title: "解析手机号失败,请重试",
icon: "none",
duration: 2000,
});
}
}
};
return (
<View className="edit_profile_page">
{/* 导航栏 */}
<View className="custom-navbar">
<View className="detail-navigator">
<View className="detail-navigator-back" onClick={() => { Taro.navigateBack() }}>
<View
className="detail-navigator-back"
onClick={() => {
Taro.navigateBack();
}}
>
<Image
className="detail-navigator-back-icon"
src={img.ICON_NAVIGATOR_BACK}
@@ -315,26 +359,35 @@ const EditProfilePage: React.FC = () => {
<View className="avatar_overlay">
<Image
className="upload_icon"
src={require('@/static/userInfo/edit2.svg')}
src={require("@/static/userInfo/edit2.svg")}
/>
</View>
</View>
</View>
{/* 基本信息编辑 */}
<View className="form_section">
{/* 名字 */}
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('nickname')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("nickname")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/user.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/user.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.nickname || '188的王晨'}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Text className="item_value">
{form_data.nickname || "188的王晨"}
</Text>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
@@ -342,14 +395,25 @@ const EditProfilePage: React.FC = () => {
{/* 性别 */}
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('gender')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("gender")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/gender.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/gender.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{convert_db_gender_to_display(form_data.gender)}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Text className="item_value">
{convert_db_gender_to_display(form_data.gender)}
</Text>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
@@ -357,14 +421,23 @@ const EditProfilePage: React.FC = () => {
{/* 生日 */}
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('birthday')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("birthday")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/birthday.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/birthday.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.birthday}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
</View>
@@ -373,16 +446,26 @@ const EditProfilePage: React.FC = () => {
{/* 简介编辑 */}
<View className="form_section">
<View className="form_group">
<View className="form_item" onClick={() => handle_open_edit_modal('personal_profile')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("personal_profile")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/introduce.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/introduce.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">
{form_data.personal_profile.replace(/\n/g, ' ') || '介绍一下自己'}
{form_data.personal_profile.replace(/\n/g, " ") ||
"介绍一下自己"}
</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
</View>
@@ -392,40 +475,67 @@ const EditProfilePage: React.FC = () => {
<View className="form_section">
<View className="form_group">
{/* 地区 */}
<View className="form_item" onClick={() => handle_open_edit_modal('location')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("location")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/gender.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/gender.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{`${form_data.country} ${form_data.province} ${form_data.city}`}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
{/* NTRP水平 */}
<View className="form_item" onClick={() => handle_open_edit_modal('ntrp_level')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("ntrp_level")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/ball.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/ball.svg")}
/>
<Text className="item_label">NTRP </Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.ntrp_level}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
{/* 职业 */}
<View className="form_item" onClick={() => handle_open_edit_modal('occupation')}>
<View
className="form_item"
onClick={() => handle_open_edit_modal("occupation")}
>
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/business.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/business.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.occupation}</Text>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
</View>
@@ -436,7 +546,10 @@ const EditProfilePage: React.FC = () => {
<View className="form_group">
<View className="form_item">
<View className="item_left">
<Image className="item_icon" src={require('@/static/userInfo/phone.svg')} />
<Image
className="item_icon"
src={require("@/static/userInfo/phone.svg")}
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
@@ -448,8 +561,17 @@ const EditProfilePage: React.FC = () => {
onInput={handle_phone_input}
onBlur={handle_phone_blur}
/> */}
<Button className={form_data.phone ? '' : 'placeholer'} openType='getPhoneNumber' onGetPhoneNumber={onGetPhoneNumber}>{form_data.phone || '未绑定'}</Button>
<Image className="arrow_icon" src={require('@/static/list/icon-list-right-arrow.svg')} />
<Button
className={form_data.phone ? "" : "placeholer"}
openType="getPhoneNumber"
onGetPhoneNumber={onGetPhoneNumber}
>
{form_data.phone || "未绑定"}
</Button>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
/>
</View>
</View>
<View className="divider"></View>
@@ -477,63 +599,94 @@ const EditProfilePage: React.FC = () => {
<EditModal
visible={edit_modal_visible}
type={editing_field}
title={editing_field === 'nickname' ? '编辑名字' : '编辑简介'}
placeholder={editing_field === 'nickname' ? '请输入您的名字' : '介绍一下你的喜好,或者训练习惯'}
initialValue={form_data[editing_field as keyof typeof form_data] || ''}
maxLength={editing_field === 'nickname' ? 20 : 100}
title={editing_field === "nickname" ? "编辑名字" : "编辑简介"}
placeholder={
editing_field === "nickname"
? "请输入您的名字"
: "介绍一下你的喜好,或者训练习惯"
}
initialValue={form_data[editing_field as keyof typeof form_data] || ""}
maxLength={editing_field === "nickname" ? 20 : 100}
onSave={handle_edit_modal_save}
onCancel={handle_edit_modal_cancel}
validationMessage={editing_field === 'nickname' ? '请填写 1-20 个字符' : '请填写 2-100 个字符'}
validationMessage={
editing_field === "nickname"
? "请填写 1-20 个字符"
: "请填写 2-100 个字符"
}
/>
{/* 性别选择弹窗 */}
{gender_picker_visible && <PopupPicker
options={[
[{ text: '男', value: '0' },
{ text: '女', value: '1' },
{ text: '保密', value: '2' }
]]}
visible={gender_picker_visible}
setvisible={setGenderPickerVisible}
value={[form_data.gender]}
onChange={handle_gender_change} />}
{gender_picker_visible && (
<PopupPicker
options={[
[
{ text: "男", value: "0" },
{ text: "女", value: "1" },
{ text: "保密", value: "2" },
],
]}
visible={gender_picker_visible}
setvisible={setGenderPickerVisible}
value={[form_data.gender]}
onChange={handle_gender_change}
/>
)}
{/* 生日选择弹窗 */}
{birthday_picker_visible && <PopupPicker
visible={birthday_picker_visible}
setvisible={setBirthdayPickerVisible}
value={[new Date(form_data.birthday).getFullYear(), new Date(form_data.birthday).getMonth() + 1, new Date(form_data.birthday).getDate()]}
type="day"
onChange={handle_birthday_change} />}
{birthday_picker_visible && (
<PopupPicker
visible={birthday_picker_visible}
setvisible={setBirthdayPickerVisible}
value={[
new Date(form_data.birthday).getFullYear(),
new Date(form_data.birthday).getMonth() + 1,
new Date(form_data.birthday).getDate(),
]}
type="day"
onChange={handle_birthday_change}
/>
)}
{/* 地区选择弹窗 */}
{location_picker_visible && <PopupPicker
options={[[{ text: "中国", value: "中国" }], [{ text: "上海", value: "上海" }], [{ text: "浦东新区", value: "浦东新区" }, {text: "静安区", value: "静安区"}]]}
visible={location_picker_visible}
setvisible={setLocationPickerVisible}
value={[form_data.country, form_data.province, form_data.city]}
onChange={handle_location_change} />}
{location_picker_visible && (
<PopupPicker
options={cities}
visible={location_picker_visible}
setvisible={setLocationPickerVisible}
value={[form_data.country, form_data.province, form_data.city]}
onChange={handle_location_change}
/>
)}
{/* NTRP水平选择弹窗 */}
{ntrp_picker_visible && <PopupPicker
options={[
[{ text: '1.5', value: '1.5' },
{ text: '2.0', value: '2.0' },
{ text: '2.5', value: '2.5' },
{ text: '3.0', value: '3.0' },
{ text: '3.5', value: '3.5' },
{ text: '4.0', value: '4.0' },
{ text: '4.5', value: '4.5' },
]]}
type="ntrp"
img={user_info.avatar}
visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible}
value={[form_data.ntrp_level]}
onChange={handle_ntrp_level_change} />}
{ntrp_picker_visible && (
<PopupPicker
options={[
[
{ text: "1.5", value: "1.5" },
{ text: "2.0", value: "2.0" },
{ text: "2.5", value: "2.5" },
{ text: "3.0", value: "3.0" },
{ text: "3.5", value: "3.5" },
{ text: "4.0", value: "4.0" },
{ text: "4.5", value: "4.5" },
],
]}
type="ntrp"
img={user_info.avatar}
visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible}
value={[form_data.ntrp_level]}
onChange={handle_ntrp_level_change}
/>
)}
{/* 职业选择弹窗 */}
{occupation_picker_visible && <PopupPicker
options={[[{ text: "时尚", value: "时尚" }], [{ text: "美妆博主", value: "美妆博主" },{ text: "设计师", value: "设计师" }]]}
visible={occupation_picker_visible}
setvisible={setOccupationPickerVisible}
value={[...form_data.occupation.split(' ')]}
onChange={handle_occupation_change} />}
{occupation_picker_visible && (
<PopupPicker
options={professions}
visible={occupation_picker_visible}
setvisible={setOccupationPickerVisible}
value={[...form_data.occupation.split(" ")]}
onChange={handle_occupation_change}
/>
)}
</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

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '设置交易密码',
})

View File

@@ -0,0 +1,60 @@
.set-transaction-password-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20px;
.form-item {
height: 50px;
display: flex;
gap: 10px;
align-items: center;
border-bottom: 1px solid #0000000D;
font-size: 14px;
.form-label {
width: 56px;
text-align: right;
}
}
.tips {
font-family: PingFang SC;
font-weight: 400;
font-style: Regular;
font-size: 12px;
line-height: 18px;
letter-spacing: 0px;
vertical-align: middle;
color: #3C3C4366;
}
.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;
}
.bottom-btn {
position: fixed;
bottom: 40px;
height: 54px;
width: calc(100vw - 40px);
margin: 0 auto;
border-radius: 16px;
font-size: 16px;
font-weight: 600;
}
}

View File

@@ -0,0 +1,114 @@
import React, { useState, useEffect } from "react";
import Taro, { useRouter } from '@tarojs/taro';
import { View, Text, Input, Button } from "@tarojs/components";
import "./index.scss";
import httpService from "@/services/httpService";
interface FormFields {
old_password?: string;
new_password: string;
confirm_password: string;
sms_code?: string;
}
const SetTransactionPassword: React.FC = () => {
const [handleType, setHandleType] = useState("set");
const router = useRouter();
const { type, phone, sms_code } = router.params;
useEffect(() => {
if (type) {
setHandleType(type);
}
}, [type]);
const [formData, setFormData] = useState<FormFields>({
old_password: "",
new_password: "",
confirm_password: "",
sms_code: "",
});
const handleInput = (e: any, field: string) => {
setFormData({ ...formData, [field]: e.detail.value });
};
const handleConfirm = async () => {
const { new_password, confirm_password } = formData;
if (new_password !== confirm_password) {
Taro.showToast({
title: "两次密码输入不一致",
icon: "none",
});
return;
}
if (handleType === "set") {
const { sms_code } = formData;
try {
await httpService.post("/wallet/set_payment_password", { password: new_password, sms_code });
Taro.showToast({
title: "设置交易密码成功",
icon: "success",
});
Taro.navigateBack();
} catch (error) {
Taro.showToast({
title: "设置交易密码失败",
icon: "none",
});
return;
}
} else if (handleType === "reset") {
// const { old_password } = formData;
try {
await httpService.post("/wallet/reset_payment_password", { phone, new_password, sms_code });
Taro.showToast({
title: "修改交易密码成功",
icon: "success",
});
Taro.navigateBack();
} catch (error) {
Taro.showToast({
title: "修改交易密码失败",
icon: "none",
});
return;
}
}
};
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>
// )
}
<View className="form-item">
<Text className="form-label"></Text>
<Input placeholder="请输入交易密码" password type="number" maxlength={6} onInput={(e) => { handleInput(e, "new_password") }}></Input>
</View>
<View className="form-item">
<Text className="form-label"></Text>
<Input placeholder="请再次输入交易密码" password type="number" maxlength={6} onInput={(e) => { handleInput(e, "confirm_password") }}></Input>
</View>
{
handleType === "set" && (
<View className="form-item">
<Text className="form-label"></Text>
<Input placeholder="请输入验证码" type="number" onInput={(e) => { handleInput(e, "sms_code") }}></Input>
<Button className="btn" ></Button>
</View>
)
}
<Text className="tips">* 6</Text>
<Button className="btn bottom-btn" onClick={handleConfirm}></Button>
</View>
);
};
export default SetTransactionPassword;

View File

@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '验证手机号',
})

View File

@@ -0,0 +1,49 @@
.set-transaction-password-page {
min-height: 100vh;
background-color: #f5f5f5;
padding: 20px;
.form-item {
height: 50px;
display: flex;
gap: 10px;
align-items: center;
border-bottom: 1px solid #0000000D;
font-size: 14px;
.form-label {
width: 56px;
text-align: right;
}
}
.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;
}
.bottom-btn {
position: fixed;
bottom: 40px;
height: 54px;
width: calc(100vw - 40px);
margin: 0 auto;
border-radius: 16px;
font-size: 16px;
font-weight: 600;
}
}

View File

@@ -0,0 +1,77 @@
import React, { useState, useEffect } from "react";
import Taro from '@tarojs/taro';
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;
sms_code?: string;
}
const ValidPhone: React.FC = () => {
const userInfo = useUserInfo();
const [formData, setFormData] = useState<FormFields>({
phone: userInfo.phone || "",
sms_code: "",
});
const handleInput = (e: any, field: string) => {
setFormData({ ...formData, [field]: e.detail.value });
};
const handleConfirm = async () => {
const isValid = await validSMSCode();
if (isValid) {
Taro.navigateTo({ url: `/user_pages/setTransactionPassword/index?type=reset&phone=${formData.phone}&sms_code=${formData.sms_code}` });
}
};
const validSMSCode = async () => {
const { phone, sms_code } = formData;
try {
const res = await httpService.post("/wallet/verify_sms_code", { phone, sms_code, type: "reset_password" });
const { verified } = res.data;
if (verified) {
return true;
} else {
Taro.showToast({ title: "验证码校验失败", icon: "none" });
return false;
}
} catch (error) {
console.log(error);
Taro.showToast({ title: "验证码校验失败", icon: "none" });
return false;
}
};
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={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" onClick={getSMSCode}></Button>
</View>
<Button className="btn bottom-btn" disabled={!formData.sms_code} onClick={handleConfirm}></Button>
</View>
);
};
export default ValidPhone;

View File

@@ -146,7 +146,6 @@
border: 0.5px solid #EBEBEB;
border-radius: 20px;
box-shadow: 0px 0px 36px 0px rgba(0, 0, 0, 0.1);
overflow: hidden;
.history_header {
display: flex;
@@ -154,6 +153,9 @@
align-items: center;
padding: 12px 20px;
border-bottom: 0.5px solid rgba(120, 120, 128, 0.12);
position: sticky;
top: 0;
background-color: #fff;
.history_title {
font-size: 16px;

View File

@@ -5,6 +5,7 @@ import "./index.scss";
import { CommonPopup } from "@/components";
import httpService from "@/services/httpService";
import { withAuth } from "@/components";
import { PopupPicker } from "@/components/Picker/index";
// 交易记录类型
interface Transaction {
@@ -103,6 +104,7 @@ const WalletPage: React.FC = () => {
const [show_withdraw_popup, set_show_withdraw_popup] = useState(false);
const [withdraw_amount, set_withdraw_amount] = useState("");
const [submitting, set_submitting] = useState(false);
const [password_status, set_password_status] = useState(false);
// 交易记录状态
const [transactions, set_transactions] = useState<Transaction[]>([]);
@@ -110,6 +112,16 @@ const WalletPage: React.FC = () => {
// 交易记录过滤状态
const [showFilterPopup, setShowFilterPopup] = useState(false);
const [showMonthPicker, setShowMonthPicker] = useState(false);
const [filterParams, setFilterParams] = useState({
type: TransactionType.All,
transaction_sub_type: TransactionSubType.All,
});
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0");
const [load_transactions_params, set_load_transactions_params] =
useState<TransactionLoadParams>({
@@ -118,15 +130,40 @@ const WalletPage: React.FC = () => {
type: TransactionType.All,
transaction_sub_type: TransactionSubType.All,
keyword: "",
date: "",
date: `${year}-${month}`
});
useEffect(() => {
load_transactions();
}, [load_transactions_params]);
// 页面显示时加载数据
useDidShow(() => {
load_wallet_data();
load_transactions();
check_password_status();
});
const modify_load_transactions_params = () => {
const { type, transaction_sub_type } = filterParams;
set_load_transactions_params((prev) => {
return {
...prev,
type,
transaction_sub_type,
}
})
};
const check_password_status = async () => {
try {
const res = await httpService.post("/wallet/check_password_status");
set_password_status(res.data.is_password_set);
} catch (e) {
console.error("检查交易密码状态失败:", e);
}
}
// 加载钱包数据
const load_wallet_data = async () => {
try {
@@ -171,11 +208,10 @@ const WalletPage: React.FC = () => {
// 加载交易记录
const load_transactions = async () => {
setShowFilterPopup(false);
set_load_transactions_params({ ...load_transactions_params, page: 1 });
// set_load_transactions_params({ ...load_transactions_params, page: 1 });
try {
set_loading_transactions(true);
console.log("开始加载交易记录...");
const response = await httpService.post("/wallet/transactions", {
...load_transactions_params,
});
@@ -216,8 +252,24 @@ 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,
});
};
// 处理提现
const handle_withdraw = () => {
if (password_status) {
navigateToSetTransactionPassword("set");
return;
}
if (wallet_info.balance <= 0) {
Taro.showToast({
title: "余额不足",
@@ -226,7 +278,10 @@ const WalletPage: React.FC = () => {
});
return;
}
set_show_withdraw_popup(true);
Taro.navigateTo({
url: "/user_pages/withdrawal/index",
});
// set_show_withdraw_popup(true);
};
// 提交提现申请
@@ -297,6 +352,7 @@ const WalletPage: React.FC = () => {
// 格式化时间显示
const format_time = (time: string) => {
time = time.replace(/-/g, "/");
const date = new Date(time);
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
@@ -350,6 +406,14 @@ const WalletPage: React.FC = () => {
setShowFilterPopup(true);
};
const handleFilterCancel = () => {
setShowFilterPopup(false);
setFilterParams({
type: load_transactions_params.type,
transaction_sub_type: load_transactions_params.transaction_sub_type,
});
};
return (
<View className="wallet_page">
{/* 钱包主卡片 */}
@@ -357,7 +421,7 @@ const WalletPage: React.FC = () => {
{/* 头部信息 */}
<View className="card_header">
<Text className="header_title"></Text>
<Text className="modify_password"></Text>
<Text className="modify_password" onClick={() => navigateToSetTransactionPassword("reset")}></Text>
</View>
{/* 余额显示 */}
@@ -420,7 +484,8 @@ const WalletPage: React.FC = () => {
/>
<Text className="function_text"></Text>
</View>
<View className="function_item">
{/* TODO 客服中心 */}
<View className="function_item" onClick={() => Taro.navigateTo({ url: "/user_pages/validPhone/index" })}>
<Image
className="function_icon"
src={require("@/static/wallet/custom-service.svg")}
@@ -434,8 +499,8 @@ const WalletPage: React.FC = () => {
{/* 标题栏 */}
<View className="history_header">
<Text className="history_title"></Text>
<View className="month_selector">
<Text className="current_month">2025-09</Text>
<View className="month_selector" onClick={() => setShowMonthPicker(true)}>
<Text className="current_month">{load_transactions_params.date}</Text>
</View>
</View>
@@ -526,12 +591,30 @@ const WalletPage: React.FC = () => {
</View>
</View>
</CommonPopup>
{/* 选择月份弹窗 */}
{showMonthPicker && (
<PopupPicker
visible={showMonthPicker}
setvisible={setShowMonthPicker}
value={[
Number(load_transactions_params.date!.split("-")[0]),
Number(load_transactions_params.date!.split("-")[1])
]}
type="month"
onChange={(e) => {
const [year, month] = e;
set_load_transactions_params({
...load_transactions_params,
date: `${year}-${String(month).padStart(2, "0")}`,
});
}}
/>
)}
{/* 筛选账单弹窗 */}
<CommonPopup
visible={showFilterPopup}
onClose={() => setShowFilterPopup(false)}
onConfirm={load_transactions}
onClose={handleFilterCancel}
onConfirm={modify_load_transactions_params}
title="选择筛选项"
className="filter_popup"
>
@@ -544,14 +627,14 @@ const WalletPage: React.FC = () => {
(option: Option<TransactionType>) => (
<View
className={
load_transactions_params.type === option.value
filterParams.type === option.value
? "option_item active"
: "option_item"
}
key={option.value}
onClick={() => {
set_load_transactions_params({
...load_transactions_params,
setFilterParams({
...filterParams,
type: option.value,
});
}}
@@ -569,15 +652,15 @@ const WalletPage: React.FC = () => {
(option: Option<TransactionSubType>) => (
<View
className={
load_transactions_params.transaction_sub_type ===
option.value
filterParams.transaction_sub_type ===
option.value
? "option_item active"
: "option_item"
}
key={option.value}
onClick={() => {
set_load_transactions_params({
...load_transactions_params,
setFilterParams({
...filterParams,
transaction_sub_type: option.value,
});
}}

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;
.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,283 @@
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;