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

View File

@@ -12,7 +12,7 @@ import {
Icon,
Button,
} 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';
interface PaymentStatusProps {
@@ -94,7 +94,24 @@ export const PaymentStatus: React.FC<PaymentStatusProps> = ({
maxW="400px"
w="full"
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 ? (
<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 && (
<Button
w="full"
colorScheme="yellow"
onClick={onRetry}
>
</Button>
)}
{onBack && (
<Button
w="full"
variant="ghost"
color="gray.400"
onClick={onBack}
>
</Button>
)}
</VStack>
{(status === 'failed' || status === 'cancelled') && onRetry && (
<Button
w="full"
colorScheme="yellow"
onClick={onRetry}
mt={4}
>
</Button>
)}
{/* 支付中提示 */}
{status === 'paying' && (

File diff suppressed because it is too large Load Diff