index.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /* eslint-disable */
  2. import React from 'react';
  3. import { resolveIcon, ResolvedIcon } from './iconRegistry';
  4. // 为了在 useAntdIcon=true 时使用 antd 的 Icon,我们做一个条件导入
  5. // 注意:如果项目没安装 @ant-design/icons,也不会报错,只是返回 undefined
  6. type AntdIconType = React.ComponentType<{
  7. component: React.ComponentType;
  8. className?: string;
  9. style?: React.CSSProperties;
  10. }>;
  11. function tryLoadAntdIcon(): AntdIconType | undefined {
  12. try {
  13. const mod = require('@ant-design/icons') as { default?: AntdIconType };
  14. return mod?.default;
  15. } catch {
  16. return undefined;
  17. }
  18. }
  19. export interface IconProps extends React.ImgHTMLAttributes<HTMLImageElement> {
  20. userId?: string;
  21. productId?: string; // 新增:产品ID
  22. module: string;
  23. name: string; // e.g. 'icon-nav-back' (不带状态与后缀)
  24. theme?: string;
  25. size?: '1x' | '2x' | '3x';
  26. state?: string;
  27. alt?: string;
  28. useAntdIcon?: boolean; // 是否尝试用 @ant-design/icons 包裹 svg
  29. widthPx?: number; // 以像素指定大小(覆盖 class 控制)
  30. url?: string; // 新增:直接指定图标URL,优先级高于其他图标解析参数
  31. }
  32. /**
  33. * 自定义 Icon 组件
  34. */
  35. const Icon: React.FC<IconProps> = ({
  36. userId,
  37. productId,
  38. module,
  39. name,
  40. theme = 'default',
  41. size = '2x',
  42. state = 'default',
  43. alt,
  44. useAntdIcon = true,
  45. widthPx,
  46. className,
  47. style,
  48. url, // 新增:解构url属性
  49. ...imgProps
  50. }) => {
  51. // 新增:优先处理URL图标
  52. if (url) {
  53. // 类型安全:确保url是字符串
  54. if (typeof url !== 'string') {
  55. console.warn('Icon组件: url属性必须是字符串类型');
  56. return null;
  57. }
  58. // 基本URL格式验证
  59. try {
  60. new URL(url); // 这会验证URL格式,但不要求必须是http/https
  61. } catch (e) {
  62. // 如果不是有效的URL,可能是相对路径,这是允许的
  63. if (!url.startsWith('/') && !url.startsWith('./') && !url.startsWith('../')) {
  64. console.warn('Icon组件: url属性格式无效', url);
  65. }
  66. }
  67. return (
  68. <img
  69. src={url}
  70. alt={alt ?? name}
  71. width={widthPx ?? undefined}
  72. height={widthPx ?? undefined}
  73. className={className}
  74. style={style}
  75. // 添加错误处理
  76. onError={(e) => {
  77. console.warn('Icon组件: URL图标加载失败', url);
  78. // 调用用户提供的onError回调(如果存在)
  79. if (imgProps.onError) {
  80. imgProps.onError(e);
  81. }
  82. }}
  83. {...imgProps}
  84. />
  85. );
  86. }
  87. const resolved: ResolvedIcon | undefined = resolveIcon({
  88. userId,
  89. productId,
  90. module,
  91. name,
  92. theme,
  93. size,
  94. state,
  95. });
  96. if (!resolved) {
  97. // 未找到图标,可以返回 null 或者 fallback
  98. return null;
  99. }
  100. // if (resolved.kind === 'svg') {
  101. // console.log(`解析到了svg图标`);
  102. // const Svg = resolved.Component;
  103. // console.log(`打印一下 resolved.Component 看是什么 === ${Svg}`)
  104. // if (useAntdIcon) {
  105. // const AntdIcon = tryLoadAntdIcon();
  106. // if (AntdIcon) {
  107. // return (
  108. // <AntdIcon
  109. // component={Svg}
  110. // className={className}
  111. // style={{
  112. // width: widthPx ?? undefined,
  113. // height: widthPx ?? undefined,
  114. // ...style,
  115. // }}
  116. // />
  117. // );
  118. // }
  119. // }
  120. // // 没有 antd 或 useAntdIcon=false,则直接渲染 svg
  121. // return (
  122. // <Svg
  123. // width={widthPx ?? undefined}
  124. // height={widthPx ?? undefined}
  125. // role={alt ? 'img' : 'presentation'}
  126. // aria-label={alt}
  127. // className={className}
  128. // style={style}
  129. // />
  130. // );
  131. // }
  132. if (resolved.kind === 'svg') {
  133. const Svg = resolved.Component;
  134. //运行时保护 — 若 resolved.Component 非函数/非组件(意外情况),尝试降级为 <img />
  135. if (Svg == null || typeof Svg !== 'function') {
  136. // 尝试从 meta.source 拿到可用的 URL(可能是 svg 被打包为 URL 的情况)
  137. const maybeSrc = (resolved.meta && (resolved.meta.source as unknown)) ?? null;
  138. if (typeof maybeSrc === 'string') {
  139. return (
  140. <img
  141. src={maybeSrc}
  142. alt={alt ?? name}
  143. width={widthPx ?? undefined}
  144. height={widthPx ?? undefined}
  145. className={className}
  146. style={style}
  147. {...imgProps}
  148. />
  149. );
  150. }
  151. // 无法降级则安全返回 null
  152. return null;
  153. }
  154. if (useAntdIcon) {
  155. const AntdIcon = tryLoadAntdIcon();
  156. if (AntdIcon) {
  157. return (
  158. <AntdIcon
  159. component={Svg}
  160. className={className}
  161. style={{
  162. width: widthPx ?? undefined,
  163. height: widthPx ?? undefined,
  164. ...style,
  165. }}
  166. />
  167. );
  168. }
  169. }
  170. // 直接渲染 svg 组件
  171. return (
  172. <Svg
  173. width={widthPx ?? undefined}
  174. height={widthPx ?? undefined}
  175. role={alt ? 'img' : 'presentation'}
  176. aria-label={alt}
  177. className={className}
  178. style={style}
  179. />
  180. );
  181. }
  182. // raster 图片
  183. return (
  184. <img
  185. src={resolved.src}
  186. srcSet={resolved.srcSet}
  187. alt={alt ?? name}
  188. width={widthPx ?? undefined}
  189. height={widthPx ?? undefined}
  190. className={className}
  191. style={style}
  192. {...imgProps}
  193. />
  194. );
  195. };
  196. export default Icon;