index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. import * as React from 'react';
  2. import { Dropdown, MenuProps, Button } from 'antd';
  3. import { CodeBracketsSquare, EditPencil, Trash, ShareIos, Star, BrightStar, Eye, Heart, Calendar, BadgeCheck, Building, Menu, ArrowRight } from 'iconoir-react';
  4. import './index.scss';
  5. export interface AppCardProps {
  6. // 基本信息
  7. id: string;
  8. name: string;
  9. description: string;
  10. icon?: string;
  11. iconImage?: string;
  12. iconBgColor?: 'blue' | 'indigo' | 'teal' | 'purple' | 'rose' | 'cyan' | 'amber' | 'green' | 'orange';
  13. // 创建者信息
  14. creator?: string;
  15. creatorId?: string;
  16. proName?: string; // 项目名称
  17. // 标签和认证
  18. tags?: Array<{
  19. label: string;
  20. color: 'slate' | 'blue' | 'indigo' | 'teal' | 'purple' | 'rose' | 'cyan' | 'amber';
  21. }>;
  22. certification?: string;
  23. // 状态标识
  24. isHot?: boolean;
  25. status?: 'draft' | 'pending' | 'approved' | 'rejected';
  26. // 统计数据
  27. viewCount?: number;
  28. favoriteCount?: number;
  29. createTime?: string;
  30. // 其他信息
  31. department?: string;
  32. visible?: 'public' | 'private' | 'vip';
  33. appId?: string;
  34. isCollect?: boolean;
  35. isCreator?: boolean;
  36. // 显示控制
  37. showCreator?: boolean;
  38. showActions?: boolean;
  39. showCertification?: boolean;
  40. showTags?: boolean;
  41. showHot?: boolean;
  42. showViewCount?: boolean;
  43. showFavoriteCount?: boolean;
  44. showCreateTime?: boolean;
  45. showStatus?: boolean;
  46. showDepartment?: boolean;
  47. showVisible?: boolean;
  48. showOperations?: boolean;
  49. // 回调函数
  50. onPlay?: () => void;
  51. onShare?: () => void;
  52. onFavorite?: () => void;
  53. onEdit?: () => void;
  54. onDelete?: () => void;
  55. onView?: () => void;
  56. onApi?: () => void;
  57. onApiDirect?: () => void; // API 调用(非创建者显示)
  58. }
  59. const AppCard: React.FC<AppCardProps> = (props) => {
  60. const {
  61. // 基本信息
  62. name,
  63. description,
  64. icon,
  65. iconImage,
  66. iconBgColor = 'blue',
  67. // 创建者信息
  68. creator,
  69. proName,
  70. // 标签和认证
  71. tags = [],
  72. certification,
  73. // 状态标识
  74. isHot,
  75. status,
  76. // 统计数据
  77. viewCount,
  78. favoriteCount,
  79. createTime,
  80. // 其他信息
  81. department,
  82. visible,
  83. isCollect = false,
  84. isCreator = false,
  85. // 显示控制
  86. showCreator = true,
  87. showActions = true,
  88. showCertification = true,
  89. showTags = true,
  90. showHot = true,
  91. showViewCount = false,
  92. showFavoriteCount = false,
  93. showCreateTime = false,
  94. showStatus = false,
  95. showDepartment = false,
  96. showVisible = false,
  97. showOperations = false,
  98. // 回调函数
  99. onShare,
  100. onFavorite,
  101. onEdit,
  102. onDelete,
  103. onView,
  104. onApi,
  105. onApiDirect,
  106. } = props;
  107. // 配置对象
  108. const statusConfig = {
  109. draft: { label: '草稿', color: '#9CA3AF' },
  110. pending: { label: '审核中', color: '#F59E0B' },
  111. approved: { label: '已通过', color: '#10B981' },
  112. rejected: { label: '已拒绝', color: '#EF4444' },
  113. };
  114. const visibleConfig = {
  115. public: { label: '公开', color: '#005D80' },
  116. private: { label: '私有', color: '#6B7280' },
  117. vip: { label: 'VIP', color: '#F59E0B' },
  118. };
  119. // 构建操作菜单项
  120. const operationItems: MenuProps['items'] = React.useMemo(() => {
  121. const items: any[] = [];
  122. if (onApi) {
  123. items.push({
  124. key: 'api',
  125. label: 'API 调用',
  126. icon: <CodeBracketsSquare width="16" height="16" />,
  127. onClick: () => onApi(),
  128. });
  129. }
  130. if (isCreator && onEdit) {
  131. items.push({
  132. key: 'edit',
  133. label: '编辑',
  134. icon: <EditPencil width="16" height="16" />,
  135. onClick: () => onEdit(),
  136. });
  137. }
  138. if (isCreator && onDelete) {
  139. items.push({
  140. key: 'delete',
  141. label: '删除',
  142. icon: <Trash width="16" height="16" />,
  143. danger: true,
  144. onClick: () => onDelete(),
  145. });
  146. }
  147. return items;
  148. }, [onApi, onEdit, onDelete, isCreator]);
  149. // 事件处理
  150. const handleShareClick = () => {
  151. onShare?.();
  152. };
  153. const handleFavoriteClick = () => {
  154. onFavorite?.();
  155. };
  156. const handleViewClick = () => {
  157. onView?.();
  158. };
  159. return (
  160. <div className='app-card'>
  161. {/* Hot 标签 */}
  162. {showHot && isHot && (
  163. <span className='card-hot-badge'>Hot</span>
  164. )}
  165. {/* 状态标签 */}
  166. {showStatus && status && (
  167. <span
  168. className='card-status-badge'
  169. style={{ background: statusConfig[status].color }}
  170. >
  171. {statusConfig[status].label}
  172. </span>
  173. )}
  174. {/* 操作按钮 - 悬停显示 */}
  175. {showActions && (
  176. <div className='card-actions'>
  177. <button
  178. className='card-action-btn'
  179. onClick={(e) => {
  180. e.stopPropagation();
  181. handleShareClick();
  182. }}
  183. title='分享'
  184. >
  185. <ShareIos width="18" height="18" />
  186. </button>
  187. <button
  188. className='card-action-btn'
  189. onClick={(e) => {
  190. e.stopPropagation();
  191. handleFavoriteClick();
  192. }}
  193. title={isCollect ? '取消收藏' : '收藏'}
  194. >
  195. {isCollect ? (
  196. <BrightStar width="18" height="18" style={{ color: '#F5E663' }} />
  197. ) : (
  198. <Star width="18" height="18" />
  199. )}
  200. </button>
  201. </div>
  202. )}
  203. {/* 卡片头部:图标 + 创建者信息 */}
  204. <div className='card-header'>
  205. <div className={`card-icon-wrapper bg-${iconBgColor}`}>
  206. {iconImage ? (
  207. <img alt={name} className='card-icon' src={iconImage} />
  208. ) : icon ? (
  209. <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24 }}>
  210. {/* 动态 icon 支持 - 如果传入的是 iconoir 组件名称则渲染,否则显示占位 */}
  211. <BrightStar width="24" height="24" />
  212. </div>
  213. ) : null}
  214. </div>
  215. {showCreator && creator && (
  216. <div className='card-creator'>
  217. <div className='card-creator-info'>
  218. <span className='card-creator-label'>创建者</span>
  219. <span className='card-creator-name'>{creator}</span>
  220. {proName && (
  221. <span className='card-pro-name' title={proName}>{proName}</span>
  222. )}
  223. </div>
  224. </div>
  225. )}
  226. </div>
  227. {/* 应用名称 */}
  228. <h5 className='card-title'>{name}</h5>
  229. {/* 应用描述 */}
  230. <p className='card-description'>{description}</p>
  231. {/* 标签 */}
  232. {showTags && tags.length > 0 && (
  233. <div className='card-tags'>
  234. {tags.map((tag, index) => (
  235. <span key={index} className={`card-tag tag-${tag.color}`}>
  236. {tag.label}
  237. </span>
  238. ))}
  239. </div>
  240. )}
  241. {/* 统计信息 */}
  242. {(showViewCount || showFavoriteCount || showCreateTime) && (
  243. <div className='card-meta'>
  244. <div className='card-meta-left'>
  245. {showViewCount && viewCount !== undefined && (
  246. <span className='card-meta-item'>
  247. <Eye width="14" height="14" />
  248. <span>{viewCount}</span>
  249. </span>
  250. )}
  251. {showFavoriteCount && favoriteCount !== undefined && (
  252. <span className='card-meta-item'>
  253. <Heart width="14" height="14" />
  254. <span>{favoriteCount}</span>
  255. </span>
  256. )}
  257. </div>
  258. {showCreateTime && createTime && (
  259. <div className='card-meta-right'>
  260. <span className='card-meta-item'>
  261. <Calendar width="14" height="14" />
  262. <span>{createTime}</span>
  263. </span>
  264. </div>
  265. )}
  266. </div>
  267. )}
  268. {/* 底部信息:认证/部门/可见性 */}
  269. {showCertification && (
  270. <div className='card-footer-info'>
  271. {certification && (
  272. <div className='card-certification'>
  273. <BadgeCheck width="14" height="14" />
  274. <span>{certification}</span>
  275. </div>
  276. )}
  277. {showDepartment && department && (
  278. <div className='card-department'>
  279. <Building width="14" height="14" />
  280. <span>{department}</span>
  281. </div>
  282. )}
  283. {showVisible && visible && (
  284. <span
  285. className='card-visible-tag'
  286. style={{ color: visibleConfig[visible].color }}
  287. >
  288. {visibleConfig[visible].label}
  289. </span>
  290. )}
  291. </div>
  292. )}
  293. {/* 悬停操作按钮层 */}
  294. {showOperations && (
  295. <div className='card-hover-actions'>
  296. {/* 创建者显示【更多操作】,非创建者显示【API 调用】 */}
  297. {isCreator ? (
  298. <Dropdown
  299. menu={{ items: operationItems }}
  300. trigger={['click']}
  301. placement='topLeft'
  302. className='card-operation-dropdown'
  303. >
  304. <Button
  305. className='card-operation-btn'
  306. icon={<Menu width="18" height="18" />}
  307. size='large'
  308. >
  309. 更多操作
  310. </Button>
  311. </Dropdown>
  312. ) : (
  313. <Button
  314. className='card-operation-btn'
  315. icon={<CodeBracketsSquare width="18" height="18" />}
  316. size='large'
  317. onClick={(e) => {
  318. e.stopPropagation();
  319. onApiDirect?.();
  320. }}
  321. >
  322. API 调用
  323. </Button>
  324. )}
  325. <Button
  326. className='card-use-btn'
  327. icon={<ArrowRight width="18" height="18" />}
  328. size='large'
  329. onClick={(e) => {
  330. e.stopPropagation();
  331. handleViewClick();
  332. }}
  333. >
  334. 立即使用
  335. </Button>
  336. </div>
  337. )}
  338. </div>
  339. );
  340. };
  341. export default AppCard;