refactor(settings): 设置页面重构 & 支付状态优化

- SettingsPage: 功能增强
  - PaymentStatus: 关闭按钮样式优化
  - InvoicePage: 支持嵌入模式
This commit is contained in:
zdl
2026-01-13 14:59:01 +08:00
parent 7148dd97c2
commit 6c25cd14c7
3 changed files with 1699 additions and 651 deletions

View File

@@ -1,7 +1,7 @@
/** /**
* 发票管理页面 * 发票管理页面
*/ */
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from "react";
import { import {
Box, Box,
Flex, Flex,
@@ -31,38 +31,49 @@ import {
StatLabel, StatLabel,
StatNumber, StatNumber,
StatHelpText, StatHelpText,
} from '@chakra-ui/react'; } from "@chakra-ui/react";
import { FileText, Plus, RefreshCw, Clock, CheckCircle, AlertCircle } from 'lucide-react'; import {
import Card from '@components/Card/Card'; FileText,
import CardHeader from '@components/Card/CardHeader'; Plus,
import { InvoiceCard, InvoiceApplyModal } from '@components/Invoice'; RefreshCw,
Clock,
CheckCircle,
AlertCircle,
} from "lucide-react";
import Card from "@components/Card/Card";
import CardHeader from "@components/Card/CardHeader";
import { InvoiceCard, InvoiceApplyModal } from "@components/Invoice";
import { import {
getInvoiceList, getInvoiceList,
getInvoiceStats, getInvoiceStats,
cancelInvoice, cancelInvoice,
downloadInvoice, downloadInvoice,
} from '@/services/invoiceService'; } from "@/services/invoiceService";
import type { InvoiceInfo, InvoiceStatus, InvoiceStats } from '@/types/invoice'; import type { InvoiceInfo, InvoiceStatus, InvoiceStats } from "@/types/invoice";
type TabType = 'all' | 'pending' | 'processing' | 'completed'; type TabType = "all" | "pending" | "processing" | "completed";
const tabConfig: { key: TabType; label: string; status?: InvoiceStatus }[] = [ const tabConfig: { key: TabType; label: string; status?: InvoiceStatus }[] = [
{ key: 'all', label: '全部' }, { key: "all", label: "全部" },
{ key: 'pending', label: '待处理', status: 'pending' }, { key: "pending", label: "待处理", status: "pending" },
{ key: 'processing', label: '处理中', status: 'processing' }, { key: "processing", label: "处理中", status: "processing" },
{ key: 'completed', label: '已完成', status: 'completed' }, { key: "completed", label: "已完成", status: "completed" },
]; ];
export default function InvoicePage() { interface InvoicePageProps {
embedded?: boolean;
}
export default function InvoicePage({ embedded = false }: InvoicePageProps) {
const [invoices, setInvoices] = useState<InvoiceInfo[]>([]); const [invoices, setInvoices] = useState<InvoiceInfo[]>([]);
const [stats, setStats] = useState<InvoiceStats | null>(null); const [stats, setStats] = useState<InvoiceStats | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState<TabType>('all'); const [activeTab, setActiveTab] = useState<TabType>("all");
const [cancelingId, setCancelingId] = useState<string | null>(null); const [cancelingId, setCancelingId] = useState<string | null>(null);
const toast = useToast(); const toast = useToast();
const textColor = useColorModeValue('gray.700', 'white'); const textColor = useColorModeValue("gray.700", "white");
const bgCard = useColorModeValue('white', 'gray.800'); const bgCard = useColorModeValue("white", "gray.800");
const cancelDialogRef = React.useRef<HTMLButtonElement>(null); const cancelDialogRef = React.useRef<HTMLButtonElement>(null);
const { const {
@@ -87,11 +98,11 @@ export default function InvoicePage() {
setInvoices(res.data.list || []); setInvoices(res.data.list || []);
} }
} catch (error) { } catch (error) {
console.error('加载发票列表失败:', error); console.error("加载发票列表失败:", error);
toast({ toast({
title: '加载失败', title: "加载失败",
description: '无法获取发票列表', description: "无法获取发票列表",
status: 'error', status: "error",
duration: 3000, duration: 3000,
}); });
} finally { } finally {
@@ -107,7 +118,7 @@ export default function InvoicePage() {
setStats(res.data); setStats(res.data);
} }
} catch (error) { } catch (error) {
console.error('加载发票统计失败:', error); console.error("加载发票统计失败:", error);
} }
}, []); }, []);
@@ -124,25 +135,25 @@ export default function InvoicePage() {
const res = await cancelInvoice(cancelingId); const res = await cancelInvoice(cancelingId);
if (res.code === 200) { if (res.code === 200) {
toast({ toast({
title: '取消成功', title: "取消成功",
status: 'success', status: "success",
duration: 2000, duration: 2000,
}); });
loadInvoices(); loadInvoices();
loadStats(); loadStats();
} else { } else {
toast({ toast({
title: '取消失败', title: "取消失败",
description: res.message, description: res.message,
status: 'error', status: "error",
duration: 3000, duration: 3000,
}); });
} }
} catch (error) { } catch (error) {
toast({ toast({
title: '取消失败', title: "取消失败",
description: '网络错误', description: "网络错误",
status: 'error', status: "error",
duration: 3000, duration: 3000,
}); });
} finally { } finally {
@@ -156,7 +167,7 @@ export default function InvoicePage() {
try { try {
const blob = await downloadInvoice(invoice.id); const blob = await downloadInvoice(invoice.id);
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement("a");
a.href = url; a.href = url;
a.download = `发票_${invoice.invoiceNo || invoice.id}.pdf`; a.download = `发票_${invoice.invoiceNo || invoice.id}.pdf`;
document.body.appendChild(a); document.body.appendChild(a);
@@ -165,9 +176,9 @@ export default function InvoicePage() {
window.URL.revokeObjectURL(url); window.URL.revokeObjectURL(url);
} catch (error) { } catch (error) {
toast({ toast({
title: '下载失败', title: "下载失败",
description: '无法下载发票文件', description: "无法下载发票文件",
status: 'error', status: "error",
duration: 3000, duration: 3000,
}); });
} }
@@ -186,11 +197,17 @@ export default function InvoicePage() {
}; };
return ( return (
<Flex direction="column" pt={{ base: '120px', md: '75px' }}> <Flex direction="column" pt={embedded ? 0 : { base: "120px", md: "75px" }}>
{/* 统计卡片 */} {/* 统计卡片 */}
{stats && ( {stats && (
<SimpleGrid columns={{ base: 2, md: 4 }} spacing={4} mb={6}> <SimpleGrid columns={{ base: 2, md: 4 }} spacing={4} mb={6}>
<Card p={4}> <Card
p={4}
bg="transparent"
border="1px solid"
borderColor="rgba(212, 175, 55, 0.3)"
backdropFilter="blur(10px)"
>
<Stat> <Stat>
<StatLabel color="gray.500"></StatLabel> <StatLabel color="gray.500"></StatLabel>
<StatNumber color={textColor}>{stats.total}</StatNumber> <StatNumber color={textColor}>{stats.total}</StatNumber>
@@ -200,7 +217,13 @@ export default function InvoicePage() {
</StatHelpText> </StatHelpText>
</Stat> </Stat>
</Card> </Card>
<Card p={4}> <Card
p={4}
bg="transparent"
border="1px solid"
borderColor="rgba(212, 175, 55, 0.3)"
backdropFilter="blur(10px)"
>
<Stat> <Stat>
<StatLabel color="gray.500"></StatLabel> <StatLabel color="gray.500"></StatLabel>
<StatNumber color="yellow.500">{stats.pending}</StatNumber> <StatNumber color="yellow.500">{stats.pending}</StatNumber>
@@ -210,7 +233,13 @@ export default function InvoicePage() {
</StatHelpText> </StatHelpText>
</Stat> </Stat>
</Card> </Card>
<Card p={4}> <Card
p={4}
bg="transparent"
border="1px solid"
borderColor="rgba(212, 175, 55, 0.3)"
backdropFilter="blur(10px)"
>
<Stat> <Stat>
<StatLabel color="gray.500"></StatLabel> <StatLabel color="gray.500"></StatLabel>
<StatNumber color="blue.500">{stats.processing}</StatNumber> <StatNumber color="blue.500">{stats.processing}</StatNumber>
@@ -220,7 +249,13 @@ export default function InvoicePage() {
</StatHelpText> </StatHelpText>
</Stat> </Stat>
</Card> </Card>
<Card p={4}> <Card
p={4}
bg="transparent"
border="1px solid"
borderColor="rgba(212, 175, 55, 0.3)"
backdropFilter="blur(10px)"
>
<Stat> <Stat>
<StatLabel color="gray.500"></StatLabel> <StatLabel color="gray.500"></StatLabel>
<StatNumber color="green.500">{stats.completed}</StatNumber> <StatNumber color="green.500">{stats.completed}</StatNumber>
@@ -234,7 +269,12 @@ export default function InvoicePage() {
)} )}
{/* 主内容区 */} {/* 主内容区 */}
<Card> <Card
bg="transparent"
border="1px solid"
borderColor="rgba(212, 175, 55, 0.3)"
backdropFilter="blur(10px)"
>
<CardHeader> <CardHeader>
<Flex justify="space-between" align="center" w="100%" mb={4}> <Flex justify="space-between" align="center" w="100%" mb={4}>
<HStack> <HStack>
@@ -340,7 +380,9 @@ export default function InvoicePage() {
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogBody></AlertDialogBody> <AlertDialogBody>
</AlertDialogBody>
<AlertDialogFooter> <AlertDialogFooter>
<Button ref={cancelDialogRef} onClick={onCancelClose}> <Button ref={cancelDialogRef} onClick={onCancelClose}>

View File

@@ -12,7 +12,7 @@ import {
Icon, Icon,
Button, Button,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { CheckCircle, XCircle, AlertCircle } from 'lucide-react'; import { CheckCircle, XCircle, AlertCircle, X } from 'lucide-react';
import type { PaymentStatus as PaymentStatusType } from '../hooks/useWechatPay'; import type { PaymentStatus as PaymentStatusType } from '../hooks/useWechatPay';
interface PaymentStatusProps { interface PaymentStatusProps {
@@ -94,7 +94,24 @@ export const PaymentStatus: React.FC<PaymentStatusProps> = ({
maxW="400px" maxW="400px"
w="full" w="full"
textAlign="center" textAlign="center"
position="relative"
> >
{/* 右上角关闭按钮 */}
{onBack && (
<Box
position="absolute"
top={3}
right={3}
cursor="pointer"
p={1}
borderRadius="full"
_hover={{ bg: 'whiteAlpha.200' }}
onClick={onBack}
>
<Icon as={X} boxSize={5} color="gray.400" />
</Box>
)}
{/* 图标或加载动画 */} {/* 图标或加载动画 */}
{config.showSpinner ? ( {config.showSpinner ? (
<Spinner size="xl" color="gold.400" thickness="4px" /> <Spinner size="xl" color="gold.400" thickness="4px" />
@@ -135,27 +152,16 @@ export const PaymentStatus: React.FC<PaymentStatusProps> = ({
)} )}
{/* 操作按钮 */} {/* 操作按钮 */}
<VStack spacing={3} w="full" pt={4}>
{(status === 'failed' || status === 'cancelled') && onRetry && ( {(status === 'failed' || status === 'cancelled') && onRetry && (
<Button <Button
w="full" w="full"
colorScheme="yellow" colorScheme="yellow"
onClick={onRetry} onClick={onRetry}
mt={4}
> >
</Button> </Button>
)} )}
{onBack && (
<Button
w="full"
variant="ghost"
color="gray.400"
onClick={onBack}
>
</Button>
)}
</VStack>
{/* 支付中提示 */} {/* 支付中提示 */}
{status === 'paying' && ( {status === 'paying' && (

File diff suppressed because it is too large Load Diff