Files
mini-programs/src/user_pages/queryTransactions/index.tsx
张成 1226350099 1
2025-11-14 23:14:18 +08:00

409 lines
12 KiB
TypeScript

import { useState, useEffect, useRef } from "react";
import { View, Image, Text } from "@tarojs/components";
import { Input } from "@nutui/nutui-react-taro";
import img from "@/config/images";
import { withAuth, GeneralNavbar } from "@/components";
import "./index.scss";
import httpService from "@/services/httpService";
import Taro, { useReachBottom } from "@tarojs/taro";
interface Transaction {
id: number;
user_id: number;
transaction_type: string;
freeze_action: string | null;
amount: string;
description: string;
create_time: string;
last_modify_time: string;
related_id: number;
type_text: string;
}
interface History {
id: number;
keyword: string;
}
interface TransactionLoadParams {
page: number;
limit: number;
keyword: string;
}
const QueryTransactions = () => {
// 获取当前页面的配置
const currentPage = Taro.getCurrentInstance();
const pageConfig = currentPage.page?.config;
const pageTitle = pageConfig?.navigationBarTitleText;
const isInitialMount = useRef(true);
const [loading_transactions, set_loading_transactions] = useState(false);
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [searchHistory, setSearchHistory] = useState<History[]>([]);
const ref = useRef<any>(null);
const [keyword, setKeyword] = useState("");
const [load_transactions_params, set_load_transactions_params] =
useState<TransactionLoadParams>({
page: 1,
limit: 20,
keyword: "",
});
const [totalpages, setTotalpages] = useState(1);
useEffect(() => {
getSearchHistory();
}, []);
// useEffect(() => {
// if (ref?.current) {
// ref.current.focus();
// }
// }, [ref.current]);
useReachBottom(() => {
if (load_transactions_params.page >= totalpages) return;
set_load_transactions_params((prev) => {
return {
...prev,
page: prev.page + 1,
};
});
});
useEffect(() => {
if (isInitialMount.current) {
isInitialMount.current = false;
} else {
if (load_transactions_params.keyword === "") return;
handleSearch();
}
}, [load_transactions_params]);
// 是否显示清空图标
const isShowClearIcon =
load_transactions_params.keyword &&
load_transactions_params.keyword?.length > 0;
const getSearchHistory = async () => {
try {
const response = await httpService.post("/wallet/search_history", {
limit: 10,
});
if (response && response.data) {
setSearchHistory(response.data);
}
} catch (e) {
console.error(e);
}
};
/**
* @description 输入
* @param value
*/
const handleChange = (value: string) => {
setKeyword(value);
};
/**
* @description 点击清空输入内容
*/
const handleClear = (e) => {
e.stopPropagation();
setKeyword("");
setTransactions([]);
set_load_transactions_params((prev) => {
return { ...prev, page: 1, keyword: "" };
});
};
/**
* @description 点击历史搜索
* @param value
*/
const handleHistoryClick = (item: { id: number; keyword: string }) => {
setKeyword(item?.keyword);
set_load_transactions_params((prev) => {
return { ...prev, page: 1, keyword: item?.keyword };
});
};
/**
* @description 清空历史搜索
*/
const handleClearHistory = async () => {
await httpService.post("/wallet/clear_search_history");
setSearchHistory([]);
};
useEffect(() => {
if (load_transactions_params.keyword === "") return;
getSearchHistory();
}, [load_transactions_params.keyword]);
/**
* @description 点击搜索
*/
const handleSearch = async () => {
// set_loading_transactions(true);
try {
const response = await httpService.post("/wallet/transactions", {
...load_transactions_params,
});
if (response && response.data && response.data.list.length) {
setTransactions([...transactions, ...response.data.list]);
setTotalpages(response.data.totalPages);
} else if (load_transactions_params.page === 1) {
setTransactions([]);
}
} catch (error) {
setTransactions([]);
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,
});
} finally {
set_loading_transactions(false);
}
};
// 格式化金额显示
const format_amount = (amount: number | string) => {
const numAmount = typeof amount === "string" ? parseFloat(amount) : amount;
return numAmount.toFixed(2);
};
// 格式化时间显示
const format_time = (time: string) => {
time = time.replace(/-/g, "/");
const date = new Date(time);
const year = String(date.getFullYear());
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return {
date: `${year}-${month}-${day}`,
time: `${hours}:${minutes}:${seconds}`,
};
};
// 获取交易类型文字
const get_transaction_type_text = (transaction: Transaction) => {
// 如果有描述信息,优先使用描述
if (transaction.description) {
return transaction.description;
}
const typeMap: { [key: string]: string } = {
income: "收入",
expense: "支出",
freeze: "冻结",
unfreeze: "解冻",
};
let typeText =
typeMap[transaction.transaction_type] || transaction.transaction_type;
// 如果有冻结操作,添加到类型文字中
if (transaction.freeze_action) {
const freezeMap: { [key: string]: string } = {
freeze: "冻结",
unfreeze: "解冻",
};
typeText += `(${freezeMap[transaction.freeze_action]})`;
}
return typeText;
};
const navigateToDetail = (id: number) => {
Taro.navigateTo({
url: `/user_pages/billDetail/index?id=${id}`,
});
};
// 获取金额显示(带符号)
const get_amount_display = (transaction: Transaction) => {
const isPositive = transaction.transaction_type === "income";
const prefix = isPositive ? "+" : "-";
return `${prefix}${format_amount(transaction.amount)}`;
};
return (
<>
<View className="listSearchContainer">
{/* 导航栏 */}
{/* <View className="custom-navbar">
<View className="detail-navigator">
<View
className="detail-navigator-back"
onClick={() => {
Taro.navigateBack();
}}
>
<Image
className="detail-navigator-back-icon"
src={img.ICON_NAVIGATOR_BACK}
/>
<Text>{pageTitle}</Text>
</View>
</View>
</View> */}
{/* 顶部导航栏 */}
<GeneralNavbar
title={pageTitle}
showBack={true}
showAvatar={false}
onBack={() => {
Taro.navigateBack();
}}
/>
{/* 搜索 */}
<View className="topSearchWrapper">
<View className="topSearch">
<Image className="searchIcon" src={img.ICON_LIST_SEARCH_SEARCH} />
<Input
placeholder="查找"
value={keyword}
defaultValue={keyword}
onChange={handleChange}
onClear={handleClear}
onBlur={() => {
// load_transactions_params.keyword &&
// setKeyword(load_transactions_params.keyword);
}}
clearable={false}
ref={ref}
/>
<View className="searchRight">
{isShowClearIcon && (
<Image
className="clearIcon icon16"
src={img.ICON_LIST_SEARCH_CLEAR}
onClick={handleClear}
/>
)}
<View className="searchLine" />
<Text
className="searchText"
onClick={() => {
isInitialMount.current = false;
setTransactions([]);
set_load_transactions_params((prev) => {
return { ...prev, page: 1, keyword };
});
}}
>
</Text>
</View>
</View>
</View>
{/* 查找历史 */}
{!isShowClearIcon && searchHistory.length > 0 && (
<View className="historySearch">
<View className="historySearchTitleWrapper">
<View className="historySearchTitle"></View>
<View className="historySearchClear" onClick={handleClearHistory}>
<Text></Text>
<Image
className="clearIcon icon16"
src={img.ICON_LIST_SEARCH_CLEAR_HISTORY}
/>
</View>
</View>
{searchHistory.length && (
<View className="historySearchList">
{(searchHistory || [])?.map((item) => {
if (!item?.keyword) {
return null;
}
return (
<Text
className="historySearchItem"
onClick={() => handleHistoryClick(item)}
>
{item?.keyword}
</Text>
);
})}
</View>
)}
</View>
)}
{/* 交易记录列表 */}
<View className="transaction_list">
{loading_transactions ? (
<View className="loading_state">
<Text className="loading_text">...</Text>
</View>
) : transactions.length > 0 &&
load_transactions_params.keyword !== "" ? (
transactions.map((transaction) => {
const timeInfo = format_time(transaction.create_time);
return (
<View
key={transaction.id}
className="transaction_item"
onClick={() => {
navigateToDetail(transaction.id);
}}
>
<View className="transaction_left">
<Text className="transaction_title">
{get_transaction_type_text(transaction)}
</Text>
<View className="transaction_time">
<Text className="transaction_date">{timeInfo.date}</Text>
<Text className="transaction_clock">{timeInfo.time}</Text>
</View>
</View>
<View className="transaction_right">
<View>
<Text
className={`type_text_tag ${
transaction.type_text === "已提现" ? "success" : ""
}`}
>
{transaction.type_text}
</Text>
<Text className="transaction_amount">
{get_amount_display(transaction)}
</Text>
</View>
<Text className="balance_info">
¥{format_amount(transaction.amount)}
</Text>
</View>
</View>
);
})
) : (
<View className="empty_state">
<Text className="empty_text"></Text>
</View>
)}
</View>
{transactions.length > 0 && (
<View className="tips_text">202491</View>
)}
</View>
</>
);
};
export default withAuth(QueryTransactions);