合并代码
This commit is contained in:
@@ -30,6 +30,9 @@ export default defineAppConfig({
|
||||
"downloadBill/index", // 下载账单
|
||||
"downloadBillRecords/index", // 下载账单记录
|
||||
"billDetail/index", // 账单详情
|
||||
"setTransactionPassword/index", // 设置交易密码
|
||||
"validPhone/index", // 验证手机号
|
||||
"withdrawal/index", // 提现
|
||||
],
|
||||
},
|
||||
// {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
// 请求拦截器配置
|
||||
|
||||
@@ -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 移过来的用户相关方法
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -341,6 +341,11 @@ const QueryTransactions = () => {
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{
|
||||
transactions.length > 0 && (
|
||||
<View className="tips_text">仅支持查找2024年9月1日以后的账单</View>
|
||||
)
|
||||
}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
|
||||
3
src/user_pages/setTransactionPassword/index.config.ts
Normal file
3
src/user_pages/setTransactionPassword/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '设置交易密码',
|
||||
})
|
||||
60
src/user_pages/setTransactionPassword/index.scss
Normal file
60
src/user_pages/setTransactionPassword/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
114
src/user_pages/setTransactionPassword/index.tsx
Normal file
114
src/user_pages/setTransactionPassword/index.tsx
Normal 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;
|
||||
3
src/user_pages/validPhone/index.config.ts
Normal file
3
src/user_pages/validPhone/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '验证手机号',
|
||||
})
|
||||
49
src/user_pages/validPhone/index.scss
Normal file
49
src/user_pages/validPhone/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
77
src/user_pages/validPhone/index.tsx
Normal file
77
src/user_pages/validPhone/index.tsx
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}}
|
||||
|
||||
3
src/user_pages/withdrawal/index.config.ts
Normal file
3
src/user_pages/withdrawal/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '钱包',
|
||||
})
|
||||
144
src/user_pages/withdrawal/index.scss
Normal file
144
src/user_pages/withdrawal/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
283
src/user_pages/withdrawal/index.tsx
Normal file
283
src/user_pages/withdrawal/index.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user