Ver código fonte

✨ feat(payments): 优化支付状态筛选逻辑
- 将支付状态筛选的"全部状态"值从空字符串改为"all"
- 优化状态筛选的onValueChange处理逻辑
- 统一支付类型筛选的默认值和处理逻辑

✨ feat(user): 重构会员信息展示
- UserInfoModal中修改账户余额卡片为会员到期时间展示
- 添加会员剩余天数计算和显示
- 调整剩余处理次数的展示位置

✨ feat(membership): 优化会员页面和套餐展示
- 移除HomePage中默认套餐展示,使用动态数据
- MemberPage新增会员信息卡片,显示到期时间和剩余天数
- 统一"续费会员"/"立即开通"按钮文案

✨ feat(pricing): 重构价格页面,使用API获取套餐数据
- 移除PricingPage中的静态套餐数据,使用useQuery获取动态数据
- 优化套餐卡片样式和交互效果
- 调整套餐特性列表展示方式

✨ feat(recharge): 更新充值页面会员状态展示
- RechargePage中修改账户状态卡片为会员状态展示
- 更新到期时间显示格式
- 修正购买提示文案,明确会员有效期为一年

yourname 3 meses atrás
pai
commit
467ec4d612

+ 6 - 6
src/client/admin/pages/Payments.tsx

@@ -293,10 +293,10 @@ export const PaymentsPage = () => {
                 />
               </div>
               <Select
-                value={searchParams.status?.toString() || ''}
+                value={searchParams.status?.toString() || 'all'}
                 onValueChange={(value) => setSearchParams(prev => ({ 
                   ...prev, 
-                  status: value ? parseInt(value) : undefined,
+                  status: value === 'all' ? undefined : parseInt(value),
                   page: 1 
                 }))}
               >
@@ -304,7 +304,7 @@ export const PaymentsPage = () => {
                   <SelectValue placeholder="状态" />
                 </SelectTrigger>
                 <SelectContent>
-                  <SelectItem value="">全部状态</SelectItem>
+                  <SelectItem value="all">全部状态</SelectItem>
                   <SelectItem value={PaymentStatus.PENDING.toString()}>待支付</SelectItem>
                   <SelectItem value={PaymentStatus.COMPLETED.toString()}>已完成</SelectItem>
                   <SelectItem value={PaymentStatus.FAILED.toString()}>失败</SelectItem>
@@ -312,10 +312,10 @@ export const PaymentsPage = () => {
                 </SelectContent>
               </Select>
               <Select
-                value={searchParams.paymentType || ''}
+                value={searchParams.paymentType || 'all'}
                 onValueChange={(value) => setSearchParams(prev => ({ 
                   ...prev, 
-                  paymentType: value || undefined,
+                  paymentType: value === 'all' ? undefined : value,
                   page: 1 
                 }))}
               >
@@ -323,7 +323,7 @@ export const PaymentsPage = () => {
                   <SelectValue placeholder="类型" />
                 </SelectTrigger>
                 <SelectContent>
-                  <SelectItem value="">全部类型</SelectItem>
+                  <SelectItem value="all">全部类型</SelectItem>
                   <SelectItem value={PaymentType.RECHARGE}>充值</SelectItem>
                   <SelectItem value={PaymentType.CONSUME}>消费</SelectItem>
                   <SelectItem value={PaymentType.REFUND}>退款</SelectItem>

+ 14 - 14
src/client/home/components/UserInfoModal.tsx

@@ -172,32 +172,32 @@ export default function UserInfoModal({ isOpen, onClose }: UserInfoModalProps) {
 
                 <Card>
                   <CardHeader>
-                    <CardTitle className="text-lg">账户余额</CardTitle>
+                    <CardTitle className="text-lg">会员到期时间</CardTitle>
                   </CardHeader>
                   <CardContent>
                     <div className="flex items-center justify-between">
                       <div>
-                        <p className="text-3xl font-bold text-blue-600">{user.remainingCount || 0}</p>
-                        <p className="text-sm text-gray-500">剩余处理次数</p>
+                        <p className="text-3xl font-bold text-blue-600">
+                          {user.expireAt ? format(new Date(user.expireAt), 'yyyy年MM月dd日', { locale: zhCN }) : '未开通'}
+                        </p>
+                        <p className="text-sm text-gray-500">
+                          {user.expireAt ? `剩余 ${Math.ceil((new Date(user.expireAt).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))} 天` : '请开通会员'}
+                        </p>
+                      </div>
+                      <div className="text-right">
+                        <p className="text-sm text-gray-500">剩余次数</p>
+                        <p className="font-semibold">{user.remainingCount || 0} 次</p>
                       </div>
-                      {user.expireAt && (
-                        <div className="text-right">
-                          <p className="text-sm text-gray-500">会员到期</p>
-                          <p className="font-semibold">
-                            {format(new Date(user.expireAt), 'yyyy年MM月dd日', { locale: zhCN })}
-                          </p>
-                        </div>
-                      )}
                     </div>
-                    <Button 
-                      className="w-full mt-4" 
+                    <Button
+                      className="w-full mt-4"
                       onClick={() => {
                         onClose();
                         navigate('/recharge');
                       }}
                     >
                       <CreditCard className="h-4 w-4 mr-2" />
-                      立即充值
+                      {user.expireAt ? '续费会员' : '立即开通'}
                     </Button>
                   </CardContent>
                 </Card>

+ 2 - 47
src/client/home/pages/HomePage.tsx

@@ -282,14 +282,7 @@ export default function HomePage() {
                         <Check className="h-5 w-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" />
                         <span className="text-gray-700 text-sm">{feature}</span>
                       </li>
-                    )) || (
-                      <>
-                        <li className="flex items-start">
-                          <Check className="h-5 w-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" />
-                          <span className="text-gray-700 text-sm">基础功能</span>
-                        </li>
-                      </>
-                    )}
+                    ))}
                   </ul>
                   
                   <Button
@@ -300,45 +293,7 @@ export default function HomePage() {
                   </Button>
                 </CardContent>
               </Card>
-            )) || (
-              // 默认套餐展示
-              <>
-                {[
-                  { name: '单次会员', price: 27, duration: '24小时有效', features: ['基础功能', '24小时使用'], icon: Clock, color: 'text-orange-600' },
-                  { name: '单月会员', price: 86, duration: '30天有效', features: ['全部功能', '无限制使用'], icon: Calendar, color: 'text-blue-600' },
-                  { name: '年会员', price: 286, duration: '365天有效', features: ['全部功能', '无限制使用', '优先支持'], icon: Calendar, color: 'text-green-600' },
-                  { name: '永久会员', price: 688, duration: '永久有效', features: ['全部功能', '永久使用', '终身更新'], icon: Infinity, color: 'text-purple-600' }
-                ].map((plan, index) => (
-                  <Card key={index} className="border-0 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-1">
-                    <CardHeader>
-                      <div className="text-center">
-                        <h3 className="text-2xl font-bold mb-2">{plan.name}</h3>
-                        <div className="text-4xl font-bold text-blue-600 mb-2">
-                          ¥{plan.price}
-                        </div>
-                        <p className="text-gray-600">{plan.duration}</p>
-                      </div>
-                    </CardHeader>
-                    <CardContent>
-                      <ul className="space-y-3">
-                        {plan.features.map((feature, idx) => (
-                          <li key={idx} className="flex items-start">
-                            <Check className="h-5 w-5 text-green-500 mr-2 flex-shrink-0 mt-0.5" />
-                            <span className="text-gray-700 text-sm">{feature}</span>
-                          </li>
-                        ))}
-                      </ul>
-                      <Button
-                        className="w-full mt-6 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
-                        onClick={() => navigate('/pricing')}
-                      >
-                        立即选择
-                      </Button>
-                    </CardContent>
-                  </Card>
-                ))}
-              </>
-            )}
+            ))}
           </div>
         </div>
       </div>

+ 41 - 4
src/client/home/pages/MemberPage.tsx

@@ -197,6 +197,43 @@ const MemberPage: React.FC = () => {
             </CardContent>
           </Card>
 
+          {/* 会员信息 */}
+          <Card className="border-0 shadow-lg">
+            <CardHeader>
+              <CardTitle className="flex items-center space-x-2">
+                <Clock className="h-5 w-5" />
+                <span>会员到期时间</span>
+              </CardTitle>
+            </CardHeader>
+            <CardContent className="space-y-4">
+              <div className="space-y-2">
+                <div className="flex items-center justify-between">
+                  <span className="text-sm text-gray-500">到期时间</span>
+                  <span className="font-semibold">
+                    {user.expireAt ? format(new Date(user.expireAt), 'yyyy年MM月dd日', { locale: zhCN }) : '未开通'}
+                  </span>
+                </div>
+                <div className="flex items-center justify-between">
+                  <span className="text-sm text-gray-500">剩余天数</span>
+                  <span className="font-semibold">
+                    {user.expireAt ? `${Math.ceil((new Date(user.expireAt).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))} 天` : '0 天'}
+                  </span>
+                </div>
+                <div className="flex items-center justify-between">
+                  <span className="text-sm text-gray-500">剩余次数</span>
+                  <span className="font-semibold">{user.remainingCount || 0} 次</span>
+                </div>
+              </div>
+              <Button
+                className="w-full"
+                onClick={() => navigate('/recharge')}
+              >
+                <CreditCard className="h-4 w-4 mr-2" />
+                {user.expireAt ? '续费会员' : '立即开通'}
+              </Button>
+            </CardContent>
+          </Card>
+
           {/* 设置区域 */}
           <Card className="border-0 shadow-lg">
             <CardHeader>
@@ -206,8 +243,8 @@ const MemberPage: React.FC = () => {
               </CardTitle>
             </CardHeader>
             <CardContent className="space-y-4">
-              <Button 
-                variant="outline" 
+              <Button
+                variant="outline"
                 className="w-full justify-start"
                 onClick={() => navigate('/profile/security')}
               >
@@ -215,8 +252,8 @@ const MemberPage: React.FC = () => {
                 <span>安全设置</span>
               </Button>
               
-              <Button 
-                variant="outline" 
+              <Button
+                variant="outline"
                 className="w-full justify-start"
                 onClick={() => navigate('/profile/preferences')}
               >

+ 54 - 77
src/client/home/pages/PricingPage.tsx

@@ -4,67 +4,31 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/cli
 import { Badge } from '@/client/components/ui/badge';
 import { Check, Star } from 'lucide-react';
 import { useNavigate } from 'react-router-dom';
+import { useQuery } from '@tanstack/react-query';
+import { membershipPlanClient } from '@/client/api';
 
 export default function PricingPage() {
   const navigate = useNavigate();
-  const [selectedPlan, setSelectedPlan] = useState<string>('standard');
-
-  const plans = [
-    {
-      id: 'basic',
-      name: '基础版',
-      price: '9.9',
-      period: '/月',
-      description: '适合个人用户和小型团队',
-      features: [
-        '每月100份文档处理',
-        '基础模板支持',
-        '标准图片压缩',
-        '邮件技术支持',
-        '5天数据保留'
-      ],
-      popular: false,
-      color: 'border-gray-200'
-    },
-    {
-      id: 'standard',
-      name: '标准版',
-      price: '29.9',
-      period: '/月',
-      description: '适合中小企业和团队使用',
-      features: [
-        '每月500份文档处理',
-        '高级模板库',
-        '智能图片处理',
-        '批量压缩打包',
-        '优先技术支持',
-        '30天数据保留',
-        'API接口调用'
-      ],
-      popular: true,
-      color: 'border-blue-500'
+  
+  const { data: membershipPlans } = useQuery({
+    queryKey: ['membership-plans-pricing'],
+    queryFn: async () => {
+      const response = await membershipPlanClient.$get();
+      if (!response.ok) throw new Error('获取套餐失败');
+      const data = await response.json();
+      return data.data.filter((plan: any) => plan.isActive === 1).sort((a: any, b: any) => a.sortOrder - b.sortOrder);
     },
-    {
-      id: 'professional',
-      name: '专业版',
-      price: '99.9',
-      period: '/月',
-      description: '适合大型企业和专业用户',
-      features: [
-        '无限文档处理',
-        '全部模板库',
-        'AI智能优化',
-        '自定义模板制作',
-        '专属技术支持',
-        '永久数据保留',
-        'API无限制调用',
-        '团队协作功能',
-        '数据统计分析'
-      ],
-      popular: false,
-      color: 'border-purple-500'
-    }
-  ];
+  });
+
+  const getTypeLabel = (type: string) => {
+    const labels = {
+      single: '单次',
+      monthly: '单月',
+      yearly: '年',
+      lifetime: '永久',
+    };
+    return labels[type as keyof typeof labels] || type;
+  };
 
   return (
     <div className="min-h-screen bg-gradient-to-br from-slate-50 via-white to-blue-50">
@@ -128,40 +92,53 @@ export default function PricingPage() {
 
       {/* Pricing Cards */}
       <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-20">
-        <div className="grid md:grid-cols-3 gap-8">
-          {plans.map((plan) => (
-            <Card 
-              key={plan.id} 
-              className={`relative ${plan.color} ${plan.popular ? 'scale-105' : ''} transition-transform duration-300`}
+        <div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
+          {membershipPlans?.map((plan) => (
+            <Card
+              key={plan.id}
+              className={`relative border-0 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-1 ${
+                plan.type === 'yearly' ? 'ring-2 ring-blue-500' : ''
+              }`}
             >
-              {plan.popular && (
-                <Badge className="absolute -top-3 left-1/2 transform -translate-x-1/2 bg-blue-500 text-white">
+              {plan.type === 'yearly' && (
+                <Badge className="absolute -top-3 left-1/2 transform -translate-x-1/2 bg-gradient-to-r from-blue-600 to-purple-600 text-white">
                   <Star className="h-4 w-4 mr-1" />
                   推荐
                 </Badge>
               )}
               
               <CardHeader>
-                <CardTitle className="text-2xl">{plan.name}</CardTitle>
-                <CardDescription>{plan.description}</CardDescription>
-                <div className="mt-4">
-                  <span className="text-4xl font-bold text-gray-900">¥{plan.price}</span>
-                  <span className="text-gray-600">{plan.period}</span>
+                <div className="text-center">
+                  <CardTitle className="text-2xl mb-2">{plan.name}</CardTitle>
+                  <CardDescription className="text-sm">{plan.description}</CardDescription>
+                  <div className="mt-4">
+                    <span className="text-4xl font-bold text-gray-900">¥{plan.price}</span>
+                  </div>
+                  <p className="text-gray-600 text-sm mt-2">
+                    {plan.durationDays === 0 ? '永久有效' :
+                     plan.durationDays === 1 ? '24小时有效' :
+                     `${plan.durationDays}天有效`}
+                  </p>
                 </div>
               </CardHeader>
               
               <CardContent>
-                <ul className="space-y-3">
-                  {plan.features.map((feature, index) => (
-                    <li key={index} className="flex items-center">
-                      <Check className="h-5 w-5 text-green-500 mr-2" />
-                      <span className="text-gray-700">{feature}</span>
+                <ul className="space-y-2">
+                  {plan.features?.map((feature: string, index: number) => (
+                    <li key={index} className="flex items-start">
+                      <Check className="h-4 w-4 text-green-500 mr-2 flex-shrink-0 mt-0.5" />
+                      <span className="text-gray-700 text-sm">{feature}</span>
+                    </li>
+                  )) || (
+                    <li className="flex items-start">
+                      <Check className="h-4 w-4 text-green-500 mr-2 flex-shrink-0 mt-0.5" />
+                      <span className="text-gray-700 text-sm">基础功能</span>
                     </li>
-                  ))}
+                  )}
                 </ul>
                 
-                <Button 
-                  className={`w-full mt-6 ${plan.popular ? 'bg-blue-600 hover:bg-blue-700' : ''}`}
+                <Button
+                  className="w-full mt-6 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700"
                   onClick={() => navigate('/register')}
                 >
                   立即选择

+ 15 - 9
src/client/home/pages/RechargePage.tsx

@@ -86,22 +86,28 @@ export default function RechargePage() {
         {/* 当前账户状态 */}
         <Card>
           <CardHeader>
-            <CardTitle>账户状态</CardTitle>
-            <CardDescription>您的当前账户信息</CardDescription>
+            <CardTitle>会员状态</CardTitle>
+            <CardDescription>您的当前会员信息</CardDescription>
           </CardHeader>
           <CardContent className="space-y-4">
             <div className="grid grid-cols-2 gap-4">
               <div>
-                <Label>当前余额</Label>
-                <p className="text-2xl font-bold text-blue-600">{user.remainingCount || 0} 份</p>
+                <Label>到期时间</Label>
+                <p className="text-2xl font-bold text-blue-600">
+                  {user.expireAt ? new Date(user.expireAt).toLocaleDateString('zh-CN') : '未开通'}
+                </p>
               </div>
               <div>
-                <Label>会员等级</Label>
-                <p className="text-lg font-semibold">
-                  {user.userType === 'premium' ? '高级会员' : '普通会员'}
-                </p>
+                <Label>剩余次数</Label>
+                <p className="text-lg font-semibold">{user.remainingCount || 0} 次</p>
               </div>
             </div>
+            <div>
+              <Label>会员等级</Label>
+              <p className="text-lg font-semibold">
+                {user.userType === 'premium' ? '高级会员' : '普通会员'}
+              </p>
+            </div>
           </CardContent>
         </Card>
 
@@ -182,7 +188,7 @@ export default function RechargePage() {
                 <div className="bg-blue-50 p-4 rounded-lg">
                   <p className="text-sm text-blue-800">
                     <Check className="h-4 w-4 inline mr-1" />
-                    购买后立即到账,永久有效,支持随时使用
+                    购买后立即到账,有效期为一年,支持随时使用
                   </p>
                 </div>
               </div>