feat(componentTest): 实现组件测试实例页面

- 新增查询单个组件测试用例列表的 API 接口
- 实现测试实例页面的 UI 界面与交互逻辑
- 添加测试实例页面的样式文件
- 优化登录页面的代码格式和逻辑处理
- 修复登录页面 token 处理相关的问题
- 移除组件测试页面面包屑导航的内边距
- 完善组件设计获取及显示功能
master
钟良源 2 months ago
parent 1fa8c545c0
commit 0fe805ce81

@ -9,3 +9,7 @@ export function getTreeComponents(params?: { name: string, identifier: string, r
return axios.get(`${urlPrefix}/componentTestCase/tree/instance`, { params });
}
// 查询单个组件的测试用例列表
export function getComponentTestCaseList(params?: { componentBaseId: string, identifier: string }) {
return axios.get(`${urlPrefix}componentTestCase/tree/testcase`, { params });
}

@ -46,7 +46,7 @@ const ComponentTest = () => {
<div className={styles['component-test']}>
{/* 面包屑导航 */}
{currentView === 'test' && (
<div style={{ padding: '16px 24px', background: '#fff', marginBottom: 16 }}>
<div style={{ background: '#fff', marginBottom: 16 }}>
<Breadcrumb>
<BreadcrumbItem style={{ cursor: 'pointer' }} onClick={handleBackToList}>

@ -0,0 +1,338 @@
.test-instance {
height: 100%;
display: flex;
flex-direction: column;
background: #f0f2f5;
// 顶部标签栏
.tabs {
background: #fff;
border-bottom: 1px solid #e5e6eb;
padding: 10px 24px;
display: flex;
align-items: center;
.tab-left {
display: flex;
gap: 8px;
}
.tab-center {
flex: 1;
display: flex;
margin-left: 20%;
gap: 8px;
}
.tab-right {
display: flex;
gap: 8px;
}
}
// 主内容区
.main-content {
flex: 1;
display: flex;
overflow: hidden;
background: #f0f2f5;
// 左侧面板
.left-panel {
width: 280px;
background: #fff;
border-right: 1px solid #e5e6eb;
display: flex;
flex-direction: column;
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #e5e6eb;
font-size: 18px;
font-weight: 700;
color: #1d2129;
.icon-btn {
cursor: pointer;
color: #4e5969;
font-size: 16px;
&:hover {
color: #165dff;
}
}
}
.tree-container {
flex: 1;
overflow-y: auto;
.tree-list {
.tree-item {
display: flex;
align-items: center;
padding: 8px 5px;
margin-bottom: 4px;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: #f2f3f5;
}
.item-icon {
margin-right: 8px;
font-size: 12px;
}
.item-text {
flex: 1;
font-size: 13px;
color: #4e5969;
}
.item-actions {
display: flex;
gap: 8px;
opacity: 1;
transition: opacity 0.2s;
svg {
font-size: 14px;
color: #86909c;
cursor: pointer;
&:hover {
color: #165dff;
}
}
}
}
}
}
}
// 中间流程图区域
.center-panel {
display: flex;
align-items: center;
justify-content: center;
padding: 16px 120px;
overflow: auto;
.flow-wrapper {
display: flex;
flex-direction: row;
align-items: center;
gap: 0;
.flow-node {
display: flex;
flex-direction: row;
align-items: center;
.node-box {
background: #fff;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
min-width: 160px;
.node-header {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border-bottom: 1px solid #e5e6eb;
.node-icon {
font-size: 12px;
}
.node-title {
font-size: 12px;
font-weight: 500;
color: #1d2129;
}
}
.node-label {
padding: 4px 10px;
font-size: 10px;
color: #86909c;
text-align: center;
}
&.node-start {
.node-header {
background: #5a6c7d;
color: #fff;
border-radius: 6px 6px 0 0;
.node-title {
color: #fff;
}
}
}
&.node-end {
.node-header {
background: #5a6c7d;
color: #fff;
border-radius: 6px 6px 0 0;
.node-title {
color: #fff;
}
}
}
&.node-component {
.node-header {
background: #ffb800;
color: #fff;
border-radius: 6px 6px 0 0;
.node-title {
color: #fff;
}
}
.node-body {
padding: 8px;
.node-function {
font-size: 11px;
font-weight: 500;
color: #1d2129;
margin-bottom: 6px;
}
.node-params {
display: flex;
flex-direction: column;
gap: 3px;
margin-bottom: 4px;
.param-item {
display: flex;
align-items: center;
gap: 4px;
.param-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: #86909c;
}
.param-label {
font-size: 10px;
color: #4e5969;
}
}
}
.param-item-right {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
.param-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: #86909c;
}
.param-label {
font-size: 10px;
color: #4e5969;
}
}
}
}
}
.node-connector {
display: flex;
flex-direction: row;
align-items: center;
margin: 0;
.connector-line {
width: 24px;
height: 2px;
background: #d9d9d9;
}
.connector-dot {
width: 5px;
height: 5px;
border-radius: 50%;
background: #d9d9d9;
margin-left: -2px;
}
}
}
}
}
// 右侧面板
.right-panel {
flex: 1;
background: #fff;
border-left: 1px solid #e5e6eb;
display: flex;
flex-direction: column;
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #e5e6eb;
font-size: 14px;
font-weight: 500;
color: #1d2129;
.header-actions {
display: flex;
gap: 12px;
.icon-btn {
cursor: pointer;
font-size: 16px;
opacity: 0.6;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
}
}
.log-content {
flex: 1;
overflow-y: auto;
padding: 20px;
.log-empty {
text-align: center;
color: #86909c;
font-size: 13px;
line-height: 1.8;
p {
margin: 8px 0;
}
}
}
}
}
}

@ -1,20 +1,197 @@
import React from 'react';
import { Button } from '@arco-design/web-react';
import { IconLeft } from '@arco-design/web-react/icon';
import React, { useEffect, useState } from 'react';
import { Button, Space, Tree, Collapse } from '@arco-design/web-react';
import { IconLeft, IconPlus, IconEdit, IconDelete, IconLink, IconSend } from '@arco-design/web-react/icon';
import styles from './style/testInstance.module.less';
import { getComponentDesign } from '@/api/componentDevelopProcess';
const CollapseItem = Collapse.Item;
const TestInstance = ({ instance, onBack }: { instance: any; onBack: () => void }) => {
const [activeTab, setActiveTab] = useState('link');
const getDesign = async () => {
const res: any = await getComponentDesign(instance.id);
console.log('res:', res);
};
useEffect(() => {
getDesign();
}, [instance]);
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24 }}>
<Button icon={<IconLeft />} onClick={onBack}>
<div className={styles['test-instance']}>
{/* 顶部标签栏 */}
<div className={styles['tabs']}>
<div className={styles['tab-left']}>
<Button
type="primary"
icon={<IconLink />}
onClick={() => setActiveTab('link')}
style={{ minWidth: 250 }}
>
</Button>
</div>
<div className={styles['tab-center']}>
<Button
type="outline"
onClick={() => setActiveTab('template')}
>
</Button>
<Button
type="outline"
onClick={() => setActiveTab('add')}
>
</Button>
<Button
type="outline"
onClick={() => setActiveTab('batch')}
>
</Button>
<Button
type="outline"
onClick={() => setActiveTab('single')}
>
</Button>
</div>
<div className={styles['tab-right']}></div>
</div>
<div className={styles['main-content']}>
{/* 左侧测试用例列表 */}
<div className={styles['left-panel']}>
<div className={styles['panel-header']}>
<span></span>
</div>
<div className={styles['tree-container']}>
<Collapse defaultActiveKey={['1']} bordered={false}>
<CollapseItem header="监听组件" name="1" extra={<IconPlus />}>
<div className={styles['tree-list']}>
<div className={styles['tree-item']}>
<span className={styles['item-text']}></span>
<div className={styles['item-actions']}>
<IconSend />
<IconEdit />
<IconDelete />
</div>
</div>
<div className={styles['tree-item']}>
<span className={styles['item-text']}>jck|ssss</span>
<div className={styles['item-actions']}>
<IconSend />
<IconEdit />
<IconDelete />
</div>
</div>
<div className={styles['tree-item']}>
<span className={styles['item-text']}></span>
<div className={styles['item-actions']}>
<IconSend />
<IconEdit />
<IconDelete />
</div>
</div>
</div>
</CollapseItem>
</Collapse>
</div>
</div>
{/* 中间流程图区域 */}
<div className={styles['center-panel']}>
<div className={styles['flow-wrapper']}>
{/* 开始节点 */}
<div className={styles['flow-node']}>
<div className={`${styles['node-box']} ${styles['node-start']}`}>
<div className={styles['node-header']}>
<span className={styles['node-icon']}>🔧</span>
<span className={styles['node-title']}></span>
</div>
<div className={styles['node-label']}></div>
</div>
<div className={styles['node-connector']}>
<div className={styles['connector-line']}></div>
<div className={styles['connector-dot']}></div>
</div>
</div>
{/* 组件节点 */}
<div className={styles['flow-node']}>
<div className={`${styles['node-box']} ${styles['node-component']}`}>
<div className={styles['node-header']}>
<span className={styles['node-icon']}></span>
<span className={styles['node-title']}>#1</span>
</div>
<div className={styles['node-body']}>
<div className={styles['node-function']}>lineMove</div>
<div className={styles['node-function']}>circleMove</div>
<div className={styles['node-params']}>
<div className={styles['param-item']}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>input</span>
</div>
<div className={styles['param-item']}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>strpos ARR</span>
</div>
<div className={styles['param-item']}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>ufNum INT</span>
</div>
<div className={styles['param-item']}>
<span className={styles['param-dot']}></span>
<span className={styles['param-label']}>ufNum INT</span>
</div>
</div>
<div className={styles['param-item-right']}>
<span className={styles['param-label']}>output</span>
<span className={styles['param-dot']}></span>
</div>
</div>
</div>
<div className={styles['node-connector']}>
<div className={styles['connector-line']}></div>
<div className={styles['connector-dot']}></div>
</div>
</div>
{/* 结束节点 */}
<div className={styles['flow-node']}>
<div className={`${styles['node-box']} ${styles['node-end']}`}>
<div className={styles['node-header']}>
<span className={styles['node-icon']}>📍</span>
<span className={styles['node-title']}></span>
</div>
<div className={styles['node-label']}></div>
</div>
</div>
</div>
</div>
{/* 右侧运行日志 */}
<div className={styles['right-panel']}>
<div className={styles['panel-header']}>
<span></span>
<div className={styles['header-actions']}>
<span className={styles['icon-btn']}>📋</span>
<span className={styles['icon-btn']}>📁</span>
<span className={styles['icon-btn']}>💾</span>
<span className={styles['icon-btn']}>📊</span>
<span className={styles['icon-btn']}>🗑</span>
</div>
</div>
<div className={styles['log-content']}>
<div className={styles['log-empty']}>
<p></p>
<p></p>
</div>
</div>
</div>
</div>
<h1>: {instance?.name}</h1>
<p>: {instance?.identifier}</p>
<p>: {instance?.runStatus}</p>
<p>: {instance?.runType}</p>
{/* 这里添加你的测试实例内容 */}
</div>
);
};

@ -11,7 +11,11 @@ import { localGet, localRemove, openWindow } from '@/utils/common';
import logoImage from '@/public/assets/logo.png';
import store from '@/store';
import { updateUserInfo } from '@/store/user';
import { getMyComponents, getPubComponents, getTeamComponents } from '@/api/components';
import {
getMyComponents,
getPubComponents,
getTeamComponents,
} from '@/api/components';
import { getPublishPage } from '@/api/flow';
import dayjs from 'dayjs';
@ -33,7 +37,7 @@ function Login() {
{ promise: getMyComponents(), key: 'myLibs' },
{ promise: getPubComponents(), key: 'pubLibs' },
{ promise: getTeamComponents(), key: 'teamLibs' },
{ promise: getPublishPage(), key: 'pubFlow' }
{ promise: getPublishPage(), key: 'pubFlow' },
// {promise: appId ? getMineSubs({id: appId}) : Promise.resolve(null), key: 'myFlow'},
// {promise: getEventList(), key: 'eventList'}
];
@ -44,7 +48,7 @@ function Login() {
teamLibs: null,
pubFlow: null,
// myFlow: null,
updateTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
updateTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
};
const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}');
@ -79,7 +83,10 @@ function Login() {
// 更新本地存储数据
obj[key] = res?.data || null;
sessionStorage.setItem(`compLibs${userInfo.userId}`, JSON.stringify(obj));
sessionStorage.setItem(
`compLibs${userInfo.userId}`,
JSON.stringify(obj)
);
} catch (error) {
console.error(`加载${key}失败:`, error);
}
@ -92,9 +99,31 @@ function Login() {
const handleLogin = async () => {
const url = new URL(window.location.href);
const code = url.searchParams.get('code');
const token = url.searchParams.get('token');
const { origin, pathname } = window.location;
setIsNeedLogin(code === null);
setIsNeedLogin(code === null && token === null);
// 处理 URL 中的 token 参数
if (token) {
try {
// 记录登录状态
localStorage.setItem('userStatus', 'login');
// 保存 Token
setToken(token);
// 获取用户信息
// await fetchUserInfo();
// 清除 URL 中的 token 参数
url.searchParams.delete('token');
window.history.replaceState({}, '', url.pathname + url.search);
// 跳转首页
window.location.href = '/dashboard/workplace';
return;
} catch (error) {
console.error('Token login error:', error);
}
}
if (code) {
const callbackUrl = `${origin}/`;
@ -102,7 +131,7 @@ function Login() {
// 根据code向后端获取token
const res: any = await getToken({
authCode: code,
callbackUrl
callbackUrl,
} as any);
if (res && res.code === 200) {
@ -112,13 +141,15 @@ function Login() {
// await userStore.info(); // 如果有对应的Redux操作可以在这里dispatch
if (localGet('system_name') === 'pc') {
openWindow(`${window.location.protocol}//${window.location.hostname}:${window.location.port}/isdp/`, {
target: '_self'
});
openWindow(
`${window.location.protocol}//${window.location.hostname}:${window.location.port}/isdp/`,
{
target: '_self',
}
);
localRemove('system_name');
return;
}
else {
} else {
fetchUserInfo();
router.push('/dashboard/workplace');
}
@ -139,12 +170,10 @@ function Login() {
<div className={styles.container}>
<>
<div className={styles.logo}>
<img
width={50}
height={50}
src={logoImage.src}
/>
<div className={styles['logo-text']}></div>
<img width={50} height={50} src={logoImage.src} />
<div className={styles['logo-text']}>
</div>
</div>
<div className={styles.banner}>
<div className={styles['banner-inner']}>
@ -159,8 +188,12 @@ function Login() {
<div className={styles['loading-content']}>
<h3 className={styles['loading-text']}>...</h3>
<div className={styles['page-loading-warp']}>
<div className={`${styles['ant-spin']} ${styles['ant-spin-lg']} ${styles['ant-spin-spinning']}`}>
<span className={`${styles['ant-spin-dot']} ${styles['ant-spin-dot-spin']}`}>
<div
className={`${styles['ant-spin']} ${styles['ant-spin-lg']} ${styles['ant-spin-spinning']}`}
>
<span
className={`${styles['ant-spin-dot']} ${styles['ant-spin-dot-spin']}`}
>
<i className={styles['ant-spin-dot-item']} />
<i className={styles['ant-spin-dot-item']} />
<i className={styles['ant-spin-dot-item']} />

Loading…
Cancel
Save