From 1cb806dfe3d4c5d8b4c841ac0fd6826157dd2f8d Mon Sep 17 00:00:00 2001 From: test Date: Wed, 9 Jul 2025 23:38:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=94=A8=E4=BE=8B=E9=9B=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=BC=80=E5=8F=91=E4=B8=8E=E6=9D=83=E9=99=90=E9=9B=86?= =?UTF-8?q?=E6=88=90=EF=BC=8C=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2=E4=B8=8E?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/X1.WebUI/src/constants/api.ts | 1 + src/X1.WebUI/src/constants/menuConfig.ts | 8 + src/X1.WebUI/src/contexts/AuthContext.tsx | 26 +- .../src/pages/scenarios/ScenarioForm.tsx | 2 +- .../pages/testcasesets/TestCaseSetForm.tsx | 135 +++++++ .../pages/testcasesets/TestCaseSetTable.tsx | 187 +++++++++ .../pages/testcasesets/TestCaseSetsView.tsx | 367 ++++++++++++++++++ src/X1.WebUI/src/pages/testcasesets/index.ts | 3 + src/X1.WebUI/src/routes/AppRouter.tsx | 8 + .../src/services/testCaseSetService.ts | 142 +++++++ 10 files changed, 873 insertions(+), 6 deletions(-) create mode 100644 src/X1.WebUI/src/pages/testcasesets/TestCaseSetForm.tsx create mode 100644 src/X1.WebUI/src/pages/testcasesets/TestCaseSetTable.tsx create mode 100644 src/X1.WebUI/src/pages/testcasesets/TestCaseSetsView.tsx create mode 100644 src/X1.WebUI/src/pages/testcasesets/index.ts create mode 100644 src/X1.WebUI/src/services/testCaseSetService.ts diff --git a/src/X1.WebUI/src/constants/api.ts b/src/X1.WebUI/src/constants/api.ts index 8a93493..d4affef 100644 --- a/src/X1.WebUI/src/constants/api.ts +++ b/src/X1.WebUI/src/constants/api.ts @@ -31,6 +31,7 @@ export const API_PATHS = { // 测试用例相关 TEST_CASES: '/test-cases', + TEST_CASE_SETS: '/test-case-sets', TEST_STEPS: '/test-steps', // 分析相关 diff --git a/src/X1.WebUI/src/constants/menuConfig.ts b/src/X1.WebUI/src/constants/menuConfig.ts index f9a80ca..ce355b0 100644 --- a/src/X1.WebUI/src/constants/menuConfig.ts +++ b/src/X1.WebUI/src/constants/menuConfig.ts @@ -19,6 +19,9 @@ export type Permission = | 'testcases.view' | 'testcases.manage' | 'testcases.create' + | 'testcasesets.view' + | 'testcasesets.manage' + | 'testcasesets.create' | 'teststeps.view' | 'teststeps.manage' | 'teststeps.create' @@ -97,6 +100,11 @@ export const menuItems: MenuItem[] = [ href: '/dashboard/testcases/list', permission: 'testcases.view', }, + { + title: '用例集', + href: '/dashboard/testcases/sets', + permission: 'testcasesets.view', + }, { title: '创建用例', href: '/dashboard/testcases/create', diff --git a/src/X1.WebUI/src/contexts/AuthContext.tsx b/src/X1.WebUI/src/contexts/AuthContext.tsx index 89a085b..15ae491 100644 --- a/src/X1.WebUI/src/contexts/AuthContext.tsx +++ b/src/X1.WebUI/src/contexts/AuthContext.tsx @@ -1,7 +1,7 @@ import { createContext, useContext, useReducer, ReactNode, useMemo, useEffect, useState } from 'react'; -import { AuthState, AuthContextType, LoginRequest, User, RegisterRequest } from '@/types/auth'; +import { AuthState, AuthContextType, LoginRequest, User as AuthUser, RegisterRequest } from '@/types/auth'; import { useSetRecoilState, useRecoilState } from 'recoil'; -import { userState } from '@/stores/userStore'; +import { userState, User as StoreUser } from '@/stores/userStore'; import { authService } from '@/services/authService'; import { useAuthSync } from '@/hooks/useAuthSync'; import { useAuthInit } from '@/hooks/useAuthInit'; @@ -24,14 +24,14 @@ const initialState: AuthState = { type AuthAction = | { type: 'LOGIN_START' } - | { type: 'LOGIN_SUCCESS'; payload: { user: User; accessToken: string; refreshToken: string; rememberMe: boolean } } + | { type: 'LOGIN_SUCCESS'; payload: { user: AuthUser; accessToken: string; refreshToken: string; rememberMe: boolean } } | { type: 'LOGIN_FAILURE'; payload: { error: string } } | { type: 'REGISTER_START' } | { type: 'REGISTER_SUCCESS' } | { type: 'REGISTER_FAILURE'; payload: { error: string } } | { type: 'LOGOUT' } | { type: 'CLEAR_ERROR' } - | { type: 'SET_USER'; payload: { user: User; accessToken: string; refreshToken: string } } + | { type: 'SET_USER'; payload: { user: AuthUser; accessToken: string; refreshToken: string } } | { type: 'SET_REMEMBER_ME'; payload: boolean }; // 获取默认权限 @@ -49,6 +49,9 @@ const getDefaultPermissions = (userPermissions: Record = {}) => 'testcases.view', 'testcases.manage', 'testcases.create', + 'testcasesets.view', + 'testcasesets.manage', + 'testcasesets.create', 'teststeps.view', 'teststeps.manage', 'teststeps.create', @@ -181,7 +184,20 @@ export function AuthProvider({ children }: { children: ReactNode }) { }, [state]); // 使用自定义 hooks - useAuthSync(state.user, setGlobalUser); + useAuthSync(state.user, (user: AuthUser | null) => { + if (user) { + const storeUser: StoreUser = { + id: user.id, + username: user.userName, + email: user.email, + roles: [], // 从用户权限中提取角色信息 + permissions: Object.keys(user.permissions || {}) + }; + setGlobalUser(storeUser); + } else { + setGlobalUser(null); + } + }); useAuthInit(dispatch); const authActions = useMemo(() => ({ diff --git a/src/X1.WebUI/src/pages/scenarios/ScenarioForm.tsx b/src/X1.WebUI/src/pages/scenarios/ScenarioForm.tsx index ecfc908..120f655 100644 --- a/src/X1.WebUI/src/pages/scenarios/ScenarioForm.tsx +++ b/src/X1.WebUI/src/pages/scenarios/ScenarioForm.tsx @@ -26,7 +26,7 @@ export default function ScenarioForm({ onSubmit, initialData, isEdit = false, is e.preventDefault(); if (isSubmitting) return; // 防止重复提交 - if (isEdit && initialData) { + if (isEdit && initialData?.scenarioId) { // 编辑模式,需要包含scenarioId const updateData: UpdateScenarioRequest = { ...formData, diff --git a/src/X1.WebUI/src/pages/testcasesets/TestCaseSetForm.tsx b/src/X1.WebUI/src/pages/testcasesets/TestCaseSetForm.tsx new file mode 100644 index 0000000..f5a8ed8 --- /dev/null +++ b/src/X1.WebUI/src/pages/testcasesets/TestCaseSetForm.tsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Checkbox } from '@/components/ui/checkbox'; +import { CreateTestCaseSetRequest, UpdateTestCaseSetRequest, TestCaseSet } from '@/services/testCaseSetService'; + +interface TestCaseSetFormProps { + onSubmit: (data: CreateTestCaseSetRequest | UpdateTestCaseSetRequest) => void; + initialData?: Partial; + isEdit?: boolean; + isSubmitting?: boolean; +} + +export default function TestCaseSetForm({ onSubmit, initialData, isEdit = false, isSubmitting = false }: TestCaseSetFormProps) { + const [formData, setFormData] = React.useState({ + code: initialData?.code || '', + name: initialData?.name || '', + version: initialData?.version || '', + description: initialData?.description || '', + versionUpdateInfo: initialData?.versionUpdateInfo || '', + remarks: initialData?.remarks || '', + isDisabled: initialData?.isDisabled ?? false + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (isSubmitting) return; // 防止重复提交 + + if (isEdit && initialData?.testCaseSetId) { + // 编辑模式,需要包含testCaseSetId + const updateData: UpdateTestCaseSetRequest = { + ...formData, + testCaseSetId: initialData.testCaseSetId + }; + onSubmit(updateData); + } else { + // 创建模式 + onSubmit(formData); + } + }; + + return ( +
+
+ + setFormData({ ...formData, code: e.target.value })} + placeholder="请输入用例集编码" + required + disabled={isSubmitting} + /> +
+ +
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="请输入用例集名称" + required + disabled={isSubmitting} + /> +
+ +
+ + setFormData({ ...formData, version: e.target.value })} + placeholder="请输入版本号" + required + disabled={isSubmitting} + /> +
+ +
+ +