日期范围选择器、账单筛选

This commit is contained in:
2025-09-26 00:01:27 +08:00
parent 19a51fc679
commit 9ba0b8eb8a
4 changed files with 205 additions and 53 deletions

View File

@@ -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());

View File

@@ -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);
};

View File

@@ -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;
@@ -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;
}
}
}
}
}
}
}

View File

@@ -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>
);
};