feat(componentMarket): 实现组件市场页面及详情页功能

master
钟良源 2 months ago
parent 8a56649019
commit d51c65d51b

@ -267,7 +267,6 @@ export interface ReviewGroup {
export interface ComponentMarketParams { export interface ComponentMarketParams {
componentClassify: string; componentClassify: string;
componentClassifyLabel: string;
keyword: string; keyword: string;
current?: string | number; current?: string | number;
size?: string | number; size?: string | number;

@ -1,73 +1,173 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import styles from './style/compCard.module.less'; import styles from './style/compCard.module.less';
import { compData } from '@/pages/componentMarket/test/data'; import { Card, Grid, Rate, Typography, Pagination, Spin, Empty, Image } from '@arco-design/web-react';
import { Card, Grid, Rate, Typography } from '@arco-design/web-react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getComponentMarket } from '@/api/componentMarket';
const { Row, Col } = Grid; const { Row, Col } = Grid;
interface CompCardProps { interface CompCardProps {
selectedTab: string;
componentClassify: string; componentClassify: string;
searchClassify: string;
searchKeyword: string;
selectedComp: (component: ComponentItem) => void;
} }
const CompCard: React.FC<CompCardProps> = ({ componentClassify }) => { interface ComponentItem {
const [componentClassifyData, setComponentClassifyData] = useState<any>([]); id: string;
name: string;
identifier?: string;
componentClassify?: string;
deployType?: string;
star?: number;
description?: string;
[key: string]: any;
}
const CompCard: React.FC<CompCardProps> = ({
selectedTab,
componentClassify,
searchClassify,
searchKeyword,
selectedComp
}) => {
const [componentList, setComponentList] = useState<ComponentItem[]>([]);
const [loading, setLoading] = useState(false);
const [current, setCurrent] = useState(1);
const [total, setTotal] = useState(0);
const pageSize = 12; // 每页显示12条数据
const router = useRouter(); const router = useRouter();
useEffect(() => { // 获取组件市场数据
if (componentClassify === '全部') setComponentClassifyData(compData); const fetchComponentData = async (page = 1) => {
else { try {
const filterData = compData.filter(v => v.componentClassify === componentClassify); setLoading(true);
setComponentClassifyData(filterData || []);
// 根据搜索条件或当前Tab确定分类
let classifyLabel = '全部';
// 如果搜索框有选择分类,使用搜索框的分类
if (searchClassify && searchClassify !== '全部') {
classifyLabel = searchClassify;
}
// 否则使用当前Tab的分类
else if (componentClassify && componentClassify !== '全部') {
classifyLabel = componentClassify;
}
const params = {
componentClassify: classifyLabel,
keyword: searchKeyword || '',
current: page,
size: pageSize
};
const res: any = await getComponentMarket(params);
if (res?.code === 200 && res?.data) {
setComponentList(res.data.list || []);
setTotal(res.data.totalCount || 0);
}
else {
setComponentList([]);
setTotal(0);
}
} catch (error) {
console.error('获取组件市场数据失败:', error);
setComponentList([]);
setTotal(0);
} finally {
setLoading(false);
} }
}, [componentClassify]); };
// 监听分类、搜索条件变化
useEffect(() => {
setCurrent(1);
fetchComponentData(1);
}, [searchKeyword, selectedTab]);
const handelDetails = () => { // 处理分页变化
router.push('/componentMarket/compDetails'); const handlePageChange = (page: number) => {
setCurrent(page);
fetchComponentData(page);
};
// 跳转到组件详情
const handleDetails = (component: ComponentItem) => {
selectedComp(component);
}; };
return ( return (
<div className={styles['comp-card']}> <div className={styles['comp-card']}>
<Row style={{ marginBottom: 16 }}> <Spin loading={loading} style={{ width: '100%' }}>
{componentClassifyData.map((v, i) => { {componentList.length > 0 ? (
return ( <>
<Col <Row style={{ marginBottom: 16 }}>
xs={12} {componentList.map((item) => (
sm={12} <Col
md={12} xs={12}
lg={6} sm={12}
xl={6} md={12}
xxl={6} lg={6}
key={i}> xl={6}
<Card style={{ cursor: 'pointer', border: '1px solid #d9d9d9' }} onClick={() => handelDetails()}> xxl={6}
{/*左侧图片*/} key={item.id}
<div className={styles['img-box']}></div> style={{ marginBottom: 16 }}
{/*右侧数据*/} >
<div className={styles['info-box']}> <Card
<div className={styles['info-title']}>{v.name}</div> style={{ cursor: 'pointer', border: '1px solid #d9d9d9' }}
<div className={styles['info-author']}>{v.identifier}</div> onClick={() => handleDetails(item)}
<div className={styles['info-score']}> hoverable
<div></div> >
<Rate readonly allowHalf value={v.star < 0 ? 5 : v.star} /> {/*左侧图片*/}
<Typography.Text <div className={styles['img-box']}>
style={{ <Image width={'100%'} height={'100%'} src={item.logoUrl} alt="暂无图片"></Image>
margin: '0 5px' </div>
}} {/*右侧数据*/}
> <div className={styles['info-box']}>
{v.star < 0 ? 5 : v.star} <div className={styles['info-title']}>{item.name || '未命名组件'}</div>
</Typography.Text> <div className={styles['info-author']}>{item.identifier || '-'}</div>
<div className={styles['info-score']}>
<div></div>
<div className={styles['info-rate']}>
<Rate readonly allowHalf value={item.star || 5} />
<Typography.Text style={{ margin: '0 5px', fontSize: 16 }}>
{item.star || 5}
</Typography.Text>
</div>
</div>
</div>
</Card>
<div className={styles['comp-card-footer']}>
<div className={styles['comp-type']}>{item.componentClassify || '-'}</div>
<div className={styles['comp-language']}>{item.deployType || '-'}</div>
</div> </div>
</div> </Col>
</Card> ))}
<div className={styles['comp-card-footer']}> </Row>
<div className={styles['comp-type']}>{v.componentClassify}</div>
<div className={styles['comp-language']}>{v.deployType}</div> {/* 分页组件 */}
{total > pageSize && (
<div style={{ display: 'flex', justifyContent: 'center', marginTop: 24 }}>
<Pagination
current={current}
pageSize={pageSize}
total={total}
onChange={handlePageChange}
showTotal
showJumper
sizeCanChange={false}
/>
</div> </div>
</Col> )}
); </>
})} ) : (
</Row> <Empty description="暂无组件数据" />
)}
</Spin>
</div> </div>
); );
}; };

@ -1,10 +1,340 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import styles from './style/compDetails.module.less';
import { Space, Divider, Button, Typography, Card, Image, Rate, Grid } from '@arco-design/web-react';
import dayjs from 'dayjs';
import { getComponentMarket } from '@/api/componentMarket';
const { Row, Col } = Grid;
const CompDetails = ({ compInfo }) => {
const [componentList, setComponentList] = useState([]);
const [currentCompInfo, setCurrentCompInfo] = useState<any>(compInfo);
// 获取组件市场数据
const fetchComponentData = async () => {
try {
const params = {
componentClassify: compInfo.componentClassify,
keyword: '',
current: 1,
size: 4
};
const res: any = await getComponentMarket(params);
if (res?.code === 200 && res?.data) {
setComponentList(res.data.list || []);
setComponentList([
{
'baseConfigJson': [],
'codeLanguage': 'Python',
'collaboratorId': 0,
'componentBaseId': '1993166956502499330',
'componentClassify': '设备数采与控制交互组件',
'componentStatus': 'DEPLOYED',
'componentVersion': 1,
'createBy': '1992851580222222338',
'createTime': 1764228733000,
'definitionJson': '{"apis":[{"apiType":"EVENT","eventApi":{"topic":"add"},"fieldIns":["a"],"fieldOuts":["v"],"id":"add","restApi":{}}],"dataIns":[{"dataType":"INTEGER","desc":"","id":"a","type":"DATA"}],"dataOuts":[{"dataType":"INTEGER","desc":"","id":"v","type":"DATA"}]}',
'deployType': 'Python',
'desc': '',
'dockerImageId': '',
'dockerImageName': '',
'extraSystemId': '',
'filesName': '',
'id': '1993945941293449218',
'identifier': 'admin_add_num_maket1',
'isDeleted': 0,
'localProjectPath': '/000000/admin_add_num_maket1/master',
'logoUrl': '',
'name': '两数之和2',
'operatesJson': [],
'permission': null,
'projectId': 'add_num_maket1',
'publicStatus': 1,
'publishTime': null,
'reviewOpinion': '',
'size': '106.30 KB',
'star': 0,
'status': 1,
'tags': [],
'tenantId': '000000',
'updateBy': '1992851580222222338',
'updateTime': 1764232122000,
'version': 'master'
},
{
'baseConfigJson': [],
'codeLanguage': 'Python',
'collaboratorId': 0,
'componentBaseId': '1993166956502499330',
'componentClassify': '设备数采与控制交互组件',
'componentStatus': 'DEPLOYED',
'componentVersion': 1,
'createBy': '1992851580222222338',
'createTime': 1764228733000,
'definitionJson': '{"apis":[{"apiType":"EVENT","eventApi":{"topic":"add"},"fieldIns":["a"],"fieldOuts":["v"],"id":"add","restApi":{}}],"dataIns":[{"dataType":"INTEGER","desc":"","id":"a","type":"DATA"}],"dataOuts":[{"dataType":"INTEGER","desc":"","id":"v","type":"DATA"}]}',
'deployType': 'Python',
'desc': '',
'dockerImageId': '',
'dockerImageName': '',
'extraSystemId': '',
'filesName': '',
'id': '1993945941293449218',
'identifier': 'admin_add_num_maket1',
'isDeleted': 0,
'localProjectPath': '/000000/admin_add_num_maket1/master',
'logoUrl': '',
'name': '两数之和3',
'operatesJson': [],
'permission': null,
'projectId': 'add_num_maket1',
'publicStatus': 1,
'publishTime': null,
'reviewOpinion': '',
'size': '106.30 KB',
'star': 0,
'status': 1,
'tags': [],
'tenantId': '000000',
'updateBy': '1992851580222222338',
'updateTime': 1764232122000,
'version': 'master'
},
{
'baseConfigJson': [],
'codeLanguage': 'Python',
'collaboratorId': 0,
'componentBaseId': '1993166956502499330',
'componentClassify': '设备数采与控制交互组件',
'componentStatus': 'DEPLOYED',
'componentVersion': 1,
'createBy': '1992851580222222338',
'createTime': 1764228733000,
'definitionJson': '{"apis":[{"apiType":"EVENT","eventApi":{"topic":"add"},"fieldIns":["a"],"fieldOuts":["v"],"id":"add","restApi":{}}],"dataIns":[{"dataType":"INTEGER","desc":"","id":"a","type":"DATA"}],"dataOuts":[{"dataType":"INTEGER","desc":"","id":"v","type":"DATA"}]}',
'deployType': 'Python',
'desc': '',
'dockerImageId': '',
'dockerImageName': '',
'extraSystemId': '',
'filesName': '',
'id': '1993945941293449218',
'identifier': 'admin_add_num_maket1',
'isDeleted': 0,
'localProjectPath': '/000000/admin_add_num_maket1/master',
'logoUrl': '',
'name': '两数之和4',
'operatesJson': [],
'permission': null,
'projectId': 'add_num_maket1',
'publicStatus': 1,
'publishTime': null,
'reviewOpinion': '',
'size': '106.30 KB',
'star': 0,
'status': 1,
'tags': [],
'tenantId': '000000',
'updateBy': '1992851580222222338',
'updateTime': 1764232122000,
'version': 'master'
},
{
'baseConfigJson': [],
'codeLanguage': 'Python',
'collaboratorId': 0,
'componentBaseId': '1993166956502499330',
'componentClassify': '设备数采与控制交互组件',
'componentStatus': 'DEPLOYED',
'componentVersion': 1,
'createBy': '1992851580222222338',
'createTime': 1764228733000,
'definitionJson': '{"apis":[{"apiType":"EVENT","eventApi":{"topic":"add"},"fieldIns":["a"],"fieldOuts":["v"],"id":"add","restApi":{}}],"dataIns":[{"dataType":"INTEGER","desc":"","id":"a","type":"DATA"}],"dataOuts":[{"dataType":"INTEGER","desc":"","id":"v","type":"DATA"}]}',
'deployType': 'Python',
'desc': '',
'dockerImageId': '',
'dockerImageName': '',
'extraSystemId': '',
'filesName': '',
'id': '1993945941293449218',
'identifier': 'admin_add_num_maket1',
'isDeleted': 0,
'localProjectPath': '/000000/admin_add_num_maket1/master',
'logoUrl': '',
'name': '两数之和5',
'operatesJson': [],
'permission': null,
'projectId': 'add_num_maket1',
'publicStatus': 1,
'publishTime': null,
'reviewOpinion': '',
'size': '106.30 KB',
'star': 0,
'status': 1,
'tags': [],
'tenantId': '000000',
'updateBy': '1992851580222222338',
'updateTime': 1764232122000,
'version': 'master'
}
]);
}
else {
setComponentList([]);
}
} catch (error) {
console.error('获取组件市场数据失败:', error);
setComponentList([]);
} finally {
}
};
useEffect(() => {
fetchComponentData();
}, []);
// 渲染组件外壳
const renderCompHousing = () => {
return (
<div className={styles['comp-housing']}>
<div className={styles['comp-housing-header']}>
<div className={styles['comp-housing-title']}>{currentCompInfo.name}</div>
</div>
<div className={styles['comp-housing-content']}>
{currentCompInfo.def ? (
<>
<div className={styles['comp-housing-content-api']}>
<div className={styles['comp-housing-content-api-in']}>
{currentCompInfo.def?.apis?.map((item: any, index) => <div key={index}>{item.id}</div>)}
</div>
<div className={styles['comp-housing-content-api-out']}>
<div>{currentCompInfo.def?.apiOut?.id}</div>
</div>
</div>
<Divider style={{ marginTop: 10, marginBottom: 10 }} />
<div className={styles['comp-housing-content-data']}>
<div className={styles['comp-housing-content-data-in']}>
{currentCompInfo.def?.dataIns?.map((item: any, index) => <div key={index}>{item.id}</div>)}
</div>
<div className={styles['comp-housing-content-data-out']}>
{currentCompInfo.def?.dataOuts?.map((item: any, index) => <div key={index}>{item.id}</div>)}
</div>
</div>
</>
) : (
<>
<div className={styles['comp-housing-content-api']}>
<div className={styles['comp-housing-content-api-in']}>
<div>contour</div>
</div>
<div className={styles['comp-housing-content-api-out']}>
<div>done</div>
</div>
</div>
<Divider style={{ marginTop: 10, marginBottom: 10 }} />
<div className={styles['comp-housing-content-data']}>
<div className={styles['comp-housing-content-data-in']}>
{currentCompInfo.flowHousVO?.dataIns?.map((item: any, index) => <div key={index}>{item.id}</div>)}
</div>
<div className={styles['comp-housing-content-data-out']}>
{currentCompInfo.flowHousVO?.dataOuts?.map((item: any, index) => <div key={index}>{item.id}</div>)}
</div>
</div>
</>
)}
</div>
</div>
);
};
const CompDetails = () => {
return ( return (
<div className="comp-details"> <>
<div className={styles['comp-container']}>
</div> <div className={styles['comp-preview']}>
<h3></h3>
{renderCompHousing()}
</div>
<div className={styles['comp-info']}>
<div className={styles['header']}>
<Space size={40}>
<div className={styles['title']}>{currentCompInfo.name}</div>
<div
className={styles['update-time']}>: {dayjs(currentCompInfo.updateTime).format('YYYY-MM-DD HH:mm:ss')}</div>
<div className={styles['update-time']}>: {currentCompInfo.size}</div>
<div className={styles['update-time']}>: V_{currentCompInfo.status}</div>
<div className={styles['update-time']}>: {currentCompInfo.star}</div>
</Space>
</div>
<div className={styles['extra']}>
<Divider style={{ borderColor: '#5484ff', marginTop: 0, marginBottom: 30 }} />
<Space size={30}>
<div className={styles['extra-font']}>{currentCompInfo.identifier}</div>
<div className={styles['extra-font']}>{currentCompInfo.componentClassify}</div>
<div className={styles['extra-font']}>{currentCompInfo.codeLanguage}</div>
</Space>
</div>
<Divider style={{ marginTop: 15, borderBottomStyle: 'dashed' }} />
<div className={styles['description']}>
{currentCompInfo.description ? currentCompInfo.description : ' - '}
</div>
<Divider style={{ borderBottomStyle: 'dashed' }} />
<div>md</div>
<Divider style={{ borderBottomStyle: 'dashed' }} />
<div className={styles['handel-box']}>
<Button type="text">: {dayjs(currentCompInfo.updateTime).format('YYYY-MM-DD HH:mm:ss')}</Button>
<Button type="text"></Button>
</div>
<Divider style={{ borderBottomStyle: 'dashed' }} />
</div>
</div>
<div className={styles['recommend']}>
<Typography.Title heading={5}></Typography.Title>
<Row style={{ marginBottom: 16 }}>
{componentList.map((item) => (
<Col
xs={12}
sm={12}
md={12}
lg={6}
xl={6}
xxl={6}
key={item.id}
style={{ marginBottom: 16 }}
>
<Card
style={{ cursor: 'pointer', border: '1px solid #d9d9d9' }}
onClick={() => setCurrentCompInfo(item)}
hoverable
>
{/*左侧图片*/}
<div className={styles['img-box']}>
<Image width={'100%'} height={'100%'} src={item.logoUrl} alt="暂无图片"></Image>
</div>
{/*右侧数据*/}
<div className={styles['info-box']}>
<div className={styles['info-title']}>{item.name || '未命名组件'}</div>
<div className={styles['info-author']}>{item.identifier || '-'}</div>
<div className={styles['info-score']}>
<div></div>
<div className={styles['info-rate']}>
<Rate readonly allowHalf value={item.star || 5} />
<Typography.Text style={{ margin: '0 5px', fontSize: 16 }}>
{item.star || 5}
</Typography.Text>
</div>
</div>
</div>
</Card>
<div className={styles['comp-card-footer']}>
<div className={styles['comp-type']}>{item.componentClassify || '-'}</div>
<div className={styles['comp-language']}>{item.deployType || '-'}</div>
</div>
</Col>
))}
</Row>
</div>
</>
); );
}; };

@ -2,68 +2,128 @@ import React, { useEffect, useState } from 'react';
import styles from './style/index.module.less'; import styles from './style/index.module.less';
import CustomCard from '@/components/CustomCard/index'; import CustomCard from '@/components/CustomCard/index';
import CompCard from './compCard'; import CompCard from './compCard';
import { menu } from './test/data'; import CompDetails from './compDetails';
import { Tabs, Input, Select } from '@arco-design/web-react'; import { Tabs, Input, Select, Button } from '@arco-design/web-react';
import { getComponentMarket } from '@/api/componentMarket'; import { IconUndo } from '@arco-design/web-react/icon';
import { getComponentClassify } from '@/api/componentClassify'; import { getComponentClassify } from '@/api/componentClassify';
const TabPane = Tabs.TabPane; const TabPane = Tabs.TabPane;
const InputSearch = Input.Search; const InputSearch = Input.Search;
function ComponentMarket() { // 分类数据接口
interface ClassifyItem {
id: string;
classifyName: string;
classifyType: string;
remark?: string;
sort?: number;
}
const getMarketData = async () => { function ComponentMarket() {
const params = { const [menuList, setMenuList] = useState<ClassifyItem[]>([]);
componentClassify: '', const [activeTab, setActiveTab] = useState<string>('全部');
componentClassifyLabel: '全部', const [searchClassify, setSearchClassify] = useState<string>('全部');
keyword: '', const [searchKeyword, setSearchKeyword] = useState<string>('');
current: 1, const [selectedComp, setSelectedComp] = useState(null);
size: 15
};
const res: any = await getComponentMarket(params);
console.log('res:', res);
};
// 获取分类菜单列表
const getMenuList = async () => { const getMenuList = async () => {
const res: any = await getComponentClassify('component'); try {
console.log('menu:', res); const res: any = await getComponentClassify('component');
if (res?.code === 200 && res?.data) {
// 添加"全部"选项到菜单列表
const allOption = {
id: '0',
classifyName: '全部',
classifyType: 'component',
sort: 0
};
setMenuList([allOption, ...res.data]);
}
} catch (error) {
console.error('获取分类菜单失败:', error);
}
}; };
useEffect(() => { useEffect(() => {
getMarketData();
getMenuList(); getMenuList();
}, []); }, []);
// 处理Tab切换
const handleTabChange = (key: string) => {
setActiveTab(key);
};
// 处理搜索
const handleSearch = (value: string) => {
setSearchKeyword(value);
};
// 处理分类选择
const handleClassifyChange = (value: string) => {
setSearchClassify(value);
};
return ( return (
<> <>
<div className={styles['comp-market-container']}> <div className={styles['comp-market-container']}>
<CustomCard> <CustomCard>
{/*公用搜索*/} {/*公用搜索*/}
<div className={styles['comp-market-header']}> <div className={styles['comp-market-header']}>
{selectedComp && (
<Button type="text" style={{ marginRight: 10 }} onClick={() => setSelectedComp(null)}>
<IconUndo style={{ fontSize: 16 }} />
</Button>)}
<div className={styles['comp-market-search']}> <div className={styles['comp-market-search']}>
<Input.Group compact> <Input.Group compact>
<Select size="large" defaultValue="全部" style={{ width: '25%' }}> <Select
{menu.map((item, index) => { size="large"
return ( value={searchClassify}
<Select.Option key={index} value={item.dictValue}>{item.dictValue}</Select.Option> onChange={handleClassifyChange}
); style={{ width: '25%' }}
})} >
{menuList.map((item) => (
<Select.Option key={item.id} value={item.classifyName}>
{item.classifyName}
</Select.Option>
))}
</Select> </Select>
<InputSearch size="large" placeholder="请输入组件名、关键词进行搜索" style={{ width: '75%' }} /> <InputSearch
size="large"
placeholder="请输入组件名、关键词进行搜索"
style={{ width: '75%' }}
onSearch={handleSearch}
allowClear
/>
</Input.Group> </Input.Group>
</div> </div>
</div> </div>
{/*单类型数据渲染*/} {selectedComp ?
<Tabs key="card" tabPosition="left"> <CompDetails compInfo={selectedComp} />
{menu.map((item) => { :
return ( <Tabs
<TabPane key={item.id} title={item.dictValue}> key="card"
<CompCard componentClassify={item.dictValue}></CompCard> tabPosition="left"
activeTab={activeTab}
onChange={handleTabChange}
>
{/*单类型数据渲染*/}
{menuList.map((item) => (
<TabPane key={item.classifyName} title={item.classifyName}>
{item.classifyName === activeTab && (
<CompCard
selectedTab={activeTab}
componentClassify={item.classifyName}
searchClassify={searchClassify}
searchKeyword={searchKeyword}
selectedComp={setSelectedComp}
/>
)}
</TabPane> </TabPane>
); ))}
})} </Tabs>
</Tabs> }
</CustomCard> </CustomCard>
</div> </div>
</> </>

@ -11,17 +11,20 @@
.img-box { .img-box {
width: 80px; width: 80px;
height: 110px; height: 110px;
background-color: #cccccc;
margin-right: 10px; margin-right: 10px;
} }
.info-box { .info-box {
width: calc(100% - 100px);
.info-author { .info-author {
margin-bottom: 40px; margin-bottom: 40px;
white-space: nowrap; /* 禁止换行 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 溢出部分用省略号表示 */
} }
.info-score { .info-rate {
display: flex; display: flex;
align-items: center; align-items: center;
} }

@ -0,0 +1,132 @@
.comp-container {
box-sizing: border-box;
display: flex;
width: 100%;
height: 100%;
background-color: #fff;
padding: 32px 27px 27px 29px;
.comp-preview {
box-sizing: border-box;
width: 345px;
height: 100%;
margin-right: 68px;
padding: 20px 25px;
border-radius: 8px;
box-shadow: 2px 2px 20px 0 rgba(0, 0, 0, .25);
.comp-housing {
width: 95%;
height: 250px;
margin: 0 auto;
border: 1px solid #CCCCCC;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
overflow: hidden;
.comp-housing-header {
background-color: #ffa100;
color: #ffffff;
padding: 5px 10px;
}
.comp-housing-content {
.comp-housing-content-api,
.comp-housing-content-data {
display: flex;
padding: 5px;
.comp-housing-content-api-in,
.comp-housing-content-api-out,
.comp-housing-content-data-in,
.comp-housing-content-data-out {
flex: 1;
}
.comp-housing-content-api-out,
.comp-housing-content-data-out {
text-align: right;
}
}
}
}
}
.comp-info {
flex: 1;
.header {
.title {
font-size: 22px;
font-weight: 700;
}
.update-time {
color: #888888;
}
}
.extra {
.extra-font {
font-size: 18px;
font-weight: 700;
}
}
.description {
max-height: 15%;
overflow-y: auto;
}
.params {
box-sizing: border-box;
padding: 10px 0 25px 20px;
background-color: #fbfbfb;
}
}
}
.recommend {
padding: 20px;
:global(.arco-col) {
padding: 10px;
}
:global(.arco-card-body) {
display: flex;
}
.img-box {
width: 80px;
height: 110px;
margin-right: 10px;
}
.info-box {
width: calc(100% - 100px);
.info-author {
margin-bottom: 40px;
white-space: nowrap; /* 禁止换行 */
overflow: hidden; /* 超出部分隐藏 */
text-overflow: ellipsis; /* 溢出部分用省略号表示 */
}
.info-rate {
display: flex;
align-items: center;
}
}
.comp-card-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
background: linear-gradient(90deg, #64b5f6, #2175f3);
color: #ffffff;
}
}
Loading…
Cancel
Save