From 9ba0b8eb8aeafb502fd426cbb85828e036c9fcb2 Mon Sep 17 00:00:00 2001 From: Ultrame <1019265060@qq.com> Date: Fri, 26 Sep 2025 00:01:27 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=97=A5=E6=9C=9F=E8=8C=83=E5=9B=B4?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=99=A8=E3=80=81=E8=B4=A6=E5=8D=95=E7=AD=9B?= =?UTF-8?q?=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CalendarDialog/DialogCalendarCard.tsx | 28 ++-- .../Picker/CalendarUI/CalendarUI.tsx | 6 +- src/user_pages/wallet/index.scss | 99 +++++++++----- src/user_pages/wallet/index.tsx | 125 +++++++++++++++++- 4 files changed, 205 insertions(+), 53 deletions(-) diff --git a/src/components/Picker/CalendarDialog/DialogCalendarCard.tsx b/src/components/Picker/CalendarDialog/DialogCalendarCard.tsx index 449b28c..daff3c1 100644 --- a/src/components/Picker/CalendarDialog/DialogCalendarCard.tsx +++ b/src/components/Picker/CalendarDialog/DialogCalendarCard.tsx @@ -28,6 +28,8 @@ const DialogCalendarCard: React.FC = ({ const [selectedBackup, setSelectedBackup] = useState( Array.isArray(value) ? [...(value as Date[])] : [value as Date] ); + const [current, setCurrent] = useState(new Date()); + const [delta, setDelta] = useState(0); const calendarRef = useRef(null); const [type, setType] = useState<"year" | "month" | "time">("year"); const [selectedHour, setSelectedHour] = useState(8); @@ -56,11 +58,11 @@ const DialogCalendarCard: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ onClose(); } }; + useEffect(() => { + calendarRef.current?.gotoMonth(delta); + }, [delta]) + useEffect(() => { if (visible && value) { setSelected(value || new Date()); diff --git a/src/components/Picker/CalendarUI/CalendarUI.tsx b/src/components/Picker/CalendarUI/CalendarUI.tsx index 08f2d4a..f58f469 100644 --- a/src/components/Picker/CalendarUI/CalendarUI.tsx +++ b/src/components/Picker/CalendarUI/CalendarUI.tsx @@ -83,13 +83,11 @@ const NutUICalendar = React.forwardRef( 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); }; diff --git a/src/user_pages/wallet/index.scss b/src/user_pages/wallet/index.scss index 3025e31..51aad1b 100644 --- a/src/user_pages/wallet/index.scss +++ b/src/user_pages/wallet/index.scss @@ -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; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/user_pages/wallet/index.tsx b/src/user_pages/wallet/index.tsx index dc7f218..3429f4c 100644 --- a/src/user_pages/wallet/index.tsx +++ b/src/user_pages/wallet/index.tsx @@ -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 { + label: string; + value: T; +} + +const income_expense_options: Option[] = [ + { + label: '全部', + value: TransactionType.All + }, + { + label: '支出', + value: TransactionType.Expense + }, + { + label: '收入', + value: TransactionType.Income + }, +]; + +const transaction_type_options: Option[] = [ + { + 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([]); const [loading_transactions, set_loading_transactions] = useState(false); + // 交易记录过滤状态 + const [showFilterPopup, setShowFilterPopup] = useState(false); + + const [load_transactions_params, set_load_transactions_params] = useState({ + 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 ( {/* 钱包主卡片 */} @@ -285,7 +365,7 @@ const WalletPage: React.FC = () => { {/* 功能按钮区域 */} - + 全部账单 @@ -392,6 +472,37 @@ const WalletPage: React.FC = () => { + + {/* 筛选账单弹窗 */} + setShowFilterPopup(false)} + onConfirm={load_transactions} + title="选择筛选项" + className="filter_popup" + > + + + + 收支类型 + + {income_expense_options.map((option: Option) => ( + { set_load_transactions_params({ ...load_transactions_params, type: option.value }) }}>{option.label} + ))} + + + + 交易类型 + + {transaction_type_options.map((option: Option) => ( + { set_load_transactions_params({ ...load_transactions_params, transaction_sub_type: option.value }) }}>{option.label} + ))} + + + + + + ); }; From d3ceda66d6fdabf369f4bb347472e165301a6a16 Mon Sep 17 00:00:00 2001 From: Ultrame <1019265060@qq.com> Date: Fri, 26 Sep 2025 17:32:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E8=B4=A6=E5=8D=95=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.config.ts | 1 + src/user_pages/billDetail/index.config.ts | 3 + src/user_pages/billDetail/index.scss | 56 +++ src/user_pages/billDetail/index.tsx | 65 ++++ .../queryTransactions/index.config.ts | 2 +- src/user_pages/queryTransactions/index.scss | 197 ++++++++++ src/user_pages/queryTransactions/index.tsx | 350 +++++++++++++++++- src/user_pages/wallet/index.tsx | 250 +++++++++---- 8 files changed, 836 insertions(+), 88 deletions(-) create mode 100644 src/user_pages/billDetail/index.config.ts create mode 100644 src/user_pages/billDetail/index.scss create mode 100644 src/user_pages/billDetail/index.tsx diff --git a/src/app.config.ts b/src/app.config.ts index 2bee88b..2a7f0a5 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -29,6 +29,7 @@ export default defineAppConfig({ "queryTransactions/index", // 查询交易 "downloadBill/index", // 下载账单 "downloadBillRecords/index", // 下载账单记录 + "billDetail/index", // 账单详情 ], }, // { diff --git a/src/user_pages/billDetail/index.config.ts b/src/user_pages/billDetail/index.config.ts new file mode 100644 index 0000000..d5ec499 --- /dev/null +++ b/src/user_pages/billDetail/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '明细详情', +}) diff --git a/src/user_pages/billDetail/index.scss b/src/user_pages/billDetail/index.scss new file mode 100644 index 0000000..dd304da --- /dev/null +++ b/src/user_pages/billDetail/index.scss @@ -0,0 +1,56 @@ +.bill-detail-page { + min-height: 100vh; + background-color: #f5f5f5; + padding: 5px; + .title-text-box { + height: 118px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 16px; + .title-text { + font-family: PingFang SC; + font-weight: 400; + font-style: Regular; + font-size: 12px; + text-align: center; + } + .amount-text { + font-family: DingTalk JinBuTi; + font-weight: 400; + font-style: Regular; + font-size: 32px; + vertical-align: middle; + text-align: center; + } + } + .detail-wrapper { + padding: 12px 20px 16px; + border: 0.5px solid #ebebeb; + box-shadow: 0px 4px 36px 0px #0000000d; + .detail-item { + display: flex; + justify-content: space-between; + font-family: PingFang SC; + font-weight: 400; + font-style: Regular; + font-size: 12px; + line-height: 18px; + color: #000; + margin-bottom: 8px; + & > Text { + &:first-child { + color: #3c3c4399; + } + } + .with-btn-box { + display: flex; + gap: 8px; + .btn { + color: #007aff; + } + } + } + } +} diff --git a/src/user_pages/billDetail/index.tsx b/src/user_pages/billDetail/index.tsx new file mode 100644 index 0000000..fa68f99 --- /dev/null +++ b/src/user_pages/billDetail/index.tsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from "react"; +import { View, Text, Input, Button, Image } from "@tarojs/components"; + +import { TransactionType, TransactionSubType } from "@/user_pages/wallet/index"; +import "./index.scss"; + +enum FreezeActions { + Unfreeze = "unfreeze", + Freeze = "freeze", +} + +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; +} + +const BillDetail: React.FC = () => { + const [billDetail, setBillDetail] = useState(null); + return ( + + + 现金交易 (元) + + + + 65.00 + + + + + 交易时间 + 2025-02-16 12:21:54 + + + 活动标题 + 女生轻松双打 + + + 现金余额 + ¥3890.00 + + + 交易单号 + + 89172371293791273912 + 复制 + + + + + ); +}; + +export default BillDetail; diff --git a/src/user_pages/queryTransactions/index.config.ts b/src/user_pages/queryTransactions/index.config.ts index 158e68c..79cc30c 100644 --- a/src/user_pages/queryTransactions/index.config.ts +++ b/src/user_pages/queryTransactions/index.config.ts @@ -1,3 +1,3 @@ export default definePageConfig({ - navigationBarTitleText: '下载记录', + navigationBarTitleText: '查找交易', }) diff --git a/src/user_pages/queryTransactions/index.scss b/src/user_pages/queryTransactions/index.scss index e69de29..7ea9960 100644 --- a/src/user_pages/queryTransactions/index.scss +++ b/src/user_pages/queryTransactions/index.scss @@ -0,0 +1,197 @@ +.listSearchContainer { + 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); + } + + .searchText { + color: #000000; + font-size: 16px; + font-weight: 600; + line-height: 20px; + } + } + + .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; + } + + .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 { + 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; + } + + .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; + } + } + } + } +} diff --git a/src/user_pages/queryTransactions/index.tsx b/src/user_pages/queryTransactions/index.tsx index 7ca533e..c86aafa 100644 --- a/src/user_pages/queryTransactions/index.tsx +++ b/src/user_pages/queryTransactions/index.tsx @@ -1,10 +1,348 @@ -import React, { useState, useEffect } from "react"; -import { View } from "@tarojs/components"; +import { useState } from "react"; +import { View, Image, Text } from "@tarojs/components"; +import { Input } from "@nutui/nutui-react-taro"; +import { useEffect, useRef } from "react"; +import img from "@/config/images"; +import { withAuth } from "@/components"; +import "./index.scss"; +import httpService from "@/services/httpService"; +import Taro 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; +} + +// 钱包信息类型 +interface WalletInfo { + balance: number; + frozen_balance?: number; + total_balance?: number; + total_income?: number; + total_withdraw?: number; +} + +interface History { + id: number; + keyword: string; +} +interface TransactionLoadParams { + page: number; + limit: number; + keyword?: string; +} + +const QueryTransactions = () => { + const [loading_transactions, set_loading_transactions] = useState(false); + const [transactions, setTransactions] = useState([]); + const [searchHistory, setSearchHistory] = useState([]); + const ref = useRef(null); + const [load_transactions_params, set_load_transactions_params] = + useState({ + page: 1, + limit: 20, + keyword: "", + }); + + useEffect(() => { + getSearchHistory(); + return () => { + handleClear(); + }; + }, []); + + useEffect(() => { + if (ref?.current) { + ref.current.focus(); + } + }, [ref.current]); + + // 是否显示清空图标 + 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) => { + set_load_transactions_params((prev) => { + return { ...prev, keyword: value }; + }); + }; + + /** + * @description 点击清空输入内容 + */ + const handleClear = () => { + setTransactions([]); + set_load_transactions_params((prev) => { + return { ...prev, keyword: "" }; + }); + }; + + /** + * @description 点击历史搜索 + * @param value + */ + const handleHistoryClick = (item: { id: number; keyword: string }) => { + set_load_transactions_params((prev) => { + handleSearch(item?.keyword); + return { ...prev, keyword: item?.keyword }; + }); + }; + + /** + * @description 清空历史搜索 + */ + const handleClearHistory = async () => { + await httpService.post("/wallet/clear_search_history"); + setSearchHistory([]); + }; + + /** + * @description 点击搜索 + */ + const handleSearch = async (val?: string) => { + if (!val) { + return; + } + set_loading_transactions(true); + try { + const response = await httpService.post("/wallet/transactions", { + ...load_transactions_params, + keyword: val, + }); + console.log("交易记录响应:", response); + + if (response && response.data && response.data.list) { + setTransactions(response.data.list); + } else { + 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 [wallet_info, set_wallet_info] = useState({ + balance: 0, + }); + + // 格式化时间显示 + const format_time = (time: string) => { + const date = new Date(time); + 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: `2025-${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)}`; + }; -const QueryTransactions: React.FC = () => { return ( - 查询交易 + <> + + {/* 搜索 */} + + + + + {isShowClearIcon && ( + + )} + + handleSearch(load_transactions_params.keyword)} + > + 搜索 + + + + {/* 查找历史 */} + {!isShowClearIcon && searchHistory.length > 0 && ( + + + 查找历史 + + 清空 + + + + + {searchHistory.length && ( + + {(searchHistory || [])?.map((item) => { + if (!item?.keyword) { + return null; + } + return ( + handleHistoryClick(item)} + > + {item?.keyword} + + ); + })} + + )} + + )} + + {/* 交易记录列表 */} + + {loading_transactions ? ( + + 加载中... + + ) : transactions.length > 0 ? ( + transactions.map((transaction) => { + const timeInfo = format_time(transaction.create_time); + return ( + { + navigateToDetail(transaction.id); + }} + > + + + {get_transaction_type_text(transaction)} + + + {timeInfo.date} + {timeInfo.time} + + + + + {get_amount_display(transaction)} + + + 余额 ¥{format_amount(wallet_info.balance)} + + + + ); + }) + ) : ( + + 暂无交易记录 + + )} + + + ); }; - -export default QueryTransactions; \ No newline at end of file +export default withAuth(QueryTransactions); diff --git a/src/user_pages/wallet/index.tsx b/src/user_pages/wallet/index.tsx index 3429f4c..e08db38 100644 --- a/src/user_pages/wallet/index.tsx +++ b/src/user_pages/wallet/index.tsx @@ -28,18 +28,18 @@ interface WalletInfo { total_withdraw?: number; } -enum TransactionType { - All = '', - Income = 'income', - Expense = 'expense', +export enum TransactionType { + All = "", + Income = "income", + Expense = "expense", } -enum TransactionSubType { - All = '', - GameActivity = 'game_activity', - Withdrawal = 'withdrawal', - Refund = 'refund', - Compensation = 'compensation', +export enum TransactionSubType { + All = "", + GameActivity = "game_activity", + Withdrawal = "withdrawal", + Refund = "refund", + Compensation = "compensation", } interface TransactionLoadParams { @@ -57,43 +57,42 @@ interface Option { const income_expense_options: Option[] = [ { - label: '全部', - value: TransactionType.All + label: "全部", + value: TransactionType.All, }, { - label: '支出', - value: TransactionType.Expense + label: "支出", + value: TransactionType.Expense, }, { - label: '收入', - value: TransactionType.Income + label: "收入", + value: TransactionType.Income, }, ]; const transaction_type_options: Option[] = [ { - label: '全部', - value: TransactionSubType.All + label: "全部", + value: TransactionSubType.All, }, { - label: '组织活动', - value: TransactionSubType.GameActivity + label: "组织活动", + value: TransactionSubType.GameActivity, }, { - label: '提现', - value: TransactionSubType.Withdrawal + label: "提现", + value: TransactionSubType.Withdrawal, }, { - label: '退款', - value: TransactionSubType.Refund + label: "退款", + value: TransactionSubType.Refund, }, { - label: '企业赔付', - value: TransactionSubType.Compensation + label: "企业赔付", + value: TransactionSubType.Compensation, }, ]; - const WalletPage: React.FC = () => { // 钱包信息状态 const [wallet_info, set_wallet_info] = useState({ @@ -112,14 +111,15 @@ const WalletPage: React.FC = () => { // 交易记录过滤状态 const [showFilterPopup, setShowFilterPopup] = useState(false); - const [load_transactions_params, set_load_transactions_params] = useState({ - page: 1, - limit: 20, - type: TransactionType.All, - transaction_sub_type: TransactionSubType.All, - keyword: "", - date: "" - }); + const [load_transactions_params, set_load_transactions_params] = + useState({ + page: 1, + limit: 20, + type: TransactionType.All, + transaction_sub_type: TransactionSubType.All, + keyword: "", + date: "", + }); // 页面显示时加载数据 useDidShow(() => { @@ -131,19 +131,30 @@ const WalletPage: React.FC = () => { 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; + const { + balance, + frozen_balance, + total_balance, + total_income, + total_withdraw, + } = response.data; set_wallet_info({ balance, frozen_balance, total_balance, total_income, - total_withdraw + total_withdraw, }); } catch (error: any) { console.error("加载钱包数据失败:", error); let errorMessage = "加载失败,请重试"; - if (error && error.response && error.response.data && error.response.data.message) { + 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; @@ -165,7 +176,9 @@ const WalletPage: React.FC = () => { set_loading_transactions(true); console.log("开始加载交易记录..."); - const response = await httpService.post("/wallet/transactions", { ...load_transactions_params }); + const response = await httpService.post("/wallet/transactions", { + ...load_transactions_params, + }); console.log("交易记录响应:", response); @@ -181,7 +194,12 @@ const WalletPage: React.FC = () => { set_transactions([]); let errorMessage = "加载交易记录失败"; - if (error && error.response && error.response.data && error.response.data.message) { + 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; @@ -231,13 +249,12 @@ const WalletPage: React.FC = () => { return; } - set_submitting(true); // 先调用后端接口获取提现参数 const response = await httpService.post("/wallet/withdraw", { amount: parseFloat(withdraw_amount), - transfer_remark: "用户申请提现" + transfer_remark: "用户申请提现", }); // 根据后端返回的数据结构解析参数 @@ -268,31 +285,28 @@ const WalletPage: React.FC = () => { }, fail: (res) => { console.log("微信转账失败:", res); - } + }, }); - - }; - // 格式化金额显示 const format_amount = (amount: number | string) => { - const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount; + const numAmount = typeof amount === "string" ? parseFloat(amount) : amount; return numAmount.toFixed(2); }; // 格式化时间显示 const format_time = (time: string) => { const date = new Date(time); - 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'); + 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: `2025-${month}-${day}`, - time: `${hours}:${minutes}:${seconds}` + time: `${hours}:${minutes}:${seconds}`, }; }; @@ -304,19 +318,20 @@ const WalletPage: React.FC = () => { } const typeMap: { [key: string]: string } = { - 'income': '收入', - 'expense': '支出', - 'freeze': '冻结', - 'unfreeze': '解冻' + income: "收入", + expense: "支出", + freeze: "冻结", + unfreeze: "解冻", }; - let typeText = typeMap[transaction.transaction_type] || transaction.transaction_type; + let typeText = + typeMap[transaction.transaction_type] || transaction.transaction_type; // 如果有冻结操作,添加到类型文字中 if (transaction.freeze_action) { const freezeMap: { [key: string]: string } = { - 'freeze': '冻结', - 'unfreeze': '解冻' + freeze: "冻结", + unfreeze: "解冻", }; typeText += `(${freezeMap[transaction.freeze_action]})`; } @@ -326,14 +341,14 @@ const WalletPage: React.FC = () => { // 获取金额显示(带符号) const get_amount_display = (transaction: Transaction) => { - const isPositive = transaction.transaction_type === 'income'; - const prefix = isPositive ? '+' : '-'; + const isPositive = transaction.transaction_type === "income"; + const prefix = isPositive ? "+" : "-"; return `${prefix}${format_amount(transaction.amount)}`; }; const show_filter_popup = () => { - setShowFilterPopup(true) - } + setShowFilterPopup(true); + }; return ( @@ -351,15 +366,24 @@ const WalletPage: React.FC = () => { ¥ - {Math.floor(wallet_info.balance)} - .{((wallet_info.balance % 1) * 100).toFixed(0).padStart(2, '0')} + + {Math.floor(wallet_info.balance)} + + + . + {((wallet_info.balance % 1) * 100) + .toFixed(0) + .padStart(2, "0")} + - 可提现金额:¥{format_amount(wallet_info.balance)} + + 可提现金额:¥{format_amount(wallet_info.balance)} + @@ -367,18 +391,40 @@ const WalletPage: React.FC = () => { 全部账单 - + - Taro.navigateTo({ url: "/user_pages/queryTransactions/index" })}> - + + Taro.navigateTo({ url: "/user_pages/queryTransactions/index" }) + } + > + 查询交易 - Taro.navigateTo({ url: "/user_pages/downloadBill/index" })}> - + + Taro.navigateTo({ url: "/user_pages/downloadBill/index" }) + } + > + 下载账单 - + 客服中心 @@ -403,7 +449,15 @@ const WalletPage: React.FC = () => { transactions.map((transaction) => { const timeInfo = format_time(transaction.create_time); return ( - + { + Taro.navigateTo({ + url: `/user_pages/billDetail/index?id=${transaction.id}`, + }); + }} + > {get_transaction_type_text(transaction)} @@ -486,20 +540,54 @@ const WalletPage: React.FC = () => { 收支类型 - {income_expense_options.map((option: Option) => ( - { set_load_transactions_params({ ...load_transactions_params, type: option.value }) }}>{option.label} - ))} + {income_expense_options.map( + (option: Option) => ( + { + set_load_transactions_params({ + ...load_transactions_params, + type: option.value, + }); + }} + > + {option.label} + + ) + )} 交易类型 - {transaction_type_options.map((option: Option) => ( - { set_load_transactions_params({ ...load_transactions_params, transaction_sub_type: option.value }) }}>{option.label} - ))} + {transaction_type_options.map( + (option: Option) => ( + { + set_load_transactions_params({ + ...load_transactions_params, + transaction_sub_type: option.value, + }); + }} + > + {option.label} + + ) + )} -