index.tsx 12 KB

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