refactor(settings): 设置页面重构 & 支付状态优化
- SettingsPage: 功能增强 - PaymentStatus: 关闭按钮样式优化 - InvoicePage: 支持嵌入模式
This commit is contained in:
@@ -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}>
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user