下载账单、优化账单查询

This commit is contained in:
2025-09-28 22:45:30 +08:00
parent 8a3e41cef6
commit 883491f466
7 changed files with 180 additions and 57 deletions

View File

@@ -178,7 +178,27 @@ const DownloadBill: React.FC = () => {
const { transaction_sub_type } = load_transactions_params; const { transaction_sub_type } = load_transactions_params;
const { start, end } = dateRange; const { start, end } = dateRange;
const date_range = [start, end]; const date_range = [start, end];
await httpService.post("/wallet/download_bill", {transaction_sub_type, date_range}); 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) { } catch (error) {
console.error(error); console.error(error);
} }
@@ -187,7 +207,7 @@ const DownloadBill: React.FC = () => {
<View className="download_bill_page"> <View className="download_bill_page">
<View className="hint_content"> <View className="hint_content">
<Text> </Text> <Text> </Text>
<Text className="button_text"></Text> {/* <Text className="button_text">示例文件</Text> */}
</View> </View>
<View className="form_container"> <View className="form_container">
{/* <View className="form_item"> {/* <View className="form_item">
@@ -226,9 +246,8 @@ const DownloadBill: React.FC = () => {
</View> </View>
<View <View
className={`option_button ${ className={`option_button ${dateType === "month" ? "active" : ""
dateType === "month" ? "active" : "" }`}
}`}
onClick={() => { onClick={() => {
selectDateRange("month"); selectDateRange("month");
}} }}
@@ -236,9 +255,8 @@ const DownloadBill: React.FC = () => {
</View> </View>
<View <View
className={`option_button ${ className={`option_button ${dateType === "custom" ? "active" : ""
dateType === "custom" ? "active" : "" }`}
}`}
onClick={() => { onClick={() => {
selectDateRange("custom"); selectDateRange("custom");
}} }}

View File

@@ -2,41 +2,71 @@ import React, { useState, useEffect } from "react";
import { View, Text } from "@tarojs/components"; import { View, Text } from "@tarojs/components";
import "./index.scss"; 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 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 ( return (
<View className="download-bill-records-page"> <View className="download-bill-records-page">
<View className="records-container"> <View className="records-container">
<View className="record-item"> {
<View className="title-text"></View> records.map((record) => (
<View className="info-item"> <View className="record-item" key={record.id}>
<Text></Text> <View className="title-text">{record.file_name}</View>
<Text>2025912 19:03:06</Text> <View className="info-item">
</View> <Text></Text>
<View className="info-item"> <Text>{record.create_time}</Text>
<Text></Text> </View>
<Text>2025912 19:03:06 2025912 19:03:06</Text> <View className="info-item">
</View> <Text></Text>
<View className="info-item"> <Text>{record.date_range_desc}</Text>
<Text></Text> </View>
<Text className="btn"></Text> <View className="info-item">
</View> <Text></Text>
</View> <Text className="btn"></Text>
<View className="record-item"> </View>
<View className="title-text"></View> </View>
<View className="info-item"> ))
<Text></Text> }
<Text>2025912 19:03:06</Text>
</View>
<View className="info-item">
<Text></Text>
<Text>2025912 19:03:06 2025912 19:03:06</Text>
</View>
<View className="info-item">
<Text></Text>
<Text className="btn"></Text>
</View>
</View>
</View> </View>
<View className="tips">7</View> <View className="tips">7</View>
</View> </View>

View File

@@ -23,8 +23,28 @@ const ValidPhone: React.FC = () => {
}; };
const handleConfirm = async () => { const handleConfirm = async () => {
// TODO: 校验验证码 const isValid = await validSMSCode();
Taro.navigateTo({ url: `/user_pages/setTransactionPassword/index?type=reset&phone=${formData.phone}&sms_code=${formData.sms_code}` }); 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 getSMSCode = async () => {

View File

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

View File

@@ -5,6 +5,7 @@ import "./index.scss";
import { CommonPopup } from "@/components"; import { CommonPopup } from "@/components";
import httpService from "@/services/httpService"; import httpService from "@/services/httpService";
import { withAuth } from "@/components"; import { withAuth } from "@/components";
import { PopupPicker } from "@/components/Picker/index";
// 交易记录类型 // 交易记录类型
interface Transaction { interface Transaction {
@@ -111,6 +112,16 @@ const WalletPage: React.FC = () => {
// 交易记录过滤状态 // 交易记录过滤状态
const [showFilterPopup, setShowFilterPopup] = useState(false); 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] = const [load_transactions_params, set_load_transactions_params] =
useState<TransactionLoadParams>({ useState<TransactionLoadParams>({
@@ -119,16 +130,31 @@ const WalletPage: React.FC = () => {
type: TransactionType.All, type: TransactionType.All,
transaction_sub_type: TransactionSubType.All, transaction_sub_type: TransactionSubType.All,
keyword: "", keyword: "",
date: "", date: `${year}-${month}`
}); });
useEffect(() => {
load_transactions();
}, [load_transactions_params]);
// 页面显示时加载数据 // 页面显示时加载数据
useDidShow(() => { useDidShow(() => {
load_wallet_data(); load_wallet_data();
load_transactions();
check_password_status(); 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 () => { const check_password_status = async () => {
try { try {
const res = await httpService.post("/wallet/check_password_status"); const res = await httpService.post("/wallet/check_password_status");
@@ -182,11 +208,10 @@ const WalletPage: React.FC = () => {
// 加载交易记录 // 加载交易记录
const load_transactions = async () => { const load_transactions = async () => {
setShowFilterPopup(false); setShowFilterPopup(false);
set_load_transactions_params({ ...load_transactions_params, page: 1 }); // set_load_transactions_params({ ...load_transactions_params, page: 1 });
try { try {
set_loading_transactions(true); set_loading_transactions(true);
console.log("开始加载交易记录..."); console.log("开始加载交易记录...");
const response = await httpService.post("/wallet/transactions", { const response = await httpService.post("/wallet/transactions", {
...load_transactions_params, ...load_transactions_params,
}); });
@@ -327,6 +352,7 @@ const WalletPage: React.FC = () => {
// 格式化时间显示 // 格式化时间显示
const format_time = (time: string) => { const format_time = (time: string) => {
time = time.replace(/-/g, "/");
const date = new Date(time); const date = new Date(time);
const month = String(date.getMonth() + 1).padStart(2, "0"); const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0");
@@ -380,6 +406,14 @@ const WalletPage: React.FC = () => {
setShowFilterPopup(true); setShowFilterPopup(true);
}; };
const handleFilterCancel = () => {
setShowFilterPopup(false);
setFilterParams({
type: load_transactions_params.type,
transaction_sub_type: load_transactions_params.transaction_sub_type,
});
};
return ( return (
<View className="wallet_page"> <View className="wallet_page">
{/* 钱包主卡片 */} {/* 钱包主卡片 */}
@@ -465,8 +499,8 @@ const WalletPage: React.FC = () => {
{/* 标题栏 */} {/* 标题栏 */}
<View className="history_header"> <View className="history_header">
<Text className="history_title"></Text> <Text className="history_title"></Text>
<View className="month_selector"> <View className="month_selector" onClick={() => setShowMonthPicker(true)}>
<Text className="current_month">2025-09</Text> <Text className="current_month">{load_transactions_params.date}</Text>
</View> </View>
</View> </View>
@@ -557,12 +591,30 @@ const WalletPage: React.FC = () => {
</View> </View>
</View> </View>
</CommonPopup> </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 <CommonPopup
visible={showFilterPopup} visible={showFilterPopup}
onClose={() => setShowFilterPopup(false)} onClose={handleFilterCancel}
onConfirm={load_transactions} onConfirm={modify_load_transactions_params}
title="选择筛选项" title="选择筛选项"
className="filter_popup" className="filter_popup"
> >
@@ -575,14 +627,14 @@ const WalletPage: React.FC = () => {
(option: Option<TransactionType>) => ( (option: Option<TransactionType>) => (
<View <View
className={ className={
load_transactions_params.type === option.value filterParams.type === option.value
? "option_item active" ? "option_item active"
: "option_item" : "option_item"
} }
key={option.value} key={option.value}
onClick={() => { onClick={() => {
set_load_transactions_params({ setFilterParams({
...load_transactions_params, ...filterParams,
type: option.value, type: option.value,
}); });
}} }}
@@ -600,15 +652,15 @@ const WalletPage: React.FC = () => {
(option: Option<TransactionSubType>) => ( (option: Option<TransactionSubType>) => (
<View <View
className={ className={
load_transactions_params.transaction_sub_type === filterParams.transaction_sub_type ===
option.value option.value
? "option_item active" ? "option_item active"
: "option_item" : "option_item"
} }
key={option.value} key={option.value}
onClick={() => { onClick={() => {
set_load_transactions_params({ setFilterParams({
...load_transactions_params, ...filterParams,
transaction_sub_type: option.value, transaction_sub_type: option.value,
}); });
}} }}

View File

@@ -95,7 +95,7 @@
} }
.tips-container { .tips-container {
padding: 20px 20px 0; padding: 20px 20px;
.title-text { .title-text {
font-weight: 600; font-weight: 600;

View File

@@ -253,6 +253,7 @@ const Withdrawal: React.FC = () => {
<Text>4. 使</Text> <Text>4. 使</Text>
</View> </View>
</View> </View>
{/* 提现输入密码弹窗 */} {/* 提现输入密码弹窗 */}
<CommonPopup <CommonPopup
visible={show_withdraw_popup} visible={show_withdraw_popup}