generate-global-utils.cjs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. #!/usr/bin/env node
  2. /**
  3. * 全局单例工具生成器
  4. *
  5. * 功能:生成 antd-global.ts 文件和更新后的 AppProvider
  6. * 使用方法:node generate-global-utils.js [输出目录]
  7. */
  8. const fs = require("fs");
  9. const path = require("path");
  10. // antd-global.ts 模板
  11. const antdGlobalTemplate = `import { message, notification, Modal } from 'antd';
  12. import type { MessageInstance } from 'antd/es/message/interface';
  13. import type { NotificationInstance } from 'antd/es/notification/interface';
  14. import type { HookAPI } from 'antd/es/modal/useModal';
  15. /**
  16. * 全局 antd 实例管理
  17. *
  18. * 用途:为无法使用 hooks 的场景(工具函数、Redux Saga 等)提供全局访问
  19. *
  20. * ⚠️ 注意:这是过渡方案!
  21. * 推荐在 React 组件中使用 App.useApp() 代替
  22. *
  23. * 使用方法:
  24. * 1. 在 AppProvider 中初始化实例(见下方示例)
  25. * 2. 在业务代码中导入使用:
  26. * import { globalMessage, globalNotification, globalModal } from '@/utils/antd-global';
  27. */
  28. let messageInstance: MessageInstance | null = null;
  29. let notificationInstance: NotificationInstance | null = null;
  30. let modalInstance: HookAPI | null = null;
  31. /**
  32. * 设置 message 实例
  33. * 在 AppProvider 中调用
  34. */
  35. export const setMessageInstance = (instance: MessageInstance) => {
  36. messageInstance = instance;
  37. console.log('✅ Global message instance initialized');
  38. };
  39. /**
  40. * 设置 notification 实例
  41. * 在 AppProvider 中调用
  42. */
  43. export const setNotificationInstance = (instance: NotificationInstance) => {
  44. notificationInstance = instance;
  45. console.log('✅ Global notification instance initialized');
  46. };
  47. /**
  48. * 设置 modal 实例
  49. * 在 AppProvider 中调用
  50. */
  51. export const setModalInstance = (instance: HookAPI) => {
  52. modalInstance = instance;
  53. console.log('✅ Global modal instance initialized');
  54. };
  55. /**
  56. * 检查实例是否已初始化
  57. */
  58. const checkInstance = (name: string, instance: any) => {
  59. if (!instance) {
  60. console.error(\`❌ \${name} instance not initialized. Did you forget to wrap <App> component?\`);
  61. return false;
  62. }
  63. return true;
  64. };
  65. /**
  66. * 全局 message API
  67. *
  68. * 使用示例:
  69. * import { globalMessage } from '@/utils/antd-global';
  70. * globalMessage.success('操作成功');
  71. */
  72. export const globalMessage = {
  73. success: (content: string, duration?: number, onClose?: () => void) => {
  74. if (!checkInstance('Message', messageInstance)) return;
  75. return messageInstance!.success(content, duration, onClose);
  76. },
  77. error: (content: string, duration?: number, onClose?: () => void) => {
  78. if (!checkInstance('Message', messageInstance)) return;
  79. return messageInstance!.error(content, duration, onClose);
  80. },
  81. warning: (content: string, duration?: number, onClose?: () => void) => {
  82. if (!checkInstance('Message', messageInstance)) return;
  83. return messageInstance!.warning(content, duration, onClose);
  84. },
  85. info: (content: string, duration?: number, onClose?: () => void) => {
  86. if (!checkInstance('Message', messageInstance)) return;
  87. return messageInstance!.info(content, duration, onClose);
  88. },
  89. loading: (content: string, duration?: number, onClose?: () => void) => {
  90. if (!checkInstance('Message', messageInstance)) return;
  91. return messageInstance!.loading(content, duration, onClose);
  92. },
  93. destroy: () => {
  94. if (!checkInstance('Message', messageInstance)) return;
  95. return messageInstance!.destroy();
  96. },
  97. };
  98. /**
  99. * 全局 notification API
  100. *
  101. * 使用示例:
  102. * import { globalNotification } from '@/utils/antd-global';
  103. * globalNotification.info({
  104. * message: '通知标题',
  105. * description: '通知内容',
  106. * });
  107. */
  108. export const globalNotification = {
  109. success: (config: any) => {
  110. if (!checkInstance('Notification', notificationInstance)) return;
  111. return notificationInstance!.success(config);
  112. },
  113. error: (config: any) => {
  114. if (!checkInstance('Notification', notificationInstance)) return;
  115. return notificationInstance!.error(config);
  116. },
  117. warning: (config: any) => {
  118. if (!checkInstance('Notification', notificationInstance)) return;
  119. return notificationInstance!.warning(config);
  120. },
  121. info: (config: any) => {
  122. if (!checkInstance('Notification', notificationInstance)) return;
  123. return notificationInstance!.info(config);
  124. },
  125. open: (config: any) => {
  126. if (!checkInstance('Notification', notificationInstance)) return;
  127. return notificationInstance!.open(config);
  128. },
  129. destroy: (key?: string) => {
  130. if (!checkInstance('Notification', notificationInstance)) return;
  131. return notificationInstance!.destroy(key);
  132. },
  133. };
  134. /**
  135. * 全局 modal API
  136. *
  137. * 使用示例:
  138. * import { globalModal } from '@/utils/antd-global';
  139. * globalModal.confirm({
  140. * title: '确认删除?',
  141. * content: '删除后无法恢复',
  142. * onOk: () => { ... },
  143. * });
  144. */
  145. export const globalModal = {
  146. confirm: (config: any) => {
  147. if (!checkInstance('Modal', modalInstance)) return;
  148. return modalInstance!.confirm(config);
  149. },
  150. info: (config: any) => {
  151. if (!checkInstance('Modal', modalInstance)) return;
  152. return modalInstance!.info(config);
  153. },
  154. success: (config: any) => {
  155. if (!checkInstance('Modal', modalInstance)) return;
  156. return modalInstance!.success(config);
  157. },
  158. error: (config: any) => {
  159. if (!checkInstance('Modal', modalInstance)) return;
  160. return modalInstance!.error(config);
  161. },
  162. warning: (config: any) => {
  163. if (!checkInstance('Modal', modalInstance)) return;
  164. return modalInstance!.warning(config);
  165. },
  166. };
  167. `;
  168. // 更新后的 AppProvider 模板(带实例初始化)
  169. const appProviderWithGlobalTemplate = `import React, { useState, useEffect } from 'react';
  170. import { ConfigProvider, App as AntdApp, theme } from 'antd';
  171. import zhCN from 'antd/locale/zh_CN';
  172. import {
  173. setMessageInstance,
  174. setNotificationInstance,
  175. setModalInstance
  176. } from '../utils/antd-global';
  177. type ThemeMode = 'light' | 'dark';
  178. /**
  179. * 初始化全局 antd 实例
  180. * 为工具函数、Redux Saga 等无法使用 hooks 的场景提供支持
  181. */
  182. const InitGlobalInstances: React.FC = () => {
  183. const { message, notification, modal } = AntdApp.useApp();
  184. useEffect(() => {
  185. setMessageInstance(message);
  186. setNotificationInstance(notification);
  187. setModalInstance(modal);
  188. }, [message, notification, modal]);
  189. return null;
  190. };
  191. /**
  192. * App Provider 组件
  193. *
  194. * v4 → v6 主题系统迁移:
  195. * - ✅ 使用 ConfigProvider 管理全局配置
  196. * - ✅ 使用 theme.algorithm 切换主题
  197. * - ✅ 使用 Design Token 自定义主题
  198. * - ✅ 使用 App 组件提供全局 message/notification/modal
  199. * - ✅ 初始化全局实例,支持工具函数调用
  200. */
  201. const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  202. const [themeMode, setThemeMode] = useState<ThemeMode>('light');
  203. // 从 localStorage 读取保存的主题
  204. useEffect(() => {
  205. const savedTheme = localStorage.getItem('theme') as ThemeMode;
  206. if (savedTheme) {
  207. setThemeMode(savedTheme);
  208. }
  209. }, []);
  210. // 保存主题到 localStorage
  211. const handleThemeChange = (mode: ThemeMode) => {
  212. setThemeMode(mode);
  213. localStorage.setItem('theme', mode);
  214. };
  215. return (
  216. <ConfigProvider
  217. locale={zhCN}
  218. theme={{
  219. // 主题算法:暗黑或明亮
  220. algorithm: themeMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
  221. // 全局 Design Token
  222. token: {
  223. colorPrimary: '#1890ff',
  224. borderRadius: 6,
  225. // TODO: 添加更多自定义 token
  226. },
  227. // 组件级别的主题定制
  228. components: {
  229. Button: {
  230. controlHeight: 32,
  231. },
  232. Card: {
  233. borderRadiusLG: 8,
  234. },
  235. // TODO: 添加更多组件定制
  236. },
  237. }}
  238. >
  239. <AntdApp>
  240. {/* 初始化全局实例 - 重要! */}
  241. <InitGlobalInstances />
  242. {/* 你可以在这里添加 ThemeSwitch 组件 */}
  243. {/* <ThemeSwitch onChange={handleThemeChange} /> */}
  244. {children}
  245. </AntdApp>
  246. </ConfigProvider>
  247. );
  248. };
  249. export default AppProvider;
  250. `;
  251. // 使用示例文档
  252. const usageExample = `# 全局单例使用示例
  253. ## 📁 文件结构
  254. \`\`\`
  255. src/
  256. ├── utils/
  257. │ └── antd-global.ts ← 全局单例工具
  258. ├── theme/
  259. │ └── AppProvider.tsx ← 更新后的 AppProvider
  260. └── main.tsx ← 根组件
  261. \`\`\`
  262. ## 🚀 快速开始
  263. ### 1. 在根组件中使用 AppProvider
  264. \`\`\`typescript
  265. // src/main.tsx
  266. import React from 'react';
  267. import ReactDOM from 'react-dom/client';
  268. import { Provider } from 'react-redux';
  269. import store from './store';
  270. import AppProvider from './theme/AppProvider';
  271. import Router from './Router';
  272. ReactDOM.createRoot(document.getElementById('root')!).render(
  273. <React.StrictMode>
  274. <Provider store={store}>
  275. <AppProvider>
  276. <Router />
  277. </AppProvider>
  278. </Provider>
  279. </React.StrictMode>
  280. );
  281. \`\`\`
  282. ### 2. 在业务代码中使用
  283. \`\`\`typescript
  284. // 任意文件中
  285. import { globalMessage, globalNotification, globalModal } from '@/utils/antd-global';
  286. // 使用
  287. globalMessage.success('操作成功');
  288. globalNotification.info({ message: '通知', description: '内容' });
  289. globalModal.confirm({ title: '确认?', onOk: () => {} });
  290. \`\`\`
  291. ## 📝 API 参考
  292. ### globalMessage
  293. - globalMessage.success(content, duration?, onClose?)
  294. - globalMessage.error(content, duration?, onClose?)
  295. - globalMessage.warning(content, duration?, onClose?)
  296. - globalMessage.info(content, duration?, onClose?)
  297. - globalMessage.loading(content, duration?, onClose?)
  298. ### globalNotification
  299. - globalNotification.success(config)
  300. - globalNotification.error(config)
  301. - globalNotification.warning(config)
  302. - globalNotification.info(config)
  303. - globalNotification.open(config)
  304. ### globalModal
  305. - globalModal.confirm(config)
  306. - globalModal.info(config)
  307. - globalModal.success(config)
  308. - globalModal.error(config)
  309. - globalModal.warning(config)
  310. `;
  311. // 主函数
  312. function main() {
  313. const args = process.argv.slice(2);
  314. const outputDir = args[0] || "./src/utils";
  315. console.log("\n🔧 生成全局单例工具文件...\n");
  316. // 创建输出目录
  317. if (!fs.existsSync(outputDir)) {
  318. fs.mkdirSync(outputDir, { recursive: true });
  319. console.log(`✅ 创建目录: ${outputDir}`);
  320. }
  321. // 生成 antd-global.ts
  322. const antdGlobalPath = path.join(outputDir, "antd-global.ts");
  323. if (fs.existsSync(antdGlobalPath)) {
  324. console.log(`⚠️ 文件已存在: ${antdGlobalPath} (跳过)`);
  325. } else {
  326. fs.writeFileSync(antdGlobalPath, antdGlobalTemplate);
  327. console.log(`✅ 已生成: ${antdGlobalPath}`);
  328. }
  329. // 生成更新后的 AppProvider
  330. const appProviderDir = path.join(outputDir, "../theme");
  331. if (!fs.existsSync(appProviderDir)) {
  332. fs.mkdirSync(appProviderDir, { recursive: true });
  333. }
  334. const appProviderPath = path.join(appProviderDir, "AppProvider.tsx");
  335. if (fs.existsSync(appProviderPath)) {
  336. console.log(`⚠️ 文件已存在: ${appProviderPath} (跳过)`);
  337. console.log(" 如需更新,请手动添加 InitGlobalInstances 组件");
  338. } else {
  339. fs.writeFileSync(appProviderPath, appProviderWithGlobalTemplate);
  340. console.log(`✅ 已生成: ${appProviderPath}`);
  341. }
  342. // 生成使用示例
  343. const examplePath = path.join(outputDir, "antd-global-usage.md");
  344. fs.writeFileSync(examplePath, usageExample);
  345. console.log(`✅ 已生成: ${examplePath}`);
  346. console.log("\n" + "=".repeat(80));
  347. console.log("✅ 全局单例工具生成完成!");
  348. console.log("=".repeat(80) + "\n");
  349. console.log("📝 下一步操作:\n");
  350. console.log("1. 在根组件中使用 AppProvider:");
  351. console.log(` import AppProvider from './theme/AppProvider';`);
  352. console.log(" <AppProvider><YourApp /></AppProvider>\n");
  353. console.log("2. 运行替换脚本,批量替换 import:");
  354. console.log(" node replace-static-import.js ./src --dry-run\n");
  355. console.log("3. 确认无误后执行替换:");
  356. console.log(" node replace-static-import.js ./src --backup\n");
  357. console.log("4. 测试所有静态方法是否正常工作\n");
  358. console.log("📖 详细说明请查看:");
  359. console.log(` - ${examplePath}`);
  360. console.log(" - static-methods-migration.md\n");
  361. }
  362. main();