Next前端文档


Nextfox文档

一、项目介绍

Nextfox是一个高效简洁易用的测试管理工具,包含:项目管理、系统配置、功能测试、接口测试、UI自动化测试、性能测试、精准测试、便捷工具等实用功能。

1、前端技术

  • 前端开发:Next + React+Antd + TypeScript + TailwindCSS

2、功能模块

二、环境搭建

1、相关依赖官网

2、前端环境搭建

  1. 创建 Next.js 项目

    设置镜像加速器

    npm config set registry https://registry.npmmirror.com
    

    首先,确保你已经安装了 Node.js 和 npm 或 yarn。然后在终端中运行以下命令创建一个新的 Next.js 项目,并启用 TypeScript:

    npx create-next-app@latest nextfox-frontend
    
  2. 安装必要的依赖项

    安装 Ant Design、postcss、autoprefixer及其所需的其他依赖项:

    npm install antd tailwindcss postcss autoprefixer
    
  3. 运行开发服务器

    在项目根目录中运行以下命令启动开发服务器:

    pnpm i # 安装项目依赖
    pnpm dev # 启动本地服务
    
    // pnpm dev 你可以在浏览器中访问 http://localhost:3000 查看项目
    

3、前端项目结构

  • 创建项目目录
mkdir -p src/{apis,app,components,contexts,enums,hooks,styles,types,utils}
  • pulbic:静态资源目录
    • images目录:存放图片
    • favicon.svg:svg图片
    • manifest.webmanifest:应用元数据
  • src:源文件夹
    • apis:请求接口
    • app:页面组件
    • components:公共组件
    • contexts:上下文管理
    • hooks:自定义钩子
    • enums:定义枚举类型
    • types:定义TypeScript类型
    • styles:全局样式
    • utils:工具函数
  • next.config.mjs: nextjs 配置
  • .env.development:开发环境变量
  • .env.production:发布版本环境变量
  • tsconfig.json:TypeScript 项目的配置文件
  • postcss.config.mjs:css样式插件
  • tailwind.config.ts: tailwindcss 全局设置
  • Dockerfile: docker 构建配置
  • .eslinttrc.cjs: eslint配置信息
  • .gitignore: git忽略文件
  • .prettierrc.cjs: 代码格式设置
  • .stylelintrc.cjs:Stylelint 配置文件
  • commitlint.config: commit 提交设置
  • next-env.d.ts:提供 Next.js 特定的类型声明

4、配置项

  • next.config.mjs:URL配置
/** @type {import('next').NextConfig} */

const nextConfig = {
  reactStrictMode: true,
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: `${process.env.NEXT_PUBLIC_API_URL}/:path*`
      },
    ]
  }
};

export default nextConfig;
  • .env.development:开发环境
NEXT_PUBLIC_API_URL=http://localhost:5173/api
  • .env.production:生产环境
NEXT_PUBLIC_API_URL=
  • postcss.config.mjs
/** @type {import('postcss-load-config').Config} */
const config = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
    'tailwindcss/nesting': {},

  },
};

export default config;
  • tailwind.config.ts:样式配置
import type { Config } from 'tailwindcss'

export default {
  content: ['./src/{app,components}/**/*.{js,jsx,ts,tsx}'],

  theme: {
    extend: {
      colors: {},

      padding: {
        main: 'var(--p-main)',
        tabContent: 'var(--p-tab-content)',
      },

      margin: {
        tabContent: 'var(--p-tab-content)',
      },
    },
  },

  corePlugins: {
    preflight: false,
  },
} satisfies Config
  • Dockerfile:docker配置

  • commitlint.config: commit 提交设置
// 安装commitlint
npm add -D @commitlint/config-conventional @commitlint/cli

四、app页面组件

page.tsx:项目主页面

import { redirect } from "next/navigation";

export default function HomePage() {
  return redirect('/login')
}

layout.tsx:项目主模版

app/layout.tsx 是整个应用的主布局文件,相当于React的main.ts或App.tsx,做以下几个事情:

  • 项目 metadata
  • 加载全局样式 globals.css
  • 加载网络/本地字体
  • 国际化 i18n
  • 第三方组件库 Provider Wrapper
  • 顶层 RootLayout 作用于所有页面,各个子 Layout 只作用于自己所属的目录下的所有页面
import type { Metadata, Viewport } from 'next';
import { Inter } from "next/font/google";
import '@/styles/globals.css';
import { getPageTitle } from '@/utils/getPageTitle';
import { App } from 'antd';
import { AntdRegistry } from '@ant-design/nextjs-registry'
import { AntdStyleProvider } from '@/components/AntdStyleProvider';
import { ThemeProviderClient } from '@/components/ThemeEditor';
import { GlobalContextProvider } from '@/contexts/GlobalContext';

export const metadata: Metadata = {
  icons: [{ url: '/favicon.svg', type: 'image/svg+xml' }],
  title: getPageTitle(),
  description: 'Nextfox--高效简洁易用的测试管理工具',
  authors: [{ name: 'TestCabana', url: 'https://github.com/TestCabana' }],
  manifest: '/manifest.webmanifest',
};


const inter = Inter({ subsets: ["latin"] });
export const viewport: Viewport = {
  colorScheme: 'light dark',
};

export default function RootLayout(props: React.PropsWithChildren) {
  return (
    
      
        
          
            
              
                
{props.children}
) }

not-found.tsx:页面不存在

'use client';

import { Button, Result } from 'antd';
import { useRouter } from 'next/navigation';


export default function NotFound() {
  const router = useRouter();
  return (
     回到主页}
      extra={
        
      }
    />
  );
}

(main)/layout.tsx:页面主模板

'use client'

import '@/styles/globals.css';
import { theme } from 'antd'
import { SideNav } from '@/components/SideNav'
import { LayoutProvider } from '@/contexts/LayoutContext'
import { HeaderNav } from '@/components/HeaderNav/HeaderNav'
import { useThemeContext } from '@/contexts/ThemeContext';


export default function MainLayout(props: React.PropsWithChildren) {
  const { themeSetting } = useThemeContext();
  const { themeMode } = themeSetting;
  const { token } = theme.useToken()

  // 根据不同的主题模式设置背景颜色
  const backgroundColor = themeMode === 'darkDefault'
    ? token.colorFillTertiary
    : 'var(--background-color)';

  // 根据不同的主题模式设置文字颜色
  const colorText = themeMode === 'darkDefault'
    ? 'var(--colorText)'
    : token.colorText;



  return (
    
{props.children}
) }

1、user

1)LoginForm.tsx:登录表单

'use client'; // 声明这个页面组件为客户端组件

import React, { useState, useEffect } from 'react';
import { Button, Input, Form, message } from 'antd';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import Image from 'next/image';
import useLoading from '@/hooks/useLoading';
import { useRouter } from 'next/navigation';
import styles from './login.module.css'; // 导入 CSS 模块
import { setLocalStorage } from '@/utils/localStorage';
import { encrypted } from '@/utils/encrypted';
import { getPublicKey, login } from '@/apis/user/user';
import { Response } from '@/types';



export default function LoginForm() {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [publicKey, setPublicKey] = useState('');
  const { loading, setLoading } = useLoading();
  const router = useRouter();


  useEffect(() => {
    // 获取公钥
    const getPublicKeyReq = async () => {
      try {
        const response = await getPublicKey();
        const publicKey = response.data; // 明确指定类型
        console.log('公钥', publicKey)
        setPublicKey(publicKey || null); // 设置公钥状态
        setLocalStorage('salt', publicKey); // 将公钥存储到本地存储

      } catch (error) {
        console.error('获取公钥失败:', error);
      }
    };

    getPublicKeyReq();
  }, []); // 依赖数组为空,确保只在组件首次加载时调用


  const handleLogin = async (username: string, password: string) => {
    if (!publicKey) {
      console.error('未获取公钥');
      return false;
    }
    try {
      // 使用公钥加密用户名和密码
      const encryptedUsername = encrypted(username);
      const encryptedPassword = encrypted(password);

      if (encryptedUsername === false || encryptedPassword === false) {
        console.error('加密失败');
        return false;
      }

      const data = {
        username: encryptedUsername,
        password: encryptedPassword,
      };

      const responseData = await login(data);
      // 登录成功后将用户信息、sessionId、token存储在LocalStorage中
      setLocalStorage('user', responseData);
      setLocalStorage('sessionId', responseData.data.sessionId)
      setLocalStorage('csrfToken', responseData.data.csrfToken)

      // 登录成功后重定向到 /dashboard
      router.push('/dashboard');
      return true;
    } catch (error) {
      console.error('登录请求失败:', error);
      return false;
    }
  };

  const handleSubmit = async (values: { username: string; password: string }) => {
    setLoading(true);
    try {
      const success = await handleLogin(values.username, values.password);
      if (!success) {
        message.error('登录请求失败');
      }
    } catch (error) {
      message.error('登录时发生错误');
    } finally {
      setLoading(false);
    }
  };

  return (
    
logo图片
Nextfox
高效简洁易用的开源测试管理工具
账号登录
} value={username} onChange={(e) => setUsername(e.target.value)} allowClear /> } value={password} onChange={(e) => setPassword(e.target.value)} allowClear />
); }

2)page.ts:登录页面

import LoginForm from './LoginForm';
import styles from './login.module.css';


export default function LoginPage() {

  return (
    
); }

2)page.tsx:登录页面

import LoginForm from './LoginForm';
import styles from './login.module.css';


export default function LoginPage() {

  return (
    
); }

3)login.module.css:登录样式

/* login.module.css */

.backgroundLogin {
  background-image: url('/images/login-background.jpg');
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  width: 100vw;
}

/* 容器样式 */
.container {
  @apply flex items-center justify-center m-2;
}


/* 登录表单包装样式 */
.loginFormWrapper {
  @apply flex flex-col items-center justify-center;
}

/* 标题样式 */
.title {
  @apply flex items-center justify-center;

  .titleText {
    @apply text-[35px] font-bold text-[#9373ee];
  }
}

.subtitle {
  @apply mt-3 text-[18px] font-bold text-[#9373ee];
}

/* 表单容器样式 */
.formContainer {
  @apply mt-8 p-10 rounded-lg bg-white shadow-2xl;

  .formTitle {
    @apply mb-8 text-[18px] font-bold text-[#9373ee];
  }

  .formItem {
    @apply mb-8;
  }

  .submitButton {
    @apply w-full flex justify-center;
  }
}

2、system-settings

1)page.tsx

'use client'

// 导入 Ant Design 组件库中的 `ConfigProvider`(配置提供者),`Menu`(菜单),以及 `Skeleton`(加载占位符)组件。
import { ConfigProvider, Menu, type MenuProps, Skeleton, theme } from 'antd'

import { LayersIcon, SettingsIcon } from 'lucide-react'
import { PanelLayout } from '@/components/PanelLayout'


// 定义 `MenuItem` 类型,它是 `MenuProps` 中 `items` 属性的类型。`Required` 强制要求 `MenuProps` 中的所有属性都必须被指定。
type MenuItem = Required['items'][number]

// 定义一个 `items` 数组,包含了菜单项的数据结构
const items: MenuItem[] = [
  {
    key: 'g1', // 菜单项的唯一标识符
    label: (
      
通用设置
), type: 'group', // 表示这是一个分组项 children: [ // 子菜单项 { key: '1', label: '基本设置' }, // 第一个子菜单项 { key: '2', // 第二个子菜单项的标识符 label: '功能设置', // 子菜单项的文本标签 children: [ // 子菜单项下的子菜单项 { key: '1x', label: '接口功能设置' }, // 子菜单项 { key: '2x', label: '高级设置' }, // 子菜单项 ], }, ], }, { key: 'g2', // 另一个分组菜单项的标识符 label: (
项目资源
), type: 'group', // 表示这是一个分组项 children: [ { key: '3', label: '常用参数' }, { key: '4', label: '公共响应' }, ], }, ] export default function SettingsPage() { const { token } = theme.useToken() return (
} right={ // 使用一个有内边距的 div 来包裹右侧内容
待实现的设置页
} /> ) }

3、dashboard

1)page.tsx

'use client';

import React, { useState } from 'react';
import { Card, Col, Row, Statistic } from 'antd';
import { BarChartOutlined, DashboardOutlined } from '@ant-design/icons';

export default function Home() {
  const [statisticLoading, setStatisticLoading] = useState(true);
  const [chartLoading, setChartLoading] = useState(true);
  const [statisticNumber, setStatisticNumber] = useState();
  const [chartData, setChartData] = useState([]);


  return (
    
} /> } suffix={`/ ${statisticNumber?.taskTotalNum}`} /> } suffix={`/ ${statisticNumber?.executorTotalNum}`} /> } />
); };

五、apis:请求接口

六、components:公共组件

AntdStyleProvider.tsx:antd组件兼容样式问题

'use client'

import { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs'
import React from 'react'

/**
 * 这部分的代码参考自:https://github.com/ant-design/ant-design/issues/38555#issuecomment-1571203559
 */
function AntdStyleRegister(props: React.PropsWithChildren) {
  const [cache] = useState(() => createCache())

  useServerInsertedHTML(() => {
    return (
      ${extractStyle(cache)}

文章作者: Nextfox
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Nextfox !
  目录