Explorar el Código

create v4->v6

visuddhinanda hace 1 mes
padre
commit
7336016494
Se han modificado 100 ficheros con 6571 adiciones y 0 borrados
  1. 432 0
      dashboard-v6/migration-tools/README.md
  2. 602 0
      dashboard-v6/migration-tools/USAGE.md
  3. 693 0
      dashboard-v6/migration-tools/component-template.cjs
  4. 430 0
      dashboard-v6/migration-tools/generate-global-utils.cjs
  5. 305 0
      dashboard-v6/migration-tools/import-replace.cjs
  6. 603 0
      dashboard-v6/migration-tools/migration-checklist.md
  7. 372 0
      dashboard-v6/migration-tools/replace-static-import.cjs
  8. 331 0
      dashboard-v6/migration-tools/scan-api-diff.cjs
  9. 585 0
      dashboard-v6/migration-tools/static-methods-migration.md
  10. BIN
      dashboard-v6/public/favicon.ico
  11. BIN
      dashboard-v6/public/logo192.png
  12. BIN
      dashboard-v6/public/logo512.png
  13. 25 0
      dashboard-v6/public/manifest.json
  14. 3 0
      dashboard-v6/public/robots.txt
  15. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-Black.ttf
  16. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-BlackItalic.ttf
  17. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-Bold.ttf
  18. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-BoldItalic.ttf
  19. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-ExtraBold.ttf
  20. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-ExtraBoldItalic.ttf
  21. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-ExtraLight.ttf
  22. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-ExtraLightItalic.ttf
  23. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-Italic.ttf
  24. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-Light.ttf
  25. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-LightItalic.ttf
  26. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-Medium.ttf
  27. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-MediumItalic.ttf
  28. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-Regular.ttf
  29. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-SemiBold.ttf
  30. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-SemiBoldItalic.ttf
  31. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-Thin.ttf
  32. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSans-ThinItalic.ttf
  33. BIN
      dashboard-v6/src/assets/font/NotoSans/NotoSansTaiTham-Regular.ttf
  34. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Black.ttf
  35. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-BlackItalic.ttf
  36. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Bold.ttf
  37. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-BoldItalic.ttf
  38. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ExtraBold.ttf
  39. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ExtraBoldItalic.ttf
  40. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ExtraLight.ttf
  41. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ExtraLightItalic.ttf
  42. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Italic.ttf
  43. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Light.ttf
  44. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-LightItalic.ttf
  45. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Medium.ttf
  46. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-MediumItalic.ttf
  47. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Regular.ttf
  48. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-SemiBold.ttf
  49. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-SemiBoldItalic.ttf
  50. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Thin.ttf
  51. BIN
      dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ThinItalic.ttf
  52. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Black.ttf
  53. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Bold.ttf
  54. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-ExtraBold.ttf
  55. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-ExtraLight.ttf
  56. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Light.ttf
  57. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Medium.ttf
  58. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Regular.ttf
  59. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-SemiBold.ttf
  60. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Thin.ttf
  61. 93 0
      dashboard-v6/src/assets/font/Noto_Sans_Myanmar/OFL.txt
  62. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-Bold.ttf
  63. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-Medium.ttf
  64. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-Regular.ttf
  65. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-SemiBold.ttf
  66. BIN
      dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-VariableFont_wght.ttf
  67. 93 0
      dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/OFL.txt
  68. 66 0
      dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/README.txt
  69. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Black.ttf
  70. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Bold.ttf
  71. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-ExtraBold.ttf
  72. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-ExtraLight.ttf
  73. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Light.ttf
  74. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Medium.ttf
  75. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Regular.ttf
  76. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-SemiBold.ttf
  77. BIN
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Thin.ttf
  78. 93 0
      dashboard-v6/src/assets/font/Noto_Serif_Myanmar/OFL.txt
  79. 457 0
      dashboard-v6/src/assets/font/main.css
  80. BIN
      dashboard-v6/src/assets/font/taitham/tai-tham-kh-new-v3.ttf
  81. BIN
      dashboard-v6/src/assets/general/images/logo_mps.png
  82. 22 0
      dashboard-v6/src/assets/general/images/wikipali_login_page.svg
  83. 89 0
      dashboard-v6/src/assets/general/images/wikipali_logo.svg
  84. 310 0
      dashboard-v6/src/assets/icon/index.tsx
  85. 34 0
      dashboard-v6/src/assets/icon/wikipali stamp2.ai
  86. 62 0
      dashboard-v6/src/assets/library/images/books.svg
  87. BIN
      dashboard-v6/src/assets/library/images/download_bg.png
  88. 127 0
      dashboard-v6/src/assets/library/images/teachers.svg
  89. 51 0
      dashboard-v6/src/assets/library/images/wikipali_logo_library.svg
  90. BIN
      dashboard-v6/src/assets/nut/code.png
  91. 22 0
      dashboard-v6/src/assets/studio/images/wikipali_banner.svg
  92. 12 0
      dashboard-v6/src/components/README.md
  93. 59 0
      dashboard-v6/src/components/admin/HeadBar.tsx
  94. 94 0
      dashboard-v6/src/components/admin/LeftSider.tsx
  95. 64 0
      dashboard-v6/src/components/admin/api/ApiDelayHour.tsx
  96. 83 0
      dashboard-v6/src/components/admin/api/ApiGauge.tsx
  97. 42 0
      dashboard-v6/src/components/admin/relation/CaseSelect.tsx
  98. 100 0
      dashboard-v6/src/components/admin/relation/DataImport.tsx
  99. 89 0
      dashboard-v6/src/components/admin/relation/GrammarSelect.tsx
  100. 128 0
      dashboard-v6/src/components/admin/relation/NissayaEndingEdit.tsx

+ 432 - 0
dashboard-v6/migration-tools/README.md

@@ -0,0 +1,432 @@
+# 🚀 Ant Design v4 → v6 迁移工具包
+
+完整的 Ant Design v4.24 到 v6 升级迁移工具包,包含自动化脚本和详细指南。
+
+## 📦 工具包内容
+
+| 文件 | 类型 | 说明 |
+|------|------|------|
+| **migration-checklist.md** | 📋 文档 | 详细的迁移检查清单(主文档) |
+| **static-methods-migration.md** | 🚨 文档 | 静态方法迁移专项指南(重要!) |
+| **scan-api-diff.js** | 🔍 脚本 | API 差异扫描器 |
+| **import-replace.js** | 🔧 脚本 | 批量替换工具 |
+| **replace-static-import.js** | 🔧 脚本 | 静态方法 import 替换工具 |
+| **generate-global-utils.js** | 📝 脚本 | 全局单例工具生成器 |
+| **component-template.js** | 📝 脚本 | 组件模板生成器 |
+| **USAGE.md** | 📖 文档 | 详细使用指南 |
+| **README.md** | 📄 文档 | 本文件 |
+
+---
+
+## 🚨 重要提醒:静态方法迁移
+
+**你的项目大量使用了 `message.*`、`notification.*`、`Modal.*` 静态方法!**
+
+这是 v4 → v6 迁移中**最重要**的变更之一。在 v6 中,这些方法必须在特定上下文中使用,否则会失效。
+
+### 快速解决方案
+
+我为你准备了**全局单例**迁移方案,只需 3 步:
+
+```bash
+# 1. 生成全局单例工具文件
+node generate-global-utils.js ./src/utils
+
+# 2. 批量替换 import 语句(预览)
+node replace-static-import.js ./src --dry-run
+
+# 3. 执行替换(备份)
+node replace-static-import.js ./src --backup
+```
+
+✅ **优势**:最小改动,业务代码几乎不需要修改
+📖 **详细说明**:查看 [static-methods-migration.md](./static-methods-migration.md)
+
+---
+
+## ⚡ 快速开始
+
+### 前置要求
+
+- Node.js 14+
+- 已创建 Ant Design v6 新项目
+- 保留 v4 旧项目代码(不要删除)
+
+### 1️⃣ 扫描旧项目,了解需要修改的内容
+
+```bash
+node scan-api-diff.js /path/to/old-project/src
+```
+
+### 2️⃣ 生成主题系统文件(重要!)
+
+```bash
+node component-template.js --theme ./src/theme
+```
+
+### 3️⃣ 开始迁移组件
+
+```bash
+# 选择一个简单模块开始(推荐 general)
+cp -r /path/to/old-project/src/components/general ./src/components/
+
+# 批量替换简单的 API
+node import-replace.js ./src/components/general --backup
+```
+
+### 4️⃣ 测试并重复
+
+逐个模块迁移并测试,确保每个模块正常工作。
+
+---
+
+## 📚 核心功能
+
+### 🔍 API 差异扫描器
+
+扫描代码中使用的 v4 API,标记所有需要修改的地方。
+
+```bash
+# 扫描并生成报告
+node scan-api-diff.js /path/to/old-project/src
+
+# 导出 JSON 格式报告
+node scan-api-diff.js /path/to/old-project/src --json=report.json
+```
+
+**报告示例:**
+```
+📊 Ant Design v4 → v6 API 差异扫描报告
+===============================================================================
+📁 扫描统计:
+   总文件数: 581
+   有问题的文件: 127
+
+🚨 问题统计:
+   🔴 Critical: 5 个    (必须立即修改)
+   🟠 High: 43 个       (高优先级)
+   🟡 Medium: 89 个     (中优先级)
+   🟢 Low: 12 个        (低优先级)
+```
+
+### 🔧 批量替换工具
+
+自动替换简单的 API 变更(visible → open, moment → dayjs 等)。
+
+```bash
+# 预览修改(强烈推荐先预览)
+node import-replace.js ./src --dry-run
+
+# 执行替换并备份
+node import-replace.js ./src --backup
+```
+
+**自动处理的变更:**
+- ✅ `visible` → `open`
+- ✅ `onVisibleChange` → `onOpenChange`
+- ✅ `moment` → `dayjs`
+- ✅ `overlay` → `menu` (Dropdown)
+- ✅ `BackTop` → `FloatButton.BackTop`
+
+### 📝 组件模板生成器
+
+生成符合 v6 最佳实践的组件模板。
+
+```bash
+# 生成基础组件
+node component-template.js basic UserCard ./src/components
+
+# 生成表单组件
+node component-template.js form UserForm ./src/components
+
+# 生成 ProTable 组件
+node component-template.js proTable UserList ./src/components
+
+# 生成完整主题系统(重要!)
+node component-template.js --theme ./src/theme
+```
+
+**可用模板:**
+- `basic` - 基础组件
+- `form` - 表单组件(含验证)
+- `modal` - Modal 弹窗组件
+- `table` - Table 列表组件
+- `proTable` - ProTable 高级表格
+
+---
+
+## 🎯 推荐工作流程(针对你的项目)
+
+### ⚡ 推荐方案:先处理静态方法
+
+因为你的项目大量使用静态方法,建议优先处理:
+
+```bash
+# === Phase 0: 处理静态方法(最重要!)===
+
+# 1. 生成全局单例工具
+node generate-global-utils.js ./src/utils
+
+# 2. 批量替换 import(预览)
+node replace-static-import.js ./src --dry-run
+
+# 3. 确认无误后执行替换
+node replace-static-import.js ./src --backup
+
+# 4. 在 src/index.tsx 中使用 AppProvider
+# 参考 static-methods-migration.md
+
+
+# === Phase 1: 评估和准备 ===
+
+# 5. 扫描旧项目代码
+node scan-api-diff.js /path/to/old-project/src --json=report.json
+
+# 6. 生成主题系统
+node component-template.js --theme ./src/theme
+
+
+# === Phase 2: 逐步迁移 ===
+
+# 7. 复制 general 组件
+cp -r /path/to/old-project/src/components/general ./src/components/
+
+# 8. 批量替换简单 API
+node import-replace.js ./src/components/general --backup
+
+# 9. 测试该模块功能
+
+# 10. 重复步骤 7-9,迁移其他模块
+
+
+# === Phase 3: 最终检查 ===
+
+# 11. 扫描新项目,确认无遗漏
+node scan-api-diff.js ./src
+```
+
+### 方案对比
+
+| 步骤 | 不处理静态方法 | 处理静态方法(推荐) |
+|------|---------------|---------------------|
+| 工作量 | 大(每个文件都要改) | 小(批量替换) |
+| 风险 | 高(容易遗漏) | 低(自动化) |
+| 耗时 | 20-30 小时 | 5-7 小时 |
+| 是否推荐 | ❌ | ✅ |
+
+---
+
+```bash
+# 1. 评估工作量
+node scan-api-diff.js /path/to/old-project/src --json=report.json
+
+# 2. 设置主题系统
+node component-template.js --theme ./src/theme
+
+# 3. 逐个模块迁移
+#    a. 复制模块代码
+cp -r /path/to/old-project/src/components/general ./src/components/
+
+#    b. 批量替换
+node import-replace.js ./src/components/general --backup
+
+#    c. 测试该模块
+
+# 4. 重复步骤 3,直到所有模块迁移完成
+
+# 5. 最终检查
+node scan-api-diff.js ./src
+```
+
+### 方案二:快速型(小型项目推荐)
+
+```bash
+# 1. 复制所有代码
+cp -r /path/to/old-project/src/* ./src/
+
+# 2. 批量替换
+node import-replace.js ./src --backup
+
+# 3. 设置主题
+node component-template.js --theme ./src/theme
+
+# 4. 检查剩余问题
+node scan-api-diff.js ./src
+
+# 5. 手动修复剩余问题
+```
+
+---
+
+## 📋 详细文档
+
+- **[migration-checklist.md](./migration-checklist.md)** - 📋 完整的分阶段迁移清单
+  - Phase 1: 基础设施层
+  - Phase 2: 通用组件层 ⭐
+  - Phase 3: 业务组件层
+  - Phase 4: 样式和主题系统
+  - Phase 5: 测试和优化
+
+- **[USAGE.md](./USAGE.md)** - 📖 详细使用指南
+  - 每个工具的详细说明
+  - 实战示例
+  - 常见问题 FAQ
+  - 最佳实践建议
+
+---
+
+## ⚠️ 重要提醒
+
+### 关于项目特性
+
+你的项目大量使用以下组件,需要特别注意:
+
+- ✅ **ProTable** - 基本兼容,但部分 API 有调整
+- ✅ **ProList** - 基本兼容
+- ✅ **ProForm** - 基本兼容,建议查看最新文档
+- ✅ **Button** - 完全兼容
+- ✅ **Tag** - 基本兼容
+- ✅ **Card** - 基本兼容,新增 `classNames` API
+- ✅ **Popover** - `visible` → `open`
+
+### 关于主题系统
+
+你的项目原本使用 CSS 方式管理主题(`theme/antd.dark.css`):
+
+- ❌ **v6 已废弃 CSS 方式**
+- ✅ **改用 ConfigProvider + Design Token**
+- ✅ 使用 `component-template.js --theme` 生成新主题系统
+- ✅ 新系统支持动态切换,无需刷新页面
+
+### 迁移优先级
+
+建议按以下顺序迁移:
+
+1. **Phase 1**: 基础设施(类型、Redux、路由)
+2. **Phase 2**: 通用组件(`components/general/*`)⭐ 优先
+3. **Phase 3**: 核心业务模块(`article`, `channel`, `corpus`)
+4. **Phase 4**: 其他业务模块
+5. **Phase 5**: 样式优化和测试
+
+---
+
+## 🔥 常见问题
+
+### Q: 替换工具安全吗?
+
+A: 请务必:
+- ✅ 先使用 `--dry-run` 预览
+- ✅ 使用 `--backup` 备份
+- ✅ 使用 Git 版本控制
+
+### Q: 扫描器报告太多问题怎么办?
+
+A: 分优先级处理:
+1. 先修复 🔴 Critical 和 🟠 High
+2. 逐步处理 🟡 Medium
+3. 最后优化 🟢 Low
+
+### Q: 某些组件无法自动迁移怎么办?
+
+A: 
+- 参考 `migration-checklist.md` 中的 API 对照表
+- 使用 `component-template.js` 生成参考模板
+- 查阅 Ant Design 官方文档
+
+### Q: 主题系统如何集成?
+
+A:
+```bash
+# 1. 生成主题文件
+node component-template.js --theme ./src/theme
+
+# 2. 在 src/index.tsx 中引入
+import AppProvider from './theme/AppProvider';
+
+ReactDOM.render(
+  <AppProvider>
+    <App />
+  </AppProvider>,
+  document.getElementById('root')
+);
+```
+
+---
+
+## 📊 项目统计
+
+基于你的项目结构分析:
+
+```
+总文件数: 581
+├── 组件数: 500+
+├── API 文件: 21
+├── Redux reducers: 29
+├── 页面: 8
+└── 国际化: 3 种语言 (en-US, zh-Hans, zh-Hant)
+```
+
+**预估工作量:**
+- 🔴 Critical 问题: 预计 1-2 天
+- 🟠 High 问题: 预计 3-5 天
+- 🟡 Medium 问题: 预计 5-7 天
+- 🟢 Low 问题: 预计 1-2 天
+- **总计: 约 10-16 天**(取决于项目复杂度和团队规模)
+
+---
+
+## 🛠️ 技术支持
+
+### 遇到问题?
+
+1. 查看 [USAGE.md](./USAGE.md) 详细文档
+2. 检查 [migration-checklist.md](./migration-checklist.md) 相关章节
+3. 参考 [Ant Design 官方文档](https://ant.design)
+
+### 贡献改进
+
+如果你发现工具的 bug 或有改进建议:
+- 修改相应的脚本文件
+- 更新文档
+- 分享给团队
+
+---
+
+## 📝 版本历史
+
+- **v1.0.0** (2026-02-14)
+  - 初始版本
+  - 包含 3 个核心工具
+  - 完整的迁移文档
+
+---
+
+## 📄 许可证
+
+这些工具脚本为你的项目专用,可自由修改和使用。
+
+---
+
+## 🎉 开始迁移
+
+现在你已经准备好了!
+
+1. 阅读 `migration-checklist.md` 了解整体流程
+2. 运行 `scan-api-diff.js` 评估工作量
+3. 使用 `component-template.js --theme` 设置主题
+4. 开始逐个模块迁移
+
+**祝你迁移顺利!🚀**
+
+---
+
+## 💡 提示
+
+- 💾 记得经常提交代码到 Git
+- 📝 在 `migration-checklist.md` 中记录进度
+- 🧪 每迁移一个模块就测试一次
+- 👥 必要时寻求团队协作
+
+---
+
+*最后更新: 2026-02-14*

+ 602 - 0
dashboard-v6/migration-tools/USAGE.md

@@ -0,0 +1,602 @@
+# 🛠️ Ant Design v4 → v6 迁移工具使用指南
+
+本工具包包含 3 个辅助脚本和 1 个详细检查清单,帮助你高效完成迁移工作。
+
+---
+
+## 📦 工具包内容
+
+```
+migration-tools/
+├── migration-checklist.md    # 📋 迁移检查清单(主文档)
+├── scan-api-diff.js          # 🔍 API 差异扫描器
+├── import-replace.js         # 🔧 批量替换工具
+├── component-template.js     # 📝 组件模板生成器
+└── USAGE.md                  # 📖 本使用指南
+```
+
+---
+
+## 🚀 快速开始
+
+### Step 1: 扫描旧项目代码
+
+先了解你的代码中有哪些需要修改的地方:
+
+```bash
+# 扫描旧项目代码,生成报告
+node scan-api-diff.js /path/to/old-project/src
+
+# 导出详细的 JSON 报告(可选)
+node scan-api-diff.js /path/to/old-project/src --json=scan-report.json
+```
+
+**输出示例:**
+```
+📊 Ant Design v4 → v6 API 差异扫描报告
+===============================================================================
+
+📁 扫描统计:
+   总文件数: 581
+   已扫描: 581
+   有问题的文件: 127
+
+🚨 问题统计:
+   🔴 Critical: 5 个
+   🟠 High: 43 个
+   🟡 Medium: 89 个
+   🟢 Low: 12 个
+
+📋 详细问题列表:
+   ...
+```
+
+### Step 2: 批量替换简单的 API
+
+对于可以自动替换的 API(如 visible → open),使用批量替换工具:
+
+```bash
+# 预览将要修改的内容(强烈推荐先预览)
+node import-replace.js /path/to/new-project/src --dry-run
+
+# 确认无误后,执行替换并备份
+node import-replace.js /path/to/new-project/src --backup
+
+# 如果你很确定,可以直接替换(不推荐)
+node import-replace.js /path/to/new-project/src
+```
+
+**该工具会自动处理:**
+- ✅ `visible` → `open`
+- ✅ `onVisibleChange` → `onOpenChange`
+- ✅ `moment` → `dayjs`
+- ✅ `overlay` → `menu` (Dropdown)
+- ✅ `BackTop` → `FloatButton.BackTop`
+
+### Step 3: 生成新组件模板
+
+在新项目中创建符合 v6 规范的组件:
+
+```bash
+# 生成基础组件
+node component-template.js basic UserCard ./src/components/user
+
+# 生成表单组件
+node component-template.js form UserForm ./src/components/user
+
+# 生成 ProTable 组件
+node component-template.js proTable UserList ./src/components/user
+
+# 生成完整的主题系统(重要!)
+node component-template.js --theme ./src/theme
+```
+
+### Step 4: 按照检查清单逐步迁移
+
+打开 `migration-checklist.md`,按照清单逐个完成迁移任务。
+
+---
+
+## 🔍 工具 1: scan-api-diff.js - API 差异扫描器
+
+### 功能
+扫描代码中使用的 antd v4 API,标记需要修改的地方。
+
+### 使用场景
+- ✅ 开始迁移前,了解代码中有多少需要修改的地方
+- ✅ 评估迁移工作量
+- ✅ 识别高风险组件
+- ✅ 迁移过程中,验证是否有遗漏
+
+### 命令详解
+
+```bash
+# 基础用法
+node scan-api-diff.js <目标目录>
+
+# 导出 JSON 报告
+node scan-api-diff.js <目标目录> --json=<输出文件.json>
+```
+
+### 实战示例
+
+```bash
+# 示例 1: 扫描整个 src 目录
+node scan-api-diff.js ../old-project/src
+
+# 示例 2: 只扫描特定模块
+node scan-api-diff.js ../old-project/src/components/general
+
+# 示例 3: 导出报告后用其他工具分析
+node scan-api-diff.js ../old-project/src --json=report.json
+cat report.json | jq '.issues | length'  # 统计问题数量
+```
+
+### 报告解读
+
+报告会按严重程度分类问题:
+
+- 🔴 **Critical** - 必须立即修改,否则代码无法运行
+  - 例如:使用了已废弃的 `getFieldDecorator`
+  
+- 🟠 **High** - 高优先级,会导致功能异常
+  - 例如:`visible` 属性不再生效
+  
+- 🟡 **Medium** - 中优先级,可能导致警告或不推荐的用法
+  - 例如:直接调用 `message.success()` 需要包裹 `<App>`
+  
+- 🟢 **Low** - 低优先级,建议优化
+  - 例如:`BackTop` 组件有更好的替代方案
+
+### 输出文件
+
+如果使用 `--json` 参数,会生成 JSON 格式的详细报告:
+
+```json
+{
+  "timestamp": "2026-02-14T10:30:00.000Z",
+  "stats": {
+    "totalFiles": 581,
+    "scannedFiles": 581,
+    "filesWithIssues": 127,
+    "issues": {
+      "critical": 5,
+      "high": 43,
+      "medium": 89,
+      "low": 12
+    }
+  },
+  "issues": {
+    "src/components/general/ThemeSelect.tsx": [
+      {
+        "line": 42,
+        "column": 8,
+        "code": "visible={true}",
+        "suggestion": "❌ 使用了 visible 属性",
+        "fix": "将 visible 改为 open",
+        "severity": "high",
+        "component": "Modal/Drawer/Popover/Tooltip/Dropdown"
+      }
+    ]
+  }
+}
+```
+
+---
+
+## 🔧 工具 2: import-replace.js - 批量替换工具
+
+### 功能
+自动替换代码中的 antd v4 API 调用。
+
+### 使用场景
+- ✅ 批量替换简单的 API 变更(如 visible → open)
+- ✅ 替换 moment.js 为 dayjs
+- ✅ 更新其他机械性的代码修改
+
+### ⚠️ 重要警告
+
+**该工具会直接修改你的代码文件!**
+
+请务必:
+1. 先使用 `--dry-run` 预览
+2. 使用 `--backup` 备份原文件
+3. 使用 Git 等版本控制系统
+4. 在测试分支上操作
+
+### 命令详解
+
+```bash
+# 预览模式(强烈推荐先运行)
+node import-replace.js <目标目录> --dry-run
+
+# 执行替换并备份
+node import-replace.js <目标目录> --backup
+
+# 直接替换(谨慎使用)
+node import-replace.js <目标目录>
+```
+
+### 实战示例
+
+```bash
+# 示例 1: 先预览整个项目的修改
+node import-replace.js ./src --dry-run
+
+# 示例 2: 确认无误后执行替换,并备份原文件
+node import-replace.js ./src --backup
+
+# 示例 3: 只处理特定目录
+node import-replace.js ./src/components/general --backup
+
+# 示例 4: 查看备份文件
+find ./src -name "*.bak" | head -5
+
+# 示例 5: 如果替换有问题,恢复备份
+find ./src -name "*.bak" -exec bash -c 'mv "$0" "${0%.bak}"' {} \;
+```
+
+### 替换规则
+
+目前支持以下自动替换:
+
+| v4 API | v6 API | 说明 |
+|--------|--------|------|
+| `visible={...}` | `open={...}` | Modal/Drawer/Popover 等 |
+| `onVisibleChange={...}` | `onOpenChange={...}` | 同上 |
+| `import moment from 'moment'` | `import dayjs from 'dayjs'` | DatePicker 相关 |
+| `moment(...)` | `dayjs(...)` | 日期处理函数 |
+| `overlay={...}` | `menu={...}` | Dropdown 组件 |
+| `<BackTop>` | `<FloatButton.BackTop>` | 返回顶部组件 |
+
+### 注意事项
+
+**⚠️ 以下情况需要手动处理:**
+
+1. **Dropdown 的 menu 属性**
+   - 自动替换后,`menu` 需要返回 `Menu` 组件
+   - 可能需要调整代码结构
+
+2. **message/notification 静态方法**
+   - 不会自动替换(因为需要包裹 `<App>` 组件)
+   - 建议迁移到 `App.useApp()` hook
+
+3. **Modal.confirm 等静态方法**
+   - 同上,需要使用 `Modal.useModal()` hook
+
+4. **PageHeader 组件**
+   - 已从 antd 移除
+   - 需要手动迁移到 `@ant-design/pro-components`
+
+---
+
+## 📝 工具 3: component-template.js - 组件模板生成器
+
+### 功能
+生成符合 v6 最佳实践的组件模板代码。
+
+### 使用场景
+- ✅ 创建新组件时,使用标准模板
+- ✅ 重写旧组件时,参考最佳实践
+- ✅ 生成主题系统文件
+
+### 可用模板类型
+
+| 类型 | 说明 | 适用场景 |
+|------|------|----------|
+| `basic` | 基础组件 | 简单的展示组件 |
+| `form` | 表单组件 | 包含表单验证、提交逻辑 |
+| `modal` | Modal 组件 | 弹窗组件 |
+| `table` | Table 组件 | 列表展示、分页 |
+| `proTable` | ProTable 组件 | 复杂的表格,带搜索、筛选 |
+
+### 命令详解
+
+```bash
+# 生成组件
+node component-template.js <类型> <组件名> [输出目录]
+
+# 生成主题系统文件(特殊命令)
+node component-template.js --theme [输出目录]
+
+# 查看帮助
+node component-template.js --help
+```
+
+### 实战示例
+
+```bash
+# 示例 1: 生成基础组件
+node component-template.js basic UserCard ./src/components/user
+# 输出: ./src/components/user/UserCard.tsx
+
+# 示例 2: 生成表单组件
+node component-template.js form UserForm ./src/components/user
+# 输出: ./src/components/user/UserForm.tsx
+
+# 示例 3: 生成 ProTable 组件
+node component-template.js proTable UserList ./src/components/user
+# 输出: ./src/components/user/UserList.tsx
+
+# 示例 4: 生成主题系统(重要!)
+node component-template.js --theme ./src/theme
+# 输出:
+#   ./src/theme/ThemeSwitch.tsx   (主题切换器)
+#   ./src/theme/AppProvider.tsx   (App 提供者)
+#   ./src/theme/tokens.ts         (Design Token 配置)
+```
+
+### 主题系统文件说明
+
+使用 `--theme` 命令会生成完整的主题系统:
+
+#### 1. ThemeSwitch.tsx - 主题切换器
+
+```tsx
+// 用法示例
+import ThemeSwitch from './theme/ThemeSwitch';
+
+<ThemeSwitch onChange={(mode) => {
+  console.log('当前主题:', mode); // 'light' | 'dark'
+}} />
+```
+
+功能:
+- ✅ 亮色/暗黑主题切换
+- ✅ 本地存储主题偏好
+- ✅ 带图标和文字提示
+
+#### 2. AppProvider.tsx - 全局配置提供者
+
+```tsx
+// 在根组件使用
+import AppProvider from './theme/AppProvider';
+
+function Root() {
+  return (
+    <AppProvider>
+      <YourApp />
+    </AppProvider>
+  );
+}
+```
+
+功能:
+- ✅ 提供 ConfigProvider 配置
+- ✅ 管理主题切换
+- ✅ 提供全局 message/modal/notification
+
+#### 3. tokens.ts - Design Token 配置
+
+```tsx
+// 自定义主题 Token
+export const lightTheme = {
+  token: {
+    colorPrimary: '#1890ff',
+    borderRadius: 6,
+    // ...
+  },
+  components: {
+    Button: {
+      controlHeight: 32,
+    },
+  },
+};
+```
+
+---
+
+## 📋 工具 4: migration-checklist.md - 迁移检查清单
+
+### 功能
+详细的分阶段迁移任务清单。
+
+### 使用方法
+
+1. 在你喜欢的 Markdown 编辑器中打开
+2. 按照 Phase 顺序完成任务
+3. 完成一项勾选一项 `[ ]` → `[x]`
+4. 记录遇到的问题和解决方案
+
+### 文档结构
+
+```
+📊 迁移进度总览
+└── 总体进度追踪
+
+🎯 Phase 1: 基础设施层
+└── 类型定义、Redux、路由
+
+🎯 Phase 2: 通用组件层 ⭐ 当前优先级
+└── general、auth、api
+
+🎯 Phase 3: 业务组件层
+└── 按模块逐个迁移
+
+🎯 Phase 4: 样式和主题系统 ⚠️ 重大变更
+└── CSS → Design Token
+
+🎯 Phase 5: 测试和优化
+└── 功能测试、性能优化
+
+📚 关键 API 变更速查表
+└── 常用组件对照表
+
+🔧 常见问题和解决方案
+└── 错误处理指南
+```
+
+---
+
+## 🎯 推荐工作流程
+
+### 方案 1: 稳妥型(推荐给大型项目)
+
+```bash
+# Step 1: 扫描旧项目,评估工作量
+node scan-api-diff.js ../old-project/src --json=scan-report.json
+
+# Step 2: 生成主题系统文件
+node component-template.js --theme ./src/theme
+
+# Step 3: 配置根组件使用 AppProvider
+# (手动修改 src/index.tsx)
+
+# Step 4: 选择一个简单模块开始迁移(如 general)
+# 方式 A: 手动迁移(更安全)
+cp -r ../old-project/src/components/general ./src/components/
+
+# 方式 B: 复制后使用替换工具
+node import-replace.js ./src/components/general --backup
+
+# Step 5: 测试该模块是否正常工作
+
+# Step 6: 重复 Step 4-5,逐个模块迁移
+
+# Step 7: 最后扫描一次,确保没有遗漏
+node scan-api-diff.js ./src
+```
+
+### 方案 2: 快速型(推荐给小型项目)
+
+```bash
+# Step 1: 直接复制所有代码到新项目
+cp -r ../old-project/src/* ./src/
+
+# Step 2: 批量替换简单的 API
+node import-replace.js ./src --dry-run  # 先预览
+node import-replace.js ./src --backup   # 执行替换
+
+# Step 3: 生成主题系统
+node component-template.js --theme ./src/theme
+
+# Step 4: 扫描剩余问题
+node scan-api-diff.js ./src
+
+# Step 5: 根据报告手动修复剩余问题
+
+# Step 6: 测试所有功能
+```
+
+---
+
+## 🔥 常见问题 FAQ
+
+### Q1: 替换工具会破坏我的代码吗?
+
+**A:** 可能会。所以:
+- ✅ 务必先用 `--dry-run` 预览
+- ✅ 使用 `--backup` 备份
+- ✅ 使用 Git 等版本控制
+- ✅ 先在小范围测试
+
+### Q2: 扫描器报告太多问题怎么办?
+
+**A:** 分优先级处理:
+1. 先处理 🔴 Critical 和 🟠 High
+2. 🟡 Medium 可以逐步处理
+3. 🟢 Low 可以最后优化
+
+### Q3: 某些 API 无法自动替换怎么办?
+
+**A:** 手动处理:
+- 参考 `migration-checklist.md` 中的 API 对照表
+- 查看生成的组件模板作为参考
+- 查阅 Ant Design 官方文档
+
+### Q4: 主题系统应该如何集成?
+
+**A:** 三步走:
+```bash
+# 1. 生成主题文件
+node component-template.js --theme ./src/theme
+
+# 2. 在根组件引入
+import AppProvider from './theme/AppProvider';
+
+# 3. 包裹你的 App
+<AppProvider>
+  <App />
+</AppProvider>
+```
+
+### Q5: ProTable/ProForm 如何迁移?
+
+**A:** 
+- ProComponents 也有版本升级
+- 使用 `component-template.js proTable` 生成新模板
+- 对比新旧代码,逐步调整
+
+### Q6: 样式突然失效怎么办?
+
+**A:** 检查:
+- ✅ 是否有覆盖 antd 默认样式的 CSS
+- ✅ 类名是否发生变化
+- ✅ 是否需要改用 Design Token
+- ✅ 是否需要使用 `classNames` API
+
+### Q7: 扫描器漏报或误报怎么办?
+
+**A:** 
+- 扫描器基于正则匹配,可能有遗漏
+- 建议结合人工检查
+- 可以修改 `scan-api-diff.js` 添加新规则
+
+---
+
+## 📚 参考资源
+
+### 官方文档
+- [Ant Design v6 官方文档](https://ant.design)
+- [v4 → v5 迁移指南](https://ant.design/docs/react/migration-v5)
+- [v5 → v6 迁移指南](https://ant.design/docs/react/migration-v6)
+- [Design Token 文档](https://ant.design/docs/react/customize-theme)
+
+### 工具资源
+- [dayjs 官方文档](https://day.js.org/)
+- [ProComponents 文档](https://procomponents.ant.design/)
+
+---
+
+## 💡 最佳实践建议
+
+### 1. 分阶段迁移
+不要一次性迁移所有代码,按模块逐个迁移并测试。
+
+### 2. 保持两个版本
+在迁移期间,保持 v4 版本可运行,方便对比。
+
+### 3. 及时记录
+在 `migration-checklist.md` 中记录遇到的问题和解决方案。
+
+### 4. 代码审查
+迁移后的代码要经过审查,确保符合 v6 最佳实践。
+
+### 5. 性能监控
+迁移前后对比性能指标,确保没有性能退化。
+
+### 6. 增量发布
+如果是生产项目,建议采用灰度发布策略。
+
+---
+
+## 🎉 完成检查清单
+
+当你完成迁移后,检查以下项目:
+
+- [ ] 所有 TypeScript 编译错误已修复
+- [ ] 所有页面可以正常访问
+- [ ] 核心功能测试通过
+- [ ] 主题切换正常工作
+- [ ] 国际化正常工作
+- [ ] 移动端响应式正常
+- [ ] 性能没有明显下降
+- [ ] 控制台无报错和警告
+- [ ] 代码已提交到 Git
+- [ ] 团队成员已了解新特性
+
+---
+
+**祝你迁移顺利!🚀**
+
+如有问题,请参考 `migration-checklist.md` 或查阅官方文档。

+ 693 - 0
dashboard-v6/migration-tools/component-template.cjs

@@ -0,0 +1,693 @@
+#!/usr/bin/env node
+
+/**
+ * Ant Design v6 组件迁移模板生成器
+ * 
+ * 功能:生成符合 v6 最佳实践的组件模板
+ * 使用方法:node component-template.js <组件类型> <组件名>
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+// 组件模板
+const templates = {
+  // 基础功能组件
+  basic: (componentName) => `import React from 'react';
+import { Card, Button } from 'antd';
+
+interface ${componentName}Props {
+  // TODO: 添加你的 props 类型
+}
+
+/**
+ * ${componentName} 组件
+ * 
+ * v4 → v6 迁移说明:
+ * - 已更新到 v6 API
+ * - 使用新的类型定义
+ */
+const ${componentName}: React.FC<${componentName}Props> = (props) => {
+  return (
+    <Card title="${componentName}">
+      {/* TODO: 实现你的组件逻辑 */}
+    </Card>
+  );
+};
+
+export default ${componentName};
+`,
+
+  // 表单组件
+  form: (componentName) => `import React from 'react';
+import { Form, Input, Button, message, App } from 'antd';
+import type { FormProps } from 'antd';
+
+interface ${componentName}FormValues {
+  // TODO: 添加表单字段类型
+  name?: string;
+}
+
+interface ${componentName}Props {
+  onSubmit?: (values: ${componentName}FormValues) => void;
+}
+
+/**
+ * ${componentName} 表单组件
+ * 
+ * v4 → v6 迁移说明:
+ * - ✅ 使用 App.useApp() 获取 message 实例
+ * - ✅ Form API 已更新到 v6
+ * - ✅ 使用 TypeScript 类型定义
+ */
+const ${componentName}: React.FC<${componentName}Props> = ({ onSubmit }) => {
+  const [form] = Form.useForm<${componentName}FormValues>();
+  const { message } = App.useApp();
+
+  const handleFinish: FormProps<${componentName}FormValues>['onFinish'] = async (values) => {
+    try {
+      await onSubmit?.(values);
+      message.success('提交成功');
+      form.resetFields();
+    } catch (error) {
+      message.error('提交失败');
+      console.error(error);
+    }
+  };
+
+  return (
+    <Form
+      form={form}
+      layout="vertical"
+      onFinish={handleFinish}
+      autoComplete="off"
+    >
+      <Form.Item
+        label="名称"
+        name="name"
+        rules={[{ required: true, message: '请输入名称' }]}
+      >
+        <Input placeholder="请输入名称" />
+      </Form.Item>
+
+      {/* TODO: 添加更多表单项 */}
+
+      <Form.Item>
+        <Button type="primary" htmlType="submit">
+          提交
+        </Button>
+      </Form.Item>
+    </Form>
+  );
+};
+
+export default ${componentName};
+`,
+
+  // 带 Modal 的组件
+  modal: (componentName) => `import React, { useState } from 'react';
+import { Modal, Button, App } from 'antd';
+import type { ModalProps } from 'antd';
+
+interface ${componentName}Props {
+  title?: string;
+  onConfirm?: () => Promise<void>;
+}
+
+/**
+ * ${componentName} Modal 组件
+ * 
+ * v4 → v6 迁移说明:
+ * - ✅ visible 改为 open
+ * - ✅ onVisibleChange 改为 onOpenChange
+ * - ✅ 使用 App.useApp() 获取 message
+ */
+const ${componentName}: React.FC<${componentName}Props> = ({ 
+  title = '${componentName}',
+  onConfirm 
+}) => {
+  const [open, setOpen] = useState(false);
+  const [loading, setLoading] = useState(false);
+  const { message } = App.useApp();
+
+  const handleOpen = () => setOpen(true);
+  
+  const handleCancel = () => {
+    if (!loading) {
+      setOpen(false);
+    }
+  };
+
+  const handleOk = async () => {
+    setLoading(true);
+    try {
+      await onConfirm?.();
+      message.success('操作成功');
+      setOpen(false);
+    } catch (error) {
+      message.error('操作失败');
+      console.error(error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <>
+      <Button type="primary" onClick={handleOpen}>
+        打开 {title}
+      </Button>
+      
+      <Modal
+        title={title}
+        open={open}
+        onOk={handleOk}
+        onCancel={handleCancel}
+        confirmLoading={loading}
+        destroyOnClose
+      >
+        {/* TODO: 添加 Modal 内容 */}
+        <p>Modal 内容</p>
+      </Modal>
+    </>
+  );
+};
+
+export default ${componentName};
+`,
+
+  // 带 Table 的组件
+  table: (componentName) => `import React, { useState, useEffect } from 'react';
+import { Table, Button, Space, App } from 'antd';
+import type { TableProps } from 'antd';
+
+interface DataType {
+  key: string;
+  // TODO: 添加你的数据类型
+  name: string;
+}
+
+interface ${componentName}Props {
+  // TODO: 添加 props
+}
+
+/**
+ * ${componentName} Table 组件
+ * 
+ * v4 → v6 迁移说明:
+ * - ✅ Table API 已更新到 v6
+ * - ✅ 使用 TypeScript 类型定义
+ * - ✅ 分页配置已更新
+ */
+const ${componentName}: React.FC<${componentName}Props> = (props) => {
+  const [loading, setLoading] = useState(false);
+  const [dataSource, setDataSource] = useState<DataType[]>([]);
+  const [pagination, setPagination] = useState({
+    current: 1,
+    pageSize: 10,
+    total: 0,
+  });
+  const { message } = App.useApp();
+
+  const columns: TableProps<DataType>['columns'] = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+      key: 'name',
+    },
+    // TODO: 添加更多列
+    {
+      title: '操作',
+      key: 'action',
+      render: (_, record) => (
+        <Space>
+          <Button type="link" size="small">
+            编辑
+          </Button>
+          <Button type="link" size="small" danger>
+            删除
+          </Button>
+        </Space>
+      ),
+    },
+  ];
+
+  const fetchData = async (page = 1, pageSize = 10) => {
+    setLoading(true);
+    try {
+      // TODO: 实现数据获取逻辑
+      const response = { data: [], total: 0 };
+      
+      setDataSource(response.data);
+      setPagination({
+        current: page,
+        pageSize,
+        total: response.total,
+      });
+    } catch (error) {
+      message.error('获取数据失败');
+      console.error(error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    fetchData();
+  }, []);
+
+  const handleTableChange: TableProps<DataType>['onChange'] = (
+    pagination,
+    filters,
+    sorter
+  ) => {
+    fetchData(pagination.current, pagination.pageSize);
+  };
+
+  return (
+    <Table
+      columns={columns}
+      dataSource={dataSource}
+      loading={loading}
+      pagination={pagination}
+      onChange={handleTableChange}
+      rowKey="key"
+    />
+  );
+};
+
+export default ${componentName};
+`,
+
+  // ProTable 组件
+  proTable: (componentName) => `import React, { useRef } from 'react';
+import { ProTable } from '@ant-design/pro-components';
+import type { ProColumns, ActionType } from '@ant-design/pro-components';
+import { Button, Space, App } from 'antd';
+
+interface DataType {
+  id: string;
+  // TODO: 添加你的数据类型
+  name: string;
+  createdAt: string;
+}
+
+/**
+ * ${componentName} ProTable 组件
+ * 
+ * v4 → v6 迁移说明:
+ * - ✅ ProTable API 已更新
+ * - ✅ request 函数签名保持兼容
+ * - ✅ 使用新的类型定义
+ */
+const ${componentName}: React.FC = () => {
+  const actionRef = useRef<ActionType>();
+  const { message } = App.useApp();
+
+  const columns: ProColumns<DataType>[] = [
+    {
+      title: '名称',
+      dataIndex: 'name',
+      key: 'name',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createdAt',
+      key: 'createdAt',
+      valueType: 'dateTime',
+    },
+    // TODO: 添加更多列
+    {
+      title: '操作',
+      key: 'action',
+      valueType: 'option',
+      render: (_, record) => [
+        <Button key="edit" type="link" size="small">
+          编辑
+        </Button>,
+        <Button key="delete" type="link" size="small" danger>
+          删除
+        </Button>,
+      ],
+    },
+  ];
+
+  const handleRequest = async (
+    params: any,
+    sort: any,
+    filter: any
+  ) => {
+    try {
+      // TODO: 实现数据获取逻辑
+      const response = {
+        data: [],
+        total: 0,
+        success: true,
+      };
+      
+      return response;
+    } catch (error) {
+      message.error('获取数据失败');
+      return {
+        data: [],
+        total: 0,
+        success: false,
+      };
+    }
+  };
+
+  return (
+    <ProTable<DataType>
+      columns={columns}
+      actionRef={actionRef}
+      request={handleRequest}
+      rowKey="id"
+      search={{
+        labelWidth: 'auto',
+      }}
+      pagination={{
+        defaultPageSize: 10,
+        showSizeChanger: true,
+      }}
+      dateFormatter="string"
+      headerTitle="${componentName}"
+      toolBarRender={() => [
+        <Button key="button" type="primary">
+          新建
+        </Button>,
+      ]}
+    />
+  );
+};
+
+export default ${componentName};
+`,
+
+  // 主题切换组件(特殊模板)
+  theme: () => `import React, { useState, useEffect } from 'react';
+import { Switch, Space, Typography } from 'antd';
+import { MoonOutlined, SunOutlined } from '@ant-design/icons';
+
+const { Text } = Typography;
+
+type ThemeMode = 'light' | 'dark';
+
+interface ThemeSwitchProps {
+  onChange?: (theme: ThemeMode) => void;
+}
+
+/**
+ * 主题切换组件
+ * 
+ * v4 → v6 迁移说明:
+ * - ❌ 废弃了 CSS 文件方式 (theme/antd.dark.css)
+ * - ✅ 使用 ConfigProvider theme 配置
+ * - ✅ 支持动态主题切换
+ * - ✅ 使用 Design Token
+ * 
+ * 使用方法:
+ * 1. 在 App 根组件中使用 ConfigProvider
+ * 2. 根据此组件的状态切换 theme.algorithm
+ */
+const ThemeSwitch: React.FC<ThemeSwitchProps> = ({ onChange }) => {
+  const [theme, setTheme] = useState<ThemeMode>('light');
+
+  useEffect(() => {
+    // 从 localStorage 读取保存的主题
+    const savedTheme = localStorage.getItem('theme') as ThemeMode;
+    if (savedTheme) {
+      setTheme(savedTheme);
+      onChange?.(savedTheme);
+    }
+  }, []);
+
+  const handleChange = (checked: boolean) => {
+    const newTheme: ThemeMode = checked ? 'dark' : 'light';
+    setTheme(newTheme);
+    localStorage.setItem('theme', newTheme);
+    onChange?.(newTheme);
+  };
+
+  return (
+    <Space align="center">
+      <SunOutlined style={{ color: theme === 'light' ? '#1890ff' : '#999' }} />
+      <Switch
+        checked={theme === 'dark'}
+        onChange={handleChange}
+        checkedChildren={<MoonOutlined />}
+        unCheckedChildren={<SunOutlined />}
+      />
+      <Text type="secondary">{theme === 'dark' ? '暗黑' : '明亮'}</Text>
+    </Space>
+  );
+};
+
+export default ThemeSwitch;
+`,
+};
+
+// App Provider 模板
+const appProviderTemplate = () => `import React, { useState } from 'react';
+import { ConfigProvider, App as AntdApp, theme } from 'antd';
+import zhCN from 'antd/locale/zh_CN';
+import ThemeSwitch from './components/ThemeSwitch';
+
+type ThemeMode = 'light' | 'dark';
+
+/**
+ * App Provider 组件
+ * 
+ * v4 → v6 主题系统迁移:
+ * - ✅ 使用 ConfigProvider 管理全局配置
+ * - ✅ 使用 theme.algorithm 切换主题
+ * - ✅ 使用 Design Token 自定义主题
+ * - ✅ 使用 App 组件提供全局 message/notification/modal
+ */
+const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [themeMode, setThemeMode] = useState<ThemeMode>('light');
+
+  return (
+    <ConfigProvider
+      locale={zhCN}
+      theme={{
+        // 主题算法:暗黑或明亮
+        algorithm: themeMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
+        
+        // 全局 Design Token
+        token: {
+          colorPrimary: '#1890ff',
+          borderRadius: 6,
+          // TODO: 添加更多自定义 token
+        },
+        
+        // 组件级别的主题定制
+        components: {
+          Button: {
+            // TODO: 自定义 Button 样式
+          },
+          Card: {
+            // TODO: 自定义 Card 样式
+          },
+          // TODO: 添加更多组件定制
+        },
+      }}
+    >
+      <AntdApp>
+        {/* 主题切换器(可以放在布局的任意位置) */}
+        <div style={{ position: 'fixed', top: 16, right: 16, zIndex: 1000 }}>
+          <ThemeSwitch onChange={setThemeMode} />
+        </div>
+        
+        {children}
+      </AntdApp>
+    </ConfigProvider>
+  );
+};
+
+export default AppProvider;
+`;
+
+// 主题 Token 配置模板
+const themeTokensTemplate = () => `import { ThemeConfig } from 'antd';
+
+/**
+ * 自定义主题 Token 配置
+ * 
+ * Design Token 说明:
+ * - token: 全局 token,影响所有组件
+ * - components: 组件级别的 token,只影响特定组件
+ * 
+ * 常用 Token:
+ * - colorPrimary: 品牌主色
+ * - colorSuccess: 成功色
+ * - colorWarning: 警告色
+ * - colorError: 错误色
+ * - colorInfo: 信息色
+ * - colorText: 文本色
+ * - colorBgContainer: 容器背景色
+ * - borderRadius: 圆角大小
+ * - fontSize: 字体大小
+ * 
+ * 完整 Token 列表:
+ * https://ant.design/docs/react/customize-theme#theme
+ */
+
+export const lightTheme: ThemeConfig = {
+  token: {
+    colorPrimary: '#1890ff',
+    colorSuccess: '#52c41a',
+    colorWarning: '#faad14',
+    colorError: '#ff4d4f',
+    colorInfo: '#1890ff',
+    
+    borderRadius: 6,
+    fontSize: 14,
+    
+    // TODO: 添加更多自定义 token
+  },
+  components: {
+    Button: {
+      controlHeight: 32,
+      borderRadius: 6,
+    },
+    Card: {
+      borderRadiusLG: 8,
+    },
+    // TODO: 添加更多组件定制
+  },
+};
+
+export const darkTheme: ThemeConfig = {
+  token: {
+    ...lightTheme.token,
+    // 暗黑模式可以覆盖特定的 token
+    // 注意:使用 theme.darkAlgorithm 会自动处理大部分颜色
+  },
+  components: lightTheme.components,
+};
+`;
+
+// 生成组件文件
+function generateComponent(type, componentName, outputDir = '.') {
+  const template = templates[type];
+  
+  if (!template) {
+    console.error(`❌ 未知的组件类型: ${type}`);
+    console.log(`可用类型: ${Object.keys(templates).join(', ')}`);
+    process.exit(1);
+  }
+  
+  const content = template(componentName);
+  const filename = `${componentName}.tsx`;
+  const filepath = path.join(outputDir, filename);
+  
+  // 检查文件是否已存在
+  if (fs.existsSync(filepath)) {
+    console.log(`⚠️ 文件已存在: ${filepath}`);
+    console.log('是否覆盖?(请手动确认)');
+    return;
+  }
+  
+  fs.writeFileSync(filepath, content);
+  console.log(`✅ 已生成组件: ${filepath}`);
+}
+
+// 生成主题相关文件
+function generateThemeFiles(outputDir = '.') {
+  const files = [
+    { name: 'ThemeSwitch.tsx', content: templates.theme() },
+    { name: 'AppProvider.tsx', content: appProviderTemplate() },
+    { name: 'tokens.ts', content: themeTokensTemplate() },
+  ];
+  
+  files.forEach(({ name, content }) => {
+    const filepath = path.join(outputDir, name);
+    
+    if (fs.existsSync(filepath)) {
+      console.log(`⚠️ 文件已存在: ${filepath} (跳过)`);
+      return;
+    }
+    
+    fs.writeFileSync(filepath, content);
+    console.log(`✅ 已生成: ${filepath}`);
+  });
+  
+  console.log('\n📝 使用说明:');
+  console.log('1. 在你的根组件中引入 AppProvider:');
+  console.log('   import AppProvider from "./AppProvider";');
+  console.log('   <AppProvider><App /></AppProvider>');
+  console.log('');
+  console.log('2. 在任意需要使用 message/modal 的组件中:');
+  console.log('   const { message } = App.useApp();');
+  console.log('');
+  console.log('3. 主题切换器已集成在 AppProvider 中');
+  console.log('   你可以根据需要调整其位置\n');
+}
+
+// 主函数
+function main() {
+  const args = process.argv.slice(2);
+  
+  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
+    console.log(`
+Ant Design v6 组件模板生成器
+
+用法:
+  node component-template.js <类型> <组件名> [输出目录]
+  node component-template.js --theme [输出目录]
+
+组件类型:
+  basic      - 基础组件
+  form       - 表单组件
+  modal      - Modal 组件
+  table      - Table 组件
+  proTable   - ProTable 组件
+
+特殊命令:
+  --theme    - 生成完整的主题系统文件(ThemeSwitch + AppProvider + tokens)
+
+示例:
+  # 生成基础组件
+  node component-template.js basic UserCard ./src/components
+
+  # 生成表单组件
+  node component-template.js form UserForm ./src/components
+
+  # 生成主题系统文件
+  node component-template.js --theme ./src/theme
+
+可用模板:
+  ${Object.keys(templates).join(', ')}
+    `);
+    process.exit(0);
+  }
+  
+  if (args[0] === '--theme') {
+    const outputDir = args[1] || '.';
+    console.log(`\n🎨 生成主题系统文件到: ${outputDir}\n`);
+    
+    if (!fs.existsSync(outputDir)) {
+      fs.mkdirSync(outputDir, { recursive: true });
+    }
+    
+    generateThemeFiles(outputDir);
+    return;
+  }
+  
+  const [type, componentName, outputDir = '.'] = args;
+  
+  if (!componentName) {
+    console.error('❌ 请提供组件名称');
+    process.exit(1);
+  }
+  
+  console.log(`\n📝 生成 ${type} 类型的组件: ${componentName}\n`);
+  
+  if (!fs.existsSync(outputDir)) {
+    fs.mkdirSync(outputDir, { recursive: true });
+  }
+  
+  generateComponent(type, componentName, outputDir);
+}
+
+main();

+ 430 - 0
dashboard-v6/migration-tools/generate-global-utils.cjs

@@ -0,0 +1,430 @@
+#!/usr/bin/env node
+
+/**
+ * 全局单例工具生成器
+ *
+ * 功能:生成 antd-global.ts 文件和更新后的 AppProvider
+ * 使用方法:node generate-global-utils.js [输出目录]
+ */
+
+const fs = require("fs");
+const path = require("path");
+
+// antd-global.ts 模板
+const antdGlobalTemplate = `import { message, notification, Modal } from 'antd';
+import type { MessageInstance } from 'antd/es/message/interface';
+import type { NotificationInstance } from 'antd/es/notification/interface';
+import type { HookAPI } from 'antd/es/modal/useModal';
+
+/**
+ * 全局 antd 实例管理
+ * 
+ * 用途:为无法使用 hooks 的场景(工具函数、Redux Saga 等)提供全局访问
+ * 
+ * ⚠️ 注意:这是过渡方案!
+ * 推荐在 React 组件中使用 App.useApp() 代替
+ * 
+ * 使用方法:
+ * 1. 在 AppProvider 中初始化实例(见下方示例)
+ * 2. 在业务代码中导入使用:
+ *    import { globalMessage, globalNotification, globalModal } from '@/utils/antd-global';
+ */
+
+let messageInstance: MessageInstance | null = null;
+let notificationInstance: NotificationInstance | null = null;
+let modalInstance: HookAPI | null = null;
+
+/**
+ * 设置 message 实例
+ * 在 AppProvider 中调用
+ */
+export const setMessageInstance = (instance: MessageInstance) => {
+  messageInstance = instance;
+  console.log('✅ Global message instance initialized');
+};
+
+/**
+ * 设置 notification 实例
+ * 在 AppProvider 中调用
+ */
+export const setNotificationInstance = (instance: NotificationInstance) => {
+  notificationInstance = instance;
+  console.log('✅ Global notification instance initialized');
+};
+
+/**
+ * 设置 modal 实例
+ * 在 AppProvider 中调用
+ */
+export const setModalInstance = (instance: HookAPI) => {
+  modalInstance = instance;
+  console.log('✅ Global modal instance initialized');
+};
+
+/**
+ * 检查实例是否已初始化
+ */
+const checkInstance = (name: string, instance: any) => {
+  if (!instance) {
+    console.error(\`❌ \${name} instance not initialized. Did you forget to wrap <App> component?\`);
+    return false;
+  }
+  return true;
+};
+
+/**
+ * 全局 message API
+ * 
+ * 使用示例:
+ * import { globalMessage } from '@/utils/antd-global';
+ * globalMessage.success('操作成功');
+ */
+export const globalMessage = {
+  success: (content: string, duration?: number, onClose?: () => void) => {
+    if (!checkInstance('Message', messageInstance)) return;
+    return messageInstance!.success(content, duration, onClose);
+  },
+  
+  error: (content: string, duration?: number, onClose?: () => void) => {
+    if (!checkInstance('Message', messageInstance)) return;
+    return messageInstance!.error(content, duration, onClose);
+  },
+  
+  warning: (content: string, duration?: number, onClose?: () => void) => {
+    if (!checkInstance('Message', messageInstance)) return;
+    return messageInstance!.warning(content, duration, onClose);
+  },
+  
+  info: (content: string, duration?: number, onClose?: () => void) => {
+    if (!checkInstance('Message', messageInstance)) return;
+    return messageInstance!.info(content, duration, onClose);
+  },
+  
+  loading: (content: string, duration?: number, onClose?: () => void) => {
+    if (!checkInstance('Message', messageInstance)) return;
+    return messageInstance!.loading(content, duration, onClose);
+  },
+  
+  destroy: () => {
+    if (!checkInstance('Message', messageInstance)) return;
+    return messageInstance!.destroy();
+  },
+};
+
+/**
+ * 全局 notification API
+ * 
+ * 使用示例:
+ * import { globalNotification } from '@/utils/antd-global';
+ * globalNotification.info({
+ *   message: '通知标题',
+ *   description: '通知内容',
+ * });
+ */
+export const globalNotification = {
+  success: (config: any) => {
+    if (!checkInstance('Notification', notificationInstance)) return;
+    return notificationInstance!.success(config);
+  },
+  
+  error: (config: any) => {
+    if (!checkInstance('Notification', notificationInstance)) return;
+    return notificationInstance!.error(config);
+  },
+  
+  warning: (config: any) => {
+    if (!checkInstance('Notification', notificationInstance)) return;
+    return notificationInstance!.warning(config);
+  },
+  
+  info: (config: any) => {
+    if (!checkInstance('Notification', notificationInstance)) return;
+    return notificationInstance!.info(config);
+  },
+  
+  open: (config: any) => {
+    if (!checkInstance('Notification', notificationInstance)) return;
+    return notificationInstance!.open(config);
+  },
+  
+  destroy: (key?: string) => {
+    if (!checkInstance('Notification', notificationInstance)) return;
+    return notificationInstance!.destroy(key);
+  },
+};
+
+/**
+ * 全局 modal API
+ * 
+ * 使用示例:
+ * import { globalModal } from '@/utils/antd-global';
+ * globalModal.confirm({
+ *   title: '确认删除?',
+ *   content: '删除后无法恢复',
+ *   onOk: () => { ... },
+ * });
+ */
+export const globalModal = {
+  confirm: (config: any) => {
+    if (!checkInstance('Modal', modalInstance)) return;
+    return modalInstance!.confirm(config);
+  },
+  
+  info: (config: any) => {
+    if (!checkInstance('Modal', modalInstance)) return;
+    return modalInstance!.info(config);
+  },
+  
+  success: (config: any) => {
+    if (!checkInstance('Modal', modalInstance)) return;
+    return modalInstance!.success(config);
+  },
+  
+  error: (config: any) => {
+    if (!checkInstance('Modal', modalInstance)) return;
+    return modalInstance!.error(config);
+  },
+  
+  warning: (config: any) => {
+    if (!checkInstance('Modal', modalInstance)) return;
+    return modalInstance!.warning(config);
+  },
+};
+`;
+
+// 更新后的 AppProvider 模板(带实例初始化)
+const appProviderWithGlobalTemplate = `import React, { useState, useEffect } from 'react';
+import { ConfigProvider, App as AntdApp, theme } from 'antd';
+import zhCN from 'antd/locale/zh_CN';
+import { 
+  setMessageInstance, 
+  setNotificationInstance, 
+  setModalInstance 
+} from '../utils/antd-global';
+
+type ThemeMode = 'light' | 'dark';
+
+/**
+ * 初始化全局 antd 实例
+ * 为工具函数、Redux Saga 等无法使用 hooks 的场景提供支持
+ */
+const InitGlobalInstances: React.FC = () => {
+  const { message, notification, modal } = AntdApp.useApp();
+  
+  useEffect(() => {
+    setMessageInstance(message);
+    setNotificationInstance(notification);
+    setModalInstance(modal);
+  }, [message, notification, modal]);
+  
+  return null;
+};
+
+/**
+ * App Provider 组件
+ * 
+ * v4 → v6 主题系统迁移:
+ * - ✅ 使用 ConfigProvider 管理全局配置
+ * - ✅ 使用 theme.algorithm 切换主题
+ * - ✅ 使用 Design Token 自定义主题
+ * - ✅ 使用 App 组件提供全局 message/notification/modal
+ * - ✅ 初始化全局实例,支持工具函数调用
+ */
+const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [themeMode, setThemeMode] = useState<ThemeMode>('light');
+
+  // 从 localStorage 读取保存的主题
+  useEffect(() => {
+    const savedTheme = localStorage.getItem('theme') as ThemeMode;
+    if (savedTheme) {
+      setThemeMode(savedTheme);
+    }
+  }, []);
+
+  // 保存主题到 localStorage
+  const handleThemeChange = (mode: ThemeMode) => {
+    setThemeMode(mode);
+    localStorage.setItem('theme', mode);
+  };
+
+  return (
+    <ConfigProvider
+      locale={zhCN}
+      theme={{
+        // 主题算法:暗黑或明亮
+        algorithm: themeMode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
+        
+        // 全局 Design Token
+        token: {
+          colorPrimary: '#1890ff',
+          borderRadius: 6,
+          // TODO: 添加更多自定义 token
+        },
+        
+        // 组件级别的主题定制
+        components: {
+          Button: {
+            controlHeight: 32,
+          },
+          Card: {
+            borderRadiusLG: 8,
+          },
+          // TODO: 添加更多组件定制
+        },
+      }}
+    >
+      <AntdApp>
+        {/* 初始化全局实例 - 重要! */}
+        <InitGlobalInstances />
+        
+        {/* 你可以在这里添加 ThemeSwitch 组件 */}
+        {/* <ThemeSwitch onChange={handleThemeChange} /> */}
+        
+        {children}
+      </AntdApp>
+    </ConfigProvider>
+  );
+};
+
+export default AppProvider;
+`;
+
+// 使用示例文档
+const usageExample = `# 全局单例使用示例
+
+## 📁 文件结构
+
+\`\`\`
+src/
+├── utils/
+│   └── antd-global.ts        ← 全局单例工具
+├── theme/
+│   └── AppProvider.tsx        ← 更新后的 AppProvider
+└── main.tsx                   ← 根组件
+\`\`\`
+
+## 🚀 快速开始
+
+### 1. 在根组件中使用 AppProvider
+
+\`\`\`typescript
+// src/main.tsx
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { Provider } from 'react-redux';
+import store from './store';
+import AppProvider from './theme/AppProvider';
+import Router from './Router';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+  <React.StrictMode>
+    <Provider store={store}>
+      <AppProvider>
+        <Router />
+      </AppProvider>
+    </Provider>
+  </React.StrictMode>
+);
+\`\`\`
+
+### 2. 在业务代码中使用
+
+\`\`\`typescript
+// 任意文件中
+import { globalMessage, globalNotification, globalModal } from '@/utils/antd-global';
+
+// 使用
+globalMessage.success('操作成功');
+globalNotification.info({ message: '通知', description: '内容' });
+globalModal.confirm({ title: '确认?', onOk: () => {} });
+\`\`\`
+
+## 📝 API 参考
+
+### globalMessage
+- globalMessage.success(content, duration?, onClose?)
+- globalMessage.error(content, duration?, onClose?)
+- globalMessage.warning(content, duration?, onClose?)
+- globalMessage.info(content, duration?, onClose?)
+- globalMessage.loading(content, duration?, onClose?)
+
+### globalNotification
+- globalNotification.success(config)
+- globalNotification.error(config)
+- globalNotification.warning(config)
+- globalNotification.info(config)
+- globalNotification.open(config)
+
+### globalModal
+- globalModal.confirm(config)
+- globalModal.info(config)
+- globalModal.success(config)
+- globalModal.error(config)
+- globalModal.warning(config)
+`;
+
+// 主函数
+function main() {
+  const args = process.argv.slice(2);
+  const outputDir = args[0] || "./src/utils";
+
+  console.log("\n🔧 生成全局单例工具文件...\n");
+
+  // 创建输出目录
+  if (!fs.existsSync(outputDir)) {
+    fs.mkdirSync(outputDir, { recursive: true });
+    console.log(`✅ 创建目录: ${outputDir}`);
+  }
+
+  // 生成 antd-global.ts
+  const antdGlobalPath = path.join(outputDir, "antd-global.ts");
+  if (fs.existsSync(antdGlobalPath)) {
+    console.log(`⚠️  文件已存在: ${antdGlobalPath} (跳过)`);
+  } else {
+    fs.writeFileSync(antdGlobalPath, antdGlobalTemplate);
+    console.log(`✅ 已生成: ${antdGlobalPath}`);
+  }
+
+  // 生成更新后的 AppProvider
+  const appProviderDir = path.join(outputDir, "../theme");
+  if (!fs.existsSync(appProviderDir)) {
+    fs.mkdirSync(appProviderDir, { recursive: true });
+  }
+
+  const appProviderPath = path.join(appProviderDir, "AppProvider.tsx");
+  if (fs.existsSync(appProviderPath)) {
+    console.log(`⚠️  文件已存在: ${appProviderPath} (跳过)`);
+    console.log("   如需更新,请手动添加 InitGlobalInstances 组件");
+  } else {
+    fs.writeFileSync(appProviderPath, appProviderWithGlobalTemplate);
+    console.log(`✅ 已生成: ${appProviderPath}`);
+  }
+
+  // 生成使用示例
+  const examplePath = path.join(outputDir, "antd-global-usage.md");
+  fs.writeFileSync(examplePath, usageExample);
+  console.log(`✅ 已生成: ${examplePath}`);
+
+  console.log("\n" + "=".repeat(80));
+  console.log("✅ 全局单例工具生成完成!");
+  console.log("=".repeat(80) + "\n");
+
+  console.log("📝 下一步操作:\n");
+  console.log("1. 在根组件中使用 AppProvider:");
+  console.log(`   import AppProvider from './theme/AppProvider';`);
+  console.log("   <AppProvider><YourApp /></AppProvider>\n");
+
+  console.log("2. 运行替换脚本,批量替换 import:");
+  console.log("   node replace-static-import.js ./src --dry-run\n");
+
+  console.log("3. 确认无误后执行替换:");
+  console.log("   node replace-static-import.js ./src --backup\n");
+
+  console.log("4. 测试所有静态方法是否正常工作\n");
+
+  console.log("📖 详细说明请查看:");
+  console.log(`   - ${examplePath}`);
+  console.log("   - static-methods-migration.md\n");
+}
+
+main();

+ 305 - 0
dashboard-v6/migration-tools/import-replace.cjs

@@ -0,0 +1,305 @@
+#!/usr/bin/env node
+
+/**
+ * Ant Design v4 → v6 Import 语句批量替换工具
+ * 
+ * 功能:自动替换代码中的 antd API 调用
+ * 使用方法:node import-replace.js <目标目录路径> [--dry-run] [--backup]
+ * 
+ * 参数说明:
+ *   --dry-run  : 只显示将要修改的内容,不实际修改文件
+ *   --backup   : 修改前备份原文件(.bak)
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+// 替换规则配置
+const replacements = [
+  // 1. visible → open
+  {
+    name: 'visible to open (props)',
+    pattern: /(\s+)visible(\s*=\s*\{)/g,
+    replacement: '$1open$2',
+    filePattern: /\.(tsx?|jsx?)$/,
+    description: 'Modal/Drawer/Popover 等组件的 visible 改为 open'
+  },
+  {
+    name: 'onVisibleChange to onOpenChange',
+    pattern: /(\s+)onVisibleChange(\s*=\s*\{)/g,
+    replacement: '$1onOpenChange$2',
+    filePattern: /\.(tsx?|jsx?)$/,
+    description: 'onVisibleChange 改为 onOpenChange'
+  },
+  
+  // 2. moment → dayjs
+  {
+    name: 'moment import to dayjs',
+    pattern: /import\s+moment\s+from\s+['"]moment['"]/g,
+    replacement: "import dayjs from 'dayjs'",
+    filePattern: /\.(tsx?|jsx?)$/,
+    description: 'moment.js import 改为 dayjs'
+  },
+  {
+    name: 'moment() to dayjs()',
+    pattern: /\bmoment\(/g,
+    replacement: 'dayjs(',
+    filePattern: /\.(tsx?|jsx?)$/,
+    description: 'moment() 调用改为 dayjs()'
+  },
+  
+  // 3. Dropdown overlay → menu
+  {
+    name: 'Dropdown overlay to menu',
+    pattern: /(\s+)overlay(\s*=\s*\{)/g,
+    replacement: '$1menu$2',
+    filePattern: /\.(tsx?|jsx?)$/,
+    description: 'Dropdown 的 overlay 改为 menu',
+    warning: '⚠️ 注意:menu 需要返回 Menu 组件,可能需要手动调整'
+  },
+  
+  // 4. PageHeader → 需要手动处理,这里只标记
+  // 不做自动替换,因为需要手动迁移到 ProComponents
+  
+  // 5. BackTop → FloatButton.BackTop
+  {
+    name: 'BackTop to FloatButton.BackTop',
+    pattern: /<BackTop(\s+[^>]*)?>/g,
+    replacement: '<FloatButton.BackTop$1>',
+    filePattern: /\.(tsx?|jsx?)$/,
+    description: 'BackTop 改为 FloatButton.BackTop',
+    warning: '⚠️ 注意:需要确保已导入 FloatButton'
+  },
+  {
+    name: 'BackTop closing tag',
+    pattern: /<\/BackTop>/g,
+    replacement: '</FloatButton.BackTop>',
+    filePattern: /\.(tsx?|jsx?)$/,
+    description: 'BackTop 结束标签'
+  },
+  
+  // 6. 添加必要的 import (如果文件中使用了 FloatButton 但未导入)
+  // 这个需要更智能的处理,暂时跳过
+];
+
+// 统计信息
+const stats = {
+  totalFiles: 0,
+  modifiedFiles: 0,
+  totalReplacements: 0,
+  replacementsByType: new Map(),
+  errors: []
+};
+
+// 递归扫描目录
+function scanDirectory(dir, options) {
+  const files = fs.readdirSync(dir);
+  
+  files.forEach(file => {
+    const filePath = path.join(dir, file);
+    const stat = fs.statSync(filePath);
+    
+    if (stat.isDirectory()) {
+      if (!['node_modules', '.git', 'build', 'dist', '.next'].includes(file)) {
+        scanDirectory(filePath, options);
+      }
+    } else if (stat.isFile()) {
+      if (/\.(tsx?|jsx?)$/.test(file)) {
+        stats.totalFiles++;
+        processFile(filePath, options);
+      }
+    }
+  });
+}
+
+// 处理单个文件
+function processFile(filePath, options) {
+  let content = fs.readFileSync(filePath, 'utf-8');
+  const originalContent = content;
+  let fileModified = false;
+  let fileReplacements = [];
+  
+  replacements.forEach(rule => {
+    if (!rule.filePattern.test(filePath)) {
+      return;
+    }
+    
+    const matches = content.match(rule.pattern);
+    if (matches && matches.length > 0) {
+      content = content.replace(rule.pattern, rule.replacement);
+      
+      const count = matches.length;
+      fileReplacements.push({
+        rule: rule.name,
+        count,
+        description: rule.description,
+        warning: rule.warning
+      });
+      
+      stats.totalReplacements += count;
+      stats.replacementsByType.set(
+        rule.name,
+        (stats.replacementsByType.get(rule.name) || 0) + count
+      );
+      
+      fileModified = true;
+    }
+  });
+  
+  if (fileModified) {
+    stats.modifiedFiles++;
+    
+    if (options.dryRun) {
+      console.log(`\n📝 [DRY RUN] 将修改: ${filePath}`);
+      fileReplacements.forEach(rep => {
+        console.log(`   ✓ ${rep.description} (${rep.count} 处)`);
+        if (rep.warning) {
+          console.log(`     ${rep.warning}`);
+        }
+      });
+    } else {
+      // 备份原文件
+      if (options.backup) {
+        fs.writeFileSync(filePath + '.bak', originalContent);
+      }
+      
+      // 写入修改后的内容
+      try {
+        fs.writeFileSync(filePath, content, 'utf-8');
+        console.log(`✅ 已修改: ${filePath}`);
+        fileReplacements.forEach(rep => {
+          console.log(`   ✓ ${rep.description} (${rep.count} 处)`);
+          if (rep.warning) {
+            console.log(`     ${rep.warning}`);
+          }
+        });
+      } catch (error) {
+        stats.errors.push({ file: filePath, error: error.message });
+        console.error(`❌ 修改失败: ${filePath}`);
+        console.error(`   错误: ${error.message}`);
+      }
+    }
+  }
+}
+
+// 生成报告
+function generateReport(options) {
+  console.log('\n' + '='.repeat(80));
+  console.log('📊 Ant Design v4 → v6 批量替换报告');
+  if (options.dryRun) {
+    console.log('   [DRY RUN 模式 - 未实际修改文件]');
+  }
+  console.log('='.repeat(80) + '\n');
+  
+  console.log('📁 文件统计:');
+  console.log(`   扫描文件总数: ${stats.totalFiles}`);
+  console.log(`   修改文件数: ${stats.modifiedFiles}`);
+  console.log(`   替换总次数: ${stats.totalReplacements}\n`);
+  
+  if (stats.replacementsByType.size > 0) {
+    console.log('📋 替换详情:');
+    const sorted = Array.from(stats.replacementsByType.entries())
+      .sort((a, b) => b[1] - a[1]);
+    
+    sorted.forEach(([rule, count]) => {
+      console.log(`   ${rule}: ${count} 次`);
+    });
+    console.log('');
+  }
+  
+  if (stats.errors.length > 0) {
+    console.log('❌ 错误列表:');
+    stats.errors.forEach(({ file, error }) => {
+      console.log(`   ${file}`);
+      console.log(`      ${error}`);
+    });
+    console.log('');
+  }
+  
+  console.log('='.repeat(80));
+  
+  if (options.dryRun) {
+    console.log('💡 提示: 这是 DRY RUN 模式,文件未被实际修改');
+    console.log('   如果确认无误,请去掉 --dry-run 参数重新运行');
+  } else {
+    console.log('✅ 替换完成!');
+    if (options.backup) {
+      console.log('   原文件已备份为 .bak 文件');
+    }
+    console.log('   建议使用 git diff 检查改动,确保无误后再提交');
+  }
+  
+  console.log('='.repeat(80) + '\n');
+  
+  // 特别提醒
+  if (stats.totalReplacements > 0) {
+    console.log('⚠️ 重要提醒:');
+    console.log('   1. 某些替换可能需要手动调整(如 Dropdown 的 menu 属性)');
+    console.log('   2. 建议运行 scan-api-diff.js 再次检查是否有遗漏');
+    console.log('   3. 必须手动处理以下情况:');
+    console.log('      - PageHeader 组件(已移除,需迁移到 ProComponents)');
+    console.log('      - message/notification 静态方法(需要 App.useApp())');
+    console.log('      - Modal.confirm 等静态方法(需要 Modal.useModal())');
+    console.log('      - Form.getFieldDecorator(已废弃)\n');
+  }
+}
+
+// 主函数
+function main() {
+  const args = process.argv.slice(2);
+  
+  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
+    console.log(`
+Ant Design v4 → v6 Import 批量替换工具
+
+用法:
+  node import-replace.js <目标目录> [选项]
+
+选项:
+  --dry-run   只显示将要修改的内容,不实际修改文件
+  --backup    修改前备份原文件(.bak)
+  --help, -h  显示帮助信息
+
+示例:
+  # 预览将要修改的内容
+  node import-replace.js ../old-project/src --dry-run
+
+  # 执行替换并备份
+  node import-replace.js ../old-project/src --backup
+
+  # 直接执行替换(谨慎使用)
+  node import-replace.js ../old-project/src
+    `);
+    process.exit(0);
+  }
+  
+  const targetDir = args[0];
+  const options = {
+    dryRun: args.includes('--dry-run'),
+    backup: args.includes('--backup')
+  };
+  
+  if (!fs.existsSync(targetDir)) {
+    console.error(`❌ 目录不存在: ${targetDir}`);
+    process.exit(1);
+  }
+  
+  if (!options.dryRun && !options.backup) {
+    console.log('⚠️ 警告: 您没有启用备份选项,文件将直接被修改!');
+    console.log('建议先使用 --dry-run 预览,或添加 --backup 备份原文件\n');
+  }
+  
+  console.log(`\n🔧 开始处理目录: ${targetDir}`);
+  if (options.dryRun) {
+    console.log('   [DRY RUN 模式]');
+  }
+  if (options.backup) {
+    console.log('   [备份模式]');
+  }
+  console.log('');
+  
+  scanDirectory(targetDir, options);
+  generateReport(options);
+}
+
+main();

+ 603 - 0
dashboard-v6/migration-tools/migration-checklist.md

@@ -0,0 +1,603 @@
+# Ant Design v4.24 → v6 迁移检查清单
+
+> 项目迁移开始日期: 2026-02-14
+> 策略: 在新脚手架上重构,保留 v4 代码不变
+
+---
+
+## 📊 迁移进度总览
+
+- [ ] **Phase 1: 基础设施层** (0/15)
+- [ ] **Phase 2: 通用组件层** (0/12)
+- [ ] **Phase 3: 业务组件层** (0/45+)
+- [ ] **Phase 4: 样式和主题** (0/5)
+- [ ] **Phase 5: 测试和优化** (0/8)
+
+**总体进度: 0% (0/85)**
+
+---
+
+## 🎯 Phase 1: 基础设施层
+
+### 1.1 类型定义和工具
+
+- [x] `src/types/article.ts` - 文章类型定义
+- [x] `src/types/chat.ts` - 聊天类型定义
+- [x] `src/types/search.ts` - 搜索类型定义
+- [x] `src/utils.ts` - 工具函数迁移
+- [x] `src/request.ts` - HTTP 请求封装
+- [x] `src/hooks.ts` - 自定义 hooks
+
+**注意事项:**
+
+- ✅ 类型定义通常不受 antd 版本影响,可直接复制
+- ⚠️ 检查是否有引用 antd 组件类型的地方
+
+### 1.2 Redux 状态管理
+
+- [x] `src/store.ts` - Store 配置
+- [x] `src/reducers/` - 所有 29 个 reducer 文件
+  - [ ] accept-pr.ts
+  - [ ] article-mode.ts
+  - [ ] cart-mode.ts
+  - [ ] command.ts
+  - [ ] course-user.ts
+  - [ ] current-course.ts
+  - [ ] current-user.ts
+  - [ ] discussion-count.ts
+  - [ ] discussion.ts
+  - [ ] focus.ts
+  - [ ] inline-dict.ts
+  - [ ] layout.ts
+  - [ ] net-status.ts
+  - [ ] nissaya-ending-vocabulary.ts
+  - [ ] open-article.ts
+  - [ ] para-change.ts
+  - [ ] pr-load.ts
+  - [ ] relation-add.ts
+  - [ ] relation.ts
+  - [ ] right-panel.ts
+  - [ ] sentence.ts
+  - [ ] sent-word.ts
+  - [ ] setting.ts
+  - [ ] suggestion.ts
+  - [ ] term-change.ts
+  - [ ] term-click.ts
+  - [ ] term-order.ts
+  - [ ] term-vocabulary.ts
+  - [ ] theme.ts
+  - [ ] wbw.ts
+
+**注意事项:**
+
+- ✅ Redux 逻辑与 antd 版本无关,可直接迁移
+- ⚠️ 检查 `theme.ts` reducer,需要适配新的主题系统
+
+### 1.3 路由和布局
+
+- [ ] `src/Router.tsx` - 路由配置
+- [ ] `src/layouts/anonymous/index.tsx` - 匿名布局
+- [ ] `src/layouts/dashboard/index.tsx` - 仪表盘布局
+
+**注意事项:**
+
+- ⚠️ Layout 组件在 v6 中有 API 变更
+- ⚠️ 响应式断点值有调整
+
+---
+
+## 🎯 Phase 2: 通用组件层 ⭐ **当前优先级**
+
+### 2.1 General 组件 (src/components/general/\*)
+
+- [ ] `BeiAn.tsx` - 备案信息
+- [ ] `EditableLabel.tsx` - 可编辑标签
+- [ ] `ErrorResult.tsx` - 错误结果页
+- [ ] `Feedback.tsx` - 反馈组件
+- [ ] `FileSize.tsx` - 文件大小显示
+- [ ] `LangSelect.tsx` - 语言选择器
+- [ ] `Marked.tsx` - Markdown 渲染
+- [ ] `Mermaid.tsx` - Mermaid 图表
+- [ ] `NetStatus.tsx` - 网络状态
+- [ ] `NissayaCard.tsx` & `NissayaCardTable.tsx` - 卡片组件
+- [ ] `ParserError.tsx` - 解析错误
+- [ ] `ReadonlyLabel.tsx` - 只读标签
+- [ ] `SearchButton.tsx` - 搜索按钮
+- [ ] `TermTextArea.tsx` & `TermTextAreaMenu.tsx` - 文本区域
+- [ ] `TextDiff.tsx` - 文本差异对比
+- [ ] `ThemeSelect.tsx` - 主题选择器 ⚠️ **需重写**
+- [ ] `TimeShow.tsx` - 时间显示
+- [ ] `UiLangSelect.tsx` - UI 语言选择
+- [ ] `VideoModal.tsx` & `VideoPlayer.tsx` & `Video.tsx` - 视频组件
+- [ ] `VisibleObserver.tsx` - 可见性观察器
+
+**迁移优先级:**
+
+1. 🔴 高优先级: `ThemeSelect.tsx` (需要重写)
+2. 🟡 中优先级: 表单相关、选择器相关
+3. 🟢 低优先级: 纯展示组件
+
+### 2.2 Auth 认证组件 (src/components/auth/\*)
+
+- [ ] `Account.tsx` - 账户信息
+- [ ] `Avatar.tsx` - 头像组件
+- [ ] `LoginAlertModal.tsx` & `LoginAlert.tsx` - 登录提示
+- [ ] `LoginButton.tsx` - 登录按钮
+- [ ] `SignInAvatar.tsx` - 登录头像
+- [ ] `SoftwareEdition.tsx` - 软件版本
+- [ ] `StudioCard.tsx` & `Studio.tsx` - 工作室
+- [ ] `ToLibrary.tsx` & `ToStudio.tsx` - 导航组件
+- [ ] `User.tsx` - 用户组件
+
+### 2.3 API 层 (src/components/api/\*)
+
+- [ ] 复制所有 21 个 API 文件(通常不受影响)
+- [ ] 验证 API 调用是否正常
+
+---
+
+## 🎯 Phase 3: 业务组件层
+
+### 3.1 核心组件 - ProComponents ⚠️ **重点关注**
+
+你的项目大量使用 ProComponents,需要特别注意:
+
+#### ProTable 相关
+
+- [ ] `src/components/pro-table/*` - 自定义 ProTable 组件
+- [ ] 所有使用 ProTable 的业务组件
+
+**v4 → v6 ProTable 主要变更:**
+
+```typescript
+// v4
+<ProTable
+  rowKey="id"
+  request={async (params) => { ... }}
+  columns={columns}
+/>
+
+// v6 (主要兼容,但有细节调整)
+<ProTable
+  rowKey="id"
+  request={async (params, sort, filter) => { ... }} // 参数顺序可能变化
+  columns={columns}
+  // 新增: cardProps, polling 等属性
+/>
+```
+
+#### ProForm 相关
+
+**主要变更:**
+
+- Form.Item 不再需要 `name` 和 `label` 同时存在
+- `dependencies` 写法优化
+- 表单联动更简洁
+
+#### ProList 相关
+
+**主要变更:**
+
+- `metas` 配置优化
+- 响应式布局改进
+
+### 3.2 Article 文章模块 (56个文件)
+
+- [ ] `ArticleCard.tsx` & 相关卡片组件
+- [ ] `ArticleEdit.tsx` & 编辑相关组件
+- [ ] `ArticleList.tsx` & 列表相关组件
+- [ ] `TocTree.tsx` & 目录相关组件
+- [ ] ... (其他 50+ 文件)
+
+### 3.3 Channel 频道模块
+
+- [ ] 迁移所有 channel 组件
+
+### 3.4 Corpus 语料模块
+
+- [ ] 迁移所有 corpus 组件
+
+### 3.5 Course 课程模块
+
+- [ ] 迁移所有 course 组件
+
+### 3.6 其他业务模块
+
+- [ ] Dict 词典模块
+- [ ] Term 术语模块
+- [ ] Task 任务模块
+- [ ] Discussion 讨论模块
+- [ ] AI 相关模块
+- [ ] Anthology 文集模块
+- [ ] Attachment 附件模块
+- [ ] Blog 博客模块
+- [ ] Chat 聊天模块
+- [ ] Export 导出模块
+- [ ] FTS 全文搜索模块
+- [ ] Group 组模块
+- [ ] Invite 邀请模块
+- [ ] Like 点赞模块
+- [ ] Notification 通知模块
+- [ ] Recent 最近访问模块
+- [ ] Share 分享模块
+- [ ] Tag 标签模块
+- [ ] Transfer 转移模块
+- [ ] Webhook 模块
+
+---
+
+## 🎯 Phase 4: 样式和主题系统 ⚠️ **重大变更**
+
+### 4.1 主题迁移 - 从 CSS 到 ConfigProvider
+
+**❌ v4 方式 (废弃):**
+
+```css
+/* theme/antd.dark.css */
+.ant-btn-primary {
+  background-color: #1890ff;
+}
+```
+
+**✅ v6 方式 (使用 Design Token):**
+
+```typescript
+import { ConfigProvider, theme } from 'antd';
+
+<ConfigProvider
+  theme={{
+    algorithm: theme.darkAlgorithm, // 暗黑主题
+    token: {
+      colorPrimary: '#00b96b',
+      borderRadius: 2,
+      // ... 其他 token
+    },
+    components: {
+      Button: {
+        colorPrimary: '#00b96b',
+      },
+      // ... 其他组件定制
+    }
+  }}
+>
+  <App />
+</ConfigProvider>
+```
+
+### 4.2 主题切换任务清单
+
+- [ ] **删除旧文件**: `src/theme/antd.dark.css`
+- [ ] **删除旧文件**: `src/theme/antpro.dark.css`
+- [ ] **创建新组件**: `src/components/theme/ThemeProvider.tsx` (主题提供者)
+- [ ] **重写组件**: `src/components/general/ThemeSelect.tsx` (主题切换器)
+- [ ] **创建配置**: `src/theme/tokens.ts` (Design Token 配置)
+
+**新主题系统特性:**
+
+- ✅ 动态主题切换(无需刷新页面)
+- ✅ CSS 变量支持
+- ✅ 组件级主题定制
+- ✅ 暗黑模式内置支持
+- ✅ 更好的 TypeScript 类型提示
+
+### 4.3 自定义样式迁移
+
+- [ ] `src/assets/font/main.css` - 字体样式
+- [ ] `src/components/article/article.css` - 文章样式
+- [ ] `src/components/dict/style.css` - 词典样式
+- [ ] `src/components/fts/search.css` - 搜索样式
+- [ ] `src/components/general/style.css` - 通用样式
+- [ ] `src/components/chat/style.css` - 聊天样式
+- [ ] `src/components/template/style.css` - 模板样式
+
+**检查要点:**
+
+- ⚠️ 是否有覆盖 antd 默认样式的 CSS 选择器
+- ⚠️ 类名是否有变化 (如 `.ant-btn` → 可能保持不变)
+- ⚠️ 使用 less 变量的地方需要改为 CSS 变量或 Design Token
+
+---
+
+## 🎯 Phase 5: 测试和优化
+
+### 5.1 功能测试
+
+- [ ] 登录/注册流程
+- [ ] 文章 CRUD 操作
+- [ ] 课程管理
+- [ ] 搜索功能
+- [ ] 主题切换
+- [ ] 国际化切换
+- [ ] 移动端响应式
+
+### 5.2 性能优化
+
+- [ ] 检查包体积 (对比 v4 vs v6)
+- [ ] 组件懒加载
+- [ ] Tree-shaking 验证
+
+### 5.3 兼容性测试
+
+- [ ] Chrome 最新版
+- [ ] Firefox 最新版
+- [ ] Safari 最新版
+- [ ] Edge 最新版
+- [ ] 移动端浏览器
+
+---
+
+## 📚 关键 API 变更速查表
+
+### 常用组件 API 对照
+
+#### Button
+
+```typescript
+// ✅ 基本兼容,无重大变更
+<Button type="primary">Click</Button>
+```
+
+#### Tag
+
+```typescript
+// ✅ 基本兼容
+// ⚠️ 注意: closeIcon 属性有调整
+<Tag closable onClose={handleClose}>Tag</Tag>
+```
+
+#### Card
+
+```typescript
+// ✅ 基本兼容
+// 新增: classNames, styles 属性用于更细粒度控制
+<Card
+  title="Title"
+  extra={<Button>More</Button>}
+  classNames={{ header: 'custom-header' }} // v6 新增
+>
+  Content
+</Card>
+```
+
+#### Popover
+
+```typescript
+// ⚠️ 有变更
+// v4
+<Popover
+  visible={visible}  // ❌ 废弃
+  onVisibleChange={handleChange}  // ❌ 废弃
+>
+
+// v6
+<Popover
+  open={open}  // ✅ 新属性
+  onOpenChange={handleChange}  // ✅ 新属性
+>
+```
+
+#### Modal
+
+```typescript
+// ⚠️ 重要变更
+// v4
+Modal.confirm({
+  title: 'Confirm',
+  visible: true,  // ❌ 废弃
+})
+
+// v6
+const [modal, contextHolder] = Modal.useModal(); // ✅ 推荐用 hook
+modal.confirm({
+  title: 'Confirm',
+  open: true,  // ✅ 新属性
+})
+
+// 或使用静态方法(需要包裹 App 组件)
+<App>
+  {contextHolder}
+  <YourComponent />
+</App>
+```
+
+#### Message / Notification
+
+```typescript
+// ⚠️ 重要变更 - 必须使用 hook 方式
+
+// v4 (不推荐)
+import { message } from 'antd';
+message.success('Success');
+
+// v6 (推荐)
+import { message, App } from 'antd';
+
+const MyComponent = () => {
+  const { message } = App.useApp();
+
+  const showMessage = () => {
+    message.success('Success');
+  };
+
+  return <Button onClick={showMessage}>Show</Button>;
+};
+
+// 在根组件包裹
+<App>
+  <MyComponent />
+</App>
+```
+
+#### Form (ProForm 也适用)
+
+```typescript
+// ⚠️ 部分 API 调整
+
+// v4
+<Form.Item
+  name="username"
+  rules={[{ required: true, message: 'Required' }]}
+>
+  <Input />
+</Form.Item>
+
+// v6 (基本兼容,但推荐使用新 API)
+<Form.Item
+  name="username"
+  rules={[{ required: true, message: 'Required' }]}
+>
+  <Input />
+</Form.Item>
+
+// v6 新特性: Form.useWatch
+const username = Form.useWatch('username', form);
+```
+
+#### Table / ProTable
+
+```typescript
+// ⚠️ 分页、筛选 API 有调整
+
+// v4
+<ProTable
+  pagination={{
+    defaultCurrent: 1,
+    defaultPageSize: 10,
+  }}
+  onChange={(pagination, filters, sorter) => {}}
+/>
+
+// v6 (基本兼容,但有新特性)
+<ProTable
+  pagination={{
+    defaultCurrent: 1,
+    defaultPageSize: 10,
+  }}
+  onChange={(pagination, filters, sorter, extra) => {
+    // extra.action: 'paginate' | 'sort' | 'filter'
+  }}
+/>
+```
+
+#### DatePicker
+
+```typescript
+// ⚠️ 重大变更: moment.js → dayjs
+
+// v4
+import moment from 'moment';
+<DatePicker defaultValue={moment()} />
+
+// v6
+import dayjs from 'dayjs';
+<DatePicker defaultValue={dayjs()} />
+
+// 需要全局替换所有 moment 引用为 dayjs
+```
+
+---
+
+## 🔧 常见问题和解决方案
+
+### 1. TypeScript 类型错误
+
+```typescript
+// 问题: Property 'visible' does not exist
+// 解决: 替换为 'open'
+<Modal visible={true} />  // ❌
+<Modal open={true} />     // ✅
+```
+
+### 2. 样式突然失效
+
+```typescript
+// 问题: 自定义 CSS 不生效
+// 解决: 检查类名是否变化,或使用 ConfigProvider
+.ant-btn-custom { ... }  // 可能失效
+// 改用 Design Token 或 classNames API
+```
+
+### 3. 静态方法调用报错
+
+```typescript
+// 问题: message.success() 不显示
+// 解决: 必须包裹 <App> 组件
+<App>
+  <YourComponent />
+</App>
+```
+
+### 4. less 变量失效
+
+```typescript
+// 问题: @primary-color 不起作用
+// 解决: 迁移到 ConfigProvider token
+// v4
+@primary-color: #1890ff;
+
+// v6
+<ConfigProvider theme={{ token: { colorPrimary: '#1890ff' } }}>
+```
+
+---
+
+## 📝 迁移日志模板
+
+在每次迁移组件后,建议记录日志:
+
+```markdown
+### 2026-02-14 - 迁移 ThemeSelect 组件
+
+**迁移内容:**
+
+- [x] 从 v4 CSS 方式改为 v6 ConfigProvider
+- [x] 新增 Design Token 配置
+- [x] 支持亮色/暗黑主题切换
+
+**遇到的问题:**
+
+1. 问题描述...
+   解决方案: ...
+
+**测试结果:**
+
+- [x] 功能正常
+- [x] 样式正确
+- [ ] 待补充单元测试
+
+**备注:**
+暗黑模式性能优于 v4,切换无闪烁
+```
+
+---
+
+## 🎯 下一步行动
+
+### 立即开始
+
+1. ✅ 已创建本 checklist
+2. ⏭️ 运行辅助脚本扫描代码
+3. ⏭️ 迁移 `components/general/*` 第一批组件
+4. ⏭️ 重写 `ThemeSelect.tsx` 和主题系统
+
+### 需要帮助时
+
+- 🔍 搜索本文档关键词
+- 📖 参考 [Ant Design v6 官方文档](https://ant.design/docs/react/migration-v5)
+- 💬 询问 AI 助手具体组件迁移方法
+
+---
+
+## 📌 重要提醒
+
+1. **不要一次性迁移所有组件** - 逐个模块验证
+2. **每迁移一个组件就测试** - 避免问题累积
+3. **保持 v4 代码可运行** - 方便对比和回退
+4. **记录所有自定义修改** - 便于后续维护
+5. **主题系统优先迁移** - 它影响所有组件样式
+
+---
+
+**祝迁移顺利!🚀**
+
+有任何问题随时在本文件顶部的"迁移日志"区域记录,或咨询 AI 助手。

+ 372 - 0
dashboard-v6/migration-tools/replace-static-import.cjs

@@ -0,0 +1,372 @@
+#!/usr/bin/env node
+
+/**
+ * 静态方法 Import 替换工具
+ * 
+ * 功能:将 antd 静态方法的 import 替换为全局单例
+ * 使用方法:node replace-static-import.js <目标目录> [--dry-run] [--backup]
+ * 
+ * 替换规则:
+ * - import { message } from 'antd' → import { globalMessage as message } from '@/utils/antd-global'
+ * - import { notification } from 'antd' → import { globalNotification as notification } from '@/utils/antd-global'
+ * - import { Modal } from 'antd' → import { globalModal as Modal } from '@/utils/antd-global'
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+// 统计信息
+const stats = {
+  totalFiles: 0,
+  scannedFiles: 0,
+  modifiedFiles: 0,
+  replacements: {
+    message: 0,
+    notification: 0,
+    Modal: 0,
+  },
+  errors: [],
+};
+
+// 替换详情
+const replacementDetails = [];
+
+/**
+ * 解析 import 语句,提取导入的模块
+ */
+function parseImports(content) {
+  // 匹配 import { ... } from 'antd'
+  const importRegex = /import\s+{([^}]+)}\s+from\s+['"]antd['"]/g;
+  const imports = [];
+  
+  let match;
+  while ((match = importRegex.exec(content)) !== null) {
+    const fullMatch = match[0];
+    const importedItems = match[1]
+      .split(',')
+      .map(item => item.trim())
+      .filter(item => item.length > 0);
+    
+    imports.push({
+      fullMatch,
+      start: match.index,
+      end: match.index + fullMatch.length,
+      items: importedItems,
+    });
+  }
+  
+  return imports;
+}
+
+/**
+ * 生成新的 import 语句
+ */
+function generateNewImports(importedItems) {
+  const targetItems = ['message', 'notification', 'Modal'];
+  
+  // 分离需要替换的和不需要替换的
+  const needReplace = [];
+  const keepOriginal = [];
+  
+  importedItems.forEach(item => {
+    // 处理 as 别名的情况: message as msg
+    const baseItem = item.split(' as ')[0].trim();
+    
+    if (targetItems.includes(baseItem)) {
+      needReplace.push(item);
+    } else {
+      keepOriginal.push(item);
+    }
+  });
+  
+  const newImports = [];
+  
+  // 如果有不需要替换的,保留原 import
+  if (keepOriginal.length > 0) {
+    newImports.push(`import { ${keepOriginal.join(', ')} } from 'antd'`);
+  }
+  
+  // 添加全局单例的 import
+  if (needReplace.length > 0) {
+    const globalImports = needReplace.map(item => {
+      const parts = item.split(' as ');
+      const baseItem = parts[0].trim();
+      const alias = parts[1] ? parts[1].trim() : baseItem;
+      
+      const mapping = {
+        'message': 'globalMessage',
+        'notification': 'globalNotification',
+        'Modal': 'globalModal',
+      };
+      
+      return `${mapping[baseItem]} as ${alias}`;
+    });
+    
+    newImports.push(`import { ${globalImports.join(', ')} } from '@/utils/antd-global'`);
+  }
+  
+  return {
+    newImports,
+    needReplace: needReplace.map(item => item.split(' as ')[0].trim()),
+  };
+}
+
+/**
+ * 处理单个文件
+ */
+function processFile(filePath, options) {
+  let content = fs.readFileSync(filePath, 'utf-8');
+  const originalContent = content;
+  
+  // 解析 import
+  const imports = parseImports(content);
+  
+  if (imports.length === 0) {
+    return; // 没有 antd import,跳过
+  }
+  
+  let modified = false;
+  const fileReplacements = {
+    message: false,
+    notification: false,
+    Modal: false,
+  };
+  
+  // 从后往前替换,避免索引变化
+  imports.reverse().forEach(importInfo => {
+    const { fullMatch, start, end, items } = importInfo;
+    
+    const { newImports, needReplace } = generateNewImports(items);
+    
+    if (needReplace.length > 0) {
+      // 记录替换项
+      needReplace.forEach(item => {
+        fileReplacements[item] = true;
+        stats.replacements[item]++;
+      });
+      
+      // 替换 import 语句
+      const before = content.substring(0, start);
+      const after = content.substring(end);
+      content = before + newImports.join('\n') + after;
+      
+      modified = true;
+    }
+  });
+  
+  if (modified) {
+    stats.modifiedFiles++;
+    
+    replacementDetails.push({
+      file: filePath,
+      replacements: fileReplacements,
+    });
+    
+    if (options.dryRun) {
+      console.log(`\n📝 [DRY RUN] 将修改: ${filePath}`);
+      if (fileReplacements.message) console.log('   ✓ message → globalMessage');
+      if (fileReplacements.notification) console.log('   ✓ notification → globalNotification');
+      if (fileReplacements.Modal) console.log('   ✓ Modal → globalModal');
+    } else {
+      // 备份
+      if (options.backup) {
+        fs.writeFileSync(filePath + '.bak', originalContent);
+      }
+      
+      // 写入
+      try {
+        fs.writeFileSync(filePath, content, 'utf-8');
+        console.log(`✅ 已修改: ${filePath}`);
+        if (fileReplacements.message) console.log('   ✓ message → globalMessage');
+        if (fileReplacements.notification) console.log('   ✓ notification → globalNotification');
+        if (fileReplacements.Modal) console.log('   ✓ Modal → globalModal');
+      } catch (error) {
+        stats.errors.push({ file: filePath, error: error.message });
+        console.error(`❌ 修改失败: ${filePath}`);
+        console.error(`   错误: ${error.message}`);
+      }
+    }
+  }
+}
+
+/**
+ * 递归扫描目录
+ */
+function scanDirectory(dir, options) {
+  const files = fs.readdirSync(dir);
+  
+  files.forEach(file => {
+    const filePath = path.join(dir, file);
+    const stat = fs.statSync(filePath);
+    
+    if (stat.isDirectory()) {
+      if (!['node_modules', '.git', 'build', 'dist', '.next'].includes(file)) {
+        scanDirectory(filePath, options);
+      }
+    } else if (stat.isFile()) {
+      if (/\.(tsx?|jsx?)$/.test(file)) {
+        stats.totalFiles++;
+        stats.scannedFiles++;
+        processFile(filePath, options);
+      }
+    }
+  });
+}
+
+/**
+ * 生成报告
+ */
+function generateReport(options) {
+  console.log('\n' + '='.repeat(80));
+  console.log('📊 静态方法 Import 替换报告');
+  if (options.dryRun) {
+    console.log('   [DRY RUN 模式 - 未实际修改文件]');
+  }
+  console.log('='.repeat(80) + '\n');
+  
+  console.log('📁 文件统计:');
+  console.log(`   扫描文件总数: ${stats.totalFiles}`);
+  console.log(`   修改文件数: ${stats.modifiedFiles}\n`);
+  
+  console.log('📋 替换统计:');
+  console.log(`   message: ${stats.replacements.message} 处`);
+  console.log(`   notification: ${stats.replacements.notification} 处`);
+  console.log(`   Modal: ${stats.replacements.Modal} 处`);
+  console.log(`   总计: ${Object.values(stats.replacements).reduce((a, b) => a + b, 0)} 处\n`);
+  
+  if (stats.errors.length > 0) {
+    console.log('❌ 错误列表:');
+    stats.errors.forEach(({ file, error }) => {
+      console.log(`   ${file}`);
+      console.log(`      ${error}`);
+    });
+    console.log('');
+  }
+  
+  console.log('='.repeat(80));
+  
+  if (options.dryRun) {
+    console.log('💡 提示: 这是 DRY RUN 模式,文件未被实际修改');
+    console.log('   如果确认无误,请去掉 --dry-run 参数重新运行');
+  } else {
+    console.log('✅ 替换完成!');
+    if (options.backup) {
+      console.log('   原文件已备份为 .bak 文件');
+    }
+    console.log('   建议使用 git diff 检查改动');
+  }
+  
+  console.log('='.repeat(80) + '\n');
+  
+  // 重要提醒
+  if (stats.modifiedFiles > 0) {
+    console.log('⚠️ 重要提醒:');
+    console.log('   1. 确保已创建 src/utils/antd-global.ts 文件');
+    console.log('   2. 确保 AppProvider 已初始化全局实例');
+    console.log('   3. 确保根组件包裹了 <App> 组件');
+    console.log('   4. 运行项目测试所有 message/notification/Modal 功能');
+    console.log('   5. 参考 static-methods-migration.md 了解详细说明\n');
+    
+    console.log('📝 下一步:');
+    console.log('   1. 检查代码改动: git diff');
+    console.log('   2. 运行项目: npm start');
+    console.log('   3. 测试静态方法是否正常工作');
+    console.log('   4. 如有问题,参考 static-methods-migration.md\n');
+  }
+}
+
+/**
+ * 生成 antd-global.ts 模板(如果文件不存在)
+ */
+function checkAntdGlobalFile(projectRoot) {
+  const targetPath = path.join(projectRoot, 'src/utils/antd-global.ts');
+  
+  if (fs.existsSync(targetPath)) {
+    console.log(`✅ 已存在: ${targetPath}\n`);
+    return;
+  }
+  
+  console.log(`\n⚠️ 警告: 未找到 src/utils/antd-global.ts 文件`);
+  console.log('   这是全局单例工具的核心文件,必须先创建!\n');
+  console.log('建议操作:');
+  console.log('   1. 运行: node migration-tools/component-template.js --global-utils ./src/utils');
+  console.log('   或');
+  console.log('   2. 参考 static-methods-migration.md 手动创建\n');
+}
+
+/**
+ * 主函数
+ */
+function main() {
+  const args = process.argv.slice(2);
+  
+  if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
+    console.log(`
+静态方法 Import 替换工具
+
+功能:
+  自动将 message/notification/Modal 的 import 替换为全局单例
+
+用法:
+  node replace-static-import.js <目标目录> [选项]
+
+选项:
+  --dry-run   只显示将要修改的内容,不实际修改文件
+  --backup    修改前备份原文件(.bak)
+  --help, -h  显示帮助信息
+
+示例:
+  # 预览将要修改的内容
+  node replace-static-import.js ./src --dry-run
+
+  # 执行替换并备份
+  node replace-static-import.js ./src --backup
+
+替换规则:
+  import { message } from 'antd'
+  → import { globalMessage as message } from '@/utils/antd-global'
+
+  import { notification } from 'antd'
+  → import { globalNotification as notification } from '@/utils/antd-global'
+
+  import { Modal } from 'antd'
+  → import { globalModal as Modal } from '@/utils/antd-global'
+
+注意事项:
+  1. 必须先创建 src/utils/antd-global.ts 文件
+  2. 必须在 AppProvider 中初始化全局实例
+  3. 参考 static-methods-migration.md 了解详细说明
+    `);
+    process.exit(0);
+  }
+  
+  const targetDir = args[0];
+  const options = {
+    dryRun: args.includes('--dry-run'),
+    backup: args.includes('--backup'),
+  };
+  
+  if (!fs.existsSync(targetDir)) {
+    console.error(`❌ 目录不存在: ${targetDir}`);
+    process.exit(1);
+  }
+  
+  console.log(`\n🔧 开始处理目录: ${targetDir}`);
+  if (options.dryRun) {
+    console.log('   [DRY RUN 模式]');
+  }
+  if (options.backup) {
+    console.log('   [备份模式]');
+  }
+  
+  // 检查 antd-global.ts 文件
+  const projectRoot = path.resolve(targetDir, '..');
+  checkAntdGlobalFile(projectRoot);
+  
+  console.log('');
+  
+  scanDirectory(targetDir, options);
+  generateReport(options);
+}
+
+main();

+ 331 - 0
dashboard-v6/migration-tools/scan-api-diff.cjs

@@ -0,0 +1,331 @@
+#!/usr/bin/env node
+
+/**
+ * Ant Design v4 → v6 组件 API 差异扫描器
+ * 
+ * 功能:扫描代码中使用的 antd v4 API,标记需要修改的地方
+ * 使用方法:node scan-api-diff.js <目标目录路径>
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+// v4 → v6 需要修改的 API 模式
+const patterns = [
+  // Modal, Drawer, Popover 等组件的 visible 改为 open
+  {
+    pattern: /visible\s*=\s*\{/g,
+    suggestion: '❌ 使用了 visible 属性',
+    fix: '将 visible 改为 open',
+    severity: 'high',
+    component: 'Modal/Drawer/Popover/Tooltip/Dropdown'
+  },
+  {
+    pattern: /onVisibleChange\s*=\s*\{/g,
+    suggestion: '❌ 使用了 onVisibleChange 属性',
+    fix: '将 onVisibleChange 改为 onOpenChange',
+    severity: 'high',
+    component: 'Modal/Drawer/Popover/Tooltip/Dropdown'
+  },
+  
+  // 静态方法调用(message, notification, Modal)
+  {
+    pattern: /message\.(success|error|info|warning|loading)\(/g,
+    suggestion: '⚠️ 直接调用静态方法',
+    fix: '改用 App.useApp() hook 或确保有 <App> 包裹',
+    severity: 'medium',
+    component: 'message'
+  },
+  {
+    pattern: /notification\.(success|error|info|warning|open)\(/g,
+    suggestion: '⚠️ 直接调用静态方法',
+    fix: '改用 App.useApp() hook 或确保有 <App> 包裹',
+    severity: 'medium',
+    component: 'notification'
+  },
+  {
+    pattern: /Modal\.(confirm|info|success|error|warning)\(/g,
+    suggestion: '⚠️ 直接调用静态方法',
+    fix: '改用 Modal.useModal() hook 或确保有 <App> 包裹',
+    severity: 'medium',
+    component: 'Modal'
+  },
+  
+  // moment.js 使用
+  {
+    pattern: /import\s+.*moment.*from\s+['"]moment['"]/g,
+    suggestion: '❌ 使用了 moment.js',
+    fix: '替换为 dayjs',
+    severity: 'high',
+    component: 'DatePicker/TimePicker/Calendar'
+  },
+  {
+    pattern: /moment\(/g,
+    suggestion: '❌ 调用了 moment()',
+    fix: '替换为 dayjs()',
+    severity: 'high',
+    component: 'DatePicker/TimePicker/Calendar'
+  },
+  
+  // Form 相关(部分需要调整)
+  {
+    pattern: /getFieldDecorator/g,
+    suggestion: '❌ 使用了旧版 Form API',
+    fix: 'v4+ 已废弃,使用 Form.Item name 属性',
+    severity: 'critical',
+    component: 'Form'
+  },
+  
+  // Dropdown overlay 改为 menu
+  {
+    pattern: /overlay\s*=\s*\{/g,
+    suggestion: '⚠️ Dropdown 使用了 overlay',
+    fix: '将 overlay 改为 menu (返回 Menu 组件)',
+    severity: 'medium',
+    component: 'Dropdown'
+  },
+  
+  // PageHeader 已移除
+  {
+    pattern: /<PageHeader/g,
+    suggestion: '❌ PageHeader 组件已移除',
+    fix: '使用 @ant-design/pro-components 的 PageContainer 或自定义',
+    severity: 'high',
+    component: 'PageHeader'
+  },
+  
+  // Comment 组件已移除
+  {
+    pattern: /<Comment/g,
+    suggestion: '❌ Comment 组件已移除',
+    fix: '使用 @ant-design/compatible 或自定义',
+    severity: 'medium',
+    component: 'Comment'
+  },
+  
+  // BackTop 改为 FloatButton.BackTop
+  {
+    pattern: /<BackTop/g,
+    suggestion: '⚠️ BackTop 组件 API 有变更',
+    fix: '使用 FloatButton.BackTop',
+    severity: 'low',
+    component: 'BackTop'
+  },
+  
+  // Less 变量
+  {
+    pattern: /@import\s+['"]~antd\/.*\.less['"]/g,
+    suggestion: '⚠️ 引入了 antd less 文件',
+    fix: '改用 ConfigProvider theme 配置',
+    severity: 'medium',
+    component: 'Theme'
+  },
+  {
+    pattern: /@primary-color|@link-color|@success-color|@warning-color|@error-color/g,
+    suggestion: '⚠️ 使用了 less 变量',
+    fix: '改用 Design Token',
+    severity: 'medium',
+    component: 'Theme'
+  },
+];
+
+// 扫描结果统计
+const stats = {
+  totalFiles: 0,
+  scannedFiles: 0,
+  filesWithIssues: 0,
+  issues: {
+    critical: 0,
+    high: 0,
+    medium: 0,
+    low: 0
+  }
+};
+
+// 问题汇总
+const issuesByFile = new Map();
+
+// 递归扫描目录
+function scanDirectory(dir, baseDir) {
+  const files = fs.readdirSync(dir);
+  
+  files.forEach(file => {
+    const filePath = path.join(dir, file);
+    const stat = fs.statSync(filePath);
+    
+    if (stat.isDirectory()) {
+      // 跳过 node_modules 等目录
+      if (!['node_modules', '.git', 'build', 'dist', '.next'].includes(file)) {
+        scanDirectory(filePath, baseDir);
+      }
+    } else if (stat.isFile()) {
+      // 只扫描 tsx, ts, jsx, js 文件
+      if (/\.(tsx?|jsx?)$/.test(file)) {
+        stats.totalFiles++;
+        scanFile(filePath, baseDir);
+      }
+    }
+  });
+}
+
+// 扫描单个文件
+function scanFile(filePath, baseDir) {
+  const content = fs.readFileSync(filePath, 'utf-8');
+  const relativePath = path.relative(baseDir, filePath);
+  
+  stats.scannedFiles++;
+  
+  const fileIssues = [];
+  
+  patterns.forEach(({ pattern, suggestion, fix, severity, component }) => {
+    const matches = content.matchAll(pattern);
+    
+    for (const match of matches) {
+      // 计算行号
+      const lines = content.substring(0, match.index).split('\n');
+      const lineNumber = lines.length;
+      const columnNumber = lines[lines.length - 1].length + 1;
+      
+      fileIssues.push({
+        line: lineNumber,
+        column: columnNumber,
+        code: match[0],
+        suggestion,
+        fix,
+        severity,
+        component
+      });
+      
+      stats.issues[severity]++;
+    }
+  });
+  
+  if (fileIssues.length > 0) {
+    stats.filesWithIssues++;
+    issuesByFile.set(relativePath, fileIssues);
+  }
+}
+
+// 生成报告
+function generateReport() {
+  console.log('\n' + '='.repeat(80));
+  console.log('📊 Ant Design v4 → v6 API 差异扫描报告');
+  console.log('='.repeat(80) + '\n');
+  
+  console.log('📁 扫描统计:');
+  console.log(`   总文件数: ${stats.totalFiles}`);
+  console.log(`   已扫描: ${stats.scannedFiles}`);
+  console.log(`   有问题的文件: ${stats.filesWithIssues}\n`);
+  
+  console.log('🚨 问题统计:');
+  console.log(`   🔴 Critical: ${stats.issues.critical} 个`);
+  console.log(`   🟠 High: ${stats.issues.high} 个`);
+  console.log(`   🟡 Medium: ${stats.issues.medium} 个`);
+  console.log(`   🟢 Low: ${stats.issues.low} 个\n`);
+  
+  console.log('='.repeat(80) + '\n');
+  
+  if (issuesByFile.size === 0) {
+    console.log('✅ 未发现明显的兼容性问题!\n');
+    return;
+  }
+  
+  console.log('📋 详细问题列表:\n');
+  
+  // 按严重程度排序
+  const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
+  const sortedFiles = Array.from(issuesByFile.entries()).sort((a, b) => {
+    const minSeverityA = Math.min(...a[1].map(i => severityOrder[i.severity]));
+    const minSeverityB = Math.min(...b[1].map(i => severityOrder[i.severity]));
+    return minSeverityA - minSeverityB;
+  });
+  
+  sortedFiles.forEach(([file, issues]) => {
+    const severityIcon = {
+      critical: '🔴',
+      high: '🟠',
+      medium: '🟡',
+      low: '🟢'
+    };
+    
+    console.log(`📄 ${file}`);
+    console.log('   ' + '-'.repeat(76));
+    
+    issues.forEach(issue => {
+      console.log(`   ${severityIcon[issue.severity]} Line ${issue.line}:${issue.column} [${issue.component}]`);
+      console.log(`      ${issue.suggestion}`);
+      console.log(`      代码: ${issue.code}`);
+      console.log(`      💡 修复建议: ${issue.fix}`);
+      console.log('');
+    });
+  });
+  
+  console.log('='.repeat(80) + '\n');
+  
+  // 按组件分组统计
+  console.log('📊 按组件分类统计:\n');
+  const componentStats = new Map();
+  
+  issuesByFile.forEach(issues => {
+    issues.forEach(issue => {
+      const count = componentStats.get(issue.component) || 0;
+      componentStats.set(issue.component, count + 1);
+    });
+  });
+  
+  const sortedComponents = Array.from(componentStats.entries())
+    .sort((a, b) => b[1] - a[1]);
+  
+  sortedComponents.forEach(([component, count]) => {
+    console.log(`   ${component}: ${count} 个问题`);
+  });
+  
+  console.log('\n' + '='.repeat(80));
+  console.log('💡 建议:');
+  console.log('   1. 优先处理 🔴 Critical 和 🟠 High 级别的问题');
+  console.log('   2. 使用 import-replace.js 脚本批量替换简单的 API');
+  console.log('   3. 参考 migration-checklist.md 了解详细迁移步骤');
+  console.log('='.repeat(80) + '\n');
+}
+
+// 导出为 JSON 格式(可选)
+function exportJson(outputPath) {
+  const result = {
+    timestamp: new Date().toISOString(),
+    stats,
+    issues: Object.fromEntries(issuesByFile)
+  };
+  
+  fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
+  console.log(`\n📝 详细报告已导出到: ${outputPath}\n`);
+}
+
+// 主函数
+function main() {
+  const args = process.argv.slice(2);
+  
+  if (args.length === 0) {
+    console.log('用法: node scan-api-diff.js <目标目录路径> [--json=输出文件.json]');
+    console.log('示例: node scan-api-diff.js ../old-project/src --json=report.json');
+    process.exit(1);
+  }
+  
+  const targetDir = args[0];
+  const jsonOutput = args.find(arg => arg.startsWith('--json='))?.split('=')[1];
+  
+  if (!fs.existsSync(targetDir)) {
+    console.error(`❌ 目录不存在: ${targetDir}`);
+    process.exit(1);
+  }
+  
+  console.log(`\n🔍 开始扫描目录: ${targetDir}\n`);
+  
+  scanDirectory(targetDir, targetDir);
+  generateReport();
+  
+  if (jsonOutput) {
+    exportJson(jsonOutput);
+  }
+}
+
+main();

+ 585 - 0
dashboard-v6/migration-tools/static-methods-migration.md

@@ -0,0 +1,585 @@
+# 🚨 静态方法迁移专项指南
+
+## ⚠️ 重要警告
+
+你的项目大量使用了以下静态方法:
+- `message.success()`, `message.error()`, `message.warning()`, `message.info()`, `message.loading()`
+- `notification.success()`, `notification.error()`, `notification.warning()`, `notification.info()`, `notification.open()`
+- `Modal.confirm()`, `Modal.info()`, `Modal.success()`, `Modal.error()`, `Modal.warning()`
+
+**这是 v4 → v6 迁移中最重要的变更之一!**
+
+在 v6 中,这些静态方法**必须**在特定的上下文中使用,否则会出现以下问题:
+- ❌ 调用后没有任何反应
+- ❌ 控制台警告: `instance is null`
+- ❌ 主题样式不生效
+- ❌ 国际化不生效
+
+---
+
+## 📊 迁移方案对比
+
+### ❌ v4 方式(不推荐)
+
+```typescript
+import { message, notification, Modal } from 'antd';
+
+// 直接调用静态方法
+const handleClick = () => {
+  message.success('操作成功');
+  
+  notification.info({
+    message: '通知标题',
+    description: '这是通知内容',
+  });
+  
+  Modal.confirm({
+    title: '确认删除?',
+    onOk: () => { /* ... */ }
+  });
+};
+```
+
+**问题:** 在 v6 中这些方法需要 Context 才能正常工作!
+
+---
+
+### ✅ v6 方式 - 方案 1: 使用 App.useApp() Hook(推荐)
+
+```typescript
+import { App, Button } from 'antd';
+
+const MyComponent = () => {
+  const { message, notification, modal } = App.useApp();
+
+  const handleClick = () => {
+    message.success('操作成功');
+    
+    notification.info({
+      message: '通知标题',
+      description: '这是通知内容',
+    });
+    
+    modal.confirm({
+      title: '确认删除?',
+      onOk: () => { /* ... */ }
+    });
+  };
+
+  return <Button onClick={handleClick}>点击</Button>;
+};
+
+export default MyComponent;
+```
+
+**前提:** 必须在根组件包裹 `<App>` 组件:
+
+```typescript
+// src/index.tsx 或 App.tsx
+import { App } from 'antd';
+
+const Root = () => {
+  return (
+    <App>
+      <YourApp />
+    </App>
+  );
+};
+```
+
+---
+
+### ✅ v6 方式 - 方案 2: 使用独立 Hook(推荐)
+
+对于 message 和 notification,antd 还提供了独立的 hook:
+
+```typescript
+import { message, notification } from 'antd';
+
+const MyComponent = () => {
+  const [messageApi, messageContextHolder] = message.useMessage();
+  const [notificationApi, notificationContextHolder] = notification.useNotification();
+
+  const handleClick = () => {
+    messageApi.success('操作成功');
+    
+    notificationApi.info({
+      message: '通知标题',
+      description: '这是通知内容',
+    });
+  };
+
+  return (
+    <>
+      {messageContextHolder}
+      {notificationContextHolder}
+      <Button onClick={handleClick}>点击</Button>
+    </>
+  );
+};
+```
+
+**缺点:** 每个组件都要声明,比较繁琐。
+
+---
+
+### ✅ v6 方式 - 方案 3: 全局单例(兼容性方案)
+
+如果你的项目中有太多地方使用静态方法,一个一个改太麻烦,可以创建全局单例:
+
+```typescript
+// src/utils/antd-global.ts
+import { message, notification, Modal } from 'antd';
+import type { MessageInstance } from 'antd/es/message/interface';
+import type { NotificationInstance } from 'antd/es/notification/interface';
+import type { HookAPI } from 'antd/es/modal/useModal';
+
+let messageInstance: MessageInstance;
+let notificationInstance: NotificationInstance;
+let modalInstance: HookAPI;
+
+// 初始化实例
+export const setMessageInstance = (instance: MessageInstance) => {
+  messageInstance = instance;
+};
+
+export const setNotificationInstance = (instance: NotificationInstance) => {
+  notificationInstance = instance;
+};
+
+export const setModalInstance = (instance: HookAPI) => {
+  modalInstance = instance;
+};
+
+// 导出全局 message
+export const globalMessage = {
+  success: (content: string, duration?: number) => {
+    if (!messageInstance) {
+      console.error('Message instance not initialized');
+      return;
+    }
+    return messageInstance.success(content, duration);
+  },
+  error: (content: string, duration?: number) => {
+    if (!messageInstance) {
+      console.error('Message instance not initialized');
+      return;
+    }
+    return messageInstance.error(content, duration);
+  },
+  warning: (content: string, duration?: number) => {
+    if (!messageInstance) {
+      console.error('Message instance not initialized');
+      return;
+    }
+    return messageInstance.warning(content, duration);
+  },
+  info: (content: string, duration?: number) => {
+    if (!messageInstance) {
+      console.error('Message instance not initialized');
+      return;
+    }
+    return messageInstance.info(content, duration);
+  },
+  loading: (content: string, duration?: number) => {
+    if (!messageInstance) {
+      console.error('Message instance not initialized');
+      return;
+    }
+    return messageInstance.loading(content, duration);
+  },
+};
+
+// 导出全局 notification
+export const globalNotification = {
+  success: (config: any) => {
+    if (!notificationInstance) {
+      console.error('Notification instance not initialized');
+      return;
+    }
+    return notificationInstance.success(config);
+  },
+  error: (config: any) => {
+    if (!notificationInstance) {
+      console.error('Notification instance not initialized');
+      return;
+    }
+    return notificationInstance.error(config);
+  },
+  warning: (config: any) => {
+    if (!notificationInstance) {
+      console.error('Notification instance not initialized');
+      return;
+    }
+    return notificationInstance.warning(config);
+  },
+  info: (config: any) => {
+    if (!notificationInstance) {
+      console.error('Notification instance not initialized');
+      return;
+    }
+    return notificationInstance.info(config);
+  },
+  open: (config: any) => {
+    if (!notificationInstance) {
+      console.error('Notification instance not initialized');
+      return;
+    }
+    return notificationInstance.open(config);
+  },
+};
+
+// 导出全局 modal
+export const globalModal = {
+  confirm: (config: any) => {
+    if (!modalInstance) {
+      console.error('Modal instance not initialized');
+      return;
+    }
+    return modalInstance.confirm(config);
+  },
+  info: (config: any) => {
+    if (!modalInstance) {
+      console.error('Modal instance not initialized');
+      return;
+    }
+    return modalInstance.info(config);
+  },
+  success: (config: any) => {
+    if (!modalInstance) {
+      console.error('Modal instance not initialized');
+      return;
+    }
+    return modalInstance.success(config);
+  },
+  error: (config: any) => {
+    if (!modalInstance) {
+      console.error('Modal instance not initialized');
+      return;
+    }
+    return modalInstance.error(config);
+  },
+  warning: (config: any) => {
+    if (!modalInstance) {
+      console.error('Modal instance not initialized');
+      return;
+    }
+    return modalInstance.warning(config);
+  },
+};
+```
+
+在 AppProvider 中初始化:
+
+```typescript
+// src/AppProvider.tsx
+import React from 'react';
+import { ConfigProvider, App as AntdApp, theme } from 'antd';
+import { 
+  setMessageInstance, 
+  setNotificationInstance, 
+  setModalInstance 
+} from './utils/antd-global';
+
+const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const InitInstances = () => {
+    const { message, notification, modal } = AntdApp.useApp();
+    
+    React.useEffect(() => {
+      setMessageInstance(message);
+      setNotificationInstance(notification);
+      setModalInstance(modal);
+    }, [message, notification, modal]);
+    
+    return null;
+  };
+
+  return (
+    <ConfigProvider>
+      <AntdApp>
+        <InitInstances />
+        {children}
+      </AntdApp>
+    </ConfigProvider>
+  );
+};
+
+export default AppProvider;
+```
+
+在业务代码中使用:
+
+```typescript
+// 任意组件中
+import { globalMessage, globalNotification, globalModal } from '@/utils/antd-global';
+
+const handleClick = () => {
+  globalMessage.success('操作成功');
+  
+  globalNotification.info({
+    message: '通知标题',
+    description: '内容',
+  });
+  
+  globalModal.confirm({
+    title: '确认?',
+    onOk: () => {},
+  });
+};
+```
+
+**优点:**
+- ✅ 最小改动,只需替换 import
+- ✅ 适合代码量大、使用广泛的场景
+- ✅ 保持原有调用方式
+
+**缺点:**
+- ⚠️ 不是官方推荐的最佳实践
+- ⚠️ 需要额外维护全局单例代码
+
+---
+
+## 🎯 推荐的迁移策略
+
+根据你的项目规模(581 个文件),我**强烈推荐使用方案 3(全局单例)**,原因:
+
+1. **改动最小** - 只需替换 import 语句
+2. **风险最低** - 不需要修改每个组件的逻辑
+3. **可逐步优化** - 迁移完成后,可以逐步改为 `App.useApp()`
+
+### 迁移步骤
+
+#### Step 1: 创建全局单例工具
+
+```bash
+# 在你的新项目中创建
+touch src/utils/antd-global.ts
+```
+
+复制上面 **方案 3** 的代码到这个文件。
+
+#### Step 2: 更新 AppProvider
+
+修改你的 `src/theme/AppProvider.tsx`(已经由工具生成),添加实例初始化。
+
+#### Step 3: 批量替换 import 语句
+
+使用我提供的工具批量替换:
+
+```bash
+# 预览替换
+node migration-tools/replace-static-import.js ./src --dry-run
+
+# 执行替换
+node migration-tools/replace-static-import.js ./src --backup
+```
+
+替换规则:
+```typescript
+// 旧的 import
+import { message } from 'antd';
+
+// 新的 import
+import { globalMessage as message } from '@/utils/antd-global';
+```
+
+这样你的业务代码**几乎不需要修改**!
+
+---
+
+## 📝 各方案适用场景
+
+| 方案 | 适用场景 | 改动量 | 推荐度 |
+|------|---------|--------|--------|
+| 方案 1: App.useApp() | 新项目、小项目 | 大 | ⭐⭐⭐⭐⭐ (官方推荐) |
+| 方案 2: 独立 Hook | 少量使用 | 大 | ⭐⭐⭐ |
+| 方案 3: 全局单例 | 大项目、使用广泛 | 小 | ⭐⭐⭐⭐ (过渡方案) |
+
+**你的情况:**
+- ✅ 项目规模大(581 个文件)
+- ✅ 大量使用静态方法
+- ✅ 建议使用**方案 3**先快速迁移
+- ✅ 后续逐步优化为方案 1
+
+---
+
+## 🔧 迁移检查清单
+
+### Phase 1: 准备工作
+- [ ] 创建 `src/utils/antd-global.ts`
+- [ ] 更新 `AppProvider.tsx` 初始化实例
+- [ ] 确保根组件包裹了 `<App>`
+
+### Phase 2: 批量替换
+- [ ] 备份代码(使用 Git)
+- [ ] 运行替换脚本(--dry-run)
+- [ ] 检查预览结果
+- [ ] 执行实际替换(--backup)
+
+### Phase 3: 测试验证
+- [ ] 测试 message.success/error/warning/info/loading
+- [ ] 测试 notification.success/error/warning/info/open
+- [ ] 测试 Modal.confirm/info/success/error/warning
+- [ ] 检查主题样式是否正确
+- [ ] 检查国际化是否正常
+
+### Phase 4: 特殊情况处理
+- [ ] 检查异步场景(setTimeout 等)
+- [ ] 检查工具类函数中的调用
+- [ ] 检查 Redux action/saga 中的调用
+- [ ] 检查 request 拦截器中的调用
+
+---
+
+## ⚠️ 常见问题和解决方案
+
+### 问题 1: 调用静态方法没有反应
+
+**原因:** 实例未初始化或 `<App>` 组件未包裹。
+
+**解决:**
+```typescript
+// 检查 src/index.tsx
+import { App } from 'antd';
+
+ReactDOM.render(
+  <App>  {/* 必须包裹 */}
+    <YourApp />
+  </App>,
+  document.getElementById('root')
+);
+```
+
+### 问题 2: 在工具函数中使用静态方法报错
+
+**原因:** 工具函数不在 React 组件中,无法使用 hook。
+
+**解决:** 使用方案 3 的全局单例:
+
+```typescript
+// src/utils/api.ts
+import { globalMessage } from './antd-global';
+
+export const request = async (url: string) => {
+  try {
+    const response = await fetch(url);
+    globalMessage.success('请求成功');
+    return response;
+  } catch (error) {
+    globalMessage.error('请求失败');
+  }
+};
+```
+
+### 问题 3: 在 Redux Saga 中使用静态方法
+
+**解决:** 同样使用全局单例:
+
+```typescript
+// src/store/sagas/user.saga.ts
+import { globalMessage } from '@/utils/antd-global';
+import { call, put } from 'redux-saga/effects';
+
+function* loginSaga(action: any) {
+  try {
+    const result = yield call(loginApi, action.payload);
+    globalMessage.success('登录成功');
+    yield put({ type: 'LOGIN_SUCCESS', payload: result });
+  } catch (error) {
+    globalMessage.error('登录失败');
+  }
+}
+```
+
+### 问题 4: 在 setTimeout 中使用静态方法失效
+
+**原因:** 异步回调中 Context 可能失效。
+
+**解决:** 使用全局单例方案不受影响:
+
+```typescript
+const handleClick = () => {
+  setTimeout(() => {
+    globalMessage.success('延迟消息');  // ✅ 正常工作
+  }, 1000);
+};
+```
+
+---
+
+## 🚀 快速迁移脚本
+
+我已经为你准备了一个专门的替换脚本 `replace-static-import.js`,它会自动:
+
+1. 检测所有使用 `message`, `notification`, `Modal` 的文件
+2. 替换 import 语句为全局单例
+3. 保持业务代码不变
+
+使用方法:
+
+```bash
+# 预览
+node migration-tools/replace-static-import.js ./src --dry-run
+
+# 执行
+node migration-tools/replace-static-import.js ./src --backup
+```
+
+---
+
+## 📊 迁移工作量评估
+
+基于你的项目:
+
+| 操作 | 预计耗时 |
+|------|---------|
+| 创建全局单例工具 | 30 分钟 |
+| 更新 AppProvider | 15 分钟 |
+| 批量替换 import | 1 小时 |
+| 测试验证 | 2-3 小时 |
+| 处理特殊情况 | 1-2 小时 |
+| **总计** | **5-7 小时** |
+
+如果手动一个一个改为 `App.useApp()`,预计需要 **20-30 小时**!
+
+---
+
+## 💡 最佳实践建议
+
+### 短期(迁移阶段)
+1. ✅ 使用全局单例快速迁移
+2. ✅ 确保所有功能正常
+3. ✅ 重点测试异步场景
+
+### 中期(优化阶段)
+1. ✅ 对于新开发的组件,使用 `App.useApp()`
+2. ✅ 逐步重构核心组件为标准方式
+3. ✅ 保持两种方式共存
+
+### 长期(重构阶段)
+1. ✅ 所有组件改用 `App.useApp()`
+2. ✅ 移除全局单例代码
+3. ✅ 完全符合 v6 最佳实践
+
+---
+
+## 🎯 下一步行动
+
+1. **立即执行:**
+   - [ ] 创建 `antd-global.ts` 文件
+   - [ ] 运行我提供的替换脚本
+   - [ ] 测试核心功能
+
+2. **稍后执行:**
+   - [ ] 处理特殊场景(工具函数、Saga 等)
+   - [ ] 添加单元测试
+   - [ ] 更新团队文档
+
+3. **长期计划:**
+   - [ ] 新组件使用 `App.useApp()`
+   - [ ] 逐步重构旧组件
+   - [ ] 最终移除全局单例
+
+---
+
+**记住:迁移不是一蹴而就的,先让代码跑起来,再逐步优化!** 🚀

BIN
dashboard-v6/public/favicon.ico


BIN
dashboard-v6/public/logo192.png


BIN
dashboard-v6/public/logo512.png


+ 25 - 0
dashboard-v6/public/manifest.json

@@ -0,0 +1,25 @@
+{
+  "short_name": "wikipali",
+  "name": "wikipali",
+  "icons": [
+    {
+      "src": "favicon.ico",
+      "sizes": "64x64 32x32 24x24 16x16",
+      "type": "image/x-icon"
+    },
+    {
+      "src": "logo192.png",
+      "type": "image/png",
+      "sizes": "192x192"
+    },
+    {
+      "src": "logo512.png",
+      "type": "image/png",
+      "sizes": "512x512"
+    }
+  ],
+  "start_url": ".",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}

+ 3 - 0
dashboard-v6/public/robots.txt

@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:

BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-Black.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-BlackItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-Bold.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-BoldItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-ExtraBold.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-ExtraBoldItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-ExtraLight.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-ExtraLightItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-Italic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-Light.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-LightItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-Medium.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-MediumItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-Regular.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-SemiBold.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-SemiBoldItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-Thin.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSans-ThinItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSans/NotoSansTaiTham-Regular.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Black.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-BlackItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Bold.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-BoldItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ExtraBold.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ExtraBoldItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ExtraLight.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ExtraLightItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Italic.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Light.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-LightItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Medium.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-MediumItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Regular.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-SemiBold.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-SemiBoldItalic.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-Thin.ttf


BIN
dashboard-v6/src/assets/font/NotoSerif/NotoSerif-ThinItalic.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Black.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Bold.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-ExtraBold.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-ExtraLight.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Light.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Medium.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Regular.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-SemiBold.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/NotoSansMyanmar-Thin.ttf


+ 93 - 0
dashboard-v6/src/assets/font/Noto_Sans_Myanmar/OFL.txt

@@ -0,0 +1,93 @@
+Copyright 2012 Google Inc. All Rights Reserved.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded, 
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.

BIN
dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-Bold.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-Medium.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-Regular.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-SemiBold.ttf


BIN
dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/NotoSansTaiTham-VariableFont_wght.ttf


+ 93 - 0
dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/OFL.txt

@@ -0,0 +1,93 @@
+Copyright 2012 Google Inc. All Rights Reserved.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded, 
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.

+ 66 - 0
dashboard-v6/src/assets/font/Noto_Sans_Tai_Tham/README.txt

@@ -0,0 +1,66 @@
+Noto Sans Tai Tham Variable Font
+================================
+
+This download contains Noto Sans Tai Tham as both a variable font and static fonts.
+
+Noto Sans Tai Tham is a variable font with this axis:
+  wght
+
+This means all the styles are contained in a single file:
+  NotoSansTaiTham-VariableFont_wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Noto Sans Tai Tham:
+  static/NotoSansTaiTham-Regular.ttf
+  static/NotoSansTaiTham-Medium.ttf
+  static/NotoSansTaiTham-SemiBold.ttf
+  static/NotoSansTaiTham-Bold.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+  https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+  https://variablefonts.typenetwork.com
+  https://medium.com/variable-fonts
+
+In desktop apps
+
+  https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+  https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+  https://developers.google.com/fonts/docs/getting_started
+  https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+  https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+  MacOS: https://support.apple.com/en-us/HT201749
+  Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+  Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+  https://developers.google.com/fonts/docs/android
+  https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (OFL.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them freely in your products & projects - print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.

BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Black.ttf


BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Bold.ttf


BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-ExtraBold.ttf


BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-ExtraLight.ttf


BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Light.ttf


BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Medium.ttf


BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Regular.ttf


BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-SemiBold.ttf


BIN
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/NotoSerifMyanmar-Thin.ttf


+ 93 - 0
dashboard-v6/src/assets/font/Noto_Serif_Myanmar/OFL.txt

@@ -0,0 +1,93 @@
+Copyright 2012 Google Inc. All Rights Reserved.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded, 
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.

+ 457 - 0
dashboard-v6/src/assets/font/main.css

@@ -0,0 +1,457 @@
+/*@import url(//fonts.googleapis.com/earlyaccess/notosanstc.css);*/
+/*@import url(//fonts.googleapis.com/earlyaccess/notosanssc.css);*/
+
+/*Pāli Roma*/
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: normal;
+  font-weight: 100;
+  src: local("Noto Sans Thin"),
+    url(./NotoSans/NotoSans-Thin.ttf) format("truetype");
+  font-display: fallback;
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: normal;
+  font-weight: 300;
+  src: local("Noto Sans Light"),
+    url(./NotoSans/NotoSans-Light.ttf) format("truetype");
+  font-display: fallback;
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: normal;
+  font-weight: 400;
+  src: local("Noto Sans Regular"),
+    url(./NotoSans/NotoSans-Regular.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: normal;
+  font-weight: 500;
+  src: local("Noto Sans Medium"),
+    url(./NotoSans/NotoSans-Medium.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: normal;
+  font-weight: 700;
+  src: local("Noto Sans Bold"),
+    url(./NotoSans/NotoSans-Bold.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: normal;
+  font-weight: 900;
+  src: local("Noto Sans Black"),
+    url(./NotoSans/NotoSans-Black.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: italic;
+  font-weight: 100;
+  src: local("Noto Sans Thin Italic"),
+    url(./NotoSans/NotoSans-ThinItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: italic;
+  font-weight: 300;
+  src: local("Noto Sans Light Italic"),
+    url(./NotoSans/NotoSans-LightItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: italic;
+  font-weight: 400;
+  src: local("Noto Sans Italic"),
+    url(./NotoSans/NotoSans-Italic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: italic;
+  font-weight: 500;
+  src: local("Noto Sans Medium Italic"),
+    url(./NotoSans/NotoSans-MediumItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: italic;
+  font-weight: 700;
+  src: local("Noto Sans Bold Italic"),
+    url(./NotoSans/NotoSans-BoldItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans";
+  font-style: italic;
+  font-weight: 900;
+  src: local("Noto Sans Black Italic"),
+    url(./NotoSans/NotoSans-BlackItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: normal;
+  font-weight: 100;
+  src: local("Noto Serif Thin"),
+    url(./NotoSerif/NotoSerif-Thin.ttf) format("truetype");
+  font-display: fallback;
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: normal;
+  font-weight: 300;
+  src: local("Noto Serif Light"),
+    url(./NotoSerif/NotoSerif-Light.ttf) format("truetype");
+  font-display: fallback;
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: normal;
+  font-weight: 400;
+  src: local("Noto Serif Regular"),
+    url(./NotoSerif/NotoSerif-Regular.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: normal;
+  font-weight: 500;
+  src: local("Noto Serif Medium"),
+    url(./NotoSerif/NotoSerif-Medium.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: normal;
+  font-weight: 600;
+  src: local("Noto Serif SemiBold"),
+    url(./NotoSerif/NotoSerif-SemiBold.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: normal;
+  font-weight: 700;
+  src: local("Noto Serif Bold"),
+    url(./NotoSerif/NotoSerif-Bold.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: normal;
+  font-weight: 900;
+  src: local("Noto Serif Black"),
+    url(./NotoSerif/NotoSerif-Black.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: italic;
+  font-weight: 100;
+  src: local("Noto Serif Thin Italic"),
+    url(./NotoSerif/NotoSerif-ThinItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: italic;
+  font-weight: 300;
+  src: local("Noto Serif Light Italic"),
+    url(./NotoSerif/NotoSerif-LightItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: italic;
+  font-weight: 400;
+  src: local("Noto Serif Italic"),
+    url(./NotoSerif/NotoSerif-Italic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: italic;
+  font-weight: 500;
+  src: local("Noto Serif Medium Italic"),
+    url(./NotoSerif/NotoSerif-MediumItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: italic;
+  font-weight: 600;
+  src: local("Noto Serif SemiBold Italic"),
+    url(./NotoSerif/NotoSerif-SemiBoldItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: italic;
+  font-weight: 700;
+  src: local("Noto Serif Bold Italic"),
+    url(./NotoSerif/NotoSerif-BoldItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Serif";
+  font-style: italic;
+  font-weight: 900;
+  src: local("Noto Serif Black Italic"),
+    url(./NotoSerif/NotoSerif-BlackItalic.ttf) format("truetype");
+  font-display: fallback;
+}
+
+/*Noto Sans Myanmar*/
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 100;
+  src: local("Noto Sans Myanmar Thin"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-Thin.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 200;
+  src: local("Noto Sans Myanmar ExtraLight"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-ExtraLight.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 300;
+  src: local("Noto Sans Myanmar Light"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-Light.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 400;
+  src: local("Noto Sans Myanmar Regular"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-Regular.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 500;
+  src: local("Noto Sans Myanmar Medium"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-Medium.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 600;
+  src: local("Noto Sans Myanmar SemiBold"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-SemiBold.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 700;
+  src: local("Noto Sans Myanmar Bold"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-Bold.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 800;
+  src: local("Noto Sans Myanmar ExtraBold"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-ExtraBold.ttf) format("truetype");
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans Myanmar";
+  font-style: normal;
+  font-weight: 900;
+  src: local("Noto Sans Myanmar Black"),
+    url(./Noto_Sans_Myanmar/NotoSansMyanmar-Black.ttf) format("truetype");
+  font-display: fallback;
+}
+/*傣仂文*/
+@font-face {
+  font-family: "ATaiThamKHNewV3-Normal";
+  font-style: normal;
+  font-weight: 400;
+  src: local("A Tai Tham KH New V3"),
+    url(./taitham/tai-tham-kh-new-v3.ttf) format("truetype");
+  font-display: fallback;
+}
+
+/*中文繁體*/
+
+@font-face {
+  font-family: "Noto Sans TC";
+  font-style: normal;
+  font-weight: 300;
+  src: local("Noto Sans TC Light"), local("Noto Sans CJK TC Light"),
+    local("Source Han Sans TWHK Light");
+  /*url(../../font/NotoSansTC/NotoSansCJKtc-Light.otf) format('opentype'),*/
+  /*url(../../font/NotoSansTC/NotoSansTC-Light.woff) format('woff')*/
+}
+
+@font-face {
+  font-family: "Noto Sans TC";
+  font-style: normal;
+  font-weight: 400;
+  src: local("Noto Sans TC Regular"), local("Noto Sans CJK TC Regular"),
+    local("Source Han Sans TWHK Regular");
+  /*url(../../font/NotoSansTC/NotoSansCJKtc-Regular.otf) format('opentype'),*/
+  /*url(../../font/NotoSansTC/NotoSansTC-Regular.woff) format('woff');*/
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans TC";
+  font-style: normal;
+  font-weight: 500;
+  src: local("Noto Sans TC Medium"), local("Noto Sans CJK TC Medium"),
+    local("Source Han Sans TWHK Medium");
+  /*url(../../font/NotoSansTC/NotoSansCJKtc-Medium.otf) format('opentype'),*/
+  /*url(../../font/NotoSansTC/NotoSansTC-Medium.woff) format('woff');*/
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans TC";
+  font-style: normal;
+  font-weight: 700;
+  src: local("Noto Sans TC Bold"), local("Noto Sans CJK TC Bold"),
+    local("Source Han Sans TWHK Bold");
+  /*url(../../font/NotoSansTC/NotoSansCJKtc-Bold.otf) format('opentype'),*/
+  /*url(../../font/NotoSansTC/NotoSansTC-Bold.woff) format('woff');*/
+  font-display: fallback;
+}
+
+/*中文简体*/
+
+@font-face {
+  font-family: "Noto Sans SC";
+  font-style: normal;
+  font-weight: 300;
+  src: local("Noto Sans SC Light"), local("Noto Sans CJK SC Light"),
+    local("Source Han Sans CN Light");
+  /*url(../../font/NotoSansSC/NotoSansCJKsc-Light.otf) format('opentype'),
+    url(../../font/NotoSansSC/NotoSansSC-Light.woff) format('woff')*/
+}
+
+@font-face {
+  font-family: "Noto Sans SC";
+  font-style: normal;
+  font-weight: 400;
+  src: local("Noto Sans SC Regular"), local("Noto Sans CJK SC Regular"),
+    local("Source Han Sans CN Regular");
+  /*url(../../font/NotoSansSC/NotoSansCJKsc-Regular.otf) format('opentype'),
+    url(../../font/NotoSansSC/NotoSansSC-Regular.woff) format('woff');*/
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans SC";
+  font-style: normal;
+  font-weight: 500;
+  src: local("Noto Sans SC Medium"), local("Noto Sans CJK SC Medium"),
+    local("Source Han Sans CN Medium");
+  /*url(../../font/NotoSansSC/NotoSansCJKsc-Medium.otf) format('opentype'),
+    url(../../font/NotoSansSC/NotoSansSC-Medium.woff) format('woff');*/
+  font-display: fallback;
+}
+
+@font-face {
+  font-family: "Noto Sans SC";
+  font-style: normal;
+  font-weight: 700;
+  src: local("Noto Sans SC Bold"), local("Noto Sans CJK SC Bold"),
+    local("Source Han Sans CN Bold");
+  /*url(../../font/NotoSansSC/NotoSansCJKsc-Bold.otf) format('opentype'),
+    url(../../font/NotoSansSC/NotoSansSC-Bold.woff) format('woff');*/
+  font-display: fallback;
+}
+
+.font_ch {
+  font-family: "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+  font-style: normal;
+}
+
+.font_pali {
+  font-family: "Noto Sans", Arial, Verdana;
+  font-style: normal;
+}
+
+.font_m {
+  font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+  font-weight: 500;
+  font-style: normal;
+}
+
+.font_r {
+  font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+  font-weight: 400;
+  font-style: normal;
+}
+
+.font_l {
+  font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+  font-weight: 300;
+  font-style: normal;
+}
+
+.font_t {
+  font-family: "Noto Sans", "Noto Sans TC", "Noto Sans SC", Arial, Verdana;
+  font-weight: 100;
+  font-style: normal;
+}

BIN
dashboard-v6/src/assets/font/taitham/tai-tham-kh-new-v3.ttf


BIN
dashboard-v6/src/assets/general/images/logo_mps.png


+ 22 - 0
dashboard-v6/src/assets/general/images/wikipali_login_page.svg

@@ -0,0 +1,22 @@
+<svg id="logo_login" xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 336.454 152">
+  <g id="Group_12" data-name="Group 12" transform="translate(-396 -319)">
+    <g id="Group_2" data-name="Group 2" transform="translate(396 319)">
+      <g id="Group_1" data-name="Group 1" transform="translate(96.697 30.301)">
+        <path id="Path_1" data-name="Path 1" d="M258.951,153.873a2.9,2.9,0,0,1-1.8-.6,2.8,2.8,0,0,1-1.039-1.543l-8.443-31.183a2.022,2.022,0,0,1-.062-.5,1.673,1.673,0,0,1,.379-1.008,1.6,1.6,0,0,1,1.32-.629h3.023a2.765,2.765,0,0,1,1.765.6,2.869,2.869,0,0,1,1.008,1.543l4.159,17.074q.064.252,1.953,9.954a.113.113,0,0,0,.129.125.112.112,0,0,0,.125-.125q2.267-9.7,2.331-9.954l4.413-17.074a2.908,2.908,0,0,1,2.773-2.14h2.456a2.908,2.908,0,0,1,2.769,2.14l4.534,17.074q.375,1.576,1.164,4.882t1.23,5.073a.112.112,0,0,0,.125.125.206.206,0,0,0,.191-.125q.187-1.136.883-4.569t1.133-5.385l4.034-17.074a2.908,2.908,0,0,1,2.773-2.14H294.8a1.618,1.618,0,0,1,1.324.629,1.772,1.772,0,0,1,.379,1.07,1.99,1.99,0,0,1-.062.441l-8,31.183a2.8,2.8,0,0,1-1.039,1.543,2.876,2.876,0,0,1-1.8.6h-4.413a2.765,2.765,0,0,1-1.765-.6,2.855,2.855,0,0,1-1.008-1.543L274.447,136.3q-.633-2.519-2.2-10.079a.232.232,0,0,0-.223-.125.222.222,0,0,0-.219.125q-1.259,6.678-2.206,10.146l-3.78,15.371a2.855,2.855,0,0,1-1.008,1.543,2.743,2.743,0,0,1-1.765.6h-4.093Z" transform="translate(-247.61 -102.469)" fill="#fff"/>
+        <path id="Path_2" data-name="Path 2" d="M399.972,86.4a4.869,4.869,0,0,1-3.4,1.23,4.758,4.758,0,0,1-3.37-1.23,4.069,4.069,0,0,1-1.32-3.12A4.145,4.145,0,0,1,393.2,80.1a4.752,4.752,0,0,1,3.37-1.23,4.845,4.845,0,0,1,3.4,1.23,4.1,4.1,0,0,1,1.355,3.179A4.027,4.027,0,0,1,399.972,86.4Zm-4.819,43.375a2.254,2.254,0,0,1-2.269-2.269V96.514a2.117,2.117,0,0,1,.66-1.543,2.186,2.186,0,0,1,1.609-.66h2.835a2.193,2.193,0,0,1,1.609.66,2.119,2.119,0,0,1,.664,1.543v30.992a2.26,2.26,0,0,1-2.273,2.269Z" transform="translate(-335.539 -78.37)" fill="#fff"/>
+        <path id="Path_3" data-name="Path 3" d="M445.639,128.994a2.253,2.253,0,0,1-2.269-2.269V79.793a2.13,2.13,0,0,1,.66-1.543,2.186,2.186,0,0,1,1.609-.66h2.71a2.214,2.214,0,0,1,1.609.66,2.115,2.115,0,0,1,.66,1.543v30.871c0,.043.031.062.094.062a.224.224,0,0,0,.16-.062L463.345,95.23a4.484,4.484,0,0,1,3.655-1.7h3.718a.947.947,0,0,1,.914.6.935.935,0,0,1-.156,1.1L461.018,107.7a.409.409,0,0,0,0,.441l12.094,18.96a1.2,1.2,0,0,1,.191.629,1.179,1.179,0,0,1-.191.629,1.1,1.1,0,0,1-1.07.629h-3.589a3.8,3.8,0,0,1-3.4-1.89l-8.318-13.922c-.082-.168-.187-.187-.316-.062l-5.6,6.49a.823.823,0,0,0-.191.5v6.615a2.254,2.254,0,0,1-2.269,2.269h-2.714Z" transform="translate(-366.921 -77.59)" fill="#fff"/>
+        <path id="Path_4" data-name="Path 4" d="M545.4,86.4a4.869,4.869,0,0,1-3.4,1.23,4.752,4.752,0,0,1-3.37-1.23,4.069,4.069,0,0,1-1.32-3.12,4.145,4.145,0,0,1,1.32-3.179A4.758,4.758,0,0,1,542,78.87a4.845,4.845,0,0,1,3.4,1.23,4.1,4.1,0,0,1,1.355,3.179A4.04,4.04,0,0,1,545.4,86.4Zm-4.819,43.375a2.26,2.26,0,0,1-2.273-2.269V96.514a2.106,2.106,0,0,1,.664-1.543,2.186,2.186,0,0,1,1.609-.66h2.835a2.193,2.193,0,0,1,1.609.66,2.115,2.115,0,0,1,.66,1.543v30.992a2.254,2.254,0,0,1-2.269,2.269Z" transform="translate(-424.176 -78.37)" fill="#fff"/>
+        <path id="Path_5" data-name="Path 5" d="M591.075,167.236a2.253,2.253,0,0,1-2.265-2.265V119.612a2.253,2.253,0,0,1,2.265-2.269h1.574a2.488,2.488,0,0,1,1.671.629,2.784,2.784,0,0,1,.914,1.574l.187,1.574c.043.086.094.129.156.129a.234.234,0,0,0,.16-.062q5.67-4.727,10.837-4.725a12.237,12.237,0,0,1,10.364,4.882q3.685,4.885,3.687,13.2a24.893,24.893,0,0,1-1.261,8.1,17.127,17.127,0,0,1-3.4,6.049,15.857,15.857,0,0,1-4.881,3.687,13.081,13.081,0,0,1-5.764,1.324q-4.534,0-9.134-3.843a.077.077,0,0,0-.125,0,.182.182,0,0,0-.062.129l.187,5.8v9.2a2.253,2.253,0,0,1-2.269,2.265h-2.839Zm12.852-19.655a7.724,7.724,0,0,0,6.491-3.433q2.519-3.433,2.519-9.482,0-12.032-8.314-12.032-3.843,0-8.252,4.159a.6.6,0,0,0-.191.441v16.82a.6.6,0,0,0,.191.441A11.6,11.6,0,0,0,603.927,147.581Z" transform="translate(-455.564 -101.28)" fill="#fff"/>
+        <path id="Path_6" data-name="Path 6" d="M697.7,134.522a10.445,10.445,0,0,1-7.529-2.8,10.46,10.46,0,0,1,2.081-16.191q5.008-3.116,15.968-4.378c.168,0,.25-.1.25-.316q-.252-7.428-6.74-7.432a17.534,17.534,0,0,0-8.377,2.456,2.118,2.118,0,0,1-1.64.219,1.987,1.987,0,0,1-1.32-1.039l-.629-1.133a2.339,2.339,0,0,1-.219-1.73,2.015,2.015,0,0,1,1.039-1.355A25.533,25.533,0,0,1,702.994,97.3q6.491,0,9.7,3.905t3.214,11.149v19.089a2.26,2.26,0,0,1-2.273,2.269h-1.574a2.471,2.471,0,0,1-1.668-.629,2.74,2.74,0,0,1-.914-1.574l-.25-1.765c-.043-.082-.094-.129-.16-.129s-.113.043-.156.129Q703.244,134.522,697.7,134.522ZM695.3,90.362a2.253,2.253,0,0,1-2.269-2.269v-.5A2.253,2.253,0,0,1,695.3,85.32h15.433A2.253,2.253,0,0,1,713,87.589v.5a2.253,2.253,0,0,1-2.265,2.269Zm4.663,38.306q3.907,0,8.318-3.909a.681.681,0,0,0,.187-.5v-8.127c0-.211-.082-.293-.25-.25q-7.5.943-10.646,2.866a5.723,5.723,0,0,0-3.152,5.01,4.458,4.458,0,0,0,1.511,3.718A6.324,6.324,0,0,0,699.967,128.668Z" transform="translate(-515.555 -82.301)" fill="#fff"/>
+        <path id="Path_7" data-name="Path 7" d="M796.443,129.811q-3.907,0-5.639-2.331t-1.734-6.807V79.793a2.119,2.119,0,0,1,.664-1.543,2.186,2.186,0,0,1,1.609-.66h2.835a2.208,2.208,0,0,1,1.609.66,2.119,2.119,0,0,1,.664,1.543v41.263a2.744,2.744,0,0,0,1.008,2.519c.082.043.269.148.566.316s.5.293.629.379.293.2.5.344a1.693,1.693,0,0,1,.473.473,1.072,1.072,0,0,1,.156.566l.25,1.386a1.808,1.808,0,0,1,.062.441,2.351,2.351,0,0,1-.379,1.324,1.94,1.94,0,0,1-1.449.945C797.681,129.791,797.072,129.811,796.443,129.811Z" transform="translate(-577.618 -77.59)" fill="#fff"/>
+        <path id="Path_8" data-name="Path 8" d="M845.452,86.4a4.869,4.869,0,0,1-3.4,1.23,4.752,4.752,0,0,1-3.37-1.23,4.069,4.069,0,0,1-1.32-3.12,4.145,4.145,0,0,1,1.32-3.179,4.752,4.752,0,0,1,3.37-1.23,4.845,4.845,0,0,1,3.4,1.23,4.1,4.1,0,0,1,1.355,3.179A4.04,4.04,0,0,1,845.452,86.4Zm-4.819,43.375a2.254,2.254,0,0,1-2.269-2.269V96.514a2.117,2.117,0,0,1,.66-1.543,2.186,2.186,0,0,1,1.609-.66h2.835a2.193,2.193,0,0,1,1.609.66,2.119,2.119,0,0,1,.664,1.543v30.992a2.26,2.26,0,0,1-2.273,2.269Z" transform="translate(-607.05 -78.37)" fill="#fff"/>
+      </g>
+      <path id="Path_9" data-name="Path 9" d="M127.853,155.309a3.752,3.752,0,0,1-3.753-3.753V138c0-21.139,10.126-33.265,27.786-33.265a3.753,3.753,0,0,1,0,7.506c-13.457,0-20.284,8.666-20.284,25.763V151.56A3.745,3.745,0,0,1,127.853,155.309Z" transform="translate(-75.636 -63.837)" fill="#f1ca23"/>
+      <path id="Path_10" data-name="Path 10" d="M146.943,223.7a3.753,3.753,0,1,1,0-7.506c7.318,0,12.434-9.8,12.434-23.83v-40.3a3.753,3.753,0,0,1,7.506,0v40.3C166.879,214.011,156.866,223.7,146.943,223.7Z" transform="translate(-87.271 -90.392)" fill="#f1ca23"/>
+      <path id="Path_11" data-name="Path 11" d="M86.483,91.472a3.752,3.752,0,0,1-3.753-3.753V3.753a3.753,3.753,0,0,1,7.506,0v83.97A3.751,3.751,0,0,1,86.483,91.472Z" transform="translate(-50.422)" fill="#f1ca23"/>
+      <path id="Path_12" data-name="Path 12" d="M45.113,91.472a3.752,3.752,0,0,1-3.753-3.753V3.753a3.753,3.753,0,0,1,7.506,0v83.97A3.751,3.751,0,0,1,45.113,91.472Z" transform="translate(-25.208)" fill="#f1ca23"/>
+      <path id="Path_13" data-name="Path 13" d="M3.753,91.472A3.752,3.752,0,0,1,0,87.719V3.753a3.753,3.753,0,0,1,7.506,0v83.97A3.756,3.756,0,0,1,3.753,91.472Z" fill="#f1ca23"/>
+    </g>
+    <text id="studio" transform="translate(639 461)" fill="#fff" font-size="33" font-family="NotoSans-ExtraLight, Noto Sans" font-weight="200"><tspan x="0" y="0">studio</tspan></text>
+  </g>
+</svg>

+ 89 - 0
dashboard-v6/src/assets/general/images/wikipali_logo.svg

@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.0" id="wikipali_logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
+	 y="0px" viewBox="0 0 100 50" style="enable-background:new 0 0 100 50;" xml:space="preserve">
+<g id="圖層_1">
+	<g id="Group_12" transform="translate(-396 -320)">
+		<g id="Group_2" transform="translate(396 320)">
+			<g id="Group_1" transform="translate(39.472 12.369)">
+				<g id="Path_1">
+					<path style="fill:#FFFFFF;" d="M-5.9,19.5c-0.2,0-0.3-0.1-0.5-0.2c-0.1-0.1-0.2-0.2-0.3-0.4l-2.2-8.2v-0.1c0-0.1,0-0.2,0.1-0.3
+						c0.1-0.1,0.2-0.2,0.3-0.2h0.8c0.2,0,0.3,0.1,0.5,0.2c0.1,0.1,0.2,0.2,0.3,0.4l1.1,4.5c0,0,0.2,0.9,0.5,2.6l0,0l0,0l0,0l0,0
+						c0.4-1.7,0.6-2.6,0.6-2.6l1.2-4.5c0.1-0.3,0.4-0.6,0.7-0.6h0.6c0.3,0,0.6,0.2,0.7,0.6l1.2,4.5c0.1,0.3,0.2,0.7,0.3,1.3
+						c0.1,0.6,0.2,1,0.3,1.3l0,0l0,0c0,0,0,0,0.1,0c0-0.2,0.1-0.6,0.2-1.2c0.3-0.6,0.4-1,0.5-1.4l1.1-4.5c0.1-0.3,0.4-0.6,0.7-0.6
+						h0.7c0.1,0,0.3,0.1,0.3,0.2c0,0.1,0.1,0.2,0.1,0.3v0.1l-2.1,8.2c0,0.2-0.1,0.3-0.3,0.4c-0.1,0.1-0.3,0.2-0.5,0.2H0
+						c-0.2,0-0.3-0.1-0.5-0.2c-0.1-0.1-0.2-0.2-0.3-0.4l-1-4.1c-0.1-0.4-0.3-1.3-0.6-2.7c0,0,0,0-0.1,0c0,0,0,0-0.1,0
+						c-0.2,1.2-0.4,2.1-0.6,2.7l-1,4.1c0,0.2-0.1,0.3-0.3,0.4c-0.1,0.1-0.3,0.2-0.5,0.2H-5.9L-5.9,19.5z"/>
+				</g>
+				<g id="Path_2">
+					<path style="fill:#FFFFFF;" d="M8.1,8.1C7.8,8.3,7.5,8.4,7.2,8.4S6.6,8.3,6.3,8.1C6.1,7.9,5.9,7.6,5.9,7.3S6,6.7,6.2,6.5
+						c0.2-0.2,0.6-0.3,0.9-0.3c0.3,0,0.7,0.1,0.9,0.3C8.2,6.7,8.4,7,8.4,7.3C8.4,7.6,8.3,7.9,8.1,8.1z M6.8,19.5
+						c-0.3,0-0.6-0.3-0.6-0.6l0,0v-8.2c0-0.2,0.1-0.3,0.2-0.4c0.1-0.1,0.3-0.2,0.4-0.2h0.7c0.2,0,0.3,0.1,0.4,0.2
+						c0.1,0.1,0.2,0.3,0.2,0.4v8.2c0,0.3-0.3,0.6-0.6,0.6l0,0H6.8z"/>
+				</g>
+				<g id="Path_3">
+					<path style="fill:#FFFFFF;" d="M11.8,19.5c-0.3,0-0.6-0.3-0.6-0.6l0,0V6.5c0-0.2,0.1-0.3,0.2-0.4c0.1-0.1,0.3-0.2,0.4-0.2h0.7
+						c0.2,0,0.3,0.1,0.4,0.2c0.1,0.1,0.2,0.3,0.2,0.4v8.1l0,0l0,0l3.3-4.1c0.2-0.3,0.6-0.5,1-0.4h1c0.1,0,0.2,0.1,0.2,0.2
+						c0.1,0.1,0,0.2,0,0.3l-2.8,3.3V14l3.2,5c0,0,0,0.1,0.1,0.2c0,0.1,0,0.1-0.1,0.2s-0.2,0.2-0.3,0.2h-0.9c-0.4,0-0.7-0.2-0.9-0.5
+						l-2.2-3.7c0,0,0,0-0.1,0l-1.5,1.7c0,0,0,0.1-0.1,0.1v1.7c0,0.3-0.3,0.6-0.6,0.6l0,0L11.8,19.5z"/>
+				</g>
+				<g id="Path_4">
+					<path style="fill:#FFFFFF;" d="M23,8.1c-0.2,0.2-0.6,0.3-0.9,0.3s-0.6-0.1-0.9-0.3c-0.2-0.2-0.3-0.5-0.3-0.8s0.1-0.6,0.3-0.8
+						c0.2-0.2,0.6-0.3,0.9-0.3s0.7,0.1,0.9,0.3c0.2,0.2,0.4,0.5,0.4,0.8C23.4,7.6,23.3,7.9,23,8.1z M21.8,19.5
+						c-0.3,0-0.6-0.3-0.6-0.6l0,0v-8.2c0-0.2,0.1-0.3,0.2-0.4c0.1-0.1,0.3-0.2,0.4-0.2h0.7c0.2,0,0.3,0.1,0.4,0.2
+						c0.1,0.1,0.2,0.3,0.2,0.4v8.2c0,0.3-0.3,0.6-0.6,0.6l0,0H21.8z"/>
+				</g>
+				<g id="Path_5">
+					<path style="fill:#FFFFFF;" d="M26.8,23.3c-0.3,0-0.6-0.3-0.6-0.6l0,0v-12c0-0.3,0.3-0.6,0.6-0.6l0,0h0.4
+						c0.2,0,0.3,0.1,0.4,0.2c0.1,0.1,0.2,0.3,0.2,0.4v0.4l0,0l0,0c0.8-0.7,1.8-1.2,2.9-1.2c1.1,0,2.1,0.4,2.7,1.3c0.7,1,1,2.2,1,3.5
+						c0,0.7-0.1,1.4-0.3,2.1c-0.2,0.6-0.5,1.1-0.9,1.6c-0.4,0.4-0.8,0.7-1.3,1c-0.5,0.2-1,0.3-1.5,0.3c-0.9,0-1.8-0.4-2.4-1l0,0l0,0
+						l0,0v1.5v2.4c0,0.3-0.3,0.6-0.6,0.6l0,0L26.8,23.3L26.8,23.3z M30.2,18.2c0.7,0,1.3-0.3,1.7-0.9c0.5-0.7,0.7-1.6,0.7-2.5
+						c0-2.1-0.7-3.2-2.2-3.2c-0.8,0.1-1.6,0.5-2.2,1.1l-0.1,0.1v4.4c0,0,0,0.1,0.1,0.1C28.8,17.8,29.5,18.1,30.2,18.2L30.2,18.2z"/>
+				</g>
+				<g id="Path_6">
+					<path style="fill:#FFFFFF;" d="M39.1,19.7c-0.7,0-1.4-0.2-2-0.7c-1.1-1.1-1-2.8,0.1-3.9c0.1-0.1,0.3-0.3,0.5-0.4
+						c1.3-0.7,2.7-1.1,4.2-1.2c0,0,0.1,0,0.1-0.1c0-1.3-0.6-2-1.8-2c-0.8,0-1.5,0.2-2.2,0.6c-0.1,0.1-0.3,0.1-0.4,0.1
+						c-0.2,0-0.3-0.1-0.3-0.3L37,11.6c-0.1-0.1-0.1-0.3-0.1-0.5s0.1-0.3,0.3-0.4c1-0.6,2.1-0.9,3.3-0.9c1-0.1,1.9,0.3,2.6,1
+						c0.6,0.9,0.9,1.9,0.8,2.9v5c0,0.3-0.3,0.6-0.6,0.6l0,0h-0.4c-0.2,0-0.3-0.1-0.4-0.2c-0.1-0.1-0.2-0.3-0.2-0.4l-0.1-0.5l0,0l0,0
+						C41.3,19.2,40.2,19.7,39.1,19.7z M38.5,8.1c-0.3,0-0.6-0.3-0.6-0.6l0,0V7.3c0-0.3,0.3-0.6,0.6-0.6l0,0h4.1
+						c0.3,0,0.6,0.3,0.6,0.6l0,0v0.1c0,0.3-0.3,0.6-0.6,0.6l0,0h-4.1V8.1z M39.7,18.2c0.8-0.1,1.6-0.4,2.2-1c0,0,0.1-0.1,0-0.1V15
+						c0-0.1,0-0.1-0.1-0.1c-1,0.1-1.9,0.3-2.8,0.8c-0.5,0.3-0.8,0.8-0.8,1.3c0,0.4,0.1,0.7,0.4,1C38.9,18.1,39.3,18.2,39.7,18.2
+						L39.7,18.2z"/>
+				</g>
+				<g id="Path_7">
+					<path style="fill:#FFFFFF;" d="M48.8,19.7c-0.6,0.1-1.1-0.2-1.5-0.6c-0.3-0.5-0.5-1.2-0.5-1.8V6.5c0-0.2,0.1-0.3,0.2-0.4
+						c0.1-0.1,0.3-0.2,0.4-0.2h0.7c0.2,0,0.3,0.1,0.4,0.2c0.1,0.1,0.2,0.3,0.2,0.4v10.9c0,0.3,0.1,0.5,0.3,0.7c0,0,0.1,0,0.1,0.1
+						c0.1,0,0.1,0.1,0.2,0.1l0.1,0.1l0.1,0.1v0.1l0.1,0.4v0.1c0,0.1,0,0.2-0.1,0.3c-0.1,0.1-0.2,0.2-0.4,0.2
+						C49.1,19.7,48.9,19.7,48.8,19.7z"/>
+				</g>
+				<g id="Path_8">
+					<path style="fill:#FFFFFF;" d="M53.9,8.1c-0.2,0.2-0.6,0.3-0.9,0.3c-0.3,0-0.6-0.1-0.9-0.3c-0.2-0.2-0.4-0.5-0.3-0.8
+						c0-0.3,0.1-0.6,0.3-0.8c0.2-0.2,0.6-0.3,0.9-0.3c0.3,0,0.7,0.1,0.9,0.3c0.2,0.2,0.4,0.5,0.4,0.8C54.3,7.6,54.2,7.9,53.9,8.1z
+						 M52.7,19.5c-0.3,0-0.6-0.3-0.6-0.6l0,0v-8.2c0-0.2,0.1-0.3,0.2-0.4c0.1-0.1,0.3-0.2,0.4-0.2h0.7c0.2,0,0.3,0.1,0.4,0.2
+						c0.1,0.1,0.2,0.3,0.2,0.4v8.2c0,0.3-0.3,0.6-0.6,0.6l0,0H52.7z"/>
+				</g>
+			</g>
+			<g id="Path_9">
+				<path style="fill:#F1CA23;" d="M18.9,34.4c-0.5,0-1-0.4-1-1l0,0v-3.6c0-5.6,2.7-8.8,7.3-8.8c0.5,0,1,0.4,1,1c0,0.5-0.4,1-1,1
+					l0,0c-3.5,0-5.3,2.3-5.3,6.8v3.6C19.8,34,19.4,34.4,18.9,34.4L18.9,34.4z"/>
+			</g>
+			<g id="Path_10">
+				<path style="fill:#F1CA23;" d="M20.8,45.5c-0.5,0-1-0.4-1-1c0-0.5,0.4-1,1-1l0,0c1.9,0,3.3-2.6,3.3-6.3V26.6c0-0.5,0.4-1,1-1
+					c0.5,0,1,0.4,1,1v10.6C26.1,42.9,23.4,45.5,20.8,45.5z"/>
+			</g>
+			<g id="Path_11">
+				<path style="fill:#F1CA23;" d="M14.6,34.4c-0.5,0-1-0.4-1-1l0,0V11.3c0-0.5,0.4-1,1-1c0.5,0,1,0.4,1,1v22.1
+					C15.6,34,15.1,34.4,14.6,34.4L14.6,34.4z"/>
+			</g>
+			<g id="Path_12">
+				<path style="fill:#F1CA23;" d="M10.3,34.4c-0.5,0-1-0.4-1-1l0,0V11.3c0-0.5,0.4-1,1-1c0.5,0,1,0.4,1,1v22.1
+					C11.3,34,10.9,34.4,10.3,34.4L10.3,34.4z"/>
+			</g>
+			<g id="Path_13">
+				<path style="fill:#F1CA23;" d="M6.1,34.4c-0.5,0-1-0.4-1-1l0,0V11.3c0-0.5,0.4-1,1-1s1,0.4,1,1v22.1C7.1,34,6.6,34.4,6.1,34.4z"
+					/>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 310 - 0
dashboard-v6/src/assets/icon/index.tsx


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 34 - 0
dashboard-v6/src/assets/icon/wikipali stamp2.ai


+ 62 - 0
dashboard-v6/src/assets/library/images/books.svg

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.0" id="books_bg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 610 947" style="enable-background:new 0 0 610 947;" xml:space="preserve">
+<g>
+	<defs>
+		<rect id="SVGID_3_" x="-135.3" y="-0.1" width="1141.1" height="947.4"/>
+	</defs>
+	<clipPath id="SVGID_2_">
+		<use xlink:href="#SVGID_3_"  style="overflow:visible;"/>
+	</clipPath>
+	<g id="Mask_Group_2_1_" style="clip-path:url(#SVGID_2_);">
+		<g id="Group_57_1_" transform="translate(-2443.92 512.47)">
+			<path id="Path_106_1_" style="fill:#EAEAEA;" d="M3010.1,259.9h-514.5l178.3-1001.2h157.9L3010.1,259.9z"/>
+			<path id="Path_128_1_" style="fill:#F0F0F0;" d="M2752.9,168c142.1,0,257.3,43,257.3,96.1s-115.2,96.1-257.3,96.1
+				s-257.3-43-257.3-96.1S2610.8,168,2752.9,168z"/>
+			<g id="Group_56_1_" transform="translate(2703.386 -23.966)">
+				<path id="Path_107_1_" style="fill:#B03C2C;" d="M28.4,261.7l-100.3,23.9c-11.4-0.1-22.1-3-23.3-7.8V114.4L5.2,90.4V99l23.3-0.7
+					L28.4,261.7z"/>
+				<path id="Path_108_1_" style="fill:#782E29;" d="M-71.9,285.6L-71.9,285.6l0-171.2h-23.3v163.4C-94,282.6-83.3,285.5-71.9,285.6
+					z"/>
+				<path id="Path_109_1_" style="fill:#FFFFFF;" d="M-94.8,115.4L4.7,91.6c1.1,4.9,12.3,6.5,23.8,6.6l-100.3,23.9
+					C-82.5,122.1-92.5,119.6-94.8,115.4z"/>
+				<path id="Path_110_1_" style="fill:#B03C2C;" d="M55.2,270.8l-100.3,23.9c-11.4-0.1-22.1-3-23.3-7.8V123.5L32,99.6v8.6l23.3-0.7
+					L55.2,270.8z"/>
+				<path id="Path_111_1_" style="fill:#782E29;" d="M-45.1,294.7L-45.1,294.7l0-171.2h-23.3v163.4
+					C-67.2,291.8-56.5,294.6-45.1,294.7z"/>
+				<path id="Path_112_1_" style="fill:#FFFFFF;" d="M-68,124.5l99.5-23.7c1.1,4.9,12.3,6.5,23.8,6.6l-100.3,23.9
+					C-55.7,131.2-65.7,128.8-68,124.5z"/>
+				<path id="Path_113_1_" style="fill:#B03C2C;" d="M82,279.9l-100.3,23.9c-11.4-0.1-22.1-3-23.3-7.8V132.6l100.3-23.9v8.6
+					l23.3-0.7L82,279.9z"/>
+				<path id="Path_114_1_" style="fill:#782E29;" d="M-18.3,303.8L-18.3,303.8l0-171.2h-23.3V296C-40.4,300.9-29.7,303.7-18.3,303.8
+					z"/>
+				<path id="Path_115_1_" style="fill:#FFFFFF;" d="M-41.2,133.6l99.5-23.7c1.1,4.9,12.3,6.5,23.8,6.6l-100.3,23.9
+					C-28.9,140.4-38.9,137.9-41.2,133.6z"/>
+				<path id="Path_116_1_" style="fill:#B03C2C;" d="M108.8,289L8.5,313c-11.4-0.1-22.1-3-23.3-7.8V141.7l100.3-23.9v8.6l23.3-0.7
+					V289z"/>
+				<path id="Path_117_1_" style="fill:#782E29;" d="M8.5,313L8.5,313l0-171.2h-23.3v163.4C-13.6,310-2.9,312.8,8.5,313z"/>
+				<path id="Path_118_1_" style="fill:#FFFFFF;" d="M-14.4,142.7L85.1,119c1.1,4.9,12.3,6.5,23.8,6.6L8.5,149.6
+					C-2.1,149.5-12.1,147-14.4,142.7z"/>
+				<path id="Path_119_1_" style="fill:#B03C2C;" d="M135.6,298.1L35.3,322.1c-11.4-0.1-22.1-3-23.3-7.8V150.9l100.3-23.9v8.6
+					l23.3-0.7V298.1z"/>
+				<path id="Path_120_1_" style="fill:#782E29;" d="M35.3,322.1L35.3,322.1l0-171.2H12v163.4C13.2,319.1,23.9,322,35.3,322.1z"/>
+				<path id="Path_121_1_" style="fill:#FFFFFF;" d="M12.4,151.9l99.5-23.7c1.1,4.9,12.3,6.5,23.8,6.6L35.3,158.7
+					C24.7,158.6,14.7,156.1,12.4,151.9z"/>
+				<path id="Path_122_1_" style="fill:#B03C2C;" d="M162.4,307.3L62.1,331.2c-11.4-0.1-22.1-3-23.3-7.8V160L139.1,136v8.6l23.3-0.7
+					V307.3z"/>
+				<path id="Path_123_1_" style="fill:#782E29;" d="M62.1,331.2L62.1,331.2l0-171.2H38.8v163.4C39.9,328.2,50.6,331.1,62.1,331.2z"
+					/>
+				<path id="Path_124_1_" style="fill:#FFFFFF;" d="M39.2,161l99.5-23.7c1.1,4.9,12.3,6.5,23.8,6.6L62.1,167.8
+					C51.5,167.7,41.5,165.2,39.2,161z"/>
+				<path id="Path_125_1_" style="fill:#B03C2C;" d="M189.2,316.4L88.9,340.3c-11.4-0.1-22.1-3-23.3-7.8V169.1l100.3-23.9v8.6
+					l23.3-0.7V316.4z"/>
+				<path id="Path_126_1_" style="fill:#782E29;" d="M88.9,340.3L88.9,340.3l0-171.2H65.6v163.4C66.7,337.4,77.4,340.2,88.9,340.3z"
+					/>
+				<path id="Path_127_1_" style="fill:#FFFFFF;" d="M66,170.1l99.5-23.7c1.1,4.9,12.3,6.5,23.8,6.6L88.9,176.9
+					C78.2,176.8,68.2,174.4,66,170.1z"/>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>

BIN
dashboard-v6/src/assets/library/images/download_bg.png


+ 127 - 0
dashboard-v6/src/assets/library/images/teachers.svg

@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.0" id="teachers_bg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 763 1491" style="enable-background:new 0 0 763 1491;" xml:space="preserve">
+<g id="Mask_Group_3_1_" transform="translate(0 -32)">
+	<g id="Group_62_1_" transform="translate(-1298.767 1264.372)">
+		<path id="Path_129_1_" style="fill:#545454;" d="M1507.6-1232.2L1361.5-17.1h630.3l-145.6-1215.1"/>
+		<ellipse id="Ellipse_4_1_" style="fill:#686868;" cx="1676.6" cy="-17.1" rx="315.1" ry="104"/>
+		<path id="Path_130_1_" style="fill:#343434;" d="M1724.5-230.8c-0.6-6.1-2.9-11.8-6.5-16.7c0,0,0.5-19.7-0.3-28
+			c-0.7-8.3-3.7-34-5.5-39.1c-1.3-3.9-4.1-38.9-8.3-51.1c-4.5-13.3-10.8-22.8-17.6-27.6c-0.9-0.6-1.8-1.2-2.7-1.8
+			c-0.8-0.8-1.7-1.5-2.6-2c-11.2-5.9-19.8-4.2-20.6-6.5c-0.4-2.7-0.5-5.5-0.3-8.2c5.9-2.3,11.4-5.5,16.3-9.5
+			c17.2-14.2,24.9-43.9,19.5-63.4c-6.3-23.1-28.6-33.7-51.9-32.4c-30.1,1.7-39.1,21.5-42.2,39.8c-2.7,16-1.1,41.2,13.4,59.5
+			c3.1,3.8,7.3,6.7,12,8.2c-0.2,0.8-0.5,1.5-0.9,2.2l0.1,0.1c-0.2,0.5-0.5,0.9-1,1.1c-0.2,0.1-0.6,0.2-1,0.4c-1.7-2.8-3-4.4-4.4-4.4
+			c-0.9,0.1-1.7,0.4-2.5,0.9c-0.3-0.2-0.6-0.4-0.9-0.5c-1.5-0.6-3.5,0.4-5.8,2.3c-14,3.8-27.5,19.8-31.5,28.8
+			c-5.3,11.8-6.3,19-9.7,30.3c-2.8,9.3-6.1,30.9-6.1,30.9s-19.9,50.2-20.9,55.3c-0.4,2.7-0.1,5.5,1.1,8c-3.6,4-6.2,8.8-7.7,14
+			c-2.4,9,6,18.4,9.7,19.8c3.7,1.4,13.7,2.6,17-8.7c0.1-0.3,0.2-0.6,0.2-0.8c0.2,0.2,0.3,0.3,0.5,0.5c-0.8,9.4-1.6,18.8-2.4,28.2
+			c0,0.3,0.2,0.6,0.6,0.8c-3.9,43.5-6.3,84.1-4.4,88.1c2.4,5.2,4.5,10.7,16.8,17.1c-3.4,5.7-7,10.2-15.7,16.8
+			c-9.3,7.1-13.4,13.5-7.1,19.1c6.2,5.6,22.1,8.9,31.2,3.9c9.1-5,13-16.2,15.9-20.6c1-1.6,1.3-5.2,1.3-9.4
+			c20.8,5.9,39.2,5.8,49.2,4.5c0.1,1.4,0.3,2.9,0.6,4.3c1.7,6.8,0.3,21.2,13.9,26.1c8.7,3.1,23.5-1.7,25.4-6.9s-0.5-13.4-7.2-22.4
+			c-2.5-3.6-4.1-7.7-4.7-12c9.7-2.2,13.4-4.7,16.9-9.2c5.1-6.7,3.6-16.3,5.3-41.4c1.5-22.2,0.5-42.4,0-77.2c2.9,6.9,8.4,10,15,9.3
+			C1723-211.7,1725.1-219.7,1724.5-230.8z M1698.8-242.5c-0.1-2.2-0.1-4.5-0.2-6.9c0.7,2.4,1.2,4,1.2,4S1699.3-244.3,1698.8-242.5
+			L1698.8-242.5z"/>
+		<path id="Path_131_1_" style="fill:#434343;" d="M1834.9-183.9c-1.5-5.3-4.1-10.2-7.7-14.3c1.2-2.5,1.5-5.4,1.1-8.1
+			c-1-5.3-20.9-56.5-20.9-56.5s-3.3-22-6.1-31.5c-3.4-11.5-4.4-19-9.7-31c-4-9.2-17.6-25.5-31.6-29.4c-2.3-1.9-4.4-2.9-5.9-2.4
+			c-0.3,0.1-0.7,0.3-0.9,0.5c-0.7-0.5-1.6-0.8-2.5-0.9c-1.4,0-2.8,1.6-4.5,4.4c-0.4-0.1-0.8-0.3-1-0.4c-0.4-0.3-0.8-0.7-1-1.1
+			l0.1-0.1c-0.4-0.7-0.7-1.5-0.9-2.2c4.7-1.5,8.9-4.4,12.1-8.3c14.6-18.7,16.2-44.5,13.5-60.8c-3.1-18.7-12.1-38.9-42.3-40.6
+			c-23.4-1.4-45.7,9.5-52.1,33c-5.4,19.9,2.3,50.2,19.6,64.7c4.9,4.1,10.5,7.4,16.4,9.7c0.2,2.8,0.1,5.6-0.3,8.4
+			c-0.8,2.3-9.5,0.6-20.7,6.6c-1,0.5-1.9,1.2-2.7,2.1c-1,0.6-1.9,1.2-2.8,1.9c-6.8,4.9-13.1,14.6-17.6,28.2
+			c-4.2,12.5-7,48.3-8.3,52.2c-1.8,5.3-4.7,31.4-5.5,39.9c-0.7,8.5-0.3,28.6-0.3,28.6c-3.7,5-5.9,10.9-6.5,17
+			c-0.7,11.4,1.5,19.6,10.4,20.5c6.6,0.7,12.1-2.5,15-9.5c-0.5,35.5-1.5,56.2,0,78.8c1.7,25.6,0.2,35.5,5.3,42.3
+			c3.5,4.6,7.2,7.2,17,9.4c-0.6,4.4-2.2,8.6-4.7,12.3c-6.8,9.2-9.1,17.6-7.2,22.9c1.9,5.3,16.7,10.2,25.4,7
+			c13.7-5,12.3-19.7,14-26.7c0.3-1.4,0.5-2.9,0.6-4.4c10.1,1.3,28.5,1.4,49.4-4.6c-0.1,4.3,0.2,8,1.3,9.6
+			c2.8,4.6,6.8,15.9,15.9,21.1s25,1.7,31.3-4s2.2-12.3-7.2-19.5c-8.7-6.7-12.3-11.3-15.7-17.1c12.3-6.5,14.4-12.1,16.8-17.4
+			c1.9-4.1-0.5-45.5-4.4-90c0.3-0.2,0.6-0.4,0.6-0.8c-0.8-9.6-1.6-19.2-2.4-28.8c0.2-0.2,0.3-0.3,0.5-0.5c0.1,0.3,0.2,0.6,0.2,0.9
+			c3.3,11.6,13.3,10.3,17.1,8.9C1828.9-165,1837.3-174.6,1834.9-183.9z M1671.9-193.2c-0.1,2.4-0.1,4.7-0.2,7c-0.6-1.9-1-3-1-3
+			S1671.1-190.7,1671.9-193.2z"/>
+		<g id="Group_61_1_" transform="translate(2271.497 -30.516)">
+			<path id="Path_132_1_" style="fill:#FFF0DE;" d="M-652.1-290.2c-6.9,3.6-18.3,26.9-20.1,37c-1.7,10.1-5.4,45.3-5.4,45.3
+				s-8,22.3-11,33.2c-2.4,8.7-6.2,31.4-6.2,31.4s-4.2,8.5-1.1,16.6c4.6,12.2,9,17.4,17.9,18.1c5.2,0.4,6.5-11.7,5-15.8
+				c-3.1-8.8-2.5-10.9-2.5-10.9s7.1-19.4,10.1-27.3c3.1-7.9,12-32.4,12.8-37.8c0.6-4.1,9.4-37.9,11.2-50.8c2-14,3.7-31.3,0.8-38.4
+				C-641.9-293.1-645.8-293.5-652.1-290.2z"/>
+			<path id="Path_133_1_" style="fill:#FFF0DE;" d="M-638.2-314.1c0,0,0.8,15,0,17.3s1.1-2.6-8.6,5.5c-10.1,8.4-19.2,34.8-21.1,49.1
+				c-1.4,10.6,1.9,38.3,1.9,38.3l103.8-4.9c0,0-0.8-61.3-12.4-74.1s-26.3-15.4-29-16.5c-2.6-1.1-4.5-17.7-4.5-17.7L-638.2-314.1z"/>
+			<g id="Group_58_1_" transform="translate(44.783 67.382)">
+				<path id="Path_134_1_" style="fill:#F9C8B0;" d="M-653-384.4c0,0,1.7,11.1,2.9,15.4c-2.7,2.5-9.5,5.8-14.6,6.9
+					c-7.3,1.5-13.1-0.6-18.5-2.2c1.3-4.3,0.1-17,0.1-17L-653-384.4z"/>
+			</g>
+			<path id="Path_135_1_" style="fill:#FFF0DE;" d="M-634.9-411.3c-29.8,2.8-38.1,23.2-40.5,41.8c-2.1,16.2,1.7,46.7,17.3,63.6
+				c10.5,11.4,54.2,11.3,66.8-7.1c12.6-18.5,15.1-47.8,9.1-67.3C-589.2-403.6-611.7-413.5-634.9-411.3z"/>
+			<path id="Path_136_1_" style="fill:#FFF0DE;" d="M-576.1,1.1c0,12.3-1.6,26,0,32.8c1.7,6.9,0.3,21.5,13.8,26.4
+				c8.7,3.2,23.3-1.7,25.2-7c1.9-5.3-0.5-13.6-7.2-22.7c-5.4-7.3-5.5-19.1-5.2-23.8c0.4-4.7,0-16.4,0-16.4L-576.1,1.1z"/>
+			<path id="Path_137_1_" style="fill:#FFF0DE;" d="M-626.9,5.2c0,4.5,2.8,24.8,0,29.3s-6.8,15.8-15.8,20.9c-9,5.1-24.8,1.7-31-3.9
+				s-2.2-12.2,7.1-19.3s12.7-11.9,16.3-18.3c3.6-6.4,2.5-16,2.5-16L-626.9,5.2z"/>
+			<path id="Path_138_1_" style="fill:#572A08;" d="M-620.4-349.9c-4.7,0-9.5-1.1-11.2-2.6c-0.4-0.4-0.5-1-0.1-1.4
+				c0.4-0.4,1-0.5,1.4-0.1c2.2,2,14.8,3.7,18.5-0.2c0.4-0.4,1-0.5,1.4-0.1c0.4,0.4,0.5,1,0.1,1.4c0,0-0.1,0.1-0.1,0.1
+				C-612.3-350.7-616.3-349.9-620.4-349.9z"/>
+			<path id="Path_139_1_" style="fill:#572A08;" d="M-660.2-348.4c-3-0.1-6-0.9-8.7-2.3c-0.5-0.2-0.7-0.8-0.5-1.4
+				c0.2-0.5,0.8-0.7,1.4-0.5c6.2,2.8,9.5,3.1,16.9-0.4c0.5-0.2,1.1,0,1.4,0.5c0.2,0.5,0,1.1-0.5,1.4
+				C-653.4-349.5-656.8-348.6-660.2-348.4z"/>
+			<path id="Path_140_1_" style="fill:#572A08;" d="M-638.6-320c-3.2,0.1-6.3-0.4-9.4-1.3c-0.5-0.2-0.8-0.8-0.6-1.3
+				c0.2-0.5,0.8-0.8,1.3-0.6c2.9,0.9,6,1.3,9.1,1.2c3.9-0.1,6.9-0.8,7.9-2.1c0.3-0.4,1-0.5,1.4-0.2c0.4,0.3,0.5,1,0.2,1.4l0,0
+				c-1.8,2.3-6.1,2.9-9.5,2.9C-638.3-320-638.4-320-638.6-320z"/>
+			<path id="Path_141_1_" style="fill:#572A08;" d="M-589.9-333.3c-0.6,0-1-0.4-1-1c0-0.5,0.4-1,0.9-1c3.1-0.4,8.2-3.9,7.7-14.5
+				c-0.1-1.9-1.2-3.6-3-4.5c-2-1.1-4.4-1-6.3,0.2c-0.5,0.3-1.1,0.1-1.4-0.4c-0.3-0.5-0.1-1.1,0.4-1.4l0,0c2.5-1.6,5.7-1.7,8.3-0.3
+				c2.4,1.2,3.9,3.6,4,6.2c0.5,10.5-4.4,15.9-9.5,16.6C-589.8-333.3-589.9-333.3-589.9-333.3z"/>
+			<path id="Path_142_1_" style="fill:#B03C2C;" d="M-626.2-267.4c-15,13.1-43.4,25.9-43.4,25.9s-6.7,71.8-7,115.8
+				c-0.4,44.6-1.9,67.5-0.2,92.9c1.7,25.4,0.2,35.2,5.2,41.9c5.1,6.8,10.7,9,35,12.4s70.5-6.8,76.1-11.3s3.2-150.4-5.5-208.9
+				c-10.7-71.9-23.7-104.6-32.3-104.8C-602.9-303.6-606.9-284.3-626.2-267.4z"/>
+			<path id="Path_143_1_" style="fill:#782E29;" d="M-600.3-288.5c-3.7,0.1-5.9,5.7-7.4,8.4c-4.4,8.3-9.9,16-16.2,23
+				c-11.5,12.1-26.6,22.1-40.5,30.8c-1.1,0.7-0.1,1.9,0.9,1.2c13-7.9,25.3-16.9,36.8-26.9c6.9-6.3,12.8-13.7,17.5-21.8
+				c1.7-2.9,3.3-5.9,5-8.7c0.7-1.1,2.2-4.3,3.8-4.4C-599.3-287-598.9-288.5-600.3-288.5L-600.3-288.5z"/>
+			<path id="Path_144_1_" style="fill:#B03C2C;" d="M-594.8-303.1c-6.5,2.5-17.5,30.7-29.9,107.5S-646.1-89-647.8-52.9
+				c-1.7,36.1-2.3,76.5,2.8,78.8c5.1,2.3,42.3,7.5,72.8-2.6c30.5-10.2,50.8-23.1,54.2-30.5c3.4-7.3,2.3-115.6-9-162.5
+				s-25.9-113.4-36.1-120.7C-573.4-297.7-589.2-305.2-594.8-303.1z"/>
+			<path id="Path_145_1_" style="fill:#782E29;" d="M-595.2-301.9c-2.7,1.1-4.3,4-5.6,6.4c-2.8,5.4-5,11.1-6.7,16.9
+				c-7.8,24.5-12.4,50.2-16.6,75.5c-5,29.8-10.2,59.5-15.9,89.2c-3.5,18.7-7.1,37.4-8.4,56.4c-1.2,16.6-1.6,33.4-1.2,50
+				c0,6.8,0.5,13.5,1.4,20.2c0.4,2.5,0.8,7.1,3.3,8.4c0.5,0.3,2.4-0.5,1.6-0.9c-2.3-1.2-2.6-6.1-3-8.3c-0.8-5.7-1.2-11.5-1.3-17.3
+				c-0.4-16,0-32,0.9-48c1-18,4-35.8,7.3-53.5c5-26.7,10.1-53.3,14.5-80.1c4.6-27.8,9.1-55.7,16.6-82.8c2-7.7,4.5-15.2,7.7-22.5
+				c1.2-2.7,3.1-7.6,6.2-8.8C-593.2-301.7-594.3-302.3-595.2-301.9L-595.2-301.9z"/>
+			<path id="Path_146_1_" style="fill:#FFF0DE;" d="M-588.8-130.7c-4.5,8.3,1.4,19.5,4.6,21.7c3.3,2.2,12.6,5.8,18.5-4.6
+				c5.9-10.4,11.2-29.2,2-32.5C-572.9-149.2-584.3-139.1-588.8-130.7z"/>
+			<path id="Path_147_1_" style="fill:#B03C2C;" d="M-560.7-291.9c15.1,10.6,24.9,30.9,34.1,50.5c9.4,19.9,22.4,79.7,22.1,96
+				c-0.3,16.5-5.2,66.6-11.9,68.3c-6.8,1.7-26.7-18.8-37.9-40.6c-11.4-22.1-20.6-28.6-18.7-32.3c2.4-4.7,15.7-46.6,15.7-46.6
+				s-8.8-22.1-13.6-30.7c-5.4-9.5-10.1-19.3-14-29.5c-4.1-10.6-9.9-38.2-4.7-40.2C-581.4-300.3-565.5-295.3-560.7-291.9z"/>
+			<path id="Path_148_1_" style="fill:#782E29;" d="M-516.6-80.5c-3.1,0.6-7.3-3.1-9.5-4.9c-4.2-3.5-8-7.5-11.5-11.7
+				c-4.1-4.9-7.8-10-11.1-15.4c-3.6-5.7-6.4-11.8-9.9-17.6c-2.7-4.5-5.6-8.8-8.7-13.1c-1.6-2.3-3.8-4.6-4.8-7.3
+				c-0.6-1.7,0-2.7,0.6-4.2c1-2.4,1.8-4.9,2.7-7.4c2.6-7.4,5-14.9,7.4-22.3c1.1-3.3,2.1-6.6,3.2-9.9c0.7-1.6,1.3-3.3,1.6-5.1
+				c-0.1-1.1-0.5-2.2-1-3.2c-1.5-3.8-3.1-7.5-4.7-11.2c-3.9-9-8.4-17.6-12.9-26.3c-5.5-10.2-9.7-21.1-12.6-32.3
+				c-1.3-5.3-2.2-10.6-2.8-16c-0.2-2.1-1.4-9.8,1.2-11c1.2-0.6,0.1-1.4-0.8-0.9c-2.1,1-2.4,3.5-2.6,5.7c-0.2,4.2,0.2,8.3,0.9,12.4
+				c1.6,10.2,4.5,20.1,8.5,29.6c4.4,10.4,10.3,20,15,30.3c2.1,4.6,4.1,9.3,6.1,14c1.1,2.7,3.2,6,3.6,8.9c-0.1,1-0.3,2-0.7,2.9
+				c-0.9,2.9-1.8,5.8-2.8,8.7c-2.5,7.9-5.1,15.8-7.8,23.7c-1,3-2.1,6.1-3.2,9.1c-0.6,1.4-1.5,2.8-1.2,4.4c0.5,2.3,2.4,4.5,3.7,6.3
+				c2.8,3.9,5.6,7.9,8.2,12c4.3,6.8,7.6,14,11.8,20.8c4,6.4,8.5,12.5,13.5,18.1c3.6,4.2,7.7,8,12.2,11.3c2.5,1.7,5.4,3.5,8.6,2.9
+				C-515-79.6-515.3-80.7-516.6-80.5L-516.6-80.5z"/>
+			<path id="Path_149_1_" style="fill:#572A08;" d="M-642.4-330.9L-642.4-330.9c-3.5-0.2-4.8-1.7-5.3-2.9c-0.9-2.1,0.2-5,2.9-8.1
+				c0.4-0.4,1-0.5,1.4-0.1c0.4,0.4,0.5,1,0.1,1.4c-2.2,2.5-3.1,4.7-2.6,6c0.5,1.2,2.1,1.5,3.4,1.6c0.6,0,1,0.5,0.9,1.1
+				C-641.4-331.3-641.9-330.9-642.4-330.9L-642.4-330.9z"/>
+			<path id="Path_150_1_" style="fill:#572A08;" d="M-627.7-363.9c0.2-0.6,0.6-1.2,1.1-1.6c0.5-0.4,1.1-0.7,1.8-0.9
+				c1.2-0.4,2.5-0.5,3.8-0.4c1.2,0.1,2.5,0.3,3.6,0.7c1.2,0.4,2.3,1,3.3,1.7c-1.2,0-2.4,0-3.6-0.1l-3.5-0.3
+				c-1.1-0.1-2.2-0.1-3.3-0.1C-625.6-364.8-626.8-364.5-627.7-363.9z"/>
+			<path id="Path_151_1_" style="fill:#572A08;" d="M-654-362.9c-1-0.6-2.1-0.9-3.2-0.9c-1.1-0.1-2.2,0-3.3,0.1l-3.5,0.3
+				c-1.2,0.1-2.4,0.2-3.6,0.1c1-0.7,2.1-1.3,3.3-1.7c1.2-0.4,2.4-0.7,3.6-0.7c1.3-0.1,2.6,0,3.8,0.4c0.6,0.2,1.2,0.5,1.8,0.9
+				C-654.7-364.1-654.3-363.5-654-362.9z"/>
+			<g id="Group_60_1_" transform="translate(0 95.202)">
+				<g id="Group_59_1_">
+					<path id="Path_152_1_" style="fill:#EDE8E6;" d="M-592.1-323.9c-5.1-35.8-25.6-55.9-56.5-47.4l0,0l0,0
+						c-23.7,6.5-39,27.3-47.2,51c-6.1,17.8-8.3,41.1,0.1,52c6.2,8,22.2,7.5,28.3,9.4c0.1,0,0.3,0.1,0.4,0.1s0.3,0.1,0.4,0.1
+						c6.7,2,26.9,14.8,41.6,11C-599-254.2-587.7-293.4-592.1-323.9z"/>
+					<path id="Path_153_1_" style="fill:#572A08;" d="M-649.4-367L-649.4-367l0.1,0c12.7-3.2,24.1-1.4,33.4,6.3
+						c9.3,7.7,15.7,21.1,18.1,38.6c1.9,14.8,0.2,29.8-5.1,43.7c-6.9,17.7-16.8,23.7-23.5,25.5c-4.2,1.2-9.8,0.7-16.5-1.2
+						c-5.3-1.6-10.5-3.6-15.5-5.9c-2.4-1.1-4.9-2.1-7.5-3c-0.1,0-0.2-0.1-0.4-0.1l-0.1,0l-0.1,0c-0.1,0-0.2-0.1-0.4-0.1
+						c-2.4-0.5-4.7-0.9-7.1-1.1c-4-0.4-7.9-1-11.8-2c-3.3-0.7-6.3-2.5-8.6-4.9c-4.5-5.6-5.1-14.6-4.9-21c0.4-9.2,2.2-18.3,5.2-27
+						c3.5-10.7,9-20.7,16.1-29.4C-670.5-357.7-660.5-364.1-649.4-367 M-648.5-371.3L-648.5-371.3L-648.5-371.3
+						c-23.8,6.5-39.1,27.4-47.3,51c-6.1,17.8-8.3,41.1,0.1,52c2.5,2.9,5.8,4.9,9.5,5.8c6.8,2.1,14.9,2.4,18.9,3.5
+						c0.1,0,0.3,0.1,0.4,0.1c0.1,0,0.3,0.1,0.4,0.1c4.1,1.2,13.1,6.4,22.9,9.4c6.3,2,13,3,18.6,1.6c26-6.6,37.3-45.7,32.9-76.3
+						C-597.2-359.8-617.7-379.8-648.5-371.3L-648.5-371.3z"/>
+				</g>
+				<path id="Path_154_1_" style="fill:#782E29;" d="M-664.5-274.3c-4.1-0.9-8.4,4.8-9.6,12.7c-0.1,0.5-0.1,0.9-0.2,1.4
+					c2.3,0.3,4.6,0.7,6.9,1.2c0.1,0,0.3,0.1,0.4,0.1s0.3,0.1,0.4,0.1c2.5,0.9,4.9,1.9,7.2,3.1c0.1-0.5,0.2-1,0.3-1.5
+					C-657.7-265.7-660.2-273.4-664.5-274.3z"/>
+				<path id="Path_155_1_" style="fill:#782E29;" d="M-670.6-214.3h-7.4l9.1-57.4l7.9,1.8L-670.6-214.3z"/>
+			</g>
+			<path id="Path_156_1_" style="fill:#FFF0DE;" d="M-694.6-142.1c0,0-6.2,7.2-5.2,16.4c1.4,13,11.3,23.3,20.3,20.3
+				c9.3-3.2,10.8-8.7,9.7-17c-1.3-9.6-6.7-13.1-6.7-13.1L-694.6-142.1z"/>
+		</g>
+	</g>
+</g>
+</svg>

+ 51 - 0
dashboard-v6/src/assets/library/images/wikipali_logo_library.svg

@@ -0,0 +1,51 @@
+<svg id="wikipali_banner" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 221 54.417">
+  <g id="Group_12" data-name="Group 12" transform="translate(-396 -320)">
+    <g id="Group_2" data-name="Group 2" transform="translate(396 320)">
+      <g id="Group_1" data-name="Group 1" transform="translate(39.472 12.369)">
+        <path id="Path_1" data-name="Path 1"
+          d="M252.239,132.886a1.184,1.184,0,0,1-.733-.244,1.144,1.144,0,0,1-.424-.63l-3.447-12.729a.825.825,0,0,1-.026-.206.683.683,0,0,1,.155-.411.655.655,0,0,1,.539-.257h1.234a1.129,1.129,0,0,1,.721.244,1.171,1.171,0,0,1,.411.63l1.7,6.97q.026.1.8,4.063a.046.046,0,0,0,.053.051.046.046,0,0,0,.051-.051q.925-3.96.952-4.063l1.8-6.97a1.187,1.187,0,0,1,1.132-.874h1a1.187,1.187,0,0,1,1.13.874l1.851,6.97q.153.643.475,1.993t.5,2.071a.046.046,0,0,0,.051.051.084.084,0,0,0,.078-.051q.076-.464.36-1.865t.462-2.2l1.647-6.97a1.187,1.187,0,0,1,1.132-.874h1.028a.66.66,0,0,1,.54.257.723.723,0,0,1,.155.437.813.813,0,0,1-.026.18l-3.266,12.729a1.143,1.143,0,0,1-.424.63,1.174,1.174,0,0,1-.733.244h-1.8a1.129,1.129,0,0,1-.721-.244,1.165,1.165,0,0,1-.411-.63l-1.62-6.3q-.258-1.028-.9-4.114a.1.1,0,0,0-.091-.051.091.091,0,0,0-.089.051q-.514,2.726-.9,4.142l-1.543,6.275a1.166,1.166,0,0,1-.411.63,1.12,1.12,0,0,1-.721.244h-1.671Z"
+          transform="translate(-247.61 -111.903)" fill="#fff" />
+        <path id="Path_2" data-name="Path 2"
+          d="M395.183,81.944a1.988,1.988,0,0,1-1.389.5,1.942,1.942,0,0,1-1.376-.5,1.661,1.661,0,0,1-.539-1.274,1.692,1.692,0,0,1,.539-1.3,1.94,1.94,0,0,1,1.376-.5,1.978,1.978,0,0,1,1.389.5,1.673,1.673,0,0,1,.553,1.3A1.644,1.644,0,0,1,395.183,81.944ZM393.216,99.65a.92.92,0,0,1-.926-.926V86.072a.864.864,0,0,1,.269-.63.892.892,0,0,1,.657-.269h1.157a.9.9,0,0,1,.657.269.865.865,0,0,1,.271.63V98.723a.923.923,0,0,1-.928.926Z"
+          transform="translate(-368.881 -78.666)" fill="#fff" />
+        <path id="Path_3" data-name="Path 3"
+          d="M444.3,98.574a.92.92,0,0,1-.926-.926V78.489a.869.869,0,0,1,.269-.63.892.892,0,0,1,.657-.269H445.4a.9.9,0,0,1,.657.269.863.863,0,0,1,.269.63v12.6c0,.018.013.026.038.026a.091.091,0,0,0,.065-.026l5.092-6.3a1.831,1.831,0,0,1,1.492-.693h1.518a.387.387,0,0,1,.373.244.382.382,0,0,1-.064.45l-4.269,5.092a.167.167,0,0,0,0,.18l4.937,7.74a.49.49,0,0,1,.078.257.481.481,0,0,1-.078.257.448.448,0,0,1-.437.257h-1.465a1.55,1.55,0,0,1-1.389-.772l-3.4-5.683c-.033-.069-.077-.077-.129-.026l-2.288,2.649a.336.336,0,0,0-.078.206v2.7a.92.92,0,0,1-.926.926Z"
+          transform="translate(-412.163 -77.59)" fill="#fff" />
+        <path id="Path_4" data-name="Path 4"
+          d="M540.613,81.944a1.987,1.987,0,0,1-1.388.5,1.94,1.94,0,0,1-1.376-.5,1.661,1.661,0,0,1-.539-1.274,1.692,1.692,0,0,1,.539-1.3,1.942,1.942,0,0,1,1.376-.5,1.977,1.977,0,0,1,1.388.5,1.673,1.673,0,0,1,.553,1.3A1.649,1.649,0,0,1,540.613,81.944ZM538.646,99.65a.923.923,0,0,1-.928-.926V86.072a.86.86,0,0,1,.271-.63.892.892,0,0,1,.657-.269H539.8a.9.9,0,0,1,.657.269.863.863,0,0,1,.269.63V98.723a.92.92,0,0,1-.926.926Z"
+          transform="translate(-491.128 -78.666)" fill="#fff" />
+        <path id="Path_5" data-name="Path 5"
+          d="M589.735,137.187a.92.92,0,0,1-.925-.925V117.746a.92.92,0,0,1,.925-.926h.642a1.016,1.016,0,0,1,.682.257,1.136,1.136,0,0,1,.373.642l.077.642c.018.035.038.053.064.053a.1.1,0,0,0,.065-.026,7.051,7.051,0,0,1,4.424-1.929,5,5,0,0,1,4.231,1.993,8.742,8.742,0,0,1,1.5,5.388,10.164,10.164,0,0,1-.515,3.3,6.991,6.991,0,0,1-1.389,2.469,6.473,6.473,0,0,1-1.993,1.5,5.339,5.339,0,0,1-2.353.54,5.846,5.846,0,0,1-3.729-1.569.031.031,0,0,0-.051,0,.075.075,0,0,0-.025.053l.076,2.366v3.754a.92.92,0,0,1-.926.925h-1.159Zm5.246-8.023a3.153,3.153,0,0,0,2.65-1.4,6.426,6.426,0,0,0,1.028-3.871q0-4.912-3.394-4.912a5.135,5.135,0,0,0-3.368,1.7.245.245,0,0,0-.078.18v6.866a.243.243,0,0,0,.078.18A4.734,4.734,0,0,0,594.981,129.164Z"
+          transform="translate(-534.418 -110.264)" fill="#fff" />
+        <path id="Path_6" data-name="Path 6"
+          d="M691.509,105.4a4.264,4.264,0,0,1-3.073-1.143,4.27,4.27,0,0,1,.85-6.609,16.4,16.4,0,0,1,6.518-1.787c.069,0,.1-.041.1-.129q-.1-3.032-2.751-3.034a7.157,7.157,0,0,0-3.419,1,.864.864,0,0,1-.669.089.811.811,0,0,1-.539-.424l-.257-.462a.955.955,0,0,1-.089-.706.822.822,0,0,1,.424-.553,10.423,10.423,0,0,1,5.066-1.44,4.826,4.826,0,0,1,3.96,1.594,6.988,6.988,0,0,1,1.312,4.551v7.792a.923.923,0,0,1-.928.926h-.642a1.008,1.008,0,0,1-.681-.257,1.118,1.118,0,0,1-.373-.642l-.1-.721c-.018-.033-.038-.053-.065-.053s-.046.018-.064.053A7.155,7.155,0,0,1,691.509,105.4Zm-.977-18.027a.92.92,0,0,1-.926-.926v-.206a.92.92,0,0,1,.926-.926h6.3a.92.92,0,0,1,.925.926v.206a.92.92,0,0,1-.925.926Zm1.9,15.637a5.287,5.287,0,0,0,3.4-1.6.278.278,0,0,0,.077-.206V97.9c0-.086-.033-.12-.1-.1a11.688,11.688,0,0,0-4.346,1.17,2.336,2.336,0,0,0-1.286,2.045,1.82,1.82,0,0,0,.617,1.518A2.582,2.582,0,0,0,692.435,103.015Z"
+          transform="translate(-617.157 -84.088)" fill="#fff" />
+        <path id="Path_7" data-name="Path 7"
+          d="M792.08,98.907a2.68,2.68,0,0,1-2.3-.952,4.607,4.607,0,0,1-.708-2.779V78.489a.865.865,0,0,1,.271-.63A.893.893,0,0,1,790,77.59h1.157a.9.9,0,0,1,.657.269.865.865,0,0,1,.271.63V95.333a1.12,1.12,0,0,0,.411,1.028c.034.018.11.061.231.129s.206.12.257.155.12.081.206.14a.691.691,0,0,1,.193.193.438.438,0,0,1,.064.231l.1.566a.736.736,0,0,1,.026.18.96.96,0,0,1-.155.54.792.792,0,0,1-.591.386C792.585,98.9,792.336,98.907,792.08,98.907Z"
+          transform="translate(-702.754 -77.59)" fill="#fff" />
+        <path id="Path_8" data-name="Path 8"
+          d="M840.663,81.944a1.988,1.988,0,0,1-1.389.5,1.94,1.94,0,0,1-1.376-.5,1.661,1.661,0,0,1-.539-1.274,1.692,1.692,0,0,1,.539-1.3,1.939,1.939,0,0,1,1.376-.5,1.978,1.978,0,0,1,1.389.5,1.673,1.673,0,0,1,.553,1.3A1.649,1.649,0,0,1,840.663,81.944ZM838.7,99.65a.92.92,0,0,1-.926-.926V86.072a.864.864,0,0,1,.269-.63.892.892,0,0,1,.657-.269h1.157a.9.9,0,0,1,.657.269.865.865,0,0,1,.271.63V98.723a.923.923,0,0,1-.928.926Z"
+          transform="translate(-743.346 -78.666)" fill="#fff" />
+      </g>
+      <path id="Path_9" data-name="Path 9"
+        d="M125.632,125.382a1.531,1.531,0,0,1-1.532-1.532v-5.532c0-8.629,4.134-13.579,11.342-13.579a1.532,1.532,0,0,1,0,3.064c-5.493,0-8.28,3.537-8.28,10.517v5.532A1.529,1.529,0,0,1,125.632,125.382Z"
+        transform="translate(-104.317 -88.043)" fill="#f1ca23" />
+      <path id="Path_10" data-name="Path 10"
+        d="M144.722,179.085a1.532,1.532,0,1,1,0-3.064c2.987,0,5.076-4,5.076-9.727V149.842a1.532,1.532,0,0,1,3.064,0v16.452C152.86,175.13,148.773,179.085,144.722,179.085Z"
+        transform="translate(-120.364 -124.667)" fill="#f1ca23" />
+      <path id="Path_11" data-name="Path 11"
+        d="M84.262,37.339a1.531,1.531,0,0,1-1.532-1.532V1.532a1.532,1.532,0,0,1,3.064,0V35.809A1.531,1.531,0,0,1,84.262,37.339Z"
+        transform="translate(-69.542)" fill="#f1ca23" />
+      <path id="Path_12" data-name="Path 12"
+        d="M42.892,37.339a1.531,1.531,0,0,1-1.532-1.532V1.532a1.532,1.532,0,0,1,3.064,0V35.809A1.531,1.531,0,0,1,42.892,37.339Z"
+        transform="translate(-34.767)" fill="#f1ca23" />
+      <path id="Path_13" data-name="Path 13"
+        d="M1.532,37.339A1.531,1.531,0,0,1,0,35.808V1.532a1.532,1.532,0,0,1,3.064,0V35.809A1.533,1.533,0,0,1,1.532,37.339Z"
+        fill="#f1ca23" />
+    </g>
+    <text id="studio" transform="translate(542 353.2)" fill="#fff" font-size="24"
+      font-family="NotoSans-ExtraLight, Noto Sans" font-weight="200">
+      <tspan x="0" y="0">Library</tspan>
+    </text>
+  </g>
+</svg>

BIN
dashboard-v6/src/assets/nut/code.png


+ 22 - 0
dashboard-v6/src/assets/studio/images/wikipali_banner.svg

@@ -0,0 +1,22 @@
+<svg id="wikipali_banner" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 221 54.417">
+  <g id="Group_12" data-name="Group 12" transform="translate(-396 -320)">
+    <g id="Group_2" data-name="Group 2" transform="translate(396 320)">
+      <g id="Group_1" data-name="Group 1" transform="translate(39.472 12.369)">
+        <path id="Path_1" data-name="Path 1" d="M252.239,132.886a1.184,1.184,0,0,1-.733-.244,1.144,1.144,0,0,1-.424-.63l-3.447-12.729a.825.825,0,0,1-.026-.206.683.683,0,0,1,.155-.411.655.655,0,0,1,.539-.257h1.234a1.129,1.129,0,0,1,.721.244,1.171,1.171,0,0,1,.411.63l1.7,6.97q.026.1.8,4.063a.046.046,0,0,0,.053.051.046.046,0,0,0,.051-.051q.925-3.96.952-4.063l1.8-6.97a1.187,1.187,0,0,1,1.132-.874h1a1.187,1.187,0,0,1,1.13.874l1.851,6.97q.153.643.475,1.993t.5,2.071a.046.046,0,0,0,.051.051.084.084,0,0,0,.078-.051q.076-.464.36-1.865t.462-2.2l1.647-6.97a1.187,1.187,0,0,1,1.132-.874h1.028a.66.66,0,0,1,.54.257.723.723,0,0,1,.155.437.813.813,0,0,1-.026.18l-3.266,12.729a1.143,1.143,0,0,1-.424.63,1.174,1.174,0,0,1-.733.244h-1.8a1.129,1.129,0,0,1-.721-.244,1.165,1.165,0,0,1-.411-.63l-1.62-6.3q-.258-1.028-.9-4.114a.1.1,0,0,0-.091-.051.091.091,0,0,0-.089.051q-.514,2.726-.9,4.142l-1.543,6.275a1.166,1.166,0,0,1-.411.63,1.12,1.12,0,0,1-.721.244h-1.671Z" transform="translate(-247.61 -111.903)" fill="#fff"/>
+        <path id="Path_2" data-name="Path 2" d="M395.183,81.944a1.988,1.988,0,0,1-1.389.5,1.942,1.942,0,0,1-1.376-.5,1.661,1.661,0,0,1-.539-1.274,1.692,1.692,0,0,1,.539-1.3,1.94,1.94,0,0,1,1.376-.5,1.978,1.978,0,0,1,1.389.5,1.673,1.673,0,0,1,.553,1.3A1.644,1.644,0,0,1,395.183,81.944ZM393.216,99.65a.92.92,0,0,1-.926-.926V86.072a.864.864,0,0,1,.269-.63.892.892,0,0,1,.657-.269h1.157a.9.9,0,0,1,.657.269.865.865,0,0,1,.271.63V98.723a.923.923,0,0,1-.928.926Z" transform="translate(-368.881 -78.666)" fill="#fff"/>
+        <path id="Path_3" data-name="Path 3" d="M444.3,98.574a.92.92,0,0,1-.926-.926V78.489a.869.869,0,0,1,.269-.63.892.892,0,0,1,.657-.269H445.4a.9.9,0,0,1,.657.269.863.863,0,0,1,.269.63v12.6c0,.018.013.026.038.026a.091.091,0,0,0,.065-.026l5.092-6.3a1.831,1.831,0,0,1,1.492-.693h1.518a.387.387,0,0,1,.373.244.382.382,0,0,1-.064.45l-4.269,5.092a.167.167,0,0,0,0,.18l4.937,7.74a.49.49,0,0,1,.078.257.481.481,0,0,1-.078.257.448.448,0,0,1-.437.257h-1.465a1.55,1.55,0,0,1-1.389-.772l-3.4-5.683c-.033-.069-.077-.077-.129-.026l-2.288,2.649a.336.336,0,0,0-.078.206v2.7a.92.92,0,0,1-.926.926Z" transform="translate(-412.163 -77.59)" fill="#fff"/>
+        <path id="Path_4" data-name="Path 4" d="M540.613,81.944a1.987,1.987,0,0,1-1.388.5,1.94,1.94,0,0,1-1.376-.5,1.661,1.661,0,0,1-.539-1.274,1.692,1.692,0,0,1,.539-1.3,1.942,1.942,0,0,1,1.376-.5,1.977,1.977,0,0,1,1.388.5,1.673,1.673,0,0,1,.553,1.3A1.649,1.649,0,0,1,540.613,81.944ZM538.646,99.65a.923.923,0,0,1-.928-.926V86.072a.86.86,0,0,1,.271-.63.892.892,0,0,1,.657-.269H539.8a.9.9,0,0,1,.657.269.863.863,0,0,1,.269.63V98.723a.92.92,0,0,1-.926.926Z" transform="translate(-491.128 -78.666)" fill="#fff"/>
+        <path id="Path_5" data-name="Path 5" d="M589.735,137.187a.92.92,0,0,1-.925-.925V117.746a.92.92,0,0,1,.925-.926h.642a1.016,1.016,0,0,1,.682.257,1.136,1.136,0,0,1,.373.642l.077.642c.018.035.038.053.064.053a.1.1,0,0,0,.065-.026,7.051,7.051,0,0,1,4.424-1.929,5,5,0,0,1,4.231,1.993,8.742,8.742,0,0,1,1.5,5.388,10.164,10.164,0,0,1-.515,3.3,6.991,6.991,0,0,1-1.389,2.469,6.473,6.473,0,0,1-1.993,1.5,5.339,5.339,0,0,1-2.353.54,5.846,5.846,0,0,1-3.729-1.569.031.031,0,0,0-.051,0,.075.075,0,0,0-.025.053l.076,2.366v3.754a.92.92,0,0,1-.926.925h-1.159Zm5.246-8.023a3.153,3.153,0,0,0,2.65-1.4,6.426,6.426,0,0,0,1.028-3.871q0-4.912-3.394-4.912a5.135,5.135,0,0,0-3.368,1.7.245.245,0,0,0-.078.18v6.866a.243.243,0,0,0,.078.18A4.734,4.734,0,0,0,594.981,129.164Z" transform="translate(-534.418 -110.264)" fill="#fff"/>
+        <path id="Path_6" data-name="Path 6" d="M691.509,105.4a4.264,4.264,0,0,1-3.073-1.143,4.27,4.27,0,0,1,.85-6.609,16.4,16.4,0,0,1,6.518-1.787c.069,0,.1-.041.1-.129q-.1-3.032-2.751-3.034a7.157,7.157,0,0,0-3.419,1,.864.864,0,0,1-.669.089.811.811,0,0,1-.539-.424l-.257-.462a.955.955,0,0,1-.089-.706.822.822,0,0,1,.424-.553,10.423,10.423,0,0,1,5.066-1.44,4.826,4.826,0,0,1,3.96,1.594,6.988,6.988,0,0,1,1.312,4.551v7.792a.923.923,0,0,1-.928.926h-.642a1.008,1.008,0,0,1-.681-.257,1.118,1.118,0,0,1-.373-.642l-.1-.721c-.018-.033-.038-.053-.065-.053s-.046.018-.064.053A7.155,7.155,0,0,1,691.509,105.4Zm-.977-18.027a.92.92,0,0,1-.926-.926v-.206a.92.92,0,0,1,.926-.926h6.3a.92.92,0,0,1,.925.926v.206a.92.92,0,0,1-.925.926Zm1.9,15.637a5.287,5.287,0,0,0,3.4-1.6.278.278,0,0,0,.077-.206V97.9c0-.086-.033-.12-.1-.1a11.688,11.688,0,0,0-4.346,1.17,2.336,2.336,0,0,0-1.286,2.045,1.82,1.82,0,0,0,.617,1.518A2.582,2.582,0,0,0,692.435,103.015Z" transform="translate(-617.157 -84.088)" fill="#fff"/>
+        <path id="Path_7" data-name="Path 7" d="M792.08,98.907a2.68,2.68,0,0,1-2.3-.952,4.607,4.607,0,0,1-.708-2.779V78.489a.865.865,0,0,1,.271-.63A.893.893,0,0,1,790,77.59h1.157a.9.9,0,0,1,.657.269.865.865,0,0,1,.271.63V95.333a1.12,1.12,0,0,0,.411,1.028c.034.018.11.061.231.129s.206.12.257.155.12.081.206.14a.691.691,0,0,1,.193.193.438.438,0,0,1,.064.231l.1.566a.736.736,0,0,1,.026.18.96.96,0,0,1-.155.54.792.792,0,0,1-.591.386C792.585,98.9,792.336,98.907,792.08,98.907Z" transform="translate(-702.754 -77.59)" fill="#fff"/>
+        <path id="Path_8" data-name="Path 8" d="M840.663,81.944a1.988,1.988,0,0,1-1.389.5,1.94,1.94,0,0,1-1.376-.5,1.661,1.661,0,0,1-.539-1.274,1.692,1.692,0,0,1,.539-1.3,1.939,1.939,0,0,1,1.376-.5,1.978,1.978,0,0,1,1.389.5,1.673,1.673,0,0,1,.553,1.3A1.649,1.649,0,0,1,840.663,81.944ZM838.7,99.65a.92.92,0,0,1-.926-.926V86.072a.864.864,0,0,1,.269-.63.892.892,0,0,1,.657-.269h1.157a.9.9,0,0,1,.657.269.865.865,0,0,1,.271.63V98.723a.923.923,0,0,1-.928.926Z" transform="translate(-743.346 -78.666)" fill="#fff"/>
+      </g>
+      <path id="Path_9" data-name="Path 9" d="M125.632,125.382a1.531,1.531,0,0,1-1.532-1.532v-5.532c0-8.629,4.134-13.579,11.342-13.579a1.532,1.532,0,0,1,0,3.064c-5.493,0-8.28,3.537-8.28,10.517v5.532A1.529,1.529,0,0,1,125.632,125.382Z" transform="translate(-104.317 -88.043)" fill="#f1ca23"/>
+      <path id="Path_10" data-name="Path 10" d="M144.722,179.085a1.532,1.532,0,1,1,0-3.064c2.987,0,5.076-4,5.076-9.727V149.842a1.532,1.532,0,0,1,3.064,0v16.452C152.86,175.13,148.773,179.085,144.722,179.085Z" transform="translate(-120.364 -124.667)" fill="#f1ca23"/>
+      <path id="Path_11" data-name="Path 11" d="M84.262,37.339a1.531,1.531,0,0,1-1.532-1.532V1.532a1.532,1.532,0,0,1,3.064,0V35.809A1.531,1.531,0,0,1,84.262,37.339Z" transform="translate(-69.542)" fill="#f1ca23"/>
+      <path id="Path_12" data-name="Path 12" d="M42.892,37.339a1.531,1.531,0,0,1-1.532-1.532V1.532a1.532,1.532,0,0,1,3.064,0V35.809A1.531,1.531,0,0,1,42.892,37.339Z" transform="translate(-34.767)" fill="#f1ca23"/>
+      <path id="Path_13" data-name="Path 13" d="M1.532,37.339A1.531,1.531,0,0,1,0,35.808V1.532a1.532,1.532,0,0,1,3.064,0V35.809A1.533,1.533,0,0,1,1.532,37.339Z" fill="#f1ca23"/>
+    </g>
+    <text id="studio" transform="translate(542 353.2)" fill="#fff" font-size="27" font-family="NotoSans-ExtraLight, Noto Sans" font-weight="200"><tspan x="0" y="0">studio</tspan></text>
+  </g>
+</svg>

+ 12 - 0
dashboard-v6/src/components/README.md

@@ -0,0 +1,12 @@
+# 组件
+
+## 目录
+
+某栏目的专用组件放在以该栏目命名的目录下
+
+- `libray` 前台(阅读)的栏目
+  - `栏目名称` 某栏目的专用组件
+  - `组件1.tsx` libray 下面的栏目的公共组件
+- `studio` 用户后台(课程发布等)
+
+nut目录为练习用途。里面的内容可能会被删除。**线上不要使用**。

+ 59 - 0
dashboard-v6/src/components/admin/HeadBar.tsx

@@ -0,0 +1,59 @@
+import { Link } from "react-router-dom";
+import { Input, Layout, Space } from "antd";
+
+import img_banner from "../../assets/studio/images/wikipali_banner.svg";
+import UiLangSelect from "../general/UiLangSelect";
+import SignInAvatar from "../auth/SignInAvatar";
+import ToLibrary from "../auth/ToLibrary";
+import ThemeSelect from "../general/ThemeSelect";
+
+const { Search } = Input;
+const { Header } = Layout;
+
+const onSearch = (value: string) => console.log(value);
+
+const HeadBarWidget = () => {
+  return (
+    <Header
+      className="header"
+      style={{
+        lineHeight: "44px",
+        height: 44,
+        paddingLeft: 10,
+        paddingRight: 10,
+      }}
+    >
+      <div
+        style={{
+          display: "flex",
+          width: "100%",
+          justifyContent: "space-between",
+        }}
+      >
+        <div style={{ width: 80 }}>
+          <Link to="/">
+            <img alt="code" style={{ height: 36 }} src={img_banner} />
+          </Link>
+        </div>
+        <div style={{ width: 500, lineHeight: 44 }}>
+          <Search
+            disabled
+            placeholder="input search text"
+            onSearch={onSearch}
+            style={{ width: "100%" }}
+          />
+        </div>
+        <div>
+          <Space>
+            <ToLibrary />
+            <SignInAvatar />
+            <UiLangSelect />
+            <ThemeSelect />
+          </Space>
+        </div>
+      </div>
+    </Header>
+  );
+};
+
+export default HeadBarWidget;

+ 94 - 0
dashboard-v6/src/components/admin/LeftSider.tsx

@@ -0,0 +1,94 @@
+import { Link } from "react-router-dom";
+import type { MenuProps } from "antd";
+import { Affix, Layout } from "antd";
+import { Menu } from "antd";
+import { AppstoreOutlined, HomeOutlined } from "@ant-design/icons";
+
+const { Sider } = Layout;
+
+const onClick: MenuProps["onClick"] = (e) => {
+  console.log("click ", e);
+};
+
+type IWidgetHeadBar = {
+  selectedKeys?: string;
+};
+const LeftSiderWidget = ({ selectedKeys = "" }: IWidgetHeadBar) => {
+  const items: MenuProps["items"] = [
+    {
+      label: "API",
+      key: "api",
+      icon: <HomeOutlined />,
+      children: [
+        {
+          label: <Link to="/admin/api/dashboard">dashboard</Link>,
+          key: "dashboard",
+        },
+      ],
+    },
+    {
+      label: "管理",
+      key: "manage",
+      icon: <HomeOutlined />,
+      children: [
+        {
+          label: <Link to="/admin/relation/list">Relation</Link>,
+          key: "relation",
+        },
+        {
+          label: <Link to="/admin/nissaya-ending/list">Nissaya Ending</Link>,
+          key: "nissaya-ending",
+        },
+        {
+          label: <Link to="/admin/ai/list">AI</Link>,
+          key: "ai",
+        },
+        {
+          label: "Dictionary",
+          key: "dict",
+          children: [
+            {
+              label: <Link to="/admin/dictionary/list">List</Link>,
+              key: "list",
+            },
+            {
+              label: <Link to="/admin/dictionary/preference">Preference</Link>,
+              key: "preference",
+            },
+          ],
+        },
+        {
+          label: <Link to="/admin/users/list">users</Link>,
+          key: "users",
+        },
+        {
+          label: <Link to="/admin/invite/list">invite</Link>,
+          key: "invite",
+        },
+      ],
+    },
+    {
+      label: "统计",
+      key: "advance",
+      icon: <AppstoreOutlined />,
+      children: [],
+    },
+  ];
+
+  return (
+    <Affix offsetTop={0}>
+      <Sider width={200} breakpoint="lg" className="site-layout-background">
+        <Menu
+          theme="light"
+          onClick={onClick}
+          defaultSelectedKeys={[selectedKeys]}
+          defaultOpenKeys={["basic", "advance", "collaboration"]}
+          mode="inline"
+          items={items}
+        />
+      </Sider>
+    </Affix>
+  );
+};
+
+export default LeftSiderWidget;

+ 64 - 0
dashboard-v6/src/components/admin/api/ApiDelayHour.tsx

@@ -0,0 +1,64 @@
+import React, { useEffect, useState } from "react";
+import { Column } from "@ant-design/plots";
+import { put } from "../../../request";
+import { StatisticCard } from "@ant-design/pro-components";
+
+interface IApiDelay {
+  date: string;
+  value: number;
+}
+interface IApiDelayResponse {
+  ok: boolean;
+  message: string;
+  data: IApiDelay[];
+}
+interface IApiRequest {
+  api: string;
+  item: string;
+}
+
+interface IWidget {
+  title?: React.ReactNode;
+  type: "average" | "count" | "delay";
+  api?: string;
+}
+
+const ApiDelayHourWidget = ({ title, type, api = "all" }: IWidget) => {
+  const [delayData, setDelayData] = useState<IApiDelay[]>([]);
+
+  useEffect(() => {
+    put<IApiRequest, IApiDelayResponse>("/v2/api/10", {
+      api: api,
+      item: type,
+    }).then((json) => {
+      console.log("data", json.data);
+      setDelayData(json.data);
+    });
+  }, []);
+
+  const config = {
+    data: delayData,
+    xField: "date",
+    yField: "value",
+    seriesField: "",
+    xAxis: {
+      label: {
+        autoHide: true,
+        autoRotate: false,
+      },
+    },
+  };
+
+  return (
+    <StatisticCard
+      statistic={{
+        title: title,
+        value: "",
+        suffix: "/ ms",
+      }}
+      chart={<Column {...config} height={300} />}
+    />
+  );
+};
+
+export default ApiDelayHourWidget;

+ 83 - 0
dashboard-v6/src/components/admin/api/ApiGauge.tsx

@@ -0,0 +1,83 @@
+import { useEffect, useRef, useState } from "react";
+import { Gauge } from "@ant-design/plots";
+import { get } from "../../../request";
+import { StatisticCard } from "@ant-design/pro-components";
+
+interface IApiResponse {
+  ok: boolean;
+  message: string;
+  data: number;
+}
+const ApiGaugeWidget = () => {
+  const min = 0;
+  const max = 1;
+  const [percent, setPercent] = useState<number>(0);
+  const [delay, setDelay] = useState<number>(0);
+  const maxAxis = 5000; //最大量程-毫秒
+
+  useEffect(() => {
+    const timer = setInterval(() => {
+      get<IApiResponse>("/v2/api/10?item=average").then((json) => {
+        setPercent(json.data / maxAxis);
+        setDelay(json.data);
+      });
+    }, 1000 * 5);
+    return () => {
+      clearInterval(timer);
+    };
+  }, []);
+
+  const graphRef: any = useRef(null);
+
+  const config = {
+    percent: percent,
+    range: {
+      ticks: [min, max],
+      color: ["l(0) 0:#30BF78 0.5:#FAAD14 1:#F4664A"],
+    },
+    indicator: {
+      pointer: {
+        style: {
+          stroke: "#D0D0D0",
+        },
+      },
+      pin: {
+        style: {
+          stroke: "#D0D0D0",
+        },
+      },
+    },
+    axis: {
+      label: {
+        formatter(v: any) {
+          return Number(v) * maxAxis;
+        },
+      },
+      subTickLine: {
+        count: 3,
+      },
+    },
+  };
+
+  return (
+    <StatisticCard
+      style={{ width: 400 }}
+      statistic={{
+        title: "平均相应时间",
+        value: delay,
+        suffix: "/ ms",
+      }}
+      chart={
+        <Gauge
+          ref={graphRef}
+          {...config}
+          onReady={(chart) => {
+            graphRef.current = chart;
+          }}
+        />
+      }
+    />
+  );
+};
+
+export default ApiGaugeWidget;

+ 42 - 0
dashboard-v6/src/components/admin/relation/CaseSelect.tsx

@@ -0,0 +1,42 @@
+import { ProFormSelect } from "@ant-design/pro-components";
+
+import { useIntl } from "react-intl";
+
+interface IWidget {
+  name?: string;
+  width?: number | "md" | "sm" | "xl" | "xs" | "lg";
+}
+const CaseSelectWidget = ({ name = "case", width = "md" }: IWidget) => {
+  const intl = useIntl();
+  const _case = [
+    "nom",
+    "acc",
+    "gen",
+    "dat",
+    "inst",
+    "abl",
+    "loc",
+    "ger",
+    "adv",
+  ];
+  const caseOptions = _case.map((item) => {
+    return {
+      value: item,
+      label: intl.formatMessage({
+        id: `dict.fields.type.${item}.label`,
+        defaultMessage: item,
+      }),
+    };
+  });
+
+  return (
+    <ProFormSelect
+      options={caseOptions}
+      width={width}
+      name={name}
+      label={intl.formatMessage({ id: "forms.fields.case.label" })}
+    />
+  );
+};
+
+export default CaseSelectWidget;

+ 100 - 0
dashboard-v6/src/components/admin/relation/DataImport.tsx

@@ -0,0 +1,100 @@
+import { ModalForm, ProFormUploadDragger } from "@ant-design/pro-components";
+import { Form, message } from "antd";
+
+import { API_HOST, get } from "../../../request";
+import { UploadFile } from "antd/es/upload/interface";
+import { IAttachmentResponse } from "../../api/Attachments";
+import modal from "antd/lib/modal";
+import { useIntl } from "react-intl";
+
+interface INissayaEndingUpload {
+  filename: UploadFile<IAttachmentResponse>[];
+}
+export interface INissayaEndingImportResponse {
+  ok: boolean;
+  message: string;
+  data: {
+    success: number;
+    fail: number;
+  };
+}
+
+interface IWidget {
+  title?: string;
+  url: string;
+  urlExtra?: string;
+  trigger?: JSX.Element;
+  onSuccess?: Function;
+}
+const DataImportWidget = ({
+  title,
+  url,
+  urlExtra,
+  trigger = <>{"trigger"}</>,
+  onSuccess,
+}: IWidget) => {
+  const intl = useIntl();
+  const [form] = Form.useForm<INissayaEndingUpload>();
+  const formTitle = title ? title : intl.formatMessage({ id: "labels.upload" });
+  return (
+    <ModalForm<INissayaEndingUpload>
+      title={formTitle}
+      trigger={trigger}
+      form={form}
+      autoFocusFirstInput
+      modalProps={{
+        destroyOnClose: true,
+        onCancel: () => console.log("run"),
+      }}
+      submitTimeout={2000}
+      onFinish={async (values) => {
+        console.log("values", values);
+        let _filename: string = "";
+
+        if (
+          typeof values.filename === "undefined" ||
+          values.filename.length === 0
+        ) {
+          _filename = "";
+        } else if (typeof values.filename[0].response === "undefined") {
+          _filename = values.filename[0].uid;
+        } else {
+          _filename = values.filename[0].response.data.filename;
+        }
+
+        const queryUrl = `${url}?filename=${_filename}&${urlExtra}`;
+        const res = await get<INissayaEndingImportResponse>(queryUrl);
+        if (res.ok) {
+          if (res.data.fail > 0) {
+            modal.info({
+              title: "error",
+              content: `成功${res.data.success}-失败${res.data.fail}\n${res.message}`,
+            });
+          } else {
+            message.success(`成功导入${res.data.success}`);
+          }
+
+          if (typeof onSuccess !== "undefined") {
+            onSuccess();
+          }
+        } else {
+          message.error(res.message);
+        }
+
+        return true;
+      }}
+    >
+      <ProFormUploadDragger
+        max={1}
+        label="请确保您的xlsx文件是用导出功能导出的。word为空可以删除该词条。使用其他studio导出的数据,请将channel_id设置为空。否则该术语将被忽略。"
+        name="filename"
+        fieldProps={{
+          name: "file",
+        }}
+        action={`${API_HOST}/api/v2/attachments?is_tmp=true`}
+      />
+    </ModalForm>
+  );
+};
+
+export default DataImportWidget;

+ 89 - 0
dashboard-v6/src/components/admin/relation/GrammarSelect.tsx

@@ -0,0 +1,89 @@
+import { ProFormSelect } from "@ant-design/pro-components";
+
+import { useIntl } from "react-intl";
+
+interface IWidget {
+  name: string;
+  trigger?: JSX.Element;
+  id?: string;
+  hidden?: boolean;
+  onSuccess?: Function;
+}
+const GrammarSelectWidget = ({
+  name,
+  trigger = <>{"trigger"}</>,
+  id,
+  hidden = false,
+  onSuccess,
+}: IWidget) => {
+  const intl = useIntl();
+  const _verb = [
+    "n",
+    "ti",
+    "v",
+    "v:ind",
+    "ind",
+    "sg",
+    "pl",
+    "nom",
+    "acc",
+    "gen",
+    "dat",
+    "inst",
+    "voc",
+    "abl",
+    "loc",
+    "base",
+    "imp",
+    "opt",
+    "pres",
+    "aor",
+    "fut",
+    "1p",
+    "2p",
+    "3p",
+    "prp",
+    "pp",
+    "grd",
+    "fpp",
+    "vdn",
+    "ger",
+    "inf",
+    "adj",
+    "pron",
+    "caus",
+    "num",
+    "adv",
+    "conj",
+    "pre",
+    "suf",
+    "ti:base",
+    "n:base",
+    "v:base",
+    "vdn",
+  ];
+  const verbOptions = _verb.map((item) => {
+    return {
+      value: item,
+      label: intl.formatMessage({
+        id: `dict.fields.type.${item}.label`,
+        defaultMessage: item,
+      }),
+    };
+  });
+  return (
+    <ProFormSelect
+      hidden={hidden}
+      options={verbOptions}
+      fieldProps={{
+        mode: "tags",
+      }}
+      width="md"
+      name={name}
+      allowClear={false}
+      label={intl.formatMessage({ id: "forms.fields.case.label" })}
+    />
+  );
+};
+
+export default GrammarSelectWidget;

+ 128 - 0
dashboard-v6/src/components/admin/relation/NissayaEndingEdit.tsx

@@ -0,0 +1,128 @@
+import { ModalForm, ProForm, ProFormText } from "@ant-design/pro-components";
+import { Form, message } from "antd";
+
+import { useState } from "react";
+import { useIntl } from "react-intl";
+import {
+  INissayaEnding,
+  INissayaEndingRequest,
+  INissayaEndingResponse,
+} from "../../../pages/admin/nissaya-ending/list";
+import { get, post, put } from "../../../request";
+import LangSelect from "../../general/LangSelect";
+import GrammarSelect from "./GrammarSelect";
+
+interface IWidget {
+  trigger?: JSX.Element;
+  id?: string;
+  onSuccess?: Function;
+}
+const NissayaEndingWidget = ({
+  trigger = <>{"trigger"}</>,
+  id,
+  onSuccess,
+}: IWidget) => {
+  const [title, setTitle] = useState<string | undefined>(id ? "" : "新建");
+  const [form] = Form.useForm<INissayaEnding>();
+  const intl = useIntl();
+  return (
+    <ModalForm<INissayaEnding>
+      title={title}
+      trigger={trigger}
+      form={form}
+      autoFocusFirstInput
+      modalProps={{
+        destroyOnClose: true,
+        onCancel: () => console.log("run"),
+      }}
+      submitTimeout={2000}
+      onFinish={async (values) => {
+        const data = values;
+        data.from = {
+          spell: values.fromSpell,
+          case: values.fromCase ? values.fromCase : undefined,
+        };
+        let res: INissayaEndingResponse;
+        if (typeof id === "undefined") {
+          res = await post<INissayaEndingRequest, INissayaEndingResponse>(
+            `/v2/nissaya-ending`,
+            data
+          );
+        } else {
+          res = await put<INissayaEndingRequest, INissayaEndingResponse>(
+            `/v2/nissaya-ending/${id}`,
+            data
+          );
+        }
+        console.log(res);
+        if (res.ok) {
+          message.success("提交成功");
+          if (typeof onSuccess !== "undefined") {
+            onSuccess();
+          }
+        } else {
+          message.error(res.message);
+        }
+
+        return true;
+      }}
+      request={
+        id
+          ? async () => {
+              const res = await get<INissayaEndingResponse>(
+                `/v2/nissaya-ending/${id}`
+              );
+              console.log("nissaya-ending get", res);
+              if (res.ok) {
+                setTitle(res.data.ending);
+
+                return {
+                  id: id,
+                  ending: res.data.ending,
+                  relation: res.data.relation,
+                  from: res.data.from,
+                  fromCase: res.data.from?.case,
+                  fromSpell: res.data.from?.spell,
+                  lang: res.data.lang,
+                };
+              } else {
+                return {
+                  id: undefined,
+                  ending: "",
+                  relation: "",
+                  lang: "",
+                };
+              }
+            }
+          : undefined
+      }
+    >
+      <ProForm.Group>
+        <ProFormText
+          width="md"
+          name="ending"
+          label={intl.formatMessage({ id: "forms.fields.ending.label" })}
+          tooltip="最长为 24 位"
+        />
+        <LangSelect width="md" />
+      </ProForm.Group>
+      <ProForm.Group>
+        <GrammarSelect name="fromCase" />
+        <ProFormText
+          width="md"
+          name="fromSpell"
+          label={intl.formatMessage({ id: "buttons.spell" })}
+        />
+      </ProForm.Group>
+      <ProForm.Group>
+        <ProFormText
+          width="md"
+          name="relation"
+          label={intl.formatMessage({ id: "forms.fields.relation.label" })}
+        />
+      </ProForm.Group>
+    </ModalForm>
+  );
+};
+
+export default NissayaEndingWidget;

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio