日期范围选择器、账单筛选
This commit is contained in:
@@ -28,6 +28,8 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
||||
const [selectedBackup, setSelectedBackup] = useState<Date[]>(
|
||||
Array.isArray(value) ? [...(value as Date[])] : [value as Date]
|
||||
);
|
||||
const [current, setCurrent] = useState<Date>(new Date());
|
||||
const [delta, setDelta] = useState(0);
|
||||
const calendarRef = useRef<CalendarUIRef>(null);
|
||||
const [type, setType] = useState<"year" | "month" | "time">("year");
|
||||
const [selectedHour, setSelectedHour] = useState(8);
|
||||
@@ -56,11 +58,11 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
||||
setPendingJump({ year, month });
|
||||
setType("year");
|
||||
if (searchType === "range") {
|
||||
const delta = calculateMonthDifference(
|
||||
selected as Date,
|
||||
calculateMonthDifference(
|
||||
current,
|
||||
new Date(year, month - 1, 1)
|
||||
);
|
||||
calendarRef.current?.gotoMonth(delta);
|
||||
|
||||
return;
|
||||
}
|
||||
setSelected(new Date(year, month - 1, 1));
|
||||
@@ -77,10 +79,10 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
||||
const minutes = minute.toString().padStart(2, "0");
|
||||
const finalDate = new Date(
|
||||
dayjs(selected as Date).format("YYYY-MM-DD") +
|
||||
" " +
|
||||
hours +
|
||||
":" +
|
||||
minutes
|
||||
" " +
|
||||
hours +
|
||||
":" +
|
||||
minutes
|
||||
);
|
||||
if (onChange) onChange(finalDate);
|
||||
}
|
||||
@@ -92,12 +94,11 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
||||
if (!(date1 instanceof Date) || !(date2 instanceof Date)) {
|
||||
throw new Error("Both arguments must be Date objects");
|
||||
}
|
||||
|
||||
setCurrent(date1)
|
||||
let months = (date2.getFullYear() - date1.getFullYear()) * 12;
|
||||
months -= date1.getMonth();
|
||||
months += date2.getMonth();
|
||||
|
||||
return months;
|
||||
setDelta(months);
|
||||
};
|
||||
|
||||
const handleChange = (d: Date | Date[]) => {
|
||||
@@ -106,9 +107,8 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
||||
if (d.length === 2) {
|
||||
return;
|
||||
} else if (d.length === 1) {
|
||||
debugger;
|
||||
if (selectedBackup.length === 0 || selectedBackup.length === 2) {
|
||||
setSelected([]);
|
||||
setSelected([...d]);
|
||||
setSelectedBackup([...d]);
|
||||
} else {
|
||||
setSelected(
|
||||
@@ -152,6 +152,10 @@ const DialogCalendarCard: React.FC<DialogCalendarCardProps> = ({
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
calendarRef.current?.gotoMonth(delta);
|
||||
}, [delta])
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && value) {
|
||||
setSelected(value || new Date());
|
||||
|
||||
@@ -83,13 +83,11 @@ const NutUICalendar = React.forwardRef<CalendarUIRef, NutUICalendarProps>(
|
||||
jumpTo: (year: number, month: number) => {
|
||||
calendarRef.current?.jumpTo(year, month);
|
||||
},
|
||||
gotoMonth: (delta: number) => {
|
||||
gotoMonth(delta);
|
||||
},
|
||||
gotoMonth,
|
||||
}));
|
||||
|
||||
const handleDateChange = (newValue: any) => {
|
||||
console.log("aaaaaaaaaaaaaaaaaaaaaa", newValue);
|
||||
if (type === "range") return;
|
||||
setSelectedValue(newValue);
|
||||
onChange?.(newValue as any);
|
||||
};
|
||||
|
||||
@@ -4,26 +4,26 @@
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 5px;
|
||||
|
||||
|
||||
.wallet_main_card {
|
||||
background: #000;
|
||||
border-radius: 20px;
|
||||
padding: 12px 20px 32px;
|
||||
margin-bottom: 8px;
|
||||
color: #fff;
|
||||
|
||||
|
||||
.card_header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
|
||||
|
||||
.header_title {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
.modify_password {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
@@ -31,37 +31,37 @@
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.balance_display {
|
||||
.amount_section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
|
||||
.amount_container {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
|
||||
|
||||
.currency_symbol {
|
||||
font-family: 'DingTalk JinBuTi', sans-serif;
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
line-height: 0.8;
|
||||
}
|
||||
|
||||
|
||||
.amount_group {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
|
||||
.main_amount {
|
||||
font-family: 'DingTalk JinBuTi', sans-serif;
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
line-height: 0.75;
|
||||
}
|
||||
|
||||
|
||||
.decimal_amount {
|
||||
font-family: 'DingTalk JinBuTi', sans-serif;
|
||||
font-size: 32px;
|
||||
@@ -70,7 +70,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.withdraw_btn {
|
||||
background: #fff;
|
||||
border: none;
|
||||
@@ -86,7 +86,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.available_amount {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
@@ -95,13 +95,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.function_buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
|
||||
.function_item {
|
||||
background: white;
|
||||
border: 0.5px solid #EBEBEB;
|
||||
@@ -114,19 +114,19 @@
|
||||
box-shadow: 0px 4px 36px 0px rgba(0, 0, 0, 0.05);
|
||||
height: 24px;
|
||||
flex: 1;
|
||||
|
||||
|
||||
.function_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
|
||||
.icon_text {
|
||||
font-size: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.function_text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
@@ -134,7 +134,7 @@
|
||||
line-height: 1.33;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
&:active {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
@@ -169,7 +169,8 @@
|
||||
position: relative;
|
||||
padding-right: 16px;
|
||||
|
||||
&::before, &::after {
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 2px;
|
||||
@@ -195,6 +196,7 @@
|
||||
}
|
||||
|
||||
.transaction_list {
|
||||
|
||||
.loading_state,
|
||||
.empty_state {
|
||||
padding: 40px 20px;
|
||||
@@ -277,21 +279,21 @@
|
||||
.popup_content {
|
||||
padding: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
.form_section {
|
||||
.form_item {
|
||||
margin-bottom: 12px;
|
||||
|
||||
|
||||
.form_label {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
.input_wrapper {
|
||||
position: relative;
|
||||
|
||||
|
||||
.amount_input {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
@@ -301,17 +303,17 @@
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
|
||||
&:focus {
|
||||
border-color: #667eea;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
&::placeholder {
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.currency_symbol {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
@@ -321,24 +323,24 @@
|
||||
color: #999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
.amount_input.with_symbol {
|
||||
padding-left: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.balance_tip {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
|
||||
.withdraw_desc {
|
||||
padding: 8px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e9ecef;
|
||||
|
||||
|
||||
.desc_text {
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
@@ -349,3 +351,40 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤弹窗
|
||||
.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,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View, Text, Input, Button, Image } from "@tarojs/components";
|
||||
import Taro, { useDidShow } from "@tarojs/taro";
|
||||
import "./index.scss";
|
||||
@@ -28,6 +28,71 @@ interface WalletInfo {
|
||||
total_withdraw?: number;
|
||||
}
|
||||
|
||||
enum TransactionType {
|
||||
All = '',
|
||||
Income = 'income',
|
||||
Expense = 'expense',
|
||||
}
|
||||
|
||||
enum TransactionSubType {
|
||||
All = '',
|
||||
GameActivity = 'game_activity',
|
||||
Withdrawal = 'withdrawal',
|
||||
Refund = 'refund',
|
||||
Compensation = 'compensation',
|
||||
}
|
||||
|
||||
interface TransactionLoadParams {
|
||||
page: number;
|
||||
limit: number;
|
||||
type: TransactionType;
|
||||
transaction_sub_type: TransactionSubType;
|
||||
keyword?: string;
|
||||
date?: string;
|
||||
}
|
||||
interface Option<T> {
|
||||
label: string;
|
||||
value: T;
|
||||
}
|
||||
|
||||
const income_expense_options: Option<TransactionType>[] = [
|
||||
{
|
||||
label: '全部',
|
||||
value: TransactionType.All
|
||||
},
|
||||
{
|
||||
label: '支出',
|
||||
value: TransactionType.Expense
|
||||
},
|
||||
{
|
||||
label: '收入',
|
||||
value: TransactionType.Income
|
||||
},
|
||||
];
|
||||
|
||||
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 WalletPage: React.FC = () => {
|
||||
// 钱包信息状态
|
||||
@@ -44,6 +109,18 @@ const WalletPage: React.FC = () => {
|
||||
const [transactions, set_transactions] = useState<Transaction[]>([]);
|
||||
const [loading_transactions, set_loading_transactions] = useState(false);
|
||||
|
||||
// 交易记录过滤状态
|
||||
const [showFilterPopup, setShowFilterPopup] = useState(false);
|
||||
|
||||
const [load_transactions_params, set_load_transactions_params] = useState<TransactionLoadParams>({
|
||||
page: 1,
|
||||
limit: 20,
|
||||
type: TransactionType.All,
|
||||
transaction_sub_type: TransactionSubType.All,
|
||||
keyword: "",
|
||||
date: ""
|
||||
});
|
||||
|
||||
// 页面显示时加载数据
|
||||
useDidShow(() => {
|
||||
load_wallet_data();
|
||||
@@ -82,14 +159,13 @@ const WalletPage: React.FC = () => {
|
||||
|
||||
// 加载交易记录
|
||||
const load_transactions = async () => {
|
||||
setShowFilterPopup(false);
|
||||
set_load_transactions_params({ ...load_transactions_params, page: 1 });
|
||||
try {
|
||||
set_loading_transactions(true);
|
||||
console.log("开始加载交易记录...");
|
||||
|
||||
const response = await httpService.post("/wallet/transactions", {
|
||||
page: 1,
|
||||
limit: 20
|
||||
});
|
||||
const response = await httpService.post("/wallet/transactions", { ...load_transactions_params });
|
||||
|
||||
console.log("交易记录响应:", response);
|
||||
|
||||
@@ -165,7 +241,7 @@ const WalletPage: React.FC = () => {
|
||||
});
|
||||
|
||||
// 根据后端返回的数据结构解析参数
|
||||
const { mch_id, app_id, package_info, open_id } = response.data;
|
||||
const { mch_id, app_id, package_info, open_id } = response.data;
|
||||
|
||||
console.log("/wallet/withdraw:", response.data);
|
||||
|
||||
@@ -255,6 +331,10 @@ const WalletPage: React.FC = () => {
|
||||
return `${prefix}${format_amount(transaction.amount)}`;
|
||||
};
|
||||
|
||||
const show_filter_popup = () => {
|
||||
setShowFilterPopup(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="wallet_page">
|
||||
{/* 钱包主卡片 */}
|
||||
@@ -285,7 +365,7 @@ const WalletPage: React.FC = () => {
|
||||
|
||||
{/* 功能按钮区域 */}
|
||||
<View className="function_buttons">
|
||||
<View className="function_item">
|
||||
<View className="function_item" onClick={show_filter_popup}>
|
||||
<Text className="function_text">全部账单</Text>
|
||||
<Image className="function_icon" src={require("@/static/wallet/arrow-down.svg")} />
|
||||
</View>
|
||||
@@ -392,6 +472,37 @@ const WalletPage: React.FC = () => {
|
||||
</View>
|
||||
</View>
|
||||
</CommonPopup>
|
||||
|
||||
{/* 筛选账单弹窗 */}
|
||||
<CommonPopup
|
||||
visible={showFilterPopup}
|
||||
onClose={() => setShowFilterPopup(false)}
|
||||
onConfirm={load_transactions}
|
||||
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">
|
||||
{income_expense_options.map((option: Option<TransactionType>) => (
|
||||
<View className={load_transactions_params.type === option.value ? "option_item active" : "option_item"} key={option.value} onClick={() => { set_load_transactions_params({ ...load_transactions_params, type: option.value }) }}>{option.label}</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<View className="form_item">
|
||||
<Text className="form_label">交易类型</Text>
|
||||
<View className="options_wrapper">
|
||||
{transaction_type_options.map((option: Option<TransactionSubType>) => (
|
||||
<View className={load_transactions_params.transaction_sub_type === option.value ? "option_item active" : "option_item"} key={option.value} onClick={() => { set_load_transactions_params({ ...load_transactions_params, transaction_sub_type: option.value }) }}>{option.label}</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
</View>
|
||||
</CommonPopup>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user