You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

552 lines
19 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { Card, Grid, Input, Tag, Typography, Divider, Collapse, Button, Message } from '@arco-design/web-react';
import { IconSearch, IconSync } from '@arco-design/web-react/icon';
import styles from './style/market.module.less';
import { useSelector, useDispatch } from 'react-redux';
import { addProjectComp, addProjectBaseComp } from '@/api/scene';
import dayjs from 'dayjs';
import { getMyComponents, getPubComponents, getTeamComponents } from '@/api/components';
import { getMyFlowList, getPubFlowList } from '@/api/flow';
const { Row, Col } = Grid;
const { Title, Text } = Typography;
type componentItemType = {
label: string;
children?: any[];
};
interface MarketProps {
updateProjectComp: () => void;
}
const Market: React.FC<MarketProps> = ({ updateProjectComp }) => {
const [compList, setCompList] = useState<any>([]);
const [searchValue, setSearchValue] = useState(''); // 添加搜索状态
const [firstLevelCategory, setFirstLevelCategory] = useState('全部'); // 第一层分类:全部,基础,复合
const [secondLevelCategory, setSecondLevelCategory] = useState('全部'); // 第二层分类:全部,我的,公开,协同
const { projectComponentData, info } = useSelector((state: any) => state.ideContainer);
const dispatch = useDispatch();
// 第一层分类选项
const firstLevelCategories = [
{ label: '全部', value: '全部' },
{ label: '基础', value: '基础' },
{ label: '复合', value: '复合' }
];
// 第二层分类选项
const secondLevelCategories = [
{ label: '全部', value: '全部' },
{ label: '我的', value: '我的' },
{ label: '公开', value: '公开' },
{ label: '协同', value: '协同' }
];
// 计算各分类的数量
const getCategoryCounts = useCallback(() => {
if (!compList) return {
allCount: 0,
basicCount: 0,
compositeCount: 0,
myCount: 0,
publicCount: 0,
teamCount: 0,
myFlowCount: 0,
pubFlowCount: 0
};
// 基础组件数量 (myLibs, pubLibs, teamLibs)
// 需要统计children中的组件数量
const myCount = compList.myLibs ? compList.myLibs.reduce((acc, cur) => acc + (cur.children ? cur.children.length : 0), 0) : 0;
const publicCount = compList.pubLibs ? compList.pubLibs.reduce((acc, cur) => acc + (cur.children ? cur.children.length : 0), 0) : 0;
const teamCount = compList.teamLibs ? compList.teamLibs.reduce((acc, cur) => acc + (cur.children ? cur.children.length : 0), 0) : 0;
const basicCount = myCount + publicCount + teamCount;
// 复合组件数量 (myFlow, pubFlow) - 直接统计数组长度
const myFlowCount = compList.myFlow ? compList.myFlow.length : 0;
const pubFlowCount = compList.pubFlow ? compList.pubFlow.length : 0;
const compositeCount = myFlowCount + pubFlowCount;
return {
allCount: basicCount + compositeCount,
basicCount,
compositeCount,
myCount,
publicCount,
teamCount,
myFlowCount,
pubFlowCount
};
}, [compList]);
// 根据搜索值过滤组件列表
const filteredCompList = useMemo(() => {
if (!searchValue) return compList;
const filtered = { ...compList };
// 过滤基础组件 (myLibs, pubLibs, teamLibs)
['myLibs', 'pubLibs', 'teamLibs'].forEach(key => {
if (filtered[key]) {
filtered[key] = filtered[key].map(category => {
if (category && category.children) {
const filteredChildren = category.children.filter(child => {
const label = child.label || child.name || '';
return label.toLowerCase().includes(searchValue.toLowerCase());
});
return { ...category, children: filteredChildren };
}
return category;
}).filter(category => category.children && category.children.length > 0);
}
});
// 过滤复合组件 (myFlow, pubFlow)
['myFlow', 'pubFlow'].forEach(key => {
if (filtered[key]) {
filtered[key] = filtered[key].filter(item => {
const name = item.flowName || item.name || '';
return name.toLowerCase().includes(searchValue.toLowerCase());
});
}
});
return filtered;
}, [compList, searchValue]);
// 渲染Tag选择器
const renderCategoryTage = useCallback(() => {
/*
* 分类规则:先分类(全部,基础,复合) 再分类(全部,我的,公开,协同:复合类型中没有协同组件)
* 目前数据类型中存在的字段: 基础类型 [myLibs,pubLibs,teamLibs], 复合类型[pubFlow,myFlow]
* 需要渲染出两层Tag标签的选择第一次的选择影响第二层的部分渲染
* */
const counts = getCategoryCounts();
// 根据第一层分类动态调整第二层分类选项
const getFilteredSecondLevelCategories = () => {
// 如果是复合类型,则不显示协同组件选项
if (firstLevelCategory === '复合') {
return secondLevelCategories.filter(item => item.value !== '协同');
}
return secondLevelCategories;
};
// 获取第二层分类的数量
const getSecondLevelCount = (category: string) => {
if (firstLevelCategory === '全部') {
switch (category) {
case '全部':
return counts.allCount;
case '我的':
return counts.myCount + counts.myFlowCount;
case '公开':
return counts.publicCount + counts.pubFlowCount;
case '协同':
return counts.teamCount;
default:
return 0;
}
}
else if (firstLevelCategory === '基础') {
switch (category) {
case '全部':
return counts.basicCount;
case '我的':
return counts.myCount;
case '公开':
return counts.publicCount;
case '协同':
return counts.teamCount;
default:
return 0;
}
}
else if (firstLevelCategory === '复合') {
switch (category) {
case '全部':
return counts.compositeCount;
case '我的':
return counts.myFlowCount;
case '公开':
return counts.pubFlowCount;
default:
return 0;
}
}
return 0;
};
return (
<div>
{/* 第一层分类标签 */}
<div style={{ marginBottom: '16px' }}>
{firstLevelCategories.map((category) => {
let count = 0;
switch (category.value) {
case '全部':
count = counts.allCount;
break;
case '基础':
count = counts.basicCount;
break;
case '复合':
count = counts.compositeCount;
break;
}
return (
<Tag
size="large"
key={category.value}
color={firstLevelCategory === category.value ? 'arcoblue' : 'gray'}
style={{ cursor: 'pointer', marginRight: '20px', padding: '0 12px' }}
onClick={() => {
setFirstLevelCategory(category.value);
// 当切换第一层分类时,默认选中"全部"作为第二层分类
setSecondLevelCategory('全部');
}}
>
{category.label}
{count}
</Tag>
);
})}
</div>
{/* 第二层分类标签 */}
<div>
{getFilteredSecondLevelCategories().map((category) => (
<Tag
size="large"
key={category.value}
color={secondLevelCategory === category.value ? 'arcoblue' : 'gray'}
style={{ cursor: 'pointer', marginRight: '20px', padding: '0 12px' }}
onClick={() => setSecondLevelCategory(category.value)}
>
{category.label}
{getSecondLevelCount(category.value)}
</Tag>
))}
</div>
</div>
);
}, [firstLevelCategory, secondLevelCategory, getCategoryCounts]);
// 渲染组件分类
const renderComponentCategory = useCallback(() => {
// 使用过滤后的组件列表
const currentCompList = searchValue ? filteredCompList : compList;
// 根据第一层和第二层分类筛选组件
let filteredComponents = [];
// 构建筛选条件
const conditions = {
includeMyLibs: (firstLevelCategory === '全部' || firstLevelCategory === '基础') &&
(secondLevelCategory === '全部' || secondLevelCategory === '我的'),
includePubLibs: (firstLevelCategory === '全部' || firstLevelCategory === '基础') &&
(secondLevelCategory === '全部' || secondLevelCategory === '公开'),
includeTeamLibs: (firstLevelCategory === '全部' || firstLevelCategory === '基础') &&
(secondLevelCategory === '全部' || secondLevelCategory === '协同'),
includeMyFlow: (firstLevelCategory === '全部' || firstLevelCategory === '复合') &&
(secondLevelCategory === '全部' || secondLevelCategory === '我的'),
includePubFlow: (firstLevelCategory === '全部' || firstLevelCategory === '复合') &&
(secondLevelCategory === '全部' || secondLevelCategory === '公开')
};
// 收集符合条件的基础组件
if (conditions.includeMyLibs && currentCompList.myLibs) {
filteredComponents = [...filteredComponents, ...currentCompList.myLibs];
}
if (conditions.includePubLibs && currentCompList.pubLibs) {
filteredComponents = [...filteredComponents, ...currentCompList.pubLibs];
}
if (conditions.includeTeamLibs && currentCompList.teamLibs) {
filteredComponents = [...filteredComponents, ...currentCompList.teamLibs];
}
// 收集符合条件的复合组件
const flowComponents = [];
if (conditions.includeMyFlow && currentCompList.myFlow) {
flowComponents.push(...currentCompList.myFlow.map(item => ({ ...item, isFlow: true })));
}
if (conditions.includePubFlow && currentCompList.pubFlow) {
flowComponents.push(...currentCompList.pubFlow.map(item => ({ ...item, isFlow: true })));
}
// 合并相同标签的组件
const mergedComponents = {};
// 处理基础组件按label分组
filteredComponents.forEach(category => {
if (category && category.children && category.children.length > 0) {
const label = category.label;
if (!mergedComponents[label]) {
mergedComponents[label] = {
label: label,
children: []
};
}
mergedComponents[label].children.push(...category.children);
}
});
// 处理复合组件
if (flowComponents.length > 0) {
const label = '复合组件';
if (!mergedComponents[label]) {
mergedComponents[label] = {
label: label,
children: []
};
}
mergedComponents[label].children.push(...flowComponents);
}
// 转换为数组格式
const resultComponents = Object.values(mergedComponents);
// 如果没有组件,显示提示信息
if (resultComponents.length === 0) {
return (
<div style={{ textAlign: 'center', padding: '40px 0', color: '#999' }}>
{searchValue ? '未找到匹配的组件' : '暂无组件数据'}
</div>
);
}
// 将组件添加到工程中
const addToProject = async (component) => {
const params = {
sceneId: info.id,
compIds: []
};
params['compIds'].push(component.id || component.comp.id);
params['type'] = component.fontCompType;
if (component.fontCompType === 'complex') {
const res: any = await addProjectComp(params);
if (res.code === 200) Message.success('添加成功');
else Message.error('添加失败');
}
else {
const res: any = await addProjectBaseComp(params);
if (res.code === 200) Message.success('添加成功');
else Message.error('添加失败');
}
// 通知父组件更新
updateProjectComp();
};
// 渲染组件
return (
<Collapse expandIconPosition={'right'} bordered={false}>
{resultComponents.map((category: componentItemType, index) => {
// 确保category有children属性
if (!category || !category.children || category.children.length === 0) {
return null;
}
return (
<Collapse.Item
key={index}
name={`${category.label}_${index}`}
header={
<div style={{ display: 'flex', alignItems: 'center' }}>
<span
style={{ marginLeft: 10, marginRight: 8, color: '#6D7278', fontSize: 14 }}>{category.label}</span>
</div>
}
>
{category.children.map((component, compIndex) => (
<div key={compIndex}>
<div className={styles['component-list']}>
<div className={styles['component-info']}>
<img src="/icons/compIcon.png" alt="" />
<span>{component.label || component.flowName}</span>
</div>
{/*两种状态 未添加的是primary 已添加的是secondary*/}
{
component.isAdd ? (
<Button type="secondary" style={{ borderRadius: 4 }}
></Button>
) : (
<Button type="primary" style={{ borderRadius: 4 }}
onClick={() => addToProject(component)}></Button>
)
}
</div>
<Divider />
</div>
))}
</Collapse.Item>
);
}).filter(item => item !== null)}
</Collapse>
);
}, [compList, filteredCompList, firstLevelCategory, secondLevelCategory, searchValue]);
// 给账号下的组件列表(本地存储中的组件列表)增加一个是否添加至工程的初始状态
const addInitState = (componentData) => {
// 当前工程下已添加的组件ID列表
const projectComponent = projectComponentData[info.id];
const compListByProject = projectComponent.compIds.concat(projectComponent.flowIds);
const objectKeys = Object.keys(componentData);
/*
* 账号下的组件列表分两种结构:
* 1. myLibs,pubLibs,teamLibs 这三个是带有children数组的结构
* 2. pubFlow,myFlow 这两个是直接一个数组
* */
objectKeys.forEach(key => {
if (key === 'pubFlow' || key === 'myFlow') {
componentData[key].forEach(item => {
item.isAdd = compListByProject.includes(item.id);
});
}
else if (key === 'myLibs' || key === 'pubLibs' || key === 'teamLibs') {
componentData[key].length && componentData[key].forEach(item => {
item.children.forEach(v => {
v.isAdd = compListByProject.includes(v.comp.id);
});
});
}
});
setCompList(componentData);
};
// 处理搜索输入变化
const handleSearchChange = (value: string) => {
setSearchValue(value);
};
// 从缓存中获取组件列表的信息
const getCompList = async () => {
try {
const requests = [
{ promise: getMyComponents(), key: 'myLibs' },
{ promise: getPubComponents(), key: 'pubLibs' },
{ promise: getTeamComponents(), key: 'teamLibs' },
{
promise: getPubFlowList({
currPage: 1,
pageSize: 999
}),
key: 'pubFlow'
},
{
promise: getMyFlowList({
currPage: 1,
pageSize: 999
}),
key: 'myFlow'
}
];
const obj: any = {
myLibs: null,
pubLibs: null,
teamLibs: null,
pubFlow: null,
myFlow: null,
updateTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
};
const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}');
// 分别处理每个请求
for (const { promise, key } of requests) {
try {
const res: any = await promise;
if (res?.code === 200) {
if (key === 'pubFlow' || key === 'myFlow') {
res?.data.list.forEach(item => {
item['fontCompType'] = 'complex';
});
// 更新本地存储数据
obj[key] = res?.data.list || null;
}
// 给协同组件添加一个后续处理数据时使用的类型
else if (key === 'teamLibs') {
res.data.forEach(item => {
item.children.forEach(v => {
v['fontCompType'] = 'team';
});
});
obj[key] = res?.data || null;
}
else {
// 更新本地存储数据
res.data.forEach(item => {
item.children.forEach(v => {
v['fontCompType'] = 'normal';
});
});
obj[key] = res?.data || null;
}
}
sessionStorage.setItem(`compLibs${userInfo.userId}`, JSON.stringify(obj));
const componentData = JSON.parse(sessionStorage.getItem(`compLibs${userInfo.userId}`));
addInitState(componentData);
} catch (error) {
console.error(`加载${key}失败:`, error);
}
}
} catch (error) {
console.error('更新组件库失败:', error);
}
};
useEffect(() => {
getCompList();
}, []);
// 监听 Redux 中 projectComponentData 的变化,更新组件列表状态
useEffect(() => {
getCompList();
}, [projectComponentData]);
return (
<div className={styles['market-container']}>
{/* 头部搜索区域 */}
<div className={styles['market-header']}>
<Title heading={5}></Title>
<div className={styles['search-section']}>
<Input
size="large"
placeholder="搜索组件"
prefix={<IconSearch />}
style={{ marginRight: 16 }}
value={searchValue}
onChange={handleSearchChange}
/>
<div className={styles['filter-tags']}>
<Button
type="primary"
icon={<IconSync />}
onClick={() => getCompList()}
/>
</div>
</div>
</div>
{/* tag样式选择器 */}
<div className={styles['category-section']}>
{renderCategoryTage()}
</div>
<Divider style={{ margin: '20px 0' }} />
<div className={styles['component-section']}>
{renderComponentCategory()}
</div>
</div>
);
};
export default Market;