Sfoglia il codice sorgente

feat: 购买页面

BaiLuoYan 1 mese fa
parent
commit
5c692c8962

+ 14 - 6
.cursor/rules/home-web-coder.mdc

@@ -13,13 +13,21 @@ alwaysApply: true
 
 - **严格遵循目录结构与开发流程**。
 - **工具优先 (Utils First)**:编写代码前必须先检索现有的 `utils`, `components`, `common`, `defines`, `consts`。**优先复用,缺失时再补充通用函数**,禁止直接在业务逻辑中硬编码通用逻辑。
-- **Lint & Cleanup Mandatory**:每次代码修改完成后,必须进行 Lint 检查,并 **彻底删除无用代码**。禁止提交包含 Lint 错误、格式警告、或残留冗余逻辑的代码。
+- **强制清理 (Lint & Cleanup Mandatory)**:每次代码修改完成后,必须进行 Lint 检查,并 **彻底删除无用代码**。禁止提交包含 Lint 错误、格式警告、或残留冗余逻辑的代码。
 - **类型安全**:全量使用 TS 接口 (Interface)。
 - **全量国际化 (i18n Mandatory)**:**禁止在页面或组件中硬编码任何文本内容**。所有展示给用户的字符串必须通过多语言方案实现,统一在 `locales` 中定义并使用翻译 Hook 调用。
 
 ## 代码编写原则 (Code Construction Rules)
 
-### 1. 通用与风格 (Zero Waste Policy)
+### 1. 导入顺序规范 (Import Order Rule)
+  
+- **必须严格按以下顺序排列,组与组之间空一行:**
+  - 1、**React 核心**:`react`, `react-dom` 及其 Hooks。
+  - 2、**第三方库**:如 `lodash`, `ramda`, `antd`, `lucide-react` 等。
+  - 3、**内部别名 (@/)**:按 `@/defines` -> `@/components` -> `@/hooks` -> `@/utils` -> `@/services` -> `@/assets` 排序。
+  - 4、**相对路径**:`./` 或 `../` 开头的文件。
+
+### 2. 通用与风格 (Zero Waste Policy)
 
 - **范式**:函数式/声明式编程,**严禁使用 Class**。
 - **模式**:采用 **RORO** (Receive Object, Return Object) 模式。
@@ -45,7 +53,7 @@ alwaysApply: true
   - 分号:**禁止省略分号**。
   - 条件语句:单行省略花括号 (如 `if (cond) return;`),**禁止使用 else** (改用 Early Return)。
 
-### 2. 组件逻辑拆分规范 (Logic Separation)
+### 3. 组件逻辑拆分规范 (Logic Separation)
 
 为了保持代码的可读性与组织性,必须遵循以下结构规范:
 
@@ -62,10 +70,10 @@ alwaysApply: true
     └── types.ts (可选)
   ```
 
-### 3. TypeScript & React & 样式
+### 4. TypeScript & React & 样式
 
 - **图标使用 (Icon Strategy)**:
-  1. 将 SVG 放入 `assets/iconify/` 对应目录 (多色图标:multi-color,单色图标:single-color)。
+  1. 将 SVG 放入 `assets/iconify/` 对应目录 (**多色图标:`multi-color`****单色图标:`single-color`**)。
   2. 代码导入:`import logo from '@/assets/iconify/...';`
   3. 使用:`<Icon icon={logo} />`。
 - **样式与布局规范**:
@@ -80,7 +88,7 @@ alwaysApply: true
   - **必须使用 Function Components**。
   - 最小化 `use client`, `useEffect`, `setState`。
 
-### 4. 错误处理 (Error Handling)
+### 5. 错误处理 (Error Handling)
 
 - **Guard Clauses**:在函数开头处理错误/边缘情况并尽早返回。
 - **Happy Path**:核心成功逻辑放在函数最底部。

+ 1 - 0
src/assets/iconify/multi-color/alipay.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769597925962" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4129" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1024.0512 701.0304V196.864A196.9664 196.9664 0 0 0 827.136 0H196.864A196.9664 196.9664 0 0 0 0 196.864v630.272A196.9152 196.9152 0 0 0 196.864 1024h630.272a197.12 197.12 0 0 0 193.8432-162.0992c-52.224-22.6304-278.528-120.32-396.4416-176.64-89.7024 108.6976-183.7056 173.9264-325.3248 173.9264s-236.1856-87.2448-224.8192-194.048c7.4752-70.0416 55.552-184.576 264.2944-164.9664 110.08 10.3424 160.4096 30.8736 250.1632 60.5184 23.1936-42.5984 42.496-89.4464 57.1392-139.264H248.064v-39.424h196.9152V311.1424H204.8V267.776h240.128V165.632s2.1504-15.9744 19.8144-15.9744h98.4576V267.776h256v43.4176h-256V381.952h208.8448a805.9904 805.9904 0 0 1-84.8384 212.6848c60.672 22.016 336.7936 106.3936 336.7936 106.3936zM283.5456 791.6032c-149.6576 0-173.312-94.464-165.376-133.9392 7.8336-39.3216 51.2-90.624 134.4-90.624 95.5904 0 181.248 24.4736 284.0576 74.5472-72.192 94.0032-160.9216 150.016-253.0816 150.016z" fill="#009FE8" p-id="4130"></path></svg>

File diff suppressed because it is too large
+ 0 - 0
src/assets/iconify/multi-color/applepay.svg


File diff suppressed because it is too large
+ 0 - 0
src/assets/iconify/multi-color/googlePay.svg


+ 1 - 0
src/assets/iconify/multi-color/paypal.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769598465587" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="20262" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M841.813333 253.269333c-0.938667 5.888-2.005333 11.946667-3.2 18.176-40.576 209.749333-179.498667 282.24-356.864 282.24H391.424a43.946667 43.946667 0 0 0-43.349333 37.376l-46.250667 295.210667-13.098667 83.626667a23.338667 23.338667 0 0 0 22.784 26.88h160.213334a38.613333 38.613333 0 0 0 38.058666-32.682667l1.578667-8.192 30.165333-192.64 1.962667-10.538667a38.613333 38.613333 0 0 1 38.058667-32.768h23.936c155.221333 0 276.736-63.445333 312.234666-246.954666 14.848-76.672 7.168-140.714667-32.085333-185.728a153.301333 153.301333 0 0 0-43.861333-34.005334z" fill="#179BD7" p-id="20263"></path><path d="M799.317333 236.202667a318.464 318.464 0 0 0-39.509333-8.789334 498.474667 498.474667 0 0 0-79.616-5.845333h-241.28a38.528 38.528 0 0 0-38.058667 32.768L349.525333 581.546667l-1.450666 9.514666a43.946667 43.946667 0 0 1 43.349333-37.376h90.325333c177.365333 0 316.288-72.533333 356.864-282.24 1.237333-6.229333 2.261333-12.288 3.157334-18.176a215.68 215.68 0 0 0-42.453334-17.066666z" fill="#222D65" p-id="20264"></path><path d="M400.853333 254.293333a38.442667 38.442667 0 0 1 38.058667-32.725333h241.28c28.586667 0 55.296 1.877333 79.616 5.845333 16.512 2.602667 32.768 6.528 48.64 11.690667 11.946667 3.968 23.082667 8.704 33.365333 14.165333 12.074667-77.525333-0.085333-130.304-41.770666-178.133333C754.133333 22.528 671.274667 0 565.248 0H257.365333c-21.632 0-40.106667 15.872-43.477333 37.418667L85.674667 855.466667a26.752 26.752 0 0 0 5.973333 21.461333c5.034667 5.888 12.373333 9.301333 20.096 9.344h190.08l47.701333-304.725333 51.328-327.253334z" fill="#253B80" p-id="20265"></path></svg>

+ 1 - 0
src/assets/iconify/multi-color/wechatpay.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1769600119362" class="icon" viewBox="0 0 1177 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21239" xmlns:xlink="http://www.w3.org/1999/xlink" width="229.8828125" height="200"><path d="M428.288 647.7312a39.6544 39.6544 0 0 1-17.664 4.1728 39.3216 39.3216 0 0 1-34.2528-19.7888l-2.56-5.5552-107.2128-231.808a19.9936 19.9936 0 0 1-1.8688-8.1664c0-10.6752 8.7808-19.328 19.6352-19.328 4.4032 0 8.4736 1.4336 11.776 3.84l126.464 88.7552a59.1616 59.1616 0 0 0 52.2752 5.888L1069.7472 204.8C963.1232 80.9472 787.5328 0 588.8 0 263.6032 0 0 216.5248 0 483.584c0 145.7408 79.3088 276.8896 203.4432 365.5424 9.984 7.0144 16.4864 18.5088 16.4864 31.5136 0 4.3008-0.9472 8.2432-2.0736 12.3136-9.9072 36.48-25.7792 94.8224-26.5472 97.536-1.2288 4.608-3.1488 9.3696-3.1488 14.1824 0 10.6496 8.7808 19.328 19.6352 19.328 4.2496 0 7.7312-1.5616 11.3408-3.584l128.896-73.3696c9.7024-5.504 19.968-8.9344 31.2576-8.9344 6.0416 0 11.8528 0.9216 17.3056 2.56A705.6128 705.6128 0 0 0 588.8 967.1936c325.1712 0 588.8-216.4992 588.8-483.584 0-80.896-24.32-157.0816-67.072-224.1024l-677.9392 385.792-4.3008 2.432z" fill="#00C800" p-id="21240"></path></svg>

+ 1 - 0
src/defines/index.ts

@@ -1,2 +1,3 @@
 export * from './errorShowType';
 export * from './planTagType';
+export * from './payMethodType';

+ 7 - 0
src/defines/payMethodType.ts

@@ -0,0 +1,7 @@
+export enum PayMethodType {
+    WECHAT = 1,
+    ALIPAY = 2,
+    PAYPAL = 4,
+    APPLE_PAY = 5,
+    GOOGLE_PAY = 6,
+}

+ 11 - 6
src/locales/en-US/pages.ts

@@ -22,17 +22,22 @@ export default {
         title: 'Purchase NOMO VPN Plan',
         selecPlan: 'Select a plan that suits you',
         selectPayMethod: 'Select your payment method',
-        orderSummary: {
+        userInfo: {
             account: 'Your Account',
-            paymentMethod: 'Payment Method',
+            planExpireDate: 'Plan Expire Date',
+        },
+        payMethod: {
+            wechat: 'WeChat',
+            alipay: 'Alipay',
+            paypal: 'PayPal',
+            applePay: 'Apple Pay',
+            googlePay: 'Google Pay',
+        },
+        orderSummary: {
             orderTotal: 'Order Total',
             terms: 'Continue to pay, you agree to our service terms',
             goPayNow: 'Pay Now',
         },
-        userInfo: {
-            account: 'Your Account',
-            planExpireDate: 'Plan Expire Date',
-        },
     },
 
     // 以下是 框架功能演示 页面的翻译

+ 11 - 6
src/locales/fa-IR/pages.ts

@@ -22,17 +22,22 @@ export default {
         title: 'خرید پلن NOMO VPN',
         selecPlan: 'یک پلن را انتخاب کنید که به شما مناسب است',
         selectPayMethod: 'روش پرداخت خود را انتخاب کنید',
-        orderSummary: {
+        userInfo: {
             account: 'حساب شما',
-            paymentMethod: 'روش پرداخت',
+            planExpireDate: 'تاریخ انقضا پلن',
+        },
+        payMethod: {
+            wechat: 'واتس‌آپ',
+            alipay: 'آلی‌پی',
+            paypal: 'پی‌پال',
+            applePay: 'اپل‌پی',
+            googlePay: 'گوگل‌پی',
+        },
+        orderSummary: {
             orderTotal: 'مجموع سفارش',
             terms: 'با ادامه پرداخت، شما به طور پیش‌فرض با شرایط خدمات ما موافقت می‌کنید.',
             goPayNow: 'پرداخت کن',
         },
-        userInfo: {
-            account: 'حساب شما',
-            planExpireDate: 'تاریخ انقضا پلن',
-        },
     },
 
     // 以下是 框架功能演示 页面的翻译

+ 11 - 6
src/locales/zh-CN/pages.ts

@@ -22,17 +22,22 @@ export default {
         title: '购买 NOMO VPN 套餐',
         selecPlan: '选择一个适合您的套餐',
         selectPayMethod: '选择您的支付方式',
-        orderSummary: {
+        userInfo: {
             account: '您的账户',
-            paymentMethod: '支付方式',
+            planExpireDate: '套餐到期日期',
+        },
+        payMethod: {
+            wechat: '微信',
+            alipay: '支付宝',
+            paypal: 'PayPal',
+            applePay: 'Apple Pay',
+            googlePay: 'Google Pay',
+        },
+        orderSummary: {
             orderTotal: '订单总额',
             terms: '继续支付,您同意我们的服务条款',
             goPayNow: '立即支付',
         },
-        userInfo: {
-            account: '您的账户',
-            planExpireDate: '套餐到期日期',
-        },
     },
 
     // 以下是 框架功能演示 页面的翻译

+ 53 - 0
src/pages/pricing/components/PayMethodCard/index.tsx

@@ -0,0 +1,53 @@
+import { memo } from 'react';
+
+import { Icon } from '@iconify/react';
+
+import type { PayMethodType } from '@/defines';
+import { useResponsive } from '@/hooks/useResponsive';
+import checkOnIcon from '@/assets/iconify/multi-color/check-on.svg';
+import checkOffIcon from '@/assets/iconify/multi-color/check-off.svg';
+
+import { useService } from './useService';
+
+export interface PlanCardProps {
+    payMethodType: PayMethodType;
+    isSelected?: boolean;
+    onClick?: () => void;
+}
+
+const PlanCard = memo(({ payMethodType, isSelected = false, onClick }: PlanCardProps) => {
+    const { isMobile } = useResponsive();
+    const { payMethodInfo } = useService(payMethodType);
+
+    return (
+        <div
+            className={`relative box-border cursor-pointer transition-all bg-white/10 border-2 flex-row-bc ${
+                isMobile
+                    ? 'py-3 px-4 w-full rounded-lg'
+                    : 'px-8 py-6 w-full max-w-[574px] rounded-2xl gap-4'
+            } ${
+                isSelected
+                    ? 'border-[#0FA4E9]'
+                    : `${isMobile ? 'border-transparent' : 'border-white/10'}`
+            }`}
+            onClick={onClick}
+        >
+            <div className="flex items-center gap-2">
+                <div className="w-[50px] h-8 rounded-[4px] border border-[#d9d9d9] bg-white flex items-center justify-center">
+                    <Icon icon={payMethodInfo.icon} className="h-5 w-auto" />
+                </div>
+                <span className="text-white text-2xl font-bold leading-8 whitespace-nowrap">
+                    {payMethodInfo.name}
+                </span>
+            </div>
+            <Icon
+                icon={isSelected ? checkOnIcon : checkOffIcon}
+                className={`w-6 h-6 flex-shrink-0`}
+            />
+        </div>
+    );
+});
+
+PlanCard.displayName = 'PlanCard';
+
+export default PlanCard;

+ 66 - 0
src/pages/pricing/components/PayMethodCard/useService.tsx

@@ -0,0 +1,66 @@
+import { useMemo } from 'react';
+
+import { useTranslation } from 'react-i18next';
+import type { IconifyIcon } from '@iconify/react';
+import logosApple from '@/assets/iconify/multi-color/applepay.svg';
+import logosGooglePay from '@/assets/iconify/multi-color/googlepay.svg';
+import logosPaypal from '@/assets/iconify/multi-color/paypal.svg';
+import siAlipay from '@/assets/iconify/multi-color/alipay.svg';
+import siWechat from '@/assets/iconify/multi-color/wechatpay.svg';
+
+import { PayMethodType } from '@/defines';
+
+export interface PayMethodInfo {
+    payMethodType: PayMethodType;
+    name: string;
+    icon: IconifyIcon;
+}
+
+export function useService(payMethodType: PayMethodType) {
+    const { t } = useTranslation();
+
+    const payMethodInfo = useMemo<PayMethodInfo>((): PayMethodInfo => {
+        switch (payMethodType) {
+            case PayMethodType.WECHAT:
+                return {
+                    payMethodType: PayMethodType.WECHAT,
+                    name: t('pages.pricing.payMethod.wechat'),
+                    icon: siWechat,
+                };
+            case PayMethodType.ALIPAY:
+                return {
+                    payMethodType: PayMethodType.ALIPAY,
+                    name: t('pages.pricing.payMethod.alipay'),
+                    icon: siAlipay,
+                };
+            case PayMethodType.PAYPAL:
+                return {
+                    payMethodType: PayMethodType.PAYPAL,
+                    name: t('pages.pricing.payMethod.paypal'),
+                    icon: logosPaypal,
+                };
+            case PayMethodType.APPLE_PAY:
+                return {
+                    payMethodType: PayMethodType.APPLE_PAY,
+                    name: t('pages.pricing.payMethod.applePay'),
+                    icon: logosApple,
+                };
+            case PayMethodType.GOOGLE_PAY:
+                return {
+                    payMethodType: PayMethodType.GOOGLE_PAY,
+                    name: t('pages.pricing.payMethod.googlePay'),
+                    icon: logosGooglePay,
+                };
+            default:
+                return {
+                    payMethodType: PayMethodType.WECHAT,
+                    name: t('pages.pricing.payMethod.wechat'),
+                    icon: siWechat,
+                };
+        }
+    }, [payMethodType, t]);
+
+    return {
+        payMethodInfo,
+    };
+}

+ 35 - 14
src/pages/pricing/index.tsx

@@ -1,17 +1,23 @@
 import { memo } from 'react';
-import UserInfo from './components/UserInfo';
-import PlanCard from './components/PlanCard';
-import OrderSummary from './components/OrderSummary';
+
 import { useTranslation } from 'react-i18next';
+
 import { useResponsive } from '@/hooks/useResponsive';
+
+import OrderSummary from './components/OrderSummary';
+import PayMethodCard from './components/PayMethodCard';
+import PlanCard from './components/PlanCard';
+import UserInfo from './components/UserInfo';
 import { useAction } from './useAction';
 import { useService } from './useService';
 
 const Pricing = memo(() => {
     const { t } = useTranslation();
     const { isMobile } = useResponsive();
-    const { selectedPlanId, handlePlanClick } = useAction();
-    const { plans } = useService();
+    const { selectedPlanId, handlePlanClick, selectedPayMethod, handlePayMethodClick } =
+        useAction();
+
+    const { plans, payMethods } = useService();
 
     const selectedPlan = plans.find((plan) => plan.id === selectedPlanId) || null;
 
@@ -35,9 +41,7 @@ const Pricing = memo(() => {
                         </span>
                         <div
                             className={`flex ${
-                                isMobile
-                                    ? 'flex-col items-center gap-5'
-                                    : 'flex-row items-center justify-center gap-8'
+                                isMobile ? 'flex-col-c gap-5' : 'flex-row-c justify-center gap-8'
                             }`}
                         >
                             {plans.map((plan) => (
@@ -55,12 +59,29 @@ const Pricing = memo(() => {
                             ))}
                         </div>
                     </div>
-                    <span
-                        className={`text-white font-semibold leading-[1.43] ${isMobile ? 'text-[16px]' : 'text-[22px]'}`}
-                    >
-                        {t('pages.pricing.selectPayMethod')}
-                    </span>
-                    <div className="flex items-center justify-start"></div>
+                    <div className="flex flex-col gap-5">
+                        <span
+                            className={`text-white font-semibold leading-[1.43] ${isMobile ? 'text-[16px]' : 'text-[22px]'}`}
+                        >
+                            {t('pages.pricing.selectPayMethod')}
+                        </span>
+                        <div
+                            className={
+                                isMobile
+                                    ? 'flex-col-c gap-5'
+                                    : 'flex flex-wrap justify-start gap-x-8 gap-y-4 [&>*]:flex-[0_1_calc(50%-1rem)]'
+                            }
+                        >
+                            {payMethods.map((payMethod) => (
+                                <PayMethodCard
+                                    key={payMethod}
+                                    payMethodType={payMethod}
+                                    isSelected={selectedPayMethod === payMethod}
+                                    onClick={() => handlePayMethodClick(payMethod)}
+                                />
+                            ))}
+                        </div>
+                    </div>
                     <OrderSummary selectedPlan={selectedPlan} />
                 </div>
             </div>

+ 11 - 0
src/pages/pricing/useAction.ts

@@ -1,19 +1,30 @@
 import { useState, useCallback } from 'react';
 
+import { PayMethodType } from '@/defines';
+
 export interface UseActionReturn {
     selectedPlanId: number | null;
     handlePlanClick: (planId: number) => void;
+    selectedPayMethod: PayMethodType | null;
+    handlePayMethodClick: (payMethodId: PayMethodType) => void;
 }
 
 export function useAction(): UseActionReturn {
     const [selectedPlanId, setSelectedPlanId] = useState<number | null>(null);
+    const [selectedPayMethod, setSelectedPayMethod] = useState<PayMethodType | null>(null);
 
     const handlePlanClick = useCallback((planId: number) => {
         setSelectedPlanId(planId);
     }, []);
 
+    const handlePayMethodClick = useCallback((payMethodId: PayMethodType) => {
+        setSelectedPayMethod(payMethodId);
+    }, []);
+
     return {
         selectedPlanId,
         handlePlanClick,
+        selectedPayMethod,
+        handlePayMethodClick,
     };
 }

+ 14 - 1
src/pages/pricing/useService.ts

@@ -1,6 +1,6 @@
 import { useMemo } from 'react';
 
-import { PlanTagType } from '@/defines';
+import { PlanTagType, PayMethodType } from '@/defines';
 
 export interface Plan {
     id: number;
@@ -14,6 +14,7 @@ export interface Plan {
 
 export interface UseServiceReturn {
     plans: Plan[];
+    payMethods: PayMethodType[];
 }
 
 export function useService(): UseServiceReturn {
@@ -59,7 +60,19 @@ export function useService(): UseServiceReturn {
         []
     );
 
+    const payMethods = useMemo<PayMethodType[]>(
+        () => [
+            PayMethodType.WECHAT,
+            PayMethodType.ALIPAY,
+            PayMethodType.PAYPAL,
+            PayMethodType.APPLE_PAY,
+            PayMethodType.GOOGLE_PAY,
+        ],
+        []
+    );
+
     return {
         plans,
+        payMethods,
     };
 }

+ 28 - 29
types/typings.d.ts

@@ -1,45 +1,44 @@
-
 declare namespace API {
     interface Result<T = any> {
-      success?: boolean;
-      data?: T;
-      errorCode?: number;
-      errorMessage?: string;
-      showType?: ErrorShowType;
-      traceId?: string;
-      host?: string;
+        success?: boolean;
+        data?: T;
+        errorCode?: number;
+        errorMessage?: string;
+        showType?: ErrorShowType;
+        traceId?: string;
+        host?: string;
     }
-  
+
     type SvrResultList<T = any> = Result<{
-      total: number;
-      list: T[];
+        total: number;
+        list: T[];
     }>;
-  
+
     interface ResultList<T = any> extends Result<T[]> {
-      total: number;
+        total: number;
     }
-  
+
     interface AntdPageParams {
-      current?: number;
-      pageSize?: number;
-      keyword?: string;
-      [key: string]: any;
+        current?: number;
+        pageSize?: number;
+        keyword?: string;
+        [key: string]: any;
     }
-  
+
     type ReqList<T> = T & AntdPageParams;
-  
+
     interface SortBy {
-      column: string;
-      asc: boolean;
+        column: string;
+        asc: boolean;
     }
-  
+
     type SvrReqList<T> = T & {
-      page: number;
-      pageSize: number;
-      sortBy?: string | SortBy | SortBy[];
+        page: number;
+        pageSize: number;
+        sortBy?: string | SortBy | SortBy[];
     };
-  
+
     interface ReqDelete<T extends number | string = number> {
-      ids: T[];
+        ids: T[];
     }
-  }
+}

+ 16 - 0
types/vite-env.d.ts

@@ -1,5 +1,21 @@
 /// <reference types="vite/client" />
 
+/**
+ * 将 @/assets/iconify/single-color 目录下的所有 SVG 文件识别为 IconifyIcon
+ */
+declare module '@/assets/iconify/single-color/*.svg' {
+    const content: import('@iconify/react').IconifyIcon;
+    export default content;
+}
+
+/**
+ * 将 @/assets/iconify/multi-color 目录下的所有 SVG 文件识别为 IconifyIcon
+ */
+declare module '@/assets/iconify/multi-color/*.svg' {
+    const content: import('@iconify/react').IconifyIcon;
+    export default content;
+}
+
 type Recordable<T = any> = Record<string, T>;
 
 /**

Some files were not shown because too many files changed in this diff