feat(market):重构组件市场页面结构和分类逻辑

master
钟良源 4 months ago
parent c375745670
commit 756a98943d

Binary file not shown.

After

Width:  |  Height:  |  Size: 988 B

@ -1,195 +1,319 @@
import React from 'react';
import { Card, Grid, Input, Tag, Typography, Divider, Avatar } from '@arco-design/web-react';
import {
IconSearch,
IconStar,
IconDownload,
IconUser,
IconCalendar,
IconFilter
} from '@arco-design/web-react/icon';
import React, { useEffect, useState, useCallback } from 'react';
import { Card, Grid, Input, Tag, Typography, Divider, Collapse, Button } 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';
const { Row, Col } = Grid;
const { Title, Text } = Typography;
// 模拟组件数据
const components = [
{
id: 1,
name: '数据表格',
description: '功能强大的数据展示表格,支持排序、筛选、分页等功能',
author: '张三',
avatar: '',
downloads: 12560,
rating: 4.8,
tags: ['数据展示', '表格', '交互'],
category: '数据展示',
updateTime: '2023-10-15'
},
{
id: 2,
name: '表单生成器',
description: '可视化表单构建工具,支持多种表单元素和校验规则',
author: '李四',
avatar: '',
downloads: 8920,
rating: 4.6,
tags: ['表单', '生成器', '可视化'],
category: '表单',
updateTime: '2023-10-18'
},
{
id: 3,
name: '图表组件',
description: '基于ECharts的图表组件库支持多种图表类型',
author: '王五',
avatar: '',
downloads: 15630,
rating: 4.9,
tags: ['图表', '数据可视化', 'ECharts'],
category: '数据可视化',
updateTime: '2023-10-10'
},
{
id: 4,
name: '流程设计器',
description: '可视化流程设计工具,支持拖拽式流程编排',
author: '赵六',
avatar: '',
downloads: 6750,
rating: 4.5,
tags: ['流程', '设计器', '可视化'],
category: '流程',
updateTime: '2023-10-20'
},
{
id: 5,
name: '文件上传',
description: '支持多种文件格式上传,包含进度显示和校验功能',
author: '孙七',
avatar: '',
downloads: 9800,
rating: 4.7,
tags: ['文件', '上传', '工具'],
category: '工具',
updateTime: '2023-10-12'
},
{
id: 6,
name: '通知中心',
description: '系统通知管理组件,支持多种通知类型和样式',
author: '周八',
avatar: '',
downloads: 5420,
rating: 4.3,
tags: ['通知', '消息', '系统'],
category: '反馈',
updateTime: '2023-10-05'
},
];
// 分类数据
const categories = [
{ id: 1, name: '全部组件', count: 24 },
{ id: 2, name: '数据展示', count: 8 },
{ id: 3, name: '表单', count: 6 },
{ id: 4, name: '数据可视化', count: 5 },
{ id: 5, name: '流程', count: 3 },
{ id: 6, name: '工具', count: 7 },
{ id: 7, name: '反馈', count: 4 },
];
const Market: React.FC = () => {
const [compList, setCompList] = useState<any>([]);
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]);
// 渲染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;
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 className={styles['market-container']}>
{/* 头部搜索区域 */}
<div className={styles['market-header']}>
<Title heading={4}></Title>
<Text type="secondary">使</Text>
<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;
}
<div className={styles['search-section']}>
<Input
return (
<Tag
size="large"
placeholder="搜索组件名称、功能或标签..."
prefix={<IconSearch />}
style={{ width: 400, marginRight: 16 }}
/>
<div className={styles['filter-tags']}>
<Tag icon={<IconFilter />} color="arcoblue"></Tag>
</div>
</div>
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>
<Divider style={{ margin: '20px 0' }} />
{/* 分类导航 */}
<div className={styles['category-section']}>
<div className={styles['category-list']}>
{categories.map(category => (
{/* 第二层分类标签 */}
<div>
{getFilteredSecondLevelCategories().map((category) => (
<Tag
key={category.id}
className={styles['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.name} <span className={styles['category-count']}>({category.count})</span>
{category.label}
{getSecondLevelCount(category.value)}
</Tag>
))}
</div>
</div>
);
}, [firstLevelCategory, secondLevelCategory, getCategoryCounts]);
{/* 组件列表 */}
<div className={styles['components-section']}>
<Row gutter={20}>
{components.map(component => (
<Col span={12} key={component.id} className={styles['component-col']}>
<Card className={styles['component-card']} hoverable>
<div className={styles['card-header']}>
<div className={styles['component-title']}>
<Title heading={6}>{component.name}</Title>
<Tag color="arcoblue">{component.category}</Tag>
</div>
<Text type="secondary" ellipsis>{component.description}</Text>
// 渲染组件分类
const renderComponentCategory = useCallback(() => {
// 根据第一层和第二层分类筛选组件
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 && compList.myLibs) {
filteredComponents = [...filteredComponents, ...compList.myLibs];
}
if (conditions.includePubLibs && compList.pubLibs) {
filteredComponents = [...filteredComponents, ...compList.pubLibs];
}
if (conditions.includeTeamLibs && compList.teamLibs) {
filteredComponents = [...filteredComponents, ...compList.teamLibs];
}
// 处理复合组件
const flowComponents = { label: '复合组件', children: [] };
if (conditions.includeMyFlow && compList.myFlow) {
flowComponents.children.push(...compList.myFlow);
}
if (conditions.includePubFlow && compList.pubFlow) {
flowComponents.children.push(...compList.pubFlow);
}
// 如果有复合组件,添加到筛选结果中
if (flowComponents.children.length > 0) {
filteredComponents = [...filteredComponents, flowComponents];
}
// 如果没有组件,显示提示信息
if (filteredComponents.length === 0) {
return (
<div style={{ textAlign: 'center', padding: '40px 0', color: '#999' }}>
</div>
);
}
<div className={styles['card-content']}>
<div className={styles['component-meta']}>
<div className={styles['meta-item']}>
<IconUser />
<span>{component.author}</span>
// 渲染组件
return (
<Collapse expandIconPosition={'right'} bordered={false}>
{filteredComponents.map((category, 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>
<div className={styles['meta-item']}>
<IconCalendar />
<span>{component.updateTime}</span>
}
>
{category.children.map((component, compIndex) => (
<>
<div className={styles['component-list']} key={compIndex}>
<div className={styles['component-info']}>
<img src="/icons/compIcon.png" alt="" />
<span>{component.label}</span>
</div>
{/*两种状态 未添加的是primary 已添加的是secondary*/}
<Button type="primary" style={{ borderRadius: 4 }}></Button>
</div>
<div className={styles['component-tags']}>
{component.tags.map((tag, index) => (
<Tag key={index} color="gray">{tag}</Tag>
<Divider />
</>
))}
</div>
</div>
</Collapse.Item>
);
}).filter(item => item !== null)}
</Collapse>
);
}, [compList, firstLevelCategory, secondLevelCategory]);
<div className={styles['card-footer']}>
<div className={styles['component-stats']}>
<div className={styles['stat-item']}>
<IconDownload />
<span>{component.downloads.toLocaleString()}</span>
</div>
<div className={styles['stat-item']}>
<IconStar />
<span>{component.rating}</span>
useEffect(() => {
const userInfo = JSON.parse(sessionStorage.getItem('userInfo') || '{}');
const componentData = JSON.parse(sessionStorage.getItem(`compLibs${userInfo.userId}`));
setCompList(componentData);
}, []);
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 }}
/>
<div className={styles['filter-tags']}>
<Button type="primary" icon={<IconSync />}></Button>
</div>
</div>
<div className={styles['component-actions']}>
<Text className={styles['install-btn']}></Text>
</div>
{/* tag样式选择器 */}
<div className={styles['category-section']}>
{renderCategoryTage()}
</div>
</Card>
</Col>
))}
</Row>
<Divider style={{ margin: '20px 0' }} />
<div className={styles['component-section']}>
{renderComponentCategory()}
</div>
</div>
);

@ -19,107 +19,44 @@
}
.category-section {
margin: 20px 0;
.category-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
.category-tag {
cursor: pointer;
transition: all 0.2s;
&:hover {
transform: translateY(-2px);
}
.category-count {
color: #86909c;
}
}
}
margin-top: 20px;
}
.components-section {
.component-col {
margin-bottom: 20px;
.component-section {
:global(.arco-collapse-item ) {
border-bottom: none;
}
.component-card {
height: 100%;
display: flex;
flex-direction: column;
.card-header {
flex: 1;
margin-bottom: 16px;
.component-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
:global(.arco-collapse-item-content-box) {
background-color: #fff;
}
.card-content {
flex: 1;
margin-bottom: 16px;
.component-meta {
display: flex;
gap: 16px;
margin-bottom: 12px;
.meta-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #86909c;
}
:global(.arco-collapse-item-header) {
border-bottom: none;
color: #6D7278;
}
.component-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
:global(.arco-collapse-item-header-right) {
padding-left: 0;
}
.card-footer {
.component-list {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid #f2f3f5;
padding-top: 16px;
.component-stats {
display: flex;
gap: 16px;
padding-top: 10px;
.stat-item {
.component-info {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: #86909c;
}
}
.component-actions {
.install-btn {
color: #626aea;
cursor: pointer;
font-weight: 500;
&:hover {
color: #4169e1;
}
}
img {
width: 40px;
height: 40px;
margin-right: 10px;
}
}
}
}
}

Loading…
Cancel
Save