Нема описа

BaiLuoYan a5f74b1af8 first commit пре 1 месец
.vscode a5f74b1af8 first commit пре 1 месец
build a5f74b1af8 first commit пре 1 месец
public a5f74b1af8 first commit пре 1 месец
src a5f74b1af8 first commit пре 1 месец
types a5f74b1af8 first commit пре 1 месец
.env a5f74b1af8 first commit пре 1 месец
.env.development a5f74b1af8 first commit пре 1 месец
.env.production a5f74b1af8 first commit пре 1 месец
.env.test a5f74b1af8 first commit пре 1 месец
.gitignore a5f74b1af8 first commit пре 1 месец
.markdownlint.json a5f74b1af8 first commit пре 1 месец
.npmrc a5f74b1af8 first commit пре 1 месец
.nvmrc a5f74b1af8 first commit пре 1 месец
.prettierrc a5f74b1af8 first commit пре 1 месец
.stylelintrc.mjs a5f74b1af8 first commit пре 1 месец
README.md a5f74b1af8 first commit пре 1 месец
commitlint.config.js a5f74b1af8 first commit пре 1 месец
eslint.config.js a5f74b1af8 first commit пре 1 месец
index.html a5f74b1af8 first commit пре 1 месец
package.json a5f74b1af8 first commit пре 1 месец
pnpm-lock.yaml a5f74b1af8 first commit пре 1 месец
postcss.config.js a5f74b1af8 first commit пре 1 месец
tailwind.config.js a5f74b1af8 first commit пре 1 месец
tsconfig.json a5f74b1af8 first commit пре 1 месец
vite.config.ts a5f74b1af8 first commit пре 1 месец

README.md

Visa Card H5

这是一个基于 React + TypeScript + Vite 的现代化 H5 项目。

环境要求

  • Node.js >= 18.20.7
  • pnpm >= 9
  • 现代浏览器支持

浏览器支持

  • Chrome >= 87
  • Firefox >= 78
  • Safari >= 14
  • Edge >= 88
  • Android >= 4.4
  • iOS >= 9

技术栈

  • 核心框架:React 18 + TypeScript
  • 构建工具:Vite 6
  • 包管理器:pnpm
  • UI 框架:Ant Design 5
  • 路由:React Router 7
  • 状态管理
    • 基于 React Context + Hooks 的自定义方案,类似 UmiJS max 中的 model
  • 样式解决方案
    • Tailwind CSS
    • SCSS
    • Less
  • 国际化:i18next
  • HTTP 客户端:Axios
  • 工具库
    • lodash-es
    • ramda
    • dayjs
  • 图标解决方案
    • @iconify/react
    • 自定义 Vite 插件(SVG 转 Iconify)
    • Ant Design Icon
  • 代码规范
    • ESLint
    • Prettier
    • Stylelint
    • Commitlint
  • Git 工作流
    • Husky
    • lint-staged

开发命令

# 安装依赖
pnpm install --frozen-lockfile

# 启动开发服务器
pnpm dev

# 构建生产环境
pnpm build

# 构建测试环境
pnpm build:test

# 预览构建结果
pnpm preview

# 代码检查
pnpm lint           # 检查 TypeScript/JavaScript 代码
pnpm lint:fix       # 自动修复 TypeScript/JavaScript 代码问题
pnpm stylelint      # 检查样式文件
pnpm stylelint:fix  # 自动修复样式文件问题

开发规范

  • 遵循 TypeScript 严格模式
  • 使用 ESLint 和 Prettier 进行代码格式化
  • 使用 Conventional Commits 规范提交信息
  • 组件采用函数式组件和 Hooks
  • 样式优先使用 Tailwind CSS,必要时使用 SCSS 或 Less

部署

项目支持多环境部署,通过 .env 文件配置不同环境的变量:

  • .env.development - 开发环境
  • .env.test - 测试环境
  • .env.production - 生产环境

项目结构

├── build/          # 构建相关配置和脚本
├── public/         # 静态资源目录
│   ├── favicon.ico # 网站图标
│   └── index.html  # HTML 模板
├── src/
│   ├── assets/     # 项目资源文件
│   │   ├── single-color/  # 单色图标
│   │   └── multi-color/   # 多色图标
│   ├── components/ # 公共组件
│   ├── config/     # 配置文件
│   │   └── request/      # 请求相关配置
│   │       ├── index.ts  # 请求基础配置
│   │       ├── authHeaderInterceptor.ts  # 认证拦截器
│   │       ├── encryptionInterceptors.ts # 加密解密拦截器
│   │       └── errorHandler.ts # 错误处理
│   ├── defines/    # 枚举和常量定义
│   │   ├── index.ts  # 导出入口
│   │   ├── errorShowType.ts # 错误展示类型枚举
│   │   └── ...  # 其他常量/枚举定义文件
│   ├── firebase.tsx # Firebase 配置
│   ├── i18n/       # 国际化配置
│   ├── layouts/    # 布局组件
│   ├── locales/    # 国际化语言包
│   ├── main.tsx    # 应用入口
│   ├── models/     # 数据模型
│   ├── pages/      # 页面组件
│   ├── router/     # 路由配置
│   ├── services/   # API 服务
│   ├── styles/     # 全局样式
│   ├── utils/      # 工具函数
│   └── App.tsx     # 根组件
├── types/          # TypeScript 类型定义
└── dist/           # 构建输出目录

目录说明

  • build/ - 包含构建相关的配置文件和脚本

    • 构建脚本
    • 环境配置
    • 部署配置
  • public/ - 静态资源目录

    • 存放不需要通过构建工具处理的静态文件
    • 包含网站图标、HTML 模板等
    • 构建时会被直接复制到输出目录
  • src/ - 源代码目录

    • 包含所有需要编译的源代码
    • 按功能模块组织代码结构
  • src/pages/ - 页面组件目录

    • 遵循"一个页面一个目录"的原则
    • 每个页面的目录结构应该类似下面的结构:

      pages/
      ├── home/                # 首页
      │   ├── index.tsx       # 页面主组件
      │   ├── components/     # 页面级组件
      │   ├── hooks/         # 页面级 Hooks
      │   ├── styles/        # 页面级样式
      │   └── types.ts       # 页面级类型定义
      ├── about/             # 关于页面
      │   ├── index.tsx
      │   ├── components/
      │   ├── hooks/
      │   ├── styles/
      │   └── types.ts
      └── ...
      
    • 页面级组件、Hooks、样式等资源应放在对应页面目录下

    • 页面间共享的组件应放在 src/components 目录下

  • types/ - TypeScript 类型定义目录

    • 存放全局类型声明文件
    • 第三方库的类型定义
  • dist/ - 构建输出目录

    • 存放构建后的生产环境代码
    • 包含优化后的静态资源
    • 部署时使用此目录的内容

状态管理

项目使用基于 React Context 和 Hooks 的 Model 方案进行状态管理,类似 UmiJS Max 的 model 方案。

目录结构

src/
  └── models/              # 模型目录
      ├── userModel.ts    # 用户模型
      └── ...            # 其他模型

使用示例

  1. 创建模型:
// src/models/userModel.ts
import { useState } from 'react';
import { createModel } from '@/utils/model/createModel';

// 定义状态类型
interface UserState {
    name: string;
    age: number;
}

// 创建自定义 Hook
const useUserModel = () => {
    const [state, setState] = useState<UserState>({
        name: 'John Doe',
        age: 30
    });

    const updateName = (name: string) => {
        setState(prev => ({ ...prev, name }));
    };

    const updateAge = (age: number) => {
        setState(prev => ({ ...prev, age }));
    };

    const incrementAge = () => {
        setState(prev => ({ ...prev, age: prev.age + 1 }));
    };

    return {
        ...state,
        updateName,
        updateAge,
        incrementAge
    };
};

// 创建 model
export const userModel = createModel(useUserModel, 'user');
  1. 在组件中使用:
// src/pages/home/index.tsx
import { userModel } from '@/models/userModel';

const Home = () => {
    const user = userModel.useModel();
    
    return (
        <div>
            <h1>Hello, {user.name}!</h1>
            <p>Age: {user.age}</p>
            <button onClick={() => user.updateName('张三')}>修改名字</button>
            <button onClick={() => user.incrementAge()}>年龄+1</button>
        </div>
    );
};

图标使用说明

项目使用了自定义的 Vite 插件,可以将指定目录的 SVG 图标转换为 Iconify 格式。

使用方法

  1. 将需要转换的图标文件存放在以下目录:
  • src/assets/single-color/ - 单色图标
  • src/assets/multi-color/ - 多色图标
  1. 直接导入图标:
import singleColorIcon from '@/assets/single-color/icon.svg';
import multiColorIcon from '@/assets/multi-color/icon.svg';
  1. 在组件中使用:
import { Icon } from '@iconify/react';

// 单色图标
<Icon icon={singleColorIcon} className="text-red-500" />

// 多色图标
<Icon icon={multiColorIcon} />

图标特性

  • 单色图标可以通过 classNamecolor 属性修改颜色
  • 多色图标保持原始颜色
  • 支持自定义大小(通过 widthheight 属性)
  • 支持所有 Iconify 组件的属性

多语言使用说明

项目使用 i18next 实现多语言支持,目前支持英文和波斯语。

目录结构

src/
├── i18n/           # i18n 配置
│   └── index.ts    # i18n 初始化配置
└── locales/        # 语言包
    ├── en-US.ts    # 英文语言包入口
    ├── fa-IR.ts    # 波斯语语言包入口
    ├── en-US/      # 英文语言包模块
    │   ├── common.ts     # 通用翻译
    │   ├── components.ts # 组件相关翻译
    │   ├── menus.ts      # 菜单相关翻译
    │   └── pages.ts      # 页面相关翻译
    └── fa-IR/      # 波斯语语言包模块
        ├── common.ts     # 通用翻译
        ├── components.ts # 组件相关翻译
        ├── menus.ts      # 菜单相关翻译
        └── pages.ts      # 页面相关翻译

翻译文件说明

  1. common.ts - 通用翻译

    • 包含按钮文本、提示信息、错误信息等通用文案
    • 适用于整个应用的共享文本
  2. components.ts - 组件相关翻译

    • 包含各个组件的标签、提示、占位符等文本
    • 按组件名称组织翻译 key
  3. menus.ts - 菜单相关翻译

    • 包含导航菜单、侧边栏等菜单项的文本
    • 按菜单层级组织翻译 key
  4. pages.ts - 页面相关翻译

    • 包含各个页面的标题、描述、提示等文本
    • 按页面名称组织翻译 key

使用方法

  1. 在组件中使用:
import { useTranslation } from 'react-i18next';

const MyComponent = () => {
  const { t } = useTranslation();
  
  return (
    <div>
      <h1>{t('common.title')}</h1>
      <p>{t('common.description')}</p>
    </div>
  );
};
  1. 语言包格式示例:
// en-US/common.ts
export default {
  yes: 'Yes',
  no: 'No',
  ok: 'OK',
  cancel: 'Cancel',
  loading: 'Loading...'
};

注意事项

  • 所有用户可见的文本都应该使用翻译函数
  • 动态内容使用插值语法:t('key', { value: dynamicValue })
  • 复数形式使用:t('key', { count: number })
  • 默认语言为英文
  • 波斯语文本需要从右到左(RTL)显示,注意布局适配

网络请求使用说明

项目使用 Axios 作为 HTTP 客户端,并封装了一套完整的请求工具。

目录结构

src/
├── config/
│   └── request/           # 请求配置
│       ├── index.ts       # 基础配置
│       ├── authHeaderInterceptor.ts  # 认证拦截器
│       ├── encryptionInterceptors.ts # 加密解密拦截器
│       └── requestErrorConfig.ts # 错误处理配置
├── services/             # API 服务
│   ├── login/           # 登录相关接口
│   │   ├── index.ts     # 接口实现
│   │   └── typings.d.ts # 类型定义
│   └── ...             # 其他模块接口
└── utils/
    └── request/        # 请求工具
        ├── index.ts    # 请求实例
        └── types.ts    # 类型定义

配置说明

  1. 基础配置 (config/request/index.ts)
import { stringify } from 'qs';
import { RequestConfig } from '@/utils/request/types';
import { authHeaderInterceptor } from './authHeaderInterceptor';
import { requestEncryptionInterceptor, responseDecryptionInterceptor } from './encryptionInterceptors';
import { errorConfig } from './requestErrorConfig';

const config: RequestConfig = {
    baseURL: import.meta.env.VITE_API_BASE_URL!,
    timeout: 15000,
    headers: {
        Accept: 'application/json, text/plain, */*',
        'Content-Type': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
    },
    paramsSerializer: (params) => stringify(params),
    ...errorConfig,
    requestInterceptors: [authHeaderInterceptor, requestEncryptionInterceptor],
    responseInterceptors: [responseDecryptionInterceptor],
};
  1. 认证拦截器 (config/request/authHeaderInterceptor.ts)

用于处理请求认证,自动添加 token 等认证信息。

  1. 加密解密配置

通过环境变量配置加密密钥:

VITE_REQUEST_ENCRYPTION_KEY=your-encryption-key
VITE_ENABLE_REQUEST_ENCRYPTION=true
  1. 加密解密拦截器 (config/request/encryptionInterceptors.ts)

用于处理请求和响应的数据加密解密。可以通过请求配置选项控制单个请求是否启用加密:

// 单个请求启用加密
request('/api/data', {
    encryption: {
        enabled: true,
        key: 'custom-key' // 可选,使用自定义密钥
    }
});

// 单个请求禁用加密
request('/api/data', {
    encryption: {
        enabled: false
    }
});
  1. 错误处理配置 (config/request/requestErrorConfig.ts)

统一处理请求错误,包括网络错误、业务错误等。

可以通过配置 skipErrorHandler: true 来跳过默认的错误处理,自行处理错误。

API 服务定义

services 目录下按模块定义 API 服务,每个模块包含类型定义和接口实现:

  1. 类型定义 (services/[module]/typings.d.ts)
declare namespace API {
    // 用户信息类型
    type UserInfo = {
        accessToken?: string;
        refreshToken?: string;
        expires?: number;
        username?: string;
        nickname?: string;
        avatar?: string;
        roles?: Array<string>;
        permissions?: Array<string>;
    };

    // 登录参数类型
    type LoginParams = {
        username: string;
        password: string;
        captchaId: string;
        captchaCode: string;
    };

    // 登录结果类型
    type LoginResult = Result<UserInfo>;
}
  1. 接口实现 (services/[module]/index.ts)
import { request } from '@/utils/request';

// 登录接口
export async function fetchLogin(body: API.LoginParams, options?: { [key: string]: any }) {
    return request<API.LoginResult>('/user/login', {
        method: 'POST',
        data: body,
        ...(options || {}),
        requireToken: false,
    });
}

// 获取验证码
export async function fetchCaptcha(options?: { [key: string]: any }) {
    return request<API.CaptchaResult>('/captcha/get', {
        method: 'POST',
        ...(options || {}),
        requireToken: false,
    });
}

使用示例

  1. 在组件中使用
import { fetchLogin, fetchCaptcha } from '@/services/login';

const LoginPage = () => {
    const handleLogin = async () => {
        try {
            // 获取验证码
            const captchaResult = await fetchCaptcha();
            
            // 登录
            const result = await fetchLogin({
                username: 'test',
                password: '123456',
                captchaId: captchaResult.id,
                captchaCode: '1234',
            });
            
            // 处理登录成功
            // 注意:request 已经处理了 success 判断,这里直接使用返回的数据
            console.log('登录成功:', result);
            
        } catch (error) {
            // 处理错误
        }
    };
};
  1. 在 Model 中使用
// models/userModel.ts
import { fetchLogin, fetchCaptcha } from '@/services/login';

const useUserModel = () => {
    const login = async (params: API.LoginParams) => {
        try {
            const result = await fetchLogin(params);
            // 直接使用返回的数据,不需要判断 success
            return result;
        } catch (error) {
            // 处理错误
            throw error;
        }
    };

    const getCaptcha = async () => {
        return fetchCaptcha();
    };

    return {
        login,
        getCaptcha,
    };
};

请求配置选项

interface IRequestOptions {
    method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
    data?: any;
    params?: any;
    headers?: Record<string, string>;
    timeout?: number;
    getResponse?: boolean;  // 是否返回完整响应
    requireToken?: boolean; // 是否需要 token
    skipErrorHandler?: boolean; // 是否跳过默认的错误处理
    requestInterceptors?: IRequestInterceptorTuple[];  // 请求拦截器
    responseInterceptors?: IResponseInterceptorTuple[];  // 响应拦截器
}

错误处理

项目提供了统一的错误处理机制:

  1. 网络错误(如超时、断网等)
  2. HTTP 错误(如 404、500 等)
  3. 业务错误(后端返回的错误信息)

可以通过配置 skipErrorHandler: true 来跳过默认的错误处理,自行处理错误。

响应数据

默认情况下,请求会直接返回服务器响应的数据。如果需要获取完整的响应信息(包括状态码、响应头等),可以配置 getResponse: true

示例:

// 获取完整响应信息
const response = await request('/api/data', {
    getResponse: true,
});
// response 包含完整的响应信息
console.log(response.status); // HTTP 状态码
console.log(response.headers); // 响应头
console.log(response.data); // 响应数据

注意事项

  1. 所有 API 请求都应该在 services 目录下按模块组织
  2. 每个模块包含 typings.d.ts(类型定义)和 index.ts(接口实现)
  3. 使用 TypeScript 类型定义请求参数和响应数据
  4. 需要认证的接口设置 requireToken: true
  5. 支持请求和响应拦截器,可以针对特定请求添加自定义拦截器

本地存储使用说明

项目提供了统一的本地存储工具,支持 localStorage 和 sessionStorage,并支持数据加密存储。

目录结构

src/
└── utils/
    ├── storage/        # 存储工具
    │   ├── index.ts    # 存储工具入口
    │   └── types.ts    # 类型定义
    ├── localUtils.ts   # localStorage 工具
    └── sessionUtils.ts # sessionStorage 工具

配置说明

  1. 加密配置

通过环境变量配置加密密钥:

VITE_STORAGE_ENCRYPTION_KEY=your-encryption-key
  1. 命名空间配置

通过环境变量配置存储命名空间:

VITE_APP_STORAGE_NAMESPACE=your-app-namespace

使用示例

  1. 创建存储工具实例
import { createLocalTools, createSessionTools } from '@/utils/localUtils';

// 创建 localStorage 工具实例
const ls = createLocalTools({
    encryptKey: true,  // 是否加密 key
    encryptValue: true,  // 是否加密 value
});

// 创建 sessionStorage 工具实例
const ss = createSessionTools({
    encryptKey: true,
    encryptValue: true,
});
  1. 使用 localStorage
// 存储数据
ls.setLocal('userInfo', { name: 'John', age: 30 });

// 获取数据
const userInfo = ls.getLocal<{ name: string; age: number }>('userInfo');

// 删除数据
ls.removeLocal('userInfo');

// 清除所有数据
ls.clearLocal();
  1. 使用 sessionStorage
// 存储数据
ss.setSession('tempData', { id: 1, status: 'pending' });

// 获取数据
const tempData = ss.getSession<{ id: number; status: string }>('tempData');

// 删除数据
ss.removeSession('tempData');

// 清除所有数据
ss.clearSession();

注意事项

  1. 所有存储的 key 都会自动添加应用命名空间前缀
  2. 加密存储的配置(key 和 value 是否加密)由创建存储工具实例时的参数决定
  3. 清除操作会清除所有属于当前应用的数据(包括加密和未加密的数据)
  4. 对于敏感数据,建议在创建存储工具实例时启用加密选项
  5. 每个存储操作都可以通过 opts 参数覆盖全局配置
  6. 建议在应用初始化时创建存储工具实例,并在整个应用中复用

类型定义

interface StorageOptions {
    expire?: number;         // 过期时间,单位秒
    encryptKey?: boolean;    // 是否加密 key
    encryptValue?: boolean;  // 是否加密 value
}

interface LocalStorageInstance {
    setLocal: <T = any>(key: string, value: T, opts?: StorageOptions) => void;
    getLocal: <T = any>(key: string, opts?: StorageOptions) => T | null;
    removeLocal: (key: string, opts?: StorageOptions) => void;
    clearLocal: () => void;
}

interface SessionStorageInstance {
    setSession: <T = any>(key: string, value: T, opts?: StorageOptions) => void;
    getSession: <T = any>(key: string, opts?: StorageOptions) => T | null;
    removeSession: (key: string, opts?: StorageOptions) => void;
    clearSession: () => void;
}