diff --git a/.codegraph/.gitignore b/.codegraph/.gitignore index d20c0fe..5f220f1 100644 --- a/.codegraph/.gitignore +++ b/.codegraph/.gitignore @@ -1,5 +1,5 @@ -# CodeGraph data files — local to each machine, not for committing. -# Ignore everything in .codegraph/ except this file itself, so transient -# files (the database, daemon.pid, sockets, logs) never show up in git. -* -!.gitignore +# CodeGraph data files — local to each machine, not for committing. +# Ignore everything in .codegraph/ except this file itself, so transient +# files (the database, daemon.pid, sockets, logs) never show up in git. +* +!.gitignore diff --git a/NOTEBOOK_DEPLOYMENT_CHECKLIST.md b/NOTEBOOK_DEPLOYMENT_CHECKLIST.md index 432075b..5430306 100644 --- a/NOTEBOOK_DEPLOYMENT_CHECKLIST.md +++ b/NOTEBOOK_DEPLOYMENT_CHECKLIST.md @@ -1,313 +1,313 @@ -# 记事本模块部署检查清单 - -## 📋 部署前检查 - -### 1. 数据库准备 -- [ ] 执行 SQL 文件创建数据表 - ```bash - mysql -u用户名 -p数据库名 < sql/yz_platform_notebook.sql - ``` -- [ ] 验证表创建成功 - ```sql - SHOW TABLES LIKE 'yz_platform_notebook'; - DESC yz_platform_notebook; - ``` - -### 2. 后端代码检查 -- [✅] 模型文件已创建: `go/models/platform_notebook.go` -- [✅] 模型已注册: `go/models/init.go` -- [✅] 控制器已创建: `go/controllers/platform_notebook.go` -- [✅] 路由已注册: `go/routers/platform/platform.go` -- [✅] 代码编译通过 - -### 3. 前端代码检查 -- [✅] API文件已创建: `platform/src/api/notebook.js` -- [✅] 主页面已创建: `platform/src/views/apps/notebook/index.vue` -- [✅] 编辑器组件已创建: `platform/src/views/apps/notebook/components/edit.vue` -- [✅] WangEditor组件已创建: `platform/src/views/apps/notebook/components/WangEditor.vue` - -### 4. 依赖检查 -- [ ] 前端安装 WangEditor - ```bash - cd platform - npm install @wangeditor/editor - # 或 - yarn add @wangeditor/editor - ``` - -### 5. 菜单配置 -- [ ] 在平台管理后台添加记事本菜单项 - - 路径: `/apps/notebook` - - 组件: `apps/notebook/index.vue` - - 图标: `fa-solid fa-book` 或其他合适的图标 - - 标题: `记事本` 或 `我的笔记` - -## 🚀 部署步骤 - -### 后端部署 - -1. **停止服务** - ```bash - # 如果服务正在运行,先停止 - pkill -f "go run main.go" - ``` - -2. **编译代码** - ```bash - cd go - go build -o server - ``` - -3. **启动服务** - ```bash - ./server - # 或使用后台运行 - nohup ./server > server.log 2>&1 & - ``` - -### 前端部署 - -1. **安装依赖** - ```bash - cd platform - npm install - # 或 - yarn install - ``` - -2. **开发模式测试** - ```bash - npm run dev - # 或 - yarn dev - ``` - -3. **生产构建** - ```bash - npm run build - # 或 - yarn build - ``` - -## ✅ 功能测试 - -### 基础功能测试 - -1. **访问页面** - - [ ] 能够正常访问记事本页面 - - [ ] 页面布局正常显示 - - [ ] 左侧列表和右侧编辑器都能正常显示 - -2. **创建笔记** - - [ ] 点击"新建笔记"按钮 - - [ ] 输入标题和内容 - - [ ] 点击"创建"按钮 - - [ ] 创建成功,笔记出现在列表中 - -3. **编辑笔记** - - [ ] 点击列表中的笔记 - - [ ] 笔记内容正确加载到编辑器 - - [ ] 修改标题和内容 - - [ ] 点击"保存"按钮 - - [ ] 保存成功,列表中的笔记信息更新 - -4. **删除笔记** - - [ ] 点击笔记右侧的"更多"按钮 - - [ ] 点击"删除" - - [ ] 确认删除 - - [ ] 笔记从列表中移除 - -5. **搜索笔记** - - [ ] 在搜索框输入关键词 - - [ ] 列表自动过滤显示匹配的笔记 - - [ ] 清空搜索框,显示所有笔记 - -### 富文本编辑器测试 - -1. **文本格式** - - [ ] 加粗、斜体、下划线 - - [ ] 标题(H1-H6) - - [ ] 字体颜色和背景色 - -2. **列表和引用** - - [ ] 有序列表 - - [ ] 无序列表 - - [ ] 引用块 - -3. **插入内容** - - [ ] 插入链接 - - [ ] 插入代码块 - - [ ] 插入表格 - -4. **图片上传** (需要配置上传接口) - - [ ] 点击图片按钮 - - [ ] 选择图片文件 - - [ ] 图片正确上传并显示 - -### API测试 (使用浏览器控制台) - -```javascript -// 1. 在浏览器控制台加载测试脚本 -// 将 test-api.js 的内容粘贴到控制台 - -// 2. 运行完整测试 -await NotebookTest.runFullTest(); - -// 3. 或单独测试各个功能 -await NotebookTest.create(); // 创建笔记 -await NotebookTest.list(); // 获取列表 -await NotebookTest.detail(1); // 获取详情 -await NotebookTest.update(1, {...}); // 更新笔记 -await NotebookTest.delete(1); // 删除笔记 -``` - -## 🐛 常见问题排查 - -### 1. 编译错误 - -**问题**: `cannot use &claims.UserID (value of type *int) as *uint64` - -**解决**: 已修复,使用类型转换 `userID := uint64(claims.UserID)` - ---- - -**问题**: `undefined: PlatformNotebook` - -**解决**: 检查 `go/models/init.go` 中是否已注册模型 - ---- - -### 2. 数据库错误 - -**问题**: 表不存在 - -**解决**: -```sql --- 检查表是否存在 -SHOW TABLES LIKE 'yz_platform_notebook'; - --- 如果不存在,执行 SQL 文件 -SOURCE sql/yz_platform_notebook.sql; -``` - ---- - -**问题**: 字段不存在 - -**解决**: 确认字段名使用 `create_time`, `update_time`, `delete_time` - ---- - -### 3. 前端错误 - -**问题**: WangEditor 未定义 - -**解决**: -```bash -npm install @wangeditor/editor -``` - ---- - -**问题**: API 请求 401 未授权 - -**解决**: -- 检查用户是否已登录 -- 检查 JWT Token 是否有效 -- 检查 Token 是否正确设置在请求头中 - ---- - -**问题**: 笔记列表为空 - -**解决**: -- 检查数据库中是否有数据 -- 检查用户ID是否匹配 -- 查看浏览器控制台和网络请求 - ---- - -### 4. 权限错误 - -**问题**: 无法访问其他用户的笔记 - -**说明**: 这是正常的,每个用户只能访问自己的笔记 - ---- - -## 📊 性能优化建议 - -### 数据库优化 -1. 为常用查询字段添加索引(已添加) - - `user_id` - - `create_time` - - `is_deleted` - -2. 定期清理软删除的数据 - ```sql - -- 删除30天前的软删除数据 - DELETE FROM yz_platform_notebook - WHERE is_deleted = 1 - AND delete_time < DATE_SUB(NOW(), INTERVAL 30 DAY); - ``` - -### 前端优化 -1. 列表分页加载(已实现) -2. 内容预览截取(已实现) -3. 懒加载编辑器组件 -4. 防抖搜索功能(可选) - -### 后端优化 -1. 添加缓存层(Redis) -2. 内容压缩存储 -3. 异步处理大文件 -4. API 限流保护 - -## 🔐 安全建议 - -1. **内容安全** - - 前端显示时做 XSS 过滤 - - 后端存储前做内容校验 - - 限制单篇笔记大小 - -2. **访问控制** - - JWT Token 验证(已实现) - - 用户权限校验(已实现) - - API 频率限制 - -3. **数据备份** - - 定期备份数据库 - - 软删除机制(已实现) - - 版本控制(可选) - -## 📝 后续功能扩展 - -- [ ] 笔记分类/文件夹 -- [ ] 笔记标签 -- [ ] 笔记分享 -- [ ] 导出功能(PDF/Markdown) -- [ ] 版本历史 -- [ ] 协作编辑 -- [ ] 全文搜索 -- [ ] 附件上传 -- [ ] 模板功能 -- [ ] 快捷键支持 - -## ✨ 完成标志 - -- [✅] SQL 表创建成功 -- [✅] 后端代码编译通过 -- [✅] 前端页面正常访问 -- [ ] 所有功能测试通过 -- [ ] 无控制台错误 -- [ ] 性能表现良好 - -## 📞 技术支持 - -如遇到问题,请检查: -1. 浏览器控制台错误信息 -2. 后端服务日志 -3. 数据库连接状态 -4. API 请求响应 - -祝部署顺利!🎉 +# 记事本模块部署检查清单 + +## 📋 部署前检查 + +### 1. 数据库准备 +- [ ] 执行 SQL 文件创建数据表 + ```bash + mysql -u用户名 -p数据库名 < sql/yz_platform_notebook.sql + ``` +- [ ] 验证表创建成功 + ```sql + SHOW TABLES LIKE 'yz_platform_notebook'; + DESC yz_platform_notebook; + ``` + +### 2. 后端代码检查 +- [✅] 模型文件已创建: `go/models/platform_notebook.go` +- [✅] 模型已注册: `go/models/init.go` +- [✅] 控制器已创建: `go/controllers/platform_notebook.go` +- [✅] 路由已注册: `go/routers/platform/platform.go` +- [✅] 代码编译通过 + +### 3. 前端代码检查 +- [✅] API文件已创建: `platform/src/api/notebook.js` +- [✅] 主页面已创建: `platform/src/views/apps/notebook/index.vue` +- [✅] 编辑器组件已创建: `platform/src/views/apps/notebook/components/edit.vue` +- [✅] WangEditor组件已创建: `platform/src/views/apps/notebook/components/WangEditor.vue` + +### 4. 依赖检查 +- [ ] 前端安装 WangEditor + ```bash + cd platform + npm install @wangeditor/editor + # 或 + yarn add @wangeditor/editor + ``` + +### 5. 菜单配置 +- [ ] 在平台管理后台添加记事本菜单项 + - 路径: `/apps/notebook` + - 组件: `apps/notebook/index.vue` + - 图标: `fa-solid fa-book` 或其他合适的图标 + - 标题: `记事本` 或 `我的笔记` + +## 🚀 部署步骤 + +### 后端部署 + +1. **停止服务** + ```bash + # 如果服务正在运行,先停止 + pkill -f "go run main.go" + ``` + +2. **编译代码** + ```bash + cd go + go build -o server + ``` + +3. **启动服务** + ```bash + ./server + # 或使用后台运行 + nohup ./server > server.log 2>&1 & + ``` + +### 前端部署 + +1. **安装依赖** + ```bash + cd platform + npm install + # 或 + yarn install + ``` + +2. **开发模式测试** + ```bash + npm run dev + # 或 + yarn dev + ``` + +3. **生产构建** + ```bash + npm run build + # 或 + yarn build + ``` + +## ✅ 功能测试 + +### 基础功能测试 + +1. **访问页面** + - [ ] 能够正常访问记事本页面 + - [ ] 页面布局正常显示 + - [ ] 左侧列表和右侧编辑器都能正常显示 + +2. **创建笔记** + - [ ] 点击"新建笔记"按钮 + - [ ] 输入标题和内容 + - [ ] 点击"创建"按钮 + - [ ] 创建成功,笔记出现在列表中 + +3. **编辑笔记** + - [ ] 点击列表中的笔记 + - [ ] 笔记内容正确加载到编辑器 + - [ ] 修改标题和内容 + - [ ] 点击"保存"按钮 + - [ ] 保存成功,列表中的笔记信息更新 + +4. **删除笔记** + - [ ] 点击笔记右侧的"更多"按钮 + - [ ] 点击"删除" + - [ ] 确认删除 + - [ ] 笔记从列表中移除 + +5. **搜索笔记** + - [ ] 在搜索框输入关键词 + - [ ] 列表自动过滤显示匹配的笔记 + - [ ] 清空搜索框,显示所有笔记 + +### 富文本编辑器测试 + +1. **文本格式** + - [ ] 加粗、斜体、下划线 + - [ ] 标题(H1-H6) + - [ ] 字体颜色和背景色 + +2. **列表和引用** + - [ ] 有序列表 + - [ ] 无序列表 + - [ ] 引用块 + +3. **插入内容** + - [ ] 插入链接 + - [ ] 插入代码块 + - [ ] 插入表格 + +4. **图片上传** (需要配置上传接口) + - [ ] 点击图片按钮 + - [ ] 选择图片文件 + - [ ] 图片正确上传并显示 + +### API测试 (使用浏览器控制台) + +```javascript +// 1. 在浏览器控制台加载测试脚本 +// 将 test-api.js 的内容粘贴到控制台 + +// 2. 运行完整测试 +await NotebookTest.runFullTest(); + +// 3. 或单独测试各个功能 +await NotebookTest.create(); // 创建笔记 +await NotebookTest.list(); // 获取列表 +await NotebookTest.detail(1); // 获取详情 +await NotebookTest.update(1, {...}); // 更新笔记 +await NotebookTest.delete(1); // 删除笔记 +``` + +## 🐛 常见问题排查 + +### 1. 编译错误 + +**问题**: `cannot use &claims.UserID (value of type *int) as *uint64` + +**解决**: 已修复,使用类型转换 `userID := uint64(claims.UserID)` + +--- + +**问题**: `undefined: PlatformNotebook` + +**解决**: 检查 `go/models/init.go` 中是否已注册模型 + +--- + +### 2. 数据库错误 + +**问题**: 表不存在 + +**解决**: +```sql +-- 检查表是否存在 +SHOW TABLES LIKE 'yz_platform_notebook'; + +-- 如果不存在,执行 SQL 文件 +SOURCE sql/yz_platform_notebook.sql; +``` + +--- + +**问题**: 字段不存在 + +**解决**: 确认字段名使用 `create_time`, `update_time`, `delete_time` + +--- + +### 3. 前端错误 + +**问题**: WangEditor 未定义 + +**解决**: +```bash +npm install @wangeditor/editor +``` + +--- + +**问题**: API 请求 401 未授权 + +**解决**: +- 检查用户是否已登录 +- 检查 JWT Token 是否有效 +- 检查 Token 是否正确设置在请求头中 + +--- + +**问题**: 笔记列表为空 + +**解决**: +- 检查数据库中是否有数据 +- 检查用户ID是否匹配 +- 查看浏览器控制台和网络请求 + +--- + +### 4. 权限错误 + +**问题**: 无法访问其他用户的笔记 + +**说明**: 这是正常的,每个用户只能访问自己的笔记 + +--- + +## 📊 性能优化建议 + +### 数据库优化 +1. 为常用查询字段添加索引(已添加) + - `user_id` + - `create_time` + - `is_deleted` + +2. 定期清理软删除的数据 + ```sql + -- 删除30天前的软删除数据 + DELETE FROM yz_platform_notebook + WHERE is_deleted = 1 + AND delete_time < DATE_SUB(NOW(), INTERVAL 30 DAY); + ``` + +### 前端优化 +1. 列表分页加载(已实现) +2. 内容预览截取(已实现) +3. 懒加载编辑器组件 +4. 防抖搜索功能(可选) + +### 后端优化 +1. 添加缓存层(Redis) +2. 内容压缩存储 +3. 异步处理大文件 +4. API 限流保护 + +## 🔐 安全建议 + +1. **内容安全** + - 前端显示时做 XSS 过滤 + - 后端存储前做内容校验 + - 限制单篇笔记大小 + +2. **访问控制** + - JWT Token 验证(已实现) + - 用户权限校验(已实现) + - API 频率限制 + +3. **数据备份** + - 定期备份数据库 + - 软删除机制(已实现) + - 版本控制(可选) + +## 📝 后续功能扩展 + +- [ ] 笔记分类/文件夹 +- [ ] 笔记标签 +- [ ] 笔记分享 +- [ ] 导出功能(PDF/Markdown) +- [ ] 版本历史 +- [ ] 协作编辑 +- [ ] 全文搜索 +- [ ] 附件上传 +- [ ] 模板功能 +- [ ] 快捷键支持 + +## ✨ 完成标志 + +- [✅] SQL 表创建成功 +- [✅] 后端代码编译通过 +- [✅] 前端页面正常访问 +- [ ] 所有功能测试通过 +- [ ] 无控制台错误 +- [ ] 性能表现良好 + +## 📞 技术支持 + +如遇到问题,请检查: +1. 浏览器控制台错误信息 +2. 后端服务日志 +3. 数据库连接状态 +4. API 请求响应 + +祝部署顺利!🎉 diff --git a/backend/.gitignore b/backend/.gitignore index d7543ec..ec6aa96 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,28 +1,28 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -.env -.env.* +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env +.env.* !.example.env \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index 1511959..658be6d 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,5 +1,5 @@ -# Vue 3 + Vite - -This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` - - -``` - ---- - -### 场景2:在编辑对话框使用字典 - -**文件**: `src/views/system/users/components/UserEdit.vue` - -```vue - - - -``` - ---- - -### 场景3:快速使用 Composable Hook - -最简单的方式,自动处理加载: - -```vue - - - -``` - ---- - -### 场景4:预加载应用启动时需要的字典 - -**文件**: `src/main.js` - -```javascript -import { createApp } from 'vue' -import { createPinia } from 'pinia' -import { useDictStore } from '@/stores/dict' -import { DICT_CODES } from '@/constants/dictCodes' - -const app = createApp(App) -const pinia = createPinia() - -app.use(pinia) - -// 在应用启动后预加载常用字典 -const dictStore = useDictStore() -await dictStore.preloadDicts([ - DICT_CODES.USER_STATUS, - DICT_CODES.COMMON_STATUS, - DICT_CODES.YES_NO, -]) - -app.mount('#app') -``` - ---- - -## 字典数据结构 - -后端返回的字典数据结构: - -```json -[ - { - "dict_id": 1, - "dict_value": "1", - "dict_label": "启用", - "dict_type": "user_status", - "remarks": "用户启用状态", - "remark": "用户启用状态" - }, - { - "dict_id": 2, - "dict_value": "0", - "dict_label": "禁用", - "dict_type": "user_status", - "remarks": "用户禁用状态", - "remark": "用户禁用状态" - } -] -``` - -**关键字段**: -- `dict_value`: 字典值(存储在数据库中) -- `dict_label`: 字典标签(显示给用户) -- `dict_type`: 字典类型编码(如 'user_status') - ---- - -## 最佳实践 - -### ✅ DO - -1. **使用常量而不是硬编码字符串** - ```javascript - // ✅ 好 - dictStore.getDictItems(DICT_CODES.USER_STATUS) - - // ❌ 差 - dictStore.getDictItems('user_status') - ``` - -2. **在父组件加载,通过 props 传给子组件** - ```javascript - // ✅ 父组件负责数据,子组件负责展示 - // index.vue - const statusDict = await dictStore.getDictItems(DICT_CODES.USER_STATUS) - - // UserEdit.vue - const props = defineProps({ statusDict: Array }) - ``` - -3. **用 Composable 简化组件逻辑** - ```javascript - // ✅ 一行代码搞定 - const { user_statusDict, loading } = useUserStatusDict() - ``` - -4. **预加载常用字典** - ```javascript - // ✅ 应用启动时预加载,避免页面初始化时加载 - await dictStore.preloadDicts([...]) - ``` - -### ❌ DON'T - -1. **不要在多个地方重复加载同一个字典** - ```javascript - // ❌ 糟糕:重复加载 - // 页面A - const dict1 = await dictStore.getDictItems('user_status') - - // 页面B(Store 会自动缓存,但代码看起来重复) - const dict2 = await dictStore.getDictItems('user_status') - ``` - -2. **不要忘记处理加载状态** - ```javascript - // ❌ 可能展示空白 - const { statusDict } = useUserStatusDict() - - // ✅ 处理加载状态 - const { statusDict, loading } = useUserStatusDict() - if (loading) { /* 显示加载中 */ } - ``` - -3. **不要混用不同的字典访问方式** - ```javascript - // ❌ 混乱 - const dict1 = await dictStore.getDictItems('user_status') - const dict2 = dictStore.getDictItemsSync('user_role') - - // ✅ 统一使用 - const dict1 = await dictStore.getDictItems('user_status') - const dict2 = await dictStore.getDictItems('user_role') - ``` - ---- - -## 性能优化建议 - -| 优化项 | 说明 | -|------|------| -| **缓存** | Store 自动缓存,同一个字典只请求一次 | -| **预加载** | 在路由切换前预加载需要的字典 | -| **同步访问** | 已加载的字典可用 `getDictItemsSync` 同步获取 | -| **避免重复** | 不要在多个组件重复请求同一个字典 | - ---- - -## 故障排查 - -### 问题1:状态选项为空 - -**原因**:字典未加载 -**解决**: -```javascript -// ❌ 错误:字典还未加载 -const statusDict = dictStore.getDictItemsSync('user_status') // 返回 [] - -// ✅ 正确:等待异步加载完成 -const statusDict = await dictStore.getDictItems('user_status') -``` - -### 问题2:重复加载字典 - -**原因**:没有使用 Store 的缓存 -**解决**: -```javascript -// 所有调用都会自动使用缓存,只请求一次 -await dictStore.getDictItems('user_status') // 首次:发送请求 -await dictStore.getDictItems('user_status') // 第二次:返回缓存 -``` - -### 问题3:字典显示不对 - -**原因**:value 类型不匹配(如 1 vs "1") -**解决**: -```javascript -// Store 会自动处理类型匹配 -const item = items.find(i => - String(i.dict_value) === String(value) || i.dict_value === value -) -``` - ---- - -## 集成检清表 - -- [ ] 创建 `src/stores/dict.js` - Store -- [ ] 创建 `src/constants/dictCodes.js` - 常量 -- [ ] 创建 `src/composables/useDict.js` - Composable -- [ ] 在 `index.vue` 中导入 `useDictStore` -- [ ] 在 `UserEdit.vue` 中接收 `statusDict` props -- [ ] 测试字典加载和显示 -- [ ] 验证缓存功能(打开浏览器 DevTools 检查 Network) -- [ ] 预加载常用字典(可选) - ---- - -## 相关文件修改 - -已修改的文件: -- ✅ `src/stores/dict.js` - 新建 -- ✅ `src/constants/dictCodes.js` - 新建 -- ✅ `src/composables/useDict.js` - 新建 -- ✅ `src/views/system/users/index.vue` - 使用 `useDictStore` -- ✅ `src/views/system/users/components/UserEdit.vue` - 导入字典库 - +# Pinia 字典管理系统使用指南 + +## 系统架构 + +``` +┌─────────────────────────────────────┐ +│ API 接口 (getDictItemsByCode) │ +│ /api/dict/items/code/{code} │ +└──────────────┬──────────────────────┘ + │ + ↓ +┌─────────────────────────────────────┐ +│ Pinia Store (useDictStore) │ +│ ✅ 自动缓存字典数据 │ +│ ✅ 避免重复请求 │ +│ ✅ 支持同步/异步访问 │ +└──────────────┬──────────────────────┘ + │ + ┌──────┴──────┐ + ↓ ↓ + ┌────────┐ ┌──────────────┐ + │组件 │ │Composable │ + │直接用 │ │useDict Hook │ + └────────┘ └──────────────┘ +``` + +--- + +## 核心文件说明 + +### 1. **Store**: `src/stores/dict.js` + +字典数据的全局管理器 + +**主要方法**: +```javascript +import { useDictStore } from '@/stores/dict' + +const dictStore = useDictStore() + +// ✅ 异步获取字典(推荐) +const items = await dictStore.getDictItems('user_status') + +// ✅ 同步获取字典(已缓存时) +const items = dictStore.getDictItemsSync('user_status') + +// ✅ 获取字典值对应的标签 +const label = dictStore.getDictLabel('user_status', 1) + +// ✅ 预加载多个字典 +await dictStore.preloadDicts(['user_status', 'user_role']) + +// ✅ 清空缓存 +dictStore.clearCache('user_status') +``` + +--- + +### 2. **常量**: `src/constants/dictCodes.js` + +集中管理所有字典编码 + +**使用示例**: +```javascript +import { DICT_CODES } from '@/constants/dictCodes' + +// 好处:避免硬编码,IDE 有自动完成 +const items = await dictStore.getDictItems(DICT_CODES.USER_STATUS) + +// 所有可用的编码: +DICT_CODES.USER_STATUS // 用户状态 +DICT_CODES.USER_GENDER // 用户性别 +DICT_CODES.USER_ROLE // 用户角色 +DICT_CODES.DEPT_STATUS // 部门状态 +DICT_CODES.POSITION_STATUS // 职位状态 +// ... 更多编码 +``` + +--- + +### 3. **Composable**: `src/composables/useDict.js` + +简化在组件中使用字典的 Hook + +**基础用法**: +```javascript +import { useDictionary, useUserStatusDict } from '@/composables/useDict' +import { DICT_CODES } from '@/constants/dictCodes' + +// 方式1:使用常量 +const { statusDict, loading } = useDictionary(DICT_CODES.USER_STATUS) + +// 方式2:使用字符串 +const { dicts, loading } = useDictionary('user_status') + +// 方式3:使用特化 Hook(推荐) +const { user_statusDict, loading } = useUserStatusDict() +``` + +--- + +## 使用场景 + +### 场景1:在列表页加载字典 + +**文件**: `src/views/system/users/index.vue` + +```vue + + + +``` + +--- + +### 场景2:在编辑对话框使用字典 + +**文件**: `src/views/system/users/components/UserEdit.vue` + +```vue + + + +``` + +--- + +### 场景3:快速使用 Composable Hook + +最简单的方式,自动处理加载: + +```vue + + + +``` + +--- + +### 场景4:预加载应用启动时需要的字典 + +**文件**: `src/main.js` + +```javascript +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import { useDictStore } from '@/stores/dict' +import { DICT_CODES } from '@/constants/dictCodes' + +const app = createApp(App) +const pinia = createPinia() + +app.use(pinia) + +// 在应用启动后预加载常用字典 +const dictStore = useDictStore() +await dictStore.preloadDicts([ + DICT_CODES.USER_STATUS, + DICT_CODES.COMMON_STATUS, + DICT_CODES.YES_NO, +]) + +app.mount('#app') +``` + +--- + +## 字典数据结构 + +后端返回的字典数据结构: + +```json +[ + { + "dict_id": 1, + "dict_value": "1", + "dict_label": "启用", + "dict_type": "user_status", + "remarks": "用户启用状态", + "remark": "用户启用状态" + }, + { + "dict_id": 2, + "dict_value": "0", + "dict_label": "禁用", + "dict_type": "user_status", + "remarks": "用户禁用状态", + "remark": "用户禁用状态" + } +] +``` + +**关键字段**: +- `dict_value`: 字典值(存储在数据库中) +- `dict_label`: 字典标签(显示给用户) +- `dict_type`: 字典类型编码(如 'user_status') + +--- + +## 最佳实践 + +### ✅ DO + +1. **使用常量而不是硬编码字符串** + ```javascript + // ✅ 好 + dictStore.getDictItems(DICT_CODES.USER_STATUS) + + // ❌ 差 + dictStore.getDictItems('user_status') + ``` + +2. **在父组件加载,通过 props 传给子组件** + ```javascript + // ✅ 父组件负责数据,子组件负责展示 + // index.vue + const statusDict = await dictStore.getDictItems(DICT_CODES.USER_STATUS) + + // UserEdit.vue + const props = defineProps({ statusDict: Array }) + ``` + +3. **用 Composable 简化组件逻辑** + ```javascript + // ✅ 一行代码搞定 + const { user_statusDict, loading } = useUserStatusDict() + ``` + +4. **预加载常用字典** + ```javascript + // ✅ 应用启动时预加载,避免页面初始化时加载 + await dictStore.preloadDicts([...]) + ``` + +### ❌ DON'T + +1. **不要在多个地方重复加载同一个字典** + ```javascript + // ❌ 糟糕:重复加载 + // 页面A + const dict1 = await dictStore.getDictItems('user_status') + + // 页面B(Store 会自动缓存,但代码看起来重复) + const dict2 = await dictStore.getDictItems('user_status') + ``` + +2. **不要忘记处理加载状态** + ```javascript + // ❌ 可能展示空白 + const { statusDict } = useUserStatusDict() + + // ✅ 处理加载状态 + const { statusDict, loading } = useUserStatusDict() + if (loading) { /* 显示加载中 */ } + ``` + +3. **不要混用不同的字典访问方式** + ```javascript + // ❌ 混乱 + const dict1 = await dictStore.getDictItems('user_status') + const dict2 = dictStore.getDictItemsSync('user_role') + + // ✅ 统一使用 + const dict1 = await dictStore.getDictItems('user_status') + const dict2 = await dictStore.getDictItems('user_role') + ``` + +--- + +## 性能优化建议 + +| 优化项 | 说明 | +|------|------| +| **缓存** | Store 自动缓存,同一个字典只请求一次 | +| **预加载** | 在路由切换前预加载需要的字典 | +| **同步访问** | 已加载的字典可用 `getDictItemsSync` 同步获取 | +| **避免重复** | 不要在多个组件重复请求同一个字典 | + +--- + +## 故障排查 + +### 问题1:状态选项为空 + +**原因**:字典未加载 +**解决**: +```javascript +// ❌ 错误:字典还未加载 +const statusDict = dictStore.getDictItemsSync('user_status') // 返回 [] + +// ✅ 正确:等待异步加载完成 +const statusDict = await dictStore.getDictItems('user_status') +``` + +### 问题2:重复加载字典 + +**原因**:没有使用 Store 的缓存 +**解决**: +```javascript +// 所有调用都会自动使用缓存,只请求一次 +await dictStore.getDictItems('user_status') // 首次:发送请求 +await dictStore.getDictItems('user_status') // 第二次:返回缓存 +``` + +### 问题3:字典显示不对 + +**原因**:value 类型不匹配(如 1 vs "1") +**解决**: +```javascript +// Store 会自动处理类型匹配 +const item = items.find(i => + String(i.dict_value) === String(value) || i.dict_value === value +) +``` + +--- + +## 集成检清表 + +- [ ] 创建 `src/stores/dict.js` - Store +- [ ] 创建 `src/constants/dictCodes.js` - 常量 +- [ ] 创建 `src/composables/useDict.js` - Composable +- [ ] 在 `index.vue` 中导入 `useDictStore` +- [ ] 在 `UserEdit.vue` 中接收 `statusDict` props +- [ ] 测试字典加载和显示 +- [ ] 验证缓存功能(打开浏览器 DevTools 检查 Network) +- [ ] 预加载常用字典(可选) + +--- + +## 相关文件修改 + +已修改的文件: +- ✅ `src/stores/dict.js` - 新建 +- ✅ `src/constants/dictCodes.js` - 新建 +- ✅ `src/composables/useDict.js` - 新建 +- ✅ `src/views/system/users/index.vue` - 使用 `useDictStore` +- ✅ `src/views/system/users/components/UserEdit.vue` - 导入字典库 + diff --git a/backend/docs/一键复制.md b/backend/docs/一键复制.md index 52c48c5..9bd39b3 100644 --- a/backend/docs/一键复制.md +++ b/backend/docs/一键复制.md @@ -1,31 +1,31 @@ - - - \ No newline at end of file diff --git a/backend/docs/拼接接口路径.md b/backend/docs/拼接接口路径.md index cb115ba..5292bb9 100644 --- a/backend/docs/拼接接口路径.md +++ b/backend/docs/拼接接口路径.md @@ -1,11 +1,11 @@ -//拼接接口路径 -const getEnvUrl = (path: string) => { - const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; - return `${API_BASE_URL}${path}`; -}; - -用例: - - - -const url = getEnvUrl('/admin/moduleCenter/modules'); +//拼接接口路径 +const getEnvUrl = (path: string) => { + const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; + return `${API_BASE_URL}${path}`; +}; + +用例: + + + +const url = getEnvUrl('/admin/moduleCenter/modules'); diff --git a/backend/docs/接口调用.md b/backend/docs/接口调用.md index 0138236..e96bf34 100644 --- a/backend/docs/接口调用.md +++ b/backend/docs/接口调用.md @@ -1,12 +1,12 @@ -import { onMounted } from "vue"; -import { getMenus } from "@/api/menu"; - -onMounted(async () => { - try{ - const response = await getMenus(); - } catch (error) { - console.error('获取菜单数据失败:', error); - } -}); - - +import { onMounted } from "vue"; +import { getMenus } from "@/api/menu"; + +onMounted(async () => { + try{ + const response = await getMenus(); + } catch (error) { + console.error('获取菜单数据失败:', error); + } +}); + + diff --git a/backend/docs/获取缓存数据.md b/backend/docs/获取缓存数据.md index ef0e2fb..176e741 100644 --- a/backend/docs/获取缓存数据.md +++ b/backend/docs/获取缓存数据.md @@ -1,23 +1,23 @@ - -import { useAuthStore } from '@/stores/auth'; -import { onMounted } from 'vue'; - -// 使用 auth store 获取用户信息 -const authStore = useAuthStore(); - -// 获取租户ID -const tenantId = (authStore.user as any)?.tid; - -// 获取用户信息 -const userInfo = authStore.user; -if (userInfo && userInfo.id) { - console.log('用户名:', userInfo.username || userInfo.nickname); - console.log('用户ID:', userInfo.id); - console.log('角色:', userInfo.role); -} else { - console.log('未找到用户信息或用户未登录'); -} - -onMounted(() => { - getUserInfo(); + +import { useAuthStore } from '@/stores/auth'; +import { onMounted } from 'vue'; + +// 使用 auth store 获取用户信息 +const authStore = useAuthStore(); + +// 获取租户ID +const tenantId = (authStore.user as any)?.tid; + +// 获取用户信息 +const userInfo = authStore.user; +if (userInfo && userInfo.id) { + console.log('用户名:', userInfo.username || userInfo.nickname); + console.log('用户ID:', userInfo.id); + console.log('角色:', userInfo.role); +} else { + console.log('未找到用户信息或用户未登录'); +} + +onMounted(() => { + getUserInfo(); }); \ No newline at end of file diff --git a/backend/docs/调用图片上传组件.md b/backend/docs/调用图片上传组件.md index 0db1a98..7215b75 100644 --- a/backend/docs/调用图片上传组件.md +++ b/backend/docs/调用图片上传组件.md @@ -1,90 +1,90 @@ - -
- - - - - - - - - Preview Image - - -
- 建议尺寸:250px × 140px -
-
-
- -import { uploadFile } from '@/api/file.js'; -import { ElMessage, ElUpload } from 'element-plus' - -// 上传相关 -const fileList = ref([]) -const dialogVisible = ref(false) -const dialogImageUrl = ref('') - -function beforeImgUpload(file: File) { - const isImage = file.type.startsWith('image/') - const isLt10M = file.size / 1024 / 1024 < 10 - if (!isImage) ElMessage.error('仅支持图片格式') - if (!isLt10M) ElMessage.error('图片大小不能超过10MB') - return isImage && isLt10M -} - -function handleImgUpload(file: File) { - const formData = new FormData() - formData.append('file', file) - formData.append('cate', 'article') - - uploadFile(formData).then((res: any) => { - if (res?.url) { - formData.image = res.url - fileList.value = [{ - name: file.name, - url: res.url - }] - } - }).catch((error: any) => { - ElMessage.error('上传失败:' + (error.msg || '未知错误')) - }) -} - -function handlePictureCardPreview(file: any) { - dialogImageUrl.value = file.url - dialogVisible.value = true -} - -function handleRemove(file: any) { - fileList.value = [] - formData.image = '' -} - - -.uploads{ - display: flex; - flex-direction: column; -} -.upload-tip { - font-size: 12px; - color: #999; + +
+ + + + + + + + + Preview Image + + +
+ 建议尺寸:250px × 140px +
+
+
+ +import { uploadFile } from '@/api/file.js'; +import { ElMessage, ElUpload } from 'element-plus' + +// 上传相关 +const fileList = ref([]) +const dialogVisible = ref(false) +const dialogImageUrl = ref('') + +function beforeImgUpload(file: File) { + const isImage = file.type.startsWith('image/') + const isLt10M = file.size / 1024 / 1024 < 10 + if (!isImage) ElMessage.error('仅支持图片格式') + if (!isLt10M) ElMessage.error('图片大小不能超过10MB') + return isImage && isLt10M +} + +function handleImgUpload(file: File) { + const formData = new FormData() + formData.append('file', file) + formData.append('cate', 'article') + + uploadFile(formData).then((res: any) => { + if (res?.url) { + formData.image = res.url + fileList.value = [{ + name: file.name, + url: res.url + }] + } + }).catch((error: any) => { + ElMessage.error('上传失败:' + (error.msg || '未知错误')) + }) +} + +function handlePictureCardPreview(file: any) { + dialogImageUrl.value = file.url + dialogVisible.value = true +} + +function handleRemove(file: any) { + fileList.value = [] + formData.image = '' +} + + +.uploads{ + display: flex; + flex-direction: column; +} +.upload-tip { + font-size: 12px; + color: #999; } \ No newline at end of file diff --git a/backend/docs/调用字典.md b/backend/docs/调用字典.md index adf787f..1abea5e 100644 --- a/backend/docs/调用字典.md +++ b/backend/docs/调用字典.md @@ -1,27 +1,27 @@ -```` - - +```` + + ```` \ No newline at end of file diff --git a/backend/index.html b/backend/index.html index 78222d2..f4f1421 100644 --- a/backend/index.html +++ b/backend/index.html @@ -1,16 +1,16 @@ - - - - - - - - - - 后台管理系统 - - -
- - - + + + + + + + + + + 后台管理系统 + + +
+ + + diff --git a/backend/package-lock.json b/backend/package-lock.json index 2f746af..491399f 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,4916 +1,4916 @@ -{ - "name": "pc", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "pc", - "version": "0.0.0", - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "@wangeditor/editor": "^5.1.23", - "axios": "^1.13.1", - "chart": "^0.1.2", - "chart.js": "^4.5.1", - "docx-preview": "^0.3.7", - "echarts": "^6.0.0", - "element-plus": "^2.11.7", - "less": "^4.4.2", - "marked": "^16.4.1", - "os": "^0.1.2", - "pinia": "^3.0.3", - "vue": "^3.5.22", - "vue-img-cutter": "^3.0.7", - "vue-router": "^4.6.3", - "vue3-pdf-app": "^1.0.3", - "xlsx": "^0.18.5" - }, - "devDependencies": { - "@types/node": "^24.10.7", - "@vitejs/plugin-vue": "^6.0.1", - "typescript": "^5.9.3", - "unplugin-auto-import": "^20.2.0", - "unplugin-vue-components": "^30.0.0", - "vite": "^7.1.7" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@ctrl/tinycolor": { - "version": "3.6.1", - "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", - "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/@element-plus/icons-vue": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", - "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@intlify/core-base": { - "version": "9.14.4", - "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.4.tgz", - "integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==", - "license": "MIT", - "dependencies": { - "@intlify/message-compiler": "9.14.4", - "@intlify/shared": "9.14.4" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@intlify/message-compiler": { - "version": "9.14.4", - "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.14.4.tgz", - "integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==", - "license": "MIT", - "dependencies": { - "@intlify/shared": "9.14.4", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@intlify/shared": { - "version": "9.14.4", - "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz", - "integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@kurkle/color": { - "version": "0.3.4", - "resolved": "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.4.tgz", - "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "license": "MIT" - }, - "node_modules/@popperjs/core": { - "name": "@sxzz/popperjs-es", - "version": "2.11.8", - "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", - "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@transloadit/prettier-bytes": { - "version": "0.0.7", - "resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", - "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==", - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/node": { - "version": "24.10.14", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.14.tgz", - "integrity": "sha512-OowOUbD1lBCOFIPOZ8xnMIhgqA4sCutMiYOmPHL1PTLt5+y1XA+g2+yC9OOyz8p+deMZqPZLxfMjYIfrKsPeFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/@uppy/companion-client": { - "version": "2.2.2", - "resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz", - "integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==", - "license": "MIT", - "dependencies": { - "@uppy/utils": "^4.1.2", - "namespace-emitter": "^2.0.1" - } - }, - "node_modules/@uppy/core": { - "version": "2.3.4", - "resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz", - "integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==", - "license": "MIT", - "dependencies": { - "@transloadit/prettier-bytes": "0.0.7", - "@uppy/store-default": "^2.1.1", - "@uppy/utils": "^4.1.3", - "lodash.throttle": "^4.1.1", - "mime-match": "^1.0.2", - "namespace-emitter": "^2.0.1", - "nanoid": "^3.1.25", - "preact": "^10.5.13" - } - }, - "node_modules/@uppy/store-default": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz", - "integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==", - "license": "MIT" - }, - "node_modules/@uppy/utils": { - "version": "4.1.3", - "resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz", - "integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==", - "license": "MIT", - "dependencies": { - "lodash.throttle": "^4.1.1" - } - }, - "node_modules/@uppy/xhr-upload": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz", - "integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==", - "license": "MIT", - "dependencies": { - "@uppy/companion-client": "^2.2.2", - "@uppy/utils": "^4.1.2", - "nanoid": "^3.1.25" - }, - "peerDependencies": { - "@uppy/core": "^2.3.3" - } - }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.4", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", - "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.2" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.29.tgz", - "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/shared": "3.5.29", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", - "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.29", - "@vue/shared": "3.5.29" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", - "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/compiler-core": "3.5.29", - "@vue/compiler-dom": "3.5.29", - "@vue/compiler-ssr": "3.5.29", - "@vue/shared": "3.5.29", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", - "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.29", - "@vue/shared": "3.5.29" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.9", - "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.9" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.9", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.29.tgz", - "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.29" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.29.tgz", - "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.29", - "@vue/shared": "3.5.29" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", - "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.29", - "@vue/runtime-core": "3.5.29", - "@vue/shared": "3.5.29", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.29.tgz", - "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.29", - "@vue/shared": "3.5.29" - }, - "peerDependencies": { - "vue": "3.5.29" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.29.tgz", - "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", - "license": "MIT" - }, - "node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", - "license": "MIT", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@wangeditor/basic-modules": { - "version": "1.1.7", - "resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz", - "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==", - "license": "MIT", - "dependencies": { - "is-url": "^1.2.4" - }, - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.throttle": "^4.1.1", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/code-highlight": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz", - "integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==", - "license": "MIT", - "dependencies": { - "prismjs": "^1.23.0" - }, - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/core": { - "version": "1.1.19", - "resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz", - "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==", - "license": "MIT", - "dependencies": { - "@types/event-emitter": "^0.3.3", - "event-emitter": "^0.3.5", - "html-void-elements": "^2.0.0", - "i18next": "^20.4.0", - "scroll-into-view-if-needed": "^2.2.28", - "slate-history": "^0.66.0" - }, - "peerDependencies": { - "@uppy/core": "^2.1.1", - "@uppy/xhr-upload": "^2.0.3", - "dom7": "^3.0.0", - "is-hotkey": "^0.2.0", - "lodash.camelcase": "^4.3.0", - "lodash.clonedeep": "^4.5.0", - "lodash.debounce": "^4.0.8", - "lodash.foreach": "^4.5.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "lodash.toarray": "^4.4.0", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/editor": { - "version": "5.1.23", - "resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz", - "integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==", - "license": "MIT", - "dependencies": { - "@uppy/core": "^2.1.1", - "@uppy/xhr-upload": "^2.0.3", - "@wangeditor/basic-modules": "^1.1.7", - "@wangeditor/code-highlight": "^1.0.3", - "@wangeditor/core": "^1.1.19", - "@wangeditor/list-module": "^1.0.5", - "@wangeditor/table-module": "^1.1.4", - "@wangeditor/upload-image-module": "^1.0.2", - "@wangeditor/video-module": "^1.1.4", - "dom7": "^3.0.0", - "is-hotkey": "^0.2.0", - "lodash.camelcase": "^4.3.0", - "lodash.clonedeep": "^4.5.0", - "lodash.debounce": "^4.0.8", - "lodash.foreach": "^4.5.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "lodash.toarray": "^4.4.0", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/list-module": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz", - "integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==", - "license": "MIT", - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/table-module": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz", - "integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==", - "license": "MIT", - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/upload-image-module": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz", - "integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==", - "license": "MIT", - "peerDependencies": { - "@uppy/core": "^2.0.3", - "@uppy/xhr-upload": "^2.0.3", - "@wangeditor/basic-modules": "1.x", - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.foreach": "^4.5.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/video-module": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz", - "integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==", - "license": "MIT", - "peerDependencies": { - "@uppy/core": "^2.1.4", - "@uppy/xhr-upload": "^2.0.7", - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adler-32": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz", - "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/cfb": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz", - "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/chart": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/chart/-/chart-0.1.2.tgz", - "integrity": "sha512-MSiVzAd3qUEXv54k9KGe1oIoC7WG32W9wtjpovlTGlzo2ue/fRiHf7kJAK1zmD736jH/0fVWNCQLh41btfAEZQ==", - "dependencies": { - "hashish": "", - "hat": "", - "mrcolor": "https://github.com/rook2pawn/mrcolor/archive/master.tar.gz" - } - }, - "node_modules/chart.js": { - "version": "4.5.1", - "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.5.1.tgz", - "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", - "license": "MIT", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/codepage": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz", - "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color-convert": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-0.2.1.tgz", - "integrity": "sha512-FWbwpCgyRV41Vml0iKU9UmL0dVTKORnm7ZC8h8cdfvutk2bU7ZcMLtSleggScK/IpUVXILg9Pw86LhPUQyTaVg==", - "engines": { - "node": "*" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compute-scroll-into-view": { - "version": "1.0.20", - "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", - "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "license": "MIT", - "dependencies": { - "is-what": "^3.14.1" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/core-js": { - "version": "3.48.0", - "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.48.0.tgz", - "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/docx-preview": { - "version": "0.3.7", - "resolved": "https://registry.npmmirror.com/docx-preview/-/docx-preview-0.3.7.tgz", - "integrity": "sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==", - "license": "Apache-2.0", - "dependencies": { - "jszip": ">=3.0.0" - } - }, - "node_modules/dom7": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz", - "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", - "license": "MIT", - "dependencies": { - "ssr-window": "^3.0.0-alpha.1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/echarts": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", - "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "2.3.0", - "zrender": "6.0.0" - } - }, - "node_modules/element-plus": { - "version": "2.13.2", - "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.2.tgz", - "integrity": "sha512-Zjzm1NnFXGhV4LYZ6Ze9skPlYi2B4KAmN18FL63A3PZcjhDfroHwhtM6RE8BonlOPHXUnPQynH0BgaoEfvhrGw==", - "license": "MIT", - "dependencies": { - "@ctrl/tinycolor": "^3.4.1", - "@element-plus/icons-vue": "^2.3.2", - "@floating-ui/dom": "^1.0.1", - "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", - "@types/lodash": "^4.17.20", - "@types/lodash-es": "^4.17.12", - "@vueuse/core": "^10.11.0", - "async-validator": "^4.2.5", - "dayjs": "^1.11.19", - "lodash": "^4.17.23", - "lodash-es": "^4.17.23", - "lodash-unified": "^1.0.3", - "memoize-one": "^6.0.0", - "normalize-wheel-es": "^1.2.0" - }, - "peerDependencies": { - "vue": "^3.3.0" - } - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "license": "MIT", - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/frac": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC", - "optional": true - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hashish": { - "version": "0.0.4", - "resolved": "https://registry.npmmirror.com/hashish/-/hashish-0.0.4.tgz", - "integrity": "sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==", - "license": "MIT/X11", - "dependencies": { - "traverse": ">=0.2.4" - }, - "engines": { - "node": "*" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hat": { - "version": "0.0.3", - "resolved": "https://registry.npmmirror.com/hat/-/hat-0.0.3.tgz", - "integrity": "sha512-zpImx2GoKXy42fVDSEad2BPKuSQdLcqsCYa48K3zHSzM/ugWuYjLDr8IXxpVuL7uCLHw56eaiLxCRthhOzf5ug==", - "license": "MIT/X11", - "engines": { - "node": "*" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz", - "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/i18next": { - "version": "20.6.1", - "resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz", - "integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "license": "MIT", - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-hotkey": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz", - "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", - "license": "MIT" - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "license": "MIT" - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "license": "MIT" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/less": { - "version": "4.5.1", - "resolved": "https://registry.npmmirror.com/less/-/less-4.5.1.tgz", - "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "needle": "^3.1.0", - "source-map": "~0.6.0" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/lodash-unified": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", - "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", - "license": "MIT", - "peerDependencies": { - "@types/lodash-es": "*", - "lodash": "*", - "lodash-es": "*" - } - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", - "license": "MIT" - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", - "license": "MIT" - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT" - }, - "node_modules/lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==", - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "license": "MIT", - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-match": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz", - "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", - "license": "ISC", - "dependencies": { - "wildcard": "^1.1.0" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/mrcolor": { - "version": "0.0.1", - "resolved": "https://github.com/rook2pawn/mrcolor/archive/master.tar.gz", - "integrity": "sha512-feteSepg0FRp0fW3RafigAjU7gXiiaa4OlMW39FEmcvQPbD7Zlpc2PSu4hVBPSBR4XNee8n6EjCTfK0O37DL5A==", - "license": "MIT/X11", - "dependencies": { - "color-convert": "0.2.x" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/namespace-emitter": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz", - "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/needle": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", - "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "license": "ISC" - }, - "node_modules/normalize-wheel-es": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", - "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", - "license": "BSD-3-Clause" - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/os": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/os/-/os-0.1.2.tgz", - "integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ==", - "license": "MIT" - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pinia": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", - "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^7.7.7" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "typescript": ">=4.5.0", - "vue": "^3.5.11" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/preact": { - "version": "10.28.4", - "resolved": "https://registry.npmmirror.com/preact/-/preact-10.28.4.tgz", - "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "license": "MIT", - "optional": true - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "optional": true - }, - "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", - "license": "BlueOak-1.0.0", - "optional": true, - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/scroll-into-view-if-needed": { - "version": "2.2.31", - "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", - "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", - "license": "MIT", - "dependencies": { - "compute-scroll-into-view": "^1.0.20" - } - }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", - "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slate": { - "version": "0.72.8", - "resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz", - "integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==", - "license": "MIT", - "dependencies": { - "immer": "^9.0.6", - "is-plain-object": "^5.0.0", - "tiny-warning": "^1.0.3" - } - }, - "node_modules/slate-history": { - "version": "0.66.0", - "resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz", - "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^5.0.0" - }, - "peerDependencies": { - "slate": ">=0.65.3" - } - }, - "node_modules/snabbdom": { - "version": "3.6.3", - "resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.3.tgz", - "integrity": "sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==", - "license": "MIT", - "engines": { - "node": ">=12.17.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssf": { - "version": "0.11.2", - "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz", - "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", - "license": "Apache-2.0", - "dependencies": { - "frac": "~1.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/ssr-window": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz", - "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==", - "license": "MIT" - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "license": "MIT", - "dependencies": { - "copy-anything": "^4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/superjson/node_modules/copy-anything": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "license": "MIT", - "dependencies": { - "is-what": "^5.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/superjson/node_modules/is-what": { - "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/traverse": { - "version": "0.6.11", - "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.6.11.tgz", - "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", - "license": "MIT", - "dependencies": { - "gopd": "^1.2.0", - "typedarray.prototype.slice": "^1.0.5", - "which-typed-array": "^1.1.18" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray.prototype.slice": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", - "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "math-intrinsics": "^1.1.0", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-offset": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unimport": { - "version": "5.6.0", - "resolved": "https://registry.npmmirror.com/unimport/-/unimport-5.6.0.tgz", - "integrity": "sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "escape-string-regexp": "^5.0.0", - "estree-walker": "^3.0.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.0", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "pkg-types": "^2.3.0", - "scule": "^1.3.0", - "strip-literal": "^3.1.0", - "tinyglobby": "^0.2.15", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin-auto-import": { - "version": "20.3.0", - "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-20.3.0.tgz", - "integrity": "sha512-RcSEQiVv7g0mLMMXibYVKk8mpteKxvyffGuDKqZZiFr7Oq3PB1HwgHdK5O7H4AzbhzHoVKG0NnMnsk/1HIVYzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "picomatch": "^4.0.3", - "unimport": "^5.5.0", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@nuxt/kit": "^4.0.0", - "@vueuse/core": "*" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - }, - "@vueuse/core": { - "optional": true - } - } - }, - "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", - "dev": true, - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/unplugin-vue-components": { - "version": "30.0.0", - "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-30.0.0.tgz", - "integrity": "sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.3", - "debug": "^4.4.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.19", - "mlly": "^1.8.0", - "tinyglobby": "^0.2.15", - "unplugin": "^2.3.10", - "unplugin-utils": "^0.3.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@babel/parser": "^7.15.8", - "@nuxt/kit": "^3.2.2 || ^4.0.0", - "vue": "2 || 3" - }, - "peerDependenciesMeta": { - "@babel/parser": { - "optional": true - }, - "@nuxt/kit": { - "optional": true - } - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.29.tgz", - "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.29", - "@vue/compiler-sfc": "3.5.29", - "@vue/runtime-dom": "3.5.29", - "@vue/server-renderer": "3.5.29", - "@vue/shared": "3.5.29" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-i18n": { - "version": "9.14.4", - "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.14.4.tgz", - "integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==", - "license": "MIT", - "dependencies": { - "@intlify/core-base": "9.14.4", - "@intlify/shared": "9.14.4", - "@vue/devtools-api": "^6.5.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/vue-i18n/node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" - }, - "node_modules/vue-img-cutter": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/vue-img-cutter/-/vue-img-cutter-3.0.7.tgz", - "integrity": "sha512-fNw3kimawg9XVXDZCw2bI74NI+Jq+H42wjymatZVVSY46wuBty6LbQsu4GeVfo/yzpS9AHY0tzckpYzX3D2fmA==", - "license": "MIT", - "dependencies": { - "core-js": "^3.20.3", - "vue": "^3.2.29", - "vue-i18n": "^9.1.10" - } - }, - "node_modules/vue-router": { - "version": "4.6.4", - "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", - "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.4" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" - }, - "node_modules/vue3-pdf-app": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/vue3-pdf-app/-/vue3-pdf-app-1.0.3.tgz", - "integrity": "sha512-qegWTIF4wYKiocZ3KreB70wRXhqSdXWbdERDyyKzT7d5PbjKbS9tD6vaKkCqh3PzTM84NyKPYrQ3iuwJb60YPQ==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wildcard": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz", - "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==", - "license": "MIT" - }, - "node_modules/wmf": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz", - "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz", - "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/xlsx": { - "version": "0.18.5", - "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", - "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "cfb": "~1.2.1", - "codepage": "~1.15.0", - "crc-32": "~1.2.1", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.3.0" - }, - "bin": { - "xlsx": "bin/xlsx.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/zrender": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", - "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "2.3.0" - } - } - } -} +{ + "name": "pc", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pc", + "version": "0.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@wangeditor/editor": "^5.1.23", + "axios": "^1.13.1", + "chart": "^0.1.2", + "chart.js": "^4.5.1", + "docx-preview": "^0.3.7", + "echarts": "^6.0.0", + "element-plus": "^2.11.7", + "less": "^4.4.2", + "marked": "^16.4.1", + "os": "^0.1.2", + "pinia": "^3.0.3", + "vue": "^3.5.22", + "vue-img-cutter": "^3.0.7", + "vue-router": "^4.6.3", + "vue3-pdf-app": "^1.0.3", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/node": "^24.10.7", + "@vitejs/plugin-vue": "^6.0.1", + "typescript": "^5.9.3", + "unplugin-auto-import": "^20.2.0", + "unplugin-vue-components": "^30.0.0", + "vite": "^7.1.7" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@intlify/core-base": { + "version": "9.14.4", + "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.4.tgz", + "integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "9.14.4", + "@intlify/shared": "9.14.4" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.14.4", + "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.14.4.tgz", + "integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "9.14.4", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.14.4", + "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz", + "integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@transloadit/prettier-bytes": { + "version": "0.0.7", + "resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", + "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==", + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.14", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.14.tgz", + "integrity": "sha512-OowOUbD1lBCOFIPOZ8xnMIhgqA4sCutMiYOmPHL1PTLt5+y1XA+g2+yC9OOyz8p+deMZqPZLxfMjYIfrKsPeFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@uppy/companion-client": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz", + "integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^4.1.2", + "namespace-emitter": "^2.0.1" + } + }, + "node_modules/@uppy/core": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz", + "integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "0.0.7", + "@uppy/store-default": "^2.1.1", + "@uppy/utils": "^4.1.3", + "lodash.throttle": "^4.1.1", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "nanoid": "^3.1.25", + "preact": "^10.5.13" + } + }, + "node_modules/@uppy/store-default": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz", + "integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==", + "license": "MIT" + }, + "node_modules/@uppy/utils": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz", + "integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==", + "license": "MIT", + "dependencies": { + "lodash.throttle": "^4.1.1" + } + }, + "node_modules/@uppy/xhr-upload": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz", + "integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==", + "license": "MIT", + "dependencies": { + "@uppy/companion-client": "^2.2.2", + "@uppy/utils": "^4.1.2", + "nanoid": "^3.1.25" + }, + "peerDependencies": { + "@uppy/core": "^2.3.3" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", + "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.29.tgz", + "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.29", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", + "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", + "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.29", + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", + "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.29.tgz", + "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.29.tgz", + "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", + "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.29", + "@vue/runtime-core": "3.5.29", + "@vue/shared": "3.5.29", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.29.tgz", + "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29" + }, + "peerDependencies": { + "vue": "3.5.29" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.29.tgz", + "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@wangeditor/basic-modules": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz", + "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==", + "license": "MIT", + "dependencies": { + "is-url": "^1.2.4" + }, + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.throttle": "^4.1.1", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/code-highlight": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz", + "integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.23.0" + }, + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/core": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz", + "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==", + "license": "MIT", + "dependencies": { + "@types/event-emitter": "^0.3.3", + "event-emitter": "^0.3.5", + "html-void-elements": "^2.0.0", + "i18next": "^20.4.0", + "scroll-into-view-if-needed": "^2.2.28", + "slate-history": "^0.66.0" + }, + "peerDependencies": { + "@uppy/core": "^2.1.1", + "@uppy/xhr-upload": "^2.0.3", + "dom7": "^3.0.0", + "is-hotkey": "^0.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.foreach": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "lodash.toarray": "^4.4.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/editor": { + "version": "5.1.23", + "resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz", + "integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==", + "license": "MIT", + "dependencies": { + "@uppy/core": "^2.1.1", + "@uppy/xhr-upload": "^2.0.3", + "@wangeditor/basic-modules": "^1.1.7", + "@wangeditor/code-highlight": "^1.0.3", + "@wangeditor/core": "^1.1.19", + "@wangeditor/list-module": "^1.0.5", + "@wangeditor/table-module": "^1.1.4", + "@wangeditor/upload-image-module": "^1.0.2", + "@wangeditor/video-module": "^1.1.4", + "dom7": "^3.0.0", + "is-hotkey": "^0.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.foreach": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "lodash.toarray": "^4.4.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/list-module": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz", + "integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==", + "license": "MIT", + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/table-module": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz", + "integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==", + "license": "MIT", + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/upload-image-module": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz", + "integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==", + "license": "MIT", + "peerDependencies": { + "@uppy/core": "^2.0.3", + "@uppy/xhr-upload": "^2.0.3", + "@wangeditor/basic-modules": "1.x", + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.foreach": "^4.5.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/video-module": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz", + "integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==", + "license": "MIT", + "peerDependencies": { + "@uppy/core": "^2.1.4", + "@uppy/xhr-upload": "^2.0.7", + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chart": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/chart/-/chart-0.1.2.tgz", + "integrity": "sha512-MSiVzAd3qUEXv54k9KGe1oIoC7WG32W9wtjpovlTGlzo2ue/fRiHf7kJAK1zmD736jH/0fVWNCQLh41btfAEZQ==", + "dependencies": { + "hashish": "", + "hat": "", + "mrcolor": "https://github.com/rook2pawn/mrcolor/archive/master.tar.gz" + } + }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-0.2.1.tgz", + "integrity": "sha512-FWbwpCgyRV41Vml0iKU9UmL0dVTKORnm7ZC8h8cdfvutk2bU7ZcMLtSleggScK/IpUVXILg9Pw86LhPUQyTaVg==", + "engines": { + "node": "*" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/docx-preview": { + "version": "0.3.7", + "resolved": "https://registry.npmmirror.com/docx-preview/-/docx-preview-0.3.7.tgz", + "integrity": "sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==", + "license": "Apache-2.0", + "dependencies": { + "jszip": ">=3.0.0" + } + }, + "node_modules/dom7": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz", + "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", + "license": "MIT", + "dependencies": { + "ssr-window": "^3.0.0-alpha.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/element-plus": { + "version": "2.13.2", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.2.tgz", + "integrity": "sha512-Zjzm1NnFXGhV4LYZ6Ze9skPlYi2B4KAmN18FL63A3PZcjhDfroHwhtM6RE8BonlOPHXUnPQynH0BgaoEfvhrGw==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "^10.11.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hashish": { + "version": "0.0.4", + "resolved": "https://registry.npmmirror.com/hashish/-/hashish-0.0.4.tgz", + "integrity": "sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.2.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hat": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/hat/-/hat-0.0.3.tgz", + "integrity": "sha512-zpImx2GoKXy42fVDSEad2BPKuSQdLcqsCYa48K3zHSzM/ugWuYjLDr8IXxpVuL7uCLHw56eaiLxCRthhOzf5ug==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/i18next": { + "version": "20.6.1", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz", + "integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", + "license": "MIT" + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/less": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/less/-/less-4.5.1.tgz", + "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", + "license": "ISC", + "dependencies": { + "wildcard": "^1.1.0" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mrcolor": { + "version": "0.0.1", + "resolved": "https://github.com/rook2pawn/mrcolor/archive/master.tar.gz", + "integrity": "sha512-feteSepg0FRp0fW3RafigAjU7gXiiaa4OlMW39FEmcvQPbD7Zlpc2PSu4hVBPSBR4XNee8n6EjCTfK0O37DL5A==", + "license": "MIT/X11", + "dependencies": { + "color-convert": "0.2.x" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/os": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/os/-/os-0.1.2.tgz", + "integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ==", + "license": "MIT" + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.28.4", + "resolved": "https://registry.npmmirror.com/preact/-/preact-10.28.4.tgz", + "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "license": "MIT", + "optional": true + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slate": { + "version": "0.72.8", + "resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz", + "integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==", + "license": "MIT", + "dependencies": { + "immer": "^9.0.6", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + } + }, + "node_modules/slate-history": { + "version": "0.66.0", + "resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz", + "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/snabbdom": { + "version": "3.6.3", + "resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.3.tgz", + "integrity": "sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==", + "license": "MIT", + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ssr-window": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz", + "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==", + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/superjson/node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/superjson/node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/traverse": { + "version": "0.6.11", + "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.6.11.tgz", + "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", + "license": "MIT", + "dependencies": { + "gopd": "^1.2.0", + "typedarray.prototype.slice": "^1.0.5", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", + "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "math-intrinsics": "^1.1.0", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-offset": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-5.6.0.tgz", + "integrity": "sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.0", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "pkg-types": "^2.3.0", + "scule": "^1.3.0", + "strip-literal": "^3.1.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.11", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "20.3.0", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-20.3.0.tgz", + "integrity": "sha512-RcSEQiVv7g0mLMMXibYVKk8mpteKxvyffGuDKqZZiFr7Oq3PB1HwgHdK5O7H4AzbhzHoVKG0NnMnsk/1HIVYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "picomatch": "^4.0.3", + "unimport": "^5.5.0", + "unplugin": "^2.3.11", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^4.0.0", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-components": { + "version": "30.0.0", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-30.0.0.tgz", + "integrity": "sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "debug": "^4.4.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.19", + "mlly": "^1.8.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.10", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2 || ^4.0.0", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.29.tgz", + "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-sfc": "3.5.29", + "@vue/runtime-dom": "3.5.29", + "@vue/server-renderer": "3.5.29", + "@vue/shared": "3.5.29" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-i18n": { + "version": "9.14.4", + "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.14.4.tgz", + "integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "9.14.4", + "@intlify/shared": "9.14.4", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-i18n/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-img-cutter": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/vue-img-cutter/-/vue-img-cutter-3.0.7.tgz", + "integrity": "sha512-fNw3kimawg9XVXDZCw2bI74NI+Jq+H42wjymatZVVSY46wuBty6LbQsu4GeVfo/yzpS9AHY0tzckpYzX3D2fmA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.20.3", + "vue": "^3.2.29", + "vue-i18n": "^9.1.10" + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue3-pdf-app": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/vue3-pdf-app/-/vue3-pdf-app-1.0.3.tgz", + "integrity": "sha512-qegWTIF4wYKiocZ3KreB70wRXhqSdXWbdERDyyKzT7d5PbjKbS9tD6vaKkCqh3PzTM84NyKPYrQ3iuwJb60YPQ==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==", + "license": "MIT" + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/backend/package.json b/backend/package.json index 3fb4aa3..b50b0fb 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,38 +1,38 @@ -{ - "name": "pc", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite --open", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "@wangeditor/editor": "^5.1.23", - "axios": "^1.13.1", - "chart": "^0.1.2", - "chart.js": "^4.5.1", - "docx-preview": "^0.3.7", - "echarts": "^6.0.0", - "element-plus": "^2.11.7", - "less": "^4.4.2", - "marked": "^16.4.1", - "os": "^0.1.2", - "pinia": "^3.0.3", - "vue": "^3.5.22", - "vue-img-cutter": "^3.0.7", - "vue-router": "^4.6.3", - "vue3-pdf-app": "^1.0.3", - "xlsx": "^0.18.5" - }, - "devDependencies": { - "@types/node": "^24.10.7", - "@vitejs/plugin-vue": "^6.0.1", - "typescript": "^5.9.3", - "unplugin-auto-import": "^20.2.0", - "unplugin-vue-components": "^30.0.0", - "vite": "^7.1.7" - } -} +{ + "name": "pc", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --open", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@wangeditor/editor": "^5.1.23", + "axios": "^1.13.1", + "chart": "^0.1.2", + "chart.js": "^4.5.1", + "docx-preview": "^0.3.7", + "echarts": "^6.0.0", + "element-plus": "^2.11.7", + "less": "^4.4.2", + "marked": "^16.4.1", + "os": "^0.1.2", + "pinia": "^3.0.3", + "vue": "^3.5.22", + "vue-img-cutter": "^3.0.7", + "vue-router": "^4.6.3", + "vue3-pdf-app": "^1.0.3", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/node": "^24.10.7", + "@vitejs/plugin-vue": "^6.0.1", + "typescript": "^5.9.3", + "unplugin-auto-import": "^20.2.0", + "unplugin-vue-components": "^30.0.0", + "vite": "^7.1.7" + } +} diff --git a/backend/src/App.vue b/backend/src/App.vue index bf9547c..3e92a0b 100644 --- a/backend/src/App.vue +++ b/backend/src/App.vue @@ -1,14 +1,14 @@ - - - - - + + + + + diff --git a/backend/src/api/analytics.js b/backend/src/api/analytics.js index 3f1780b..fc8d9d4 100644 --- a/backend/src/api/analytics.js +++ b/backend/src/api/analytics.js @@ -1,18 +1,18 @@ -// 数据统计相关API -import request from "@/utils/request"; - -// 获取内容统计 -export function getContentStats() { - return request({ - url: "/backend/contentstats", - method: "get", - }); -} - -// 获取用户统计 -export function getUserStats() { - return request({ - url: "/backend/usersstats", - method: "get", - }); +// 数据统计相关API +import request from "@/utils/request"; + +// 获取内容统计 +export function getContentStats() { + return request({ + url: "/backend/contentstats", + method: "get", + }); +} + +// 获取用户统计 +export function getUserStats() { + return request({ + url: "/backend/usersstats", + method: "get", + }); } \ No newline at end of file diff --git a/backend/src/api/article.js b/backend/src/api/article.js index 12348d5..6b89b9a 100644 --- a/backend/src/api/article.js +++ b/backend/src/api/article.js @@ -1,170 +1,170 @@ -// 文章管理相关API -import request from "@/utils/request"; - -// 获取文章列表 -export function listArticles(params) { - return request({ - url: `/backend/articlesList`, - method: "get", - params, - }); -} - -// 获取文章所有文章 -export function listAllArticles(params) { - return request({ - url: `/backend/allarticles`, - method: "get", - params, - }); -} - -// 获取文章详情 -export function getArticle(id) { - return request({ - url: `/backend/articles/${id}`, - method: "get", - }); -} - -// 创建文章 -export function createArticle(data) { - return request({ - url: '/backend/createarticle', - method: 'post', - data, - }); -} - -// 编辑文章 -export function editArticle(id, data) { - return request({ - url: `/backend/editarticle/${id}`, - method: 'post', - data, - }); -} - -// 删除文章 -export function deleteArticle(id) { - return request({ - url: `/backend/deletearticle/${id}`, - method: "delete", - }); -} - -// 发布文章 -export function publishArticle(id,uid) { - return request({ - url: `/backend/publisharticle/${id}`, - method: 'post', - data: { - uid - } - }); -} - -// 下架文章 -export function unPublishArticle(id) { - return request({ - url: `/backend/unPublisharticle/${id}`, - method: 'post' - }); -} - -// 文章推荐 -export function articleRecommend(id) { - return request({ - url: `/backend/articleRecommend/${id}`, - method: 'post' - }); -} - -// 取消文章推荐 -export function unArticleRecommend(id) { - return request({ - url: `/backend/unArticleRecommend/${id}`, - method: 'post' - }); -} - -// 文章置顶 -export function articleTop(id) { - return request({ - url: `/backend/articleTop/${id}`, - method: 'post' - }); -} - -// 取消文章置顶 -export function unArticleTop(id) { - return request({ - url: `/backend/unArticleTop/${id}`, - method: 'post' - }); -} - - - -////////////////////////////分类相关//////////////////////////// - -// 获取所有分类列表 -export function allCategories(params) { - return request({ - url: `/backend/allcategories`, - method: "get", - params, - }); -} - -// 获取分类列表 -export function listCategories(params) { - return request({ - url: `/backend/categories`, - method: "get", - params, - }); -} - -// 获取分类详情 -export function getCategory(id) { - return request({ - url: `/backend/categories/${id}`, - method: "get", - }); -} - -// 创建分类 -export function createCategory(data) { - return request({ - url: `/backend/createCategory`, - method: "post", - data, - }); -} - -// 更新分类 -export function editCategory(id, data) { - return request({ - url: `/backend/editCategory/${id}`, - method: "post", - data, - }); -} - -// 删除分类 -export function deleteCategory(id) { - return request({ - url: `/backend/categories/${id}`, - method: "delete", - }); -} - -// 更新分类状态 -export function updateCategoryStatus(id, status) { - return request({ - url: `/backend/categories/${id}/status`, - method: "patch", - data: { status }, - }); +// 文章管理相关API +import request from "@/utils/request"; + +// 获取文章列表 +export function listArticles(params) { + return request({ + url: `/backend/articlesList`, + method: "get", + params, + }); +} + +// 获取文章所有文章 +export function listAllArticles(params) { + return request({ + url: `/backend/allarticles`, + method: "get", + params, + }); +} + +// 获取文章详情 +export function getArticle(id) { + return request({ + url: `/backend/articles/${id}`, + method: "get", + }); +} + +// 创建文章 +export function createArticle(data) { + return request({ + url: '/backend/createarticle', + method: 'post', + data, + }); +} + +// 编辑文章 +export function editArticle(id, data) { + return request({ + url: `/backend/editarticle/${id}`, + method: 'post', + data, + }); +} + +// 删除文章 +export function deleteArticle(id) { + return request({ + url: `/backend/deletearticle/${id}`, + method: "delete", + }); +} + +// 发布文章 +export function publishArticle(id,uid) { + return request({ + url: `/backend/publisharticle/${id}`, + method: 'post', + data: { + uid + } + }); +} + +// 下架文章 +export function unPublishArticle(id) { + return request({ + url: `/backend/unPublisharticle/${id}`, + method: 'post' + }); +} + +// 文章推荐 +export function articleRecommend(id) { + return request({ + url: `/backend/articleRecommend/${id}`, + method: 'post' + }); +} + +// 取消文章推荐 +export function unArticleRecommend(id) { + return request({ + url: `/backend/unArticleRecommend/${id}`, + method: 'post' + }); +} + +// 文章置顶 +export function articleTop(id) { + return request({ + url: `/backend/articleTop/${id}`, + method: 'post' + }); +} + +// 取消文章置顶 +export function unArticleTop(id) { + return request({ + url: `/backend/unArticleTop/${id}`, + method: 'post' + }); +} + + + +////////////////////////////分类相关//////////////////////////// + +// 获取所有分类列表 +export function allCategories(params) { + return request({ + url: `/backend/allcategories`, + method: "get", + params, + }); +} + +// 获取分类列表 +export function listCategories(params) { + return request({ + url: `/backend/categories`, + method: "get", + params, + }); +} + +// 获取分类详情 +export function getCategory(id) { + return request({ + url: `/backend/categories/${id}`, + method: "get", + }); +} + +// 创建分类 +export function createCategory(data) { + return request({ + url: `/backend/createCategory`, + method: "post", + data, + }); +} + +// 更新分类 +export function editCategory(id, data) { + return request({ + url: `/backend/editCategory/${id}`, + method: "post", + data, + }); +} + +// 删除分类 +export function deleteCategory(id) { + return request({ + url: `/backend/categories/${id}`, + method: "delete", + }); +} + +// 更新分类状态 +export function updateCategoryStatus(id, status) { + return request({ + url: `/backend/categories/${id}/status`, + method: "patch", + data: { status }, + }); } \ No newline at end of file diff --git a/backend/src/api/babyhealth.js b/backend/src/api/babyhealth.js index b50be60..484f7c9 100644 --- a/backend/src/api/babyhealth.js +++ b/backend/src/api/babyhealth.js @@ -1,179 +1,179 @@ -import request from "@/utils/request"; - -/************************************************* - ****************** 宝贝相关接口 ****************** - *************************************************/ - -/** - * 获取宝贝列表 - * @returns {Promise} - */ -export function getBabyList() { - return request({ - url: '/backend/babys/list', - method: 'get' - }); -} - -/** - * 获取宝贝详情 - * @param {number} id 宝贝ID - * @returns {Promise} - */ -export function getBabyDetail(id) { - return request({ - url: `/backend/babys/${id}`, - method: "get", - }); -} - -/** - * 创建宝贝数据 - * @param {Object} data 宝贝数据 - * @returns {Promise} - */ -export function createBaby(data) { - return request({ - url: "/backend/babys", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -// 更新宝贝信息 -export function editBaby(id, data) { - return request({ - url: `/backend/baby/update/${id}`, - method: 'post', - data: data - }); -} - -/** - * 删除宝贝数据 - * @param {number} id 宝贝ID - * @returns {Promise} - */ -export function deleteBaby(id) { - return request({ - url: `/backend/babys/${id}`, - method: "delete", - }); -} - -/** - * 绑定父母 - * @param {number} id 宝贝ID - * @param {Object} data 绑定数据 - * @returns {Promise} - */ -export function bindParent(id, data) { - return request({ - url: `/backend/babys/bindparents/${id}`, - method: "post", - data: data, - }); -} - -/** - * 获取父母 - * @param {number} id 宝贝ID - * @returns {Promise} - */ -export function getParents(id) { - return request({ - url: `/backend/babys/getParents/${id}`, - method: "get", - }); -} - -/************************************************* - ****************** 用户相关接口 ****************** - *************************************************/ - -/** - * 获取用户列表 - * @returns {Promise} - */ -export function getUserList() { - return request({ - url: "/backend/babyhealthUser/list", - method: "get", - }); -} - -/** - * 获取用户详情 - * @param {number} id 用户ID - * @returns {Promise} - */ -export function getUserDetail(id) { - return request({ - url: `/backend/babyhealthUser/detail/${id}`, - method: "get", - }); -} - -/** - * 创建用户数据 - * @param {Object} data 用户数据 - * @returns {Promise} - */ -export function createUser(data) { - return request({ - url: "/backend/babyhealthUser/create", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 更新用户数据 - * @param {number} id 用户ID - * @param {Object} data 更新的数据 - * @returns {Promise} - */ -export function updateUser(id, data) { - return request({ - url: `/backend/babyhealthUser/update/${id}`, - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 删除用户数据 - * @param {number} id 用户ID - * @returns {Promise} - */ -export function deleteUser(id) { - return request({ - url: `/backend/babyhealthUser/delete/${id}`, - method: "delete", - }); -} - - -/************************************************* - ****************** 仪表盘相关接口 ****************** - *************************************************/ - -/** - * dashborad总体输出 - * @returns {Promise} - */ -export function getDashborad() { - return request({ - url: "/backend/babyhealthDashborad/dashborad", - method: "get", - }); +import request from "@/utils/request"; + +/************************************************* + ****************** 宝贝相关接口 ****************** + *************************************************/ + +/** + * 获取宝贝列表 + * @returns {Promise} + */ +export function getBabyList() { + return request({ + url: '/backend/babys/list', + method: 'get' + }); +} + +/** + * 获取宝贝详情 + * @param {number} id 宝贝ID + * @returns {Promise} + */ +export function getBabyDetail(id) { + return request({ + url: `/backend/babys/${id}`, + method: "get", + }); +} + +/** + * 创建宝贝数据 + * @param {Object} data 宝贝数据 + * @returns {Promise} + */ +export function createBaby(data) { + return request({ + url: "/backend/babys", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +// 更新宝贝信息 +export function editBaby(id, data) { + return request({ + url: `/backend/baby/update/${id}`, + method: 'post', + data: data + }); +} + +/** + * 删除宝贝数据 + * @param {number} id 宝贝ID + * @returns {Promise} + */ +export function deleteBaby(id) { + return request({ + url: `/backend/babys/${id}`, + method: "delete", + }); +} + +/** + * 绑定父母 + * @param {number} id 宝贝ID + * @param {Object} data 绑定数据 + * @returns {Promise} + */ +export function bindParent(id, data) { + return request({ + url: `/backend/babys/bindparents/${id}`, + method: "post", + data: data, + }); +} + +/** + * 获取父母 + * @param {number} id 宝贝ID + * @returns {Promise} + */ +export function getParents(id) { + return request({ + url: `/backend/babys/getParents/${id}`, + method: "get", + }); +} + +/************************************************* + ****************** 用户相关接口 ****************** + *************************************************/ + +/** + * 获取用户列表 + * @returns {Promise} + */ +export function getUserList() { + return request({ + url: "/backend/babyhealthUser/list", + method: "get", + }); +} + +/** + * 获取用户详情 + * @param {number} id 用户ID + * @returns {Promise} + */ +export function getUserDetail(id) { + return request({ + url: `/backend/babyhealthUser/detail/${id}`, + method: "get", + }); +} + +/** + * 创建用户数据 + * @param {Object} data 用户数据 + * @returns {Promise} + */ +export function createUser(data) { + return request({ + url: "/backend/babyhealthUser/create", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 更新用户数据 + * @param {number} id 用户ID + * @param {Object} data 更新的数据 + * @returns {Promise} + */ +export function updateUser(id, data) { + return request({ + url: `/backend/babyhealthUser/update/${id}`, + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 删除用户数据 + * @param {number} id 用户ID + * @returns {Promise} + */ +export function deleteUser(id) { + return request({ + url: `/backend/babyhealthUser/delete/${id}`, + method: "delete", + }); +} + + +/************************************************* + ****************** 仪表盘相关接口 ****************** + *************************************************/ + +/** + * dashborad总体输出 + * @returns {Promise} + */ +export function getDashborad() { + return request({ + url: "/backend/babyhealthDashborad/dashborad", + method: "get", + }); } \ No newline at end of file diff --git a/backend/src/api/banner.js b/backend/src/api/banner.js index 51c6b75..1733daa 100644 --- a/backend/src/api/banner.js +++ b/backend/src/api/banner.js @@ -1,55 +1,55 @@ -import request from "@/utils/request"; - -/** - * 获取所有Banner - * @returns {Promise} - */ -export function getBanners() { - return request({ - url: "/backend/allbanners", - method: "get", - }); -} - -/** - * 创建Banner - * @param {Object} bannerData Banner数据 - * @returns {Promise} - */ -export function createBanner(formData) { - return request({ - url: "/backend/createbanner", - method: "post", - data: formData, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 编辑Banner - * @param {number|string} id Banner ID - * @param {Object} bannerData 更新的数据 - * @returns {Promise} - */ -export function editBanner(id, bannerData) { - return request({ - url: `/backend/editbanner/${id}`, - method: "post", - data: bannerData, - }); -} - -/** - * 删除Banner - * @param {number|string} id Banner ID - * @returns {Promise} - */ -export function deleteBanner(id) { - return request({ - url: `/backend/deletebanner/${id}`, - method: "delete", - }); -} - +import request from "@/utils/request"; + +/** + * 获取所有Banner + * @returns {Promise} + */ +export function getBanners() { + return request({ + url: "/backend/allbanners", + method: "get", + }); +} + +/** + * 创建Banner + * @param {Object} bannerData Banner数据 + * @returns {Promise} + */ +export function createBanner(formData) { + return request({ + url: "/backend/createbanner", + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 编辑Banner + * @param {number|string} id Banner ID + * @param {Object} bannerData 更新的数据 + * @returns {Promise} + */ +export function editBanner(id, bannerData) { + return request({ + url: `/backend/editbanner/${id}`, + method: "post", + data: bannerData, + }); +} + +/** + * 删除Banner + * @param {number|string} id Banner ID + * @returns {Promise} + */ +export function deleteBanner(id) { + return request({ + url: `/backend/deletebanner/${id}`, + method: "delete", + }); +} + diff --git a/backend/src/api/contact.js b/backend/src/api/contact.js index 81580d1..308d07a 100644 --- a/backend/src/api/contact.js +++ b/backend/src/api/contact.js @@ -1,33 +1,33 @@ -import request from '@/utils/request' - -export function listContacts(params) { - return request({ - url: '/backend/crm/contact/list', - method: 'get', - params, - }) -} - -export function createContact(data) { - return request({ - url: '/backend/crm/contact/add', - method: 'post', - data, - }) -} - -export function updateContact(data) { - return request({ - url: '/backend/crm/contact/edit', - method: 'post', - data, - }) -} - -export function deleteContact(data) { - return request({ - url: '/backend/crm/contact/delete', - method: 'post', - data, - }) -} +import request from '@/utils/request' + +export function listContacts(params) { + return request({ + url: '/backend/crm/contact/list', + method: 'get', + params, + }) +} + +export function createContact(data) { + return request({ + url: '/backend/crm/contact/add', + method: 'post', + data, + }) +} + +export function updateContact(data) { + return request({ + url: '/backend/crm/contact/edit', + method: 'post', + data, + }) +} + +export function deleteContact(data) { + return request({ + url: '/backend/crm/contact/delete', + method: 'post', + data, + }) +} diff --git a/backend/src/api/dashboard.js b/backend/src/api/dashboard.js index ca48105..cb35249 100644 --- a/backend/src/api/dashboard.js +++ b/backend/src/api/dashboard.js @@ -1,41 +1,41 @@ -import request from "@/utils/request"; - -/** - * 获取平台统计数据(平台用户使用) - * @returns {Promise} - */ -export function getPlatformStats() { - return request({ - url: "/backend/dashboard/platform-stats", - method: "get", - }); -} - -/** - * 获取租户统计数据(租户员工使用) - * @returns {Promise} - */ -export function getTenantStats() { - return request({ - url: "/backend/dashboard/tenant-stats", - method: "get", - }); -} - -/** - * 获取用户活动日志(操作日志和登录日志) - * @param {number} pageNum - 页码 - * @param {number} pageSize - 每页数量 - * @returns {Promise} - */ -export function getActivityLogs(pageNum = 1, pageSize = 10) { - return request({ - url: "/backend/dashboard/user-activity-logs", - method: "get", - params: { - page_num: pageNum, - page_size: pageSize, - }, - }); -} - +import request from "@/utils/request"; + +/** + * 获取平台统计数据(平台用户使用) + * @returns {Promise} + */ +export function getPlatformStats() { + return request({ + url: "/backend/dashboard/platform-stats", + method: "get", + }); +} + +/** + * 获取租户统计数据(租户员工使用) + * @returns {Promise} + */ +export function getTenantStats() { + return request({ + url: "/backend/dashboard/tenant-stats", + method: "get", + }); +} + +/** + * 获取用户活动日志(操作日志和登录日志) + * @param {number} pageNum - 页码 + * @param {number} pageSize - 每页数量 + * @returns {Promise} + */ +export function getActivityLogs(pageNum = 1, pageSize = 10) { + return request({ + url: "/backend/dashboard/user-activity-logs", + method: "get", + params: { + page_num: pageNum, + page_size: pageSize, + }, + }); +} + diff --git a/backend/src/api/demand.js b/backend/src/api/demand.js index 35a96a9..33c17bd 100644 --- a/backend/src/api/demand.js +++ b/backend/src/api/demand.js @@ -1,51 +1,51 @@ -import request from "@/utils/request"; - -/** - * 获取需求列表 - * @returns {Promise} - */ -export function getDemandList() { - return request({ - url: "/backend/demandList", - method: "get", - }); -} - -/** - * 新增需求 - * @param {Object} data 需求数据 - * @returns {Promise} - */ -export function addDemand(data) { - return request({ - url: "/backend/addDemand", - method: "post", - data, - }); -} - -/** - * 编辑需求 - * @param {number} id 需求ID - * @param {Object} data 需求数据 - * @returns {Promise} - */ -export function editDemand(id, data) { - return request({ - url: `/backend/editDemand/${id}`, - method: "post", - data, - }); -} - -/** - * 删除需求 - * @param {number} id 需求ID - * @returns {Promise} - */ -export function deleteDemand(id) { - return request({ - url: `/backend/deleteDemand/${id}`, - method: "post", - }); -} +import request from "@/utils/request"; + +/** + * 获取需求列表 + * @returns {Promise} + */ +export function getDemandList() { + return request({ + url: "/backend/demandList", + method: "get", + }); +} + +/** + * 新增需求 + * @param {Object} data 需求数据 + * @returns {Promise} + */ +export function addDemand(data) { + return request({ + url: "/backend/addDemand", + method: "post", + data, + }); +} + +/** + * 编辑需求 + * @param {number} id 需求ID + * @param {Object} data 需求数据 + * @returns {Promise} + */ +export function editDemand(id, data) { + return request({ + url: `/backend/editDemand/${id}`, + method: "post", + data, + }); +} + +/** + * 删除需求 + * @param {number} id 需求ID + * @returns {Promise} + */ +export function deleteDemand(id) { + return request({ + url: `/backend/deleteDemand/${id}`, + method: "post", + }); +} diff --git a/backend/src/api/department.js b/backend/src/api/department.js index 13882eb..5d16868 100644 --- a/backend/src/api/department.js +++ b/backend/src/api/department.js @@ -1,44 +1,44 @@ -import request from '@/utils/request'; - -// 获取租户下的所有部门 -export function getTenantDepartments(tenantId) { - return request({ - url: `/backend/departments/tenant/${tenantId}`, - method: 'get', - }); -} - -// 获取部门详情 -export function getDepartmentInfo(departmentId) { - return request({ - url: `/backend/departments/${departmentId}`, - method: 'get', - }); -} - -// 添加部门 -export function addDepartment(data) { - return request({ - url: '/backend/departments', - method: 'post', - data, - }); -} - -// 更新部门信息 -export function editDepartment(departmentId, data) { - return request({ - url: `/backend/departments/${departmentId}`, - method: 'put', - data, - }); -} - -// 删除部门 -export function deleteDepartment(departmentId) { - return request({ - url: `/backend/departments/${departmentId}`, - method: 'delete', - }); -} - +import request from '@/utils/request'; + +// 获取租户下的所有部门 +export function getTenantDepartments(tenantId) { + return request({ + url: `/backend/departments/tenant/${tenantId}`, + method: 'get', + }); +} + +// 获取部门详情 +export function getDepartmentInfo(departmentId) { + return request({ + url: `/backend/departments/${departmentId}`, + method: 'get', + }); +} + +// 添加部门 +export function addDepartment(data) { + return request({ + url: '/backend/departments', + method: 'post', + data, + }); +} + +// 更新部门信息 +export function editDepartment(departmentId, data) { + return request({ + url: `/backend/departments/${departmentId}`, + method: 'put', + data, + }); +} + +// 删除部门 +export function deleteDepartment(departmentId) { + return request({ + url: `/backend/departments/${departmentId}`, + method: 'delete', + }); +} + diff --git a/backend/src/api/dict.js b/backend/src/api/dict.js index 6935034..6021512 100644 --- a/backend/src/api/dict.js +++ b/backend/src/api/dict.js @@ -1,114 +1,114 @@ -import request from '@/utils/request' - -// 获取字典类型列表 -export function getDictTypes(params) { - return request({ - url: '/backend/dict/types', - method: 'get', - params - }) -} - -// 根据ID获取字典类型 -export function getDictTypeById(id) { - return request({ - url: `/backend/dict/types/${id}`, - method: 'get' - }) -} - -// 添加字典类型 -export function addDictType(data) { - return request({ - url: '/backend/dict/types', - method: 'post', - data: { - ...data, - is_global: data.is_global !== undefined ? data.is_global : 0 - } - }) -} - -// 更新字典类型 -export function updateDictType(id, data) { - return request({ - url: `/backend/dict/types/${id}`, - method: 'put', - data: { - ...data, - is_global: data.is_global !== undefined ? data.is_global : 0 - } - }) -} - -// 删除字典类型 -export function deleteDictType(id) { - return request({ - url: `/backend/dict/types/${id}`, - method: 'delete' - }) -} - -// 获取字典项列表 -export function getDictItems(params) { - return request({ - url: '/backend/dict/items', - method: 'get', - params - }) -} - -// 根据ID获取字典项 -export function getDictItemById(id) { - return request({ - url: `/backend/dict/items/${id}`, - method: 'get' - }) -} - -// 添加字典项 -export function addDictItem(data) { - return request({ - url: '/backend/dict/items', - method: 'post', - data - }) -} - -// 更新字典项 -export function updateDictItem(id, data) { - return request({ - url: `/backend/dict/items/${id}`, - method: 'put', - data - }) -} - -// 删除字典项 -export function deleteDictItem(id) { - return request({ - url: `/backend/dict/items/${id}`, - method: 'delete' - }) -} - -// 根据字典编码获取字典项(用于业务查询) -export function getDictItemsByCode(code, includeDisabled = false) { - return request({ - url: `/backend/dict/items/code/${code}`, - method: 'get', - params: { - include_disabled: includeDisabled ? '1' : '0' - } - }) -} - -// 批量更新字典项排序 -export function batchUpdateDictItemSort(data) { - return request({ - url: '/backend/dict/items/sort', - method: 'put', - data - }) -} - +import request from '@/utils/request' + +// 获取字典类型列表 +export function getDictTypes(params) { + return request({ + url: '/backend/dict/types', + method: 'get', + params + }) +} + +// 根据ID获取字典类型 +export function getDictTypeById(id) { + return request({ + url: `/backend/dict/types/${id}`, + method: 'get' + }) +} + +// 添加字典类型 +export function addDictType(data) { + return request({ + url: '/backend/dict/types', + method: 'post', + data: { + ...data, + is_global: data.is_global !== undefined ? data.is_global : 0 + } + }) +} + +// 更新字典类型 +export function updateDictType(id, data) { + return request({ + url: `/backend/dict/types/${id}`, + method: 'put', + data: { + ...data, + is_global: data.is_global !== undefined ? data.is_global : 0 + } + }) +} + +// 删除字典类型 +export function deleteDictType(id) { + return request({ + url: `/backend/dict/types/${id}`, + method: 'delete' + }) +} + +// 获取字典项列表 +export function getDictItems(params) { + return request({ + url: '/backend/dict/items', + method: 'get', + params + }) +} + +// 根据ID获取字典项 +export function getDictItemById(id) { + return request({ + url: `/backend/dict/items/${id}`, + method: 'get' + }) +} + +// 添加字典项 +export function addDictItem(data) { + return request({ + url: '/backend/dict/items', + method: 'post', + data + }) +} + +// 更新字典项 +export function updateDictItem(id, data) { + return request({ + url: `/backend/dict/items/${id}`, + method: 'put', + data + }) +} + +// 删除字典项 +export function deleteDictItem(id) { + return request({ + url: `/backend/dict/items/${id}`, + method: 'delete' + }) +} + +// 根据字典编码获取字典项(用于业务查询) +export function getDictItemsByCode(code, includeDisabled = false) { + return request({ + url: `/backend/dict/items/code/${code}`, + method: 'get', + params: { + include_disabled: includeDisabled ? '1' : '0' + } + }) +} + +// 批量更新字典项排序 +export function batchUpdateDictItemSort(data) { + return request({ + url: '/backend/dict/items/sort', + method: 'put', + data + }) +} + diff --git a/backend/src/api/domain.js b/backend/src/api/domain.js index 6ebec0c..1df1d21 100644 --- a/backend/src/api/domain.js +++ b/backend/src/api/domain.js @@ -1,110 +1,110 @@ -import request from '@/utils/request' - -// ==================== 主域名池管理 ==================== - -// 获取域名池列表 -export function getDomainPoolList(params) { - return request({ - url: '/backend/domain/pool/index', - method: 'get', - params - }) -} - -// 获取启用的主域名列表 -export function getEnabledDomains() { - return request({ - url: '/backend/domain/pool/getEnabledDomains', - method: 'get' - }) -} - -// 创建主域名 -export function createDomainPool(data) { - return request({ - url: '/backend/domain/pool/create', - method: 'post', - data - }) -} - -// 更新主域名 -export function updateDomainPool(data) { - return request({ - url: '/backend/domain/pool/update', - method: 'post', - data - }) -} - -// 删除主域名 -export function deleteDomainPool(id) { - return request({ - url: `/backend/domain/pool/delete/${id}`, - method: 'delete' - }) -} - -// 切换主域名状态 -export function toggleDomainPoolStatus(id) { - return request({ - url: '/backend/domain/pool/toggleStatus', - method: 'post', - data: { id } - }) -} - -// ==================== 租户域名管理 ==================== - -// 获取租户域名列表(管理员) -export function getTenantDomainList(params) { - return request({ - url: '/backend/domain/tenant/index', - method: 'get', - params - }) -} - -// 获取当前租户的域名列表 -export function getMyDomains(params) { - return request({ - url: '/backend/domain/tenant/myDomains', - method: 'get', - params - }) -} - -// 申请二级域名 -export function applyTenantDomain(data) { - return request({ - url: '/backend/domain/tenant/apply', - method: 'post', - data - }) -} - -// 审核租户域名 -export function auditTenantDomain(data) { - return request({ - url: '/backend/domain/tenant/audit', - method: 'post', - data - }) -} - -// 禁用/启用租户域名 -export function toggleTenantDomainStatus(id) { - return request({ - url: '/backend/domain/tenant/toggleStatus', - method: 'post', - data: { id } - }) -} - -// 删除租户域名 -export function deleteTenantDomain(id) { - return request({ - url: `/backend/domain/tenant/delete/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +// ==================== 主域名池管理 ==================== + +// 获取域名池列表 +export function getDomainPoolList(params) { + return request({ + url: '/backend/domain/pool/index', + method: 'get', + params + }) +} + +// 获取启用的主域名列表 +export function getEnabledDomains() { + return request({ + url: '/backend/domain/pool/getEnabledDomains', + method: 'get' + }) +} + +// 创建主域名 +export function createDomainPool(data) { + return request({ + url: '/backend/domain/pool/create', + method: 'post', + data + }) +} + +// 更新主域名 +export function updateDomainPool(data) { + return request({ + url: '/backend/domain/pool/update', + method: 'post', + data + }) +} + +// 删除主域名 +export function deleteDomainPool(id) { + return request({ + url: `/backend/domain/pool/delete/${id}`, + method: 'delete' + }) +} + +// 切换主域名状态 +export function toggleDomainPoolStatus(id) { + return request({ + url: '/backend/domain/pool/toggleStatus', + method: 'post', + data: { id } + }) +} + +// ==================== 租户域名管理 ==================== + +// 获取租户域名列表(管理员) +export function getTenantDomainList(params) { + return request({ + url: '/backend/domain/tenant/index', + method: 'get', + params + }) +} + +// 获取当前租户的域名列表 +export function getMyDomains(params) { + return request({ + url: '/backend/domain/tenant/myDomains', + method: 'get', + params + }) +} + +// 申请二级域名 +export function applyTenantDomain(data) { + return request({ + url: '/backend/domain/tenant/apply', + method: 'post', + data + }) +} + +// 审核租户域名 +export function auditTenantDomain(data) { + return request({ + url: '/backend/domain/tenant/audit', + method: 'post', + data + }) +} + +// 禁用/启用租户域名 +export function toggleTenantDomainStatus(id) { + return request({ + url: '/backend/domain/tenant/toggleStatus', + method: 'post', + data: { id } + }) +} + +// 删除租户域名 +export function deleteTenantDomain(id) { + return request({ + url: `/backend/domain/tenant/delete/${id}`, + method: 'delete' + }) +} diff --git a/backend/src/api/email.js b/backend/src/api/email.js index b6bf74b..889de93 100644 --- a/backend/src/api/email.js +++ b/backend/src/api/email.js @@ -1,36 +1,36 @@ -import request from "@/utils/request"; - -/** - * 获取邮箱信息 - * @returns {Promise} - */ -export function getEmailInfo() { - return request({ - url: "/backend/email/info", - method: "get", - }); -} - -/** - * 编辑邮箱信息 - * @returns {Promise} - */ -export function editEmailInfo(data) { - return request({ - url: "/backend/email/editinfo", - method: "post", - data, - }); -} - -/** - * 发送测试邮件 - * @returns {Promise} - */ -export function sendTestEmail(data) { - return request({ - url: "/backend/email/sendtestemail", - method: "post", - data, - }); +import request from "@/utils/request"; + +/** + * 获取邮箱信息 + * @returns {Promise} + */ +export function getEmailInfo() { + return request({ + url: "/backend/email/info", + method: "get", + }); +} + +/** + * 编辑邮箱信息 + * @returns {Promise} + */ +export function editEmailInfo(data) { + return request({ + url: "/backend/email/editinfo", + method: "post", + data, + }); +} + +/** + * 发送测试邮件 + * @returns {Promise} + */ +export function sendTestEmail(data) { + return request({ + url: "/backend/email/sendtestemail", + method: "post", + data, + }); } \ No newline at end of file diff --git a/backend/src/api/erp.js b/backend/src/api/erp.js index 3684bd7..84cde82 100644 --- a/backend/src/api/erp.js +++ b/backend/src/api/erp.js @@ -1,155 +1,155 @@ -import request from "@/utils/request"; - -/************************************************* - ****************** 组织机构相关接口 ****************** - *************************************************/ - -/** - * 获取组织机构列表 - * @returns {Promise} - */ -export function getOrganizationList() { - return request({ - url: '/backend/erp/getOrganization', - method: 'get' - }); -} - -/** - * 获取组织机构详情 - * @param {number} id 组织机构ID - * @returns {Promise} - */ -export function getOrganizationDetail(id) { - return request({ - url: `/backend/erp/getOrganizationDetail/${id}`, - method: "get", - }); -} - -/** - * 创建组织机构数据 - * @param {Object} data 组织机构数据 - * @returns {Promise} - */ -export function createOrganization(data) { - return request({ - url: "/backend/erp/createOrganization", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -// 更新组织机构信息 -export function editOrganization(id, data) { - return request({ - url: `/backend/erp/editOrganization/${id}`, - method: 'post', - data: data - }); -} - -/** - * 删除组织机构数据 - * @param {number} id 组织机构ID - * @returns {Promise} - */ -export function deleteOrganization(id) { - return request({ - url: `/backend/erp/deleteOrganization/${id}`, - method: "delete", - }); -} - -/** - * 获取企业单位列表 - * @returns {Promise} - */ -export function getCompanys() { - return request({ - url: '/backend/erp/getCompanys', - method: 'get' - }); -} - -/** - * 获取部门列表 - * @param {number} parentId 隶属单位ID - * @returns {Promise} - */ -export function getDepartments(parentId) { - return request({ - url: '/backend/erp/getDepartments', - method: 'get', - params: parentId ? { parent_id: parentId } : {} - }); -} - -/************************************************* - ****************** 员工相关接口 ****************** - *************************************************/ - -/** - * 获取员工列表 - * @param {number} tenantId 租户ID - * @returns {Promise} - */ -export function getEmployeeList(tenantId) { - return request({ - url: '/backend/erp/getEmployee', - method: 'get', - params: { tid: tenantId } - }); -} - -/** - * 获取员工详情 - * @param {number} id 员工ID - * @returns {Promise} - */ -export function getEmployeeDetail(id) { - return request({ - url: `/backend/erp/getEmployeeDetail/${id}`, - method: "get", - }); -} - -/** - * 创建员工数据 - * @param {Object} data 员工数据 - * @returns {Promise} - */ -export function createEmployee(data) { - return request({ - url: "/backend/erp/createEmployee", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -// 更新员工信息 -export function editEmployee(id, data) { - return request({ - url: `/backend/erp/editEmployee/${id}`, - method: 'post', - data: data - }); -} - -/** - * 删除员工数据 - * @param {number} id 员工ID - * @returns {Promise} - */ -export function deleteEmployee(id) { - return request({ - url: `/backend/erp/deleteEmployee/${id}`, - method: "delete", - }); -} +import request from "@/utils/request"; + +/************************************************* + ****************** 组织机构相关接口 ****************** + *************************************************/ + +/** + * 获取组织机构列表 + * @returns {Promise} + */ +export function getOrganizationList() { + return request({ + url: '/backend/erp/getOrganization', + method: 'get' + }); +} + +/** + * 获取组织机构详情 + * @param {number} id 组织机构ID + * @returns {Promise} + */ +export function getOrganizationDetail(id) { + return request({ + url: `/backend/erp/getOrganizationDetail/${id}`, + method: "get", + }); +} + +/** + * 创建组织机构数据 + * @param {Object} data 组织机构数据 + * @returns {Promise} + */ +export function createOrganization(data) { + return request({ + url: "/backend/erp/createOrganization", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +// 更新组织机构信息 +export function editOrganization(id, data) { + return request({ + url: `/backend/erp/editOrganization/${id}`, + method: 'post', + data: data + }); +} + +/** + * 删除组织机构数据 + * @param {number} id 组织机构ID + * @returns {Promise} + */ +export function deleteOrganization(id) { + return request({ + url: `/backend/erp/deleteOrganization/${id}`, + method: "delete", + }); +} + +/** + * 获取企业单位列表 + * @returns {Promise} + */ +export function getCompanys() { + return request({ + url: '/backend/erp/getCompanys', + method: 'get' + }); +} + +/** + * 获取部门列表 + * @param {number} parentId 隶属单位ID + * @returns {Promise} + */ +export function getDepartments(parentId) { + return request({ + url: '/backend/erp/getDepartments', + method: 'get', + params: parentId ? { parent_id: parentId } : {} + }); +} + +/************************************************* + ****************** 员工相关接口 ****************** + *************************************************/ + +/** + * 获取员工列表 + * @param {number} tenantId 租户ID + * @returns {Promise} + */ +export function getEmployeeList(tenantId) { + return request({ + url: '/backend/erp/getEmployee', + method: 'get', + params: { tid: tenantId } + }); +} + +/** + * 获取员工详情 + * @param {number} id 员工ID + * @returns {Promise} + */ +export function getEmployeeDetail(id) { + return request({ + url: `/backend/erp/getEmployeeDetail/${id}`, + method: "get", + }); +} + +/** + * 创建员工数据 + * @param {Object} data 员工数据 + * @returns {Promise} + */ +export function createEmployee(data) { + return request({ + url: "/backend/erp/createEmployee", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +// 更新员工信息 +export function editEmployee(id, data) { + return request({ + url: `/backend/erp/editEmployee/${id}`, + method: 'post', + data: data + }); +} + +/** + * 删除员工数据 + * @param {number} id 员工ID + * @returns {Promise} + */ +export function deleteEmployee(id) { + return request({ + url: `/backend/erp/deleteEmployee/${id}`, + method: "delete", + }); +} diff --git a/backend/src/api/file.js b/backend/src/api/file.js index 232844d..33b6ec4 100644 --- a/backend/src/api/file.js +++ b/backend/src/api/file.js @@ -1,211 +1,211 @@ -import request from "@/utils/request"; - -/** - * 获取用户分类 - * @returns {Promise} - */ -export function getUserCate() { - return request({ - url: `/backend/usercate`, - method: "get", - }); -} - -/** - * 获取所有文件 - * @returns {Promise} - */ -export function getAllFiles() { - return request({ - url: "/backend/allfiles", - method: "get", - }); -} - -/** - * 新建文件分组 - * @param {Object} data 文件分组数据 - * @returns {Promise} - */ -export function createFileCate(data) { - return request({ - url: "/backend/createfilecate", - method: "post", - data, - }); -} - -/** - * 重命名文件分组 - * @param {number|string} id 文件分组ID - * @param {Object} data 文件分组数据 - * @returns {Promise} - */ -export function renameFileCate(id, data) { - return request({ - url: `/backend/renamefilecate/${id}`, - method: "post", - data, - }); -} - -/** - * 删除文件分组 - * @param {number|string} id 文件分组ID - * @returns {Promise} - */ -export function deleteFileCate(id) { - return request({ - url: `/backend/deletefilecate/${id}`, - method: "delete", - }); -} - -/** - * 根据分类ID获取文件 - * @param {number|string} id 分类ID - * @param {number} page 页码,默认1 - * @param {number} pageSize 每页数量,默认24 - * @param {string} keyword 搜索关键词,可选 - * @returns {Promise} - */ -export function getCateFiles(id, page = 1, pageSize = 24, keyword = "") { - const params = { - page, - pageSize, - }; - if (keyword) { - params.keyword = keyword; - } - return request({ - url: `/backend/catefiles/${id}`, - method: "get", - params, - }); -} - -/** - * 根据ID获取文件 - * @param {number|string} id 文件ID - * @returns {Promise} - */ -export function getFileById(id) { - return request({ - url: `/backend/catefiles`, - method: "get", - }); -} - -/** - * 上传文件 - * @param {FormData} formData 文件数据 - * @param {Object} options 额外选项 - * @param {string} [options.cate] - * @returns {Promise} - */ -export function uploadFile(formData, options = {}) { - if (options.cate) { - formData.append('cate', options.cate); - } - - return request({ - url: "/backend/uploadfile", - method: "post", - data: formData, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 更新文件信息 - * @param {number|string} id 文件ID - * @param {Object} fileData 更新的数据 - * @returns {Promise} - */ -export function updateFile(id, fileData) { - return request({ - url: `/backend/updatefile/${id}`, - method: "post", - data: fileData, - }); -} - -/** - * 删除文件 - * @param {number|string} id 文件ID - * @returns {Promise} - */ -export function deleteFile(id) { - return request({ - url: `/backend/deletefile/${id}`, - method: "delete", - }); -} - -/** - * 删除文件 - * @param {number|string} id 文件ID - * @returns {Promise} - */ -export function deleteFilePermanently(id) { - return request({ - url: `/backend/deleteFilePermanently/${id}`, - method: "delete", - }); -} - -/** - * 移动文件 - * @param {number|string} id 文件ID - * @param {Object} fileData 更新的数据 - * @returns {Promise} - */ -export function moveFile(id, cate) { - return request({ - url: `/backend/movefile/${id}`, - method: "get", - params: { cate }, - }); -} - -/** - * 批量删除文件 - * @param {Array} ids 文件ID数组 - * @returns {Promise} - */ -export function batchDeleteFiles(ids) { - return request({ - url: "/backend/batchDeleteFiles", - method: "post", - data: { ids }, - }); -} - -/** - * 批量彻底删除文件 - * @param {Array} ids 文件ID数组 - * @returns {Promise} - */ -export function batchDeleteFilesPermanently(ids) { - return request({ - url: "/backend/batchDeleteFilesPermanently", - method: "post", - data: { ids }, - }); -} - -/** - * 批量移动文件 - * @param {Array} ids 文件ID数组 - * @param {number} cate 目标分类ID - * @returns {Promise} - */ -export function batchMoveFiles(ids, cate) { - return request({ - url: "/backend/batchMoveFiles", - method: "post", - data: { ids, cate }, - }); +import request from "@/utils/request"; + +/** + * 获取用户分类 + * @returns {Promise} + */ +export function getUserCate() { + return request({ + url: `/backend/usercate`, + method: "get", + }); +} + +/** + * 获取所有文件 + * @returns {Promise} + */ +export function getAllFiles() { + return request({ + url: "/backend/allfiles", + method: "get", + }); +} + +/** + * 新建文件分组 + * @param {Object} data 文件分组数据 + * @returns {Promise} + */ +export function createFileCate(data) { + return request({ + url: "/backend/createfilecate", + method: "post", + data, + }); +} + +/** + * 重命名文件分组 + * @param {number|string} id 文件分组ID + * @param {Object} data 文件分组数据 + * @returns {Promise} + */ +export function renameFileCate(id, data) { + return request({ + url: `/backend/renamefilecate/${id}`, + method: "post", + data, + }); +} + +/** + * 删除文件分组 + * @param {number|string} id 文件分组ID + * @returns {Promise} + */ +export function deleteFileCate(id) { + return request({ + url: `/backend/deletefilecate/${id}`, + method: "delete", + }); +} + +/** + * 根据分类ID获取文件 + * @param {number|string} id 分类ID + * @param {number} page 页码,默认1 + * @param {number} pageSize 每页数量,默认24 + * @param {string} keyword 搜索关键词,可选 + * @returns {Promise} + */ +export function getCateFiles(id, page = 1, pageSize = 24, keyword = "") { + const params = { + page, + pageSize, + }; + if (keyword) { + params.keyword = keyword; + } + return request({ + url: `/backend/catefiles/${id}`, + method: "get", + params, + }); +} + +/** + * 根据ID获取文件 + * @param {number|string} id 文件ID + * @returns {Promise} + */ +export function getFileById(id) { + return request({ + url: `/backend/catefiles`, + method: "get", + }); +} + +/** + * 上传文件 + * @param {FormData} formData 文件数据 + * @param {Object} options 额外选项 + * @param {string} [options.cate] + * @returns {Promise} + */ +export function uploadFile(formData, options = {}) { + if (options.cate) { + formData.append('cate', options.cate); + } + + return request({ + url: "/backend/uploadfile", + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 更新文件信息 + * @param {number|string} id 文件ID + * @param {Object} fileData 更新的数据 + * @returns {Promise} + */ +export function updateFile(id, fileData) { + return request({ + url: `/backend/updatefile/${id}`, + method: "post", + data: fileData, + }); +} + +/** + * 删除文件 + * @param {number|string} id 文件ID + * @returns {Promise} + */ +export function deleteFile(id) { + return request({ + url: `/backend/deletefile/${id}`, + method: "delete", + }); +} + +/** + * 删除文件 + * @param {number|string} id 文件ID + * @returns {Promise} + */ +export function deleteFilePermanently(id) { + return request({ + url: `/backend/deleteFilePermanently/${id}`, + method: "delete", + }); +} + +/** + * 移动文件 + * @param {number|string} id 文件ID + * @param {Object} fileData 更新的数据 + * @returns {Promise} + */ +export function moveFile(id, cate) { + return request({ + url: `/backend/movefile/${id}`, + method: "get", + params: { cate }, + }); +} + +/** + * 批量删除文件 + * @param {Array} ids 文件ID数组 + * @returns {Promise} + */ +export function batchDeleteFiles(ids) { + return request({ + url: "/backend/batchDeleteFiles", + method: "post", + data: { ids }, + }); +} + +/** + * 批量彻底删除文件 + * @param {Array} ids 文件ID数组 + * @returns {Promise} + */ +export function batchDeleteFilesPermanently(ids) { + return request({ + url: "/backend/batchDeleteFilesPermanently", + method: "post", + data: { ids }, + }); +} + +/** + * 批量移动文件 + * @param {Array} ids 文件ID数组 + * @param {number} cate 目标分类ID + * @returns {Promise} + */ +export function batchMoveFiles(ids, cate) { + return request({ + url: "/backend/batchMoveFiles", + method: "post", + data: { ids, cate }, + }); } \ No newline at end of file diff --git a/backend/src/api/friendlink.js b/backend/src/api/friendlink.js index 0391654..984841f 100644 --- a/backend/src/api/friendlink.js +++ b/backend/src/api/friendlink.js @@ -1,77 +1,77 @@ -import request from '@/utils/request' - -/** - * 获取友情链接列表 - * @param {Object} params - 查询参数 - * @returns {Promise} - */ -export function getFriendlinkList(params) { - return request({ - url: '/backend/friendlinks', - method: 'get', - params - }) -} - -/** - * 获取所有友情链接(下拉选择用) - * @returns {Promise} - */ -export function getAllFriendlinks() { - return request({ - url: '/backend/friendlinks/all', - method: 'get' - }) -} - -/** - * 添加友情链接 - * @param {Object} data - 链接数据 - * @returns {Promise} - */ -export function addFriendlink(data) { - return request({ - url: '/backend/friendlinks', - method: 'post', - data - }) -} - -/** - * 更新友情链接 - * @param {number} id - 链接ID - * @param {Object} data - 链接数据 - * @returns {Promise} - */ -export function updateFriendlink(id, data) { - return request({ - url: `/backend/friendlinks/${id}`, - method: 'put', - data - }) -} - -/** - * 删除友情链接 - * @param {number} id - 链接ID - * @returns {Promise} - */ -export function deleteFriendlink(id) { - return request({ - url: `/backend/friendlinks/${id}`, - method: 'delete' - }) -} - -/** - * 批量删除友情链接 - * @param {Array} ids - 链接ID数组 - * @returns {Promise} - */ -export function batchDeleteFriendlinks(ids) { - return request({ - url: '/backend/friendlinks/batchdelete', - method: 'post', - data: { ids } - }) -} +import request from '@/utils/request' + +/** + * 获取友情链接列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getFriendlinkList(params) { + return request({ + url: '/backend/friendlinks', + method: 'get', + params + }) +} + +/** + * 获取所有友情链接(下拉选择用) + * @returns {Promise} + */ +export function getAllFriendlinks() { + return request({ + url: '/backend/friendlinks/all', + method: 'get' + }) +} + +/** + * 添加友情链接 + * @param {Object} data - 链接数据 + * @returns {Promise} + */ +export function addFriendlink(data) { + return request({ + url: '/backend/friendlinks', + method: 'post', + data + }) +} + +/** + * 更新友情链接 + * @param {number} id - 链接ID + * @param {Object} data - 链接数据 + * @returns {Promise} + */ +export function updateFriendlink(id, data) { + return request({ + url: `/backend/friendlinks/${id}`, + method: 'put', + data + }) +} + +/** + * 删除友情链接 + * @param {number} id - 链接ID + * @returns {Promise} + */ +export function deleteFriendlink(id) { + return request({ + url: `/backend/friendlinks/${id}`, + method: 'delete' + }) +} + +/** + * 批量删除友情链接 + * @param {Array} ids - 链接ID数组 + * @returns {Promise} + */ +export function batchDeleteFriendlinks(ids) { + return request({ + url: '/backend/friendlinks/batchdelete', + method: 'post', + data: { ids } + }) +} diff --git a/backend/src/api/frontMenu.js b/backend/src/api/frontMenu.js index 869f7de..f3f4dac 100644 --- a/backend/src/api/frontMenu.js +++ b/backend/src/api/frontMenu.js @@ -1,55 +1,55 @@ -import request from "@/utils/request"; - -/** - * 获取所有前端导航 - * @returns {Promise} - */ -export function getFrontMenus() { - return request({ - url: "/backend/frontmenus", - method: "get", - }); -} - -/** - * 创建前端导航 - * @param {Object} frontMenuData 前端导航数据 - * @returns {Promise} - */ -export function createFrontMenu(formData, options = {}) { - return request({ - url: "/backend/createfrontmenu", - method: "post", - data: formData, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 编辑前端导航 - * @param {number|string} id 前端导航ID - * @param {Object} frontMenuData 更新的数据 - * @returns {Promise} - */ -export function editFrontMenu(id, frontMenuData) { - return request({ - url: `/backend/editfrontmenu/${id}`, - method: "post", - data: frontMenuData, - }); -} - -/** - * 删除前端导航 - * @param {number|string} id 前端导航ID - * @returns {Promise} - */ -export function deleteFrontMenu(id) { - return request({ - url: `/backend/deletefrontmenu/${id}`, - method: "delete", - }); -} - +import request from "@/utils/request"; + +/** + * 获取所有前端导航 + * @returns {Promise} + */ +export function getFrontMenus() { + return request({ + url: "/backend/frontmenus", + method: "get", + }); +} + +/** + * 创建前端导航 + * @param {Object} frontMenuData 前端导航数据 + * @returns {Promise} + */ +export function createFrontMenu(formData, options = {}) { + return request({ + url: "/backend/createfrontmenu", + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 编辑前端导航 + * @param {number|string} id 前端导航ID + * @param {Object} frontMenuData 更新的数据 + * @returns {Promise} + */ +export function editFrontMenu(id, frontMenuData) { + return request({ + url: `/backend/editfrontmenu/${id}`, + method: "post", + data: frontMenuData, + }); +} + +/** + * 删除前端导航 + * @param {number|string} id 前端导航ID + * @returns {Promise} + */ +export function deleteFrontMenu(id) { + return request({ + url: `/backend/deletefrontmenu/${id}`, + method: "delete", + }); +} + diff --git a/backend/src/api/login.js b/backend/src/api/login.js index 7f09bc5..561b4f4 100644 --- a/backend/src/api/login.js +++ b/backend/src/api/login.js @@ -1,117 +1,117 @@ -import request from "@/utils/request"; - -// 登录(使用租户名称) -export function login(data) { - return request({ - url: `/backend/login`, - method: "post", - data, - }); -} - -// 发送登录验证码(手机号) -export function sendLoginCode(data) { - return request({ - url: "/backend/sendLoginCode", - method: "post", - data, - }); -} - -// 手机号验证码登录 -export function loginBySms(data) { - return request({ - url: "/backend/loginBySms", - method: "post", - data, - }); -} -// 登出 -export function logout(userInfo = null) { - // 如果没有传入 userInfo,尝试从 localStorage 获取 - if (!userInfo) { - const cachedUserInfo = localStorage.getItem('userInfo'); - if (cachedUserInfo) { - try { - userInfo = JSON.parse(cachedUserInfo); - } catch (e) { - console.error('Failed to parse userInfo from localStorage:', e); - } - } - } - - return request({ - url: `/backend/logout`, - method: "post", - data: userInfo ? { userInfo: userInfo } : {}, - }); -} - -/** - * 获取极验3.0数据 - * @returns {Promise} - */ -export function getGeetest3Infos() { - return request({ - url: '/backend/login/getGeetest3Infos', - method: 'get' - }); -} - -/** - * 获取极验4.0数据 - * @returns {Promise} - */ -export function getGeetest4Infos() { - return request({ - url: '/backend/login/getGeetest4Infos', - method: 'get' - }); -} - -/** - * 判断是否开启验证 - * @returns {Promise} - */ -export function getOpenVerify() { - return request({ - url: '/backend/login/getOpenVerify', - method: 'get' - }); -} - -// 注册 -export function register(data) { - return request({ - url: "/backend/register", - method: "post", - data, - }); -} - -// 发送注册验证码 -export function sendRegisterCode(data) { - return request({ - url: "/backend/sendRegisterCode", - method: "post", - data, - }); -} - -// 忘记密码重置 -export function resetPassword(data) { - return request({ - url: "/backend/resetPassword", - method: "post", - data, - }); -} - -// 发送找回密码验证码 -export function sendResetCode(data) { - return request({ - url: "/backend/sendResetCode", - method: "post", - data, - }); +import request from "@/utils/request"; + +// 登录(使用租户名称) +export function login(data) { + return request({ + url: `/backend/login`, + method: "post", + data, + }); +} + +// 发送登录验证码(手机号) +export function sendLoginCode(data) { + return request({ + url: "/backend/sendLoginCode", + method: "post", + data, + }); +} + +// 手机号验证码登录 +export function loginBySms(data) { + return request({ + url: "/backend/loginBySms", + method: "post", + data, + }); +} +// 登出 +export function logout(userInfo = null) { + // 如果没有传入 userInfo,尝试从 localStorage 获取 + if (!userInfo) { + const cachedUserInfo = localStorage.getItem('userInfo'); + if (cachedUserInfo) { + try { + userInfo = JSON.parse(cachedUserInfo); + } catch (e) { + console.error('Failed to parse userInfo from localStorage:', e); + } + } + } + + return request({ + url: `/backend/logout`, + method: "post", + data: userInfo ? { userInfo: userInfo } : {}, + }); +} + +/** + * 获取极验3.0数据 + * @returns {Promise} + */ +export function getGeetest3Infos() { + return request({ + url: '/backend/login/getGeetest3Infos', + method: 'get' + }); +} + +/** + * 获取极验4.0数据 + * @returns {Promise} + */ +export function getGeetest4Infos() { + return request({ + url: '/backend/login/getGeetest4Infos', + method: 'get' + }); +} + +/** + * 判断是否开启验证 + * @returns {Promise} + */ +export function getOpenVerify() { + return request({ + url: '/backend/login/getOpenVerify', + method: 'get' + }); +} + +// 注册 +export function register(data) { + return request({ + url: "/backend/register", + method: "post", + data, + }); +} + +// 发送注册验证码 +export function sendRegisterCode(data) { + return request({ + url: "/backend/sendRegisterCode", + method: "post", + data, + }); +} + +// 忘记密码重置 +export function resetPassword(data) { + return request({ + url: "/backend/resetPassword", + method: "post", + data, + }); +} + +// 发送找回密码验证码 +export function sendResetCode(data) { + return request({ + url: "/backend/sendResetCode", + method: "post", + data, + }); } \ No newline at end of file diff --git a/backend/src/api/menu.js b/backend/src/api/menu.js index d3d5c4b..00cd43c 100644 --- a/backend/src/api/menu.js +++ b/backend/src/api/menu.js @@ -1,52 +1,52 @@ -import request from "@/utils/request"; - -// 获取所有菜单 -export function getAllMenus() { - return request({ - url: `/backend/allmenu`, - method: "get", - }); -} - -//获取用户菜单 -export function getMenus(id){ - return request({ - url: `/backend/menu/${parseInt(id)}`, - method: "get", - }); -} - -// 更新菜单状态 -export function updateMenuStatus(menuId, status) { - return request({ - url: `/backend/menu/status/${menuId}`, - method: "patch", - data: { status }, - }); -} - -// 创建菜单 -export function createMenu(menuData) { - return request({ - url: `/backend/createmenu`, - method: "post", - data: menuData, - }); -} - -// 更新菜单 -export function updateMenu(menuId, menuData) { - return request({ - url: `/backend/updatemenu/${menuId}`, - method: "put", - data: menuData, - }); -} - -// 删除菜单 -export function deleteMenu(menuId) { - return request({ - url: `/backend/deletemenu/${menuId}`, - method: "delete", - }); -} +import request from "@/utils/request"; + +// 获取所有菜单 +export function getAllMenus() { + return request({ + url: `/backend/allmenu`, + method: "get", + }); +} + +//获取用户菜单 +export function getMenus(id){ + return request({ + url: `/backend/menu/${parseInt(id)}`, + method: "get", + }); +} + +// 更新菜单状态 +export function updateMenuStatus(menuId, status) { + return request({ + url: `/backend/menu/status/${menuId}`, + method: "patch", + data: { status }, + }); +} + +// 创建菜单 +export function createMenu(menuData) { + return request({ + url: `/backend/createmenu`, + method: "post", + data: menuData, + }); +} + +// 更新菜单 +export function updateMenu(menuId, menuData) { + return request({ + url: `/backend/updatemenu/${menuId}`, + method: "put", + data: menuData, + }); +} + +// 删除菜单 +export function deleteMenu(menuId) { + return request({ + url: `/backend/deletemenu/${menuId}`, + method: "delete", + }); +} diff --git a/backend/src/api/moduleCenter.js b/backend/src/api/moduleCenter.js index f9613b9..3819863 100644 --- a/backend/src/api/moduleCenter.js +++ b/backend/src/api/moduleCenter.js @@ -1,57 +1,57 @@ -import request from "@/utils/request"; - -/** - * 获取模块中心分类 - * @returns {Promise} - */ -export function getModuleCategory() { - return request({ - url: "/backend/moduleCategory", - method: "get", - }); -} - -/** - * 获取模块中心列表 - * @param {number} cid 分类id - * @returns {Promise} - */ -export function getModules(cid) { - return request({ - url: "/backend/moduleCenter/modules", - method: "get", - params: { cid } - }); -} - -/** - * 编辑模块分类 - * @param {Object} data 分类数据 - * @param {number} data.id 分类id(编辑时必填,新增时不填) - * @param {string} data.title 分类名称 - * @param {number} data.status 分类状态 - * @returns {Promise} - */ -export function editModuleCategory(data) { - return request({ - url: "/backend/moduleCenter/editCategory", - method: "post", - data - }); -} - -/** - * 编辑模块 - * @param {Object} data 模块数据 - * @param {number} data.id 模块id(编辑时必填,新增时不填) - * @param {string} data.title 模块名称 - * @param {number} data.status 模块状态 - * @returns {Promise} - */ -export function editModules(data) { - return request({ - url: "/backend/moduleCenter/editModules", - method: "post", - data - }); -} +import request from "@/utils/request"; + +/** + * 获取模块中心分类 + * @returns {Promise} + */ +export function getModuleCategory() { + return request({ + url: "/backend/moduleCategory", + method: "get", + }); +} + +/** + * 获取模块中心列表 + * @param {number} cid 分类id + * @returns {Promise} + */ +export function getModules(cid) { + return request({ + url: "/backend/moduleCenter/modules", + method: "get", + params: { cid } + }); +} + +/** + * 编辑模块分类 + * @param {Object} data 分类数据 + * @param {number} data.id 分类id(编辑时必填,新增时不填) + * @param {string} data.title 分类名称 + * @param {number} data.status 分类状态 + * @returns {Promise} + */ +export function editModuleCategory(data) { + return request({ + url: "/backend/moduleCenter/editCategory", + method: "post", + data + }); +} + +/** + * 编辑模块 + * @param {Object} data 模块数据 + * @param {number} data.id 模块id(编辑时必填,新增时不填) + * @param {string} data.title 模块名称 + * @param {number} data.status 模块状态 + * @returns {Promise} + */ +export function editModules(data) { + return request({ + url: "/backend/moduleCenter/editModules", + method: "post", + data + }); +} diff --git a/backend/src/api/modules.js b/backend/src/api/modules.js index 696dae1..d62d209 100644 --- a/backend/src/api/modules.js +++ b/backend/src/api/modules.js @@ -1,68 +1,68 @@ -import request from '@/utils/request'; - -export function getModulesList() { - return request({ - url: '/backend/modules/list', - method: 'get', - }); -} - -export function getTenantList() { - return request({ - url: '/backend/modules/getTenantList', - method: 'get', - }); -} - -export function getModuleDetail(id) { - return request({ - url: `/backend/modules/${id}`, - method: 'get', - }); -} - -export function addModule(data) { - return request({ - url: '/backend/modules', - method: 'post', - data, - }); -} - -export function editModule(id, data) { - return request({ - url: `/backend/modules/${id}`, - method: 'put', - data, - }); -} - -export function deleteModule(id) { - return request({ - url: `/backend/modules/${id}`, - method: 'delete', - }); -} - -export function batchDeleteModules(ids) { - return request({ - url: '/backend/modules/batchDelete', - method: 'post', - data: { ids }, - }); -} - -export function changeModuleStatus(id, status) { - return request({ - url: '/backend/modules/status', - method: 'post', - data: { id, status }, - }); -} - -export function getModulesSelectList() { - return request({ - url: '/backend/modules/select/list', - method: 'get', - }); -} +import request from '@/utils/request'; + +export function getModulesList() { + return request({ + url: '/backend/modules/list', + method: 'get', + }); +} + +export function getTenantList() { + return request({ + url: '/backend/modules/getTenantList', + method: 'get', + }); +} + +export function getModuleDetail(id) { + return request({ + url: `/backend/modules/${id}`, + method: 'get', + }); +} + +export function addModule(data) { + return request({ + url: '/backend/modules', + method: 'post', + data, + }); +} + +export function editModule(id, data) { + return request({ + url: `/backend/modules/${id}`, + method: 'put', + data, + }); +} + +export function deleteModule(id) { + return request({ + url: `/backend/modules/${id}`, + method: 'delete', + }); +} + +export function batchDeleteModules(ids) { + return request({ + url: '/backend/modules/batchDelete', + method: 'post', + data: { ids }, + }); +} + +export function changeModuleStatus(id, status) { + return request({ + url: '/backend/modules/status', + method: 'post', + data: { id, status }, + }); +} + +export function getModulesSelectList() { + return request({ + url: '/backend/modules/select/list', + method: 'get', + }); +} diff --git a/backend/src/api/onepage.js b/backend/src/api/onepage.js index 2ce2c7f..0ac6826 100644 --- a/backend/src/api/onepage.js +++ b/backend/src/api/onepage.js @@ -1,64 +1,64 @@ -import request from "@/utils/request"; - -/** - * 获取所有单页 - * @returns {Promise} - */ -export function getOnePages() { - return request({ - url: "/backend/allonepages", - method: "get", - }); -} - -/** - * 创建单页 - * @param {Object} onePageData 单页数据 - * @returns {Promise} - */ -export function createOnePage(formData) { - return request({ - url: "/backend/createonepage", - method: "post", - data: formData, - }); -} - -/** - * 编辑单页 - * @param {number|string} id 单页ID - * @param {Object} onePageData 更新的数据 - * @returns {Promise} - */ -export function editOnePage(id, onePageData) { - return request({ - url: `/backend/editonepage/${id}`, - method: "post", - data: onePageData, - }); -} - -/** - * 删除单页 - * @param {number|string} id 单页ID - * @returns {Promise} - */ -export function deleteOnePage(id) { - return request({ - url: `/backend/deleteonepage/${id}`, - method: "delete", - }); -} - -/** - * 根据路径获取单页(前端使用) - * @param {string} path 路由路径 - * @returns {Promise} - */ -export function getOnePageByPath(path) { - return request({ - url: `/index/onepage/${encodeURIComponent(path)}`, - method: "get", - }); -} - +import request from "@/utils/request"; + +/** + * 获取所有单页 + * @returns {Promise} + */ +export function getOnePages() { + return request({ + url: "/backend/allonepages", + method: "get", + }); +} + +/** + * 创建单页 + * @param {Object} onePageData 单页数据 + * @returns {Promise} + */ +export function createOnePage(formData) { + return request({ + url: "/backend/createonepage", + method: "post", + data: formData, + }); +} + +/** + * 编辑单页 + * @param {number|string} id 单页ID + * @param {Object} onePageData 更新的数据 + * @returns {Promise} + */ +export function editOnePage(id, onePageData) { + return request({ + url: `/backend/editonepage/${id}`, + method: "post", + data: onePageData, + }); +} + +/** + * 删除单页 + * @param {number|string} id 单页ID + * @returns {Promise} + */ +export function deleteOnePage(id) { + return request({ + url: `/backend/deleteonepage/${id}`, + method: "delete", + }); +} + +/** + * 根据路径获取单页(前端使用) + * @param {string} path 路由路径 + * @returns {Promise} + */ +export function getOnePageByPath(path) { + return request({ + url: `/index/onepage/${encodeURIComponent(path)}`, + method: "get", + }); +} + diff --git a/backend/src/api/operationLog.js b/backend/src/api/operationLog.js index f3473e0..fe27e50 100644 --- a/backend/src/api/operationLog.js +++ b/backend/src/api/operationLog.js @@ -1,70 +1,70 @@ -import request from "@/utils/request"; - -/** - * 获取操作日志列表 - * @param {Object} params 查询参数 - * @param {number} params.page 页码 - * @param {number} params.pageSize 每页数量 - * @param {string} params.keyword 关键词搜索 - * @param {string} params.module 模块筛选 - * @param {string} params.action 操作动作筛选 - * @param {string} params.status 状态筛选 - * @param {string} params.startTime 开始时间 - * @param {string} params.endTime 结束时间 - * @returns {Promise} - */ -export function getOperationLogs(params) { - return request({ - url: "/backend/operationLogs", - method: "get", - params, - }); -} - -/** - * 获取操作日志详情 - * @param {number|string} id 日志ID - * @returns {Promise} - */ -export function getOperationLogDetail(id) { - return request({ - url: `/backend/operationLogs/${id}`, - method: "get", - }); -} - -/** - * 删除操作日志 - * @param {number|string} id 日志ID - * @returns {Promise} - */ -export function deleteOperationLog(id) { - return request({ - url: `/backend/operationLogs/${id}`, - method: "delete", - }); -} - -/** - * 批量删除操作日志 - * @param {Array} ids 日志ID数组 - * @returns {Promise} - */ -export function batchDeleteOperationLogs(ids) { - return request({ - url: "/backend/operationLogs/batchDelete", - method: "post", - data: { ids }, - }); -} - -/** - * 获取操作统计信息 - * @returns {Promise} - */ -export function getOperationStatistics() { - return request({ - url: "/backend/operationLogs/statistics", - method: "get", - }); +import request from "@/utils/request"; + +/** + * 获取操作日志列表 + * @param {Object} params 查询参数 + * @param {number} params.page 页码 + * @param {number} params.pageSize 每页数量 + * @param {string} params.keyword 关键词搜索 + * @param {string} params.module 模块筛选 + * @param {string} params.action 操作动作筛选 + * @param {string} params.status 状态筛选 + * @param {string} params.startTime 开始时间 + * @param {string} params.endTime 结束时间 + * @returns {Promise} + */ +export function getOperationLogs(params) { + return request({ + url: "/backend/operationLogs", + method: "get", + params, + }); +} + +/** + * 获取操作日志详情 + * @param {number|string} id 日志ID + * @returns {Promise} + */ +export function getOperationLogDetail(id) { + return request({ + url: `/backend/operationLogs/${id}`, + method: "get", + }); +} + +/** + * 删除操作日志 + * @param {number|string} id 日志ID + * @returns {Promise} + */ +export function deleteOperationLog(id) { + return request({ + url: `/backend/operationLogs/${id}`, + method: "delete", + }); +} + +/** + * 批量删除操作日志 + * @param {Array} ids 日志ID数组 + * @returns {Promise} + */ +export function batchDeleteOperationLogs(ids) { + return request({ + url: "/backend/operationLogs/batchDelete", + method: "post", + data: { ids }, + }); +} + +/** + * 获取操作统计信息 + * @returns {Promise} + */ +export function getOperationStatistics() { + return request({ + url: "/backend/operationLogs/statistics", + method: "get", + }); } \ No newline at end of file diff --git a/backend/src/api/permission.js b/backend/src/api/permission.js index 43d6215..6a8f36f 100644 --- a/backend/src/api/permission.js +++ b/backend/src/api/permission.js @@ -1,24 +1,24 @@ -import request from '@/utils/request'; - -export function getAllMenuPermissions(params = {}) { - return request({ - url: '/backend/allmenupermissions', - method: 'get', - params - }); -} - -export function getRolePermissions(roleId) { - return request({ - url: `/backend/rolepermissions/${roleId}`, - method: 'get' - }); -} - -export function assignRolePermissions(roleId, permissions) { - return request({ - url: `/backend/assignrolepermissions/${roleId}`, - method: 'post', - data: { permissions } - }); -} +import request from '@/utils/request'; + +export function getAllMenuPermissions(params = {}) { + return request({ + url: '/backend/allmenupermissions', + method: 'get', + params + }); +} + +export function getRolePermissions(roleId) { + return request({ + url: `/backend/rolepermissions/${roleId}`, + method: 'get' + }); +} + +export function assignRolePermissions(roleId, permissions) { + return request({ + url: `/backend/assignrolepermissions/${roleId}`, + method: 'post', + data: { permissions } + }); +} diff --git a/backend/src/api/position.js b/backend/src/api/position.js index 283a7b7..560d57d 100644 --- a/backend/src/api/position.js +++ b/backend/src/api/position.js @@ -1,52 +1,52 @@ -import request from '@/utils/request'; - -// 获取租户下的所有职位 -export function getTenantPositions(tenantId) { - return request({ - url: `/backend/positions/tenant/${tenantId}`, - method: 'get', - }); -} - -// 根据部门ID获取职位列表 -export function getPositionsByDepartment(departmentId) { - return request({ - url: `/backend/positions/department/${departmentId}`, - method: 'get', - }); -} - -// 获取职位详情 -export function getPositionInfo(positionId) { - return request({ - url: `/backend/positions/${positionId}`, - method: 'get', - }); -} - -// 添加职位 -export function addPosition(data) { - return request({ - url: '/backend/positions', - method: 'post', - data, - }); -} - -// 更新职位信息 -export function editPosition(positionId, data) { - return request({ - url: `/backend/positions/${positionId}`, - method: 'put', - data, - }); -} - -// 删除职位 -export function deletePosition(positionId) { - return request({ - url: `/backend/positions/${positionId}`, - method: 'delete', - }); -} - +import request from '@/utils/request'; + +// 获取租户下的所有职位 +export function getTenantPositions(tenantId) { + return request({ + url: `/backend/positions/tenant/${tenantId}`, + method: 'get', + }); +} + +// 根据部门ID获取职位列表 +export function getPositionsByDepartment(departmentId) { + return request({ + url: `/backend/positions/department/${departmentId}`, + method: 'get', + }); +} + +// 获取职位详情 +export function getPositionInfo(positionId) { + return request({ + url: `/backend/positions/${positionId}`, + method: 'get', + }); +} + +// 添加职位 +export function addPosition(data) { + return request({ + url: '/backend/positions', + method: 'post', + data, + }); +} + +// 更新职位信息 +export function editPosition(positionId, data) { + return request({ + url: `/backend/positions/${positionId}`, + method: 'put', + data, + }); +} + +// 删除职位 +export function deletePosition(positionId) { + return request({ + url: `/backend/positions/${positionId}`, + method: 'delete', + }); +} + diff --git a/backend/src/api/products.js b/backend/src/api/products.js index d9314ea..cb5533a 100644 --- a/backend/src/api/products.js +++ b/backend/src/api/products.js @@ -1,105 +1,105 @@ -import request from '@/utils/request' - -/** - * 获取特色产品列表 - * @param {Object} params - 查询参数 - * @returns {Promise} - */ -export function getProductsList(params) { - return request({ - url: '/backend/productsList', - method: 'get', - params - }) -} - -/** - * 添加特色产品 - * @param {Object} data - 产品数据 - * @returns {Promise} - */ -export function addProducts(data) { - return request({ - url: '/backend/addProducts', - method: 'post', - data - }) -} - -/** - * 更新特色产品 - * @param {number} id - 产品ID - * @param {Object} data - 产品数据 - * @returns {Promise} - */ -export function updateProducts(id, data) { - return request({ - url: `/backend/editProducts/${id}`, - method: 'put', - data - }) -} - -/** - * 删除特色产品 - * @param {number} id - 产品ID - * @returns {Promise} - */ -export function deleteProducts(id) { - return request({ - url: `/backend/deleteProducts/${id}`, - method: 'delete' - }) -} - -/** - * 获取产品分类列表 - * @param {Object} params - 查询参数 - * @returns {Promise} - */ -export function getProductsTypesList(params) { - return request({ - url: '/backend/productsTypesList', - method: 'get', - params - }) -} - -/** - * 添加产品分类 - * @param {Object} data - 分类数据 - * @returns {Promise} - */ -export function addProductsTypes(data) { - return request({ - url: '/backend/addProductsTypes', - method: 'post', - data - }) -} - -/** - * 更新产品分类 - * @param {number} id - 分类ID - * @param {Object} data - 分类数据 - * @returns {Promise} - */ -export function updateProductsTypes(id, data) { - return request({ - url: `/backend/editProductsTypes/${id}`, - method: 'put', - data - }) -} - -/** - * 删除产品分类 - * @param {number} id - 分类ID - * @returns {Promise} - */ -export function deleteProductsTypes(id) { - return request({ - url: `/backend/deleteProductsTypes/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +/** + * 获取特色产品列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getProductsList(params) { + return request({ + url: '/backend/productsList', + method: 'get', + params + }) +} + +/** + * 添加特色产品 + * @param {Object} data - 产品数据 + * @returns {Promise} + */ +export function addProducts(data) { + return request({ + url: '/backend/addProducts', + method: 'post', + data + }) +} + +/** + * 更新特色产品 + * @param {number} id - 产品ID + * @param {Object} data - 产品数据 + * @returns {Promise} + */ +export function updateProducts(id, data) { + return request({ + url: `/backend/editProducts/${id}`, + method: 'put', + data + }) +} + +/** + * 删除特色产品 + * @param {number} id - 产品ID + * @returns {Promise} + */ +export function deleteProducts(id) { + return request({ + url: `/backend/deleteProducts/${id}`, + method: 'delete' + }) +} + +/** + * 获取产品分类列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getProductsTypesList(params) { + return request({ + url: '/backend/productsTypesList', + method: 'get', + params + }) +} + +/** + * 添加产品分类 + * @param {Object} data - 分类数据 + * @returns {Promise} + */ +export function addProductsTypes(data) { + return request({ + url: '/backend/addProductsTypes', + method: 'post', + data + }) +} + +/** + * 更新产品分类 + * @param {number} id - 分类ID + * @param {Object} data - 分类数据 + * @returns {Promise} + */ +export function updateProductsTypes(id, data) { + return request({ + url: `/backend/editProductsTypes/${id}`, + method: 'put', + data + }) +} + +/** + * 删除产品分类 + * @param {number} id - 分类ID + * @returns {Promise} + */ +export function deleteProductsTypes(id) { + return request({ + url: `/backend/deleteProductsTypes/${id}`, + method: 'delete' + }) +} diff --git a/backend/src/api/role.js b/backend/src/api/role.js index 6b33318..e5cc285 100644 --- a/backend/src/api/role.js +++ b/backend/src/api/role.js @@ -1,38 +1,38 @@ -import request from '@/utils/request' - -export function getAllRoles() { - return request({ - url: '/backend/allRoles', - method: 'get' - }) -} - -export function getRoleById(id) { - return request({ - url: `/backend/roles/${id}`, - method: 'get' - }) -} - -export function createRole(data) { - return request({ - url: '/backend/roles', - method: 'post', - data - }) -} - -export function updateRole(id, data) { - return request({ - url: `/backend/roles/${id}`, - method: 'put', - data - }) -} - -export function deleteRole(id) { - return request({ - url: `/backend/roles/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +export function getAllRoles() { + return request({ + url: '/backend/allRoles', + method: 'get' + }) +} + +export function getRoleById(id) { + return request({ + url: `/backend/roles/${id}`, + method: 'get' + }) +} + +export function createRole(data) { + return request({ + url: '/backend/roles', + method: 'post', + data + }) +} + +export function updateRole(id, data) { + return request({ + url: `/backend/roles/${id}`, + method: 'put', + data + }) +} + +export function deleteRole(id) { + return request({ + url: `/backend/roles/${id}`, + method: 'delete' + }) +} diff --git a/backend/src/api/services.js b/backend/src/api/services.js index c962b44..80aeee8 100644 --- a/backend/src/api/services.js +++ b/backend/src/api/services.js @@ -1,53 +1,53 @@ -import request from '@/utils/request' - -/** - * 获取特色服务列表 - * @param {Object} params - 查询参数 - * @returns {Promise} - */ -export function getServiceList(params) { - return request({ - url: '/backend/servicesList', - method: 'get', - params - }) -} - -/** - * 添加特色服务 - * @param {Object} data - 服务数据 - * @returns {Promise} - */ -export function addService(data) { - return request({ - url: '/backend/addServices', - method: 'post', - data - }) -} - -/** - * 更新特色服务 - * @param {number} id - 服务ID - * @param {Object} data - 服务数据 - * @returns {Promise} - */ -export function updateService(id, data) { - return request({ - url: `/backend/editServices/${id}`, - method: 'put', - data - }) -} - -/** - * 删除特色服务 - * @param {number} id - 服务ID - * @returns {Promise} - */ -export function deleteService(id) { - return request({ - url: `/backend/deleteServices/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +/** + * 获取特色服务列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getServiceList(params) { + return request({ + url: '/backend/servicesList', + method: 'get', + params + }) +} + +/** + * 添加特色服务 + * @param {Object} data - 服务数据 + * @returns {Promise} + */ +export function addService(data) { + return request({ + url: '/backend/addServices', + method: 'post', + data + }) +} + +/** + * 更新特色服务 + * @param {number} id - 服务ID + * @param {Object} data - 服务数据 + * @returns {Promise} + */ +export function updateService(id, data) { + return request({ + url: `/backend/editServices/${id}`, + method: 'put', + data + }) +} + +/** + * 删除特色服务 + * @param {number} id - 服务ID + * @returns {Promise} + */ +export function deleteService(id) { + return request({ + url: `/backend/deleteServices/${id}`, + method: 'delete' + }) +} diff --git a/backend/src/api/sitereminder.js b/backend/src/api/sitereminder.js index 76029e2..44c8de0 100644 --- a/backend/src/api/sitereminder.js +++ b/backend/src/api/sitereminder.js @@ -1,27 +1,27 @@ -import request from "@/utils/request"; - -/** 获取我的消息列表 */ -export function getMySiteReminders(params) { - return request({ - url: "/backend/sitereminder/myList", - method: "get", - params, - }); -} - -/** 标记消息为已读 */ -export function readSiteReminder(id) { - return request({ - url: "/backend/sitereminder/read", - method: "post", - data: { id }, - }); -} - -/** 一键全部已读 */ -export function readAllSiteReminders() { - return request({ - url: "/backend/sitereminder/readall", - method: "post", - }); -} +import request from "@/utils/request"; + +/** 获取我的消息列表 */ +export function getMySiteReminders(params) { + return request({ + url: "/backend/sitereminder/myList", + method: "get", + params, + }); +} + +/** 标记消息为已读 */ +export function readSiteReminder(id) { + return request({ + url: "/backend/sitereminder/read", + method: "post", + data: { id }, + }); +} + +/** 一键全部已读 */ +export function readAllSiteReminders() { + return request({ + url: "/backend/sitereminder/readall", + method: "post", + }); +} diff --git a/backend/src/api/sitesettings.js b/backend/src/api/sitesettings.js index 792c372..43946ae 100644 --- a/backend/src/api/sitesettings.js +++ b/backend/src/api/sitesettings.js @@ -1,129 +1,129 @@ -import request from "@/utils/request"; - -/** - * 获取基本信息 - * @param {number} tid 租户ID - * @returns {Promise} - */ -export function getNormalInfos(tid) { - return request({ - url: "/backend/normalInfos", - method: "get", - params: { tid } - }); -} - -/** - * 保存基本信息 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveNormalInfos(data) { - return request({ - url: "/backend/saveNormalInfos", - method: "post", - data: data, - }); -} - -/** - * 获取登录验证数据 - * @returns {Promise} - */ -export function getVerifyInfos() { - return request({ - url: "/backend/loginVerifyInfos", - method: "get", - }); -} - -/** - * 保存登录验证数据 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveVerifyInfos(data) { - return request({ - url: "/backend/saveloginVerifyInfos", - method: "post", - data: data, - }); -} - -/** - * 获取法律声明和隐私条款 - * @param {number} tid 租户ID - * @returns {Promise} - */ -export function getLegalInfos(tid) { - return request({ - url: "/backend/legalInfos", - method: "get", - params: { tid } - }); -} - -/** - * 保存法律声明和隐私条款 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveLegalInfos(data) { - return request({ - url: "/backend/saveLegalInfos", - method: "post", - data: data, - }); -} - -/** - * 获取企业信息 - * @param {number} tid 租户ID - * @returns {Promise} - */ -export function getCompanyInfos(tid) { - return request({ - url: "/backend/companyInfos", - method: "get", - params: { tid } - }); -} - -/** - * 保存企业信息 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveCompanyInfos(data) { - return request({ - url: "/backend/saveCompanyInfos", - method: "post", - data: data, - }); -} - -/** - * 获取企业SEO - * @param {number} tid 租户ID - * @returns {Promise} - */ -export function getCompanySeo(tid) { - return request({ - url: "/backend/companySeo", - method: "get", - params: { tid } - }); -} - -/** - * 保存企业SEO - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveCompanySeo(data) { - return request({ - url: "/backend/saveCompanySeo", - method: "post", - data: data, - }); +import request from "@/utils/request"; + +/** + * 获取基本信息 + * @param {number} tid 租户ID + * @returns {Promise} + */ +export function getNormalInfos(tid) { + return request({ + url: "/backend/normalInfos", + method: "get", + params: { tid } + }); +} + +/** + * 保存基本信息 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveNormalInfos(data) { + return request({ + url: "/backend/saveNormalInfos", + method: "post", + data: data, + }); +} + +/** + * 获取登录验证数据 + * @returns {Promise} + */ +export function getVerifyInfos() { + return request({ + url: "/backend/loginVerifyInfos", + method: "get", + }); +} + +/** + * 保存登录验证数据 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveVerifyInfos(data) { + return request({ + url: "/backend/saveloginVerifyInfos", + method: "post", + data: data, + }); +} + +/** + * 获取法律声明和隐私条款 + * @param {number} tid 租户ID + * @returns {Promise} + */ +export function getLegalInfos(tid) { + return request({ + url: "/backend/legalInfos", + method: "get", + params: { tid } + }); +} + +/** + * 保存法律声明和隐私条款 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveLegalInfos(data) { + return request({ + url: "/backend/saveLegalInfos", + method: "post", + data: data, + }); +} + +/** + * 获取企业信息 + * @param {number} tid 租户ID + * @returns {Promise} + */ +export function getCompanyInfos(tid) { + return request({ + url: "/backend/companyInfos", + method: "get", + params: { tid } + }); +} + +/** + * 保存企业信息 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveCompanyInfos(data) { + return request({ + url: "/backend/saveCompanyInfos", + method: "post", + data: data, + }); +} + +/** + * 获取企业SEO + * @param {number} tid 租户ID + * @returns {Promise} + */ +export function getCompanySeo(tid) { + return request({ + url: "/backend/companySeo", + method: "get", + params: { tid } + }); +} + +/** + * 保存企业SEO + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveCompanySeo(data) { + return request({ + url: "/backend/saveCompanySeo", + method: "post", + data: data, + }); } \ No newline at end of file diff --git a/backend/src/api/sms.ts b/backend/src/api/sms.ts index 6147dbb..0762ba4 100644 --- a/backend/src/api/sms.ts +++ b/backend/src/api/sms.ts @@ -1,56 +1,56 @@ -import request from "@/utils/request"; - -/** - * 获取短信网关配置 - */ -export function getSmsInfo() { - return request({ - url: "/backend/sms/info", - method: "get", - }); -} - -/** - * 编辑短信网关配置 - */ -export function editSmsInfo(data: any) { - return request({ - url: "/backend/sms/editinfo", - method: "post", - data, - }); -} - -/** - * 发送测试短信(入队任务,等待网关发送) - */ -export function sendTestSms(data: any) { - return request({ - url: "/backend/sms/sendtest", - method: "post", - data, - }); -} - -/** - * 获取短信任务列表(租户隔离) - */ -export function getSmsTaskList(params: { status?: string | number; phone?: string } = {}) { - return request({ - url: "/backend/sms/taskList", - method: "get", - params, - }); -} - -/** - * 编辑短信任务 - */ -export function editSmsTask(id: number | string, data: any) { - return request({ - url: `/backend/sms/taskEdit/${id}`, - method: "post", - data, - }); -} - +import request from "@/utils/request"; + +/** + * 获取短信网关配置 + */ +export function getSmsInfo() { + return request({ + url: "/backend/sms/info", + method: "get", + }); +} + +/** + * 编辑短信网关配置 + */ +export function editSmsInfo(data: any) { + return request({ + url: "/backend/sms/editinfo", + method: "post", + data, + }); +} + +/** + * 发送测试短信(入队任务,等待网关发送) + */ +export function sendTestSms(data: any) { + return request({ + url: "/backend/sms/sendtest", + method: "post", + data, + }); +} + +/** + * 获取短信任务列表(租户隔离) + */ +export function getSmsTaskList(params: { status?: string | number; phone?: string } = {}) { + return request({ + url: "/backend/sms/taskList", + method: "get", + params, + }); +} + +/** + * 编辑短信任务 + */ +export function editSmsTask(id: number | string, data: any) { + return request({ + url: `/backend/sms/taskEdit/${id}`, + method: "post", + data, + }); +} + diff --git a/backend/src/api/tenant.js b/backend/src/api/tenant.js index cf4af9e..319287c 100644 --- a/backend/src/api/tenant.js +++ b/backend/src/api/tenant.js @@ -1,84 +1,84 @@ -import request from "@/utils/request"; - -/************************************************* - ****************** 租户相关接口 ****************** - *************************************************/ - -/** - * 获取租户列表 - * @param {Object} params 包含 page 和 pageSize - * @returns {Promise} - */ -export function getTenantList(params) { - return request({ - url: "/backend/tenant/getTenant", - method: "get", - params: params, - }); -} - -/** - * 获取租户详情 - * @param {number} id 租户ID - * @returns {Promise} - */ -export function getTenantDetail(id) { - return request({ - url: `/backend/tenant/getTenantDetail/${id}`, - method: "get", - }); -} - -/** - * 创建租户数据 - * @param {Object} data 租户数据 - * @returns {Promise} - */ -export function createTenant(data) { - return request({ - url: "/backend/tenant/createTenant", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data", - }, - }); -} - -/** - * 更新租户数据 - * @param {Object} data 租户数据 - * @returns {Promise} - */ -export function editTenant(id, data) { - return request({ - url: `/backend/tenant/editTenant/${id}`, - method: "post", - data: data, - }); -} - -/** - * 删除租户数据 - * @param {number} id 租户ID - * @returns {Promise} - */ -export function deleteTenant(id) { - return request({ - url: `/backend/tenant/deleteTenant/${id}`, - method: "delete", - }); -} - -/** - * 校验租户编码是否重复 - * @param {string} tenant_code 编码 - * @param {number} id 可选,当前编辑的租户ID - */ -export function checkTenantCode(tenant_code) { - return request({ - url: '/backend/tenant/findTenantCode', - method: 'get', - params: { tenant_code } - }); +import request from "@/utils/request"; + +/************************************************* + ****************** 租户相关接口 ****************** + *************************************************/ + +/** + * 获取租户列表 + * @param {Object} params 包含 page 和 pageSize + * @returns {Promise} + */ +export function getTenantList(params) { + return request({ + url: "/backend/tenant/getTenant", + method: "get", + params: params, + }); +} + +/** + * 获取租户详情 + * @param {number} id 租户ID + * @returns {Promise} + */ +export function getTenantDetail(id) { + return request({ + url: `/backend/tenant/getTenantDetail/${id}`, + method: "get", + }); +} + +/** + * 创建租户数据 + * @param {Object} data 租户数据 + * @returns {Promise} + */ +export function createTenant(data) { + return request({ + url: "/backend/tenant/createTenant", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data", + }, + }); +} + +/** + * 更新租户数据 + * @param {Object} data 租户数据 + * @returns {Promise} + */ +export function editTenant(id, data) { + return request({ + url: `/backend/tenant/editTenant/${id}`, + method: "post", + data: data, + }); +} + +/** + * 删除租户数据 + * @param {number} id 租户ID + * @returns {Promise} + */ +export function deleteTenant(id) { + return request({ + url: `/backend/tenant/deleteTenant/${id}`, + method: "delete", + }); +} + +/** + * 校验租户编码是否重复 + * @param {string} tenant_code 编码 + * @param {number} id 可选,当前编辑的租户ID + */ +export function checkTenantCode(tenant_code) { + return request({ + url: '/backend/tenant/findTenantCode', + method: 'get', + params: { tenant_code } + }); } \ No newline at end of file diff --git a/backend/src/api/theme.js b/backend/src/api/theme.js index dc4452c..9892390 100644 --- a/backend/src/api/theme.js +++ b/backend/src/api/theme.js @@ -1,36 +1,36 @@ -import request from '@/utils/request' - -// 获取模板列表 -export function getThemeList() { - return request({ - url: '/backend/theme', - method: 'get' - }) -} - -// 切换模板 -export function switchTheme(data) { - return request({ - url: '/backend/theme/switch', - method: 'post', - data - }) -} - -// 获取模板数据 -export function getThemeData(params) { - return request({ - url: '/backend/theme/data', - method: 'get', - params - }) -} - -// 保存模板数据 -export function saveThemeData(data) { - return request({ - url: '/backend/theme/data', - method: 'post', - data - }) -} +import request from '@/utils/request' + +// 获取模板列表 +export function getThemeList() { + return request({ + url: '/backend/theme', + method: 'get' + }) +} + +// 切换模板 +export function switchTheme(data) { + return request({ + url: '/backend/theme/switch', + method: 'post', + data + }) +} + +// 获取模板数据 +export function getThemeData(params) { + return request({ + url: '/backend/theme/data', + method: 'get', + params + }) +} + +// 保存模板数据 +export function saveThemeData(data) { + return request({ + url: '/backend/theme/data', + method: 'post', + data + }) +} diff --git a/backend/src/api/user.js b/backend/src/api/user.js index bb075b6..e90d0a8 100644 --- a/backend/src/api/user.js +++ b/backend/src/api/user.js @@ -1,84 +1,84 @@ -import request from '@/utils/request'; - -//获取所有用户信息 -export function getAllUsers(params) { - return request({ - url: '/backend/getAllUsers', - method: 'get', - params, - }); -} - -//获取租户用户 -export function getTenantUsers(tenantId) { - return request({ - url: `/backend/getTenantUsers/${tenantId}`, - method: 'get', - }); -} - -// 获取用户信息 -export function getUserInfo(userId) { - return request({ - url: `/backend/getUserInfo/${userId}`, - method: 'get', - }); -} - -// 添加用户 -export function addUser(data) { - return request({ - url: '/backend/addUser', - method: 'post', - data, - }); -} - -// 编辑用户信息 -export function editUser(userId, data) { - return request({ - url: `/backend/editUser/${userId}`, - method: 'post', - data, - }); -} - -// 更新用户信息(编辑用户的别名) -export function updateUserInfo(userId, data) { - return editUser(userId, data); -} - -// 更新个人资料 -export function updateUserProfile(userId, data) { - return editUser(userId, data); -} - -// 绑定/更换手机号 -export function bindPhone(userId, phone) { - return editUser(userId, { phone }); -} - -// 绑定/更换邮箱 -export function bindEmail(userId, email) { - return editUser(userId, { email }); -} - -// 删除用户 -export function deleteUser(userId) { - return request({ - url: `/backend/deleteUser/${userId}`, - method: 'delete', - }); -} - -// 修改密码 -export function changePassword(userId, data) { - return request({ - url: '/backend/changePassword', - method: 'post', - data: { - id: userId, - password: data.newPassword - }, - }); -} +import request from '@/utils/request'; + +//获取所有用户信息 +export function getAllUsers(params) { + return request({ + url: '/backend/getAllUsers', + method: 'get', + params, + }); +} + +//获取租户用户 +export function getTenantUsers(tenantId) { + return request({ + url: `/backend/getTenantUsers/${tenantId}`, + method: 'get', + }); +} + +// 获取用户信息 +export function getUserInfo(userId) { + return request({ + url: `/backend/getUserInfo/${userId}`, + method: 'get', + }); +} + +// 添加用户 +export function addUser(data) { + return request({ + url: '/backend/addUser', + method: 'post', + data, + }); +} + +// 编辑用户信息 +export function editUser(userId, data) { + return request({ + url: `/backend/editUser/${userId}`, + method: 'post', + data, + }); +} + +// 更新用户信息(编辑用户的别名) +export function updateUserInfo(userId, data) { + return editUser(userId, data); +} + +// 更新个人资料 +export function updateUserProfile(userId, data) { + return editUser(userId, data); +} + +// 绑定/更换手机号 +export function bindPhone(userId, phone) { + return editUser(userId, { phone }); +} + +// 绑定/更换邮箱 +export function bindEmail(userId, email) { + return editUser(userId, { email }); +} + +// 删除用户 +export function deleteUser(userId) { + return request({ + url: `/backend/deleteUser/${userId}`, + method: 'delete', + }); +} + +// 修改密码 +export function changePassword(userId, data) { + return request({ + url: '/backend/changePassword', + method: 'post', + data: { + id: userId, + password: data.newPassword + }, + }); +} diff --git a/backend/src/api/workbench.js b/backend/src/api/workbench.js index 379ef9e..d4414c1 100644 --- a/backend/src/api/workbench.js +++ b/backend/src/api/workbench.js @@ -1,11 +1,11 @@ -// 文章管理相关API -import request from "@/utils/request"; - -// 获取文章列表 -export function GetCRMWorkbench(params) { - return request({ - url: `/backend/workbench/crm`, - method: "get", - params, - }); -} +// 文章管理相关API +import request from "@/utils/request"; + +// 获取文章列表 +export function GetCRMWorkbench(params) { + return request({ + url: `/backend/workbench/crm`, + method: "get", + params, + }); +} diff --git a/backend/src/assets/css/all.min.css b/backend/src/assets/css/all.min.css index 6591894..b9cb950 100644 --- a/backend/src/assets/css/all.min.css +++ b/backend/src/assets/css/all.min.css @@ -1,9 +1,9 @@ -/*! - * Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - * Copyright 2025 Fonticons, Inc. - */ -.fa,.fa-brands,.fa-classic,.fa-regular,.fa-solid,.fab,.far,.fas{--_fa-family:var(--fa-family,var(--fa-style-family,"Font Awesome 7 Free"));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:var(--fa-display,inline-block);font-family:var(--_fa-family);font-feature-settings:normal;font-style:normal;font-synthesis:none;font-variant:normal;font-weight:var(--fa-style,900);line-height:1;text-align:center;text-rendering:auto;width:var(--fa-width,1.25em)}:is(.fas,.far,.fab,.fa-solid,.fa-regular,.fa-brands,.fa-classic,.fa):before{content:var(--fa);content:var(--fa)/""}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-width-auto{--fa-width:auto}.fa-fw,.fa-width-fixed{--fa-width:1.25em}.fa-ul{list-style-type:none;margin-inline-start:var(--fa-li-margin,2.5em);padding-inline-start:0}.fa-ul>li{position:relative}.fa-li{inset-inline-start:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.0625em) var(--fa-border-style,solid) var(--fa-border-color,#eee);box-sizing:var(--fa-border-box-sizing,content-box);padding:var(--fa-border-padding,.1875em .25em)}.fa-pull-left,.fa-pull-start{float:inline-start;margin-inline-end:var(--fa-pull-margin,.3em)}.fa-pull-end,.fa-pull-right{float:inline-end;margin-inline-start:var(--fa-pull-margin,.3em)}.fa-beat{animation-name:fa-beat;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{animation-name:fa-bounce;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{animation-name:fa-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{animation-name:fa-beat-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{animation-name:fa-flip;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{animation-name:fa-shake;animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{animation-name:fa-spin;animation-duration:var(--fa-animation-duration,2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{animation-name:fa-spin;animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{animation:none!important;transition:none!important}}@keyframes fa-beat{0%,90%{transform:scale(1)}45%{transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-bounce{0%{transform:scale(1) translateY(0)}10%{transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{transform:scale(1) translateY(0)}to{transform:scale(1) translateY(0)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);transform:scale(1)}50%{opacity:1;transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-flip{50%{transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-shake{0%{transform:rotate(-15deg)}4%{transform:rotate(15deg)}8%,24%{transform:rotate(-18deg)}12%,28%{transform:rotate(18deg)}16%{transform:rotate(-22deg)}20%{transform:rotate(22deg)}32%{transform:rotate(-12deg)}36%{transform:rotate(12deg)}40%,to{transform:rotate(0deg)}}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}.fa-rotate-by{transform:rotate(var(--fa-rotate-angle,0))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} - -.fa-0{--fa:"\30 "}.fa-1{--fa:"\31 "}.fa-2{--fa:"\32 "}.fa-3{--fa:"\33 "}.fa-4{--fa:"\34 "}.fa-5{--fa:"\35 "}.fa-6{--fa:"\36 "}.fa-7{--fa:"\37 "}.fa-8{--fa:"\38 "}.fa-9{--fa:"\39 "}.fa-exclamation{--fa:"\!"}.fa-hashtag{--fa:"\#"}.fa-dollar,.fa-dollar-sign,.fa-usd{--fa:"\$"}.fa-percent,.fa-percentage{--fa:"\%"}.fa-asterisk{--fa:"\*"}.fa-add,.fa-plus{--fa:"\+"}.fa-less-than{--fa:"\<"}.fa-equals{--fa:"\="}.fa-greater-than{--fa:"\>"}.fa-question{--fa:"\?"}.fa-at{--fa:"\@"}.fa-a{--fa:"A"}.fa-b{--fa:"B"}.fa-c{--fa:"C"}.fa-d{--fa:"D"}.fa-e{--fa:"E"}.fa-f{--fa:"F"}.fa-g{--fa:"G"}.fa-h{--fa:"H"}.fa-i{--fa:"I"}.fa-j{--fa:"J"}.fa-k{--fa:"K"}.fa-l{--fa:"L"}.fa-m{--fa:"M"}.fa-n{--fa:"N"}.fa-o{--fa:"O"}.fa-p{--fa:"P"}.fa-q{--fa:"Q"}.fa-r{--fa:"R"}.fa-s{--fa:"S"}.fa-t{--fa:"T"}.fa-u{--fa:"U"}.fa-v{--fa:"V"}.fa-w{--fa:"W"}.fa-x{--fa:"X"}.fa-y{--fa:"Y"}.fa-z{--fa:"Z"}.fa-faucet{--fa:"\e005"}.fa-faucet-drip{--fa:"\e006"}.fa-house-chimney-window{--fa:"\e00d"}.fa-house-signal{--fa:"\e012"}.fa-temperature-arrow-down,.fa-temperature-down{--fa:"\e03f"}.fa-temperature-arrow-up,.fa-temperature-up{--fa:"\e040"}.fa-trailer{--fa:"\e041"}.fa-bacteria{--fa:"\e059"}.fa-bacterium{--fa:"\e05a"}.fa-box-tissue{--fa:"\e05b"}.fa-hand-holding-medical{--fa:"\e05c"}.fa-hand-sparkles{--fa:"\e05d"}.fa-hands-bubbles,.fa-hands-wash{--fa:"\e05e"}.fa-handshake-alt-slash,.fa-handshake-simple-slash,.fa-handshake-slash{--fa:"\e060"}.fa-head-side-cough{--fa:"\e061"}.fa-head-side-cough-slash{--fa:"\e062"}.fa-head-side-mask{--fa:"\e063"}.fa-head-side-virus{--fa:"\e064"}.fa-house-chimney-user{--fa:"\e065"}.fa-house-laptop,.fa-laptop-house{--fa:"\e066"}.fa-lungs-virus{--fa:"\e067"}.fa-people-arrows,.fa-people-arrows-left-right{--fa:"\e068"}.fa-plane-slash{--fa:"\e069"}.fa-pump-medical{--fa:"\e06a"}.fa-pump-soap{--fa:"\e06b"}.fa-shield-virus{--fa:"\e06c"}.fa-sink{--fa:"\e06d"}.fa-soap{--fa:"\e06e"}.fa-stopwatch-20{--fa:"\e06f"}.fa-shop-slash,.fa-store-alt-slash{--fa:"\e070"}.fa-store-slash{--fa:"\e071"}.fa-toilet-paper-slash{--fa:"\e072"}.fa-users-slash{--fa:"\e073"}.fa-virus{--fa:"\e074"}.fa-virus-slash{--fa:"\e075"}.fa-viruses{--fa:"\e076"}.fa-vest{--fa:"\e085"}.fa-vest-patches{--fa:"\e086"}.fa-arrow-trend-down{--fa:"\e097"}.fa-arrow-trend-up{--fa:"\e098"}.fa-arrow-up-from-bracket{--fa:"\e09a"}.fa-austral-sign{--fa:"\e0a9"}.fa-baht-sign{--fa:"\e0ac"}.fa-bitcoin-sign{--fa:"\e0b4"}.fa-bolt-lightning{--fa:"\e0b7"}.fa-book-bookmark{--fa:"\e0bb"}.fa-camera-rotate{--fa:"\e0d8"}.fa-cedi-sign{--fa:"\e0df"}.fa-chart-column{--fa:"\e0e3"}.fa-chart-gantt{--fa:"\e0e4"}.fa-clapperboard{--fa:"\e131"}.fa-clover{--fa:"\e139"}.fa-code-compare{--fa:"\e13a"}.fa-code-fork{--fa:"\e13b"}.fa-code-pull-request{--fa:"\e13c"}.fa-colon-sign{--fa:"\e140"}.fa-cruzeiro-sign{--fa:"\e152"}.fa-display{--fa:"\e163"}.fa-dong-sign{--fa:"\e169"}.fa-elevator{--fa:"\e16d"}.fa-filter-circle-xmark{--fa:"\e17b"}.fa-florin-sign{--fa:"\e184"}.fa-folder-closed{--fa:"\e185"}.fa-franc-sign{--fa:"\e18f"}.fa-guarani-sign{--fa:"\e19a"}.fa-gun{--fa:"\e19b"}.fa-hands-clapping{--fa:"\e1a8"}.fa-home-user,.fa-house-user{--fa:"\e1b0"}.fa-indian-rupee,.fa-indian-rupee-sign,.fa-inr{--fa:"\e1bc"}.fa-kip-sign{--fa:"\e1c4"}.fa-lari-sign{--fa:"\e1c8"}.fa-litecoin-sign{--fa:"\e1d3"}.fa-manat-sign{--fa:"\e1d5"}.fa-mask-face{--fa:"\e1d7"}.fa-mill-sign{--fa:"\e1ed"}.fa-money-bills{--fa:"\e1f3"}.fa-naira-sign{--fa:"\e1f6"}.fa-notdef{--fa:"\e1fe"}.fa-panorama{--fa:"\e209"}.fa-peseta-sign{--fa:"\e221"}.fa-peso-sign{--fa:"\e222"}.fa-plane-up{--fa:"\e22d"}.fa-rupiah-sign{--fa:"\e23d"}.fa-stairs{--fa:"\e289"}.fa-timeline{--fa:"\e29c"}.fa-truck-front{--fa:"\e2b7"}.fa-try,.fa-turkish-lira,.fa-turkish-lira-sign{--fa:"\e2bb"}.fa-vault{--fa:"\e2c5"}.fa-magic-wand-sparkles,.fa-wand-magic-sparkles{--fa:"\e2ca"}.fa-wheat-alt,.fa-wheat-awn{--fa:"\e2cd"}.fa-wheelchair-alt,.fa-wheelchair-move{--fa:"\e2ce"}.fa-bangladeshi-taka-sign{--fa:"\e2e6"}.fa-bowl-rice{--fa:"\e2eb"}.fa-person-pregnant{--fa:"\e31e"}.fa-home-lg,.fa-house-chimney{--fa:"\e3af"}.fa-house-crack{--fa:"\e3b1"}.fa-house-medical{--fa:"\e3b2"}.fa-cent-sign{--fa:"\e3f5"}.fa-plus-minus{--fa:"\e43c"}.fa-sailboat{--fa:"\e445"}.fa-section{--fa:"\e447"}.fa-shrimp{--fa:"\e448"}.fa-brazilian-real-sign{--fa:"\e46c"}.fa-chart-simple{--fa:"\e473"}.fa-diagram-next{--fa:"\e476"}.fa-diagram-predecessor{--fa:"\e477"}.fa-diagram-successor{--fa:"\e47a"}.fa-earth-oceania,.fa-globe-oceania{--fa:"\e47b"}.fa-bug-slash{--fa:"\e490"}.fa-file-circle-plus{--fa:"\e494"}.fa-shop-lock{--fa:"\e4a5"}.fa-virus-covid{--fa:"\e4a8"}.fa-virus-covid-slash{--fa:"\e4a9"}.fa-anchor-circle-check{--fa:"\e4aa"}.fa-anchor-circle-exclamation{--fa:"\e4ab"}.fa-anchor-circle-xmark{--fa:"\e4ac"}.fa-anchor-lock{--fa:"\e4ad"}.fa-arrow-down-up-across-line{--fa:"\e4af"}.fa-arrow-down-up-lock{--fa:"\e4b0"}.fa-arrow-right-to-city{--fa:"\e4b3"}.fa-arrow-up-from-ground-water{--fa:"\e4b5"}.fa-arrow-up-from-water-pump{--fa:"\e4b6"}.fa-arrow-up-right-dots{--fa:"\e4b7"}.fa-arrows-down-to-line{--fa:"\e4b8"}.fa-arrows-down-to-people{--fa:"\e4b9"}.fa-arrows-left-right-to-line{--fa:"\e4ba"}.fa-arrows-spin{--fa:"\e4bb"}.fa-arrows-split-up-and-left{--fa:"\e4bc"}.fa-arrows-to-circle{--fa:"\e4bd"}.fa-arrows-to-dot{--fa:"\e4be"}.fa-arrows-to-eye{--fa:"\e4bf"}.fa-arrows-turn-right{--fa:"\e4c0"}.fa-arrows-turn-to-dots{--fa:"\e4c1"}.fa-arrows-up-to-line{--fa:"\e4c2"}.fa-bore-hole{--fa:"\e4c3"}.fa-bottle-droplet{--fa:"\e4c4"}.fa-bottle-water{--fa:"\e4c5"}.fa-bowl-food{--fa:"\e4c6"}.fa-boxes-packing{--fa:"\e4c7"}.fa-bridge{--fa:"\e4c8"}.fa-bridge-circle-check{--fa:"\e4c9"}.fa-bridge-circle-exclamation{--fa:"\e4ca"}.fa-bridge-circle-xmark{--fa:"\e4cb"}.fa-bridge-lock{--fa:"\e4cc"}.fa-bridge-water{--fa:"\e4ce"}.fa-bucket{--fa:"\e4cf"}.fa-bugs{--fa:"\e4d0"}.fa-building-circle-arrow-right{--fa:"\e4d1"}.fa-building-circle-check{--fa:"\e4d2"}.fa-building-circle-exclamation{--fa:"\e4d3"}.fa-building-circle-xmark{--fa:"\e4d4"}.fa-building-flag{--fa:"\e4d5"}.fa-building-lock{--fa:"\e4d6"}.fa-building-ngo{--fa:"\e4d7"}.fa-building-shield{--fa:"\e4d8"}.fa-building-un{--fa:"\e4d9"}.fa-building-user{--fa:"\e4da"}.fa-building-wheat{--fa:"\e4db"}.fa-burst{--fa:"\e4dc"}.fa-car-on{--fa:"\e4dd"}.fa-car-tunnel{--fa:"\e4de"}.fa-child-combatant,.fa-child-rifle{--fa:"\e4e0"}.fa-children{--fa:"\e4e1"}.fa-circle-nodes{--fa:"\e4e2"}.fa-clipboard-question{--fa:"\e4e3"}.fa-cloud-showers-water{--fa:"\e4e4"}.fa-computer{--fa:"\e4e5"}.fa-cubes-stacked{--fa:"\e4e6"}.fa-envelope-circle-check{--fa:"\e4e8"}.fa-explosion{--fa:"\e4e9"}.fa-ferry{--fa:"\e4ea"}.fa-file-circle-exclamation{--fa:"\e4eb"}.fa-file-circle-minus{--fa:"\e4ed"}.fa-file-circle-question{--fa:"\e4ef"}.fa-file-shield{--fa:"\e4f0"}.fa-fire-burner{--fa:"\e4f1"}.fa-fish-fins{--fa:"\e4f2"}.fa-flask-vial{--fa:"\e4f3"}.fa-glass-water{--fa:"\e4f4"}.fa-glass-water-droplet{--fa:"\e4f5"}.fa-group-arrows-rotate{--fa:"\e4f6"}.fa-hand-holding-hand{--fa:"\e4f7"}.fa-handcuffs{--fa:"\e4f8"}.fa-hands-bound{--fa:"\e4f9"}.fa-hands-holding-child{--fa:"\e4fa"}.fa-hands-holding-circle{--fa:"\e4fb"}.fa-heart-circle-bolt{--fa:"\e4fc"}.fa-heart-circle-check{--fa:"\e4fd"}.fa-heart-circle-exclamation{--fa:"\e4fe"}.fa-heart-circle-minus{--fa:"\e4ff"}.fa-heart-circle-plus{--fa:"\e500"}.fa-heart-circle-xmark{--fa:"\e501"}.fa-helicopter-symbol{--fa:"\e502"}.fa-helmet-un{--fa:"\e503"}.fa-hill-avalanche{--fa:"\e507"}.fa-hill-rockslide{--fa:"\e508"}.fa-house-circle-check{--fa:"\e509"}.fa-house-circle-exclamation{--fa:"\e50a"}.fa-house-circle-xmark{--fa:"\e50b"}.fa-house-fire{--fa:"\e50c"}.fa-house-flag{--fa:"\e50d"}.fa-house-flood-water{--fa:"\e50e"}.fa-house-flood-water-circle-arrow-right{--fa:"\e50f"}.fa-house-lock{--fa:"\e510"}.fa-house-medical-circle-check{--fa:"\e511"}.fa-house-medical-circle-exclamation{--fa:"\e512"}.fa-house-medical-circle-xmark{--fa:"\e513"}.fa-house-medical-flag{--fa:"\e514"}.fa-house-tsunami{--fa:"\e515"}.fa-jar{--fa:"\e516"}.fa-jar-wheat{--fa:"\e517"}.fa-jet-fighter-up{--fa:"\e518"}.fa-jug-detergent{--fa:"\e519"}.fa-kitchen-set{--fa:"\e51a"}.fa-land-mine-on{--fa:"\e51b"}.fa-landmark-flag{--fa:"\e51c"}.fa-laptop-file{--fa:"\e51d"}.fa-lines-leaning{--fa:"\e51e"}.fa-location-pin-lock{--fa:"\e51f"}.fa-locust{--fa:"\e520"}.fa-magnifying-glass-arrow-right{--fa:"\e521"}.fa-magnifying-glass-chart{--fa:"\e522"}.fa-mars-and-venus-burst{--fa:"\e523"}.fa-mask-ventilator{--fa:"\e524"}.fa-mattress-pillow{--fa:"\e525"}.fa-mobile-retro{--fa:"\e527"}.fa-money-bill-transfer{--fa:"\e528"}.fa-money-bill-trend-up{--fa:"\e529"}.fa-money-bill-wheat{--fa:"\e52a"}.fa-mosquito{--fa:"\e52b"}.fa-mosquito-net{--fa:"\e52c"}.fa-mound{--fa:"\e52d"}.fa-mountain-city{--fa:"\e52e"}.fa-mountain-sun{--fa:"\e52f"}.fa-oil-well{--fa:"\e532"}.fa-people-group{--fa:"\e533"}.fa-people-line{--fa:"\e534"}.fa-people-pulling{--fa:"\e535"}.fa-people-robbery{--fa:"\e536"}.fa-people-roof{--fa:"\e537"}.fa-person-arrow-down-to-line{--fa:"\e538"}.fa-person-arrow-up-from-line{--fa:"\e539"}.fa-person-breastfeeding{--fa:"\e53a"}.fa-person-burst{--fa:"\e53b"}.fa-person-cane{--fa:"\e53c"}.fa-person-chalkboard{--fa:"\e53d"}.fa-person-circle-check{--fa:"\e53e"}.fa-person-circle-exclamation{--fa:"\e53f"}.fa-person-circle-minus{--fa:"\e540"}.fa-person-circle-plus{--fa:"\e541"}.fa-person-circle-question{--fa:"\e542"}.fa-person-circle-xmark{--fa:"\e543"}.fa-person-dress-burst{--fa:"\e544"}.fa-person-drowning{--fa:"\e545"}.fa-person-falling{--fa:"\e546"}.fa-person-falling-burst{--fa:"\e547"}.fa-person-half-dress{--fa:"\e548"}.fa-person-harassing{--fa:"\e549"}.fa-person-military-pointing{--fa:"\e54a"}.fa-person-military-rifle{--fa:"\e54b"}.fa-person-military-to-person{--fa:"\e54c"}.fa-person-rays{--fa:"\e54d"}.fa-person-rifle{--fa:"\e54e"}.fa-person-shelter{--fa:"\e54f"}.fa-person-walking-arrow-loop-left{--fa:"\e551"}.fa-person-walking-arrow-right{--fa:"\e552"}.fa-person-walking-dashed-line-arrow-right{--fa:"\e553"}.fa-person-walking-luggage{--fa:"\e554"}.fa-plane-circle-check{--fa:"\e555"}.fa-plane-circle-exclamation{--fa:"\e556"}.fa-plane-circle-xmark{--fa:"\e557"}.fa-plane-lock{--fa:"\e558"}.fa-plate-wheat{--fa:"\e55a"}.fa-plug-circle-bolt{--fa:"\e55b"}.fa-plug-circle-check{--fa:"\e55c"}.fa-plug-circle-exclamation{--fa:"\e55d"}.fa-plug-circle-minus{--fa:"\e55e"}.fa-plug-circle-plus{--fa:"\e55f"}.fa-plug-circle-xmark{--fa:"\e560"}.fa-ranking-star{--fa:"\e561"}.fa-road-barrier{--fa:"\e562"}.fa-road-bridge{--fa:"\e563"}.fa-road-circle-check{--fa:"\e564"}.fa-road-circle-exclamation{--fa:"\e565"}.fa-road-circle-xmark{--fa:"\e566"}.fa-road-lock{--fa:"\e567"}.fa-road-spikes{--fa:"\e568"}.fa-rug{--fa:"\e569"}.fa-sack-xmark{--fa:"\e56a"}.fa-school-circle-check{--fa:"\e56b"}.fa-school-circle-exclamation{--fa:"\e56c"}.fa-school-circle-xmark{--fa:"\e56d"}.fa-school-flag{--fa:"\e56e"}.fa-school-lock{--fa:"\e56f"}.fa-sheet-plastic{--fa:"\e571"}.fa-shield-cat{--fa:"\e572"}.fa-shield-dog{--fa:"\e573"}.fa-shield-heart{--fa:"\e574"}.fa-square-nfi{--fa:"\e576"}.fa-square-person-confined{--fa:"\e577"}.fa-square-virus{--fa:"\e578"}.fa-rod-asclepius,.fa-rod-snake,.fa-staff-aesculapius,.fa-staff-snake{--fa:"\e579"}.fa-sun-plant-wilt{--fa:"\e57a"}.fa-tarp{--fa:"\e57b"}.fa-tarp-droplet{--fa:"\e57c"}.fa-tent{--fa:"\e57d"}.fa-tent-arrow-down-to-line{--fa:"\e57e"}.fa-tent-arrow-left-right{--fa:"\e57f"}.fa-tent-arrow-turn-left{--fa:"\e580"}.fa-tent-arrows-down{--fa:"\e581"}.fa-tents{--fa:"\e582"}.fa-toilet-portable{--fa:"\e583"}.fa-toilets-portable{--fa:"\e584"}.fa-tower-cell{--fa:"\e585"}.fa-tower-observation{--fa:"\e586"}.fa-tree-city{--fa:"\e587"}.fa-trowel{--fa:"\e589"}.fa-trowel-bricks{--fa:"\e58a"}.fa-truck-arrow-right{--fa:"\e58b"}.fa-truck-droplet{--fa:"\e58c"}.fa-truck-field{--fa:"\e58d"}.fa-truck-field-un{--fa:"\e58e"}.fa-truck-plane{--fa:"\e58f"}.fa-users-between-lines{--fa:"\e591"}.fa-users-line{--fa:"\e592"}.fa-users-rays{--fa:"\e593"}.fa-users-rectangle{--fa:"\e594"}.fa-users-viewfinder{--fa:"\e595"}.fa-vial-circle-check{--fa:"\e596"}.fa-vial-virus{--fa:"\e597"}.fa-wheat-awn-circle-exclamation{--fa:"\e598"}.fa-worm{--fa:"\e599"}.fa-xmarks-lines{--fa:"\e59a"}.fa-child-dress{--fa:"\e59c"}.fa-child-reaching{--fa:"\e59d"}.fa-file-circle-check{--fa:"\e5a0"}.fa-file-circle-xmark{--fa:"\e5a1"}.fa-person-through-window{--fa:"\e5a9"}.fa-plant-wilt{--fa:"\e5aa"}.fa-stapler{--fa:"\e5af"}.fa-train-tram{--fa:"\e5b4"}.fa-table-cells-column-lock{--fa:"\e678"}.fa-table-cells-row-lock{--fa:"\e67a"}.fa-thumb-tack-slash,.fa-thumbtack-slash{--fa:"\e68f"}.fa-table-cells-row-unlock{--fa:"\e691"}.fa-chart-diagram{--fa:"\e695"}.fa-comment-nodes{--fa:"\e696"}.fa-file-fragment{--fa:"\e697"}.fa-file-half-dashed{--fa:"\e698"}.fa-hexagon-nodes{--fa:"\e699"}.fa-hexagon-nodes-bolt{--fa:"\e69a"}.fa-square-binary{--fa:"\e69b"}.fa-pentagon{--fa:"\e790"}.fa-non-binary{--fa:"\e807"}.fa-spiral{--fa:"\e80a"}.fa-mobile-vibrate{--fa:"\e816"}.fa-single-quote-left{--fa:"\e81b"}.fa-single-quote-right{--fa:"\e81c"}.fa-bus-side{--fa:"\e81d"}.fa-heptagon,.fa-septagon{--fa:"\e820"}.fa-glass-martini,.fa-martini-glass-empty{--fa:"\f000"}.fa-music{--fa:"\f001"}.fa-magnifying-glass,.fa-search{--fa:"\f002"}.fa-heart{--fa:"\f004"}.fa-star{--fa:"\f005"}.fa-user,.fa-user-alt,.fa-user-large{--fa:"\f007"}.fa-film,.fa-film-alt,.fa-film-simple{--fa:"\f008"}.fa-table-cells-large,.fa-th-large{--fa:"\f009"}.fa-table-cells,.fa-th{--fa:"\f00a"}.fa-table-list,.fa-th-list{--fa:"\f00b"}.fa-check{--fa:"\f00c"}.fa-close,.fa-multiply,.fa-remove,.fa-times,.fa-xmark{--fa:"\f00d"}.fa-magnifying-glass-plus,.fa-search-plus{--fa:"\f00e"}.fa-magnifying-glass-minus,.fa-search-minus{--fa:"\f010"}.fa-power-off{--fa:"\f011"}.fa-signal,.fa-signal-5,.fa-signal-perfect{--fa:"\f012"}.fa-cog,.fa-gear{--fa:"\f013"}.fa-home,.fa-home-alt,.fa-home-lg-alt,.fa-house{--fa:"\f015"}.fa-clock,.fa-clock-four{--fa:"\f017"}.fa-road{--fa:"\f018"}.fa-download{--fa:"\f019"}.fa-inbox{--fa:"\f01c"}.fa-arrow-right-rotate,.fa-arrow-rotate-forward,.fa-arrow-rotate-right,.fa-redo{--fa:"\f01e"}.fa-arrows-rotate,.fa-refresh,.fa-sync{--fa:"\f021"}.fa-list-alt,.fa-rectangle-list{--fa:"\f022"}.fa-lock{--fa:"\f023"}.fa-flag{--fa:"\f024"}.fa-headphones,.fa-headphones-alt,.fa-headphones-simple{--fa:"\f025"}.fa-volume-off{--fa:"\f026"}.fa-volume-down,.fa-volume-low{--fa:"\f027"}.fa-volume-high,.fa-volume-up{--fa:"\f028"}.fa-qrcode{--fa:"\f029"}.fa-barcode{--fa:"\f02a"}.fa-tag{--fa:"\f02b"}.fa-tags{--fa:"\f02c"}.fa-book{--fa:"\f02d"}.fa-bookmark{--fa:"\f02e"}.fa-print{--fa:"\f02f"}.fa-camera,.fa-camera-alt{--fa:"\f030"}.fa-font{--fa:"\f031"}.fa-bold{--fa:"\f032"}.fa-italic{--fa:"\f033"}.fa-text-height{--fa:"\f034"}.fa-text-width{--fa:"\f035"}.fa-align-left{--fa:"\f036"}.fa-align-center{--fa:"\f037"}.fa-align-right{--fa:"\f038"}.fa-align-justify{--fa:"\f039"}.fa-list,.fa-list-squares{--fa:"\f03a"}.fa-dedent,.fa-outdent{--fa:"\f03b"}.fa-indent{--fa:"\f03c"}.fa-video,.fa-video-camera{--fa:"\f03d"}.fa-image{--fa:"\f03e"}.fa-location-pin,.fa-map-marker{--fa:"\f041"}.fa-adjust,.fa-circle-half-stroke{--fa:"\f042"}.fa-droplet,.fa-tint{--fa:"\f043"}.fa-edit,.fa-pen-to-square{--fa:"\f044"}.fa-arrows,.fa-arrows-up-down-left-right{--fa:"\f047"}.fa-backward-step,.fa-step-backward{--fa:"\f048"}.fa-backward-fast,.fa-fast-backward{--fa:"\f049"}.fa-backward{--fa:"\f04a"}.fa-play{--fa:"\f04b"}.fa-pause{--fa:"\f04c"}.fa-stop{--fa:"\f04d"}.fa-forward{--fa:"\f04e"}.fa-fast-forward,.fa-forward-fast{--fa:"\f050"}.fa-forward-step,.fa-step-forward{--fa:"\f051"}.fa-eject{--fa:"\f052"}.fa-chevron-left{--fa:"\f053"}.fa-chevron-right{--fa:"\f054"}.fa-circle-plus,.fa-plus-circle{--fa:"\f055"}.fa-circle-minus,.fa-minus-circle{--fa:"\f056"}.fa-circle-xmark,.fa-times-circle,.fa-xmark-circle{--fa:"\f057"}.fa-check-circle,.fa-circle-check{--fa:"\f058"}.fa-circle-question,.fa-question-circle{--fa:"\f059"}.fa-circle-info,.fa-info-circle{--fa:"\f05a"}.fa-crosshairs{--fa:"\f05b"}.fa-ban,.fa-cancel{--fa:"\f05e"}.fa-arrow-left{--fa:"\f060"}.fa-arrow-right{--fa:"\f061"}.fa-arrow-up{--fa:"\f062"}.fa-arrow-down{--fa:"\f063"}.fa-mail-forward,.fa-share{--fa:"\f064"}.fa-expand{--fa:"\f065"}.fa-compress{--fa:"\f066"}.fa-minus,.fa-subtract{--fa:"\f068"}.fa-circle-exclamation,.fa-exclamation-circle{--fa:"\f06a"}.fa-gift{--fa:"\f06b"}.fa-leaf{--fa:"\f06c"}.fa-fire{--fa:"\f06d"}.fa-eye{--fa:"\f06e"}.fa-eye-slash{--fa:"\f070"}.fa-exclamation-triangle,.fa-triangle-exclamation,.fa-warning{--fa:"\f071"}.fa-plane{--fa:"\f072"}.fa-calendar-alt,.fa-calendar-days{--fa:"\f073"}.fa-random,.fa-shuffle{--fa:"\f074"}.fa-comment{--fa:"\f075"}.fa-magnet{--fa:"\f076"}.fa-chevron-up{--fa:"\f077"}.fa-chevron-down{--fa:"\f078"}.fa-retweet{--fa:"\f079"}.fa-cart-shopping,.fa-shopping-cart{--fa:"\f07a"}.fa-folder,.fa-folder-blank{--fa:"\f07b"}.fa-folder-open{--fa:"\f07c"}.fa-arrows-up-down,.fa-arrows-v{--fa:"\f07d"}.fa-arrows-h,.fa-arrows-left-right{--fa:"\f07e"}.fa-bar-chart,.fa-chart-bar{--fa:"\f080"}.fa-camera-retro{--fa:"\f083"}.fa-key{--fa:"\f084"}.fa-cogs,.fa-gears{--fa:"\f085"}.fa-comments{--fa:"\f086"}.fa-star-half{--fa:"\f089"}.fa-arrow-right-from-bracket,.fa-sign-out{--fa:"\f08b"}.fa-thumb-tack,.fa-thumbtack{--fa:"\f08d"}.fa-arrow-up-right-from-square,.fa-external-link{--fa:"\f08e"}.fa-arrow-right-to-bracket,.fa-sign-in{--fa:"\f090"}.fa-trophy{--fa:"\f091"}.fa-upload{--fa:"\f093"}.fa-lemon{--fa:"\f094"}.fa-phone{--fa:"\f095"}.fa-phone-square,.fa-square-phone{--fa:"\f098"}.fa-unlock{--fa:"\f09c"}.fa-credit-card,.fa-credit-card-alt{--fa:"\f09d"}.fa-feed,.fa-rss{--fa:"\f09e"}.fa-hard-drive,.fa-hdd{--fa:"\f0a0"}.fa-bullhorn{--fa:"\f0a1"}.fa-certificate{--fa:"\f0a3"}.fa-hand-point-right{--fa:"\f0a4"}.fa-hand-point-left{--fa:"\f0a5"}.fa-hand-point-up{--fa:"\f0a6"}.fa-hand-point-down{--fa:"\f0a7"}.fa-arrow-circle-left,.fa-circle-arrow-left{--fa:"\f0a8"}.fa-arrow-circle-right,.fa-circle-arrow-right{--fa:"\f0a9"}.fa-arrow-circle-up,.fa-circle-arrow-up{--fa:"\f0aa"}.fa-arrow-circle-down,.fa-circle-arrow-down{--fa:"\f0ab"}.fa-globe{--fa:"\f0ac"}.fa-wrench{--fa:"\f0ad"}.fa-list-check,.fa-tasks{--fa:"\f0ae"}.fa-filter{--fa:"\f0b0"}.fa-briefcase{--fa:"\f0b1"}.fa-arrows-alt,.fa-up-down-left-right{--fa:"\f0b2"}.fa-users{--fa:"\f0c0"}.fa-chain,.fa-link{--fa:"\f0c1"}.fa-cloud{--fa:"\f0c2"}.fa-flask{--fa:"\f0c3"}.fa-cut,.fa-scissors{--fa:"\f0c4"}.fa-copy{--fa:"\f0c5"}.fa-paperclip{--fa:"\f0c6"}.fa-floppy-disk,.fa-save{--fa:"\f0c7"}.fa-square{--fa:"\f0c8"}.fa-bars,.fa-navicon{--fa:"\f0c9"}.fa-list-dots,.fa-list-ul{--fa:"\f0ca"}.fa-list-1-2,.fa-list-numeric,.fa-list-ol{--fa:"\f0cb"}.fa-strikethrough{--fa:"\f0cc"}.fa-underline{--fa:"\f0cd"}.fa-table{--fa:"\f0ce"}.fa-magic,.fa-wand-magic{--fa:"\f0d0"}.fa-truck{--fa:"\f0d1"}.fa-money-bill{--fa:"\f0d6"}.fa-caret-down{--fa:"\f0d7"}.fa-caret-up{--fa:"\f0d8"}.fa-caret-left{--fa:"\f0d9"}.fa-caret-right{--fa:"\f0da"}.fa-columns,.fa-table-columns{--fa:"\f0db"}.fa-sort,.fa-unsorted{--fa:"\f0dc"}.fa-sort-desc,.fa-sort-down{--fa:"\f0dd"}.fa-sort-asc,.fa-sort-up{--fa:"\f0de"}.fa-envelope{--fa:"\f0e0"}.fa-arrow-left-rotate,.fa-arrow-rotate-back,.fa-arrow-rotate-backward,.fa-arrow-rotate-left,.fa-undo{--fa:"\f0e2"}.fa-gavel,.fa-legal{--fa:"\f0e3"}.fa-bolt,.fa-zap{--fa:"\f0e7"}.fa-sitemap{--fa:"\f0e8"}.fa-umbrella{--fa:"\f0e9"}.fa-file-clipboard,.fa-paste{--fa:"\f0ea"}.fa-lightbulb{--fa:"\f0eb"}.fa-arrow-right-arrow-left,.fa-exchange{--fa:"\f0ec"}.fa-cloud-arrow-down,.fa-cloud-download,.fa-cloud-download-alt{--fa:"\f0ed"}.fa-cloud-arrow-up,.fa-cloud-upload,.fa-cloud-upload-alt{--fa:"\f0ee"}.fa-user-doctor,.fa-user-md{--fa:"\f0f0"}.fa-stethoscope{--fa:"\f0f1"}.fa-suitcase{--fa:"\f0f2"}.fa-bell{--fa:"\f0f3"}.fa-coffee,.fa-mug-saucer{--fa:"\f0f4"}.fa-hospital,.fa-hospital-alt,.fa-hospital-wide{--fa:"\f0f8"}.fa-ambulance,.fa-truck-medical{--fa:"\f0f9"}.fa-medkit,.fa-suitcase-medical{--fa:"\f0fa"}.fa-fighter-jet,.fa-jet-fighter{--fa:"\f0fb"}.fa-beer,.fa-beer-mug-empty{--fa:"\f0fc"}.fa-h-square,.fa-square-h{--fa:"\f0fd"}.fa-plus-square,.fa-square-plus{--fa:"\f0fe"}.fa-angle-double-left,.fa-angles-left{--fa:"\f100"}.fa-angle-double-right,.fa-angles-right{--fa:"\f101"}.fa-angle-double-up,.fa-angles-up{--fa:"\f102"}.fa-angle-double-down,.fa-angles-down{--fa:"\f103"}.fa-angle-left{--fa:"\f104"}.fa-angle-right{--fa:"\f105"}.fa-angle-up{--fa:"\f106"}.fa-angle-down{--fa:"\f107"}.fa-laptop{--fa:"\f109"}.fa-tablet-button{--fa:"\f10a"}.fa-mobile-button{--fa:"\f10b"}.fa-quote-left,.fa-quote-left-alt{--fa:"\f10d"}.fa-quote-right,.fa-quote-right-alt{--fa:"\f10e"}.fa-spinner{--fa:"\f110"}.fa-circle{--fa:"\f111"}.fa-face-smile,.fa-smile{--fa:"\f118"}.fa-face-frown,.fa-frown{--fa:"\f119"}.fa-face-meh,.fa-meh{--fa:"\f11a"}.fa-gamepad{--fa:"\f11b"}.fa-keyboard{--fa:"\f11c"}.fa-flag-checkered{--fa:"\f11e"}.fa-terminal{--fa:"\f120"}.fa-code{--fa:"\f121"}.fa-mail-reply-all,.fa-reply-all{--fa:"\f122"}.fa-location-arrow{--fa:"\f124"}.fa-crop{--fa:"\f125"}.fa-code-branch{--fa:"\f126"}.fa-chain-broken,.fa-chain-slash,.fa-link-slash,.fa-unlink{--fa:"\f127"}.fa-info{--fa:"\f129"}.fa-superscript{--fa:"\f12b"}.fa-subscript{--fa:"\f12c"}.fa-eraser{--fa:"\f12d"}.fa-puzzle-piece{--fa:"\f12e"}.fa-microphone{--fa:"\f130"}.fa-microphone-slash{--fa:"\f131"}.fa-shield,.fa-shield-blank{--fa:"\f132"}.fa-calendar{--fa:"\f133"}.fa-fire-extinguisher{--fa:"\f134"}.fa-rocket{--fa:"\f135"}.fa-chevron-circle-left,.fa-circle-chevron-left{--fa:"\f137"}.fa-chevron-circle-right,.fa-circle-chevron-right{--fa:"\f138"}.fa-chevron-circle-up,.fa-circle-chevron-up{--fa:"\f139"}.fa-chevron-circle-down,.fa-circle-chevron-down{--fa:"\f13a"}.fa-anchor{--fa:"\f13d"}.fa-unlock-alt,.fa-unlock-keyhole{--fa:"\f13e"}.fa-bullseye{--fa:"\f140"}.fa-ellipsis,.fa-ellipsis-h{--fa:"\f141"}.fa-ellipsis-v,.fa-ellipsis-vertical{--fa:"\f142"}.fa-rss-square,.fa-square-rss{--fa:"\f143"}.fa-circle-play,.fa-play-circle{--fa:"\f144"}.fa-ticket{--fa:"\f145"}.fa-minus-square,.fa-square-minus{--fa:"\f146"}.fa-arrow-turn-up,.fa-level-up{--fa:"\f148"}.fa-arrow-turn-down,.fa-level-down{--fa:"\f149"}.fa-check-square,.fa-square-check{--fa:"\f14a"}.fa-pen-square,.fa-pencil-square,.fa-square-pen{--fa:"\f14b"}.fa-external-link-square,.fa-square-arrow-up-right{--fa:"\f14c"}.fa-share-from-square,.fa-share-square{--fa:"\f14d"}.fa-compass{--fa:"\f14e"}.fa-caret-square-down,.fa-square-caret-down{--fa:"\f150"}.fa-caret-square-up,.fa-square-caret-up{--fa:"\f151"}.fa-caret-square-right,.fa-square-caret-right{--fa:"\f152"}.fa-eur,.fa-euro,.fa-euro-sign{--fa:"\f153"}.fa-gbp,.fa-pound-sign,.fa-sterling-sign{--fa:"\f154"}.fa-rupee,.fa-rupee-sign{--fa:"\f156"}.fa-cny,.fa-jpy,.fa-rmb,.fa-yen,.fa-yen-sign{--fa:"\f157"}.fa-rouble,.fa-rub,.fa-ruble,.fa-ruble-sign{--fa:"\f158"}.fa-krw,.fa-won,.fa-won-sign{--fa:"\f159"}.fa-file{--fa:"\f15b"}.fa-file-alt,.fa-file-lines,.fa-file-text{--fa:"\f15c"}.fa-arrow-down-a-z,.fa-sort-alpha-asc,.fa-sort-alpha-down{--fa:"\f15d"}.fa-arrow-up-a-z,.fa-sort-alpha-up{--fa:"\f15e"}.fa-arrow-down-wide-short,.fa-sort-amount-asc,.fa-sort-amount-down{--fa:"\f160"}.fa-arrow-up-wide-short,.fa-sort-amount-up{--fa:"\f161"}.fa-arrow-down-1-9,.fa-sort-numeric-asc,.fa-sort-numeric-down{--fa:"\f162"}.fa-arrow-up-1-9,.fa-sort-numeric-up{--fa:"\f163"}.fa-thumbs-up{--fa:"\f164"}.fa-thumbs-down{--fa:"\f165"}.fa-arrow-down-long,.fa-long-arrow-down{--fa:"\f175"}.fa-arrow-up-long,.fa-long-arrow-up{--fa:"\f176"}.fa-arrow-left-long,.fa-long-arrow-left{--fa:"\f177"}.fa-arrow-right-long,.fa-long-arrow-right{--fa:"\f178"}.fa-female,.fa-person-dress{--fa:"\f182"}.fa-male,.fa-person{--fa:"\f183"}.fa-sun{--fa:"\f185"}.fa-moon{--fa:"\f186"}.fa-archive,.fa-box-archive{--fa:"\f187"}.fa-bug{--fa:"\f188"}.fa-caret-square-left,.fa-square-caret-left{--fa:"\f191"}.fa-circle-dot,.fa-dot-circle{--fa:"\f192"}.fa-wheelchair{--fa:"\f193"}.fa-lira-sign{--fa:"\f195"}.fa-shuttle-space,.fa-space-shuttle{--fa:"\f197"}.fa-envelope-square,.fa-square-envelope{--fa:"\f199"}.fa-bank,.fa-building-columns,.fa-institution,.fa-museum,.fa-university{--fa:"\f19c"}.fa-graduation-cap,.fa-mortar-board{--fa:"\f19d"}.fa-language{--fa:"\f1ab"}.fa-fax{--fa:"\f1ac"}.fa-building{--fa:"\f1ad"}.fa-child{--fa:"\f1ae"}.fa-paw{--fa:"\f1b0"}.fa-cube{--fa:"\f1b2"}.fa-cubes{--fa:"\f1b3"}.fa-recycle{--fa:"\f1b8"}.fa-automobile,.fa-car{--fa:"\f1b9"}.fa-cab,.fa-taxi{--fa:"\f1ba"}.fa-tree{--fa:"\f1bb"}.fa-database{--fa:"\f1c0"}.fa-file-pdf{--fa:"\f1c1"}.fa-file-word{--fa:"\f1c2"}.fa-file-excel{--fa:"\f1c3"}.fa-file-powerpoint{--fa:"\f1c4"}.fa-file-image{--fa:"\f1c5"}.fa-file-archive,.fa-file-zipper{--fa:"\f1c6"}.fa-file-audio{--fa:"\f1c7"}.fa-file-video{--fa:"\f1c8"}.fa-file-code{--fa:"\f1c9"}.fa-life-ring{--fa:"\f1cd"}.fa-circle-notch{--fa:"\f1ce"}.fa-paper-plane{--fa:"\f1d8"}.fa-clock-rotate-left,.fa-history{--fa:"\f1da"}.fa-header,.fa-heading{--fa:"\f1dc"}.fa-paragraph{--fa:"\f1dd"}.fa-sliders,.fa-sliders-h{--fa:"\f1de"}.fa-share-alt,.fa-share-nodes{--fa:"\f1e0"}.fa-share-alt-square,.fa-square-share-nodes{--fa:"\f1e1"}.fa-bomb{--fa:"\f1e2"}.fa-futbol,.fa-futbol-ball,.fa-soccer-ball{--fa:"\f1e3"}.fa-teletype,.fa-tty{--fa:"\f1e4"}.fa-binoculars{--fa:"\f1e5"}.fa-plug{--fa:"\f1e6"}.fa-newspaper{--fa:"\f1ea"}.fa-wifi,.fa-wifi-3,.fa-wifi-strong{--fa:"\f1eb"}.fa-calculator{--fa:"\f1ec"}.fa-bell-slash{--fa:"\f1f6"}.fa-trash{--fa:"\f1f8"}.fa-copyright{--fa:"\f1f9"}.fa-eye-dropper,.fa-eye-dropper-empty,.fa-eyedropper{--fa:"\f1fb"}.fa-paint-brush,.fa-paintbrush{--fa:"\f1fc"}.fa-birthday-cake,.fa-cake,.fa-cake-candles{--fa:"\f1fd"}.fa-area-chart,.fa-chart-area{--fa:"\f1fe"}.fa-chart-pie,.fa-pie-chart{--fa:"\f200"}.fa-chart-line,.fa-line-chart{--fa:"\f201"}.fa-toggle-off{--fa:"\f204"}.fa-toggle-on{--fa:"\f205"}.fa-bicycle{--fa:"\f206"}.fa-bus{--fa:"\f207"}.fa-closed-captioning{--fa:"\f20a"}.fa-ils,.fa-shekel,.fa-shekel-sign,.fa-sheqel,.fa-sheqel-sign{--fa:"\f20b"}.fa-cart-plus{--fa:"\f217"}.fa-cart-arrow-down{--fa:"\f218"}.fa-diamond{--fa:"\f219"}.fa-ship{--fa:"\f21a"}.fa-user-secret{--fa:"\f21b"}.fa-motorcycle{--fa:"\f21c"}.fa-street-view{--fa:"\f21d"}.fa-heart-pulse,.fa-heartbeat{--fa:"\f21e"}.fa-venus{--fa:"\f221"}.fa-mars{--fa:"\f222"}.fa-mercury{--fa:"\f223"}.fa-mars-and-venus{--fa:"\f224"}.fa-transgender,.fa-transgender-alt{--fa:"\f225"}.fa-venus-double{--fa:"\f226"}.fa-mars-double{--fa:"\f227"}.fa-venus-mars{--fa:"\f228"}.fa-mars-stroke{--fa:"\f229"}.fa-mars-stroke-up,.fa-mars-stroke-v{--fa:"\f22a"}.fa-mars-stroke-h,.fa-mars-stroke-right{--fa:"\f22b"}.fa-neuter{--fa:"\f22c"}.fa-genderless{--fa:"\f22d"}.fa-server{--fa:"\f233"}.fa-user-plus{--fa:"\f234"}.fa-user-times,.fa-user-xmark{--fa:"\f235"}.fa-bed{--fa:"\f236"}.fa-train{--fa:"\f238"}.fa-subway,.fa-train-subway{--fa:"\f239"}.fa-battery,.fa-battery-5,.fa-battery-full{--fa:"\f240"}.fa-battery-4,.fa-battery-three-quarters{--fa:"\f241"}.fa-battery-3,.fa-battery-half{--fa:"\f242"}.fa-battery-2,.fa-battery-quarter{--fa:"\f243"}.fa-battery-0,.fa-battery-empty{--fa:"\f244"}.fa-arrow-pointer,.fa-mouse-pointer{--fa:"\f245"}.fa-i-cursor{--fa:"\f246"}.fa-object-group{--fa:"\f247"}.fa-object-ungroup{--fa:"\f248"}.fa-note-sticky,.fa-sticky-note{--fa:"\f249"}.fa-clone{--fa:"\f24d"}.fa-balance-scale,.fa-scale-balanced{--fa:"\f24e"}.fa-hourglass-1,.fa-hourglass-start{--fa:"\f251"}.fa-hourglass-2,.fa-hourglass-half{--fa:"\f252"}.fa-hourglass-3,.fa-hourglass-end{--fa:"\f253"}.fa-hourglass,.fa-hourglass-empty{--fa:"\f254"}.fa-hand-back-fist,.fa-hand-rock{--fa:"\f255"}.fa-hand,.fa-hand-paper{--fa:"\f256"}.fa-hand-scissors{--fa:"\f257"}.fa-hand-lizard{--fa:"\f258"}.fa-hand-spock{--fa:"\f259"}.fa-hand-pointer{--fa:"\f25a"}.fa-hand-peace{--fa:"\f25b"}.fa-trademark{--fa:"\f25c"}.fa-registered{--fa:"\f25d"}.fa-television,.fa-tv,.fa-tv-alt{--fa:"\f26c"}.fa-calendar-plus{--fa:"\f271"}.fa-calendar-minus{--fa:"\f272"}.fa-calendar-times,.fa-calendar-xmark{--fa:"\f273"}.fa-calendar-check{--fa:"\f274"}.fa-industry{--fa:"\f275"}.fa-map-pin{--fa:"\f276"}.fa-map-signs,.fa-signs-post{--fa:"\f277"}.fa-map{--fa:"\f279"}.fa-comment-alt,.fa-message{--fa:"\f27a"}.fa-circle-pause,.fa-pause-circle{--fa:"\f28b"}.fa-circle-stop,.fa-stop-circle{--fa:"\f28d"}.fa-bag-shopping,.fa-shopping-bag{--fa:"\f290"}.fa-basket-shopping,.fa-shopping-basket{--fa:"\f291"}.fa-universal-access{--fa:"\f29a"}.fa-blind,.fa-person-walking-with-cane{--fa:"\f29d"}.fa-audio-description{--fa:"\f29e"}.fa-phone-volume,.fa-volume-control-phone{--fa:"\f2a0"}.fa-braille{--fa:"\f2a1"}.fa-assistive-listening-systems,.fa-ear-listen{--fa:"\f2a2"}.fa-american-sign-language-interpreting,.fa-asl-interpreting,.fa-hands-american-sign-language-interpreting,.fa-hands-asl-interpreting{--fa:"\f2a3"}.fa-deaf,.fa-deafness,.fa-ear-deaf,.fa-hard-of-hearing{--fa:"\f2a4"}.fa-hands,.fa-sign-language,.fa-signing{--fa:"\f2a7"}.fa-eye-low-vision,.fa-low-vision{--fa:"\f2a8"}.fa-handshake,.fa-handshake-alt,.fa-handshake-simple{--fa:"\f2b5"}.fa-envelope-open{--fa:"\f2b6"}.fa-address-book,.fa-contact-book{--fa:"\f2b9"}.fa-address-card,.fa-contact-card,.fa-vcard{--fa:"\f2bb"}.fa-circle-user,.fa-user-circle{--fa:"\f2bd"}.fa-id-badge{--fa:"\f2c1"}.fa-drivers-license,.fa-id-card{--fa:"\f2c2"}.fa-temperature-4,.fa-temperature-full,.fa-thermometer-4,.fa-thermometer-full{--fa:"\f2c7"}.fa-temperature-3,.fa-temperature-three-quarters,.fa-thermometer-3,.fa-thermometer-three-quarters{--fa:"\f2c8"}.fa-temperature-2,.fa-temperature-half,.fa-thermometer-2,.fa-thermometer-half{--fa:"\f2c9"}.fa-temperature-1,.fa-temperature-quarter,.fa-thermometer-1,.fa-thermometer-quarter{--fa:"\f2ca"}.fa-temperature-0,.fa-temperature-empty,.fa-thermometer-0,.fa-thermometer-empty{--fa:"\f2cb"}.fa-shower{--fa:"\f2cc"}.fa-bath,.fa-bathtub{--fa:"\f2cd"}.fa-podcast{--fa:"\f2ce"}.fa-window-maximize{--fa:"\f2d0"}.fa-window-minimize{--fa:"\f2d1"}.fa-window-restore{--fa:"\f2d2"}.fa-square-xmark,.fa-times-square,.fa-xmark-square{--fa:"\f2d3"}.fa-microchip{--fa:"\f2db"}.fa-snowflake{--fa:"\f2dc"}.fa-spoon,.fa-utensil-spoon{--fa:"\f2e5"}.fa-cutlery,.fa-utensils{--fa:"\f2e7"}.fa-rotate-back,.fa-rotate-backward,.fa-rotate-left,.fa-undo-alt{--fa:"\f2ea"}.fa-trash-alt,.fa-trash-can{--fa:"\f2ed"}.fa-rotate,.fa-sync-alt{--fa:"\f2f1"}.fa-stopwatch{--fa:"\f2f2"}.fa-right-from-bracket,.fa-sign-out-alt{--fa:"\f2f5"}.fa-right-to-bracket,.fa-sign-in-alt{--fa:"\f2f6"}.fa-redo-alt,.fa-rotate-forward,.fa-rotate-right{--fa:"\f2f9"}.fa-poo{--fa:"\f2fe"}.fa-images{--fa:"\f302"}.fa-pencil,.fa-pencil-alt{--fa:"\f303"}.fa-pen{--fa:"\f304"}.fa-pen-alt,.fa-pen-clip{--fa:"\f305"}.fa-octagon{--fa:"\f306"}.fa-down-long,.fa-long-arrow-alt-down{--fa:"\f309"}.fa-left-long,.fa-long-arrow-alt-left{--fa:"\f30a"}.fa-long-arrow-alt-right,.fa-right-long{--fa:"\f30b"}.fa-long-arrow-alt-up,.fa-up-long{--fa:"\f30c"}.fa-hexagon{--fa:"\f312"}.fa-file-edit,.fa-file-pen{--fa:"\f31c"}.fa-expand-arrows-alt,.fa-maximize{--fa:"\f31e"}.fa-clipboard{--fa:"\f328"}.fa-arrows-alt-h,.fa-left-right{--fa:"\f337"}.fa-arrows-alt-v,.fa-up-down{--fa:"\f338"}.fa-alarm-clock{--fa:"\f34e"}.fa-arrow-alt-circle-down,.fa-circle-down{--fa:"\f358"}.fa-arrow-alt-circle-left,.fa-circle-left{--fa:"\f359"}.fa-arrow-alt-circle-right,.fa-circle-right{--fa:"\f35a"}.fa-arrow-alt-circle-up,.fa-circle-up{--fa:"\f35b"}.fa-external-link-alt,.fa-up-right-from-square{--fa:"\f35d"}.fa-external-link-square-alt,.fa-square-up-right{--fa:"\f360"}.fa-exchange-alt,.fa-right-left{--fa:"\f362"}.fa-repeat{--fa:"\f363"}.fa-code-commit{--fa:"\f386"}.fa-code-merge{--fa:"\f387"}.fa-desktop,.fa-desktop-alt{--fa:"\f390"}.fa-gem{--fa:"\f3a5"}.fa-level-down-alt,.fa-turn-down{--fa:"\f3be"}.fa-level-up-alt,.fa-turn-up{--fa:"\f3bf"}.fa-lock-open{--fa:"\f3c1"}.fa-location-dot,.fa-map-marker-alt{--fa:"\f3c5"}.fa-microphone-alt,.fa-microphone-lines{--fa:"\f3c9"}.fa-mobile-alt,.fa-mobile-screen-button{--fa:"\f3cd"}.fa-mobile,.fa-mobile-android,.fa-mobile-phone{--fa:"\f3ce"}.fa-mobile-android-alt,.fa-mobile-screen{--fa:"\f3cf"}.fa-money-bill-1,.fa-money-bill-alt{--fa:"\f3d1"}.fa-phone-slash{--fa:"\f3dd"}.fa-image-portrait,.fa-portrait{--fa:"\f3e0"}.fa-mail-reply,.fa-reply{--fa:"\f3e5"}.fa-shield-alt,.fa-shield-halved{--fa:"\f3ed"}.fa-tablet-alt,.fa-tablet-screen-button{--fa:"\f3fa"}.fa-tablet,.fa-tablet-android{--fa:"\f3fb"}.fa-ticket-alt,.fa-ticket-simple{--fa:"\f3ff"}.fa-rectangle-times,.fa-rectangle-xmark,.fa-times-rectangle,.fa-window-close{--fa:"\f410"}.fa-compress-alt,.fa-down-left-and-up-right-to-center{--fa:"\f422"}.fa-expand-alt,.fa-up-right-and-down-left-from-center{--fa:"\f424"}.fa-baseball-bat-ball{--fa:"\f432"}.fa-baseball,.fa-baseball-ball{--fa:"\f433"}.fa-basketball,.fa-basketball-ball{--fa:"\f434"}.fa-bowling-ball{--fa:"\f436"}.fa-chess{--fa:"\f439"}.fa-chess-bishop{--fa:"\f43a"}.fa-chess-board{--fa:"\f43c"}.fa-chess-king{--fa:"\f43f"}.fa-chess-knight{--fa:"\f441"}.fa-chess-pawn{--fa:"\f443"}.fa-chess-queen{--fa:"\f445"}.fa-chess-rook{--fa:"\f447"}.fa-dumbbell{--fa:"\f44b"}.fa-football,.fa-football-ball{--fa:"\f44e"}.fa-golf-ball,.fa-golf-ball-tee{--fa:"\f450"}.fa-hockey-puck{--fa:"\f453"}.fa-broom-ball,.fa-quidditch,.fa-quidditch-broom-ball{--fa:"\f458"}.fa-square-full{--fa:"\f45c"}.fa-ping-pong-paddle-ball,.fa-table-tennis,.fa-table-tennis-paddle-ball{--fa:"\f45d"}.fa-volleyball,.fa-volleyball-ball{--fa:"\f45f"}.fa-allergies,.fa-hand-dots{--fa:"\f461"}.fa-band-aid,.fa-bandage{--fa:"\f462"}.fa-box{--fa:"\f466"}.fa-boxes,.fa-boxes-alt,.fa-boxes-stacked{--fa:"\f468"}.fa-briefcase-medical{--fa:"\f469"}.fa-burn,.fa-fire-flame-simple{--fa:"\f46a"}.fa-capsules{--fa:"\f46b"}.fa-clipboard-check{--fa:"\f46c"}.fa-clipboard-list{--fa:"\f46d"}.fa-diagnoses,.fa-person-dots-from-line{--fa:"\f470"}.fa-dna{--fa:"\f471"}.fa-dolly,.fa-dolly-box{--fa:"\f472"}.fa-cart-flatbed,.fa-dolly-flatbed{--fa:"\f474"}.fa-file-medical{--fa:"\f477"}.fa-file-medical-alt,.fa-file-waveform{--fa:"\f478"}.fa-first-aid,.fa-kit-medical{--fa:"\f479"}.fa-circle-h,.fa-hospital-symbol{--fa:"\f47e"}.fa-id-card-alt,.fa-id-card-clip{--fa:"\f47f"}.fa-notes-medical{--fa:"\f481"}.fa-pallet{--fa:"\f482"}.fa-pills{--fa:"\f484"}.fa-prescription-bottle{--fa:"\f485"}.fa-prescription-bottle-alt,.fa-prescription-bottle-medical{--fa:"\f486"}.fa-bed-pulse,.fa-procedures{--fa:"\f487"}.fa-shipping-fast,.fa-truck-fast{--fa:"\f48b"}.fa-smoking{--fa:"\f48d"}.fa-syringe{--fa:"\f48e"}.fa-tablets{--fa:"\f490"}.fa-thermometer{--fa:"\f491"}.fa-vial{--fa:"\f492"}.fa-vials{--fa:"\f493"}.fa-warehouse{--fa:"\f494"}.fa-weight,.fa-weight-scale{--fa:"\f496"}.fa-x-ray{--fa:"\f497"}.fa-box-open{--fa:"\f49e"}.fa-comment-dots,.fa-commenting{--fa:"\f4ad"}.fa-comment-slash{--fa:"\f4b3"}.fa-couch{--fa:"\f4b8"}.fa-circle-dollar-to-slot,.fa-donate{--fa:"\f4b9"}.fa-dove{--fa:"\f4ba"}.fa-hand-holding{--fa:"\f4bd"}.fa-hand-holding-heart{--fa:"\f4be"}.fa-hand-holding-dollar,.fa-hand-holding-usd{--fa:"\f4c0"}.fa-hand-holding-droplet,.fa-hand-holding-water{--fa:"\f4c1"}.fa-hands-holding{--fa:"\f4c2"}.fa-hands-helping,.fa-handshake-angle{--fa:"\f4c4"}.fa-parachute-box{--fa:"\f4cd"}.fa-people-carry,.fa-people-carry-box{--fa:"\f4ce"}.fa-piggy-bank{--fa:"\f4d3"}.fa-ribbon{--fa:"\f4d6"}.fa-route{--fa:"\f4d7"}.fa-seedling,.fa-sprout{--fa:"\f4d8"}.fa-sign,.fa-sign-hanging{--fa:"\f4d9"}.fa-face-smile-wink,.fa-smile-wink{--fa:"\f4da"}.fa-tape{--fa:"\f4db"}.fa-truck-loading,.fa-truck-ramp-box{--fa:"\f4de"}.fa-truck-moving{--fa:"\f4df"}.fa-video-slash{--fa:"\f4e2"}.fa-wine-glass{--fa:"\f4e3"}.fa-user-astronaut{--fa:"\f4fb"}.fa-user-check{--fa:"\f4fc"}.fa-user-clock{--fa:"\f4fd"}.fa-user-cog,.fa-user-gear{--fa:"\f4fe"}.fa-user-edit,.fa-user-pen{--fa:"\f4ff"}.fa-user-friends,.fa-user-group{--fa:"\f500"}.fa-user-graduate{--fa:"\f501"}.fa-user-lock{--fa:"\f502"}.fa-user-minus{--fa:"\f503"}.fa-user-ninja{--fa:"\f504"}.fa-user-shield{--fa:"\f505"}.fa-user-alt-slash,.fa-user-large-slash,.fa-user-slash{--fa:"\f506"}.fa-user-tag{--fa:"\f507"}.fa-user-tie{--fa:"\f508"}.fa-users-cog,.fa-users-gear{--fa:"\f509"}.fa-balance-scale-left,.fa-scale-unbalanced{--fa:"\f515"}.fa-balance-scale-right,.fa-scale-unbalanced-flip{--fa:"\f516"}.fa-blender{--fa:"\f517"}.fa-book-open{--fa:"\f518"}.fa-broadcast-tower,.fa-tower-broadcast{--fa:"\f519"}.fa-broom{--fa:"\f51a"}.fa-blackboard,.fa-chalkboard{--fa:"\f51b"}.fa-chalkboard-teacher,.fa-chalkboard-user{--fa:"\f51c"}.fa-church{--fa:"\f51d"}.fa-coins{--fa:"\f51e"}.fa-compact-disc{--fa:"\f51f"}.fa-crow{--fa:"\f520"}.fa-crown{--fa:"\f521"}.fa-dice{--fa:"\f522"}.fa-dice-five{--fa:"\f523"}.fa-dice-four{--fa:"\f524"}.fa-dice-one{--fa:"\f525"}.fa-dice-six{--fa:"\f526"}.fa-dice-three{--fa:"\f527"}.fa-dice-two{--fa:"\f528"}.fa-divide{--fa:"\f529"}.fa-door-closed{--fa:"\f52a"}.fa-door-open{--fa:"\f52b"}.fa-feather{--fa:"\f52d"}.fa-frog{--fa:"\f52e"}.fa-gas-pump{--fa:"\f52f"}.fa-glasses{--fa:"\f530"}.fa-greater-than-equal{--fa:"\f532"}.fa-helicopter{--fa:"\f533"}.fa-infinity{--fa:"\f534"}.fa-kiwi-bird{--fa:"\f535"}.fa-less-than-equal{--fa:"\f537"}.fa-memory{--fa:"\f538"}.fa-microphone-alt-slash,.fa-microphone-lines-slash{--fa:"\f539"}.fa-money-bill-wave{--fa:"\f53a"}.fa-money-bill-1-wave,.fa-money-bill-wave-alt{--fa:"\f53b"}.fa-money-check{--fa:"\f53c"}.fa-money-check-alt,.fa-money-check-dollar{--fa:"\f53d"}.fa-not-equal{--fa:"\f53e"}.fa-palette{--fa:"\f53f"}.fa-parking,.fa-square-parking{--fa:"\f540"}.fa-diagram-project,.fa-project-diagram{--fa:"\f542"}.fa-receipt{--fa:"\f543"}.fa-robot{--fa:"\f544"}.fa-ruler{--fa:"\f545"}.fa-ruler-combined{--fa:"\f546"}.fa-ruler-horizontal{--fa:"\f547"}.fa-ruler-vertical{--fa:"\f548"}.fa-school{--fa:"\f549"}.fa-screwdriver{--fa:"\f54a"}.fa-shoe-prints{--fa:"\f54b"}.fa-skull{--fa:"\f54c"}.fa-ban-smoking,.fa-smoking-ban{--fa:"\f54d"}.fa-store{--fa:"\f54e"}.fa-shop,.fa-store-alt{--fa:"\f54f"}.fa-bars-staggered,.fa-reorder,.fa-stream{--fa:"\f550"}.fa-stroopwafel{--fa:"\f551"}.fa-toolbox{--fa:"\f552"}.fa-shirt,.fa-t-shirt,.fa-tshirt{--fa:"\f553"}.fa-person-walking,.fa-walking{--fa:"\f554"}.fa-wallet{--fa:"\f555"}.fa-angry,.fa-face-angry{--fa:"\f556"}.fa-archway{--fa:"\f557"}.fa-atlas,.fa-book-atlas{--fa:"\f558"}.fa-award{--fa:"\f559"}.fa-backspace,.fa-delete-left{--fa:"\f55a"}.fa-bezier-curve{--fa:"\f55b"}.fa-bong{--fa:"\f55c"}.fa-brush{--fa:"\f55d"}.fa-bus-alt,.fa-bus-simple{--fa:"\f55e"}.fa-cannabis{--fa:"\f55f"}.fa-check-double{--fa:"\f560"}.fa-cocktail,.fa-martini-glass-citrus{--fa:"\f561"}.fa-bell-concierge,.fa-concierge-bell{--fa:"\f562"}.fa-cookie{--fa:"\f563"}.fa-cookie-bite{--fa:"\f564"}.fa-crop-alt,.fa-crop-simple{--fa:"\f565"}.fa-digital-tachograph,.fa-tachograph-digital{--fa:"\f566"}.fa-dizzy,.fa-face-dizzy{--fa:"\f567"}.fa-compass-drafting,.fa-drafting-compass{--fa:"\f568"}.fa-drum{--fa:"\f569"}.fa-drum-steelpan{--fa:"\f56a"}.fa-feather-alt,.fa-feather-pointed{--fa:"\f56b"}.fa-file-contract{--fa:"\f56c"}.fa-file-arrow-down,.fa-file-download{--fa:"\f56d"}.fa-arrow-right-from-file,.fa-file-export{--fa:"\f56e"}.fa-arrow-right-to-file,.fa-file-import{--fa:"\f56f"}.fa-file-invoice{--fa:"\f570"}.fa-file-invoice-dollar{--fa:"\f571"}.fa-file-prescription{--fa:"\f572"}.fa-file-signature{--fa:"\f573"}.fa-file-arrow-up,.fa-file-upload{--fa:"\f574"}.fa-fill{--fa:"\f575"}.fa-fill-drip{--fa:"\f576"}.fa-fingerprint{--fa:"\f577"}.fa-fish{--fa:"\f578"}.fa-face-flushed,.fa-flushed{--fa:"\f579"}.fa-face-frown-open,.fa-frown-open{--fa:"\f57a"}.fa-glass-martini-alt,.fa-martini-glass{--fa:"\f57b"}.fa-earth-africa,.fa-globe-africa{--fa:"\f57c"}.fa-earth,.fa-earth-america,.fa-earth-americas,.fa-globe-americas{--fa:"\f57d"}.fa-earth-asia,.fa-globe-asia{--fa:"\f57e"}.fa-face-grimace,.fa-grimace{--fa:"\f57f"}.fa-face-grin,.fa-grin{--fa:"\f580"}.fa-face-grin-wide,.fa-grin-alt{--fa:"\f581"}.fa-face-grin-beam,.fa-grin-beam{--fa:"\f582"}.fa-face-grin-beam-sweat,.fa-grin-beam-sweat{--fa:"\f583"}.fa-face-grin-hearts,.fa-grin-hearts{--fa:"\f584"}.fa-face-grin-squint,.fa-grin-squint{--fa:"\f585"}.fa-face-grin-squint-tears,.fa-grin-squint-tears{--fa:"\f586"}.fa-face-grin-stars,.fa-grin-stars{--fa:"\f587"}.fa-face-grin-tears,.fa-grin-tears{--fa:"\f588"}.fa-face-grin-tongue,.fa-grin-tongue{--fa:"\f589"}.fa-face-grin-tongue-squint,.fa-grin-tongue-squint{--fa:"\f58a"}.fa-face-grin-tongue-wink,.fa-grin-tongue-wink{--fa:"\f58b"}.fa-face-grin-wink,.fa-grin-wink{--fa:"\f58c"}.fa-grid-horizontal,.fa-grip,.fa-grip-horizontal{--fa:"\f58d"}.fa-grid-vertical,.fa-grip-vertical{--fa:"\f58e"}.fa-headset{--fa:"\f590"}.fa-highlighter{--fa:"\f591"}.fa-hot-tub,.fa-hot-tub-person{--fa:"\f593"}.fa-hotel{--fa:"\f594"}.fa-joint{--fa:"\f595"}.fa-face-kiss,.fa-kiss{--fa:"\f596"}.fa-face-kiss-beam,.fa-kiss-beam{--fa:"\f597"}.fa-face-kiss-wink-heart,.fa-kiss-wink-heart{--fa:"\f598"}.fa-face-laugh,.fa-laugh{--fa:"\f599"}.fa-face-laugh-beam,.fa-laugh-beam{--fa:"\f59a"}.fa-face-laugh-squint,.fa-laugh-squint{--fa:"\f59b"}.fa-face-laugh-wink,.fa-laugh-wink{--fa:"\f59c"}.fa-cart-flatbed-suitcase,.fa-luggage-cart{--fa:"\f59d"}.fa-map-location,.fa-map-marked{--fa:"\f59f"}.fa-map-location-dot,.fa-map-marked-alt{--fa:"\f5a0"}.fa-marker{--fa:"\f5a1"}.fa-medal{--fa:"\f5a2"}.fa-face-meh-blank,.fa-meh-blank{--fa:"\f5a4"}.fa-face-rolling-eyes,.fa-meh-rolling-eyes{--fa:"\f5a5"}.fa-monument{--fa:"\f5a6"}.fa-mortar-pestle{--fa:"\f5a7"}.fa-paint-roller{--fa:"\f5aa"}.fa-passport{--fa:"\f5ab"}.fa-pen-fancy{--fa:"\f5ac"}.fa-pen-nib{--fa:"\f5ad"}.fa-pen-ruler,.fa-pencil-ruler{--fa:"\f5ae"}.fa-plane-arrival{--fa:"\f5af"}.fa-plane-departure{--fa:"\f5b0"}.fa-prescription{--fa:"\f5b1"}.fa-face-sad-cry,.fa-sad-cry{--fa:"\f5b3"}.fa-face-sad-tear,.fa-sad-tear{--fa:"\f5b4"}.fa-shuttle-van,.fa-van-shuttle{--fa:"\f5b6"}.fa-signature{--fa:"\f5b7"}.fa-face-smile-beam,.fa-smile-beam{--fa:"\f5b8"}.fa-solar-panel{--fa:"\f5ba"}.fa-spa{--fa:"\f5bb"}.fa-splotch{--fa:"\f5bc"}.fa-spray-can{--fa:"\f5bd"}.fa-stamp{--fa:"\f5bf"}.fa-star-half-alt,.fa-star-half-stroke{--fa:"\f5c0"}.fa-suitcase-rolling{--fa:"\f5c1"}.fa-face-surprise,.fa-surprise{--fa:"\f5c2"}.fa-swatchbook{--fa:"\f5c3"}.fa-person-swimming,.fa-swimmer{--fa:"\f5c4"}.fa-ladder-water,.fa-swimming-pool,.fa-water-ladder{--fa:"\f5c5"}.fa-droplet-slash,.fa-tint-slash{--fa:"\f5c7"}.fa-face-tired,.fa-tired{--fa:"\f5c8"}.fa-tooth{--fa:"\f5c9"}.fa-umbrella-beach{--fa:"\f5ca"}.fa-weight-hanging{--fa:"\f5cd"}.fa-wine-glass-alt,.fa-wine-glass-empty{--fa:"\f5ce"}.fa-air-freshener,.fa-spray-can-sparkles{--fa:"\f5d0"}.fa-apple-alt,.fa-apple-whole{--fa:"\f5d1"}.fa-atom{--fa:"\f5d2"}.fa-bone{--fa:"\f5d7"}.fa-book-open-reader,.fa-book-reader{--fa:"\f5da"}.fa-brain{--fa:"\f5dc"}.fa-car-alt,.fa-car-rear{--fa:"\f5de"}.fa-battery-car,.fa-car-battery{--fa:"\f5df"}.fa-car-burst,.fa-car-crash{--fa:"\f5e1"}.fa-car-side{--fa:"\f5e4"}.fa-charging-station{--fa:"\f5e7"}.fa-diamond-turn-right,.fa-directions{--fa:"\f5eb"}.fa-draw-polygon,.fa-vector-polygon{--fa:"\f5ee"}.fa-laptop-code{--fa:"\f5fc"}.fa-layer-group{--fa:"\f5fd"}.fa-location,.fa-location-crosshairs{--fa:"\f601"}.fa-lungs{--fa:"\f604"}.fa-microscope{--fa:"\f610"}.fa-oil-can{--fa:"\f613"}.fa-poop{--fa:"\f619"}.fa-shapes,.fa-triangle-circle-square{--fa:"\f61f"}.fa-star-of-life{--fa:"\f621"}.fa-dashboard,.fa-gauge,.fa-gauge-med,.fa-tachometer-alt-average{--fa:"\f624"}.fa-gauge-high,.fa-tachometer-alt,.fa-tachometer-alt-fast{--fa:"\f625"}.fa-gauge-simple,.fa-gauge-simple-med,.fa-tachometer-average{--fa:"\f629"}.fa-gauge-simple-high,.fa-tachometer,.fa-tachometer-fast{--fa:"\f62a"}.fa-teeth{--fa:"\f62e"}.fa-teeth-open{--fa:"\f62f"}.fa-masks-theater,.fa-theater-masks{--fa:"\f630"}.fa-traffic-light{--fa:"\f637"}.fa-truck-monster{--fa:"\f63b"}.fa-truck-pickup{--fa:"\f63c"}.fa-ad,.fa-rectangle-ad{--fa:"\f641"}.fa-ankh{--fa:"\f644"}.fa-bible,.fa-book-bible{--fa:"\f647"}.fa-briefcase-clock,.fa-business-time{--fa:"\f64a"}.fa-city{--fa:"\f64f"}.fa-comment-dollar{--fa:"\f651"}.fa-comments-dollar{--fa:"\f653"}.fa-cross{--fa:"\f654"}.fa-dharmachakra{--fa:"\f655"}.fa-envelope-open-text{--fa:"\f658"}.fa-folder-minus{--fa:"\f65d"}.fa-folder-plus{--fa:"\f65e"}.fa-filter-circle-dollar,.fa-funnel-dollar{--fa:"\f662"}.fa-gopuram{--fa:"\f664"}.fa-hamsa{--fa:"\f665"}.fa-bahai,.fa-haykal{--fa:"\f666"}.fa-jedi{--fa:"\f669"}.fa-book-journal-whills,.fa-journal-whills{--fa:"\f66a"}.fa-kaaba{--fa:"\f66b"}.fa-khanda{--fa:"\f66d"}.fa-landmark{--fa:"\f66f"}.fa-envelopes-bulk,.fa-mail-bulk{--fa:"\f674"}.fa-menorah{--fa:"\f676"}.fa-mosque{--fa:"\f678"}.fa-om{--fa:"\f679"}.fa-pastafarianism,.fa-spaghetti-monster-flying{--fa:"\f67b"}.fa-peace{--fa:"\f67c"}.fa-place-of-worship{--fa:"\f67f"}.fa-poll,.fa-square-poll-vertical{--fa:"\f681"}.fa-poll-h,.fa-square-poll-horizontal{--fa:"\f682"}.fa-person-praying,.fa-pray{--fa:"\f683"}.fa-hands-praying,.fa-praying-hands{--fa:"\f684"}.fa-book-quran,.fa-quran{--fa:"\f687"}.fa-magnifying-glass-dollar,.fa-search-dollar{--fa:"\f688"}.fa-magnifying-glass-location,.fa-search-location{--fa:"\f689"}.fa-socks{--fa:"\f696"}.fa-square-root-alt,.fa-square-root-variable{--fa:"\f698"}.fa-star-and-crescent{--fa:"\f699"}.fa-star-of-david{--fa:"\f69a"}.fa-synagogue{--fa:"\f69b"}.fa-scroll-torah,.fa-torah{--fa:"\f6a0"}.fa-torii-gate{--fa:"\f6a1"}.fa-vihara{--fa:"\f6a7"}.fa-volume-mute,.fa-volume-times,.fa-volume-xmark{--fa:"\f6a9"}.fa-yin-yang{--fa:"\f6ad"}.fa-blender-phone{--fa:"\f6b6"}.fa-book-dead,.fa-book-skull{--fa:"\f6b7"}.fa-campground{--fa:"\f6bb"}.fa-cat{--fa:"\f6be"}.fa-chair{--fa:"\f6c0"}.fa-cloud-moon{--fa:"\f6c3"}.fa-cloud-sun{--fa:"\f6c4"}.fa-cow{--fa:"\f6c8"}.fa-dice-d20{--fa:"\f6cf"}.fa-dice-d6{--fa:"\f6d1"}.fa-dog{--fa:"\f6d3"}.fa-dragon{--fa:"\f6d5"}.fa-drumstick-bite{--fa:"\f6d7"}.fa-dungeon{--fa:"\f6d9"}.fa-file-csv{--fa:"\f6dd"}.fa-fist-raised,.fa-hand-fist{--fa:"\f6de"}.fa-ghost{--fa:"\f6e2"}.fa-hammer{--fa:"\f6e3"}.fa-hanukiah{--fa:"\f6e6"}.fa-hat-wizard{--fa:"\f6e8"}.fa-hiking,.fa-person-hiking{--fa:"\f6ec"}.fa-hippo{--fa:"\f6ed"}.fa-horse{--fa:"\f6f0"}.fa-house-chimney-crack,.fa-house-damage{--fa:"\f6f1"}.fa-hryvnia,.fa-hryvnia-sign{--fa:"\f6f2"}.fa-mask{--fa:"\f6fa"}.fa-mountain{--fa:"\f6fc"}.fa-network-wired{--fa:"\f6ff"}.fa-otter{--fa:"\f700"}.fa-ring{--fa:"\f70b"}.fa-person-running,.fa-running{--fa:"\f70c"}.fa-scroll{--fa:"\f70e"}.fa-skull-crossbones{--fa:"\f714"}.fa-slash{--fa:"\f715"}.fa-spider{--fa:"\f717"}.fa-toilet-paper,.fa-toilet-paper-alt,.fa-toilet-paper-blank{--fa:"\f71e"}.fa-tractor{--fa:"\f722"}.fa-user-injured{--fa:"\f728"}.fa-vr-cardboard{--fa:"\f729"}.fa-wand-sparkles{--fa:"\f72b"}.fa-wind{--fa:"\f72e"}.fa-wine-bottle{--fa:"\f72f"}.fa-cloud-meatball{--fa:"\f73b"}.fa-cloud-moon-rain{--fa:"\f73c"}.fa-cloud-rain{--fa:"\f73d"}.fa-cloud-showers-heavy{--fa:"\f740"}.fa-cloud-sun-rain{--fa:"\f743"}.fa-democrat{--fa:"\f747"}.fa-flag-usa{--fa:"\f74d"}.fa-hurricane{--fa:"\f751"}.fa-landmark-alt,.fa-landmark-dome{--fa:"\f752"}.fa-meteor{--fa:"\f753"}.fa-person-booth{--fa:"\f756"}.fa-poo-bolt,.fa-poo-storm{--fa:"\f75a"}.fa-rainbow{--fa:"\f75b"}.fa-republican{--fa:"\f75e"}.fa-smog{--fa:"\f75f"}.fa-temperature-high{--fa:"\f769"}.fa-temperature-low{--fa:"\f76b"}.fa-cloud-bolt,.fa-thunderstorm{--fa:"\f76c"}.fa-tornado{--fa:"\f76f"}.fa-volcano{--fa:"\f770"}.fa-check-to-slot,.fa-vote-yea{--fa:"\f772"}.fa-water{--fa:"\f773"}.fa-baby{--fa:"\f77c"}.fa-baby-carriage,.fa-carriage-baby{--fa:"\f77d"}.fa-biohazard{--fa:"\f780"}.fa-blog{--fa:"\f781"}.fa-calendar-day{--fa:"\f783"}.fa-calendar-week{--fa:"\f784"}.fa-candy-cane{--fa:"\f786"}.fa-carrot{--fa:"\f787"}.fa-cash-register{--fa:"\f788"}.fa-compress-arrows-alt,.fa-minimize{--fa:"\f78c"}.fa-dumpster{--fa:"\f793"}.fa-dumpster-fire{--fa:"\f794"}.fa-ethernet{--fa:"\f796"}.fa-gifts{--fa:"\f79c"}.fa-champagne-glasses,.fa-glass-cheers{--fa:"\f79f"}.fa-glass-whiskey,.fa-whiskey-glass{--fa:"\f7a0"}.fa-earth-europe,.fa-globe-europe{--fa:"\f7a2"}.fa-grip-lines{--fa:"\f7a4"}.fa-grip-lines-vertical{--fa:"\f7a5"}.fa-guitar{--fa:"\f7a6"}.fa-heart-broken,.fa-heart-crack{--fa:"\f7a9"}.fa-holly-berry{--fa:"\f7aa"}.fa-horse-head{--fa:"\f7ab"}.fa-icicles{--fa:"\f7ad"}.fa-igloo{--fa:"\f7ae"}.fa-mitten{--fa:"\f7b5"}.fa-mug-hot{--fa:"\f7b6"}.fa-radiation{--fa:"\f7b9"}.fa-circle-radiation,.fa-radiation-alt{--fa:"\f7ba"}.fa-restroom{--fa:"\f7bd"}.fa-satellite{--fa:"\f7bf"}.fa-satellite-dish{--fa:"\f7c0"}.fa-sd-card{--fa:"\f7c2"}.fa-sim-card{--fa:"\f7c4"}.fa-person-skating,.fa-skating{--fa:"\f7c5"}.fa-person-skiing,.fa-skiing{--fa:"\f7c9"}.fa-person-skiing-nordic,.fa-skiing-nordic{--fa:"\f7ca"}.fa-sleigh{--fa:"\f7cc"}.fa-comment-sms,.fa-sms{--fa:"\f7cd"}.fa-person-snowboarding,.fa-snowboarding{--fa:"\f7ce"}.fa-snowman{--fa:"\f7d0"}.fa-snowplow{--fa:"\f7d2"}.fa-tenge,.fa-tenge-sign{--fa:"\f7d7"}.fa-toilet{--fa:"\f7d8"}.fa-screwdriver-wrench,.fa-tools{--fa:"\f7d9"}.fa-cable-car,.fa-tram{--fa:"\f7da"}.fa-fire-alt,.fa-fire-flame-curved{--fa:"\f7e4"}.fa-bacon{--fa:"\f7e5"}.fa-book-medical{--fa:"\f7e6"}.fa-bread-slice{--fa:"\f7ec"}.fa-cheese{--fa:"\f7ef"}.fa-clinic-medical,.fa-house-chimney-medical{--fa:"\f7f2"}.fa-clipboard-user{--fa:"\f7f3"}.fa-comment-medical{--fa:"\f7f5"}.fa-crutch{--fa:"\f7f7"}.fa-disease{--fa:"\f7fa"}.fa-egg{--fa:"\f7fb"}.fa-folder-tree{--fa:"\f802"}.fa-burger,.fa-hamburger{--fa:"\f805"}.fa-hand-middle-finger{--fa:"\f806"}.fa-hard-hat,.fa-hat-hard,.fa-helmet-safety{--fa:"\f807"}.fa-hospital-user{--fa:"\f80d"}.fa-hotdog{--fa:"\f80f"}.fa-ice-cream{--fa:"\f810"}.fa-laptop-medical{--fa:"\f812"}.fa-pager{--fa:"\f815"}.fa-pepper-hot{--fa:"\f816"}.fa-pizza-slice{--fa:"\f818"}.fa-sack-dollar{--fa:"\f81d"}.fa-book-tanakh,.fa-tanakh{--fa:"\f827"}.fa-bars-progress,.fa-tasks-alt{--fa:"\f828"}.fa-trash-arrow-up,.fa-trash-restore{--fa:"\f829"}.fa-trash-can-arrow-up,.fa-trash-restore-alt{--fa:"\f82a"}.fa-user-nurse{--fa:"\f82f"}.fa-wave-square{--fa:"\f83e"}.fa-biking,.fa-person-biking{--fa:"\f84a"}.fa-border-all{--fa:"\f84c"}.fa-border-none{--fa:"\f850"}.fa-border-style,.fa-border-top-left{--fa:"\f853"}.fa-digging,.fa-person-digging{--fa:"\f85e"}.fa-fan{--fa:"\f863"}.fa-heart-music-camera-bolt,.fa-icons{--fa:"\f86d"}.fa-phone-alt,.fa-phone-flip{--fa:"\f879"}.fa-phone-square-alt,.fa-square-phone-flip{--fa:"\f87b"}.fa-photo-film,.fa-photo-video{--fa:"\f87c"}.fa-remove-format,.fa-text-slash{--fa:"\f87d"}.fa-arrow-down-z-a,.fa-sort-alpha-desc,.fa-sort-alpha-down-alt{--fa:"\f881"}.fa-arrow-up-z-a,.fa-sort-alpha-up-alt{--fa:"\f882"}.fa-arrow-down-short-wide,.fa-sort-amount-desc,.fa-sort-amount-down-alt{--fa:"\f884"}.fa-arrow-up-short-wide,.fa-sort-amount-up-alt{--fa:"\f885"}.fa-arrow-down-9-1,.fa-sort-numeric-desc,.fa-sort-numeric-down-alt{--fa:"\f886"}.fa-arrow-up-9-1,.fa-sort-numeric-up-alt{--fa:"\f887"}.fa-spell-check{--fa:"\f891"}.fa-voicemail{--fa:"\f897"}.fa-hat-cowboy{--fa:"\f8c0"}.fa-hat-cowboy-side{--fa:"\f8c1"}.fa-computer-mouse,.fa-mouse{--fa:"\f8cc"}.fa-radio{--fa:"\f8d7"}.fa-record-vinyl{--fa:"\f8d9"}.fa-walkie-talkie{--fa:"\f8ef"}.fa-caravan{--fa:"\f8ff"} +/*! + * Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2025 Fonticons, Inc. + */ +.fa,.fa-brands,.fa-classic,.fa-regular,.fa-solid,.fab,.far,.fas{--_fa-family:var(--fa-family,var(--fa-style-family,"Font Awesome 7 Free"));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:var(--fa-display,inline-block);font-family:var(--_fa-family);font-feature-settings:normal;font-style:normal;font-synthesis:none;font-variant:normal;font-weight:var(--fa-style,900);line-height:1;text-align:center;text-rendering:auto;width:var(--fa-width,1.25em)}:is(.fas,.far,.fab,.fa-solid,.fa-regular,.fa-brands,.fa-classic,.fa):before{content:var(--fa);content:var(--fa)/""}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-width-auto{--fa-width:auto}.fa-fw,.fa-width-fixed{--fa-width:1.25em}.fa-ul{list-style-type:none;margin-inline-start:var(--fa-li-margin,2.5em);padding-inline-start:0}.fa-ul>li{position:relative}.fa-li{inset-inline-start:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.0625em) var(--fa-border-style,solid) var(--fa-border-color,#eee);box-sizing:var(--fa-border-box-sizing,content-box);padding:var(--fa-border-padding,.1875em .25em)}.fa-pull-left,.fa-pull-start{float:inline-start;margin-inline-end:var(--fa-pull-margin,.3em)}.fa-pull-end,.fa-pull-right{float:inline-end;margin-inline-start:var(--fa-pull-margin,.3em)}.fa-beat{animation-name:fa-beat;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{animation-name:fa-bounce;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{animation-name:fa-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{animation-name:fa-beat-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{animation-name:fa-flip;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{animation-name:fa-shake;animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{animation-name:fa-spin;animation-duration:var(--fa-animation-duration,2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{animation-name:fa-spin;animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{animation:none!important;transition:none!important}}@keyframes fa-beat{0%,90%{transform:scale(1)}45%{transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-bounce{0%{transform:scale(1) translateY(0)}10%{transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{transform:scale(1) translateY(0)}to{transform:scale(1) translateY(0)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);transform:scale(1)}50%{opacity:1;transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-flip{50%{transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-shake{0%{transform:rotate(-15deg)}4%{transform:rotate(15deg)}8%,24%{transform:rotate(-18deg)}12%,28%{transform:rotate(18deg)}16%{transform:rotate(-22deg)}20%{transform:rotate(22deg)}32%{transform:rotate(-12deg)}36%{transform:rotate(12deg)}40%,to{transform:rotate(0deg)}}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}.fa-rotate-by{transform:rotate(var(--fa-rotate-angle,0))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} + +.fa-0{--fa:"\30 "}.fa-1{--fa:"\31 "}.fa-2{--fa:"\32 "}.fa-3{--fa:"\33 "}.fa-4{--fa:"\34 "}.fa-5{--fa:"\35 "}.fa-6{--fa:"\36 "}.fa-7{--fa:"\37 "}.fa-8{--fa:"\38 "}.fa-9{--fa:"\39 "}.fa-exclamation{--fa:"\!"}.fa-hashtag{--fa:"\#"}.fa-dollar,.fa-dollar-sign,.fa-usd{--fa:"\$"}.fa-percent,.fa-percentage{--fa:"\%"}.fa-asterisk{--fa:"\*"}.fa-add,.fa-plus{--fa:"\+"}.fa-less-than{--fa:"\<"}.fa-equals{--fa:"\="}.fa-greater-than{--fa:"\>"}.fa-question{--fa:"\?"}.fa-at{--fa:"\@"}.fa-a{--fa:"A"}.fa-b{--fa:"B"}.fa-c{--fa:"C"}.fa-d{--fa:"D"}.fa-e{--fa:"E"}.fa-f{--fa:"F"}.fa-g{--fa:"G"}.fa-h{--fa:"H"}.fa-i{--fa:"I"}.fa-j{--fa:"J"}.fa-k{--fa:"K"}.fa-l{--fa:"L"}.fa-m{--fa:"M"}.fa-n{--fa:"N"}.fa-o{--fa:"O"}.fa-p{--fa:"P"}.fa-q{--fa:"Q"}.fa-r{--fa:"R"}.fa-s{--fa:"S"}.fa-t{--fa:"T"}.fa-u{--fa:"U"}.fa-v{--fa:"V"}.fa-w{--fa:"W"}.fa-x{--fa:"X"}.fa-y{--fa:"Y"}.fa-z{--fa:"Z"}.fa-faucet{--fa:"\e005"}.fa-faucet-drip{--fa:"\e006"}.fa-house-chimney-window{--fa:"\e00d"}.fa-house-signal{--fa:"\e012"}.fa-temperature-arrow-down,.fa-temperature-down{--fa:"\e03f"}.fa-temperature-arrow-up,.fa-temperature-up{--fa:"\e040"}.fa-trailer{--fa:"\e041"}.fa-bacteria{--fa:"\e059"}.fa-bacterium{--fa:"\e05a"}.fa-box-tissue{--fa:"\e05b"}.fa-hand-holding-medical{--fa:"\e05c"}.fa-hand-sparkles{--fa:"\e05d"}.fa-hands-bubbles,.fa-hands-wash{--fa:"\e05e"}.fa-handshake-alt-slash,.fa-handshake-simple-slash,.fa-handshake-slash{--fa:"\e060"}.fa-head-side-cough{--fa:"\e061"}.fa-head-side-cough-slash{--fa:"\e062"}.fa-head-side-mask{--fa:"\e063"}.fa-head-side-virus{--fa:"\e064"}.fa-house-chimney-user{--fa:"\e065"}.fa-house-laptop,.fa-laptop-house{--fa:"\e066"}.fa-lungs-virus{--fa:"\e067"}.fa-people-arrows,.fa-people-arrows-left-right{--fa:"\e068"}.fa-plane-slash{--fa:"\e069"}.fa-pump-medical{--fa:"\e06a"}.fa-pump-soap{--fa:"\e06b"}.fa-shield-virus{--fa:"\e06c"}.fa-sink{--fa:"\e06d"}.fa-soap{--fa:"\e06e"}.fa-stopwatch-20{--fa:"\e06f"}.fa-shop-slash,.fa-store-alt-slash{--fa:"\e070"}.fa-store-slash{--fa:"\e071"}.fa-toilet-paper-slash{--fa:"\e072"}.fa-users-slash{--fa:"\e073"}.fa-virus{--fa:"\e074"}.fa-virus-slash{--fa:"\e075"}.fa-viruses{--fa:"\e076"}.fa-vest{--fa:"\e085"}.fa-vest-patches{--fa:"\e086"}.fa-arrow-trend-down{--fa:"\e097"}.fa-arrow-trend-up{--fa:"\e098"}.fa-arrow-up-from-bracket{--fa:"\e09a"}.fa-austral-sign{--fa:"\e0a9"}.fa-baht-sign{--fa:"\e0ac"}.fa-bitcoin-sign{--fa:"\e0b4"}.fa-bolt-lightning{--fa:"\e0b7"}.fa-book-bookmark{--fa:"\e0bb"}.fa-camera-rotate{--fa:"\e0d8"}.fa-cedi-sign{--fa:"\e0df"}.fa-chart-column{--fa:"\e0e3"}.fa-chart-gantt{--fa:"\e0e4"}.fa-clapperboard{--fa:"\e131"}.fa-clover{--fa:"\e139"}.fa-code-compare{--fa:"\e13a"}.fa-code-fork{--fa:"\e13b"}.fa-code-pull-request{--fa:"\e13c"}.fa-colon-sign{--fa:"\e140"}.fa-cruzeiro-sign{--fa:"\e152"}.fa-display{--fa:"\e163"}.fa-dong-sign{--fa:"\e169"}.fa-elevator{--fa:"\e16d"}.fa-filter-circle-xmark{--fa:"\e17b"}.fa-florin-sign{--fa:"\e184"}.fa-folder-closed{--fa:"\e185"}.fa-franc-sign{--fa:"\e18f"}.fa-guarani-sign{--fa:"\e19a"}.fa-gun{--fa:"\e19b"}.fa-hands-clapping{--fa:"\e1a8"}.fa-home-user,.fa-house-user{--fa:"\e1b0"}.fa-indian-rupee,.fa-indian-rupee-sign,.fa-inr{--fa:"\e1bc"}.fa-kip-sign{--fa:"\e1c4"}.fa-lari-sign{--fa:"\e1c8"}.fa-litecoin-sign{--fa:"\e1d3"}.fa-manat-sign{--fa:"\e1d5"}.fa-mask-face{--fa:"\e1d7"}.fa-mill-sign{--fa:"\e1ed"}.fa-money-bills{--fa:"\e1f3"}.fa-naira-sign{--fa:"\e1f6"}.fa-notdef{--fa:"\e1fe"}.fa-panorama{--fa:"\e209"}.fa-peseta-sign{--fa:"\e221"}.fa-peso-sign{--fa:"\e222"}.fa-plane-up{--fa:"\e22d"}.fa-rupiah-sign{--fa:"\e23d"}.fa-stairs{--fa:"\e289"}.fa-timeline{--fa:"\e29c"}.fa-truck-front{--fa:"\e2b7"}.fa-try,.fa-turkish-lira,.fa-turkish-lira-sign{--fa:"\e2bb"}.fa-vault{--fa:"\e2c5"}.fa-magic-wand-sparkles,.fa-wand-magic-sparkles{--fa:"\e2ca"}.fa-wheat-alt,.fa-wheat-awn{--fa:"\e2cd"}.fa-wheelchair-alt,.fa-wheelchair-move{--fa:"\e2ce"}.fa-bangladeshi-taka-sign{--fa:"\e2e6"}.fa-bowl-rice{--fa:"\e2eb"}.fa-person-pregnant{--fa:"\e31e"}.fa-home-lg,.fa-house-chimney{--fa:"\e3af"}.fa-house-crack{--fa:"\e3b1"}.fa-house-medical{--fa:"\e3b2"}.fa-cent-sign{--fa:"\e3f5"}.fa-plus-minus{--fa:"\e43c"}.fa-sailboat{--fa:"\e445"}.fa-section{--fa:"\e447"}.fa-shrimp{--fa:"\e448"}.fa-brazilian-real-sign{--fa:"\e46c"}.fa-chart-simple{--fa:"\e473"}.fa-diagram-next{--fa:"\e476"}.fa-diagram-predecessor{--fa:"\e477"}.fa-diagram-successor{--fa:"\e47a"}.fa-earth-oceania,.fa-globe-oceania{--fa:"\e47b"}.fa-bug-slash{--fa:"\e490"}.fa-file-circle-plus{--fa:"\e494"}.fa-shop-lock{--fa:"\e4a5"}.fa-virus-covid{--fa:"\e4a8"}.fa-virus-covid-slash{--fa:"\e4a9"}.fa-anchor-circle-check{--fa:"\e4aa"}.fa-anchor-circle-exclamation{--fa:"\e4ab"}.fa-anchor-circle-xmark{--fa:"\e4ac"}.fa-anchor-lock{--fa:"\e4ad"}.fa-arrow-down-up-across-line{--fa:"\e4af"}.fa-arrow-down-up-lock{--fa:"\e4b0"}.fa-arrow-right-to-city{--fa:"\e4b3"}.fa-arrow-up-from-ground-water{--fa:"\e4b5"}.fa-arrow-up-from-water-pump{--fa:"\e4b6"}.fa-arrow-up-right-dots{--fa:"\e4b7"}.fa-arrows-down-to-line{--fa:"\e4b8"}.fa-arrows-down-to-people{--fa:"\e4b9"}.fa-arrows-left-right-to-line{--fa:"\e4ba"}.fa-arrows-spin{--fa:"\e4bb"}.fa-arrows-split-up-and-left{--fa:"\e4bc"}.fa-arrows-to-circle{--fa:"\e4bd"}.fa-arrows-to-dot{--fa:"\e4be"}.fa-arrows-to-eye{--fa:"\e4bf"}.fa-arrows-turn-right{--fa:"\e4c0"}.fa-arrows-turn-to-dots{--fa:"\e4c1"}.fa-arrows-up-to-line{--fa:"\e4c2"}.fa-bore-hole{--fa:"\e4c3"}.fa-bottle-droplet{--fa:"\e4c4"}.fa-bottle-water{--fa:"\e4c5"}.fa-bowl-food{--fa:"\e4c6"}.fa-boxes-packing{--fa:"\e4c7"}.fa-bridge{--fa:"\e4c8"}.fa-bridge-circle-check{--fa:"\e4c9"}.fa-bridge-circle-exclamation{--fa:"\e4ca"}.fa-bridge-circle-xmark{--fa:"\e4cb"}.fa-bridge-lock{--fa:"\e4cc"}.fa-bridge-water{--fa:"\e4ce"}.fa-bucket{--fa:"\e4cf"}.fa-bugs{--fa:"\e4d0"}.fa-building-circle-arrow-right{--fa:"\e4d1"}.fa-building-circle-check{--fa:"\e4d2"}.fa-building-circle-exclamation{--fa:"\e4d3"}.fa-building-circle-xmark{--fa:"\e4d4"}.fa-building-flag{--fa:"\e4d5"}.fa-building-lock{--fa:"\e4d6"}.fa-building-ngo{--fa:"\e4d7"}.fa-building-shield{--fa:"\e4d8"}.fa-building-un{--fa:"\e4d9"}.fa-building-user{--fa:"\e4da"}.fa-building-wheat{--fa:"\e4db"}.fa-burst{--fa:"\e4dc"}.fa-car-on{--fa:"\e4dd"}.fa-car-tunnel{--fa:"\e4de"}.fa-child-combatant,.fa-child-rifle{--fa:"\e4e0"}.fa-children{--fa:"\e4e1"}.fa-circle-nodes{--fa:"\e4e2"}.fa-clipboard-question{--fa:"\e4e3"}.fa-cloud-showers-water{--fa:"\e4e4"}.fa-computer{--fa:"\e4e5"}.fa-cubes-stacked{--fa:"\e4e6"}.fa-envelope-circle-check{--fa:"\e4e8"}.fa-explosion{--fa:"\e4e9"}.fa-ferry{--fa:"\e4ea"}.fa-file-circle-exclamation{--fa:"\e4eb"}.fa-file-circle-minus{--fa:"\e4ed"}.fa-file-circle-question{--fa:"\e4ef"}.fa-file-shield{--fa:"\e4f0"}.fa-fire-burner{--fa:"\e4f1"}.fa-fish-fins{--fa:"\e4f2"}.fa-flask-vial{--fa:"\e4f3"}.fa-glass-water{--fa:"\e4f4"}.fa-glass-water-droplet{--fa:"\e4f5"}.fa-group-arrows-rotate{--fa:"\e4f6"}.fa-hand-holding-hand{--fa:"\e4f7"}.fa-handcuffs{--fa:"\e4f8"}.fa-hands-bound{--fa:"\e4f9"}.fa-hands-holding-child{--fa:"\e4fa"}.fa-hands-holding-circle{--fa:"\e4fb"}.fa-heart-circle-bolt{--fa:"\e4fc"}.fa-heart-circle-check{--fa:"\e4fd"}.fa-heart-circle-exclamation{--fa:"\e4fe"}.fa-heart-circle-minus{--fa:"\e4ff"}.fa-heart-circle-plus{--fa:"\e500"}.fa-heart-circle-xmark{--fa:"\e501"}.fa-helicopter-symbol{--fa:"\e502"}.fa-helmet-un{--fa:"\e503"}.fa-hill-avalanche{--fa:"\e507"}.fa-hill-rockslide{--fa:"\e508"}.fa-house-circle-check{--fa:"\e509"}.fa-house-circle-exclamation{--fa:"\e50a"}.fa-house-circle-xmark{--fa:"\e50b"}.fa-house-fire{--fa:"\e50c"}.fa-house-flag{--fa:"\e50d"}.fa-house-flood-water{--fa:"\e50e"}.fa-house-flood-water-circle-arrow-right{--fa:"\e50f"}.fa-house-lock{--fa:"\e510"}.fa-house-medical-circle-check{--fa:"\e511"}.fa-house-medical-circle-exclamation{--fa:"\e512"}.fa-house-medical-circle-xmark{--fa:"\e513"}.fa-house-medical-flag{--fa:"\e514"}.fa-house-tsunami{--fa:"\e515"}.fa-jar{--fa:"\e516"}.fa-jar-wheat{--fa:"\e517"}.fa-jet-fighter-up{--fa:"\e518"}.fa-jug-detergent{--fa:"\e519"}.fa-kitchen-set{--fa:"\e51a"}.fa-land-mine-on{--fa:"\e51b"}.fa-landmark-flag{--fa:"\e51c"}.fa-laptop-file{--fa:"\e51d"}.fa-lines-leaning{--fa:"\e51e"}.fa-location-pin-lock{--fa:"\e51f"}.fa-locust{--fa:"\e520"}.fa-magnifying-glass-arrow-right{--fa:"\e521"}.fa-magnifying-glass-chart{--fa:"\e522"}.fa-mars-and-venus-burst{--fa:"\e523"}.fa-mask-ventilator{--fa:"\e524"}.fa-mattress-pillow{--fa:"\e525"}.fa-mobile-retro{--fa:"\e527"}.fa-money-bill-transfer{--fa:"\e528"}.fa-money-bill-trend-up{--fa:"\e529"}.fa-money-bill-wheat{--fa:"\e52a"}.fa-mosquito{--fa:"\e52b"}.fa-mosquito-net{--fa:"\e52c"}.fa-mound{--fa:"\e52d"}.fa-mountain-city{--fa:"\e52e"}.fa-mountain-sun{--fa:"\e52f"}.fa-oil-well{--fa:"\e532"}.fa-people-group{--fa:"\e533"}.fa-people-line{--fa:"\e534"}.fa-people-pulling{--fa:"\e535"}.fa-people-robbery{--fa:"\e536"}.fa-people-roof{--fa:"\e537"}.fa-person-arrow-down-to-line{--fa:"\e538"}.fa-person-arrow-up-from-line{--fa:"\e539"}.fa-person-breastfeeding{--fa:"\e53a"}.fa-person-burst{--fa:"\e53b"}.fa-person-cane{--fa:"\e53c"}.fa-person-chalkboard{--fa:"\e53d"}.fa-person-circle-check{--fa:"\e53e"}.fa-person-circle-exclamation{--fa:"\e53f"}.fa-person-circle-minus{--fa:"\e540"}.fa-person-circle-plus{--fa:"\e541"}.fa-person-circle-question{--fa:"\e542"}.fa-person-circle-xmark{--fa:"\e543"}.fa-person-dress-burst{--fa:"\e544"}.fa-person-drowning{--fa:"\e545"}.fa-person-falling{--fa:"\e546"}.fa-person-falling-burst{--fa:"\e547"}.fa-person-half-dress{--fa:"\e548"}.fa-person-harassing{--fa:"\e549"}.fa-person-military-pointing{--fa:"\e54a"}.fa-person-military-rifle{--fa:"\e54b"}.fa-person-military-to-person{--fa:"\e54c"}.fa-person-rays{--fa:"\e54d"}.fa-person-rifle{--fa:"\e54e"}.fa-person-shelter{--fa:"\e54f"}.fa-person-walking-arrow-loop-left{--fa:"\e551"}.fa-person-walking-arrow-right{--fa:"\e552"}.fa-person-walking-dashed-line-arrow-right{--fa:"\e553"}.fa-person-walking-luggage{--fa:"\e554"}.fa-plane-circle-check{--fa:"\e555"}.fa-plane-circle-exclamation{--fa:"\e556"}.fa-plane-circle-xmark{--fa:"\e557"}.fa-plane-lock{--fa:"\e558"}.fa-plate-wheat{--fa:"\e55a"}.fa-plug-circle-bolt{--fa:"\e55b"}.fa-plug-circle-check{--fa:"\e55c"}.fa-plug-circle-exclamation{--fa:"\e55d"}.fa-plug-circle-minus{--fa:"\e55e"}.fa-plug-circle-plus{--fa:"\e55f"}.fa-plug-circle-xmark{--fa:"\e560"}.fa-ranking-star{--fa:"\e561"}.fa-road-barrier{--fa:"\e562"}.fa-road-bridge{--fa:"\e563"}.fa-road-circle-check{--fa:"\e564"}.fa-road-circle-exclamation{--fa:"\e565"}.fa-road-circle-xmark{--fa:"\e566"}.fa-road-lock{--fa:"\e567"}.fa-road-spikes{--fa:"\e568"}.fa-rug{--fa:"\e569"}.fa-sack-xmark{--fa:"\e56a"}.fa-school-circle-check{--fa:"\e56b"}.fa-school-circle-exclamation{--fa:"\e56c"}.fa-school-circle-xmark{--fa:"\e56d"}.fa-school-flag{--fa:"\e56e"}.fa-school-lock{--fa:"\e56f"}.fa-sheet-plastic{--fa:"\e571"}.fa-shield-cat{--fa:"\e572"}.fa-shield-dog{--fa:"\e573"}.fa-shield-heart{--fa:"\e574"}.fa-square-nfi{--fa:"\e576"}.fa-square-person-confined{--fa:"\e577"}.fa-square-virus{--fa:"\e578"}.fa-rod-asclepius,.fa-rod-snake,.fa-staff-aesculapius,.fa-staff-snake{--fa:"\e579"}.fa-sun-plant-wilt{--fa:"\e57a"}.fa-tarp{--fa:"\e57b"}.fa-tarp-droplet{--fa:"\e57c"}.fa-tent{--fa:"\e57d"}.fa-tent-arrow-down-to-line{--fa:"\e57e"}.fa-tent-arrow-left-right{--fa:"\e57f"}.fa-tent-arrow-turn-left{--fa:"\e580"}.fa-tent-arrows-down{--fa:"\e581"}.fa-tents{--fa:"\e582"}.fa-toilet-portable{--fa:"\e583"}.fa-toilets-portable{--fa:"\e584"}.fa-tower-cell{--fa:"\e585"}.fa-tower-observation{--fa:"\e586"}.fa-tree-city{--fa:"\e587"}.fa-trowel{--fa:"\e589"}.fa-trowel-bricks{--fa:"\e58a"}.fa-truck-arrow-right{--fa:"\e58b"}.fa-truck-droplet{--fa:"\e58c"}.fa-truck-field{--fa:"\e58d"}.fa-truck-field-un{--fa:"\e58e"}.fa-truck-plane{--fa:"\e58f"}.fa-users-between-lines{--fa:"\e591"}.fa-users-line{--fa:"\e592"}.fa-users-rays{--fa:"\e593"}.fa-users-rectangle{--fa:"\e594"}.fa-users-viewfinder{--fa:"\e595"}.fa-vial-circle-check{--fa:"\e596"}.fa-vial-virus{--fa:"\e597"}.fa-wheat-awn-circle-exclamation{--fa:"\e598"}.fa-worm{--fa:"\e599"}.fa-xmarks-lines{--fa:"\e59a"}.fa-child-dress{--fa:"\e59c"}.fa-child-reaching{--fa:"\e59d"}.fa-file-circle-check{--fa:"\e5a0"}.fa-file-circle-xmark{--fa:"\e5a1"}.fa-person-through-window{--fa:"\e5a9"}.fa-plant-wilt{--fa:"\e5aa"}.fa-stapler{--fa:"\e5af"}.fa-train-tram{--fa:"\e5b4"}.fa-table-cells-column-lock{--fa:"\e678"}.fa-table-cells-row-lock{--fa:"\e67a"}.fa-thumb-tack-slash,.fa-thumbtack-slash{--fa:"\e68f"}.fa-table-cells-row-unlock{--fa:"\e691"}.fa-chart-diagram{--fa:"\e695"}.fa-comment-nodes{--fa:"\e696"}.fa-file-fragment{--fa:"\e697"}.fa-file-half-dashed{--fa:"\e698"}.fa-hexagon-nodes{--fa:"\e699"}.fa-hexagon-nodes-bolt{--fa:"\e69a"}.fa-square-binary{--fa:"\e69b"}.fa-pentagon{--fa:"\e790"}.fa-non-binary{--fa:"\e807"}.fa-spiral{--fa:"\e80a"}.fa-mobile-vibrate{--fa:"\e816"}.fa-single-quote-left{--fa:"\e81b"}.fa-single-quote-right{--fa:"\e81c"}.fa-bus-side{--fa:"\e81d"}.fa-heptagon,.fa-septagon{--fa:"\e820"}.fa-glass-martini,.fa-martini-glass-empty{--fa:"\f000"}.fa-music{--fa:"\f001"}.fa-magnifying-glass,.fa-search{--fa:"\f002"}.fa-heart{--fa:"\f004"}.fa-star{--fa:"\f005"}.fa-user,.fa-user-alt,.fa-user-large{--fa:"\f007"}.fa-film,.fa-film-alt,.fa-film-simple{--fa:"\f008"}.fa-table-cells-large,.fa-th-large{--fa:"\f009"}.fa-table-cells,.fa-th{--fa:"\f00a"}.fa-table-list,.fa-th-list{--fa:"\f00b"}.fa-check{--fa:"\f00c"}.fa-close,.fa-multiply,.fa-remove,.fa-times,.fa-xmark{--fa:"\f00d"}.fa-magnifying-glass-plus,.fa-search-plus{--fa:"\f00e"}.fa-magnifying-glass-minus,.fa-search-minus{--fa:"\f010"}.fa-power-off{--fa:"\f011"}.fa-signal,.fa-signal-5,.fa-signal-perfect{--fa:"\f012"}.fa-cog,.fa-gear{--fa:"\f013"}.fa-home,.fa-home-alt,.fa-home-lg-alt,.fa-house{--fa:"\f015"}.fa-clock,.fa-clock-four{--fa:"\f017"}.fa-road{--fa:"\f018"}.fa-download{--fa:"\f019"}.fa-inbox{--fa:"\f01c"}.fa-arrow-right-rotate,.fa-arrow-rotate-forward,.fa-arrow-rotate-right,.fa-redo{--fa:"\f01e"}.fa-arrows-rotate,.fa-refresh,.fa-sync{--fa:"\f021"}.fa-list-alt,.fa-rectangle-list{--fa:"\f022"}.fa-lock{--fa:"\f023"}.fa-flag{--fa:"\f024"}.fa-headphones,.fa-headphones-alt,.fa-headphones-simple{--fa:"\f025"}.fa-volume-off{--fa:"\f026"}.fa-volume-down,.fa-volume-low{--fa:"\f027"}.fa-volume-high,.fa-volume-up{--fa:"\f028"}.fa-qrcode{--fa:"\f029"}.fa-barcode{--fa:"\f02a"}.fa-tag{--fa:"\f02b"}.fa-tags{--fa:"\f02c"}.fa-book{--fa:"\f02d"}.fa-bookmark{--fa:"\f02e"}.fa-print{--fa:"\f02f"}.fa-camera,.fa-camera-alt{--fa:"\f030"}.fa-font{--fa:"\f031"}.fa-bold{--fa:"\f032"}.fa-italic{--fa:"\f033"}.fa-text-height{--fa:"\f034"}.fa-text-width{--fa:"\f035"}.fa-align-left{--fa:"\f036"}.fa-align-center{--fa:"\f037"}.fa-align-right{--fa:"\f038"}.fa-align-justify{--fa:"\f039"}.fa-list,.fa-list-squares{--fa:"\f03a"}.fa-dedent,.fa-outdent{--fa:"\f03b"}.fa-indent{--fa:"\f03c"}.fa-video,.fa-video-camera{--fa:"\f03d"}.fa-image{--fa:"\f03e"}.fa-location-pin,.fa-map-marker{--fa:"\f041"}.fa-adjust,.fa-circle-half-stroke{--fa:"\f042"}.fa-droplet,.fa-tint{--fa:"\f043"}.fa-edit,.fa-pen-to-square{--fa:"\f044"}.fa-arrows,.fa-arrows-up-down-left-right{--fa:"\f047"}.fa-backward-step,.fa-step-backward{--fa:"\f048"}.fa-backward-fast,.fa-fast-backward{--fa:"\f049"}.fa-backward{--fa:"\f04a"}.fa-play{--fa:"\f04b"}.fa-pause{--fa:"\f04c"}.fa-stop{--fa:"\f04d"}.fa-forward{--fa:"\f04e"}.fa-fast-forward,.fa-forward-fast{--fa:"\f050"}.fa-forward-step,.fa-step-forward{--fa:"\f051"}.fa-eject{--fa:"\f052"}.fa-chevron-left{--fa:"\f053"}.fa-chevron-right{--fa:"\f054"}.fa-circle-plus,.fa-plus-circle{--fa:"\f055"}.fa-circle-minus,.fa-minus-circle{--fa:"\f056"}.fa-circle-xmark,.fa-times-circle,.fa-xmark-circle{--fa:"\f057"}.fa-check-circle,.fa-circle-check{--fa:"\f058"}.fa-circle-question,.fa-question-circle{--fa:"\f059"}.fa-circle-info,.fa-info-circle{--fa:"\f05a"}.fa-crosshairs{--fa:"\f05b"}.fa-ban,.fa-cancel{--fa:"\f05e"}.fa-arrow-left{--fa:"\f060"}.fa-arrow-right{--fa:"\f061"}.fa-arrow-up{--fa:"\f062"}.fa-arrow-down{--fa:"\f063"}.fa-mail-forward,.fa-share{--fa:"\f064"}.fa-expand{--fa:"\f065"}.fa-compress{--fa:"\f066"}.fa-minus,.fa-subtract{--fa:"\f068"}.fa-circle-exclamation,.fa-exclamation-circle{--fa:"\f06a"}.fa-gift{--fa:"\f06b"}.fa-leaf{--fa:"\f06c"}.fa-fire{--fa:"\f06d"}.fa-eye{--fa:"\f06e"}.fa-eye-slash{--fa:"\f070"}.fa-exclamation-triangle,.fa-triangle-exclamation,.fa-warning{--fa:"\f071"}.fa-plane{--fa:"\f072"}.fa-calendar-alt,.fa-calendar-days{--fa:"\f073"}.fa-random,.fa-shuffle{--fa:"\f074"}.fa-comment{--fa:"\f075"}.fa-magnet{--fa:"\f076"}.fa-chevron-up{--fa:"\f077"}.fa-chevron-down{--fa:"\f078"}.fa-retweet{--fa:"\f079"}.fa-cart-shopping,.fa-shopping-cart{--fa:"\f07a"}.fa-folder,.fa-folder-blank{--fa:"\f07b"}.fa-folder-open{--fa:"\f07c"}.fa-arrows-up-down,.fa-arrows-v{--fa:"\f07d"}.fa-arrows-h,.fa-arrows-left-right{--fa:"\f07e"}.fa-bar-chart,.fa-chart-bar{--fa:"\f080"}.fa-camera-retro{--fa:"\f083"}.fa-key{--fa:"\f084"}.fa-cogs,.fa-gears{--fa:"\f085"}.fa-comments{--fa:"\f086"}.fa-star-half{--fa:"\f089"}.fa-arrow-right-from-bracket,.fa-sign-out{--fa:"\f08b"}.fa-thumb-tack,.fa-thumbtack{--fa:"\f08d"}.fa-arrow-up-right-from-square,.fa-external-link{--fa:"\f08e"}.fa-arrow-right-to-bracket,.fa-sign-in{--fa:"\f090"}.fa-trophy{--fa:"\f091"}.fa-upload{--fa:"\f093"}.fa-lemon{--fa:"\f094"}.fa-phone{--fa:"\f095"}.fa-phone-square,.fa-square-phone{--fa:"\f098"}.fa-unlock{--fa:"\f09c"}.fa-credit-card,.fa-credit-card-alt{--fa:"\f09d"}.fa-feed,.fa-rss{--fa:"\f09e"}.fa-hard-drive,.fa-hdd{--fa:"\f0a0"}.fa-bullhorn{--fa:"\f0a1"}.fa-certificate{--fa:"\f0a3"}.fa-hand-point-right{--fa:"\f0a4"}.fa-hand-point-left{--fa:"\f0a5"}.fa-hand-point-up{--fa:"\f0a6"}.fa-hand-point-down{--fa:"\f0a7"}.fa-arrow-circle-left,.fa-circle-arrow-left{--fa:"\f0a8"}.fa-arrow-circle-right,.fa-circle-arrow-right{--fa:"\f0a9"}.fa-arrow-circle-up,.fa-circle-arrow-up{--fa:"\f0aa"}.fa-arrow-circle-down,.fa-circle-arrow-down{--fa:"\f0ab"}.fa-globe{--fa:"\f0ac"}.fa-wrench{--fa:"\f0ad"}.fa-list-check,.fa-tasks{--fa:"\f0ae"}.fa-filter{--fa:"\f0b0"}.fa-briefcase{--fa:"\f0b1"}.fa-arrows-alt,.fa-up-down-left-right{--fa:"\f0b2"}.fa-users{--fa:"\f0c0"}.fa-chain,.fa-link{--fa:"\f0c1"}.fa-cloud{--fa:"\f0c2"}.fa-flask{--fa:"\f0c3"}.fa-cut,.fa-scissors{--fa:"\f0c4"}.fa-copy{--fa:"\f0c5"}.fa-paperclip{--fa:"\f0c6"}.fa-floppy-disk,.fa-save{--fa:"\f0c7"}.fa-square{--fa:"\f0c8"}.fa-bars,.fa-navicon{--fa:"\f0c9"}.fa-list-dots,.fa-list-ul{--fa:"\f0ca"}.fa-list-1-2,.fa-list-numeric,.fa-list-ol{--fa:"\f0cb"}.fa-strikethrough{--fa:"\f0cc"}.fa-underline{--fa:"\f0cd"}.fa-table{--fa:"\f0ce"}.fa-magic,.fa-wand-magic{--fa:"\f0d0"}.fa-truck{--fa:"\f0d1"}.fa-money-bill{--fa:"\f0d6"}.fa-caret-down{--fa:"\f0d7"}.fa-caret-up{--fa:"\f0d8"}.fa-caret-left{--fa:"\f0d9"}.fa-caret-right{--fa:"\f0da"}.fa-columns,.fa-table-columns{--fa:"\f0db"}.fa-sort,.fa-unsorted{--fa:"\f0dc"}.fa-sort-desc,.fa-sort-down{--fa:"\f0dd"}.fa-sort-asc,.fa-sort-up{--fa:"\f0de"}.fa-envelope{--fa:"\f0e0"}.fa-arrow-left-rotate,.fa-arrow-rotate-back,.fa-arrow-rotate-backward,.fa-arrow-rotate-left,.fa-undo{--fa:"\f0e2"}.fa-gavel,.fa-legal{--fa:"\f0e3"}.fa-bolt,.fa-zap{--fa:"\f0e7"}.fa-sitemap{--fa:"\f0e8"}.fa-umbrella{--fa:"\f0e9"}.fa-file-clipboard,.fa-paste{--fa:"\f0ea"}.fa-lightbulb{--fa:"\f0eb"}.fa-arrow-right-arrow-left,.fa-exchange{--fa:"\f0ec"}.fa-cloud-arrow-down,.fa-cloud-download,.fa-cloud-download-alt{--fa:"\f0ed"}.fa-cloud-arrow-up,.fa-cloud-upload,.fa-cloud-upload-alt{--fa:"\f0ee"}.fa-user-doctor,.fa-user-md{--fa:"\f0f0"}.fa-stethoscope{--fa:"\f0f1"}.fa-suitcase{--fa:"\f0f2"}.fa-bell{--fa:"\f0f3"}.fa-coffee,.fa-mug-saucer{--fa:"\f0f4"}.fa-hospital,.fa-hospital-alt,.fa-hospital-wide{--fa:"\f0f8"}.fa-ambulance,.fa-truck-medical{--fa:"\f0f9"}.fa-medkit,.fa-suitcase-medical{--fa:"\f0fa"}.fa-fighter-jet,.fa-jet-fighter{--fa:"\f0fb"}.fa-beer,.fa-beer-mug-empty{--fa:"\f0fc"}.fa-h-square,.fa-square-h{--fa:"\f0fd"}.fa-plus-square,.fa-square-plus{--fa:"\f0fe"}.fa-angle-double-left,.fa-angles-left{--fa:"\f100"}.fa-angle-double-right,.fa-angles-right{--fa:"\f101"}.fa-angle-double-up,.fa-angles-up{--fa:"\f102"}.fa-angle-double-down,.fa-angles-down{--fa:"\f103"}.fa-angle-left{--fa:"\f104"}.fa-angle-right{--fa:"\f105"}.fa-angle-up{--fa:"\f106"}.fa-angle-down{--fa:"\f107"}.fa-laptop{--fa:"\f109"}.fa-tablet-button{--fa:"\f10a"}.fa-mobile-button{--fa:"\f10b"}.fa-quote-left,.fa-quote-left-alt{--fa:"\f10d"}.fa-quote-right,.fa-quote-right-alt{--fa:"\f10e"}.fa-spinner{--fa:"\f110"}.fa-circle{--fa:"\f111"}.fa-face-smile,.fa-smile{--fa:"\f118"}.fa-face-frown,.fa-frown{--fa:"\f119"}.fa-face-meh,.fa-meh{--fa:"\f11a"}.fa-gamepad{--fa:"\f11b"}.fa-keyboard{--fa:"\f11c"}.fa-flag-checkered{--fa:"\f11e"}.fa-terminal{--fa:"\f120"}.fa-code{--fa:"\f121"}.fa-mail-reply-all,.fa-reply-all{--fa:"\f122"}.fa-location-arrow{--fa:"\f124"}.fa-crop{--fa:"\f125"}.fa-code-branch{--fa:"\f126"}.fa-chain-broken,.fa-chain-slash,.fa-link-slash,.fa-unlink{--fa:"\f127"}.fa-info{--fa:"\f129"}.fa-superscript{--fa:"\f12b"}.fa-subscript{--fa:"\f12c"}.fa-eraser{--fa:"\f12d"}.fa-puzzle-piece{--fa:"\f12e"}.fa-microphone{--fa:"\f130"}.fa-microphone-slash{--fa:"\f131"}.fa-shield,.fa-shield-blank{--fa:"\f132"}.fa-calendar{--fa:"\f133"}.fa-fire-extinguisher{--fa:"\f134"}.fa-rocket{--fa:"\f135"}.fa-chevron-circle-left,.fa-circle-chevron-left{--fa:"\f137"}.fa-chevron-circle-right,.fa-circle-chevron-right{--fa:"\f138"}.fa-chevron-circle-up,.fa-circle-chevron-up{--fa:"\f139"}.fa-chevron-circle-down,.fa-circle-chevron-down{--fa:"\f13a"}.fa-anchor{--fa:"\f13d"}.fa-unlock-alt,.fa-unlock-keyhole{--fa:"\f13e"}.fa-bullseye{--fa:"\f140"}.fa-ellipsis,.fa-ellipsis-h{--fa:"\f141"}.fa-ellipsis-v,.fa-ellipsis-vertical{--fa:"\f142"}.fa-rss-square,.fa-square-rss{--fa:"\f143"}.fa-circle-play,.fa-play-circle{--fa:"\f144"}.fa-ticket{--fa:"\f145"}.fa-minus-square,.fa-square-minus{--fa:"\f146"}.fa-arrow-turn-up,.fa-level-up{--fa:"\f148"}.fa-arrow-turn-down,.fa-level-down{--fa:"\f149"}.fa-check-square,.fa-square-check{--fa:"\f14a"}.fa-pen-square,.fa-pencil-square,.fa-square-pen{--fa:"\f14b"}.fa-external-link-square,.fa-square-arrow-up-right{--fa:"\f14c"}.fa-share-from-square,.fa-share-square{--fa:"\f14d"}.fa-compass{--fa:"\f14e"}.fa-caret-square-down,.fa-square-caret-down{--fa:"\f150"}.fa-caret-square-up,.fa-square-caret-up{--fa:"\f151"}.fa-caret-square-right,.fa-square-caret-right{--fa:"\f152"}.fa-eur,.fa-euro,.fa-euro-sign{--fa:"\f153"}.fa-gbp,.fa-pound-sign,.fa-sterling-sign{--fa:"\f154"}.fa-rupee,.fa-rupee-sign{--fa:"\f156"}.fa-cny,.fa-jpy,.fa-rmb,.fa-yen,.fa-yen-sign{--fa:"\f157"}.fa-rouble,.fa-rub,.fa-ruble,.fa-ruble-sign{--fa:"\f158"}.fa-krw,.fa-won,.fa-won-sign{--fa:"\f159"}.fa-file{--fa:"\f15b"}.fa-file-alt,.fa-file-lines,.fa-file-text{--fa:"\f15c"}.fa-arrow-down-a-z,.fa-sort-alpha-asc,.fa-sort-alpha-down{--fa:"\f15d"}.fa-arrow-up-a-z,.fa-sort-alpha-up{--fa:"\f15e"}.fa-arrow-down-wide-short,.fa-sort-amount-asc,.fa-sort-amount-down{--fa:"\f160"}.fa-arrow-up-wide-short,.fa-sort-amount-up{--fa:"\f161"}.fa-arrow-down-1-9,.fa-sort-numeric-asc,.fa-sort-numeric-down{--fa:"\f162"}.fa-arrow-up-1-9,.fa-sort-numeric-up{--fa:"\f163"}.fa-thumbs-up{--fa:"\f164"}.fa-thumbs-down{--fa:"\f165"}.fa-arrow-down-long,.fa-long-arrow-down{--fa:"\f175"}.fa-arrow-up-long,.fa-long-arrow-up{--fa:"\f176"}.fa-arrow-left-long,.fa-long-arrow-left{--fa:"\f177"}.fa-arrow-right-long,.fa-long-arrow-right{--fa:"\f178"}.fa-female,.fa-person-dress{--fa:"\f182"}.fa-male,.fa-person{--fa:"\f183"}.fa-sun{--fa:"\f185"}.fa-moon{--fa:"\f186"}.fa-archive,.fa-box-archive{--fa:"\f187"}.fa-bug{--fa:"\f188"}.fa-caret-square-left,.fa-square-caret-left{--fa:"\f191"}.fa-circle-dot,.fa-dot-circle{--fa:"\f192"}.fa-wheelchair{--fa:"\f193"}.fa-lira-sign{--fa:"\f195"}.fa-shuttle-space,.fa-space-shuttle{--fa:"\f197"}.fa-envelope-square,.fa-square-envelope{--fa:"\f199"}.fa-bank,.fa-building-columns,.fa-institution,.fa-museum,.fa-university{--fa:"\f19c"}.fa-graduation-cap,.fa-mortar-board{--fa:"\f19d"}.fa-language{--fa:"\f1ab"}.fa-fax{--fa:"\f1ac"}.fa-building{--fa:"\f1ad"}.fa-child{--fa:"\f1ae"}.fa-paw{--fa:"\f1b0"}.fa-cube{--fa:"\f1b2"}.fa-cubes{--fa:"\f1b3"}.fa-recycle{--fa:"\f1b8"}.fa-automobile,.fa-car{--fa:"\f1b9"}.fa-cab,.fa-taxi{--fa:"\f1ba"}.fa-tree{--fa:"\f1bb"}.fa-database{--fa:"\f1c0"}.fa-file-pdf{--fa:"\f1c1"}.fa-file-word{--fa:"\f1c2"}.fa-file-excel{--fa:"\f1c3"}.fa-file-powerpoint{--fa:"\f1c4"}.fa-file-image{--fa:"\f1c5"}.fa-file-archive,.fa-file-zipper{--fa:"\f1c6"}.fa-file-audio{--fa:"\f1c7"}.fa-file-video{--fa:"\f1c8"}.fa-file-code{--fa:"\f1c9"}.fa-life-ring{--fa:"\f1cd"}.fa-circle-notch{--fa:"\f1ce"}.fa-paper-plane{--fa:"\f1d8"}.fa-clock-rotate-left,.fa-history{--fa:"\f1da"}.fa-header,.fa-heading{--fa:"\f1dc"}.fa-paragraph{--fa:"\f1dd"}.fa-sliders,.fa-sliders-h{--fa:"\f1de"}.fa-share-alt,.fa-share-nodes{--fa:"\f1e0"}.fa-share-alt-square,.fa-square-share-nodes{--fa:"\f1e1"}.fa-bomb{--fa:"\f1e2"}.fa-futbol,.fa-futbol-ball,.fa-soccer-ball{--fa:"\f1e3"}.fa-teletype,.fa-tty{--fa:"\f1e4"}.fa-binoculars{--fa:"\f1e5"}.fa-plug{--fa:"\f1e6"}.fa-newspaper{--fa:"\f1ea"}.fa-wifi,.fa-wifi-3,.fa-wifi-strong{--fa:"\f1eb"}.fa-calculator{--fa:"\f1ec"}.fa-bell-slash{--fa:"\f1f6"}.fa-trash{--fa:"\f1f8"}.fa-copyright{--fa:"\f1f9"}.fa-eye-dropper,.fa-eye-dropper-empty,.fa-eyedropper{--fa:"\f1fb"}.fa-paint-brush,.fa-paintbrush{--fa:"\f1fc"}.fa-birthday-cake,.fa-cake,.fa-cake-candles{--fa:"\f1fd"}.fa-area-chart,.fa-chart-area{--fa:"\f1fe"}.fa-chart-pie,.fa-pie-chart{--fa:"\f200"}.fa-chart-line,.fa-line-chart{--fa:"\f201"}.fa-toggle-off{--fa:"\f204"}.fa-toggle-on{--fa:"\f205"}.fa-bicycle{--fa:"\f206"}.fa-bus{--fa:"\f207"}.fa-closed-captioning{--fa:"\f20a"}.fa-ils,.fa-shekel,.fa-shekel-sign,.fa-sheqel,.fa-sheqel-sign{--fa:"\f20b"}.fa-cart-plus{--fa:"\f217"}.fa-cart-arrow-down{--fa:"\f218"}.fa-diamond{--fa:"\f219"}.fa-ship{--fa:"\f21a"}.fa-user-secret{--fa:"\f21b"}.fa-motorcycle{--fa:"\f21c"}.fa-street-view{--fa:"\f21d"}.fa-heart-pulse,.fa-heartbeat{--fa:"\f21e"}.fa-venus{--fa:"\f221"}.fa-mars{--fa:"\f222"}.fa-mercury{--fa:"\f223"}.fa-mars-and-venus{--fa:"\f224"}.fa-transgender,.fa-transgender-alt{--fa:"\f225"}.fa-venus-double{--fa:"\f226"}.fa-mars-double{--fa:"\f227"}.fa-venus-mars{--fa:"\f228"}.fa-mars-stroke{--fa:"\f229"}.fa-mars-stroke-up,.fa-mars-stroke-v{--fa:"\f22a"}.fa-mars-stroke-h,.fa-mars-stroke-right{--fa:"\f22b"}.fa-neuter{--fa:"\f22c"}.fa-genderless{--fa:"\f22d"}.fa-server{--fa:"\f233"}.fa-user-plus{--fa:"\f234"}.fa-user-times,.fa-user-xmark{--fa:"\f235"}.fa-bed{--fa:"\f236"}.fa-train{--fa:"\f238"}.fa-subway,.fa-train-subway{--fa:"\f239"}.fa-battery,.fa-battery-5,.fa-battery-full{--fa:"\f240"}.fa-battery-4,.fa-battery-three-quarters{--fa:"\f241"}.fa-battery-3,.fa-battery-half{--fa:"\f242"}.fa-battery-2,.fa-battery-quarter{--fa:"\f243"}.fa-battery-0,.fa-battery-empty{--fa:"\f244"}.fa-arrow-pointer,.fa-mouse-pointer{--fa:"\f245"}.fa-i-cursor{--fa:"\f246"}.fa-object-group{--fa:"\f247"}.fa-object-ungroup{--fa:"\f248"}.fa-note-sticky,.fa-sticky-note{--fa:"\f249"}.fa-clone{--fa:"\f24d"}.fa-balance-scale,.fa-scale-balanced{--fa:"\f24e"}.fa-hourglass-1,.fa-hourglass-start{--fa:"\f251"}.fa-hourglass-2,.fa-hourglass-half{--fa:"\f252"}.fa-hourglass-3,.fa-hourglass-end{--fa:"\f253"}.fa-hourglass,.fa-hourglass-empty{--fa:"\f254"}.fa-hand-back-fist,.fa-hand-rock{--fa:"\f255"}.fa-hand,.fa-hand-paper{--fa:"\f256"}.fa-hand-scissors{--fa:"\f257"}.fa-hand-lizard{--fa:"\f258"}.fa-hand-spock{--fa:"\f259"}.fa-hand-pointer{--fa:"\f25a"}.fa-hand-peace{--fa:"\f25b"}.fa-trademark{--fa:"\f25c"}.fa-registered{--fa:"\f25d"}.fa-television,.fa-tv,.fa-tv-alt{--fa:"\f26c"}.fa-calendar-plus{--fa:"\f271"}.fa-calendar-minus{--fa:"\f272"}.fa-calendar-times,.fa-calendar-xmark{--fa:"\f273"}.fa-calendar-check{--fa:"\f274"}.fa-industry{--fa:"\f275"}.fa-map-pin{--fa:"\f276"}.fa-map-signs,.fa-signs-post{--fa:"\f277"}.fa-map{--fa:"\f279"}.fa-comment-alt,.fa-message{--fa:"\f27a"}.fa-circle-pause,.fa-pause-circle{--fa:"\f28b"}.fa-circle-stop,.fa-stop-circle{--fa:"\f28d"}.fa-bag-shopping,.fa-shopping-bag{--fa:"\f290"}.fa-basket-shopping,.fa-shopping-basket{--fa:"\f291"}.fa-universal-access{--fa:"\f29a"}.fa-blind,.fa-person-walking-with-cane{--fa:"\f29d"}.fa-audio-description{--fa:"\f29e"}.fa-phone-volume,.fa-volume-control-phone{--fa:"\f2a0"}.fa-braille{--fa:"\f2a1"}.fa-assistive-listening-systems,.fa-ear-listen{--fa:"\f2a2"}.fa-american-sign-language-interpreting,.fa-asl-interpreting,.fa-hands-american-sign-language-interpreting,.fa-hands-asl-interpreting{--fa:"\f2a3"}.fa-deaf,.fa-deafness,.fa-ear-deaf,.fa-hard-of-hearing{--fa:"\f2a4"}.fa-hands,.fa-sign-language,.fa-signing{--fa:"\f2a7"}.fa-eye-low-vision,.fa-low-vision{--fa:"\f2a8"}.fa-handshake,.fa-handshake-alt,.fa-handshake-simple{--fa:"\f2b5"}.fa-envelope-open{--fa:"\f2b6"}.fa-address-book,.fa-contact-book{--fa:"\f2b9"}.fa-address-card,.fa-contact-card,.fa-vcard{--fa:"\f2bb"}.fa-circle-user,.fa-user-circle{--fa:"\f2bd"}.fa-id-badge{--fa:"\f2c1"}.fa-drivers-license,.fa-id-card{--fa:"\f2c2"}.fa-temperature-4,.fa-temperature-full,.fa-thermometer-4,.fa-thermometer-full{--fa:"\f2c7"}.fa-temperature-3,.fa-temperature-three-quarters,.fa-thermometer-3,.fa-thermometer-three-quarters{--fa:"\f2c8"}.fa-temperature-2,.fa-temperature-half,.fa-thermometer-2,.fa-thermometer-half{--fa:"\f2c9"}.fa-temperature-1,.fa-temperature-quarter,.fa-thermometer-1,.fa-thermometer-quarter{--fa:"\f2ca"}.fa-temperature-0,.fa-temperature-empty,.fa-thermometer-0,.fa-thermometer-empty{--fa:"\f2cb"}.fa-shower{--fa:"\f2cc"}.fa-bath,.fa-bathtub{--fa:"\f2cd"}.fa-podcast{--fa:"\f2ce"}.fa-window-maximize{--fa:"\f2d0"}.fa-window-minimize{--fa:"\f2d1"}.fa-window-restore{--fa:"\f2d2"}.fa-square-xmark,.fa-times-square,.fa-xmark-square{--fa:"\f2d3"}.fa-microchip{--fa:"\f2db"}.fa-snowflake{--fa:"\f2dc"}.fa-spoon,.fa-utensil-spoon{--fa:"\f2e5"}.fa-cutlery,.fa-utensils{--fa:"\f2e7"}.fa-rotate-back,.fa-rotate-backward,.fa-rotate-left,.fa-undo-alt{--fa:"\f2ea"}.fa-trash-alt,.fa-trash-can{--fa:"\f2ed"}.fa-rotate,.fa-sync-alt{--fa:"\f2f1"}.fa-stopwatch{--fa:"\f2f2"}.fa-right-from-bracket,.fa-sign-out-alt{--fa:"\f2f5"}.fa-right-to-bracket,.fa-sign-in-alt{--fa:"\f2f6"}.fa-redo-alt,.fa-rotate-forward,.fa-rotate-right{--fa:"\f2f9"}.fa-poo{--fa:"\f2fe"}.fa-images{--fa:"\f302"}.fa-pencil,.fa-pencil-alt{--fa:"\f303"}.fa-pen{--fa:"\f304"}.fa-pen-alt,.fa-pen-clip{--fa:"\f305"}.fa-octagon{--fa:"\f306"}.fa-down-long,.fa-long-arrow-alt-down{--fa:"\f309"}.fa-left-long,.fa-long-arrow-alt-left{--fa:"\f30a"}.fa-long-arrow-alt-right,.fa-right-long{--fa:"\f30b"}.fa-long-arrow-alt-up,.fa-up-long{--fa:"\f30c"}.fa-hexagon{--fa:"\f312"}.fa-file-edit,.fa-file-pen{--fa:"\f31c"}.fa-expand-arrows-alt,.fa-maximize{--fa:"\f31e"}.fa-clipboard{--fa:"\f328"}.fa-arrows-alt-h,.fa-left-right{--fa:"\f337"}.fa-arrows-alt-v,.fa-up-down{--fa:"\f338"}.fa-alarm-clock{--fa:"\f34e"}.fa-arrow-alt-circle-down,.fa-circle-down{--fa:"\f358"}.fa-arrow-alt-circle-left,.fa-circle-left{--fa:"\f359"}.fa-arrow-alt-circle-right,.fa-circle-right{--fa:"\f35a"}.fa-arrow-alt-circle-up,.fa-circle-up{--fa:"\f35b"}.fa-external-link-alt,.fa-up-right-from-square{--fa:"\f35d"}.fa-external-link-square-alt,.fa-square-up-right{--fa:"\f360"}.fa-exchange-alt,.fa-right-left{--fa:"\f362"}.fa-repeat{--fa:"\f363"}.fa-code-commit{--fa:"\f386"}.fa-code-merge{--fa:"\f387"}.fa-desktop,.fa-desktop-alt{--fa:"\f390"}.fa-gem{--fa:"\f3a5"}.fa-level-down-alt,.fa-turn-down{--fa:"\f3be"}.fa-level-up-alt,.fa-turn-up{--fa:"\f3bf"}.fa-lock-open{--fa:"\f3c1"}.fa-location-dot,.fa-map-marker-alt{--fa:"\f3c5"}.fa-microphone-alt,.fa-microphone-lines{--fa:"\f3c9"}.fa-mobile-alt,.fa-mobile-screen-button{--fa:"\f3cd"}.fa-mobile,.fa-mobile-android,.fa-mobile-phone{--fa:"\f3ce"}.fa-mobile-android-alt,.fa-mobile-screen{--fa:"\f3cf"}.fa-money-bill-1,.fa-money-bill-alt{--fa:"\f3d1"}.fa-phone-slash{--fa:"\f3dd"}.fa-image-portrait,.fa-portrait{--fa:"\f3e0"}.fa-mail-reply,.fa-reply{--fa:"\f3e5"}.fa-shield-alt,.fa-shield-halved{--fa:"\f3ed"}.fa-tablet-alt,.fa-tablet-screen-button{--fa:"\f3fa"}.fa-tablet,.fa-tablet-android{--fa:"\f3fb"}.fa-ticket-alt,.fa-ticket-simple{--fa:"\f3ff"}.fa-rectangle-times,.fa-rectangle-xmark,.fa-times-rectangle,.fa-window-close{--fa:"\f410"}.fa-compress-alt,.fa-down-left-and-up-right-to-center{--fa:"\f422"}.fa-expand-alt,.fa-up-right-and-down-left-from-center{--fa:"\f424"}.fa-baseball-bat-ball{--fa:"\f432"}.fa-baseball,.fa-baseball-ball{--fa:"\f433"}.fa-basketball,.fa-basketball-ball{--fa:"\f434"}.fa-bowling-ball{--fa:"\f436"}.fa-chess{--fa:"\f439"}.fa-chess-bishop{--fa:"\f43a"}.fa-chess-board{--fa:"\f43c"}.fa-chess-king{--fa:"\f43f"}.fa-chess-knight{--fa:"\f441"}.fa-chess-pawn{--fa:"\f443"}.fa-chess-queen{--fa:"\f445"}.fa-chess-rook{--fa:"\f447"}.fa-dumbbell{--fa:"\f44b"}.fa-football,.fa-football-ball{--fa:"\f44e"}.fa-golf-ball,.fa-golf-ball-tee{--fa:"\f450"}.fa-hockey-puck{--fa:"\f453"}.fa-broom-ball,.fa-quidditch,.fa-quidditch-broom-ball{--fa:"\f458"}.fa-square-full{--fa:"\f45c"}.fa-ping-pong-paddle-ball,.fa-table-tennis,.fa-table-tennis-paddle-ball{--fa:"\f45d"}.fa-volleyball,.fa-volleyball-ball{--fa:"\f45f"}.fa-allergies,.fa-hand-dots{--fa:"\f461"}.fa-band-aid,.fa-bandage{--fa:"\f462"}.fa-box{--fa:"\f466"}.fa-boxes,.fa-boxes-alt,.fa-boxes-stacked{--fa:"\f468"}.fa-briefcase-medical{--fa:"\f469"}.fa-burn,.fa-fire-flame-simple{--fa:"\f46a"}.fa-capsules{--fa:"\f46b"}.fa-clipboard-check{--fa:"\f46c"}.fa-clipboard-list{--fa:"\f46d"}.fa-diagnoses,.fa-person-dots-from-line{--fa:"\f470"}.fa-dna{--fa:"\f471"}.fa-dolly,.fa-dolly-box{--fa:"\f472"}.fa-cart-flatbed,.fa-dolly-flatbed{--fa:"\f474"}.fa-file-medical{--fa:"\f477"}.fa-file-medical-alt,.fa-file-waveform{--fa:"\f478"}.fa-first-aid,.fa-kit-medical{--fa:"\f479"}.fa-circle-h,.fa-hospital-symbol{--fa:"\f47e"}.fa-id-card-alt,.fa-id-card-clip{--fa:"\f47f"}.fa-notes-medical{--fa:"\f481"}.fa-pallet{--fa:"\f482"}.fa-pills{--fa:"\f484"}.fa-prescription-bottle{--fa:"\f485"}.fa-prescription-bottle-alt,.fa-prescription-bottle-medical{--fa:"\f486"}.fa-bed-pulse,.fa-procedures{--fa:"\f487"}.fa-shipping-fast,.fa-truck-fast{--fa:"\f48b"}.fa-smoking{--fa:"\f48d"}.fa-syringe{--fa:"\f48e"}.fa-tablets{--fa:"\f490"}.fa-thermometer{--fa:"\f491"}.fa-vial{--fa:"\f492"}.fa-vials{--fa:"\f493"}.fa-warehouse{--fa:"\f494"}.fa-weight,.fa-weight-scale{--fa:"\f496"}.fa-x-ray{--fa:"\f497"}.fa-box-open{--fa:"\f49e"}.fa-comment-dots,.fa-commenting{--fa:"\f4ad"}.fa-comment-slash{--fa:"\f4b3"}.fa-couch{--fa:"\f4b8"}.fa-circle-dollar-to-slot,.fa-donate{--fa:"\f4b9"}.fa-dove{--fa:"\f4ba"}.fa-hand-holding{--fa:"\f4bd"}.fa-hand-holding-heart{--fa:"\f4be"}.fa-hand-holding-dollar,.fa-hand-holding-usd{--fa:"\f4c0"}.fa-hand-holding-droplet,.fa-hand-holding-water{--fa:"\f4c1"}.fa-hands-holding{--fa:"\f4c2"}.fa-hands-helping,.fa-handshake-angle{--fa:"\f4c4"}.fa-parachute-box{--fa:"\f4cd"}.fa-people-carry,.fa-people-carry-box{--fa:"\f4ce"}.fa-piggy-bank{--fa:"\f4d3"}.fa-ribbon{--fa:"\f4d6"}.fa-route{--fa:"\f4d7"}.fa-seedling,.fa-sprout{--fa:"\f4d8"}.fa-sign,.fa-sign-hanging{--fa:"\f4d9"}.fa-face-smile-wink,.fa-smile-wink{--fa:"\f4da"}.fa-tape{--fa:"\f4db"}.fa-truck-loading,.fa-truck-ramp-box{--fa:"\f4de"}.fa-truck-moving{--fa:"\f4df"}.fa-video-slash{--fa:"\f4e2"}.fa-wine-glass{--fa:"\f4e3"}.fa-user-astronaut{--fa:"\f4fb"}.fa-user-check{--fa:"\f4fc"}.fa-user-clock{--fa:"\f4fd"}.fa-user-cog,.fa-user-gear{--fa:"\f4fe"}.fa-user-edit,.fa-user-pen{--fa:"\f4ff"}.fa-user-friends,.fa-user-group{--fa:"\f500"}.fa-user-graduate{--fa:"\f501"}.fa-user-lock{--fa:"\f502"}.fa-user-minus{--fa:"\f503"}.fa-user-ninja{--fa:"\f504"}.fa-user-shield{--fa:"\f505"}.fa-user-alt-slash,.fa-user-large-slash,.fa-user-slash{--fa:"\f506"}.fa-user-tag{--fa:"\f507"}.fa-user-tie{--fa:"\f508"}.fa-users-cog,.fa-users-gear{--fa:"\f509"}.fa-balance-scale-left,.fa-scale-unbalanced{--fa:"\f515"}.fa-balance-scale-right,.fa-scale-unbalanced-flip{--fa:"\f516"}.fa-blender{--fa:"\f517"}.fa-book-open{--fa:"\f518"}.fa-broadcast-tower,.fa-tower-broadcast{--fa:"\f519"}.fa-broom{--fa:"\f51a"}.fa-blackboard,.fa-chalkboard{--fa:"\f51b"}.fa-chalkboard-teacher,.fa-chalkboard-user{--fa:"\f51c"}.fa-church{--fa:"\f51d"}.fa-coins{--fa:"\f51e"}.fa-compact-disc{--fa:"\f51f"}.fa-crow{--fa:"\f520"}.fa-crown{--fa:"\f521"}.fa-dice{--fa:"\f522"}.fa-dice-five{--fa:"\f523"}.fa-dice-four{--fa:"\f524"}.fa-dice-one{--fa:"\f525"}.fa-dice-six{--fa:"\f526"}.fa-dice-three{--fa:"\f527"}.fa-dice-two{--fa:"\f528"}.fa-divide{--fa:"\f529"}.fa-door-closed{--fa:"\f52a"}.fa-door-open{--fa:"\f52b"}.fa-feather{--fa:"\f52d"}.fa-frog{--fa:"\f52e"}.fa-gas-pump{--fa:"\f52f"}.fa-glasses{--fa:"\f530"}.fa-greater-than-equal{--fa:"\f532"}.fa-helicopter{--fa:"\f533"}.fa-infinity{--fa:"\f534"}.fa-kiwi-bird{--fa:"\f535"}.fa-less-than-equal{--fa:"\f537"}.fa-memory{--fa:"\f538"}.fa-microphone-alt-slash,.fa-microphone-lines-slash{--fa:"\f539"}.fa-money-bill-wave{--fa:"\f53a"}.fa-money-bill-1-wave,.fa-money-bill-wave-alt{--fa:"\f53b"}.fa-money-check{--fa:"\f53c"}.fa-money-check-alt,.fa-money-check-dollar{--fa:"\f53d"}.fa-not-equal{--fa:"\f53e"}.fa-palette{--fa:"\f53f"}.fa-parking,.fa-square-parking{--fa:"\f540"}.fa-diagram-project,.fa-project-diagram{--fa:"\f542"}.fa-receipt{--fa:"\f543"}.fa-robot{--fa:"\f544"}.fa-ruler{--fa:"\f545"}.fa-ruler-combined{--fa:"\f546"}.fa-ruler-horizontal{--fa:"\f547"}.fa-ruler-vertical{--fa:"\f548"}.fa-school{--fa:"\f549"}.fa-screwdriver{--fa:"\f54a"}.fa-shoe-prints{--fa:"\f54b"}.fa-skull{--fa:"\f54c"}.fa-ban-smoking,.fa-smoking-ban{--fa:"\f54d"}.fa-store{--fa:"\f54e"}.fa-shop,.fa-store-alt{--fa:"\f54f"}.fa-bars-staggered,.fa-reorder,.fa-stream{--fa:"\f550"}.fa-stroopwafel{--fa:"\f551"}.fa-toolbox{--fa:"\f552"}.fa-shirt,.fa-t-shirt,.fa-tshirt{--fa:"\f553"}.fa-person-walking,.fa-walking{--fa:"\f554"}.fa-wallet{--fa:"\f555"}.fa-angry,.fa-face-angry{--fa:"\f556"}.fa-archway{--fa:"\f557"}.fa-atlas,.fa-book-atlas{--fa:"\f558"}.fa-award{--fa:"\f559"}.fa-backspace,.fa-delete-left{--fa:"\f55a"}.fa-bezier-curve{--fa:"\f55b"}.fa-bong{--fa:"\f55c"}.fa-brush{--fa:"\f55d"}.fa-bus-alt,.fa-bus-simple{--fa:"\f55e"}.fa-cannabis{--fa:"\f55f"}.fa-check-double{--fa:"\f560"}.fa-cocktail,.fa-martini-glass-citrus{--fa:"\f561"}.fa-bell-concierge,.fa-concierge-bell{--fa:"\f562"}.fa-cookie{--fa:"\f563"}.fa-cookie-bite{--fa:"\f564"}.fa-crop-alt,.fa-crop-simple{--fa:"\f565"}.fa-digital-tachograph,.fa-tachograph-digital{--fa:"\f566"}.fa-dizzy,.fa-face-dizzy{--fa:"\f567"}.fa-compass-drafting,.fa-drafting-compass{--fa:"\f568"}.fa-drum{--fa:"\f569"}.fa-drum-steelpan{--fa:"\f56a"}.fa-feather-alt,.fa-feather-pointed{--fa:"\f56b"}.fa-file-contract{--fa:"\f56c"}.fa-file-arrow-down,.fa-file-download{--fa:"\f56d"}.fa-arrow-right-from-file,.fa-file-export{--fa:"\f56e"}.fa-arrow-right-to-file,.fa-file-import{--fa:"\f56f"}.fa-file-invoice{--fa:"\f570"}.fa-file-invoice-dollar{--fa:"\f571"}.fa-file-prescription{--fa:"\f572"}.fa-file-signature{--fa:"\f573"}.fa-file-arrow-up,.fa-file-upload{--fa:"\f574"}.fa-fill{--fa:"\f575"}.fa-fill-drip{--fa:"\f576"}.fa-fingerprint{--fa:"\f577"}.fa-fish{--fa:"\f578"}.fa-face-flushed,.fa-flushed{--fa:"\f579"}.fa-face-frown-open,.fa-frown-open{--fa:"\f57a"}.fa-glass-martini-alt,.fa-martini-glass{--fa:"\f57b"}.fa-earth-africa,.fa-globe-africa{--fa:"\f57c"}.fa-earth,.fa-earth-america,.fa-earth-americas,.fa-globe-americas{--fa:"\f57d"}.fa-earth-asia,.fa-globe-asia{--fa:"\f57e"}.fa-face-grimace,.fa-grimace{--fa:"\f57f"}.fa-face-grin,.fa-grin{--fa:"\f580"}.fa-face-grin-wide,.fa-grin-alt{--fa:"\f581"}.fa-face-grin-beam,.fa-grin-beam{--fa:"\f582"}.fa-face-grin-beam-sweat,.fa-grin-beam-sweat{--fa:"\f583"}.fa-face-grin-hearts,.fa-grin-hearts{--fa:"\f584"}.fa-face-grin-squint,.fa-grin-squint{--fa:"\f585"}.fa-face-grin-squint-tears,.fa-grin-squint-tears{--fa:"\f586"}.fa-face-grin-stars,.fa-grin-stars{--fa:"\f587"}.fa-face-grin-tears,.fa-grin-tears{--fa:"\f588"}.fa-face-grin-tongue,.fa-grin-tongue{--fa:"\f589"}.fa-face-grin-tongue-squint,.fa-grin-tongue-squint{--fa:"\f58a"}.fa-face-grin-tongue-wink,.fa-grin-tongue-wink{--fa:"\f58b"}.fa-face-grin-wink,.fa-grin-wink{--fa:"\f58c"}.fa-grid-horizontal,.fa-grip,.fa-grip-horizontal{--fa:"\f58d"}.fa-grid-vertical,.fa-grip-vertical{--fa:"\f58e"}.fa-headset{--fa:"\f590"}.fa-highlighter{--fa:"\f591"}.fa-hot-tub,.fa-hot-tub-person{--fa:"\f593"}.fa-hotel{--fa:"\f594"}.fa-joint{--fa:"\f595"}.fa-face-kiss,.fa-kiss{--fa:"\f596"}.fa-face-kiss-beam,.fa-kiss-beam{--fa:"\f597"}.fa-face-kiss-wink-heart,.fa-kiss-wink-heart{--fa:"\f598"}.fa-face-laugh,.fa-laugh{--fa:"\f599"}.fa-face-laugh-beam,.fa-laugh-beam{--fa:"\f59a"}.fa-face-laugh-squint,.fa-laugh-squint{--fa:"\f59b"}.fa-face-laugh-wink,.fa-laugh-wink{--fa:"\f59c"}.fa-cart-flatbed-suitcase,.fa-luggage-cart{--fa:"\f59d"}.fa-map-location,.fa-map-marked{--fa:"\f59f"}.fa-map-location-dot,.fa-map-marked-alt{--fa:"\f5a0"}.fa-marker{--fa:"\f5a1"}.fa-medal{--fa:"\f5a2"}.fa-face-meh-blank,.fa-meh-blank{--fa:"\f5a4"}.fa-face-rolling-eyes,.fa-meh-rolling-eyes{--fa:"\f5a5"}.fa-monument{--fa:"\f5a6"}.fa-mortar-pestle{--fa:"\f5a7"}.fa-paint-roller{--fa:"\f5aa"}.fa-passport{--fa:"\f5ab"}.fa-pen-fancy{--fa:"\f5ac"}.fa-pen-nib{--fa:"\f5ad"}.fa-pen-ruler,.fa-pencil-ruler{--fa:"\f5ae"}.fa-plane-arrival{--fa:"\f5af"}.fa-plane-departure{--fa:"\f5b0"}.fa-prescription{--fa:"\f5b1"}.fa-face-sad-cry,.fa-sad-cry{--fa:"\f5b3"}.fa-face-sad-tear,.fa-sad-tear{--fa:"\f5b4"}.fa-shuttle-van,.fa-van-shuttle{--fa:"\f5b6"}.fa-signature{--fa:"\f5b7"}.fa-face-smile-beam,.fa-smile-beam{--fa:"\f5b8"}.fa-solar-panel{--fa:"\f5ba"}.fa-spa{--fa:"\f5bb"}.fa-splotch{--fa:"\f5bc"}.fa-spray-can{--fa:"\f5bd"}.fa-stamp{--fa:"\f5bf"}.fa-star-half-alt,.fa-star-half-stroke{--fa:"\f5c0"}.fa-suitcase-rolling{--fa:"\f5c1"}.fa-face-surprise,.fa-surprise{--fa:"\f5c2"}.fa-swatchbook{--fa:"\f5c3"}.fa-person-swimming,.fa-swimmer{--fa:"\f5c4"}.fa-ladder-water,.fa-swimming-pool,.fa-water-ladder{--fa:"\f5c5"}.fa-droplet-slash,.fa-tint-slash{--fa:"\f5c7"}.fa-face-tired,.fa-tired{--fa:"\f5c8"}.fa-tooth{--fa:"\f5c9"}.fa-umbrella-beach{--fa:"\f5ca"}.fa-weight-hanging{--fa:"\f5cd"}.fa-wine-glass-alt,.fa-wine-glass-empty{--fa:"\f5ce"}.fa-air-freshener,.fa-spray-can-sparkles{--fa:"\f5d0"}.fa-apple-alt,.fa-apple-whole{--fa:"\f5d1"}.fa-atom{--fa:"\f5d2"}.fa-bone{--fa:"\f5d7"}.fa-book-open-reader,.fa-book-reader{--fa:"\f5da"}.fa-brain{--fa:"\f5dc"}.fa-car-alt,.fa-car-rear{--fa:"\f5de"}.fa-battery-car,.fa-car-battery{--fa:"\f5df"}.fa-car-burst,.fa-car-crash{--fa:"\f5e1"}.fa-car-side{--fa:"\f5e4"}.fa-charging-station{--fa:"\f5e7"}.fa-diamond-turn-right,.fa-directions{--fa:"\f5eb"}.fa-draw-polygon,.fa-vector-polygon{--fa:"\f5ee"}.fa-laptop-code{--fa:"\f5fc"}.fa-layer-group{--fa:"\f5fd"}.fa-location,.fa-location-crosshairs{--fa:"\f601"}.fa-lungs{--fa:"\f604"}.fa-microscope{--fa:"\f610"}.fa-oil-can{--fa:"\f613"}.fa-poop{--fa:"\f619"}.fa-shapes,.fa-triangle-circle-square{--fa:"\f61f"}.fa-star-of-life{--fa:"\f621"}.fa-dashboard,.fa-gauge,.fa-gauge-med,.fa-tachometer-alt-average{--fa:"\f624"}.fa-gauge-high,.fa-tachometer-alt,.fa-tachometer-alt-fast{--fa:"\f625"}.fa-gauge-simple,.fa-gauge-simple-med,.fa-tachometer-average{--fa:"\f629"}.fa-gauge-simple-high,.fa-tachometer,.fa-tachometer-fast{--fa:"\f62a"}.fa-teeth{--fa:"\f62e"}.fa-teeth-open{--fa:"\f62f"}.fa-masks-theater,.fa-theater-masks{--fa:"\f630"}.fa-traffic-light{--fa:"\f637"}.fa-truck-monster{--fa:"\f63b"}.fa-truck-pickup{--fa:"\f63c"}.fa-ad,.fa-rectangle-ad{--fa:"\f641"}.fa-ankh{--fa:"\f644"}.fa-bible,.fa-book-bible{--fa:"\f647"}.fa-briefcase-clock,.fa-business-time{--fa:"\f64a"}.fa-city{--fa:"\f64f"}.fa-comment-dollar{--fa:"\f651"}.fa-comments-dollar{--fa:"\f653"}.fa-cross{--fa:"\f654"}.fa-dharmachakra{--fa:"\f655"}.fa-envelope-open-text{--fa:"\f658"}.fa-folder-minus{--fa:"\f65d"}.fa-folder-plus{--fa:"\f65e"}.fa-filter-circle-dollar,.fa-funnel-dollar{--fa:"\f662"}.fa-gopuram{--fa:"\f664"}.fa-hamsa{--fa:"\f665"}.fa-bahai,.fa-haykal{--fa:"\f666"}.fa-jedi{--fa:"\f669"}.fa-book-journal-whills,.fa-journal-whills{--fa:"\f66a"}.fa-kaaba{--fa:"\f66b"}.fa-khanda{--fa:"\f66d"}.fa-landmark{--fa:"\f66f"}.fa-envelopes-bulk,.fa-mail-bulk{--fa:"\f674"}.fa-menorah{--fa:"\f676"}.fa-mosque{--fa:"\f678"}.fa-om{--fa:"\f679"}.fa-pastafarianism,.fa-spaghetti-monster-flying{--fa:"\f67b"}.fa-peace{--fa:"\f67c"}.fa-place-of-worship{--fa:"\f67f"}.fa-poll,.fa-square-poll-vertical{--fa:"\f681"}.fa-poll-h,.fa-square-poll-horizontal{--fa:"\f682"}.fa-person-praying,.fa-pray{--fa:"\f683"}.fa-hands-praying,.fa-praying-hands{--fa:"\f684"}.fa-book-quran,.fa-quran{--fa:"\f687"}.fa-magnifying-glass-dollar,.fa-search-dollar{--fa:"\f688"}.fa-magnifying-glass-location,.fa-search-location{--fa:"\f689"}.fa-socks{--fa:"\f696"}.fa-square-root-alt,.fa-square-root-variable{--fa:"\f698"}.fa-star-and-crescent{--fa:"\f699"}.fa-star-of-david{--fa:"\f69a"}.fa-synagogue{--fa:"\f69b"}.fa-scroll-torah,.fa-torah{--fa:"\f6a0"}.fa-torii-gate{--fa:"\f6a1"}.fa-vihara{--fa:"\f6a7"}.fa-volume-mute,.fa-volume-times,.fa-volume-xmark{--fa:"\f6a9"}.fa-yin-yang{--fa:"\f6ad"}.fa-blender-phone{--fa:"\f6b6"}.fa-book-dead,.fa-book-skull{--fa:"\f6b7"}.fa-campground{--fa:"\f6bb"}.fa-cat{--fa:"\f6be"}.fa-chair{--fa:"\f6c0"}.fa-cloud-moon{--fa:"\f6c3"}.fa-cloud-sun{--fa:"\f6c4"}.fa-cow{--fa:"\f6c8"}.fa-dice-d20{--fa:"\f6cf"}.fa-dice-d6{--fa:"\f6d1"}.fa-dog{--fa:"\f6d3"}.fa-dragon{--fa:"\f6d5"}.fa-drumstick-bite{--fa:"\f6d7"}.fa-dungeon{--fa:"\f6d9"}.fa-file-csv{--fa:"\f6dd"}.fa-fist-raised,.fa-hand-fist{--fa:"\f6de"}.fa-ghost{--fa:"\f6e2"}.fa-hammer{--fa:"\f6e3"}.fa-hanukiah{--fa:"\f6e6"}.fa-hat-wizard{--fa:"\f6e8"}.fa-hiking,.fa-person-hiking{--fa:"\f6ec"}.fa-hippo{--fa:"\f6ed"}.fa-horse{--fa:"\f6f0"}.fa-house-chimney-crack,.fa-house-damage{--fa:"\f6f1"}.fa-hryvnia,.fa-hryvnia-sign{--fa:"\f6f2"}.fa-mask{--fa:"\f6fa"}.fa-mountain{--fa:"\f6fc"}.fa-network-wired{--fa:"\f6ff"}.fa-otter{--fa:"\f700"}.fa-ring{--fa:"\f70b"}.fa-person-running,.fa-running{--fa:"\f70c"}.fa-scroll{--fa:"\f70e"}.fa-skull-crossbones{--fa:"\f714"}.fa-slash{--fa:"\f715"}.fa-spider{--fa:"\f717"}.fa-toilet-paper,.fa-toilet-paper-alt,.fa-toilet-paper-blank{--fa:"\f71e"}.fa-tractor{--fa:"\f722"}.fa-user-injured{--fa:"\f728"}.fa-vr-cardboard{--fa:"\f729"}.fa-wand-sparkles{--fa:"\f72b"}.fa-wind{--fa:"\f72e"}.fa-wine-bottle{--fa:"\f72f"}.fa-cloud-meatball{--fa:"\f73b"}.fa-cloud-moon-rain{--fa:"\f73c"}.fa-cloud-rain{--fa:"\f73d"}.fa-cloud-showers-heavy{--fa:"\f740"}.fa-cloud-sun-rain{--fa:"\f743"}.fa-democrat{--fa:"\f747"}.fa-flag-usa{--fa:"\f74d"}.fa-hurricane{--fa:"\f751"}.fa-landmark-alt,.fa-landmark-dome{--fa:"\f752"}.fa-meteor{--fa:"\f753"}.fa-person-booth{--fa:"\f756"}.fa-poo-bolt,.fa-poo-storm{--fa:"\f75a"}.fa-rainbow{--fa:"\f75b"}.fa-republican{--fa:"\f75e"}.fa-smog{--fa:"\f75f"}.fa-temperature-high{--fa:"\f769"}.fa-temperature-low{--fa:"\f76b"}.fa-cloud-bolt,.fa-thunderstorm{--fa:"\f76c"}.fa-tornado{--fa:"\f76f"}.fa-volcano{--fa:"\f770"}.fa-check-to-slot,.fa-vote-yea{--fa:"\f772"}.fa-water{--fa:"\f773"}.fa-baby{--fa:"\f77c"}.fa-baby-carriage,.fa-carriage-baby{--fa:"\f77d"}.fa-biohazard{--fa:"\f780"}.fa-blog{--fa:"\f781"}.fa-calendar-day{--fa:"\f783"}.fa-calendar-week{--fa:"\f784"}.fa-candy-cane{--fa:"\f786"}.fa-carrot{--fa:"\f787"}.fa-cash-register{--fa:"\f788"}.fa-compress-arrows-alt,.fa-minimize{--fa:"\f78c"}.fa-dumpster{--fa:"\f793"}.fa-dumpster-fire{--fa:"\f794"}.fa-ethernet{--fa:"\f796"}.fa-gifts{--fa:"\f79c"}.fa-champagne-glasses,.fa-glass-cheers{--fa:"\f79f"}.fa-glass-whiskey,.fa-whiskey-glass{--fa:"\f7a0"}.fa-earth-europe,.fa-globe-europe{--fa:"\f7a2"}.fa-grip-lines{--fa:"\f7a4"}.fa-grip-lines-vertical{--fa:"\f7a5"}.fa-guitar{--fa:"\f7a6"}.fa-heart-broken,.fa-heart-crack{--fa:"\f7a9"}.fa-holly-berry{--fa:"\f7aa"}.fa-horse-head{--fa:"\f7ab"}.fa-icicles{--fa:"\f7ad"}.fa-igloo{--fa:"\f7ae"}.fa-mitten{--fa:"\f7b5"}.fa-mug-hot{--fa:"\f7b6"}.fa-radiation{--fa:"\f7b9"}.fa-circle-radiation,.fa-radiation-alt{--fa:"\f7ba"}.fa-restroom{--fa:"\f7bd"}.fa-satellite{--fa:"\f7bf"}.fa-satellite-dish{--fa:"\f7c0"}.fa-sd-card{--fa:"\f7c2"}.fa-sim-card{--fa:"\f7c4"}.fa-person-skating,.fa-skating{--fa:"\f7c5"}.fa-person-skiing,.fa-skiing{--fa:"\f7c9"}.fa-person-skiing-nordic,.fa-skiing-nordic{--fa:"\f7ca"}.fa-sleigh{--fa:"\f7cc"}.fa-comment-sms,.fa-sms{--fa:"\f7cd"}.fa-person-snowboarding,.fa-snowboarding{--fa:"\f7ce"}.fa-snowman{--fa:"\f7d0"}.fa-snowplow{--fa:"\f7d2"}.fa-tenge,.fa-tenge-sign{--fa:"\f7d7"}.fa-toilet{--fa:"\f7d8"}.fa-screwdriver-wrench,.fa-tools{--fa:"\f7d9"}.fa-cable-car,.fa-tram{--fa:"\f7da"}.fa-fire-alt,.fa-fire-flame-curved{--fa:"\f7e4"}.fa-bacon{--fa:"\f7e5"}.fa-book-medical{--fa:"\f7e6"}.fa-bread-slice{--fa:"\f7ec"}.fa-cheese{--fa:"\f7ef"}.fa-clinic-medical,.fa-house-chimney-medical{--fa:"\f7f2"}.fa-clipboard-user{--fa:"\f7f3"}.fa-comment-medical{--fa:"\f7f5"}.fa-crutch{--fa:"\f7f7"}.fa-disease{--fa:"\f7fa"}.fa-egg{--fa:"\f7fb"}.fa-folder-tree{--fa:"\f802"}.fa-burger,.fa-hamburger{--fa:"\f805"}.fa-hand-middle-finger{--fa:"\f806"}.fa-hard-hat,.fa-hat-hard,.fa-helmet-safety{--fa:"\f807"}.fa-hospital-user{--fa:"\f80d"}.fa-hotdog{--fa:"\f80f"}.fa-ice-cream{--fa:"\f810"}.fa-laptop-medical{--fa:"\f812"}.fa-pager{--fa:"\f815"}.fa-pepper-hot{--fa:"\f816"}.fa-pizza-slice{--fa:"\f818"}.fa-sack-dollar{--fa:"\f81d"}.fa-book-tanakh,.fa-tanakh{--fa:"\f827"}.fa-bars-progress,.fa-tasks-alt{--fa:"\f828"}.fa-trash-arrow-up,.fa-trash-restore{--fa:"\f829"}.fa-trash-can-arrow-up,.fa-trash-restore-alt{--fa:"\f82a"}.fa-user-nurse{--fa:"\f82f"}.fa-wave-square{--fa:"\f83e"}.fa-biking,.fa-person-biking{--fa:"\f84a"}.fa-border-all{--fa:"\f84c"}.fa-border-none{--fa:"\f850"}.fa-border-style,.fa-border-top-left{--fa:"\f853"}.fa-digging,.fa-person-digging{--fa:"\f85e"}.fa-fan{--fa:"\f863"}.fa-heart-music-camera-bolt,.fa-icons{--fa:"\f86d"}.fa-phone-alt,.fa-phone-flip{--fa:"\f879"}.fa-phone-square-alt,.fa-square-phone-flip{--fa:"\f87b"}.fa-photo-film,.fa-photo-video{--fa:"\f87c"}.fa-remove-format,.fa-text-slash{--fa:"\f87d"}.fa-arrow-down-z-a,.fa-sort-alpha-desc,.fa-sort-alpha-down-alt{--fa:"\f881"}.fa-arrow-up-z-a,.fa-sort-alpha-up-alt{--fa:"\f882"}.fa-arrow-down-short-wide,.fa-sort-amount-desc,.fa-sort-amount-down-alt{--fa:"\f884"}.fa-arrow-up-short-wide,.fa-sort-amount-up-alt{--fa:"\f885"}.fa-arrow-down-9-1,.fa-sort-numeric-desc,.fa-sort-numeric-down-alt{--fa:"\f886"}.fa-arrow-up-9-1,.fa-sort-numeric-up-alt{--fa:"\f887"}.fa-spell-check{--fa:"\f891"}.fa-voicemail{--fa:"\f897"}.fa-hat-cowboy{--fa:"\f8c0"}.fa-hat-cowboy-side{--fa:"\f8c1"}.fa-computer-mouse,.fa-mouse{--fa:"\f8cc"}.fa-radio{--fa:"\f8d7"}.fa-record-vinyl{--fa:"\f8d9"}.fa-walkie-talkie{--fa:"\f8ef"}.fa-caravan{--fa:"\f8ff"} :host,:root{--fa-family-brands:"Font Awesome 7 Brands";--fa-font-brands:normal 400 1em/1 var(--fa-family-brands)}@font-face{font-family:"Font Awesome 7 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2)}.fa-brands,.fa-classic.fa-brands,.fab{--fa-family:var(--fa-family-brands);--fa-style:400}.fa-firefox-browser{--fa:"\e007"}.fa-ideal{--fa:"\e013"}.fa-microblog{--fa:"\e01a"}.fa-pied-piper-square,.fa-square-pied-piper{--fa:"\e01e"}.fa-unity{--fa:"\e049"}.fa-dailymotion{--fa:"\e052"}.fa-instagram-square,.fa-square-instagram{--fa:"\e055"}.fa-mixer{--fa:"\e056"}.fa-shopify{--fa:"\e057"}.fa-deezer{--fa:"\e077"}.fa-edge-legacy{--fa:"\e078"}.fa-google-pay{--fa:"\e079"}.fa-rust{--fa:"\e07a"}.fa-tiktok{--fa:"\e07b"}.fa-unsplash{--fa:"\e07c"}.fa-cloudflare{--fa:"\e07d"}.fa-guilded{--fa:"\e07e"}.fa-hive{--fa:"\e07f"}.fa-42-group,.fa-innosoft{--fa:"\e080"}.fa-instalod{--fa:"\e081"}.fa-octopus-deploy{--fa:"\e082"}.fa-perbyte{--fa:"\e083"}.fa-uncharted{--fa:"\e084"}.fa-watchman-monitoring{--fa:"\e087"}.fa-wodu{--fa:"\e088"}.fa-wirsindhandwerk,.fa-wsh{--fa:"\e2d0"}.fa-bots{--fa:"\e340"}.fa-cmplid{--fa:"\e360"}.fa-bilibili{--fa:"\e3d9"}.fa-golang{--fa:"\e40f"}.fa-pix{--fa:"\e43a"}.fa-sitrox{--fa:"\e44a"}.fa-hashnode{--fa:"\e499"}.fa-meta{--fa:"\e49b"}.fa-padlet{--fa:"\e4a0"}.fa-nfc-directional{--fa:"\e530"}.fa-nfc-symbol{--fa:"\e531"}.fa-screenpal{--fa:"\e570"}.fa-space-awesome{--fa:"\e5ac"}.fa-square-font-awesome{--fa:"\e5ad"}.fa-gitlab-square,.fa-square-gitlab{--fa:"\e5ae"}.fa-odysee{--fa:"\e5c6"}.fa-stubber{--fa:"\e5c7"}.fa-debian{--fa:"\e60b"}.fa-shoelace{--fa:"\e60c"}.fa-threads{--fa:"\e618"}.fa-square-threads{--fa:"\e619"}.fa-square-x-twitter{--fa:"\e61a"}.fa-x-twitter{--fa:"\e61b"}.fa-opensuse{--fa:"\e62b"}.fa-letterboxd{--fa:"\e62d"}.fa-square-letterboxd{--fa:"\e62e"}.fa-mintbit{--fa:"\e62f"}.fa-google-scholar{--fa:"\e63b"}.fa-brave{--fa:"\e63c"}.fa-brave-reverse{--fa:"\e63d"}.fa-pixiv{--fa:"\e640"}.fa-upwork{--fa:"\e641"}.fa-webflow{--fa:"\e65c"}.fa-signal-messenger{--fa:"\e663"}.fa-bluesky{--fa:"\e671"}.fa-jxl{--fa:"\e67b"}.fa-square-upwork{--fa:"\e67c"}.fa-web-awesome{--fa:"\e682"}.fa-square-web-awesome{--fa:"\e683"}.fa-square-web-awesome-stroke{--fa:"\e684"}.fa-dart-lang{--fa:"\e693"}.fa-flutter{--fa:"\e694"}.fa-files-pinwheel{--fa:"\e69f"}.fa-css{--fa:"\e6a2"}.fa-square-bluesky{--fa:"\e6a3"}.fa-openai{--fa:"\e7cf"}.fa-square-linkedin{--fa:"\e7d0"}.fa-cash-app{--fa:"\e7d4"}.fa-disqus{--fa:"\e7d5"}.fa-11ty,.fa-eleventy{--fa:"\e7d6"}.fa-kakao-talk{--fa:"\e7d7"}.fa-linktree{--fa:"\e7d8"}.fa-notion{--fa:"\e7d9"}.fa-pandora{--fa:"\e7da"}.fa-pixelfed{--fa:"\e7db"}.fa-tidal{--fa:"\e7dc"}.fa-vsco{--fa:"\e7dd"}.fa-w3c{--fa:"\e7de"}.fa-lumon{--fa:"\e7e2"}.fa-lumon-drop{--fa:"\e7e3"}.fa-square-figma{--fa:"\e7e4"}.fa-tex{--fa:"\e7ff"}.fa-duolingo{--fa:"\e812"}.fa-square-twitter,.fa-twitter-square{--fa:"\f081"}.fa-facebook-square,.fa-square-facebook{--fa:"\f082"}.fa-linkedin{--fa:"\f08c"}.fa-github-square,.fa-square-github{--fa:"\f092"}.fa-twitter{--fa:"\f099"}.fa-facebook{--fa:"\f09a"}.fa-github{--fa:"\f09b"}.fa-pinterest{--fa:"\f0d2"}.fa-pinterest-square,.fa-square-pinterest{--fa:"\f0d3"}.fa-google-plus-square,.fa-square-google-plus{--fa:"\f0d4"}.fa-google-plus-g{--fa:"\f0d5"}.fa-linkedin-in{--fa:"\f0e1"}.fa-github-alt{--fa:"\f113"}.fa-maxcdn{--fa:"\f136"}.fa-html5{--fa:"\f13b"}.fa-css3{--fa:"\f13c"}.fa-btc{--fa:"\f15a"}.fa-youtube{--fa:"\f167"}.fa-xing{--fa:"\f168"}.fa-square-xing,.fa-xing-square{--fa:"\f169"}.fa-dropbox{--fa:"\f16b"}.fa-stack-overflow{--fa:"\f16c"}.fa-instagram{--fa:"\f16d"}.fa-flickr{--fa:"\f16e"}.fa-adn{--fa:"\f170"}.fa-bitbucket{--fa:"\f171"}.fa-tumblr{--fa:"\f173"}.fa-square-tumblr,.fa-tumblr-square{--fa:"\f174"}.fa-apple{--fa:"\f179"}.fa-windows{--fa:"\f17a"}.fa-android{--fa:"\f17b"}.fa-linux{--fa:"\f17c"}.fa-dribbble{--fa:"\f17d"}.fa-skype{--fa:"\f17e"}.fa-foursquare{--fa:"\f180"}.fa-trello{--fa:"\f181"}.fa-gratipay{--fa:"\f184"}.fa-vk{--fa:"\f189"}.fa-weibo{--fa:"\f18a"}.fa-renren{--fa:"\f18b"}.fa-pagelines{--fa:"\f18c"}.fa-stack-exchange{--fa:"\f18d"}.fa-square-vimeo,.fa-vimeo-square{--fa:"\f194"}.fa-slack,.fa-slack-hash{--fa:"\f198"}.fa-wordpress{--fa:"\f19a"}.fa-openid{--fa:"\f19b"}.fa-yahoo{--fa:"\f19e"}.fa-google{--fa:"\f1a0"}.fa-reddit{--fa:"\f1a1"}.fa-reddit-square,.fa-square-reddit{--fa:"\f1a2"}.fa-stumbleupon-circle{--fa:"\f1a3"}.fa-stumbleupon{--fa:"\f1a4"}.fa-delicious{--fa:"\f1a5"}.fa-digg{--fa:"\f1a6"}.fa-pied-piper-pp{--fa:"\f1a7"}.fa-pied-piper-alt{--fa:"\f1a8"}.fa-drupal{--fa:"\f1a9"}.fa-joomla{--fa:"\f1aa"}.fa-behance{--fa:"\f1b4"}.fa-behance-square,.fa-square-behance{--fa:"\f1b5"}.fa-steam{--fa:"\f1b6"}.fa-square-steam,.fa-steam-square{--fa:"\f1b7"}.fa-spotify{--fa:"\f1bc"}.fa-deviantart{--fa:"\f1bd"}.fa-soundcloud{--fa:"\f1be"}.fa-vine{--fa:"\f1ca"}.fa-codepen{--fa:"\f1cb"}.fa-jsfiddle{--fa:"\f1cc"}.fa-rebel{--fa:"\f1d0"}.fa-empire{--fa:"\f1d1"}.fa-git-square,.fa-square-git{--fa:"\f1d2"}.fa-git{--fa:"\f1d3"}.fa-hacker-news{--fa:"\f1d4"}.fa-tencent-weibo{--fa:"\f1d5"}.fa-qq{--fa:"\f1d6"}.fa-weixin{--fa:"\f1d7"}.fa-slideshare{--fa:"\f1e7"}.fa-twitch{--fa:"\f1e8"}.fa-yelp{--fa:"\f1e9"}.fa-paypal{--fa:"\f1ed"}.fa-google-wallet{--fa:"\f1ee"}.fa-cc-visa{--fa:"\f1f0"}.fa-cc-mastercard{--fa:"\f1f1"}.fa-cc-discover{--fa:"\f1f2"}.fa-cc-amex{--fa:"\f1f3"}.fa-cc-paypal{--fa:"\f1f4"}.fa-cc-stripe{--fa:"\f1f5"}.fa-lastfm{--fa:"\f202"}.fa-lastfm-square,.fa-square-lastfm{--fa:"\f203"}.fa-ioxhost{--fa:"\f208"}.fa-angellist{--fa:"\f209"}.fa-buysellads{--fa:"\f20d"}.fa-connectdevelop{--fa:"\f20e"}.fa-dashcube{--fa:"\f210"}.fa-forumbee{--fa:"\f211"}.fa-leanpub{--fa:"\f212"}.fa-sellsy{--fa:"\f213"}.fa-shirtsinbulk{--fa:"\f214"}.fa-simplybuilt{--fa:"\f215"}.fa-skyatlas{--fa:"\f216"}.fa-pinterest-p{--fa:"\f231"}.fa-whatsapp{--fa:"\f232"}.fa-viacoin{--fa:"\f237"}.fa-medium,.fa-medium-m{--fa:"\f23a"}.fa-y-combinator{--fa:"\f23b"}.fa-optin-monster{--fa:"\f23c"}.fa-opencart{--fa:"\f23d"}.fa-expeditedssl{--fa:"\f23e"}.fa-cc-jcb{--fa:"\f24b"}.fa-cc-diners-club{--fa:"\f24c"}.fa-creative-commons{--fa:"\f25e"}.fa-gg{--fa:"\f260"}.fa-gg-circle{--fa:"\f261"}.fa-odnoklassniki{--fa:"\f263"}.fa-odnoklassniki-square,.fa-square-odnoklassniki{--fa:"\f264"}.fa-get-pocket{--fa:"\f265"}.fa-wikipedia-w{--fa:"\f266"}.fa-safari{--fa:"\f267"}.fa-chrome{--fa:"\f268"}.fa-firefox{--fa:"\f269"}.fa-opera{--fa:"\f26a"}.fa-internet-explorer{--fa:"\f26b"}.fa-contao{--fa:"\f26d"}.fa-500px{--fa:"\f26e"}.fa-amazon{--fa:"\f270"}.fa-houzz{--fa:"\f27c"}.fa-vimeo-v{--fa:"\f27d"}.fa-black-tie{--fa:"\f27e"}.fa-fonticons{--fa:"\f280"}.fa-reddit-alien{--fa:"\f281"}.fa-edge{--fa:"\f282"}.fa-codiepie{--fa:"\f284"}.fa-modx{--fa:"\f285"}.fa-fort-awesome{--fa:"\f286"}.fa-usb{--fa:"\f287"}.fa-product-hunt{--fa:"\f288"}.fa-mixcloud{--fa:"\f289"}.fa-scribd{--fa:"\f28a"}.fa-bluetooth{--fa:"\f293"}.fa-bluetooth-b{--fa:"\f294"}.fa-gitlab{--fa:"\f296"}.fa-wpbeginner{--fa:"\f297"}.fa-wpforms{--fa:"\f298"}.fa-envira{--fa:"\f299"}.fa-glide{--fa:"\f2a5"}.fa-glide-g{--fa:"\f2a6"}.fa-viadeo{--fa:"\f2a9"}.fa-square-viadeo,.fa-viadeo-square{--fa:"\f2aa"}.fa-snapchat,.fa-snapchat-ghost{--fa:"\f2ab"}.fa-snapchat-square,.fa-square-snapchat{--fa:"\f2ad"}.fa-pied-piper{--fa:"\f2ae"}.fa-first-order{--fa:"\f2b0"}.fa-yoast{--fa:"\f2b1"}.fa-themeisle{--fa:"\f2b2"}.fa-google-plus{--fa:"\f2b3"}.fa-font-awesome,.fa-font-awesome-flag,.fa-font-awesome-logo-full{--fa:"\f2b4"}.fa-linode{--fa:"\f2b8"}.fa-quora{--fa:"\f2c4"}.fa-free-code-camp{--fa:"\f2c5"}.fa-telegram,.fa-telegram-plane{--fa:"\f2c6"}.fa-bandcamp{--fa:"\f2d5"}.fa-grav{--fa:"\f2d6"}.fa-etsy{--fa:"\f2d7"}.fa-imdb{--fa:"\f2d8"}.fa-ravelry{--fa:"\f2d9"}.fa-sellcast{--fa:"\f2da"}.fa-superpowers{--fa:"\f2dd"}.fa-wpexplorer{--fa:"\f2de"}.fa-meetup{--fa:"\f2e0"}.fa-font-awesome-alt,.fa-square-font-awesome-stroke{--fa:"\f35c"}.fa-accessible-icon{--fa:"\f368"}.fa-accusoft{--fa:"\f369"}.fa-adversal{--fa:"\f36a"}.fa-affiliatetheme{--fa:"\f36b"}.fa-algolia{--fa:"\f36c"}.fa-amilia{--fa:"\f36d"}.fa-angrycreative{--fa:"\f36e"}.fa-app-store{--fa:"\f36f"}.fa-app-store-ios{--fa:"\f370"}.fa-apper{--fa:"\f371"}.fa-asymmetrik{--fa:"\f372"}.fa-audible{--fa:"\f373"}.fa-avianex{--fa:"\f374"}.fa-aws{--fa:"\f375"}.fa-bimobject{--fa:"\f378"}.fa-bitcoin{--fa:"\f379"}.fa-bity{--fa:"\f37a"}.fa-blackberry{--fa:"\f37b"}.fa-blogger{--fa:"\f37c"}.fa-blogger-b{--fa:"\f37d"}.fa-buromobelexperte{--fa:"\f37f"}.fa-centercode{--fa:"\f380"}.fa-cloudscale{--fa:"\f383"}.fa-cloudsmith{--fa:"\f384"}.fa-cloudversify{--fa:"\f385"}.fa-cpanel{--fa:"\f388"}.fa-css3-alt{--fa:"\f38b"}.fa-cuttlefish{--fa:"\f38c"}.fa-d-and-d{--fa:"\f38d"}.fa-deploydog{--fa:"\f38e"}.fa-deskpro{--fa:"\f38f"}.fa-digital-ocean{--fa:"\f391"}.fa-discord{--fa:"\f392"}.fa-discourse{--fa:"\f393"}.fa-dochub{--fa:"\f394"}.fa-docker{--fa:"\f395"}.fa-draft2digital{--fa:"\f396"}.fa-dribbble-square,.fa-square-dribbble{--fa:"\f397"}.fa-dyalog{--fa:"\f399"}.fa-earlybirds{--fa:"\f39a"}.fa-erlang{--fa:"\f39d"}.fa-facebook-f{--fa:"\f39e"}.fa-facebook-messenger{--fa:"\f39f"}.fa-firstdraft{--fa:"\f3a1"}.fa-fonticons-fi{--fa:"\f3a2"}.fa-fort-awesome-alt{--fa:"\f3a3"}.fa-freebsd{--fa:"\f3a4"}.fa-gitkraken{--fa:"\f3a6"}.fa-gofore{--fa:"\f3a7"}.fa-goodreads{--fa:"\f3a8"}.fa-goodreads-g{--fa:"\f3a9"}.fa-google-drive{--fa:"\f3aa"}.fa-google-play{--fa:"\f3ab"}.fa-gripfire{--fa:"\f3ac"}.fa-grunt{--fa:"\f3ad"}.fa-gulp{--fa:"\f3ae"}.fa-hacker-news-square,.fa-square-hacker-news{--fa:"\f3af"}.fa-hire-a-helper{--fa:"\f3b0"}.fa-hotjar{--fa:"\f3b1"}.fa-hubspot{--fa:"\f3b2"}.fa-itunes{--fa:"\f3b4"}.fa-itunes-note{--fa:"\f3b5"}.fa-jenkins{--fa:"\f3b6"}.fa-joget{--fa:"\f3b7"}.fa-js{--fa:"\f3b8"}.fa-js-square,.fa-square-js{--fa:"\f3b9"}.fa-keycdn{--fa:"\f3ba"}.fa-kickstarter,.fa-square-kickstarter{--fa:"\f3bb"}.fa-kickstarter-k{--fa:"\f3bc"}.fa-laravel{--fa:"\f3bd"}.fa-line{--fa:"\f3c0"}.fa-lyft{--fa:"\f3c3"}.fa-magento{--fa:"\f3c4"}.fa-medapps{--fa:"\f3c6"}.fa-medrt{--fa:"\f3c8"}.fa-microsoft{--fa:"\f3ca"}.fa-mix{--fa:"\f3cb"}.fa-mizuni{--fa:"\f3cc"}.fa-monero{--fa:"\f3d0"}.fa-napster{--fa:"\f3d2"}.fa-node-js{--fa:"\f3d3"}.fa-npm{--fa:"\f3d4"}.fa-ns8{--fa:"\f3d5"}.fa-nutritionix{--fa:"\f3d6"}.fa-page4{--fa:"\f3d7"}.fa-palfed{--fa:"\f3d8"}.fa-patreon{--fa:"\f3d9"}.fa-periscope{--fa:"\f3da"}.fa-phabricator{--fa:"\f3db"}.fa-phoenix-framework{--fa:"\f3dc"}.fa-playstation{--fa:"\f3df"}.fa-pushed{--fa:"\f3e1"}.fa-python{--fa:"\f3e2"}.fa-red-river{--fa:"\f3e3"}.fa-rendact,.fa-wpressr{--fa:"\f3e4"}.fa-replyd{--fa:"\f3e6"}.fa-resolving{--fa:"\f3e7"}.fa-rocketchat{--fa:"\f3e8"}.fa-rockrms{--fa:"\f3e9"}.fa-schlix{--fa:"\f3ea"}.fa-searchengin{--fa:"\f3eb"}.fa-servicestack{--fa:"\f3ec"}.fa-sistrix{--fa:"\f3ee"}.fa-speakap{--fa:"\f3f3"}.fa-staylinked{--fa:"\f3f5"}.fa-steam-symbol{--fa:"\f3f6"}.fa-sticker-mule{--fa:"\f3f7"}.fa-studiovinari{--fa:"\f3f8"}.fa-supple{--fa:"\f3f9"}.fa-uber{--fa:"\f402"}.fa-uikit{--fa:"\f403"}.fa-uniregistry{--fa:"\f404"}.fa-untappd{--fa:"\f405"}.fa-ussunnah{--fa:"\f407"}.fa-vaadin{--fa:"\f408"}.fa-viber{--fa:"\f409"}.fa-vimeo{--fa:"\f40a"}.fa-vnv{--fa:"\f40b"}.fa-square-whatsapp,.fa-whatsapp-square{--fa:"\f40c"}.fa-whmcs{--fa:"\f40d"}.fa-wordpress-simple{--fa:"\f411"}.fa-xbox{--fa:"\f412"}.fa-yandex{--fa:"\f413"}.fa-yandex-international{--fa:"\f414"}.fa-apple-pay{--fa:"\f415"}.fa-cc-apple-pay{--fa:"\f416"}.fa-fly{--fa:"\f417"}.fa-node{--fa:"\f419"}.fa-osi{--fa:"\f41a"}.fa-react{--fa:"\f41b"}.fa-autoprefixer{--fa:"\f41c"}.fa-less{--fa:"\f41d"}.fa-sass{--fa:"\f41e"}.fa-vuejs{--fa:"\f41f"}.fa-angular{--fa:"\f420"}.fa-aviato{--fa:"\f421"}.fa-ember{--fa:"\f423"}.fa-gitter{--fa:"\f426"}.fa-hooli{--fa:"\f427"}.fa-strava{--fa:"\f428"}.fa-stripe{--fa:"\f429"}.fa-stripe-s{--fa:"\f42a"}.fa-typo3{--fa:"\f42b"}.fa-amazon-pay{--fa:"\f42c"}.fa-cc-amazon-pay{--fa:"\f42d"}.fa-ethereum{--fa:"\f42e"}.fa-korvue{--fa:"\f42f"}.fa-elementor{--fa:"\f430"}.fa-square-youtube,.fa-youtube-square{--fa:"\f431"}.fa-flipboard{--fa:"\f44d"}.fa-hips{--fa:"\f452"}.fa-php{--fa:"\f457"}.fa-quinscape{--fa:"\f459"}.fa-readme{--fa:"\f4d5"}.fa-java{--fa:"\f4e4"}.fa-pied-piper-hat{--fa:"\f4e5"}.fa-creative-commons-by{--fa:"\f4e7"}.fa-creative-commons-nc{--fa:"\f4e8"}.fa-creative-commons-nc-eu{--fa:"\f4e9"}.fa-creative-commons-nc-jp{--fa:"\f4ea"}.fa-creative-commons-nd{--fa:"\f4eb"}.fa-creative-commons-pd{--fa:"\f4ec"}.fa-creative-commons-pd-alt{--fa:"\f4ed"}.fa-creative-commons-remix{--fa:"\f4ee"}.fa-creative-commons-sa{--fa:"\f4ef"}.fa-creative-commons-sampling{--fa:"\f4f0"}.fa-creative-commons-sampling-plus{--fa:"\f4f1"}.fa-creative-commons-share{--fa:"\f4f2"}.fa-creative-commons-zero{--fa:"\f4f3"}.fa-ebay{--fa:"\f4f4"}.fa-keybase{--fa:"\f4f5"}.fa-mastodon{--fa:"\f4f6"}.fa-r-project{--fa:"\f4f7"}.fa-researchgate{--fa:"\f4f8"}.fa-teamspeak{--fa:"\f4f9"}.fa-first-order-alt{--fa:"\f50a"}.fa-fulcrum{--fa:"\f50b"}.fa-galactic-republic{--fa:"\f50c"}.fa-galactic-senate{--fa:"\f50d"}.fa-jedi-order{--fa:"\f50e"}.fa-mandalorian{--fa:"\f50f"}.fa-old-republic{--fa:"\f510"}.fa-phoenix-squadron{--fa:"\f511"}.fa-sith{--fa:"\f512"}.fa-trade-federation{--fa:"\f513"}.fa-wolf-pack-battalion{--fa:"\f514"}.fa-hornbill{--fa:"\f592"}.fa-mailchimp{--fa:"\f59e"}.fa-megaport{--fa:"\f5a3"}.fa-nimblr{--fa:"\f5a8"}.fa-rev{--fa:"\f5b2"}.fa-shopware{--fa:"\f5b5"}.fa-squarespace{--fa:"\f5be"}.fa-themeco{--fa:"\f5c6"}.fa-weebly{--fa:"\f5cc"}.fa-wix{--fa:"\f5cf"}.fa-ello{--fa:"\f5f1"}.fa-hackerrank{--fa:"\f5f7"}.fa-kaggle{--fa:"\f5fa"}.fa-markdown{--fa:"\f60f"}.fa-neos{--fa:"\f612"}.fa-zhihu{--fa:"\f63f"}.fa-alipay{--fa:"\f642"}.fa-the-red-yeti{--fa:"\f69d"}.fa-critical-role{--fa:"\f6c9"}.fa-d-and-d-beyond{--fa:"\f6ca"}.fa-dev{--fa:"\f6cc"}.fa-fantasy-flight-games{--fa:"\f6dc"}.fa-wizards-of-the-coast{--fa:"\f730"}.fa-think-peaks{--fa:"\f731"}.fa-reacteurope{--fa:"\f75d"}.fa-artstation{--fa:"\f77a"}.fa-atlassian{--fa:"\f77b"}.fa-canadian-maple-leaf{--fa:"\f785"}.fa-centos{--fa:"\f789"}.fa-confluence{--fa:"\f78d"}.fa-dhl{--fa:"\f790"}.fa-diaspora{--fa:"\f791"}.fa-fedex{--fa:"\f797"}.fa-fedora{--fa:"\f798"}.fa-figma{--fa:"\f799"}.fa-intercom{--fa:"\f7af"}.fa-invision{--fa:"\f7b0"}.fa-jira{--fa:"\f7b1"}.fa-mendeley{--fa:"\f7b3"}.fa-raspberry-pi{--fa:"\f7bb"}.fa-redhat{--fa:"\f7bc"}.fa-sketch{--fa:"\f7c6"}.fa-sourcetree{--fa:"\f7d3"}.fa-suse{--fa:"\f7d6"}.fa-ubuntu{--fa:"\f7df"}.fa-ups{--fa:"\f7e0"}.fa-usps{--fa:"\f7e1"}.fa-yarn{--fa:"\f7e3"}.fa-airbnb{--fa:"\f834"}.fa-battle-net{--fa:"\f835"}.fa-bootstrap{--fa:"\f836"}.fa-buffer{--fa:"\f837"}.fa-chromecast{--fa:"\f838"}.fa-evernote{--fa:"\f839"}.fa-itch-io{--fa:"\f83a"}.fa-salesforce{--fa:"\f83b"}.fa-speaker-deck{--fa:"\f83c"}.fa-symfony{--fa:"\f83d"}.fa-waze{--fa:"\f83f"}.fa-yammer{--fa:"\f840"}.fa-git-alt{--fa:"\f841"}.fa-stackpath{--fa:"\f842"}.fa-cotton-bureau{--fa:"\f89e"}.fa-buy-n-large{--fa:"\f8a6"}.fa-mdb{--fa:"\f8ca"}.fa-orcid{--fa:"\f8d2"}.fa-swift{--fa:"\f8e1"}.fa-umbraco{--fa:"\f8e8"}:host,:root{--fa-font-regular:normal 400 1em/1 var(--fa-family-classic)}@font-face{font-family:"Font Awesome 7 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2)}.far{--fa-family:var(--fa-family-classic)}.fa-regular,.far{--fa-style:400}:host,:root{--fa-family-classic:"Font Awesome 7 Free";--fa-font-solid:normal 900 1em/1 var(--fa-family-classic);--fa-style-family-classic:var(--fa-family-classic)}@font-face{font-family:"Font Awesome 7 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2)}.fas{--fa-style:900}.fa-classic,.fas{--fa-family:var(--fa-family-classic)}.fa-solid{--fa-style:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} \ No newline at end of file diff --git a/backend/src/assets/js/all.min.js b/backend/src/assets/js/all.min.js index a763a12..29ec616 100644 --- a/backend/src/assets/js/all.min.js +++ b/backend/src/assets/js/all.min.js @@ -1,6 +1,6 @@ -/*! - * Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - * Copyright 2025 Fonticons, Inc. - */ +/*! + * Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2025 Fonticons, Inc. + */ (()=>{var c={},l={};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document)}catch(c){}var s=void 0===(s=(c.navigator||{}).userAgent)?"":s;function a(c,l){(null==l||l>c.length)&&(l=c.length);for(var s=0,a=Array(l);s{if("object"!=typeof c||!c)return c;var s=c[Symbol.toPrimitive];if(void 0===s)return("string"===l?String:Number)(c);if("object"!=typeof(s=s.call(c,l||"default")))return s;throw new TypeError("@@toPrimitive must return a primitive value.")})(l,"string"))?a:a+"")in c?Object.defineProperty(c,l,{value:s,enumerable:!0,configurable:!0,writable:!0}):c[l]=s,c;var a}function z(l,c){var s,a=Object.keys(l);return Object.getOwnPropertySymbols&&(s=Object.getOwnPropertySymbols(l),c&&(s=s.filter(function(c){return Object.getOwnPropertyDescriptor(l,c).enumerable})),a.push.apply(a,s)),a}function t(l){for(var c=1;c{if(Array.isArray(c))return a(c)})(c)||(c=>{if("undefined"!=typeof Symbol&&null!=c[Symbol.iterator]||null!=c["@@iterator"])return Array.from(c)})(c)||((c,l)=>{var s;if(c)return"string"==typeof c?a(c,l):"Map"===(s="Object"===(s={}.toString.call(c).slice(8,-1))&&c.constructor?c.constructor.name:s)||"Set"===s?Array.from(c):"Arguments"===s||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s)?a(c,l):void 0})(c)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}c.document,l.documentElement&&l.head&&"function"==typeof l.addEventListener&&l.createElement,~s.indexOf("MSIE")||s.indexOf("Trident/");var l={classic:{fa:"solid",fas:"solid","fa-solid":"solid",far:"regular","fa-regular":"regular",fal:"light","fa-light":"light",fat:"thin","fa-thin":"thin",fab:"brands","fa-brands":"brands"},duotone:{fa:"solid",fad:"solid","fa-solid":"solid","fa-duotone":"solid",fadr:"regular","fa-regular":"regular",fadl:"light","fa-light":"light",fadt:"thin","fa-thin":"thin"},sharp:{fa:"solid",fass:"solid","fa-solid":"solid",fasr:"regular","fa-regular":"regular",fasl:"light","fa-light":"light",fast:"thin","fa-thin":"thin"},"sharp-duotone":{fa:"solid",fasds:"solid","fa-solid":"solid",fasdr:"regular","fa-regular":"regular",fasdl:"light","fa-light":"light",fasdt:"thin","fa-thin":"thin"},slab:{"fa-regular":"regular",faslr:"regular"},"slab-press":{"fa-regular":"regular",faslpr:"regular"},thumbprint:{"fa-light":"light",fatl:"light"},whiteboard:{"fa-semibold":"semibold",fawsb:"semibold"},notdog:{"fa-solid":"solid",fans:"solid"},"notdog-duo":{"fa-solid":"solid",fands:"solid"},etch:{"fa-solid":"solid",faes:"solid"},jelly:{"fa-regular":"regular",fajr:"regular"},"jelly-fill":{"fa-regular":"regular",fajfr:"regular"},"jelly-duo":{"fa-regular":"regular",fajdr:"regular"},chisel:{"fa-regular":"regular",facr:"regular"}},n="classic",s=(e(e(e(e(e(e(e(e(e(e(s={},n,"Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(s,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),{fak:"kit","fa-kit":"kit"}),M={fakd:"kit-duotone","fa-kit-duotone":"kit-duotone"},o=(e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),{kit:"fak"}),f={"kit-duotone":"fakd"},i="duotone-group",m="swap-opacity",L="primary",d="secondary",u=(e(e(e(e(e(e(e(e(e(e(u={},"classic","Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(u,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),[1,2,3,4,5,6,7,8,9,10]),h=u.concat([11,12,13,14,15,16,17,18,19,20]),i=[].concat(r(Object.keys({classic:["fas","far","fal","fat","fad"],duotone:["fadr","fadl","fadt"],sharp:["fass","fasr","fasl","fast"],"sharp-duotone":["fasds","fasdr","fasdl","fasdt"],slab:["faslr"],"slab-press":["faslpr"],whiteboard:["fawsb"],thumbprint:["fatl"],notdog:["fans"],"notdog-duo":["fands"],etch:["faes"],jelly:["fajr"],"jelly-fill":["fajfr"],"jelly-duo":["fajdr"],chisel:["facr"]})),["solid","regular","light","thin","duotone","brands","semibold"],["aw","fw","pull-left","pull-right"],["2xs","xs","sm","lg","xl","2xl","beat","border","fade","beat-fade","bounce","flip-both","flip-horizontal","flip-vertical","flip","inverse","layers","layers-bottom-left","layers-bottom-right","layers-counter","layers-text","layers-top-left","layers-top-right","li","pull-end","pull-start","pulse","rotate-180","rotate-270","rotate-90","rotate-by","shake","spin-pulse","spin-reverse","spin","stack-1x","stack-2x","stack","ul","width-auto","width-fixed",i,m,L,d]).concat(u.map(function(c){return"".concat(c,"x")})).concat(h.map(function(c){return"w-".concat(c)})),m="___FONT_AWESOME___",C=(()=>{try{return!0}catch(c){return!1}})();function g(c){return new Proxy(c,{get:function(c,l){return l in c?c[l]:c[n]}})}(L=t({},l))[n]=t(t(t(t({},{"fa-duotone":"duotone"}),l[n]),s),M),g(L),(d=t({},{chisel:{regular:"facr"},classic:{brands:"fab",light:"fal",regular:"far",solid:"fas",thin:"fat"},duotone:{light:"fadl",regular:"fadr",solid:"fad",thin:"fadt"},etch:{solid:"faes"},jelly:{regular:"fajr"},"jelly-duo":{regular:"fajdr"},"jelly-fill":{regular:"fajfr"},notdog:{solid:"fans"},"notdog-duo":{solid:"fands"},sharp:{light:"fasl",regular:"fasr",solid:"fass",thin:"fast"},"sharp-duotone":{light:"fasdl",regular:"fasdr",solid:"fasds",thin:"fasdt"},slab:{regular:"faslr"},"slab-press":{regular:"faslpr"},thumbprint:{light:"fatl"},whiteboard:{semibold:"fawsb"}}))[n]=t(t(t(t({},{duotone:"fad"}),d[n]),o),f),g(d),(u=t({},{classic:{fab:"fa-brands",fad:"fa-duotone",fal:"fa-light",far:"fa-regular",fas:"fa-solid",fat:"fa-thin"},duotone:{fadr:"fa-regular",fadl:"fa-light",fadt:"fa-thin"},sharp:{fass:"fa-solid",fasr:"fa-regular",fasl:"fa-light",fast:"fa-thin"},"sharp-duotone":{fasds:"fa-solid",fasdr:"fa-regular",fasdl:"fa-light",fasdt:"fa-thin"},slab:{faslr:"fa-regular"},"slab-press":{faslpr:"fa-regular"},whiteboard:{fawsb:"fa-semibold"},thumbprint:{fatl:"fa-light"},notdog:{fans:"fa-solid"},"notdog-duo":{fands:"fa-solid"},etch:{faes:"fa-solid"},jelly:{fajr:"fa-regular"},"jelly-fill":{fajfr:"fa-regular"},"jelly-duo":{fajdr:"fa-regular"},chisel:{facr:"fa-regular"}}))[n]=t(t({},u[n]),{fak:"fa-kit"}),g(u),(h=t({},{classic:{"fa-brands":"fab","fa-duotone":"fad","fa-light":"fal","fa-regular":"far","fa-solid":"fas","fa-thin":"fat"},duotone:{"fa-regular":"fadr","fa-light":"fadl","fa-thin":"fadt"},sharp:{"fa-solid":"fass","fa-regular":"fasr","fa-light":"fasl","fa-thin":"fast"},"sharp-duotone":{"fa-solid":"fasds","fa-regular":"fasdr","fa-light":"fasdl","fa-thin":"fasdt"},slab:{"fa-regular":"faslr"},"slab-press":{"fa-regular":"faslpr"},whiteboard:{"fa-semibold":"fawsb"},thumbprint:{"fa-light":"fatl"},notdog:{"fa-solid":"fans"},"notdog-duo":{"fa-solid":"fands"},etch:{"fa-solid":"faes"},jelly:{"fa-regular":"fajr"},"jelly-fill":{"fa-regular":"fajfr"},"jelly-duo":{"fa-regular":"fajdr"},chisel:{"fa-regular":"facr"}}))[n]=t(t({},h[n]),{"fa-kit":"fak"}),g(h),g(t({},{classic:{900:"fas",400:"far",normal:"far",300:"fal",100:"fat"},duotone:{900:"fad",400:"fadr",300:"fadl",100:"fadt"},sharp:{900:"fass",400:"fasr",300:"fasl",100:"fast"},"sharp-duotone":{900:"fasds",400:"fasdr",300:"fasdl",100:"fasdt"},slab:{400:"faslr"},"slab-press":{400:"faslpr"},whiteboard:{600:"fawsb"},thumbprint:{300:"fatl"},notdog:{900:"fans"},"notdog-duo":{900:"fands"},etch:{900:"faes"},chisel:{400:"facr"},jelly:{400:"fajr"},"jelly-fill":{400:"fajfr"},"jelly-duo":{400:"fajdr"}})),[].concat(r(["kit"]),r(i));(l=c||{})[m]||(l[m]={}),l[m].styles||(l[m].styles={}),l[m].hooks||(l[m].hooks={}),l[m].shims||(l[m].shims=[]);var p=l[m];function b(a){return Object.keys(a).reduce(function(c,l){var s=a[l];return!!s.icon?c[s.iconName]=s.icon:c[l]=s,c},{})}function S(c,l,s){var a=(2{var c={},l={};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document)}catch(c){}var s=void 0===(s=(c.navigator||{}).userAgent)?"":s;function a(c,l){(null==l||l>c.length)&&(l=c.length);for(var s=0,a=Array(l);s{if("object"!=typeof c||!c)return c;var s=c[Symbol.toPrimitive];if(void 0===s)return("string"===l?String:Number)(c);if("object"!=typeof(s=s.call(c,l||"default")))return s;throw new TypeError("@@toPrimitive must return a primitive value.")})(l,"string"))?a:a+"")in c?Object.defineProperty(c,l,{value:s,enumerable:!0,configurable:!0,writable:!0}):c[l]=s,c;var a}function z(l,c){var s,a=Object.keys(l);return Object.getOwnPropertySymbols&&(s=Object.getOwnPropertySymbols(l),c&&(s=s.filter(function(c){return Object.getOwnPropertyDescriptor(l,c).enumerable})),a.push.apply(a,s)),a}function t(l){for(var c=1;c{if(Array.isArray(c))return a(c)})(c)||(c=>{if("undefined"!=typeof Symbol&&null!=c[Symbol.iterator]||null!=c["@@iterator"])return Array.from(c)})(c)||((c,l)=>{var s;if(c)return"string"==typeof c?a(c,l):"Map"===(s="Object"===(s={}.toString.call(c).slice(8,-1))&&c.constructor?c.constructor.name:s)||"Set"===s?Array.from(c):"Arguments"===s||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s)?a(c,l):void 0})(c)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}c.document,l.documentElement&&l.head&&"function"==typeof l.addEventListener&&l.createElement,~s.indexOf("MSIE")||s.indexOf("Trident/");var l={classic:{fa:"solid",fas:"solid","fa-solid":"solid",far:"regular","fa-regular":"regular",fal:"light","fa-light":"light",fat:"thin","fa-thin":"thin",fab:"brands","fa-brands":"brands"},duotone:{fa:"solid",fad:"solid","fa-solid":"solid","fa-duotone":"solid",fadr:"regular","fa-regular":"regular",fadl:"light","fa-light":"light",fadt:"thin","fa-thin":"thin"},sharp:{fa:"solid",fass:"solid","fa-solid":"solid",fasr:"regular","fa-regular":"regular",fasl:"light","fa-light":"light",fast:"thin","fa-thin":"thin"},"sharp-duotone":{fa:"solid",fasds:"solid","fa-solid":"solid",fasdr:"regular","fa-regular":"regular",fasdl:"light","fa-light":"light",fasdt:"thin","fa-thin":"thin"},slab:{"fa-regular":"regular",faslr:"regular"},"slab-press":{"fa-regular":"regular",faslpr:"regular"},thumbprint:{"fa-light":"light",fatl:"light"},whiteboard:{"fa-semibold":"semibold",fawsb:"semibold"},notdog:{"fa-solid":"solid",fans:"solid"},"notdog-duo":{"fa-solid":"solid",fands:"solid"},etch:{"fa-solid":"solid",faes:"solid"},jelly:{"fa-regular":"regular",fajr:"regular"},"jelly-fill":{"fa-regular":"regular",fajfr:"regular"},"jelly-duo":{"fa-regular":"regular",fajdr:"regular"},chisel:{"fa-regular":"regular",facr:"regular"}},n="classic",s=(e(e(e(e(e(e(e(e(e(e(s={},n,"Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(s,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),{fak:"kit","fa-kit":"kit"}),M={fakd:"kit-duotone","fa-kit-duotone":"kit-duotone"},o=(e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),{kit:"fak"}),f={"kit-duotone":"fakd"},i="duotone-group",m="swap-opacity",L="primary",d="secondary",u=(e(e(e(e(e(e(e(e(e(e(u={},"classic","Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(u,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),[1,2,3,4,5,6,7,8,9,10]),h=u.concat([11,12,13,14,15,16,17,18,19,20]),i=[].concat(r(Object.keys({classic:["fas","far","fal","fat","fad"],duotone:["fadr","fadl","fadt"],sharp:["fass","fasr","fasl","fast"],"sharp-duotone":["fasds","fasdr","fasdl","fasdt"],slab:["faslr"],"slab-press":["faslpr"],whiteboard:["fawsb"],thumbprint:["fatl"],notdog:["fans"],"notdog-duo":["fands"],etch:["faes"],jelly:["fajr"],"jelly-fill":["fajfr"],"jelly-duo":["fajdr"],chisel:["facr"]})),["solid","regular","light","thin","duotone","brands","semibold"],["aw","fw","pull-left","pull-right"],["2xs","xs","sm","lg","xl","2xl","beat","border","fade","beat-fade","bounce","flip-both","flip-horizontal","flip-vertical","flip","inverse","layers","layers-bottom-left","layers-bottom-right","layers-counter","layers-text","layers-top-left","layers-top-right","li","pull-end","pull-start","pulse","rotate-180","rotate-270","rotate-90","rotate-by","shake","spin-pulse","spin-reverse","spin","stack-1x","stack-2x","stack","ul","width-auto","width-fixed",i,m,L,d]).concat(u.map(function(c){return"".concat(c,"x")})).concat(h.map(function(c){return"w-".concat(c)})),m="___FONT_AWESOME___",C=(()=>{try{return!0}catch(c){return!1}})();function g(c){return new Proxy(c,{get:function(c,l){return l in c?c[l]:c[n]}})}(L=t({},l))[n]=t(t(t(t({},{"fa-duotone":"duotone"}),l[n]),s),M),g(L),(d=t({},{chisel:{regular:"facr"},classic:{brands:"fab",light:"fal",regular:"far",solid:"fas",thin:"fat"},duotone:{light:"fadl",regular:"fadr",solid:"fad",thin:"fadt"},etch:{solid:"faes"},jelly:{regular:"fajr"},"jelly-duo":{regular:"fajdr"},"jelly-fill":{regular:"fajfr"},notdog:{solid:"fans"},"notdog-duo":{solid:"fands"},sharp:{light:"fasl",regular:"fasr",solid:"fass",thin:"fast"},"sharp-duotone":{light:"fasdl",regular:"fasdr",solid:"fasds",thin:"fasdt"},slab:{regular:"faslr"},"slab-press":{regular:"faslpr"},thumbprint:{light:"fatl"},whiteboard:{semibold:"fawsb"}}))[n]=t(t(t(t({},{duotone:"fad"}),d[n]),o),f),g(d),(u=t({},{classic:{fab:"fa-brands",fad:"fa-duotone",fal:"fa-light",far:"fa-regular",fas:"fa-solid",fat:"fa-thin"},duotone:{fadr:"fa-regular",fadl:"fa-light",fadt:"fa-thin"},sharp:{fass:"fa-solid",fasr:"fa-regular",fasl:"fa-light",fast:"fa-thin"},"sharp-duotone":{fasds:"fa-solid",fasdr:"fa-regular",fasdl:"fa-light",fasdt:"fa-thin"},slab:{faslr:"fa-regular"},"slab-press":{faslpr:"fa-regular"},whiteboard:{fawsb:"fa-semibold"},thumbprint:{fatl:"fa-light"},notdog:{fans:"fa-solid"},"notdog-duo":{fands:"fa-solid"},etch:{faes:"fa-solid"},jelly:{fajr:"fa-regular"},"jelly-fill":{fajfr:"fa-regular"},"jelly-duo":{fajdr:"fa-regular"},chisel:{facr:"fa-regular"}}))[n]=t(t({},u[n]),{fak:"fa-kit"}),g(u),(h=t({},{classic:{"fa-brands":"fab","fa-duotone":"fad","fa-light":"fal","fa-regular":"far","fa-solid":"fas","fa-thin":"fat"},duotone:{"fa-regular":"fadr","fa-light":"fadl","fa-thin":"fadt"},sharp:{"fa-solid":"fass","fa-regular":"fasr","fa-light":"fasl","fa-thin":"fast"},"sharp-duotone":{"fa-solid":"fasds","fa-regular":"fasdr","fa-light":"fasdl","fa-thin":"fasdt"},slab:{"fa-regular":"faslr"},"slab-press":{"fa-regular":"faslpr"},whiteboard:{"fa-semibold":"fawsb"},thumbprint:{"fa-light":"fatl"},notdog:{"fa-solid":"fans"},"notdog-duo":{"fa-solid":"fands"},etch:{"fa-solid":"faes"},jelly:{"fa-regular":"fajr"},"jelly-fill":{"fa-regular":"fajfr"},"jelly-duo":{"fa-regular":"fajdr"},chisel:{"fa-regular":"facr"}}))[n]=t(t({},h[n]),{"fa-kit":"fak"}),g(h),g(t({},{classic:{900:"fas",400:"far",normal:"far",300:"fal",100:"fat"},duotone:{900:"fad",400:"fadr",300:"fadl",100:"fadt"},sharp:{900:"fass",400:"fasr",300:"fasl",100:"fast"},"sharp-duotone":{900:"fasds",400:"fasdr",300:"fasdl",100:"fasdt"},slab:{400:"faslr"},"slab-press":{400:"faslpr"},whiteboard:{600:"fawsb"},thumbprint:{300:"fatl"},notdog:{900:"fans"},"notdog-duo":{900:"fands"},etch:{900:"faes"},chisel:{400:"facr"},jelly:{400:"fajr"},"jelly-fill":{400:"fajfr"},"jelly-duo":{400:"fajdr"}})),[].concat(r(["kit"]),r(i));(l=c||{})[m]||(l[m]={}),l[m].styles||(l[m].styles={}),l[m].hooks||(l[m].hooks={}),l[m].shims||(l[m].shims=[]);var p=l[m];function b(a){return Object.keys(a).reduce(function(c,l){var s=a[l];return!!s.icon?c[s.iconName]=s.icon:c[l]=s,c},{})}function S(c,l,s){var a=(2{var c={},l={};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document)}catch(c){}var s=void 0===(s=(c.navigator||{}).userAgent)?"":s;function a(c,l){(null==l||l>c.length)&&(l=c.length);for(var s=0,a=Array(l);s{if("object"!=typeof c||!c)return c;var s=c[Symbol.toPrimitive];if(void 0===s)return("string"===l?String:Number)(c);if("object"!=typeof(s=s.call(c,l||"default")))return s;throw new TypeError("@@toPrimitive must return a primitive value.")})(l,"string"))?a:a+"")in c?Object.defineProperty(c,l,{value:s,enumerable:!0,configurable:!0,writable:!0}):c[l]=s,c;var a}function z(l,c){var s,a=Object.keys(l);return Object.getOwnPropertySymbols&&(s=Object.getOwnPropertySymbols(l),c&&(s=s.filter(function(c){return Object.getOwnPropertyDescriptor(l,c).enumerable})),a.push.apply(a,s)),a}function t(l){for(var c=1;c{if(Array.isArray(c))return a(c)})(c)||(c=>{if("undefined"!=typeof Symbol&&null!=c[Symbol.iterator]||null!=c["@@iterator"])return Array.from(c)})(c)||((c,l)=>{var s;if(c)return"string"==typeof c?a(c,l):"Map"===(s="Object"===(s={}.toString.call(c).slice(8,-1))&&c.constructor?c.constructor.name:s)||"Set"===s?Array.from(c):"Arguments"===s||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s)?a(c,l):void 0})(c)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}c.document,l.documentElement&&l.head&&"function"==typeof l.addEventListener&&l.createElement,~s.indexOf("MSIE")||s.indexOf("Trident/");var l={classic:{fa:"solid",fas:"solid","fa-solid":"solid",far:"regular","fa-regular":"regular",fal:"light","fa-light":"light",fat:"thin","fa-thin":"thin",fab:"brands","fa-brands":"brands"},duotone:{fa:"solid",fad:"solid","fa-solid":"solid","fa-duotone":"solid",fadr:"regular","fa-regular":"regular",fadl:"light","fa-light":"light",fadt:"thin","fa-thin":"thin"},sharp:{fa:"solid",fass:"solid","fa-solid":"solid",fasr:"regular","fa-regular":"regular",fasl:"light","fa-light":"light",fast:"thin","fa-thin":"thin"},"sharp-duotone":{fa:"solid",fasds:"solid","fa-solid":"solid",fasdr:"regular","fa-regular":"regular",fasdl:"light","fa-light":"light",fasdt:"thin","fa-thin":"thin"},slab:{"fa-regular":"regular",faslr:"regular"},"slab-press":{"fa-regular":"regular",faslpr:"regular"},thumbprint:{"fa-light":"light",fatl:"light"},whiteboard:{"fa-semibold":"semibold",fawsb:"semibold"},notdog:{"fa-solid":"solid",fans:"solid"},"notdog-duo":{"fa-solid":"solid",fands:"solid"},etch:{"fa-solid":"solid",faes:"solid"},jelly:{"fa-regular":"regular",fajr:"regular"},"jelly-fill":{"fa-regular":"regular",fajfr:"regular"},"jelly-duo":{"fa-regular":"regular",fajdr:"regular"},chisel:{"fa-regular":"regular",facr:"regular"}},n="classic",s=(e(e(e(e(e(e(e(e(e(e(s={},n,"Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(s,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),{fak:"kit","fa-kit":"kit"}),M={fakd:"kit-duotone","fa-kit-duotone":"kit-duotone"},o=(e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),{kit:"fak"}),f={"kit-duotone":"fakd"},i="duotone-group",m="swap-opacity",L="primary",d="secondary",u=(e(e(e(e(e(e(e(e(e(e(u={},"classic","Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(u,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),[1,2,3,4,5,6,7,8,9,10]),h=u.concat([11,12,13,14,15,16,17,18,19,20]),i=[].concat(r(Object.keys({classic:["fas","far","fal","fat","fad"],duotone:["fadr","fadl","fadt"],sharp:["fass","fasr","fasl","fast"],"sharp-duotone":["fasds","fasdr","fasdl","fasdt"],slab:["faslr"],"slab-press":["faslpr"],whiteboard:["fawsb"],thumbprint:["fatl"],notdog:["fans"],"notdog-duo":["fands"],etch:["faes"],jelly:["fajr"],"jelly-fill":["fajfr"],"jelly-duo":["fajdr"],chisel:["facr"]})),["solid","regular","light","thin","duotone","brands","semibold"],["aw","fw","pull-left","pull-right"],["2xs","xs","sm","lg","xl","2xl","beat","border","fade","beat-fade","bounce","flip-both","flip-horizontal","flip-vertical","flip","inverse","layers","layers-bottom-left","layers-bottom-right","layers-counter","layers-text","layers-top-left","layers-top-right","li","pull-end","pull-start","pulse","rotate-180","rotate-270","rotate-90","rotate-by","shake","spin-pulse","spin-reverse","spin","stack-1x","stack-2x","stack","ul","width-auto","width-fixed",i,m,L,d]).concat(u.map(function(c){return"".concat(c,"x")})).concat(h.map(function(c){return"w-".concat(c)})),m="___FONT_AWESOME___",C=(()=>{try{return!0}catch(c){return!1}})();function g(c){return new Proxy(c,{get:function(c,l){return l in c?c[l]:c[n]}})}(L=t({},l))[n]=t(t(t(t({},{"fa-duotone":"duotone"}),l[n]),s),M),g(L),(d=t({},{chisel:{regular:"facr"},classic:{brands:"fab",light:"fal",regular:"far",solid:"fas",thin:"fat"},duotone:{light:"fadl",regular:"fadr",solid:"fad",thin:"fadt"},etch:{solid:"faes"},jelly:{regular:"fajr"},"jelly-duo":{regular:"fajdr"},"jelly-fill":{regular:"fajfr"},notdog:{solid:"fans"},"notdog-duo":{solid:"fands"},sharp:{light:"fasl",regular:"fasr",solid:"fass",thin:"fast"},"sharp-duotone":{light:"fasdl",regular:"fasdr",solid:"fasds",thin:"fasdt"},slab:{regular:"faslr"},"slab-press":{regular:"faslpr"},thumbprint:{light:"fatl"},whiteboard:{semibold:"fawsb"}}))[n]=t(t(t(t({},{duotone:"fad"}),d[n]),o),f),g(d),(u=t({},{classic:{fab:"fa-brands",fad:"fa-duotone",fal:"fa-light",far:"fa-regular",fas:"fa-solid",fat:"fa-thin"},duotone:{fadr:"fa-regular",fadl:"fa-light",fadt:"fa-thin"},sharp:{fass:"fa-solid",fasr:"fa-regular",fasl:"fa-light",fast:"fa-thin"},"sharp-duotone":{fasds:"fa-solid",fasdr:"fa-regular",fasdl:"fa-light",fasdt:"fa-thin"},slab:{faslr:"fa-regular"},"slab-press":{faslpr:"fa-regular"},whiteboard:{fawsb:"fa-semibold"},thumbprint:{fatl:"fa-light"},notdog:{fans:"fa-solid"},"notdog-duo":{fands:"fa-solid"},etch:{faes:"fa-solid"},jelly:{fajr:"fa-regular"},"jelly-fill":{fajfr:"fa-regular"},"jelly-duo":{fajdr:"fa-regular"},chisel:{facr:"fa-regular"}}))[n]=t(t({},u[n]),{fak:"fa-kit"}),g(u),(h=t({},{classic:{"fa-brands":"fab","fa-duotone":"fad","fa-light":"fal","fa-regular":"far","fa-solid":"fas","fa-thin":"fat"},duotone:{"fa-regular":"fadr","fa-light":"fadl","fa-thin":"fadt"},sharp:{"fa-solid":"fass","fa-regular":"fasr","fa-light":"fasl","fa-thin":"fast"},"sharp-duotone":{"fa-solid":"fasds","fa-regular":"fasdr","fa-light":"fasdl","fa-thin":"fasdt"},slab:{"fa-regular":"faslr"},"slab-press":{"fa-regular":"faslpr"},whiteboard:{"fa-semibold":"fawsb"},thumbprint:{"fa-light":"fatl"},notdog:{"fa-solid":"fans"},"notdog-duo":{"fa-solid":"fands"},etch:{"fa-solid":"faes"},jelly:{"fa-regular":"fajr"},"jelly-fill":{"fa-regular":"fajfr"},"jelly-duo":{"fa-regular":"fajdr"},chisel:{"fa-regular":"facr"}}))[n]=t(t({},h[n]),{"fa-kit":"fak"}),g(h),g(t({},{classic:{900:"fas",400:"far",normal:"far",300:"fal",100:"fat"},duotone:{900:"fad",400:"fadr",300:"fadl",100:"fadt"},sharp:{900:"fass",400:"fasr",300:"fasl",100:"fast"},"sharp-duotone":{900:"fasds",400:"fasdr",300:"fasdl",100:"fasdt"},slab:{400:"faslr"},"slab-press":{400:"faslpr"},whiteboard:{600:"fawsb"},thumbprint:{300:"fatl"},notdog:{900:"fans"},"notdog-duo":{900:"fands"},etch:{900:"faes"},chisel:{400:"facr"},jelly:{400:"fajr"},"jelly-fill":{400:"fajfr"},"jelly-duo":{400:"fajdr"}})),[].concat(r(["kit"]),r(i));(l=c||{})[m]||(l[m]={}),l[m].styles||(l[m].styles={}),l[m].hooks||(l[m].hooks={}),l[m].shims||(l[m].shims=[]);var p=l[m];function b(a){return Object.keys(a).reduce(function(c,l){var s=a[l];return!!s.icon?c[s.iconName]=s.icon:c[l]=s,c},{})}function S(c,l,s){var a=(2{function I(c,l){(null==l||l>c.length)&&(l=c.length);for(var s=0,a=Array(l);s=c.length?{done:!0}:{done:!1,value:c[z++]}},e:function(c){throw c},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function a(c,l,s){return(l=R(l))in c?Object.defineProperty(c,l,{value:s,enumerable:!0,configurable:!0,writable:!0}):c[l]=s,c}function W(l,c){var s,a=Object.keys(l);return Object.getOwnPropertySymbols&&(s=Object.getOwnPropertySymbols(l),c&&(s=s.filter(function(c){return Object.getOwnPropertyDescriptor(l,c).enumerable})),a.push.apply(a,s)),a}function u(l){for(var c=1;c{if(Array.isArray(c))return c})(c)||((c,l)=>{var s=null==c?null:"undefined"!=typeof Symbol&&c[Symbol.iterator]||c["@@iterator"];if(null!=s){var a,e,z,t,r=[],n=!0,M=!1;try{if(z=(s=s.call(c)).next,0===l){if(Object(s)!==s)return;n=!1}else for(;!(n=(a=z.call(s)).done)&&(r.push(a.value),r.length!==l);n=!0);}catch(c){M=!0,e=c}finally{try{if(!n&&null!=s.return&&(t=s.return(),Object(t)!==t))return}finally{if(M)throw e}}return r}})(c,l)||_(c,l)||(()=>{throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function h(c){return(c=>{if(Array.isArray(c))return I(c)})(c)||(c=>{if("undefined"!=typeof Symbol&&null!=c[Symbol.iterator]||null!=c["@@iterator"])return Array.from(c)})(c)||_(c)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function R(c){var l=((c,l)=>{if("object"!=typeof c||!c)return c;var s=c[Symbol.toPrimitive];if(void 0===s)return("string"===l?String:Number)(c);if("object"!=typeof(s=s.call(c,l||"default")))return s;throw new TypeError("@@toPrimitive must return a primitive value.")})(c,"string");return"symbol"==typeof l?l:l+""}function J(c){return(J="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(c){return typeof c}:function(c){return c&&"function"==typeof Symbol&&c.constructor===Symbol&&c!==Symbol.prototype?"symbol":typeof c})(c)}function _(c,l){var s;if(c)return"string"==typeof c?I(c,l):"Map"===(s="Object"===(s={}.toString.call(c).slice(8,-1))&&c.constructor?c.constructor.name:s)||"Set"===s?Array.from(c):"Arguments"===s||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s)?I(c,l):void 0}function Y(){}var c={},l={},s=null,e={mark:Y,measure:Y};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document),"undefined"!=typeof MutationObserver&&(s=MutationObserver),"undefined"!=typeof performance&&(e=performance)}catch(V){}var z=void 0===(z=(c.navigator||{}).userAgent)?"":z,C=c,g=l,K=s,c=e,H=!!C.document,i=!!g.documentElement&&!!g.head&&"function"==typeof g.addEventListener&&"function"==typeof g.createElement,U=~z.indexOf("MSIE")||~z.indexOf("Trident/"),l={classic:{fa:"solid",fas:"solid","fa-solid":"solid",far:"regular","fa-regular":"regular",fal:"light","fa-light":"light",fat:"thin","fa-thin":"thin",fab:"brands","fa-brands":"brands"},duotone:{fa:"solid",fad:"solid","fa-solid":"solid","fa-duotone":"solid",fadr:"regular","fa-regular":"regular",fadl:"light","fa-light":"light",fadt:"thin","fa-thin":"thin"},sharp:{fa:"solid",fass:"solid","fa-solid":"solid",fasr:"regular","fa-regular":"regular",fasl:"light","fa-light":"light",fast:"thin","fa-thin":"thin"},"sharp-duotone":{fa:"solid",fasds:"solid","fa-solid":"solid",fasdr:"regular","fa-regular":"regular",fasdl:"light","fa-light":"light",fasdt:"thin","fa-thin":"thin"},slab:{"fa-regular":"regular",faslr:"regular"},"slab-press":{"fa-regular":"regular",faslpr:"regular"},thumbprint:{"fa-light":"light",fatl:"light"},whiteboard:{"fa-semibold":"semibold",fawsb:"semibold"},notdog:{"fa-solid":"solid",fans:"solid"},"notdog-duo":{"fa-solid":"solid",fands:"solid"},etch:{"fa-solid":"solid",faes:"solid"},jelly:{"fa-regular":"regular",fajr:"regular"},"jelly-fill":{"fa-regular":"regular",fajfr:"regular"},"jelly-duo":{"fa-regular":"regular",fajdr:"regular"},chisel:{"fa-regular":"regular",facr:"regular"}},B=["fa-classic","fa-duotone","fa-sharp","fa-sharp-duotone","fa-thumbprint","fa-whiteboard","fa-notdog","fa-notdog-duo","fa-chisel","fa-etch","fa-jelly","fa-jelly-fill","fa-jelly-duo","fa-slab","fa-slab-press"],L="classic",d="duotone",V="thumbprint",X=[L,d,"sharp",s="sharp-duotone","chisel","etch","jelly",e="jelly-duo",z="jelly-fill","notdog",Z="notdog-duo","slab",t="slab-press",V,r="whiteboard"],$=(a(a(a(a(a(a(a(a(a(a(Q={},L,"Classic"),d,"Duotone"),"sharp","Sharp"),s,"Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),e,"Jelly Duo"),z,"Jelly Fill"),"notdog","Notdog"),a(a(a(a(a(Q,Z,"Notdog Duo"),"slab","Slab"),t,"Slab Press"),V,"Thumbprint"),r,"Whiteboard"),new Map([["classic",{defaultShortPrefixId:"fas",defaultStyleId:"solid",styleIds:["solid","regular","light","thin","brands"],futureStyleIds:[],defaultFontWeight:900}],["duotone",{defaultShortPrefixId:"fad",defaultStyleId:"solid",styleIds:["solid","regular","light","thin"],futureStyleIds:[],defaultFontWeight:900}],["sharp",{defaultShortPrefixId:"fass",defaultStyleId:"solid",styleIds:["solid","regular","light","thin"],futureStyleIds:[],defaultFontWeight:900}],["sharp-duotone",{defaultShortPrefixId:"fasds",defaultStyleId:"solid",styleIds:["solid","regular","light","thin"],futureStyleIds:[],defaultFontWeight:900}],["chisel",{defaultShortPrefixId:"facr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["etch",{defaultShortPrefixId:"faes",defaultStyleId:"solid",styleIds:["solid"],futureStyleIds:[],defaultFontWeight:900}],["jelly",{defaultShortPrefixId:"fajr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["jelly-duo",{defaultShortPrefixId:"fajdr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["jelly-fill",{defaultShortPrefixId:"fajfr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["notdog",{defaultShortPrefixId:"fans",defaultStyleId:"solid",styleIds:["solid"],futureStyleIds:[],defaultFontWeight:900}],["notdog-duo",{defaultShortPrefixId:"fands",defaultStyleId:"solid",styleIds:["solid"],futureStyleIds:[],defaultFontWeight:900}],["slab",{defaultShortPrefixId:"faslr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["slab-press",{defaultShortPrefixId:"faslpr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["thumbprint",{defaultShortPrefixId:"fatl",defaultStyleId:"light",styleIds:["light"],futureStyleIds:[],defaultFontWeight:300}],["whiteboard",{defaultShortPrefixId:"fawsb",defaultStyleId:"semibold",styleIds:["semibold"],futureStyleIds:[],defaultFontWeight:600}]])),G=["fak","fa-kit","fakd","fa-kit-duotone"],s={fak:"kit","fa-kit":"kit"},e={fakd:"kit-duotone","fa-kit-duotone":"kit-duotone"},z=(a(a({},"kit","Kit"),"kit-duotone","Kit Duotone"),{kit:"fak"}),Q={"kit-duotone":"fakd"},Z="duotone-group",t="swap-opacity",r="primary",n="secondary",c1=(a(a(a(a(a(a(a(a(a(a(F={},"classic","Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),a(a(a(a(a(F,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),a(a({},"kit","Kit"),"kit-duotone","Kit Duotone"),{classic:{fab:"fa-brands",fad:"fa-duotone",fal:"fa-light",far:"fa-regular",fas:"fa-solid",fat:"fa-thin"},duotone:{fadr:"fa-regular",fadl:"fa-light",fadt:"fa-thin"},sharp:{fass:"fa-solid",fasr:"fa-regular",fasl:"fa-light",fast:"fa-thin"},"sharp-duotone":{fasds:"fa-solid",fasdr:"fa-regular",fasdl:"fa-light",fasdt:"fa-thin"},slab:{faslr:"fa-regular"},"slab-press":{faslpr:"fa-regular"},whiteboard:{fawsb:"fa-semibold"},thumbprint:{fatl:"fa-light"},notdog:{fans:"fa-solid"},"notdog-duo":{fands:"fa-solid"},etch:{faes:"fa-solid"},jelly:{fajr:"fa-regular"},"jelly-fill":{fajfr:"fa-regular"},"jelly-duo":{fajdr:"fa-regular"},chisel:{facr:"fa-regular"}}),l1=["fa","fas","far","fal","fat","fad","fadr","fadl","fadt","fab","fass","fasr","fasl","fast","fasds","fasdr","fasdl","fasdt","faslr","faslpr","fawsb","fatl","fans","fands","faes","fajr","fajfr","fajdr","facr"].concat(["fa-classic","fa-duotone","fa-sharp","fa-sharp-duotone","fa-thumbprint","fa-whiteboard","fa-notdog","fa-notdog-duo","fa-chisel","fa-etch","fa-jelly","fa-jelly-fill","fa-jelly-duo","fa-slab","fa-slab-press"],["fa-solid","fa-regular","fa-light","fa-thin","fa-duotone","fa-brands","fa-semibold"]),M=(F=[1,2,3,4,5,6,7,8,9,10]).concat([11,12,13,14,15,16,17,18,19,20]),Z=[].concat(h(Object.keys({classic:["fas","far","fal","fat","fad"],duotone:["fadr","fadl","fadt"],sharp:["fass","fasr","fasl","fast"],"sharp-duotone":["fasds","fasdr","fasdl","fasdt"],slab:["faslr"],"slab-press":["faslpr"],whiteboard:["fawsb"],thumbprint:["fatl"],notdog:["fans"],"notdog-duo":["fands"],etch:["faes"],jelly:["fajr"],"jelly-fill":["fajfr"],"jelly-duo":["fajdr"],chisel:["facr"]})),["solid","regular","light","thin","duotone","brands","semibold"],["aw","fw","pull-left","pull-right"],["2xs","xs","sm","lg","xl","2xl","beat","border","fade","beat-fade","bounce","flip-both","flip-horizontal","flip-vertical","flip","inverse","layers","layers-bottom-left","layers-bottom-right","layers-counter","layers-text","layers-top-left","layers-top-right","li","pull-end","pull-start","pulse","rotate-180","rotate-270","rotate-90","rotate-by","shake","spin-pulse","spin-reverse","spin","stack-1x","stack-2x","stack","ul","width-auto","width-fixed",Z,t,r,n]).concat(F.map(function(c){return"".concat(c,"x")})).concat(M.map(function(c){return"w-".concat(c)})),t="___FONT_AWESOME___",s1=16,a1="svg-inline--fa",p="data-fa-i2svg",e1="data-fa-pseudo-element",z1="data-fa-pseudo-element-pending",t1="data-prefix",r1="data-icon",n1="fontawesome-i2svg",M1="async",o1=["HTML","HEAD","STYLE","SCRIPT"],f1=["::before","::after",":before",":after"],i1=(()=>{try{return!0}catch(c){return!1}})();function o(c){return new Proxy(c,{get:function(c,l){return l in c?c[l]:c[L]}})}(r=u({},l))[L]=u(u(u(u({},{"fa-duotone":"duotone"}),l[L]),s),e);var m1=o(r),L1=((n=u({},{chisel:{regular:"facr"},classic:{brands:"fab",light:"fal",regular:"far",solid:"fas",thin:"fat"},duotone:{light:"fadl",regular:"fadr",solid:"fad",thin:"fadt"},etch:{solid:"faes"},jelly:{regular:"fajr"},"jelly-duo":{regular:"fajdr"},"jelly-fill":{regular:"fajfr"},notdog:{solid:"fans"},"notdog-duo":{solid:"fands"},sharp:{light:"fasl",regular:"fasr",solid:"fass",thin:"fast"},"sharp-duotone":{light:"fasdl",regular:"fasdr",solid:"fasds",thin:"fasdt"},slab:{regular:"faslr"},"slab-press":{regular:"faslpr"},thumbprint:{light:"fatl"},whiteboard:{semibold:"fawsb"}}))[L]=u(u(u(u({},{duotone:"fad"}),n[L]),z),Q),o(n)),d1=((F=u({},c1))[L]=u(u({},F[L]),{fak:"fa-kit"}),o(F)),u1=((M=u({},{classic:{"fa-brands":"fab","fa-duotone":"fad","fa-light":"fal","fa-regular":"far","fa-solid":"fas","fa-thin":"fat"},duotone:{"fa-regular":"fadr","fa-light":"fadl","fa-thin":"fadt"},sharp:{"fa-solid":"fass","fa-regular":"fasr","fa-light":"fasl","fa-thin":"fast"},"sharp-duotone":{"fa-solid":"fasds","fa-regular":"fasdr","fa-light":"fasdl","fa-thin":"fasdt"},slab:{"fa-regular":"faslr"},"slab-press":{"fa-regular":"faslpr"},whiteboard:{"fa-semibold":"fawsb"},thumbprint:{"fa-light":"fatl"},notdog:{"fa-solid":"fans"},"notdog-duo":{"fa-solid":"fands"},etch:{"fa-solid":"faes"},jelly:{"fa-regular":"fajr"},"jelly-fill":{"fa-regular":"fajfr"},"jelly-duo":{"fa-regular":"fajdr"},chisel:{"fa-regular":"facr"}}))[L]=u(u({},M[L]),{"fa-kit":"fak"}),o(M),/fa(k|kd|s|r|l|t|d|dr|dl|dt|b|slr|slpr|wsb|tl|ns|nds|es|jr|jfr|jdr|cr|ss|sr|sl|st|sds|sdr|sdl|sdt)?[\-\ ]/),h1="fa-layers-text",C1=/Font ?Awesome ?([567 ]*)(Solid|Regular|Light|Thin|Duotone|Brands|Free|Pro|Sharp Duotone|Sharp|Kit|Notdog Duo|Notdog|Chisel|Etch|Thumbprint|Jelly Fill|Jelly Duo|Jelly|Slab Press|Slab|Whiteboard)?.*/i,g1=(o(u({},{classic:{900:"fas",400:"far",normal:"far",300:"fal",100:"fat"},duotone:{900:"fad",400:"fadr",300:"fadl",100:"fadt"},sharp:{900:"fass",400:"fasr",300:"fasl",100:"fast"},"sharp-duotone":{900:"fasds",400:"fasdr",300:"fasdl",100:"fasdt"},slab:{400:"faslr"},"slab-press":{400:"faslpr"},whiteboard:{600:"fawsb"},thumbprint:{300:"fatl"},notdog:{900:"fans"},"notdog-duo":{900:"fands"},etch:{900:"faes"},chisel:{400:"facr"},jelly:{400:"fajr"},"jelly-fill":{400:"fajfr"},"jelly-duo":{400:"fajdr"}})),["class","data-prefix","data-icon","data-fa-transform","data-fa-mask"]),p1={GROUP:"duotone-group",SWAP_OPACITY:"swap-opacity",PRIMARY:"primary",SECONDARY:"secondary"},b1=[].concat(h(["kit"]),h(Z)),f=C.FontAwesomeConfig||{},l=(g&&"function"==typeof g.querySelector&&[["data-family-prefix","familyPrefix"],["data-css-prefix","cssPrefix"],["data-family-default","familyDefault"],["data-style-default","styleDefault"],["data-replacement-class","replacementClass"],["data-auto-replace-svg","autoReplaceSvg"],["data-auto-add-css","autoAddCss"],["data-search-pseudo-elements","searchPseudoElements"],["data-search-pseudo-elements-warnings","searchPseudoElementsWarnings"],["data-search-pseudo-elements-full-scan","searchPseudoElementsFullScan"],["data-observe-mutations","observeMutations"],["data-mutate-approach","mutateApproach"],["data-keep-original-source","keepOriginalSource"],["data-measure-performance","measurePerformance"],["data-show-missing-icons","showMissingIcons"]].forEach(function(c){var l=m(c,2),s=l[0],l=l[1],s=""===(c=(c=>{var l=g.querySelector("script["+c+"]");if(l)return l.getAttribute(c)})(s))||"false"!==c&&("true"===c||c);null!=s&&(f[l]=s)}),{styleDefault:"solid",familyDefault:L,cssPrefix:"fa",replacementClass:a1,autoReplaceSvg:!0,autoAddCss:!0,searchPseudoElements:!1,searchPseudoElementsWarnings:!0,searchPseudoElementsFullScan:!1,observeMutations:!0,mutateApproach:"async",keepOriginalSource:!0,measurePerformance:!1,showMissingIcons:!0}),b=(f.familyPrefix&&(f.cssPrefix=f.familyPrefix),u(u({},l),f)),S=(b.autoReplaceSvg||(b.observeMutations=!1),{}),S1=(Object.keys(l).forEach(function(l){Object.defineProperty(S,l,{enumerable:!0,set:function(c){b[l]=c,S1.forEach(function(c){return c(S)})},get:function(){return b[l]}})}),Object.defineProperty(S,"familyPrefix",{enumerable:!0,set:function(c){b.cssPrefix=c,S1.forEach(function(c){return c(S)})},get:function(){return b.cssPrefix}}),C.FontAwesomeConfig=S,[]),y=s1,v={size:16,x:0,y:0,rotate:0,flipX:!1,flipY:!1};function y1(){for(var c=12,l="";0>>0;s--;)l[s]=c[s];return l}function v1(c){return c.classList?w(c.classList):(c.getAttribute("class")||"").split(" ").filter(function(c){return c})}function w1(c){return"".concat(c).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function k1(s){return Object.keys(s||{}).reduce(function(c,l){return c+"".concat(l,": ").concat(s[l].trim(),";")},"")}function x1(c){return c.size!==v.size||c.x!==v.x||c.y!==v.y||c.rotate!==v.rotate||c.flipX||c.flipY}function j1(){var c,l,s=a1,a=S.cssPrefix,e=S.replacementClass,z=':root, :host {\n --fa-font-solid: normal 900 1em/1 "Font Awesome 7 Free";\n --fa-font-regular: normal 400 1em/1 "Font Awesome 7 Free";\n --fa-font-light: normal 300 1em/1 "Font Awesome 7 Pro";\n --fa-font-thin: normal 100 1em/1 "Font Awesome 7 Pro";\n --fa-font-duotone: normal 900 1em/1 "Font Awesome 7 Duotone";\n --fa-font-duotone-regular: normal 400 1em/1 "Font Awesome 7 Duotone";\n --fa-font-duotone-light: normal 300 1em/1 "Font Awesome 7 Duotone";\n --fa-font-duotone-thin: normal 100 1em/1 "Font Awesome 7 Duotone";\n --fa-font-brands: normal 400 1em/1 "Font Awesome 7 Brands";\n --fa-font-sharp-solid: normal 900 1em/1 "Font Awesome 7 Sharp";\n --fa-font-sharp-regular: normal 400 1em/1 "Font Awesome 7 Sharp";\n --fa-font-sharp-light: normal 300 1em/1 "Font Awesome 7 Sharp";\n --fa-font-sharp-thin: normal 100 1em/1 "Font Awesome 7 Sharp";\n --fa-font-sharp-duotone-solid: normal 900 1em/1 "Font Awesome 7 Sharp Duotone";\n --fa-font-sharp-duotone-regular: normal 400 1em/1 "Font Awesome 7 Sharp Duotone";\n --fa-font-sharp-duotone-light: normal 300 1em/1 "Font Awesome 7 Sharp Duotone";\n --fa-font-sharp-duotone-thin: normal 100 1em/1 "Font Awesome 7 Sharp Duotone";\n --fa-font-slab-regular: normal 400 1em/1 "Font Awesome 7 Slab";\n --fa-font-slab-press-regular: normal 400 1em/1 "Font Awesome 7 Slab Press";\n --fa-font-whiteboard-semibold: normal 600 1em/1 "Font Awesome 7 Whiteboard";\n --fa-font-thumbprint-light: normal 300 1em/1 "Font Awesome 7 Thumbprint";\n --fa-font-notdog-solid: normal 900 1em/1 "Font Awesome 7 Notdog";\n --fa-font-notdog-duo-solid: normal 900 1em/1 "Font Awesome 7 Notdog Duo";\n --fa-font-etch-solid: normal 900 1em/1 "Font Awesome 7 Etch";\n --fa-font-jelly-regular: normal 400 1em/1 "Font Awesome 7 Jelly";\n --fa-font-jelly-fill-regular: normal 400 1em/1 "Font Awesome 7 Jelly Fill";\n --fa-font-jelly-duo-regular: normal 400 1em/1 "Font Awesome 7 Jelly Duo";\n --fa-font-chisel-regular: normal 400 1em/1 "Font Awesome 7 Chisel";\n}\n\n.svg-inline--fa {\n box-sizing: content-box;\n display: var(--fa-display, inline-block);\n height: 1em;\n overflow: visible;\n vertical-align: -0.125em;\n width: var(--fa-width, 1.25em);\n}\n.svg-inline--fa.fa-2xs {\n vertical-align: 0.1em;\n}\n.svg-inline--fa.fa-xs {\n vertical-align: 0em;\n}\n.svg-inline--fa.fa-sm {\n vertical-align: -0.0714285714em;\n}\n.svg-inline--fa.fa-lg {\n vertical-align: -0.2em;\n}\n.svg-inline--fa.fa-xl {\n vertical-align: -0.25em;\n}\n.svg-inline--fa.fa-2xl {\n vertical-align: -0.3125em;\n}\n.svg-inline--fa.fa-pull-left,\n.svg-inline--fa .fa-pull-start {\n float: inline-start;\n margin-inline-end: var(--fa-pull-margin, 0.3em);\n}\n.svg-inline--fa.fa-pull-right,\n.svg-inline--fa .fa-pull-end {\n float: inline-end;\n margin-inline-start: var(--fa-pull-margin, 0.3em);\n}\n.svg-inline--fa.fa-li {\n width: var(--fa-li-width, 2em);\n inset-inline-start: calc(-1 * var(--fa-li-width, 2em));\n inset-block-start: 0.25em; /* syncing vertical alignment with Web Font rendering */\n}\n\n.fa-layers-counter, .fa-layers-text {\n display: inline-block;\n position: absolute;\n text-align: center;\n}\n\n.fa-layers {\n display: inline-block;\n height: 1em;\n position: relative;\n text-align: center;\n vertical-align: -0.125em;\n width: var(--fa-width, 1.25em);\n}\n.fa-layers .svg-inline--fa {\n inset: 0;\n margin: auto;\n position: absolute;\n transform-origin: center center;\n}\n\n.fa-layers-text {\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n transform-origin: center center;\n}\n\n.fa-layers-counter {\n background-color: var(--fa-counter-background-color, #ff253a);\n border-radius: var(--fa-counter-border-radius, 1em);\n box-sizing: border-box;\n color: var(--fa-inverse, #fff);\n line-height: var(--fa-counter-line-height, 1);\n max-width: var(--fa-counter-max-width, 5em);\n min-width: var(--fa-counter-min-width, 1.5em);\n overflow: hidden;\n padding: var(--fa-counter-padding, 0.25em 0.5em);\n right: var(--fa-right, 0);\n text-overflow: ellipsis;\n top: var(--fa-top, 0);\n transform: scale(var(--fa-counter-scale, 0.25));\n transform-origin: top right;\n}\n\n.fa-layers-bottom-right {\n bottom: var(--fa-bottom, 0);\n right: var(--fa-right, 0);\n top: auto;\n transform: scale(var(--fa-layers-scale, 0.25));\n transform-origin: bottom right;\n}\n\n.fa-layers-bottom-left {\n bottom: var(--fa-bottom, 0);\n left: var(--fa-left, 0);\n right: auto;\n top: auto;\n transform: scale(var(--fa-layers-scale, 0.25));\n transform-origin: bottom left;\n}\n\n.fa-layers-top-right {\n top: var(--fa-top, 0);\n right: var(--fa-right, 0);\n transform: scale(var(--fa-layers-scale, 0.25));\n transform-origin: top right;\n}\n\n.fa-layers-top-left {\n left: var(--fa-left, 0);\n right: auto;\n top: var(--fa-top, 0);\n transform: scale(var(--fa-layers-scale, 0.25));\n transform-origin: top left;\n}\n\n.fa-1x {\n font-size: 1em;\n}\n\n.fa-2x {\n font-size: 2em;\n}\n\n.fa-3x {\n font-size: 3em;\n}\n\n.fa-4x {\n font-size: 4em;\n}\n\n.fa-5x {\n font-size: 5em;\n}\n\n.fa-6x {\n font-size: 6em;\n}\n\n.fa-7x {\n font-size: 7em;\n}\n\n.fa-8x {\n font-size: 8em;\n}\n\n.fa-9x {\n font-size: 9em;\n}\n\n.fa-10x {\n font-size: 10em;\n}\n\n.fa-2xs {\n font-size: calc(10 / 16 * 1em); /* converts a 10px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 10 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 10 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-xs {\n font-size: calc(12 / 16 * 1em); /* converts a 12px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 12 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 12 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-sm {\n font-size: calc(14 / 16 * 1em); /* converts a 14px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 14 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 14 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-lg {\n font-size: calc(20 / 16 * 1em); /* converts a 20px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 20 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 20 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-xl {\n font-size: calc(24 / 16 * 1em); /* converts a 24px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 24 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 24 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-2xl {\n font-size: calc(32 / 16 * 1em); /* converts a 32px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 32 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 32 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-width-auto {\n --fa-width: auto;\n}\n\n.fa-fw,\n.fa-width-fixed {\n --fa-width: 1.25em;\n}\n\n.fa-ul {\n list-style-type: none;\n margin-inline-start: var(--fa-li-margin, 2.5em);\n padding-inline-start: 0;\n}\n.fa-ul > li {\n position: relative;\n}\n\n.fa-li {\n inset-inline-start: calc(-1 * var(--fa-li-width, 2em));\n position: absolute;\n text-align: center;\n width: var(--fa-li-width, 2em);\n line-height: inherit;\n}\n\n/* Heads Up: Bordered Icons will not be supported in the future!\n - This feature will be deprecated in the next major release of Font Awesome (v8)!\n - You may continue to use it in this version *v7), but it will not be supported in Font Awesome v8.\n*/\n/* Notes:\n* --@{v.$css-prefix}-border-width = 1/16 by default (to render as ~1px based on a 16px default font-size)\n* --@{v.$css-prefix}-border-padding =\n ** 3/16 for vertical padding (to give ~2px of vertical whitespace around an icon considering it\'s vertical alignment)\n ** 4/16 for horizontal padding (to give ~4px of horizontal whitespace around an icon)\n*/\n.fa-border {\n border-color: var(--fa-border-color, #eee);\n border-radius: var(--fa-border-radius, 0.1em);\n border-style: var(--fa-border-style, solid);\n border-width: var(--fa-border-width, 0.0625em);\n box-sizing: var(--fa-border-box-sizing, content-box);\n padding: var(--fa-border-padding, 0.1875em 0.25em);\n}\n\n.fa-pull-left,\n.fa-pull-start {\n float: inline-start;\n margin-inline-end: var(--fa-pull-margin, 0.3em);\n}\n\n.fa-pull-right,\n.fa-pull-end {\n float: inline-end;\n margin-inline-start: var(--fa-pull-margin, 0.3em);\n}\n\n.fa-beat {\n animation-name: fa-beat;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, ease-in-out);\n}\n\n.fa-bounce {\n animation-name: fa-bounce;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1));\n}\n\n.fa-fade {\n animation-name: fa-fade;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));\n}\n\n.fa-beat-fade {\n animation-name: fa-beat-fade;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));\n}\n\n.fa-flip {\n animation-name: fa-flip;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, ease-in-out);\n}\n\n.fa-shake {\n animation-name: fa-shake;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, linear);\n}\n\n.fa-spin {\n animation-name: fa-spin;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 2s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, linear);\n}\n\n.fa-spin-reverse {\n --fa-animation-direction: reverse;\n}\n\n.fa-pulse,\n.fa-spin-pulse {\n animation-name: fa-spin;\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, steps(8));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fa-beat,\n .fa-bounce,\n .fa-fade,\n .fa-beat-fade,\n .fa-flip,\n .fa-pulse,\n .fa-shake,\n .fa-spin,\n .fa-spin-pulse {\n animation: none !important;\n transition: none !important;\n }\n}\n@keyframes fa-beat {\n 0%, 90% {\n transform: scale(1);\n }\n 45% {\n transform: scale(var(--fa-beat-scale, 1.25));\n }\n}\n@keyframes fa-bounce {\n 0% {\n transform: scale(1, 1) translateY(0);\n }\n 10% {\n transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);\n }\n 30% {\n transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));\n }\n 50% {\n transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);\n }\n 57% {\n transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));\n }\n 64% {\n transform: scale(1, 1) translateY(0);\n }\n 100% {\n transform: scale(1, 1) translateY(0);\n }\n}\n@keyframes fa-fade {\n 50% {\n opacity: var(--fa-fade-opacity, 0.4);\n }\n}\n@keyframes fa-beat-fade {\n 0%, 100% {\n opacity: var(--fa-beat-fade-opacity, 0.4);\n transform: scale(1);\n }\n 50% {\n opacity: 1;\n transform: scale(var(--fa-beat-fade-scale, 1.125));\n }\n}\n@keyframes fa-flip {\n 50% {\n transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));\n }\n}\n@keyframes fa-shake {\n 0% {\n transform: rotate(-15deg);\n }\n 4% {\n transform: rotate(15deg);\n }\n 8%, 24% {\n transform: rotate(-18deg);\n }\n 12%, 28% {\n transform: rotate(18deg);\n }\n 16% {\n transform: rotate(-22deg);\n }\n 20% {\n transform: rotate(22deg);\n }\n 32% {\n transform: rotate(-12deg);\n }\n 36% {\n transform: rotate(12deg);\n }\n 40%, 100% {\n transform: rotate(0deg);\n }\n}\n@keyframes fa-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n.fa-rotate-90 {\n transform: rotate(90deg);\n}\n\n.fa-rotate-180 {\n transform: rotate(180deg);\n}\n\n.fa-rotate-270 {\n transform: rotate(270deg);\n}\n\n.fa-flip-horizontal {\n transform: scale(-1, 1);\n}\n\n.fa-flip-vertical {\n transform: scale(1, -1);\n}\n\n.fa-flip-both,\n.fa-flip-horizontal.fa-flip-vertical {\n transform: scale(-1, -1);\n}\n\n.fa-rotate-by {\n transform: rotate(var(--fa-rotate-angle, 0));\n}\n\n.svg-inline--fa .fa-primary {\n fill: var(--fa-primary-color, currentColor);\n opacity: var(--fa-primary-opacity, 1);\n}\n\n.svg-inline--fa .fa-secondary {\n fill: var(--fa-secondary-color, currentColor);\n opacity: var(--fa-secondary-opacity, 0.4);\n}\n\n.svg-inline--fa.fa-swap-opacity .fa-primary {\n opacity: var(--fa-secondary-opacity, 0.4);\n}\n\n.svg-inline--fa.fa-swap-opacity .fa-secondary {\n opacity: var(--fa-primary-opacity, 1);\n}\n\n.svg-inline--fa mask .fa-primary,\n.svg-inline--fa mask .fa-secondary {\n fill: black;\n}\n\n.svg-inline--fa.fa-inverse {\n fill: var(--fa-inverse, #fff);\n}\n\n.fa-stack {\n display: inline-block;\n height: 2em;\n line-height: 2em;\n position: relative;\n vertical-align: middle;\n width: 2.5em;\n}\n\n.fa-inverse {\n color: var(--fa-inverse, #fff);\n}\n\n.svg-inline--fa.fa-stack-1x {\n height: 1em;\n width: 1.25em;\n}\n.svg-inline--fa.fa-stack-2x {\n height: 2em;\n width: 2.5em;\n}\n\n.fa-stack-1x,\n.fa-stack-2x {\n bottom: 0;\n left: 0;\n margin: auto;\n position: absolute;\n right: 0;\n top: 0;\n z-index: var(--fa-stack-z-index, auto);\n}';return"fa"===a&&e===s||(c=new RegExp("\\.".concat("fa","\\-"),"g"),l=new RegExp("\\--".concat("fa","\\-"),"g"),s=new RegExp("\\.".concat(s),"g"),z=z.replace(c,".".concat(a,"-")).replace(l,"--".concat(a,"-")).replace(s,".".concat(e))),z}var q1=!1;function A1(){if(S.autoAddCss&&!q1){var c=j1();if(c&&i){for(var l=g.createElement("style"),s=(l.setAttribute("type","text/css"),l.innerHTML=c,g.head.childNodes),a=null,e=s.length-1;-1").concat(e.map(x).join(""),"")}function F1(c,l,s){if(c&&c[l]&&c[l][s])return{prefix:l,iconName:s,icon:c[l][s]}}function I1(c,l,s,a){for(var e,z,t=Object.keys(c),r=t.length,n=void 0!==a?D1(l,a):l,M=void 0===s?(e=1,c[t[0]]):(e=0,s);e{var l=c.values,s=c.family,a=c.canonical,e=void 0===(e=c.givenPrefix)?"":e,z=void 0===(z=c.styles)?{}:z,t=void 0===(t=c.config)?{}:t,r=s===d,n=l.includes("fa-duotone")||l.includes("fad"),M="duotone"===t.familyDefault,o="fad"===a.prefix||"fa-duotone"===a.prefix;return!r&&(n||M||o)&&(a.prefix="fad"),(l.includes("fa-brands")||l.includes("fab"))&&(a.prefix="fab"),!a.prefix&&e2.includes(s)&&(Object.keys(z).find(function(c){return z2.includes(c)})||t.autoFetchSvg)&&(r=$.get(s).defaultShortPrefixId,a.prefix=r,a.iconName=A(a.prefix,a.iconName)||a.iconName),"fa"!==a.prefix&&"fa"!==e||(a.prefix=q||"fas"),a})({values:c,family:o,styles:j,config:S,canonical:f,givenPrefix:M})),(l=n,c=M,i=(r=f).prefix,o=r.iconName,!l&&i&&o&&(n="fa"===c?Q1(o):{},f=A(i,o),o=n.iconName||f||o,"far"!==(i=n.prefix||i)||j.far||!j.fas||S.autoFetchSvg||(i="fas")),{prefix:i,iconName:o}))}var e2=X.filter(function(c){return c!==L||c!==d}),z2=Object.keys(c1).filter(function(c){return c!==L}).map(function(c){return Object.keys(c1[c])}).flat(),r=(()=>{function c(){if(!(this instanceof c))throw new TypeError("Cannot call a class as a function");this.definitions={}}return l=c,(s=[{key:"add",value:function(){for(var l=this,c=arguments.length,s=new Array(c),a=0;a=K2[0]&&M<=K2[1],n=2===n.length&&n[0]===n[1],M=M||n||s,n=G1(e,i),z=n,f&&(s=B1[i],f=G1("fas",i),(i=s||(f?{prefix:"fas",iconName:f}:null)||{prefix:null,iconName:null}).iconName)&&i.prefix&&(n=i.iconName,e=i.prefix),!n)||M||l&&l.getAttribute(t1)===e&&l.getAttribute(r1)===z?a():(m.setAttribute(d,z),l&&m.removeChild(l),(r=(t={iconName:null,prefix:null,transform:v,symbol:!1,mask:{iconName:null,prefix:null,rest:[]},maskId:null,extra:{classes:[],styles:{},attributes:{}}}).extra).attributes[e1]=L,C2(n,e).then(function(c){var l=m2(u(u({},t),{},{icons:{main:c,mask:Z1()},prefix:e,iconName:z,extra:r,watchable:!0})),s=g.createElementNS("http://www.w3.org/2000/svg","svg");"::before"===L?m.insertBefore(s,m.firstChild):m.appendChild(s),s.outerHTML=l.map(x).join("\n"),m.removeAttribute(d),a()}).catch(c))))})}function X2(c){return Promise.all([V2(c,"::before"),V2(c,"::after")])}function $2(c){return!(c.parentNode===document.head||~o1.indexOf(c.tagName.toUpperCase())||c.getAttribute(e1)||c.parentNode&&"svg"===c.parentNode.tagName)}function G2(c){if(!c)return[];for(var l=new Set,s=[c],a=0,e=[/(?=\s:)/,/(?<=\)\)?[^,]*,)/];a{var l=e[a];s=s.flatMap(function(c){return c.split(l).map(function(c){return c.replace(/,\s*$/,"").trim()})})})();var z,t=T(s=s.flatMap(function(c){return c.includes("(")?c:c.split(",").map(function(c){return c.trim()})}));try{for(t.s();!(z=t.n()).done;){var r,n=z.value;Q2(n)&&""!==(r=f1.reduce(function(c,l){return c.replace(l,"")},n))&&"*"!==r&&l.add(r)}}catch(c){t.e(c)}finally{t.f()}return l}var Q2=function(l){return!!l&&f1.some(function(c){return l.includes(c)})};function Z2(c){var e,l=1, enable searchPseudoElementsFullScan for slower but more thorough DOM parsing, or suppress this warning by setting searchPseudoElementsWarnings to false.'))}}}catch(c){z.e(c)}finally{z.f()}if(!a.size)return;l=Array.from(a).join(", ");try{e=c.querySelectorAll(l)}catch(c){}}return new Promise(function(c,l){var s=w(e).filter($2).map(X2),a=y2.begin("searchPseudoElements");O2(),Promise.all(s).then(function(){a(),E2(),c()}).catch(function(){a(),E2(),l()})})}}function c4(c){return c.toLowerCase().split(" ").reduce(function(c,l){var s=l.toLowerCase().split("-"),a=s[0],e=s.slice(1).join("-");if(a&&"h"===e)c.flipX=!0;else if(a&&"v"===e)c.flipY=!0;else if(e=parseFloat(e),!isNaN(e))switch(a){case"grow":c.size=c.size+e;break;case"shrink":c.size=c.size-e;break;case"left":c.x=c.x-e;break;case"right":c.x=c.x+e;break;case"up":c.y=c.y-e;break;case"down":c.y=c.y+e;break;case"rotate":c.rotate=c.rotate+e}return c},{size:16,x:0,y:0,flipX:!1,flipY:!1,rotate:0})}var l4,s4=!1,a4={x:0,y:0,width:"100%",height:"100%"};function e4(c){return c.attributes&&(c.attributes.fill||(!(1= 1 && month <= 9) { - month = '0' + month; - } - if (day >= 0 && day <= 9) { - day = '0' + day; - } - if (hours >= 0 && hours <= 9) { - hours = '0' + hours; - } - if (minutes >= 0 && minutes <= 9) { - minutes = '0' + minutes; - } - if (seconds >= 0 && seconds <= 9) { - seconds = '0' + seconds; - } - var currentdate = year + '-' + month + '-' + day + " " + hours + ":" + minutes + ":" + seconds; - return currentdate; -} - -var random = function () { - return parseInt(Math.random() * 10000) + (new Date()).valueOf(); -}; - -var loadScript = function (url, cb) { - var script = document.createElement("script"); - script.charset = "UTF-8"; - script.async = true; - - // 对geetestçš„é™æ€èµ„æºæ·»åŠ crossOrigin - if ( /static\.geetest\.com/g.test(url)) { - script.crossOrigin = "anonymous"; - } - - script.onerror = function () { - cb(true); - }; - var loaded = false; - script.onload = script.onreadystatechange = function () { - if (!loaded && - (!script.readyState || - "loaded" === script.readyState || - "complete" === script.readyState)) { - - loaded = true; - setTimeout(function () { - cb(false); - }, 0); - } - }; - script.src = url; - head.appendChild(script); -}; - -var normalizeDomain = function (domain) { - // special domain: uems.sysu.edu.cn/jwxt/geetest/ - // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn - return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest -}; -var normalizePath = function (path) { - path = path.replace(/\/+/g, '/'); - if (path.indexOf('/') !== 0) { - path = '/' + path; - } - return path; -}; -var normalizeQuery = function (query) { - if (!query) { - return ''; - } - var q = '?'; - new _Object(query)._each(function (key, value) { - if (isString(value) || isNumber(value) || isBoolean(value)) { - q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; - } - }); - if (q === '?') { - q = ''; - } - return q.replace(/&$/, ''); -}; -var makeURL = function (protocol, domain, path, query) { - domain = normalizeDomain(domain); - - var url = normalizePath(path) + normalizeQuery(query); - if (domain) { - url = protocol + domain + url; - } - - return url; -}; - -var load = function (config, send, protocol, domains, path, query, cb) { - var tryRequest = function (at) { - - var url = makeURL(protocol, domains[at], path, query); - loadScript(url, function (err) { - if (err) { - if (at >= domains.length - 1) { - cb(true); - // report gettype error - if (send) { - config.error_code = 508; - var url = protocol + domains[at] + path; - reportError(config, url); - } - } else { - tryRequest(at + 1); - } - } else { - cb(false); - } - }); - }; - tryRequest(0); -}; - - -var jsonp = function (domains, path, config, callback) { - if (isObject(config.getLib)) { - config._extend(config.getLib); - callback(config); - return; - } - if (config.offline) { - callback(config._get_fallback_config()); - return; - } - - var cb = "geetest_" + random(); - window[cb] = function (data) { - if (data.status == 'success') { - callback(data.data); - } else if (!data.status) { - callback(data); - } else { - callback(config._get_fallback_config()); - } - window[cb] = undefined; - try { - delete window[cb]; - } catch (e) { - } - }; - load(config, true, config.protocol, domains, path, { - gt: config.gt, - callback: cb - }, function (err) { - if (err) { - callback(config._get_fallback_config()); - } - }); -}; - -var reportError = function (config, url) { - load(config, false, config.protocol, ['monitor.geetest.com'], '/monitor/send', { - time: nowDate(), - captcha_id: config.gt, - challenge: config.challenge, - pt: pt, - exception_url: url, - error_code: config.error_code - }, function (err) {}) -} - -var throwError = function (errorType, config) { - var errors = { - networkError: '网络错误', - gtTypeError: 'gt字段不是字符串类型' - }; - if (typeof config.onError === 'function') { - config.onError(errors[errorType]); - } else { - throw new Error(errors[errorType]); - } -}; - -var detect = function () { - return window.Geetest || document.getElementById("gt_lib"); -}; - -if (detect()) { - status.slide = "loaded"; -} - -window.initGeetest = function (userConfig, callback) { - - var config = new Config(userConfig); - - if (userConfig.https) { - config.protocol = 'https://'; - } else if (!userConfig.protocol) { - config.protocol = window.location.protocol + '//'; - } - - // for KFC - if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || - userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { - config.apiserver = 'yumchina.geetest.com/'; // for old js - config.api_server = 'yumchina.geetest.com'; - } - - if(userConfig.gt){ - window.GeeGT = userConfig.gt - } - - if(userConfig.challenge){ - window.GeeChallenge = userConfig.challenge - } - - if (isObject(userConfig.getType)) { - config._extend(userConfig.getType); - } - jsonp((config.api_server_v3 || [config.api_server || config.apiserver]), config.typePath, config, function (newConfig) { - var type = newConfig.type; - var init = function () { - config._extend(newConfig); - callback(new window.Geetest(config)); - }; - - callbacks[type] = callbacks[type] || []; - var s = status[type] || 'init'; - if (s === 'init') { - status[type] = 'loading'; - - callbacks[type].push(init); - - load(config, true, config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { - if (err) { - status[type] = 'fail'; - throwError('networkError', config); - } else { - status[type] = 'loaded'; - var cbs = callbacks[type]; - for (var i = 0, len = cbs.length; i < len; i = i + 1) { - var cb = cbs[i]; - if (isFunction(cb)) { - cb(); - } - } - callbacks[type] = []; - } - }); - } else if (s === "loaded") { - init(); - } else if (s === "fail") { - throwError('networkError', config); - } else if (s === "loading") { - callbacks[type].push(init); - } - }); - -}; - - -})(window); +"v0.5.0 Geetest Inc."; + +(function (window) { + "use strict"; + if (typeof window === 'undefined') { + throw new Error('Geetest requires browser environment'); + } + +var document = window.document; +var Math = window.Math; +var head = document.getElementsByTagName("head")[0]; + +function _Object(obj) { + this._obj = obj; +} + +_Object.prototype = { + _each: function (process) { + var _obj = this._obj; + for (var k in _obj) { + if (_obj.hasOwnProperty(k)) { + process(k, _obj[k]); + } + } + return this; + } +}; + +function Config(config) { + var self = this; + new _Object(config)._each(function (key, value) { + self[key] = value; + }); +} + +Config.prototype = { + api_server: 'api.geetest.com', + protocol: 'http://', + typePath: '/gettype.php', + fallback_config: { + slide: { + static_servers: ["static.geetest.com", "static.geevisit.com"], + type: 'slide', + slide: '/static/js/geetest.0.0.0.js' + }, + fullpage: { + static_servers: ["static.geetest.com", "static.geevisit.com"], + type: 'fullpage', + fullpage: '/static/js/fullpage.0.0.0.js' + } + }, + _get_fallback_config: function () { + var self = this; + if (isString(self.type)) { + return self.fallback_config[self.type]; + } else if (self.new_captcha) { + return self.fallback_config.fullpage; + } else { + return self.fallback_config.slide; + } + }, + _extend: function (obj) { + var self = this; + new _Object(obj)._each(function (key, value) { + self[key] = value; + }) + } +}; +var isNumber = function (value) { + return (typeof value === 'number'); +}; +var isString = function (value) { + return (typeof value === 'string'); +}; +var isBoolean = function (value) { + return (typeof value === 'boolean'); +}; +var isObject = function (value) { + return (typeof value === 'object' && value !== null); +}; +var isFunction = function (value) { + return (typeof value === 'function'); +}; +var MOBILE = /Mobi/i.test(navigator.userAgent); +var pt = MOBILE ? 3 : 0; + +var callbacks = {}; +var status = {}; + +var nowDate = function () { + var date = new Date(); + var year = date.getFullYear(); + var month = date.getMonth() + 1; + var day = date.getDate(); + var hours = date.getHours(); + var minutes = date.getMinutes(); + var seconds = date.getSeconds(); + + if (month >= 1 && month <= 9) { + month = '0' + month; + } + if (day >= 0 && day <= 9) { + day = '0' + day; + } + if (hours >= 0 && hours <= 9) { + hours = '0' + hours; + } + if (minutes >= 0 && minutes <= 9) { + minutes = '0' + minutes; + } + if (seconds >= 0 && seconds <= 9) { + seconds = '0' + seconds; + } + var currentdate = year + '-' + month + '-' + day + " " + hours + ":" + minutes + ":" + seconds; + return currentdate; +} + +var random = function () { + return parseInt(Math.random() * 10000) + (new Date()).valueOf(); +}; + +var loadScript = function (url, cb) { + var script = document.createElement("script"); + script.charset = "UTF-8"; + script.async = true; + + // 对geetestçš„é™æ€èµ„æºæ·»åŠ crossOrigin + if ( /static\.geetest\.com/g.test(url)) { + script.crossOrigin = "anonymous"; + } + + script.onerror = function () { + cb(true); + }; + var loaded = false; + script.onload = script.onreadystatechange = function () { + if (!loaded && + (!script.readyState || + "loaded" === script.readyState || + "complete" === script.readyState)) { + + loaded = true; + setTimeout(function () { + cb(false); + }, 0); + } + }; + script.src = url; + head.appendChild(script); +}; + +var normalizeDomain = function (domain) { + // special domain: uems.sysu.edu.cn/jwxt/geetest/ + // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn + return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest +}; +var normalizePath = function (path) { + path = path.replace(/\/+/g, '/'); + if (path.indexOf('/') !== 0) { + path = '/' + path; + } + return path; +}; +var normalizeQuery = function (query) { + if (!query) { + return ''; + } + var q = '?'; + new _Object(query)._each(function (key, value) { + if (isString(value) || isNumber(value) || isBoolean(value)) { + q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; + } + }); + if (q === '?') { + q = ''; + } + return q.replace(/&$/, ''); +}; +var makeURL = function (protocol, domain, path, query) { + domain = normalizeDomain(domain); + + var url = normalizePath(path) + normalizeQuery(query); + if (domain) { + url = protocol + domain + url; + } + + return url; +}; + +var load = function (config, send, protocol, domains, path, query, cb) { + var tryRequest = function (at) { + + var url = makeURL(protocol, domains[at], path, query); + loadScript(url, function (err) { + if (err) { + if (at >= domains.length - 1) { + cb(true); + // report gettype error + if (send) { + config.error_code = 508; + var url = protocol + domains[at] + path; + reportError(config, url); + } + } else { + tryRequest(at + 1); + } + } else { + cb(false); + } + }); + }; + tryRequest(0); +}; + + +var jsonp = function (domains, path, config, callback) { + if (isObject(config.getLib)) { + config._extend(config.getLib); + callback(config); + return; + } + if (config.offline) { + callback(config._get_fallback_config()); + return; + } + + var cb = "geetest_" + random(); + window[cb] = function (data) { + if (data.status == 'success') { + callback(data.data); + } else if (!data.status) { + callback(data); + } else { + callback(config._get_fallback_config()); + } + window[cb] = undefined; + try { + delete window[cb]; + } catch (e) { + } + }; + load(config, true, config.protocol, domains, path, { + gt: config.gt, + callback: cb + }, function (err) { + if (err) { + callback(config._get_fallback_config()); + } + }); +}; + +var reportError = function (config, url) { + load(config, false, config.protocol, ['monitor.geetest.com'], '/monitor/send', { + time: nowDate(), + captcha_id: config.gt, + challenge: config.challenge, + pt: pt, + exception_url: url, + error_code: config.error_code + }, function (err) {}) +} + +var throwError = function (errorType, config) { + var errors = { + networkError: '网络错误', + gtTypeError: 'gt字段不是字符串类型' + }; + if (typeof config.onError === 'function') { + config.onError(errors[errorType]); + } else { + throw new Error(errors[errorType]); + } +}; + +var detect = function () { + return window.Geetest || document.getElementById("gt_lib"); +}; + +if (detect()) { + status.slide = "loaded"; +} + +window.initGeetest = function (userConfig, callback) { + + var config = new Config(userConfig); + + if (userConfig.https) { + config.protocol = 'https://'; + } else if (!userConfig.protocol) { + config.protocol = window.location.protocol + '//'; + } + + // for KFC + if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || + userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { + config.apiserver = 'yumchina.geetest.com/'; // for old js + config.api_server = 'yumchina.geetest.com'; + } + + if(userConfig.gt){ + window.GeeGT = userConfig.gt + } + + if(userConfig.challenge){ + window.GeeChallenge = userConfig.challenge + } + + if (isObject(userConfig.getType)) { + config._extend(userConfig.getType); + } + jsonp((config.api_server_v3 || [config.api_server || config.apiserver]), config.typePath, config, function (newConfig) { + var type = newConfig.type; + var init = function () { + config._extend(newConfig); + callback(new window.Geetest(config)); + }; + + callbacks[type] = callbacks[type] || []; + var s = status[type] || 'init'; + if (s === 'init') { + status[type] = 'loading'; + + callbacks[type].push(init); + + load(config, true, config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { + if (err) { + status[type] = 'fail'; + throwError('networkError', config); + } else { + status[type] = 'loaded'; + var cbs = callbacks[type]; + for (var i = 0, len = cbs.length; i < len; i = i + 1) { + var cb = cbs[i]; + if (isFunction(cb)) { + cb(); + } + } + callbacks[type] = []; + } + }); + } else if (s === "loaded") { + init(); + } else if (s === "fail") { + throwError('networkError', config); + } else if (s === "loading") { + callbacks[type].push(init); + } + }); + +}; + + +})(window); diff --git a/backend/src/assets/js/gt4.js b/backend/src/assets/js/gt4.js index 6b2f6ae..7d63a3e 100644 --- a/backend/src/assets/js/gt4.js +++ b/backend/src/assets/js/gt4.js @@ -1,487 +1,487 @@ -"v4.2.0 Geetest Inc."; - -(function (window) { - "use strict"; - if (typeof window === 'undefined') { - throw new Error('Geetest requires browser environment'); - } - -var document = window.document; -var Math = window.Math; -var head = document.getElementsByTagName("head")[0]; -var TIMEOUT = 10000; - -function _Object(obj) { - this._obj = obj; -} - -_Object.prototype = { - _each: function (process) { - var _obj = this._obj; - for (var k in _obj) { - if (_obj.hasOwnProperty(k)) { - process(k, _obj[k]); - } - } - return this; - }, - _extend: function (obj){ - var self = this; - new _Object(obj)._each(function (key, value){ - self._obj[key] = value; - }) - } -}; - -var uuid = function () { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0; - var v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - }; - -function Config(config) { - var self = this; - new _Object(config)._each(function (key, value) { - self[key] = value; - }); -} - -Config.prototype = { - apiServers: ['gcaptcha4.geetest.com','gcaptcha4.geevisit.com','gcaptcha4.gsensebot.com'], - staticServers: ["static.geetest.com",'static.geevisit.com'], - protocol: 'http://', - typePath: '/load', - fallback_config: { - bypass: { - staticServers: ["static.geetest.com",'static.geevisit.com'], - type: 'bypass', - bypass: '/v4/bypass.js' - } - }, - _get_fallback_config: function () { - var self = this; - if (isString(self.type)) { - return self.fallback_config[self.type]; - } else { - return self.fallback_config.bypass; - } - }, - _extend: function (obj) { - var self = this; - new _Object(obj)._each(function (key, value) { - self[key] = value; - }) - } -}; -var isNumber = function (value) { - return (typeof value === 'number'); -}; -var isString = function (value) { - return (typeof value === 'string'); -}; -var isBoolean = function (value) { - return (typeof value === 'boolean'); -}; -var isObject = function (value) { - return (typeof value === 'object' && value !== null); -}; -var isFunction = function (value) { - return (typeof value === 'function'); -}; -var MOBILE = /Mobi/i.test(navigator.userAgent); - -var callbacks = {}; -var status = {}; - -var random = function () { - return parseInt(Math.random() * 10000) + (new Date()).valueOf(); -}; - -// bind 函数polify, 不带new功能的bind - -var bind = function(target,context){ - if(typeof target !== 'function'){ - return; - } - var args = Array.prototype.slice.call(arguments,2); - - if(Function.prototype.bind){ - return target.bind(context, args); - }else { - return function(){ - var _args = Array.prototype.slice.call(arguments); - return target.apply(context,args.concat(_args)); - } - } -} - - - -var toString = Object.prototype.toString; - -var _isFunction = function(obj) { - return typeof(obj) === 'function'; -}; -var _isObject = function(obj) { - return obj === Object(obj); -}; -var _isArray = function(obj) { - return toString.call(obj) == '[object Array]'; -}; -var _isDate = function(obj) { - return toString.call(obj) == '[object Date]'; -}; -var _isRegExp = function(obj) { - return toString.call(obj) == '[object RegExp]'; -}; -var _isBoolean = function(obj) { - return toString.call(obj) == '[object Boolean]'; -}; - - -function resolveKey(input){ - return input.replace(/(\S)(_([a-zA-Z]))/g, function(match, $1, $2, $3){ - return $1 + $3.toUpperCase() || ""; - }) -} - -function camelizeKeys(input, convert){ - if(!_isObject(input) || _isDate(input) || _isRegExp(input) || _isBoolean(input) || _isFunction(input)){ - return convert ? resolveKey(input) : input; - } - - if(_isArray(input)){ - var temp = []; - for(var i = 0; i < input.length; i++){ - temp.push(camelizeKeys(input[i])); - } - - }else { - var temp = {}; - for(var prop in input){ - if(input.hasOwnProperty(prop)){ - temp[camelizeKeys(prop, true)] = camelizeKeys(input[prop]); - } - } - } - return temp; -} - -var loadScript = function (url, cb, timeout) { - var script = document.createElement("script"); - script.charset = "UTF-8"; - script.async = true; - - // 对geetestçš„é™æ€èµ„æºæ·»åŠ crossOrigin - if ( /static\.geetest\.com/g.test(url)) { - script.crossOrigin = "anonymous"; - } - - script.onerror = function () { - cb(true); - // 错误触发了,超时逻辑就不用了 - loaded = true; - }; - var loaded = false; - script.onload = script.onreadystatechange = function () { - if (!loaded && - (!script.readyState || - "loaded" === script.readyState || - "complete" === script.readyState)) { - - loaded = true; - setTimeout(function () { - cb(false); - }, 0); - } - }; - script.src = url; - head.appendChild(script); - - setTimeout(function () { - if (!loaded) { - script.onerror = script.onload = null; - script.remove && script.remove(); - cb(true); - } - }, timeout || TIMEOUT); -}; - -var normalizeDomain = function (domain) { - // special domain: uems.sysu.edu.cn/jwxt/geetest/ - // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn - return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest -}; -var normalizePath = function (path) { - - path = path && path.replace(/\/+/g, '/'); - if (path.indexOf('/') !== 0) { - path = '/' + path; - } - return path; -}; -var normalizeQuery = function (query) { - if (!query) { - return ''; - } - var q = '?'; - new _Object(query)._each(function (key, value) { - if (isString(value) || isNumber(value) || isBoolean(value)) { - q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; - } - }); - if (q === '?') { - q = ''; - } - return q.replace(/&$/, ''); -}; -var makeURL = function (protocol, domain, path, query) { - domain = normalizeDomain(domain); - - var url = normalizePath(path) + normalizeQuery(query); - if (domain) { - url = protocol + domain + url; - } - - return url; -}; - -var load = function (config, protocol, domains, path, query, cb, handleCb) { - var tryRequest = function (at) { - // 处理jsonp回调,这里为了保证每个不同jsonp都有唯一的回调函数 - if(handleCb){ - var cbName = "geetest_" + random(); - // 需要与预先定义好cbnameå‚æ•°ï¼Œåˆ é™¤å¯¹è±¡ - window[cbName] = bind(handleCb, null, cbName); - query.callback = cbName; - } - var url = makeURL(protocol, domains[at], path, query); - loadScript(url, function (err) { - if (err) { - // 超时或者出错的时候 移除回调 - if(cbName){ - try { - window[cbName] = function(){ - window[cbName] = null; - } - } catch (e) {} - } - - if (at >= domains.length - 1) { - cb(true); - // report gettype error - } else { - tryRequest(at + 1); - } - } else { - cb(false); - } - }, config.timeout); - }; - tryRequest(0); -}; - - -var jsonp = function (domains, path, config, callback) { - - var handleCb = function (cbName, data) { - - // 保证只执行一次,全部超时的情况下不会再触发; - - if (data.status == 'success') { - callback(data.data); - } else if (!data.status) { - callback(data); - } else { - //接口有返回,但是返回了错误状态,进入报错逻辑 - callback(data); - } - window[cbName] = undefined; - try { - delete window[cbName]; - } catch (e) { - } - }; - load(config, config.protocol, domains, path, { - callback: '', - captcha_id: config.captchaId, - challenge: config.challenge || uuid(), - client_type: config.clientType ? config.clientType : (MOBILE? 'h5':'web'), - risk_type: config.riskType, - user_info: config.userInfo, - call_type: config.callType, - lang: config.language? config.language : navigator.appName === 'Netscape' ? navigator.language.toLowerCase() : navigator.userLanguage.toLowerCase() - }, function (err) { - // ç½‘ç»œé—®é¢˜æŽ¥å£æ²¡æœ‰è¿”å›žï¼Œç›´æŽ¥ä½¿ç”¨æœ¬åœ°éªŒè¯ç ï¼Œèµ°å®•æœºæ¨¡å¼ - // è¿™é‡Œå¯ä»¥æ·»åŠ ç”¨æˆ·çš„é€»è¾‘ - if(err && typeof config.offlineCb === 'function'){ - // 执行自己的宕机 - config.offlineCb(); - return; - } - if(err){ - callback(config._get_fallback_config()); - } - }, handleCb); -}; - -var reportError = function (config, url) { - load(config, config.protocol, ['monitor.geetest.com'], '/monitor/send', { - time: Date.now().getTime(), - captcha_id: config.gt, - challenge: config.challenge, - exception_url: url, - error_code: config.error_code - }, function (err) {}) -} - -var throwError = function (errorType, config, errObj) { - var errors = { - networkError: '网络错误', - gtTypeError: 'gt字段不是字符串类型' - }; - if (typeof config.onError === 'function') { - config.onError({ - desc: errObj.desc, - msg: errObj.msg, - code: errObj.code - }); - } else { - throw new Error(errors[errorType]); - } -}; - -var detect = function () { - return window.Geetest || document.getElementById("gt_lib"); -}; - -if (detect()) { - status.slide = "loaded"; -} -var GeetestIsLoad = function (fname) { - var GeetestIsLoad = false; - var tags = { js: 'script', css: 'link' }; - var tagname = fname && tags[fname.split('.').pop()]; - if (tagname !== undefined) { - var elts = document.getElementsByTagName(tagname); - for (var i in elts) { - if ((elts[i].href && elts[i].href.toString().indexOf(fname) > 0) - || (elts[i].src && elts[i].src.toString().indexOf(fname) > 0)) { - GeetestIsLoad = true; - } - } - } - return GeetestIsLoad; -}; -window.initGeetest4 = function (userConfig,callback) { - - var config = new Config(userConfig); - if (userConfig.https) { - config.protocol = 'https://'; - } else if (!userConfig.protocol) { - config.protocol = window.location.protocol + '//'; - } - - - if (isObject(userConfig.getType)) { - config._extend(userConfig.getType); - } - - jsonp(config.apiServers , config.typePath, config, function (newConfig) { - //错误捕获,第一个load请求可能直接报错 - var newConfig = camelizeKeys(newConfig); - - if(newConfig.status === 'error'){ - return throwError('networkError', config, newConfig); - } - - var type = newConfig.type; - if(config.debug){ - new _Object(newConfig)._extend(config.debug) - } - var init = function () { - config._extend(newConfig); - callback(new window.Geetest4(config)); - }; - - callbacks[type] = callbacks[type] || []; - - var s = status[type] || 'init'; - if (s === 'init') { - status[type] = 'loading'; - - callbacks[type].push(init); - - if(newConfig.gctPath){ - load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ - if(err){ - throwError('networkError', config, { - code: '60205', - msg: 'Network failure', - desc: { - detail: 'gct resource load timeout' - } - }); - } - }) - } - - load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers, newConfig.bypass || (newConfig.staticPath + newConfig.js), null, function (err) { - if (err) { - status[type] = 'fail'; - throwError('networkError', config, { - code: '60204', - msg: 'Network failure', - desc: { - detail: 'js resource load timeout' - } - }); - } else { - - status[type] = 'loaded'; - var cbs = callbacks[type]; - for (var i = 0, len = cbs.length; i < len; i = i + 1) { - var cb = cbs[i]; - if (isFunction(cb)) { - cb(); - } - } - callbacks[type] = []; - status[type] = 'init'; - } - }); - } else if (s === "loaded") { - // 判断gctæ˜¯å¦éœ€è¦é‡æ–°åŠ è½½ - if(newConfig.gctPath && !GeetestIsLoad(newConfig.gctPath)){ - load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ - if(err){ - throwError('networkError', config, { - code: '60205', - msg: 'Network failure', - desc: { - detail: 'gct resource load timeout' - } - }); - } - }) - } - return init(); - } else if (s === "fail") { - throwError('networkError', config, { - code: '60204', - msg: 'Network failure', - desc: { - detail: 'js resource load timeout' - } - }); - } else if (s === "loading") { - callbacks[type].push(init); - } - }); - -}; - - -})(window); +"v4.2.0 Geetest Inc."; + +(function (window) { + "use strict"; + if (typeof window === 'undefined') { + throw new Error('Geetest requires browser environment'); + } + +var document = window.document; +var Math = window.Math; +var head = document.getElementsByTagName("head")[0]; +var TIMEOUT = 10000; + +function _Object(obj) { + this._obj = obj; +} + +_Object.prototype = { + _each: function (process) { + var _obj = this._obj; + for (var k in _obj) { + if (_obj.hasOwnProperty(k)) { + process(k, _obj[k]); + } + } + return this; + }, + _extend: function (obj){ + var self = this; + new _Object(obj)._each(function (key, value){ + self._obj[key] = value; + }) + } +}; + +var uuid = function () { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0; + var v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }; + +function Config(config) { + var self = this; + new _Object(config)._each(function (key, value) { + self[key] = value; + }); +} + +Config.prototype = { + apiServers: ['gcaptcha4.geetest.com','gcaptcha4.geevisit.com','gcaptcha4.gsensebot.com'], + staticServers: ["static.geetest.com",'static.geevisit.com'], + protocol: 'http://', + typePath: '/load', + fallback_config: { + bypass: { + staticServers: ["static.geetest.com",'static.geevisit.com'], + type: 'bypass', + bypass: '/v4/bypass.js' + } + }, + _get_fallback_config: function () { + var self = this; + if (isString(self.type)) { + return self.fallback_config[self.type]; + } else { + return self.fallback_config.bypass; + } + }, + _extend: function (obj) { + var self = this; + new _Object(obj)._each(function (key, value) { + self[key] = value; + }) + } +}; +var isNumber = function (value) { + return (typeof value === 'number'); +}; +var isString = function (value) { + return (typeof value === 'string'); +}; +var isBoolean = function (value) { + return (typeof value === 'boolean'); +}; +var isObject = function (value) { + return (typeof value === 'object' && value !== null); +}; +var isFunction = function (value) { + return (typeof value === 'function'); +}; +var MOBILE = /Mobi/i.test(navigator.userAgent); + +var callbacks = {}; +var status = {}; + +var random = function () { + return parseInt(Math.random() * 10000) + (new Date()).valueOf(); +}; + +// bind 函数polify, 不带new功能的bind + +var bind = function(target,context){ + if(typeof target !== 'function'){ + return; + } + var args = Array.prototype.slice.call(arguments,2); + + if(Function.prototype.bind){ + return target.bind(context, args); + }else { + return function(){ + var _args = Array.prototype.slice.call(arguments); + return target.apply(context,args.concat(_args)); + } + } +} + + + +var toString = Object.prototype.toString; + +var _isFunction = function(obj) { + return typeof(obj) === 'function'; +}; +var _isObject = function(obj) { + return obj === Object(obj); +}; +var _isArray = function(obj) { + return toString.call(obj) == '[object Array]'; +}; +var _isDate = function(obj) { + return toString.call(obj) == '[object Date]'; +}; +var _isRegExp = function(obj) { + return toString.call(obj) == '[object RegExp]'; +}; +var _isBoolean = function(obj) { + return toString.call(obj) == '[object Boolean]'; +}; + + +function resolveKey(input){ + return input.replace(/(\S)(_([a-zA-Z]))/g, function(match, $1, $2, $3){ + return $1 + $3.toUpperCase() || ""; + }) +} + +function camelizeKeys(input, convert){ + if(!_isObject(input) || _isDate(input) || _isRegExp(input) || _isBoolean(input) || _isFunction(input)){ + return convert ? resolveKey(input) : input; + } + + if(_isArray(input)){ + var temp = []; + for(var i = 0; i < input.length; i++){ + temp.push(camelizeKeys(input[i])); + } + + }else { + var temp = {}; + for(var prop in input){ + if(input.hasOwnProperty(prop)){ + temp[camelizeKeys(prop, true)] = camelizeKeys(input[prop]); + } + } + } + return temp; +} + +var loadScript = function (url, cb, timeout) { + var script = document.createElement("script"); + script.charset = "UTF-8"; + script.async = true; + + // 对geetestçš„é™æ€èµ„æºæ·»åŠ crossOrigin + if ( /static\.geetest\.com/g.test(url)) { + script.crossOrigin = "anonymous"; + } + + script.onerror = function () { + cb(true); + // 错误触发了,超时逻辑就不用了 + loaded = true; + }; + var loaded = false; + script.onload = script.onreadystatechange = function () { + if (!loaded && + (!script.readyState || + "loaded" === script.readyState || + "complete" === script.readyState)) { + + loaded = true; + setTimeout(function () { + cb(false); + }, 0); + } + }; + script.src = url; + head.appendChild(script); + + setTimeout(function () { + if (!loaded) { + script.onerror = script.onload = null; + script.remove && script.remove(); + cb(true); + } + }, timeout || TIMEOUT); +}; + +var normalizeDomain = function (domain) { + // special domain: uems.sysu.edu.cn/jwxt/geetest/ + // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn + return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest +}; +var normalizePath = function (path) { + + path = path && path.replace(/\/+/g, '/'); + if (path.indexOf('/') !== 0) { + path = '/' + path; + } + return path; +}; +var normalizeQuery = function (query) { + if (!query) { + return ''; + } + var q = '?'; + new _Object(query)._each(function (key, value) { + if (isString(value) || isNumber(value) || isBoolean(value)) { + q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; + } + }); + if (q === '?') { + q = ''; + } + return q.replace(/&$/, ''); +}; +var makeURL = function (protocol, domain, path, query) { + domain = normalizeDomain(domain); + + var url = normalizePath(path) + normalizeQuery(query); + if (domain) { + url = protocol + domain + url; + } + + return url; +}; + +var load = function (config, protocol, domains, path, query, cb, handleCb) { + var tryRequest = function (at) { + // 处理jsonp回调,这里为了保证每个不同jsonp都有唯一的回调函数 + if(handleCb){ + var cbName = "geetest_" + random(); + // 需要与预先定义好cbnameå‚æ•°ï¼Œåˆ é™¤å¯¹è±¡ + window[cbName] = bind(handleCb, null, cbName); + query.callback = cbName; + } + var url = makeURL(protocol, domains[at], path, query); + loadScript(url, function (err) { + if (err) { + // 超时或者出错的时候 移除回调 + if(cbName){ + try { + window[cbName] = function(){ + window[cbName] = null; + } + } catch (e) {} + } + + if (at >= domains.length - 1) { + cb(true); + // report gettype error + } else { + tryRequest(at + 1); + } + } else { + cb(false); + } + }, config.timeout); + }; + tryRequest(0); +}; + + +var jsonp = function (domains, path, config, callback) { + + var handleCb = function (cbName, data) { + + // 保证只执行一次,全部超时的情况下不会再触发; + + if (data.status == 'success') { + callback(data.data); + } else if (!data.status) { + callback(data); + } else { + //接口有返回,但是返回了错误状态,进入报错逻辑 + callback(data); + } + window[cbName] = undefined; + try { + delete window[cbName]; + } catch (e) { + } + }; + load(config, config.protocol, domains, path, { + callback: '', + captcha_id: config.captchaId, + challenge: config.challenge || uuid(), + client_type: config.clientType ? config.clientType : (MOBILE? 'h5':'web'), + risk_type: config.riskType, + user_info: config.userInfo, + call_type: config.callType, + lang: config.language? config.language : navigator.appName === 'Netscape' ? navigator.language.toLowerCase() : navigator.userLanguage.toLowerCase() + }, function (err) { + // ç½‘ç»œé—®é¢˜æŽ¥å£æ²¡æœ‰è¿”å›žï¼Œç›´æŽ¥ä½¿ç”¨æœ¬åœ°éªŒè¯ç ï¼Œèµ°å®•æœºæ¨¡å¼ + // è¿™é‡Œå¯ä»¥æ·»åŠ ç”¨æˆ·çš„é€»è¾‘ + if(err && typeof config.offlineCb === 'function'){ + // 执行自己的宕机 + config.offlineCb(); + return; + } + if(err){ + callback(config._get_fallback_config()); + } + }, handleCb); +}; + +var reportError = function (config, url) { + load(config, config.protocol, ['monitor.geetest.com'], '/monitor/send', { + time: Date.now().getTime(), + captcha_id: config.gt, + challenge: config.challenge, + exception_url: url, + error_code: config.error_code + }, function (err) {}) +} + +var throwError = function (errorType, config, errObj) { + var errors = { + networkError: '网络错误', + gtTypeError: 'gt字段不是字符串类型' + }; + if (typeof config.onError === 'function') { + config.onError({ + desc: errObj.desc, + msg: errObj.msg, + code: errObj.code + }); + } else { + throw new Error(errors[errorType]); + } +}; + +var detect = function () { + return window.Geetest || document.getElementById("gt_lib"); +}; + +if (detect()) { + status.slide = "loaded"; +} +var GeetestIsLoad = function (fname) { + var GeetestIsLoad = false; + var tags = { js: 'script', css: 'link' }; + var tagname = fname && tags[fname.split('.').pop()]; + if (tagname !== undefined) { + var elts = document.getElementsByTagName(tagname); + for (var i in elts) { + if ((elts[i].href && elts[i].href.toString().indexOf(fname) > 0) + || (elts[i].src && elts[i].src.toString().indexOf(fname) > 0)) { + GeetestIsLoad = true; + } + } + } + return GeetestIsLoad; +}; +window.initGeetest4 = function (userConfig,callback) { + + var config = new Config(userConfig); + if (userConfig.https) { + config.protocol = 'https://'; + } else if (!userConfig.protocol) { + config.protocol = window.location.protocol + '//'; + } + + + if (isObject(userConfig.getType)) { + config._extend(userConfig.getType); + } + + jsonp(config.apiServers , config.typePath, config, function (newConfig) { + //错误捕获,第一个load请求可能直接报错 + var newConfig = camelizeKeys(newConfig); + + if(newConfig.status === 'error'){ + return throwError('networkError', config, newConfig); + } + + var type = newConfig.type; + if(config.debug){ + new _Object(newConfig)._extend(config.debug) + } + var init = function () { + config._extend(newConfig); + callback(new window.Geetest4(config)); + }; + + callbacks[type] = callbacks[type] || []; + + var s = status[type] || 'init'; + if (s === 'init') { + status[type] = 'loading'; + + callbacks[type].push(init); + + if(newConfig.gctPath){ + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ + if(err){ + throwError('networkError', config, { + code: '60205', + msg: 'Network failure', + desc: { + detail: 'gct resource load timeout' + } + }); + } + }) + } + + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers, newConfig.bypass || (newConfig.staticPath + newConfig.js), null, function (err) { + if (err) { + status[type] = 'fail'; + throwError('networkError', config, { + code: '60204', + msg: 'Network failure', + desc: { + detail: 'js resource load timeout' + } + }); + } else { + + status[type] = 'loaded'; + var cbs = callbacks[type]; + for (var i = 0, len = cbs.length; i < len; i = i + 1) { + var cb = cbs[i]; + if (isFunction(cb)) { + cb(); + } + } + callbacks[type] = []; + status[type] = 'init'; + } + }); + } else if (s === "loaded") { + // 判断gctæ˜¯å¦éœ€è¦é‡æ–°åŠ è½½ + if(newConfig.gctPath && !GeetestIsLoad(newConfig.gctPath)){ + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ + if(err){ + throwError('networkError', config, { + code: '60205', + msg: 'Network failure', + desc: { + detail: 'gct resource load timeout' + } + }); + } + }) + } + return init(); + } else if (s === "fail") { + throwError('networkError', config, { + code: '60204', + msg: 'Network failure', + desc: { + detail: 'js resource load timeout' + } + }); + } else if (s === "loading") { + callbacks[type].push(init); + } + }); + +}; + + +})(window); diff --git a/backend/src/assets/less/index.less b/backend/src/assets/less/index.less index 21ba6db..d81a23b 100644 --- a/backend/src/assets/less/index.less +++ b/backend/src/assets/less/index.less @@ -1,2 +1,2 @@ -@import './reset.less'; +@import './reset.less'; @import './style.less'; \ No newline at end of file diff --git a/backend/src/assets/less/reset.less b/backend/src/assets/less/reset.less index bc30922..dc2365a 100644 --- a/backend/src/assets/less/reset.less +++ b/backend/src/assets/less/reset.less @@ -1,192 +1,192 @@ -// reset.less - 现代 CSS 样式重置 -// 统一盒模型为 border-box -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -// 基础字体与颜色设置 -html { - // 基础字体大小 (1rem = 16px) - font-size: 16px; - // 平滑滚动 - scroll-behavior: smooth; - height: 100%; - width: 100%; -} - -body { - // 继承父级字体设置 - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; - font-size: 1rem; - line-height: 1.5; // 舒适行高 - color: #333; // 基础文本色 - // background-color: #fff; // 基础背景色 - -webkit-text-size-adjust: 100%; // 防止iOS横屏字体放大 - height: 100%; - width: 100%; - margin: 0; - padding: 0; -} - -// 移除默认边框 -img, -iframe, -embed, -object, -video { - border: 0; -} - -// 图片与媒体元素自适应 -img, -svg, -video, -canvas, -audio, -iframe, -embed, -object { - display: block; - max-width: 100%; - height: auto; -} - -// 表格重置 -table { - border-collapse: collapse; - border-spacing: 0; - width: 100%; -} - -// 列表样式重置 -ul, -ol, -li { - list-style: none; -} - -// 文本元素重置 -a { - color: inherit; // 继承父级颜色 - text-decoration: none; - background-color: transparent; -} - -a:hover, -a:focus { - outline: none; -} - -// 标题元素重置 -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: inherit; - font-weight: inherit; - margin: 0; -} - -// 表单元素重置 -button, -input, -optgroup, -select, -textarea { - font-family: inherit; - font-size: 100%; - line-height: 1.15; - margin: 0; - padding: 0; - border: none; - background: transparent; - color: inherit; -} - -button, -input { - overflow: visible; -} - -button, -select { - text-transform: none; -} - -// 按钮样式重置 -button, -[type="button"], -[type="reset"], -[type="submit"] { - -webkit-appearance: button; - cursor: pointer; -} - -button::-moz-focus-inner, -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner { - border-style: none; - padding: 0; -} - -// 输入框聚焦样式 -input:focus, -select:focus, -textarea:focus, -button:focus { - outline: none; -} - -// 文本区域不允许拖拽调整大小 -textarea { - overflow: auto; - resize: vertical; // 仅允许垂直调整 -} - -// 移除占位符默认样式 -::-webkit-input-placeholder { - color: #999; - opacity: 1; -} - -::-moz-placeholder { - color: #999; - opacity: 1; -} - -:-ms-input-placeholder { - color: #999; - opacity: 1; -} - -::placeholder { - color: #999; - opacity: 1; -} - -// 清除浮动 -.clearfix::after { - content: ""; - display: table; - clear: both; -} - -// 隐藏元素(屏幕阅读器可见) -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border-width: 0; +// reset.less - 现代 CSS 样式重置 +// 统一盒模型为 border-box +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +// 基础字体与颜色设置 +html { + // 基础字体大小 (1rem = 16px) + font-size: 16px; + // 平滑滚动 + scroll-behavior: smooth; + height: 100%; + width: 100%; +} + +body { + // 继承父级字体设置 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + font-size: 1rem; + line-height: 1.5; // 舒适行高 + color: #333; // 基础文本色 + // background-color: #fff; // 基础背景色 + -webkit-text-size-adjust: 100%; // 防止iOS横屏字体放大 + height: 100%; + width: 100%; + margin: 0; + padding: 0; +} + +// 移除默认边框 +img, +iframe, +embed, +object, +video { + border: 0; +} + +// 图片与媒体元素自适应 +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + max-width: 100%; + height: auto; +} + +// 表格重置 +table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; +} + +// 列表样式重置 +ul, +ol, +li { + list-style: none; +} + +// 文本元素重置 +a { + color: inherit; // 继承父级颜色 + text-decoration: none; + background-color: transparent; +} + +a:hover, +a:focus { + outline: none; +} + +// 标题元素重置 +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; + margin: 0; +} + +// 表单元素重置 +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; + padding: 0; + border: none; + background: transparent; + color: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +// 按钮样式重置 +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +// 输入框聚焦样式 +input:focus, +select:focus, +textarea:focus, +button:focus { + outline: none; +} + +// 文本区域不允许拖拽调整大小 +textarea { + overflow: auto; + resize: vertical; // 仅允许垂直调整 +} + +// 移除占位符默认样式 +::-webkit-input-placeholder { + color: #999; + opacity: 1; +} + +::-moz-placeholder { + color: #999; + opacity: 1; +} + +:-ms-input-placeholder { + color: #999; + opacity: 1; +} + +::placeholder { + color: #999; + opacity: 1; +} + +// 清除浮动 +.clearfix::after { + content: ""; + display: table; + clear: both; +} + +// 隐藏元素(屏幕阅读器可见) +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } \ No newline at end of file diff --git a/backend/src/assets/less/style.less b/backend/src/assets/less/style.less index 4ecac9f..212f329 100644 --- a/backend/src/assets/less/style.less +++ b/backend/src/assets/less/style.less @@ -1,432 +1,432 @@ -// Element Plus Message z-index -:root { - --el-message-z-index: 9999; -} - -// body 样式 -body { - // background-color: #f5f7fa; - color: #303133; - transition: background-color 0.3s ease, color 0.3s ease; -} - -// container-box 样式 -.container-box { - // background-color: #ffffff; - // border: 1px solid #ebeef5; - padding: 24px; - background-color: var(--el-bg-color); - border: 1px solid var(--el-border-color-lighter); - box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04); - border-radius: 8px; - padding: 24px; - transition: background-color 0.3s, border-color 0.3s, box-shadow 0.3s; -} - -.header-bar { - display: flex; - align-items: center; - justify-content: space-between; -} - -.pagination-bar { - display: flex; - justify-content: flex-end; - margin: 14px 0 0 0; -} - -// 修复 ElMessage 显示问题 -// 只修复定位,保持 Element Plus 官方样式 -.el-message { - // 确保消息固定在页面顶部中央,不受父容器影响 - position: fixed !important; - z-index: var(--el-message-z-index, 9999) !important; - pointer-events: auto !important; -} - -.wang-editor-wrapper{ - border: 1px solid #dcdfe6 !important; - - .toolbar-container{ - border-bottom: 1px solid #dcdfe6 !important; - } - - .editor-container { - background-color: #ffffff !important; - } - - :deep(.w-e-text), - :deep(.w-e-text-container) { - background-color: transparent !important; - - * { - color: #1a1a2e !important; - } - - p { - color: #1a1a2e !important; - margin: 8px 0 !important; - line-height: 1.8 !important; - font-size: 14px !important; - text-indent: 0 !important; - } - - span { - color: #1a1a2e !important; - font-size: 14px !important; - line-height: 1.8 !important; - } - - strong, b { - font-weight: 600 !important; - color: #1a1a2e !important; - } - - em, i { - font-style: italic !important; - } - - u { - text-decoration: underline !important; - } - - s, del { - text-decoration: line-through !important; - } - - h1 { - color: #1a1a2e !important; - font-size: 28px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h2 { - color: #1a1a2e !important; - font-size: 24px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h3 { - color: #1a1a2e !important; - font-size: 20px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h4 { - color: #1a1a2e !important; - font-size: 18px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h5 { - color: #1a1a2e !important; - font-size: 16px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h6 { - color: #1a1a2e !important; - font-size: 14px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - a { - color: #3973ff !important; - text-decoration: underline !important; - - &:hover { - color: #3973ff !important; - opacity: 0.8 !important; - } - } - - code { - background-color: #f5f7fa !important; - color: #1a1a2e !important; - border: 1px solid #e4e7ed !important; - border-radius: 3px !important; - padding: 2px 6px !important; - font-family: 'Consolas', 'Monaco', monospace !important; - font-size: 13px !important; - } - - pre { - background-color: #f5f7fa !important; - border: 1px solid #e4e7ed !important; - border-radius: 4px !important; - color: #1a1a2e !important; - padding: 12px 16px !important; - margin: 12px 0 !important; - overflow-x: auto; - - code { - background-color: transparent !important; - border: none !important; - padding: 0 !important; - color: #1a1a2e !important; - font-size: 13px !important; - line-height: 1.6 !important; - } - } - - blockquote { - border-left: 4px solid #3973ff !important; - background-color: #f5f7fa !important; - color: #606266 !important; - padding: 8px 16px !important; - margin: 12px 0 !important; - } - - table { - border-collapse: collapse !important; - border: 1px solid #e4e7ed !important; - width: 100% !important; - - th, td { - border: 1px solid #e4e7ed !important; - background-color: #ffffff !important; - color: #1a1a2e !important; - padding: 8px 12px !important; - min-width: 60px; - } - - th { - background-color: #f5f7fa !important; - font-weight: 600 !important; - } - } - - ul { - list-style-type: disc !important; - color: #1a1a2e !important; - padding-left: 24px !important; - margin: 8px 0 !important; - } - - ol { - list-style-type: decimal !important; - color: #1a1a2e !important; - padding-left: 24px !important; - margin: 8px 0 !important; - } - - li { - color: #1a1a2e !important; - line-height: 1.8 !important; - margin: 4px 0 !important; - } - - hr { - border-top: 1px solid #e4e7ed !important; - margin: 16px 0 !important; - } - - img { - max-width: 100% !important; - border-radius: 4px !important; - margin: 8px 0 !important; - } - - video { - max-width: 100% !important; - border-radius: 4px !important; - margin: 8px 0 !important; - } - - .w-e-panel-tab-content { - color: #1a1a2e !important; - } - } -} - -html.dark { - .wang-editor-wrapper { - border-color: #3d3d3d !important; - background-color: #1a1a1a !important; - - .toolbar-container { - background-color: #2d2d2d !important; - border-color: #3d3d3d !important; - } - - .editor-container { - background-color: #1a1a1a !important; - - &::-webkit-scrollbar-thumb { - background: #4d4d4d !important; - } - - &::-webkit-scrollbar-track { - background: #2d2d2d !important; - } - } - - :deep(.w-e-text), - :deep(.w-e-text-container) { - background-color: transparent !important; - - * { - color: #e0e0e0 !important; - } - - p { - color: #e0e0e0 !important; - margin: 8px 0 !important; - line-height: 1.8 !important; - font-size: 14px !important; - text-indent: 0 !important; - } - - span { - color: #e0e0e0 !important; - font-size: 14px !important; - line-height: 1.8 !important; - } - - strong, b { - font-weight: 600 !important; - color: #e0e0e0 !important; - } - - em, i { - font-style: italic !important; - } - - u { - text-decoration: underline !important; - } - - s, del { - text-decoration: line-through !important; - } - - h1 { - color: #e0e0e0 !important; - font-size: 28px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h2 { - color: #e0e0e0 !important; - font-size: 24px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h3 { - color: #e0e0e0 !important; - font-size: 20px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h4 { - color: #e0e0e0 !important; - font-size: 18px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h5 { - color: #e0e0e0 !important; - font-size: 16px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h6 { - color: #e0e0e0 !important; - font-size: 14px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - a { - color: #4f84ff !important; - text-decoration: underline !important; - - &:hover { - color: #4f84ff !important; - opacity: 0.8 !important; - } - } - - code { - background-color: #2d2d2d !important; - color: #e0e0e0 !important; - border-color: #3d3d3d !important; - } - - pre { - background-color: #2d2d2d !important; - border-color: #3d3d3d !important; - color: #e0e0e0 !important; - - code { - background-color: transparent !important; - border: none !important; - color: #e0e0e0 !important; - } - } - - blockquote { - border-left-color: #4f84ff !important; - background-color: #2d2d2d !important; - color: #b0b0b0 !important; - } - - table { - border-color: #3d3d3d !important; - - th, td { - border-color: #3d3d3d !important; - background-color: #1a1a1a !important; - color: #e0e0e0 !important; - } - - th { - background-color: #2d2d2d !important; - } - } - - ul, ol, li { - color: #e0e0e0 !important; - } - - hr { - border-top-color: #3d3d3d !important; - } - - img, video { - max-width: 100% !important; - border-radius: 4px !important; - } - - .w-e-panel-tab-content { - color: #e0e0e0 !important; - } - } - } -} -.el-form-item__label{ - min-width: 80px !important; +// Element Plus Message z-index +:root { + --el-message-z-index: 9999; +} + +// body 样式 +body { + // background-color: #f5f7fa; + color: #303133; + transition: background-color 0.3s ease, color 0.3s ease; +} + +// container-box 样式 +.container-box { + // background-color: #ffffff; + // border: 1px solid #ebeef5; + padding: 24px; + background-color: var(--el-bg-color); + border: 1px solid var(--el-border-color-lighter); + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04); + border-radius: 8px; + padding: 24px; + transition: background-color 0.3s, border-color 0.3s, box-shadow 0.3s; +} + +.header-bar { + display: flex; + align-items: center; + justify-content: space-between; +} + +.pagination-bar { + display: flex; + justify-content: flex-end; + margin: 14px 0 0 0; +} + +// 修复 ElMessage 显示问题 +// 只修复定位,保持 Element Plus 官方样式 +.el-message { + // 确保消息固定在页面顶部中央,不受父容器影响 + position: fixed !important; + z-index: var(--el-message-z-index, 9999) !important; + pointer-events: auto !important; +} + +.wang-editor-wrapper{ + border: 1px solid #dcdfe6 !important; + + .toolbar-container{ + border-bottom: 1px solid #dcdfe6 !important; + } + + .editor-container { + background-color: #ffffff !important; + } + + :deep(.w-e-text), + :deep(.w-e-text-container) { + background-color: transparent !important; + + * { + color: #1a1a2e !important; + } + + p { + color: #1a1a2e !important; + margin: 8px 0 !important; + line-height: 1.8 !important; + font-size: 14px !important; + text-indent: 0 !important; + } + + span { + color: #1a1a2e !important; + font-size: 14px !important; + line-height: 1.8 !important; + } + + strong, b { + font-weight: 600 !important; + color: #1a1a2e !important; + } + + em, i { + font-style: italic !important; + } + + u { + text-decoration: underline !important; + } + + s, del { + text-decoration: line-through !important; + } + + h1 { + color: #1a1a2e !important; + font-size: 28px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h2 { + color: #1a1a2e !important; + font-size: 24px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h3 { + color: #1a1a2e !important; + font-size: 20px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h4 { + color: #1a1a2e !important; + font-size: 18px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h5 { + color: #1a1a2e !important; + font-size: 16px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h6 { + color: #1a1a2e !important; + font-size: 14px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + a { + color: #3973ff !important; + text-decoration: underline !important; + + &:hover { + color: #3973ff !important; + opacity: 0.8 !important; + } + } + + code { + background-color: #f5f7fa !important; + color: #1a1a2e !important; + border: 1px solid #e4e7ed !important; + border-radius: 3px !important; + padding: 2px 6px !important; + font-family: 'Consolas', 'Monaco', monospace !important; + font-size: 13px !important; + } + + pre { + background-color: #f5f7fa !important; + border: 1px solid #e4e7ed !important; + border-radius: 4px !important; + color: #1a1a2e !important; + padding: 12px 16px !important; + margin: 12px 0 !important; + overflow-x: auto; + + code { + background-color: transparent !important; + border: none !important; + padding: 0 !important; + color: #1a1a2e !important; + font-size: 13px !important; + line-height: 1.6 !important; + } + } + + blockquote { + border-left: 4px solid #3973ff !important; + background-color: #f5f7fa !important; + color: #606266 !important; + padding: 8px 16px !important; + margin: 12px 0 !important; + } + + table { + border-collapse: collapse !important; + border: 1px solid #e4e7ed !important; + width: 100% !important; + + th, td { + border: 1px solid #e4e7ed !important; + background-color: #ffffff !important; + color: #1a1a2e !important; + padding: 8px 12px !important; + min-width: 60px; + } + + th { + background-color: #f5f7fa !important; + font-weight: 600 !important; + } + } + + ul { + list-style-type: disc !important; + color: #1a1a2e !important; + padding-left: 24px !important; + margin: 8px 0 !important; + } + + ol { + list-style-type: decimal !important; + color: #1a1a2e !important; + padding-left: 24px !important; + margin: 8px 0 !important; + } + + li { + color: #1a1a2e !important; + line-height: 1.8 !important; + margin: 4px 0 !important; + } + + hr { + border-top: 1px solid #e4e7ed !important; + margin: 16px 0 !important; + } + + img { + max-width: 100% !important; + border-radius: 4px !important; + margin: 8px 0 !important; + } + + video { + max-width: 100% !important; + border-radius: 4px !important; + margin: 8px 0 !important; + } + + .w-e-panel-tab-content { + color: #1a1a2e !important; + } + } +} + +html.dark { + .wang-editor-wrapper { + border-color: #3d3d3d !important; + background-color: #1a1a1a !important; + + .toolbar-container { + background-color: #2d2d2d !important; + border-color: #3d3d3d !important; + } + + .editor-container { + background-color: #1a1a1a !important; + + &::-webkit-scrollbar-thumb { + background: #4d4d4d !important; + } + + &::-webkit-scrollbar-track { + background: #2d2d2d !important; + } + } + + :deep(.w-e-text), + :deep(.w-e-text-container) { + background-color: transparent !important; + + * { + color: #e0e0e0 !important; + } + + p { + color: #e0e0e0 !important; + margin: 8px 0 !important; + line-height: 1.8 !important; + font-size: 14px !important; + text-indent: 0 !important; + } + + span { + color: #e0e0e0 !important; + font-size: 14px !important; + line-height: 1.8 !important; + } + + strong, b { + font-weight: 600 !important; + color: #e0e0e0 !important; + } + + em, i { + font-style: italic !important; + } + + u { + text-decoration: underline !important; + } + + s, del { + text-decoration: line-through !important; + } + + h1 { + color: #e0e0e0 !important; + font-size: 28px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h2 { + color: #e0e0e0 !important; + font-size: 24px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h3 { + color: #e0e0e0 !important; + font-size: 20px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h4 { + color: #e0e0e0 !important; + font-size: 18px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h5 { + color: #e0e0e0 !important; + font-size: 16px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h6 { + color: #e0e0e0 !important; + font-size: 14px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + a { + color: #4f84ff !important; + text-decoration: underline !important; + + &:hover { + color: #4f84ff !important; + opacity: 0.8 !important; + } + } + + code { + background-color: #2d2d2d !important; + color: #e0e0e0 !important; + border-color: #3d3d3d !important; + } + + pre { + background-color: #2d2d2d !important; + border-color: #3d3d3d !important; + color: #e0e0e0 !important; + + code { + background-color: transparent !important; + border: none !important; + color: #e0e0e0 !important; + } + } + + blockquote { + border-left-color: #4f84ff !important; + background-color: #2d2d2d !important; + color: #b0b0b0 !important; + } + + table { + border-color: #3d3d3d !important; + + th, td { + border-color: #3d3d3d !important; + background-color: #1a1a1a !important; + color: #e0e0e0 !important; + } + + th { + background-color: #2d2d2d !important; + } + } + + ul, ol, li { + color: #e0e0e0 !important; + } + + hr { + border-top-color: #3d3d3d !important; + } + + img, video { + max-width: 100% !important; + border-radius: 4px !important; + } + + .w-e-panel-tab-content { + color: #e0e0e0 !important; + } + } + } +} +.el-form-item__label{ + min-width: 80px !important; } \ No newline at end of file diff --git a/backend/src/auto-imports.d.ts b/backend/src/auto-imports.d.ts index 9d24007..34d4958 100644 --- a/backend/src/auto-imports.d.ts +++ b/backend/src/auto-imports.d.ts @@ -1,10 +1,10 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// noinspection JSUnusedGlobalSymbols -// Generated by unplugin-auto-import -// biome-ignore lint: disable -export {} -declare global { - -} +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + +} diff --git a/backend/src/components.d.ts b/backend/src/components.d.ts index f6e645c..3f9b2b9 100644 --- a/backend/src/components.d.ts +++ b/backend/src/components.d.ts @@ -1,18 +1,18 @@ -/* eslint-disable */ -// @ts-nocheck -// biome-ignore lint: disable -// oxlint-disable -// ------ -// Generated by unplugin-vue-components -// Read more: https://github.com/vuejs/core/pull/3399 - -export {} - -/* prettier-ignore */ -declare module 'vue' { - export interface GlobalComponents { - ElButton: typeof import('element-plus/es')['ElButton'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] - } -} +/* eslint-disable */ +// @ts-nocheck +// biome-ignore lint: disable +// oxlint-disable +// ------ +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 + +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + ElButton: typeof import('element-plus/es')['ElButton'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} diff --git a/backend/src/components/CommonAside.vue b/backend/src/components/CommonAside.vue index 479e138..5b55ba0 100644 --- a/backend/src/components/CommonAside.vue +++ b/backend/src/components/CommonAside.vue @@ -1,559 +1,559 @@ - - - - - + + + + + diff --git a/backend/src/components/CommonHeader.vue b/backend/src/components/CommonHeader.vue index 798be93..cc6ff59 100644 --- a/backend/src/components/CommonHeader.vue +++ b/backend/src/components/CommonHeader.vue @@ -1,747 +1,747 @@ - - - - - + + + + + diff --git a/backend/src/components/MessageDetailDialog.vue b/backend/src/components/MessageDetailDialog.vue index 9cf4878..32e367a 100644 --- a/backend/src/components/MessageDetailDialog.vue +++ b/backend/src/components/MessageDetailDialog.vue @@ -1,118 +1,118 @@ - - - - - + + + + + diff --git a/backend/src/env.d.ts b/backend/src/env.d.ts index b08d4ba..1e3c0b5 100644 --- a/backend/src/env.d.ts +++ b/backend/src/env.d.ts @@ -1,47 +1,47 @@ -/// - -declare module '*.vue' { - import type { DefineComponent } from 'vue'; - const component: DefineComponent<{}, {}, any>; - export default component; -} - -declare module '@/*' { - import type { ComponentOptions } from 'vue'; - const component: ComponentOptions; - export default component; -} - -declare module '@/api/erp' { - export function getOrganizationList(): Promise; - export function getOrganizationDetail(id: number | string): Promise; - export function createOrganization(data: any): Promise; - export function editOrganization(id: number | string, data: any): Promise; - export function deleteOrganization(id: number | string): Promise; - export function getCompanys(): Promise; - export function getDepartments(parentId?: number | string): Promise; - export function getEmployeeList(tenantId?: number | string): Promise; - export function getEmployeeDetail(id: number | string): Promise; - export function createEmployee(data: any): Promise; - export function editEmployee(id: number | string, data: any): Promise; - export function deleteEmployee(id: number | string): Promise; -} - -declare module '@/stores/auth' { - export function useAuthStore(): any; -} - -interface ImportMetaEnv { - readonly VITE_API_BASE_URL: string; - // 添加其他环境变量... -} - -interface ImportMeta { - readonly env: ImportMetaEnv; -} - -declare module 'vue-cropper' { - import { DefineComponent } from 'vue'; - const VueCropper: DefineComponent<{}, {}, any>; - export default VueCropper; -} +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + const component: DefineComponent<{}, {}, any>; + export default component; +} + +declare module '@/*' { + import type { ComponentOptions } from 'vue'; + const component: ComponentOptions; + export default component; +} + +declare module '@/api/erp' { + export function getOrganizationList(): Promise; + export function getOrganizationDetail(id: number | string): Promise; + export function createOrganization(data: any): Promise; + export function editOrganization(id: number | string, data: any): Promise; + export function deleteOrganization(id: number | string): Promise; + export function getCompanys(): Promise; + export function getDepartments(parentId?: number | string): Promise; + export function getEmployeeList(tenantId?: number | string): Promise; + export function getEmployeeDetail(id: number | string): Promise; + export function createEmployee(data: any): Promise; + export function editEmployee(id: number | string, data: any): Promise; + export function deleteEmployee(id: number | string): Promise; +} + +declare module '@/stores/auth' { + export function useAuthStore(): any; +} + +interface ImportMetaEnv { + readonly VITE_API_BASE_URL: string; + // 添加其他环境变量... +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + +declare module 'vue-cropper' { + import { DefineComponent } from 'vue'; + const VueCropper: DefineComponent<{}, {}, any>; + export default VueCropper; +} diff --git a/backend/src/main.js b/backend/src/main.js index 0478597..b0a1386 100644 --- a/backend/src/main.js +++ b/backend/src/main.js @@ -1,54 +1,54 @@ -import { createApp } from 'vue' -import App from '@/App.vue' -import * as ElementPlusIconsVue from '@element-plus/icons-vue' -// 导入 Element Plus 样式(必须) -import 'element-plus/dist/index.css' -// 导入 Element Plus 暗黑模式样式 -import 'element-plus/theme-chalk/dark/css-vars.css' -import '@/assets/less/index.less' -import '@/assets/css/all.min.css' -import '@/assets/js/all.min.js' -import router from './router' -import { loadAndAddDynamicRoutes } from './router' -import { createPinia } from 'pinia' -import { useAuthStore } from './stores/auth' -// import { initTheme } from './utils/theme' -// 导入全局组件 -import WangEditor from '@/views/components/WangEditor.vue'; - -const app = createApp(App) -const pinia = createPinia() -// 全局注册 WangEditor 组件 -app.component('WangEditor', WangEditor); - -for (const [key, component] of Object.entries(ElementPlusIconsVue)) { - app.component(key, component) -} - -app.use(pinia) -app.use(router) - -// 初始化主题(必须在挂载前执行) -// initTheme() - -// 初始化时检查认证状态 -const authStore = useAuthStore() -authStore.checkAuth() - -// 如果用户已登录,在应用启动时加载动态路由 -if (authStore.isLoggedIn) { - loadAndAddDynamicRoutes() - .catch(err => { - console.error('应用启动时加载动态路由失败:', err); - }) - .then(() => { - // 检查是否因为 token 无效而导致路由加载失败 - const token = localStorage.getItem('token'); - if (!token) { - authStore.clearToken(); - window.location.href = '#/login'; - } - }); -} - +import { createApp } from 'vue' +import App from '@/App.vue' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +// 导入 Element Plus 样式(必须) +import 'element-plus/dist/index.css' +// 导入 Element Plus 暗黑模式样式 +import 'element-plus/theme-chalk/dark/css-vars.css' +import '@/assets/less/index.less' +import '@/assets/css/all.min.css' +import '@/assets/js/all.min.js' +import router from './router' +import { loadAndAddDynamicRoutes } from './router' +import { createPinia } from 'pinia' +import { useAuthStore } from './stores/auth' +// import { initTheme } from './utils/theme' +// 导入全局组件 +import WangEditor from '@/views/components/WangEditor.vue'; + +const app = createApp(App) +const pinia = createPinia() +// 全局注册 WangEditor 组件 +app.component('WangEditor', WangEditor); + +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(pinia) +app.use(router) + +// 初始化主题(必须在挂载前执行) +// initTheme() + +// 初始化时检查认证状态 +const authStore = useAuthStore() +authStore.checkAuth() + +// 如果用户已登录,在应用启动时加载动态路由 +if (authStore.isLoggedIn) { + loadAndAddDynamicRoutes() + .catch(err => { + console.error('应用启动时加载动态路由失败:', err); + }) + .then(() => { + // 检查是否因为 token 无效而导致路由加载失败 + const token = localStorage.getItem('token'); + if (!token) { + authStore.clearToken(); + window.location.href = '#/login'; + } + }); +} + app.mount('#app') \ No newline at end of file diff --git a/backend/src/router/dynamicRoutes.js b/backend/src/router/dynamicRoutes.js index c325149..d1fb33c 100644 --- a/backend/src/router/dynamicRoutes.js +++ b/backend/src/router/dynamicRoutes.js @@ -1,176 +1,176 @@ -import { createComponentLoader } from '@/utils/pathResolver'; - -function computeFullPath(menuPath, parentPath) { - if (!menuPath) return parentPath || ''; - if (menuPath.startsWith('/')) { - return menuPath.replace(/\/+/g, '/'); - } - const base = (parentPath || '').replace(/\/$/, ''); - return `${base}/${menuPath}`.replace(/\/+/g, '/'); -} - -/** 将子路由的绝对路径转为相对父布局的路径,供 Vue Router 嵌套使用 */ -function toRelativeChildPath(parentAbs, childAbs) { - const base = (parentAbs || '').replace(/\/$/, ''); - const target = (childAbs || '').replace(/\/$/, ''); - if (!target) return ''; - if (target === base) return ''; - const prefix = `${base}/`; - if (target.startsWith(prefix)) { - return target.slice(prefix.length); - } - // 兜底:取最后一段(菜单 path 配置异常时) - const parts = target.split('/').filter(Boolean); - return parts.length ? parts[parts.length - 1] : ''; -} - -function hasPageComponent(menu) { - return menu.type === 4 || (menu.component_path && String(menu.component_path).trim() !== ''); -} - -function resolvePageComponent(menu) { - if (menu.type === 4) { - return () => import('@/views/onepage/index.vue'); - } - if (menu.component_path && String(menu.component_path).trim() !== '') { - return createComponentLoader(menu.component_path); - } - return () => import('@/views/404/404.vue'); -} - -/** - * 菜单子节点 -> 嵌套路由(path 相对 layoutAbsPath) - */ -function convertNestedMenuChildren(children, layoutAbsPath) { - if (!children || children.length === 0) return []; - return children.map((child) => nestedMenuToRoute(child, layoutAbsPath)); -} - -function nestedMenuToRoute(menu, layoutAbsPath) { - const childAbs = computeFullPath(menu.path, layoutAbsPath); - const relPath = toRelativeChildPath(layoutAbsPath, childAbs); - const hasChildren = menu.children && menu.children.length > 0; - const ownPage = hasPageComponent(menu); - - const meta = { - title: menu.title, - icon: menu.icon, - id: menu.id, - componentPath: menu.component_path, - }; - - // 既有自己的页面又有子菜单:套一层 EmptyLayout,避免父页面组件里没有 导致子路由无法渲染 - if (hasChildren && ownPage) { - return { - path: relPath, - name: `menu_${menu.id}`, - meta, - component: () => import('@/views/layouts/EmptyLayout.vue'), - children: [ - { - path: '', - name: `menu_${menu.id}_index`, - meta: { ...meta }, - component: resolvePageComponent(menu), - }, - ...convertNestedMenuChildren(menu.children, childAbs), - ], - }; - } - - // 纯目录 + 子节点 - if (hasChildren && !ownPage) { - const route = { - path: relPath, - name: `menu_${menu.id}`, - meta, - component: () => import('@/views/layouts/EmptyLayout.vue'), - children: convertNestedMenuChildren(menu.children, childAbs), - }; - const firstChild = menu.children[0]; - if (firstChild && firstChild.path) { - const firstAbs = computeFullPath(firstChild.path, childAbs); - const firstRel = toRelativeChildPath(childAbs, firstAbs); - if (firstRel) { - route.redirect = firstRel; - } - } - return route; - } - - // 叶子页面 - return { - path: relPath, - name: `menu_${menu.id}`, - meta, - component: resolvePageComponent(menu), - }; -} - -// 递归转换嵌套菜单为嵌套路由 -export function convertMenusToRoutes(menus, parentPath = '') { - if (!menus || menus.length === 0) return []; - - return menus.map((menu) => { - const fullPath = menu.path - ? menu.path.startsWith('/') - ? menu.path.replace(/\/+/g, '/') - : `${(parentPath || '').replace(/\/$/, '')}/${menu.path}`.replace(/\/+/g, '/') - : ''; - - const hasChildren = menu.children && menu.children.length > 0; - const ownPage = hasPageComponent(menu); - - const meta = { - title: menu.title, - icon: menu.icon, - id: menu.id, - componentPath: menu.component_path, - }; - - // 顶层:有页面 + 有子菜单 -> EmptyLayout + 默认子路由 + 相对 path 子路由 - if (hasChildren && ownPage) { - return { - path: fullPath || menu.path || '', - name: `menu_${menu.id}`, - meta, - component: () => import('@/views/layouts/EmptyLayout.vue'), - children: [ - { - path: '', - name: `menu_${menu.id}_index`, - meta: { ...meta }, - component: resolvePageComponent(menu), - }, - ...convertNestedMenuChildren(menu.children, fullPath), - ], - }; - } - - const route = { - path: fullPath || menu.path || '', - name: `menu_${menu.id}`, - meta, - }; - - if (menu.type === 4) { - route.component = () => import('@/views/onepage/index.vue'); - } else if (menu.component_path && menu.component_path.trim() !== '') { - route.component = createComponentLoader(menu.component_path); - } else if (hasChildren) { - route.component = () => import('@/views/layouts/EmptyLayout.vue'); - route.children = convertMenusToRoutes(menu.children, fullPath); - const firstChild = menu.children[0]; - if (firstChild && firstChild.path) { - const childFullPath = firstChild.path.startsWith('/') - ? firstChild.path - : `${fullPath}/${firstChild.path}`; - route.redirect = childFullPath; - } - } else { - route.component = () => import('@/views/404/404.vue'); - } - - return route; - }); -} +import { createComponentLoader } from '@/utils/pathResolver'; + +function computeFullPath(menuPath, parentPath) { + if (!menuPath) return parentPath || ''; + if (menuPath.startsWith('/')) { + return menuPath.replace(/\/+/g, '/'); + } + const base = (parentPath || '').replace(/\/$/, ''); + return `${base}/${menuPath}`.replace(/\/+/g, '/'); +} + +/** 将子路由的绝对路径转为相对父布局的路径,供 Vue Router 嵌套使用 */ +function toRelativeChildPath(parentAbs, childAbs) { + const base = (parentAbs || '').replace(/\/$/, ''); + const target = (childAbs || '').replace(/\/$/, ''); + if (!target) return ''; + if (target === base) return ''; + const prefix = `${base}/`; + if (target.startsWith(prefix)) { + return target.slice(prefix.length); + } + // 兜底:取最后一段(菜单 path 配置异常时) + const parts = target.split('/').filter(Boolean); + return parts.length ? parts[parts.length - 1] : ''; +} + +function hasPageComponent(menu) { + return menu.type === 4 || (menu.component_path && String(menu.component_path).trim() !== ''); +} + +function resolvePageComponent(menu) { + if (menu.type === 4) { + return () => import('@/views/onepage/index.vue'); + } + if (menu.component_path && String(menu.component_path).trim() !== '') { + return createComponentLoader(menu.component_path); + } + return () => import('@/views/404/404.vue'); +} + +/** + * 菜单子节点 -> 嵌套路由(path 相对 layoutAbsPath) + */ +function convertNestedMenuChildren(children, layoutAbsPath) { + if (!children || children.length === 0) return []; + return children.map((child) => nestedMenuToRoute(child, layoutAbsPath)); +} + +function nestedMenuToRoute(menu, layoutAbsPath) { + const childAbs = computeFullPath(menu.path, layoutAbsPath); + const relPath = toRelativeChildPath(layoutAbsPath, childAbs); + const hasChildren = menu.children && menu.children.length > 0; + const ownPage = hasPageComponent(menu); + + const meta = { + title: menu.title, + icon: menu.icon, + id: menu.id, + componentPath: menu.component_path, + }; + + // 既有自己的页面又有子菜单:套一层 EmptyLayout,避免父页面组件里没有 导致子路由无法渲染 + if (hasChildren && ownPage) { + return { + path: relPath, + name: `menu_${menu.id}`, + meta, + component: () => import('@/views/layouts/EmptyLayout.vue'), + children: [ + { + path: '', + name: `menu_${menu.id}_index`, + meta: { ...meta }, + component: resolvePageComponent(menu), + }, + ...convertNestedMenuChildren(menu.children, childAbs), + ], + }; + } + + // 纯目录 + 子节点 + if (hasChildren && !ownPage) { + const route = { + path: relPath, + name: `menu_${menu.id}`, + meta, + component: () => import('@/views/layouts/EmptyLayout.vue'), + children: convertNestedMenuChildren(menu.children, childAbs), + }; + const firstChild = menu.children[0]; + if (firstChild && firstChild.path) { + const firstAbs = computeFullPath(firstChild.path, childAbs); + const firstRel = toRelativeChildPath(childAbs, firstAbs); + if (firstRel) { + route.redirect = firstRel; + } + } + return route; + } + + // 叶子页面 + return { + path: relPath, + name: `menu_${menu.id}`, + meta, + component: resolvePageComponent(menu), + }; +} + +// 递归转换嵌套菜单为嵌套路由 +export function convertMenusToRoutes(menus, parentPath = '') { + if (!menus || menus.length === 0) return []; + + return menus.map((menu) => { + const fullPath = menu.path + ? menu.path.startsWith('/') + ? menu.path.replace(/\/+/g, '/') + : `${(parentPath || '').replace(/\/$/, '')}/${menu.path}`.replace(/\/+/g, '/') + : ''; + + const hasChildren = menu.children && menu.children.length > 0; + const ownPage = hasPageComponent(menu); + + const meta = { + title: menu.title, + icon: menu.icon, + id: menu.id, + componentPath: menu.component_path, + }; + + // 顶层:有页面 + 有子菜单 -> EmptyLayout + 默认子路由 + 相对 path 子路由 + if (hasChildren && ownPage) { + return { + path: fullPath || menu.path || '', + name: `menu_${menu.id}`, + meta, + component: () => import('@/views/layouts/EmptyLayout.vue'), + children: [ + { + path: '', + name: `menu_${menu.id}_index`, + meta: { ...meta }, + component: resolvePageComponent(menu), + }, + ...convertNestedMenuChildren(menu.children, fullPath), + ], + }; + } + + const route = { + path: fullPath || menu.path || '', + name: `menu_${menu.id}`, + meta, + }; + + if (menu.type === 4) { + route.component = () => import('@/views/onepage/index.vue'); + } else if (menu.component_path && menu.component_path.trim() !== '') { + route.component = createComponentLoader(menu.component_path); + } else if (hasChildren) { + route.component = () => import('@/views/layouts/EmptyLayout.vue'); + route.children = convertMenusToRoutes(menu.children, fullPath); + const firstChild = menu.children[0]; + if (firstChild && firstChild.path) { + const childFullPath = firstChild.path.startsWith('/') + ? firstChild.path + : `${fullPath}/${firstChild.path}`; + route.redirect = childFullPath; + } + } else { + route.component = () => import('@/views/404/404.vue'); + } + + return route; + }); +} diff --git a/backend/src/router/index.js b/backend/src/router/index.js index 14f609f..8798c45 100644 --- a/backend/src/router/index.js +++ b/backend/src/router/index.js @@ -1,195 +1,195 @@ -import { createRouter, createWebHashHistory } from "vue-router"; -import { convertMenusToRoutes } from "./dynamicRoutes"; - -// 静态子路由:需要在 Main 框架内显示的页面 -const staticMainChildren = [ - { - path: "/user/userProfile", - name: "userProfile", - component: () => import("@/views/user/userProfile.vue"), - meta: { requiresAuth: true, title: "用户中心" } - }, - // 兼容拼写错误的路径重定向 - { - path: "/apps/erp/dashborad", - redirect: "/apps/erp/dashboard" - } -]; - -// 静态路由:登录页独立、home 导航门户独立、404 页面独立 -const staticRoutes = [ - { - path: "/login", - name: "Login", - component: () => import("@/views/login/index.vue"), - meta: { requiresAuth: false } - }, - { - path: "/register", - name: "Register", - component: () => import("@/views/login/register.vue"), - meta: { requiresAuth: false } - }, - { - path: "/forget", - name: "ForgetPassword", - component: () => import("@/views/login/forget.vue"), - meta: { requiresAuth: false } - }, - { - path: "/home", - name: "Home", - component: () => import("@/views/home/index.vue"), - meta: { requiresAuth: true, title: "系统导航", isStandalone: true } - }, - // 兼容路径拼写错误:dashborad -> dashboard - { - path: "/apps/erp/dashborad", - redirect: "/apps/erp/dashboard" - }, - { - path: "/:pathMatch(.*)*", - name: "NotFound", - component: () => import("@/views/404/404.vue"), - meta: { requiresAuth: false } - } -]; - -const router = createRouter({ - history: createWebHashHistory(), - routes: staticRoutes -}); - -let dynamicRoutesAdded = false; -let dynamicRoutesData = []; -let routesLoadingPromise = null; - -export function resetDynamicRoutes() { - dynamicRoutesAdded = false; - routesLoadingPromise = null; -} - -export async function loadAndAddDynamicRoutes() { - if (routesLoadingPromise) { - return routesLoadingPromise; - } - - if (dynamicRoutesAdded) { - return Promise.resolve(); - } - - routesLoadingPromise = (async () => { - try { - const { useMenuStore } = await import("@/stores/menu"); - const menuStore = useMenuStore(); - const menuData = await menuStore.fetchMenus(); - - if (menuData && menuData.length > 0) { - addDynamicRoutes(menuData); - dynamicRoutesAdded = true; - routesLoadingPromise = null; - return Promise.resolve(); - } else { - dynamicRoutesAdded = true; - routesLoadingPromise = null; - return Promise.resolve(); - } - } catch (error) { - console.error('加载动态路由失败:', error); - dynamicRoutesAdded = true; - routesLoadingPromise = null; - throw error; - } - })(); - - return routesLoadingPromise; -} - -// 核心修改:移除扁平化,直接使用嵌套菜单生成路由 -function addDynamicRoutes(menus) { - if (!menus?.length) { - return; - } - - // 直接转换嵌套菜单为嵌套路由(不再扁平化) - const dynamicRoutes = convertMenusToRoutes(menus); - - if (router.hasRoute('Main')) { - router.removeRoute('Main'); - } - - // 重新添加主路由,合并静态子路由和动态路由 - router.addRoute({ - path: "/", - name: "Main", - component: () => import("@/views/Main.vue"), - redirect: "/dashboard", - meta: { requiresAuth: true }, - children: [...staticMainChildren, ...dynamicRoutes] // 合并静态和动态子路由 - }); - - dynamicRoutesAdded = true; -} - -function findRouteByName(routes, routeName) { - for (const route of routes) { - if (route.name === routeName) { - return route; - } - if (route.children) { - const found = findRouteByName(route.children, routeName); - if (found) { - return found; - } - } - } - return null; -} - -function findFirstValidRoute(routes) { - for (const route of routes) { - if (route.component) { - return route; - } - if (route.children && route.children.length > 0) { - const childRoute = findFirstValidRoute(route.children); - if (childRoute) { - return childRoute; - } - } - } - return null; -} - -router.beforeEach(async (to, from, next) => { - const token = localStorage.getItem("token"); - const publicPaths = ["/login", "/register", "/forget"]; - - if (publicPaths.includes(to.path)) { - if (token) { - if (!dynamicRoutesAdded) { - await loadAndAddDynamicRoutes(); - } - next({ path: "/home" }); - } else { - next(); - } - return; - } - - if (!token) { - next({ path: "/login", query: { redirect: to.path } }); - return; - } - - if (!dynamicRoutesAdded) { - await loadAndAddDynamicRoutes(); - // 路由加载后重新导航,确保路由匹配正确 - next({ path: to.path, replace: true }); - return; - } - - next(); -}); - -export default router; +import { createRouter, createWebHashHistory } from "vue-router"; +import { convertMenusToRoutes } from "./dynamicRoutes"; + +// 静态子路由:需要在 Main 框架内显示的页面 +const staticMainChildren = [ + { + path: "/user/userProfile", + name: "userProfile", + component: () => import("@/views/user/userProfile.vue"), + meta: { requiresAuth: true, title: "用户中心" } + }, + // 兼容拼写错误的路径重定向 + { + path: "/apps/erp/dashborad", + redirect: "/apps/erp/dashboard" + } +]; + +// 静态路由:登录页独立、home 导航门户独立、404 页面独立 +const staticRoutes = [ + { + path: "/login", + name: "Login", + component: () => import("@/views/login/index.vue"), + meta: { requiresAuth: false } + }, + { + path: "/register", + name: "Register", + component: () => import("@/views/login/register.vue"), + meta: { requiresAuth: false } + }, + { + path: "/forget", + name: "ForgetPassword", + component: () => import("@/views/login/forget.vue"), + meta: { requiresAuth: false } + }, + { + path: "/home", + name: "Home", + component: () => import("@/views/home/index.vue"), + meta: { requiresAuth: true, title: "系统导航", isStandalone: true } + }, + // 兼容路径拼写错误:dashborad -> dashboard + { + path: "/apps/erp/dashborad", + redirect: "/apps/erp/dashboard" + }, + { + path: "/:pathMatch(.*)*", + name: "NotFound", + component: () => import("@/views/404/404.vue"), + meta: { requiresAuth: false } + } +]; + +const router = createRouter({ + history: createWebHashHistory(), + routes: staticRoutes +}); + +let dynamicRoutesAdded = false; +let dynamicRoutesData = []; +let routesLoadingPromise = null; + +export function resetDynamicRoutes() { + dynamicRoutesAdded = false; + routesLoadingPromise = null; +} + +export async function loadAndAddDynamicRoutes() { + if (routesLoadingPromise) { + return routesLoadingPromise; + } + + if (dynamicRoutesAdded) { + return Promise.resolve(); + } + + routesLoadingPromise = (async () => { + try { + const { useMenuStore } = await import("@/stores/menu"); + const menuStore = useMenuStore(); + const menuData = await menuStore.fetchMenus(); + + if (menuData && menuData.length > 0) { + addDynamicRoutes(menuData); + dynamicRoutesAdded = true; + routesLoadingPromise = null; + return Promise.resolve(); + } else { + dynamicRoutesAdded = true; + routesLoadingPromise = null; + return Promise.resolve(); + } + } catch (error) { + console.error('加载动态路由失败:', error); + dynamicRoutesAdded = true; + routesLoadingPromise = null; + throw error; + } + })(); + + return routesLoadingPromise; +} + +// 核心修改:移除扁平化,直接使用嵌套菜单生成路由 +function addDynamicRoutes(menus) { + if (!menus?.length) { + return; + } + + // 直接转换嵌套菜单为嵌套路由(不再扁平化) + const dynamicRoutes = convertMenusToRoutes(menus); + + if (router.hasRoute('Main')) { + router.removeRoute('Main'); + } + + // 重新添加主路由,合并静态子路由和动态路由 + router.addRoute({ + path: "/", + name: "Main", + component: () => import("@/views/Main.vue"), + redirect: "/dashboard", + meta: { requiresAuth: true }, + children: [...staticMainChildren, ...dynamicRoutes] // 合并静态和动态子路由 + }); + + dynamicRoutesAdded = true; +} + +function findRouteByName(routes, routeName) { + for (const route of routes) { + if (route.name === routeName) { + return route; + } + if (route.children) { + const found = findRouteByName(route.children, routeName); + if (found) { + return found; + } + } + } + return null; +} + +function findFirstValidRoute(routes) { + for (const route of routes) { + if (route.component) { + return route; + } + if (route.children && route.children.length > 0) { + const childRoute = findFirstValidRoute(route.children); + if (childRoute) { + return childRoute; + } + } + } + return null; +} + +router.beforeEach(async (to, from, next) => { + const token = localStorage.getItem("token"); + const publicPaths = ["/login", "/register", "/forget"]; + + if (publicPaths.includes(to.path)) { + if (token) { + if (!dynamicRoutesAdded) { + await loadAndAddDynamicRoutes(); + } + next({ path: "/home" }); + } else { + next(); + } + return; + } + + if (!token) { + next({ path: "/login", query: { redirect: to.path } }); + return; + } + + if (!dynamicRoutesAdded) { + await loadAndAddDynamicRoutes(); + // 路由加载后重新导航,确保路由匹配正确 + next({ path: to.path, replace: true }); + return; + } + + next(); +}); + +export default router; diff --git a/backend/src/stores/auth.js b/backend/src/stores/auth.js index 185131c..0138f71 100644 --- a/backend/src/stores/auth.js +++ b/backend/src/stores/auth.js @@ -1,107 +1,107 @@ -import { defineStore } from 'pinia' -import { ref, reactive } from 'vue' - -// 用户信息类型 -const defaultUser = { - id:'', - account: '', - name: '', - group_id: '', - type: 'backend', - avatar: '' -} - -export const useAuthStore = defineStore('auth', () => { - const token = ref(localStorage.getItem('token') || '') - const isLoggedIn = ref(!!token.value) - const user = reactive({ ...defaultUser }) - - // 从缓存加载用户信息 - function loadUserFromCache() { - const cachedUser = localStorage.getItem('userInfo') - if (cachedUser) { - try { - const userInfo = JSON.parse(cachedUser) - Object.assign(user, userInfo) - } catch (e) { - console.error('Failed to parse user info from cache:', e) - } - } - } - - // 初始化时加载用户信息 - loadUserFromCache() - - // 保存登录信息(token 和用户信息) - function setLoginInfo(loginData) { - const userInfo = loginData.user || loginData - - const normalizedUser = { - id: parseInt(userInfo.id) || null, - account: userInfo.account || '', - name: userInfo.name || '', - group_id: userInfo.group_id || '', - type: 'backend', - tid: userInfo.tid || '', - avatar: userInfo.avatar || '' - } - - // 使用后端返回的真实 JWT token - const accessToken = loginData.token || '' - - token.value = accessToken - isLoggedIn.value = !!accessToken - localStorage.setItem('token', accessToken) - - Object.assign(user, normalizedUser) - localStorage.setItem('userInfo', JSON.stringify(normalizedUser)) - } - - // 设置 token(兼容旧代码) - function setToken(newToken) { - token.value = newToken - isLoggedIn.value = true - localStorage.setItem('token', newToken) - } - - // 清除登录信息 - function clearToken() { - token.value = '' - isLoggedIn.value = false - Object.assign(user, defaultUser) - localStorage.removeItem('token') - localStorage.removeItem('userInfo') - } - - // 检查认证状态 - function checkAuth() { - const storedToken = localStorage.getItem('token') - if (storedToken) { - token.value = storedToken - isLoggedIn.value = true - loadUserFromCache() - } else { - token.value = '' - isLoggedIn.value = false - Object.assign(user, defaultUser) - } - } - - // 更新用户信息 - function updateUserInfo(userInfo) { - Object.assign(user, userInfo) - localStorage.setItem('userInfo', JSON.stringify(userInfo)) - } - - return { - token, - isLoggedIn, - user, - setLoginInfo, - setToken, - clearToken, - checkAuth, - updateUserInfo - } -}) - +import { defineStore } from 'pinia' +import { ref, reactive } from 'vue' + +// 用户信息类型 +const defaultUser = { + id:'', + account: '', + name: '', + group_id: '', + type: 'backend', + avatar: '' +} + +export const useAuthStore = defineStore('auth', () => { + const token = ref(localStorage.getItem('token') || '') + const isLoggedIn = ref(!!token.value) + const user = reactive({ ...defaultUser }) + + // 从缓存加载用户信息 + function loadUserFromCache() { + const cachedUser = localStorage.getItem('userInfo') + if (cachedUser) { + try { + const userInfo = JSON.parse(cachedUser) + Object.assign(user, userInfo) + } catch (e) { + console.error('Failed to parse user info from cache:', e) + } + } + } + + // 初始化时加载用户信息 + loadUserFromCache() + + // 保存登录信息(token 和用户信息) + function setLoginInfo(loginData) { + const userInfo = loginData.user || loginData + + const normalizedUser = { + id: parseInt(userInfo.id) || null, + account: userInfo.account || '', + name: userInfo.name || '', + group_id: userInfo.group_id || '', + type: 'backend', + tid: userInfo.tid || '', + avatar: userInfo.avatar || '' + } + + // 使用后端返回的真实 JWT token + const accessToken = loginData.token || '' + + token.value = accessToken + isLoggedIn.value = !!accessToken + localStorage.setItem('token', accessToken) + + Object.assign(user, normalizedUser) + localStorage.setItem('userInfo', JSON.stringify(normalizedUser)) + } + + // 设置 token(兼容旧代码) + function setToken(newToken) { + token.value = newToken + isLoggedIn.value = true + localStorage.setItem('token', newToken) + } + + // 清除登录信息 + function clearToken() { + token.value = '' + isLoggedIn.value = false + Object.assign(user, defaultUser) + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + } + + // 检查认证状态 + function checkAuth() { + const storedToken = localStorage.getItem('token') + if (storedToken) { + token.value = storedToken + isLoggedIn.value = true + loadUserFromCache() + } else { + token.value = '' + isLoggedIn.value = false + Object.assign(user, defaultUser) + } + } + + // 更新用户信息 + function updateUserInfo(userInfo) { + Object.assign(user, userInfo) + localStorage.setItem('userInfo', JSON.stringify(userInfo)) + } + + return { + token, + isLoggedIn, + user, + setLoginInfo, + setToken, + clearToken, + checkAuth, + updateUserInfo + } +}) + diff --git a/backend/src/stores/index.js b/backend/src/stores/index.js index 757f154..8076ca4 100644 --- a/backend/src/stores/index.js +++ b/backend/src/stores/index.js @@ -1,197 +1,197 @@ -import { defineStore } from 'pinia'; -import { ref, computed, reactive } from 'vue'; - -// ========== 全局状态 Store ========== -function initState() { - return { - isCollapse: false, - }; -} - -export const useAllDataStore = defineStore('allData', () => { - const state = reactive(initState()); - const count = ref(0); - const doubleCount = computed(() => count.value * 2); - function increment() { - count.value++; - } - return { - state, - count, - doubleCount, - increment, - }; -}); - -// ========== 多标签页 Tabs Store ========== -import { defineStore as defineTabsStore } from 'pinia'; -import { ref as vueRef } from 'vue'; - -/** - * 多标签页Tabs状态管理 - * tabList每个tab结构: { - * title: 标签显示名, - * fullPath: 路由路径(唯一key), - * name: 路由name, - * icon: 图标(可选) - * } - */ -export const useTabsStore = defineTabsStore('tabs', () => { - // 固定首页tab - const defaultDashboardPath = '/home'; - - // 从 localStorage 恢复 tabs 状态 - function loadTabsFromStorage() { - try { - const savedTabs = localStorage.getItem('tabs_list'); - const savedActiveTab = localStorage.getItem('active_tab'); - if (savedTabs) { - const tabs = JSON.parse(savedTabs); - // 确保至少包含首页 - const hasHome = tabs.some(t => t.fullPath === defaultDashboardPath); - if (!hasHome) { - tabs.unshift({ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }); - } - return tabs; - } - } catch (e) { - console.warn('恢复 tabs 失败:', e); - } - return [{ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }]; - } - - // 保存 tabs 到 localStorage - function saveTabsToStorage(tabs, active) { - try { - localStorage.setItem('tabs_list', JSON.stringify(tabs)); - if (active) { - localStorage.setItem('active_tab', active); - } - } catch (e) { - console.warn('保存 tabs 失败:', e); - } - } - - const tabList = vueRef(loadTabsFromStorage()); - const savedActiveTab = localStorage.getItem('active_tab'); - const activeTab = vueRef(savedActiveTab || defaultDashboardPath); - - // 添加tab,若已存在则激活 - function addTab(tab) { - const exist = tabList.value.find((t) => t.fullPath === tab.fullPath); - if (!exist) { - tabList.value.push(tab); - } - activeTab.value = tab.fullPath; - saveTabsToStorage(tabList.value, activeTab.value); - } - - // 删除指定tab并切换激活tab - function removeTab(fullPath) { - const idx = tabList.value.findIndex((t) => t.fullPath === fullPath); - if (idx > -1) { - tabList.value.splice(idx, 1); - // 只在关闭当前激活tab时切换激活tab - if (activeTab.value === fullPath) { - if (tabList.value.length > 0) { - // 优先激活右侧(如无则激活左侧) - const newIdx = idx >= tabList.value.length ? tabList.value.length - 1 : idx; - activeTab.value = tabList.value[newIdx].fullPath; - } else { - // 全部关闭,兜底首页 - activeTab.value = defaultDashboardPath; - } - } - saveTabsToStorage(tabList.value, activeTab.value); - } - } - - // 关闭其他,只留首页和当前激活tab - function closeOthers() { - tabList.value = tabList.value.filter( - (t) => t.fullPath === defaultDashboardPath || t.fullPath === activeTab.value - ); - saveTabsToStorage(tabList.value, activeTab.value); - } - - // 关闭左侧(关闭指定tab左侧的所有tab,保留首页和目标tab) - function closeLeft(targetFullPath) { - const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath); - if (targetIndex > -1) { - // 保留首页和目标tab及其右侧的所有tab - const beforeIndex = tabList.value.slice(0, targetIndex); - const hasCloseableLeft = beforeIndex.some(t => t.fullPath !== defaultDashboardPath); - - if (hasCloseableLeft) { - tabList.value = tabList.value.filter((t, index) => - t.fullPath === defaultDashboardPath || index >= targetIndex - ); - // 如果关闭的tab中包含了当前激活的tab,则激活目标tab - if (!tabList.value.find(t => t.fullPath === activeTab.value)) { - activeTab.value = targetFullPath; - } - saveTabsToStorage(tabList.value, activeTab.value); - } - } - } - - // 关闭右侧(关闭指定tab右侧的所有tab,保留首页和目标tab) - function closeRight(targetFullPath) { - const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath); - if (targetIndex > -1) { - // 保留首页和目标tab及其左侧的所有tab - const afterIndex = tabList.value.slice(targetIndex + 1); - const hasCloseableRight = afterIndex.length > 0; - - if (hasCloseableRight) { - tabList.value = tabList.value.filter((t, index) => - t.fullPath === defaultDashboardPath || index <= targetIndex - ); - // 如果关闭的tab中包含了当前激活的tab,则激活目标tab - if (!tabList.value.find(t => t.fullPath === activeTab.value)) { - activeTab.value = targetFullPath; - } - saveTabsToStorage(tabList.value, activeTab.value); - } - } - } - - // 关闭全部,只留首页 - function closeAll() { - tabList.value = tabList.value.filter((t) => t.fullPath === defaultDashboardPath); - activeTab.value = defaultDashboardPath; - saveTabsToStorage(tabList.value, activeTab.value); - } - - // 设置激活tab(不触发路由跳转,仅用于更新状态) - function setActiveTab(fullPath) { - activeTab.value = fullPath; - saveTabsToStorage(tabList.value, activeTab.value); - } - - // 重置 tabs store 到初始状态(登出时使用) - function resetTabs() { - tabList.value = [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }]; - activeTab.value = defaultDashboardPath; - // 清除 localStorage 中的 tabs 数据 - localStorage.removeItem('tabs_list'); - localStorage.removeItem('active_tab'); - } - - return { - tabList, - activeTab, - addTab, - removeTab, - closeOthers, - closeLeft, - closeRight, - closeAll, - setActiveTab, - saveTabsToStorage, - resetTabs, - }; -}); - -// ========== 菜单 Menu Store ========== +import { defineStore } from 'pinia'; +import { ref, computed, reactive } from 'vue'; + +// ========== 全局状态 Store ========== +function initState() { + return { + isCollapse: false, + }; +} + +export const useAllDataStore = defineStore('allData', () => { + const state = reactive(initState()); + const count = ref(0); + const doubleCount = computed(() => count.value * 2); + function increment() { + count.value++; + } + return { + state, + count, + doubleCount, + increment, + }; +}); + +// ========== 多标签页 Tabs Store ========== +import { defineStore as defineTabsStore } from 'pinia'; +import { ref as vueRef } from 'vue'; + +/** + * 多标签页Tabs状态管理 + * tabList每个tab结构: { + * title: 标签显示名, + * fullPath: 路由路径(唯一key), + * name: 路由name, + * icon: 图标(可选) + * } + */ +export const useTabsStore = defineTabsStore('tabs', () => { + // 固定首页tab + const defaultDashboardPath = '/home'; + + // 从 localStorage 恢复 tabs 状态 + function loadTabsFromStorage() { + try { + const savedTabs = localStorage.getItem('tabs_list'); + const savedActiveTab = localStorage.getItem('active_tab'); + if (savedTabs) { + const tabs = JSON.parse(savedTabs); + // 确保至少包含首页 + const hasHome = tabs.some(t => t.fullPath === defaultDashboardPath); + if (!hasHome) { + tabs.unshift({ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }); + } + return tabs; + } + } catch (e) { + console.warn('恢复 tabs 失败:', e); + } + return [{ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }]; + } + + // 保存 tabs 到 localStorage + function saveTabsToStorage(tabs, active) { + try { + localStorage.setItem('tabs_list', JSON.stringify(tabs)); + if (active) { + localStorage.setItem('active_tab', active); + } + } catch (e) { + console.warn('保存 tabs 失败:', e); + } + } + + const tabList = vueRef(loadTabsFromStorage()); + const savedActiveTab = localStorage.getItem('active_tab'); + const activeTab = vueRef(savedActiveTab || defaultDashboardPath); + + // 添加tab,若已存在则激活 + function addTab(tab) { + const exist = tabList.value.find((t) => t.fullPath === tab.fullPath); + if (!exist) { + tabList.value.push(tab); + } + activeTab.value = tab.fullPath; + saveTabsToStorage(tabList.value, activeTab.value); + } + + // 删除指定tab并切换激活tab + function removeTab(fullPath) { + const idx = tabList.value.findIndex((t) => t.fullPath === fullPath); + if (idx > -1) { + tabList.value.splice(idx, 1); + // 只在关闭当前激活tab时切换激活tab + if (activeTab.value === fullPath) { + if (tabList.value.length > 0) { + // 优先激活右侧(如无则激活左侧) + const newIdx = idx >= tabList.value.length ? tabList.value.length - 1 : idx; + activeTab.value = tabList.value[newIdx].fullPath; + } else { + // 全部关闭,兜底首页 + activeTab.value = defaultDashboardPath; + } + } + saveTabsToStorage(tabList.value, activeTab.value); + } + } + + // 关闭其他,只留首页和当前激活tab + function closeOthers() { + tabList.value = tabList.value.filter( + (t) => t.fullPath === defaultDashboardPath || t.fullPath === activeTab.value + ); + saveTabsToStorage(tabList.value, activeTab.value); + } + + // 关闭左侧(关闭指定tab左侧的所有tab,保留首页和目标tab) + function closeLeft(targetFullPath) { + const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath); + if (targetIndex > -1) { + // 保留首页和目标tab及其右侧的所有tab + const beforeIndex = tabList.value.slice(0, targetIndex); + const hasCloseableLeft = beforeIndex.some(t => t.fullPath !== defaultDashboardPath); + + if (hasCloseableLeft) { + tabList.value = tabList.value.filter((t, index) => + t.fullPath === defaultDashboardPath || index >= targetIndex + ); + // 如果关闭的tab中包含了当前激活的tab,则激活目标tab + if (!tabList.value.find(t => t.fullPath === activeTab.value)) { + activeTab.value = targetFullPath; + } + saveTabsToStorage(tabList.value, activeTab.value); + } + } + } + + // 关闭右侧(关闭指定tab右侧的所有tab,保留首页和目标tab) + function closeRight(targetFullPath) { + const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath); + if (targetIndex > -1) { + // 保留首页和目标tab及其左侧的所有tab + const afterIndex = tabList.value.slice(targetIndex + 1); + const hasCloseableRight = afterIndex.length > 0; + + if (hasCloseableRight) { + tabList.value = tabList.value.filter((t, index) => + t.fullPath === defaultDashboardPath || index <= targetIndex + ); + // 如果关闭的tab中包含了当前激活的tab,则激活目标tab + if (!tabList.value.find(t => t.fullPath === activeTab.value)) { + activeTab.value = targetFullPath; + } + saveTabsToStorage(tabList.value, activeTab.value); + } + } + } + + // 关闭全部,只留首页 + function closeAll() { + tabList.value = tabList.value.filter((t) => t.fullPath === defaultDashboardPath); + activeTab.value = defaultDashboardPath; + saveTabsToStorage(tabList.value, activeTab.value); + } + + // 设置激活tab(不触发路由跳转,仅用于更新状态) + function setActiveTab(fullPath) { + activeTab.value = fullPath; + saveTabsToStorage(tabList.value, activeTab.value); + } + + // 重置 tabs store 到初始状态(登出时使用) + function resetTabs() { + tabList.value = [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }]; + activeTab.value = defaultDashboardPath; + // 清除 localStorage 中的 tabs 数据 + localStorage.removeItem('tabs_list'); + localStorage.removeItem('active_tab'); + } + + return { + tabList, + activeTab, + addTab, + removeTab, + closeOthers, + closeLeft, + closeRight, + closeAll, + setActiveTab, + saveTabsToStorage, + resetTabs, + }; +}); + +// ========== 菜单 Menu Store ========== export { useMenuStore } from './menu'; \ No newline at end of file diff --git a/backend/src/stores/menu.js b/backend/src/stores/menu.js index 4a128e1..803dec5 100644 --- a/backend/src/stores/menu.js +++ b/backend/src/stores/menu.js @@ -1,237 +1,237 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -// import { getUserInfo } from '@/utils/auth' -import { getMenus } from '@/api/menu'; - -export const useMenuStore = defineStore('menu', () => { - // 菜单数据 - const menus = ref([]); - - // 加载状态 - const loading = ref(false); - - // 加载错误 - const error = ref(null); - - // 正在加载的 Promise(用于避免重复请求) - let loadingPromise = null; - - // 菜单缓存 key(基于用户类型和角色ID) - const getCacheKey = () => { - try { - const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); - const loginType = userInfo.type || 'user'; - const roleId = userInfo.group_id || 0; - return `menu_cache_${loginType}_${roleId}`; - } catch (e) { - return 'menu_cache_default'; - } - }; - - // 从缓存加载菜单 - const loadFromCache = () => { - try { - const cacheKey = getCacheKey(); - const cached = localStorage.getItem(cacheKey); - if (cached) { - const menuData = JSON.parse(cached); - // 检查缓存是否过期(5分钟过期) - if (menuData.timestamp && Date.now() - menuData.timestamp < 5 * 60 * 1000) { - return menuData.menus; - } - } - } catch (e) { - console.warn('加载菜单缓存失败:', e); - } - return null; - }; - - // 保存菜单到缓存 - const saveToCache = (menuData) => { - try { - const cacheKey = getCacheKey(); - localStorage.setItem(cacheKey, JSON.stringify({ - menus: menuData, - timestamp: Date.now() - })); - } catch (e) { - console.warn('保存菜单缓存失败:', e); - } - }; - - // 清除菜单缓存 - const clearCache = () => { - try { - const cacheKey = getCacheKey(); - localStorage.removeItem(cacheKey); - // 也清除其他可能的缓存key(兼容旧代码) - localStorage.removeItem('menu_cache'); - } catch (e) { - console.warn('清除菜单缓存失败:', e); - } - }; - - // 获取用户信息 - const getUserInfo = () => { - try { - return JSON.parse(localStorage.getItem('userInfo') || '{}'); - } catch (e) { - return {}; - } - }; - - // 从 API 加载菜单(核心方法,确保只请求一次) - const fetchMenus = async (forceRefresh = false) => { - // 如果已经有正在加载的请求,直接返回该 Promise - if (loadingPromise && !forceRefresh) { - return loadingPromise; - } - - // 如果不强制刷新,先尝试从缓存加载 - if (!forceRefresh) { - const cachedMenus = loadFromCache(); - if (cachedMenus && cachedMenus.length > 0) { - menus.value = cachedMenus; - return Promise.resolve(cachedMenus); - } - } - - // 如果正在加载且不是强制刷新,返回现有的 Promise - if (loading.value && !forceRefresh) { - return loadingPromise; - } - - // 创建新的加载 Promise - loadingPromise = (async () => { - loading.value = true; - error.value = null; - - try { - const userInfo = getUserInfo(); - const loginType = userInfo.type || 'user'; - const roleId = userInfo.group_id || 0; - - let res; - - // 检查用户ID是否存在 - if (!userInfo.id) { - throw new Error('用户ID不存在,请重新登录'); - } - - // 用户登录,使用 getMenus 接口 - res = await getMenus(userInfo.id); - - // 检查响应格式 - if (!res) { - throw new Error('获取菜单失败:服务器无响应'); - } - - // 检查后端返回的 code 字段 - if (res.code !== 200) { - throw new Error(res.msg || '获取菜单失败'); - } - - // 如果 code 为 200,检查 data - if (res.code === 200) { - // data 可能是空数组,这也是有效的 - if (res.data !== undefined && res.data !== null) { - // 确保 data 是数组 - const menuData = Array.isArray(res.data) ? res.data : []; - // 直接使用后端返回的树形结构数据,不需要额外过滤 - menus.value = menuData; - // 保存到缓存 - saveToCache(menuData); - return menuData; - } else { - // data 为 null 或 undefined,使用空数组 - console.warn('菜单数据为空,使用空数组'); - menus.value = []; - saveToCache([]); - return []; - } - } - - // 如果响应格式不符合预期,尝试直接使用 res.data - if (res.data !== undefined) { - const menuData = Array.isArray(res.data) ? res.data : []; - const filtered = menuData.filter(m => (m.isShow ?? 1) !== 0); - menus.value = filtered; - saveToCache(filtered); - return filtered; - } - - // 如果都不符合,抛出错误 - throw new Error(res.message || '获取菜单失败:响应格式错误'); - } catch (err) { - error.value = err.message || '获取菜单失败'; - console.error('获取菜单失败:', err); - console.error('错误详情:', { - message: err.message, - response: err.response, - stack: err.stack - }); - - // 如果是 token 无效错误,不使用缓存,直接抛出 - if (err.message === 'token无效' || err.response?.status === 401) { - clearCache(); - menus.value = []; - throw err; - } - - // 如果出错,尝试使用缓存数据 - const cachedMenus = loadFromCache(); - if (cachedMenus && cachedMenus.length > 0) { - console.warn('使用缓存的菜单数据'); - menus.value = cachedMenus; - return cachedMenus; - } - - // 如果连缓存都没有,设置空数组,避免页面崩溃 - menus.value = []; - throw err; - } finally { - loading.value = false; - loadingPromise = null; - } - })(); - - return loadingPromise; - }; - - // 刷新菜单(强制从 API 获取) - const refreshMenus = async () => { - clearCache(); - return await fetchMenus(true); - }; - - // 重置菜单 store(登出时使用) - const resetMenus = () => { - menus.value = []; - loading.value = false; - error.value = null; - loadingPromise = null; - clearCache(); - }; - - // 计算属性:获取菜单列表 - const menuList = computed(() => menus.value); - - // 计算属性:菜单是否已加载 - const isLoaded = computed(() => menus.value.length > 0); - - return { - // 状态 - menus: menuList, - loading, - error, - isLoaded, - - // 方法 - fetchMenus, - refreshMenus, - resetMenus, - clearCache, - loadFromCache, - }; -}); - +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +// import { getUserInfo } from '@/utils/auth' +import { getMenus } from '@/api/menu'; + +export const useMenuStore = defineStore('menu', () => { + // 菜单数据 + const menus = ref([]); + + // 加载状态 + const loading = ref(false); + + // 加载错误 + const error = ref(null); + + // 正在加载的 Promise(用于避免重复请求) + let loadingPromise = null; + + // 菜单缓存 key(基于用户类型和角色ID) + const getCacheKey = () => { + try { + const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); + const loginType = userInfo.type || 'user'; + const roleId = userInfo.group_id || 0; + return `menu_cache_${loginType}_${roleId}`; + } catch (e) { + return 'menu_cache_default'; + } + }; + + // 从缓存加载菜单 + const loadFromCache = () => { + try { + const cacheKey = getCacheKey(); + const cached = localStorage.getItem(cacheKey); + if (cached) { + const menuData = JSON.parse(cached); + // 检查缓存是否过期(5分钟过期) + if (menuData.timestamp && Date.now() - menuData.timestamp < 5 * 60 * 1000) { + return menuData.menus; + } + } + } catch (e) { + console.warn('加载菜单缓存失败:', e); + } + return null; + }; + + // 保存菜单到缓存 + const saveToCache = (menuData) => { + try { + const cacheKey = getCacheKey(); + localStorage.setItem(cacheKey, JSON.stringify({ + menus: menuData, + timestamp: Date.now() + })); + } catch (e) { + console.warn('保存菜单缓存失败:', e); + } + }; + + // 清除菜单缓存 + const clearCache = () => { + try { + const cacheKey = getCacheKey(); + localStorage.removeItem(cacheKey); + // 也清除其他可能的缓存key(兼容旧代码) + localStorage.removeItem('menu_cache'); + } catch (e) { + console.warn('清除菜单缓存失败:', e); + } + }; + + // 获取用户信息 + const getUserInfo = () => { + try { + return JSON.parse(localStorage.getItem('userInfo') || '{}'); + } catch (e) { + return {}; + } + }; + + // 从 API 加载菜单(核心方法,确保只请求一次) + const fetchMenus = async (forceRefresh = false) => { + // 如果已经有正在加载的请求,直接返回该 Promise + if (loadingPromise && !forceRefresh) { + return loadingPromise; + } + + // 如果不强制刷新,先尝试从缓存加载 + if (!forceRefresh) { + const cachedMenus = loadFromCache(); + if (cachedMenus && cachedMenus.length > 0) { + menus.value = cachedMenus; + return Promise.resolve(cachedMenus); + } + } + + // 如果正在加载且不是强制刷新,返回现有的 Promise + if (loading.value && !forceRefresh) { + return loadingPromise; + } + + // 创建新的加载 Promise + loadingPromise = (async () => { + loading.value = true; + error.value = null; + + try { + const userInfo = getUserInfo(); + const loginType = userInfo.type || 'user'; + const roleId = userInfo.group_id || 0; + + let res; + + // 检查用户ID是否存在 + if (!userInfo.id) { + throw new Error('用户ID不存在,请重新登录'); + } + + // 用户登录,使用 getMenus 接口 + res = await getMenus(userInfo.id); + + // 检查响应格式 + if (!res) { + throw new Error('获取菜单失败:服务器无响应'); + } + + // 检查后端返回的 code 字段 + if (res.code !== 200) { + throw new Error(res.msg || '获取菜单失败'); + } + + // 如果 code 为 200,检查 data + if (res.code === 200) { + // data 可能是空数组,这也是有效的 + if (res.data !== undefined && res.data !== null) { + // 确保 data 是数组 + const menuData = Array.isArray(res.data) ? res.data : []; + // 直接使用后端返回的树形结构数据,不需要额外过滤 + menus.value = menuData; + // 保存到缓存 + saveToCache(menuData); + return menuData; + } else { + // data 为 null 或 undefined,使用空数组 + console.warn('菜单数据为空,使用空数组'); + menus.value = []; + saveToCache([]); + return []; + } + } + + // 如果响应格式不符合预期,尝试直接使用 res.data + if (res.data !== undefined) { + const menuData = Array.isArray(res.data) ? res.data : []; + const filtered = menuData.filter(m => (m.isShow ?? 1) !== 0); + menus.value = filtered; + saveToCache(filtered); + return filtered; + } + + // 如果都不符合,抛出错误 + throw new Error(res.message || '获取菜单失败:响应格式错误'); + } catch (err) { + error.value = err.message || '获取菜单失败'; + console.error('获取菜单失败:', err); + console.error('错误详情:', { + message: err.message, + response: err.response, + stack: err.stack + }); + + // 如果是 token 无效错误,不使用缓存,直接抛出 + if (err.message === 'token无效' || err.response?.status === 401) { + clearCache(); + menus.value = []; + throw err; + } + + // 如果出错,尝试使用缓存数据 + const cachedMenus = loadFromCache(); + if (cachedMenus && cachedMenus.length > 0) { + console.warn('使用缓存的菜单数据'); + menus.value = cachedMenus; + return cachedMenus; + } + + // 如果连缓存都没有,设置空数组,避免页面崩溃 + menus.value = []; + throw err; + } finally { + loading.value = false; + loadingPromise = null; + } + })(); + + return loadingPromise; + }; + + // 刷新菜单(强制从 API 获取) + const refreshMenus = async () => { + clearCache(); + return await fetchMenus(true); + }; + + // 重置菜单 store(登出时使用) + const resetMenus = () => { + menus.value = []; + loading.value = false; + error.value = null; + loadingPromise = null; + clearCache(); + }; + + // 计算属性:获取菜单列表 + const menuList = computed(() => menus.value); + + // 计算属性:菜单是否已加载 + const isLoaded = computed(() => menus.value.length > 0); + + return { + // 状态 + menus: menuList, + loading, + error, + isLoaded, + + // 方法 + fetchMenus, + refreshMenus, + resetMenus, + clearCache, + loadFromCache, + }; +}); + diff --git a/backend/src/types/vue-cropper.d.ts b/backend/src/types/vue-cropper.d.ts index 8877070..e13cf12 100644 --- a/backend/src/types/vue-cropper.d.ts +++ b/backend/src/types/vue-cropper.d.ts @@ -1,4 +1,4 @@ -declare module 'vue-cropper' { - import { Component } from 'vue' - export const VueCropper: Component -} +declare module 'vue-cropper' { + import { Component } from 'vue' + export const VueCropper: Component +} diff --git a/backend/src/utils/pathResolver.js b/backend/src/utils/pathResolver.js index e98ae83..0806aae 100644 --- a/backend/src/utils/pathResolver.js +++ b/backend/src/utils/pathResolver.js @@ -1,94 +1,94 @@ -/** - * 通用的别名路径解析工具 - * 用于在动态导入时解析 @ 别名路径 - */ - -// 使用 import.meta.glob 预加载所有组件 -const viewsModules = import.meta.glob('../views/**/*.vue'); - -// 创建路径映射表 -const pathMap = new Map(); - -// 初始化路径映射 -Object.keys(viewsModules).forEach(relativePath => { - // relativePath 示例: ../views/system/users.vue - - // 统一去掉扩展名进行存储,方便各种格式匹配 - const baseNoExt = relativePath.replace('../views/', '').replace('.vue', ''); - const baseWithExt = relativePath.replace('../views/', ''); - - // 1. 存储标准路径 - pathMap.set(relativePath, viewsModules[relativePath]); - // 2. 存储 @/views 路径 - pathMap.set(relativePath.replace('../views', '@/views'), viewsModules[relativePath]); - // 3. 存储 /system/users 格式(不带扩展名) - pathMap.set(`/${baseNoExt}`, viewsModules[relativePath]); - // 4. 存储 system/users 格式(不带扩展名) - pathMap.set(baseNoExt, viewsModules[relativePath]); - // 5. 存储 /system/users.vue 格式(带扩展名) - pathMap.set(`/${baseWithExt}`, viewsModules[relativePath]); - // 6. 存储 system/users.vue 格式(带扩展名) - pathMap.set(baseWithExt, viewsModules[relativePath]); -}); - -/** - * 解析别名路径为实际模块加载器 - * @param {string} path - 支持的路径格式: - * - @/views/dashboard/index.vue (别名格式) - * - /dashboard/index.vue (数据库格式,带前导斜杠) - * - dashboard/index.vue (相对格式) - * @returns {Function|null} 返回模块加载器函数,找不到时返回 null - */ -export function resolveComponent(path) { - if (!path) return null; - - // 预处理 path:去掉可能的 .vue 后缀统一查找 - const cleanPath = path.replace('.vue', ''); - - // 尝试直接匹配 - const loader = pathMap.get(path) || pathMap.get(cleanPath); - if (loader) return loader; - - // 数据库格式补全匹配 (针对 /system/users) - const dbFormat = cleanPath.startsWith('/') ? cleanPath : `/${cleanPath}`; - if (pathMap.get(dbFormat)) return pathMap.get(dbFormat); - - // 模糊匹配:文件名匹配 - const fileName = cleanPath.split('/').pop(); - for (const [mappedPath, loader] of pathMap.entries()) { - if (mappedPath.endsWith(`${fileName}.vue`) || mappedPath.endsWith(fileName)) { - return loader; - } - } - return null; -} - -/** - * 创建组件加载器 - * @param {string} componentPath - 组件路径 - * @returns {Function} Vue 路由组件加载函数 - */ -export function createComponentLoader(componentPath) { - const loader = resolveComponent(componentPath); - if (loader) return loader; - - console.error(`❌ [路由错误] 未找到组件: ${componentPath}`); - - // 返回一个标准的 Vue 组件对象,确保 Router 不报错 - return () => Promise.resolve({ - name: 'ComponentNotFound', - render: () => { - import('element-plus').then(El => El.ElMessage.error(`路径错误: ${componentPath}`)); - return h('div', { style: 'padding:20px; color:red;' }, `组件路径不存在: ${componentPath}`); - } - }); -} - -/** - * 获取所有已加载的模块路径(用于调试) - * @returns {Array} 所有可用的路径列表 - */ -export function getAllModulePaths() { - return Array.from(pathMap.keys()); -} - +/** + * 通用的别名路径解析工具 + * 用于在动态导入时解析 @ 别名路径 + */ + +// 使用 import.meta.glob 预加载所有组件 +const viewsModules = import.meta.glob('../views/**/*.vue'); + +// 创建路径映射表 +const pathMap = new Map(); + +// 初始化路径映射 +Object.keys(viewsModules).forEach(relativePath => { + // relativePath 示例: ../views/system/users.vue + + // 统一去掉扩展名进行存储,方便各种格式匹配 + const baseNoExt = relativePath.replace('../views/', '').replace('.vue', ''); + const baseWithExt = relativePath.replace('../views/', ''); + + // 1. 存储标准路径 + pathMap.set(relativePath, viewsModules[relativePath]); + // 2. 存储 @/views 路径 + pathMap.set(relativePath.replace('../views', '@/views'), viewsModules[relativePath]); + // 3. 存储 /system/users 格式(不带扩展名) + pathMap.set(`/${baseNoExt}`, viewsModules[relativePath]); + // 4. 存储 system/users 格式(不带扩展名) + pathMap.set(baseNoExt, viewsModules[relativePath]); + // 5. 存储 /system/users.vue 格式(带扩展名) + pathMap.set(`/${baseWithExt}`, viewsModules[relativePath]); + // 6. 存储 system/users.vue 格式(带扩展名) + pathMap.set(baseWithExt, viewsModules[relativePath]); +}); + +/** + * 解析别名路径为实际模块加载器 + * @param {string} path - 支持的路径格式: + * - @/views/dashboard/index.vue (别名格式) + * - /dashboard/index.vue (数据库格式,带前导斜杠) + * - dashboard/index.vue (相对格式) + * @returns {Function|null} 返回模块加载器函数,找不到时返回 null + */ +export function resolveComponent(path) { + if (!path) return null; + + // 预处理 path:去掉可能的 .vue 后缀统一查找 + const cleanPath = path.replace('.vue', ''); + + // 尝试直接匹配 + const loader = pathMap.get(path) || pathMap.get(cleanPath); + if (loader) return loader; + + // 数据库格式补全匹配 (针对 /system/users) + const dbFormat = cleanPath.startsWith('/') ? cleanPath : `/${cleanPath}`; + if (pathMap.get(dbFormat)) return pathMap.get(dbFormat); + + // 模糊匹配:文件名匹配 + const fileName = cleanPath.split('/').pop(); + for (const [mappedPath, loader] of pathMap.entries()) { + if (mappedPath.endsWith(`${fileName}.vue`) || mappedPath.endsWith(fileName)) { + return loader; + } + } + return null; +} + +/** + * 创建组件加载器 + * @param {string} componentPath - 组件路径 + * @returns {Function} Vue 路由组件加载函数 + */ +export function createComponentLoader(componentPath) { + const loader = resolveComponent(componentPath); + if (loader) return loader; + + console.error(`❌ [路由错误] 未找到组件: ${componentPath}`); + + // 返回一个标准的 Vue 组件对象,确保 Router 不报错 + return () => Promise.resolve({ + name: 'ComponentNotFound', + render: () => { + import('element-plus').then(El => El.ElMessage.error(`路径错误: ${componentPath}`)); + return h('div', { style: 'padding:20px; color:red;' }, `组件路径不存在: ${componentPath}`); + } + }); +} + +/** + * 获取所有已加载的模块路径(用于调试) + * @returns {Array} 所有可用的路径列表 + */ +export function getAllModulePaths() { + return Array.from(pathMap.keys()); +} + diff --git a/backend/src/utils/request.js b/backend/src/utils/request.js index 7e4571d..805e569 100644 --- a/backend/src/utils/request.js +++ b/backend/src/utils/request.js @@ -1,65 +1,65 @@ -import axios from 'axios'; - -// 获取API基础URL,添加调试信息 -const apiBaseURL = import.meta.env.VITE_API_BASE_URL; - -// 创建axios实例 -const service = axios.create({ - baseURL: apiBaseURL, - timeout: 10000, - withCredentials: false // JWT 不需要 Cookie -}); - -// 请求拦截器 -service.interceptors.request.use( - config => { - const token = localStorage.getItem('token'); - if (token) { - config.headers['Authorization'] = `Bearer ${token}`; - } - - // 对于有 body 的请求(POST、PUT、PATCH),确保设置 Content-Type - if (config.data && ['post', 'put', 'patch'].includes(config.method?.toLowerCase())) { - if (!config.headers['Content-Type'] && !config.headers['content-type']) { - config.headers['Content-Type'] = 'application/json'; - } - } - return config; - }, - error => { - return Promise.reject(error); - } -); - -// 响应拦截器 -service.interceptors.response.use( - response => { - return response.data; - }, - error => { - if (error.response) { - switch (error.response.status) { - case 401: - console.error('未授权,请重新登录'); - localStorage.removeItem('token'); - localStorage.removeItem('userInfo'); - if (window.location.hash !== '#/login') { - window.location.href = '#/login'; - } - return Promise.reject(new Error('token无效')); - case 404: - console.error('请求的资源不存在'); - break; - default: - console.error('请求失败,请稍后再试'); - } - } else if (error.request) { - console.error('请求失败,请检查网络连接'); - } else { - console.error('请求配置错误'); - } - return Promise.reject(error); - } -); - +import axios from 'axios'; + +// 获取API基础URL,添加调试信息 +const apiBaseURL = import.meta.env.VITE_API_BASE_URL; + +// 创建axios实例 +const service = axios.create({ + baseURL: apiBaseURL, + timeout: 10000, + withCredentials: false // JWT 不需要 Cookie +}); + +// 请求拦截器 +service.interceptors.request.use( + config => { + const token = localStorage.getItem('token'); + if (token) { + config.headers['Authorization'] = `Bearer ${token}`; + } + + // 对于有 body 的请求(POST、PUT、PATCH),确保设置 Content-Type + if (config.data && ['post', 'put', 'patch'].includes(config.method?.toLowerCase())) { + if (!config.headers['Content-Type'] && !config.headers['content-type']) { + config.headers['Content-Type'] = 'application/json'; + } + } + return config; + }, + error => { + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + response => { + return response.data; + }, + error => { + if (error.response) { + switch (error.response.status) { + case 401: + console.error('未授权,请重新登录'); + localStorage.removeItem('token'); + localStorage.removeItem('userInfo'); + if (window.location.hash !== '#/login') { + window.location.href = '#/login'; + } + return Promise.reject(new Error('token无效')); + case 404: + console.error('请求的资源不存在'); + break; + default: + console.error('请求失败,请稍后再试'); + } + } else if (error.request) { + console.error('请求失败,请检查网络连接'); + } else { + console.error('请求配置错误'); + } + return Promise.reject(error); + } +); + export default service; \ No newline at end of file diff --git a/backend/src/views/404/404.vue b/backend/src/views/404/404.vue index c050a6d..bbab10e 100644 --- a/backend/src/views/404/404.vue +++ b/backend/src/views/404/404.vue @@ -1,93 +1,93 @@ - - - - - + + + + + diff --git a/backend/src/views/Main.vue b/backend/src/views/Main.vue index 4598f76..977f7a7 100644 --- a/backend/src/views/Main.vue +++ b/backend/src/views/Main.vue @@ -1,745 +1,745 @@ - - - - - - - + + + + + + + diff --git a/backend/src/views/analytics/users/index.vue b/backend/src/views/analytics/users/index.vue index 8fd2610..93397c5 100644 --- a/backend/src/views/analytics/users/index.vue +++ b/backend/src/views/analytics/users/index.vue @@ -1,196 +1,196 @@ - - - - - \ No newline at end of file diff --git a/backend/src/views/apps/cms/analytics/content/index.vue b/backend/src/views/apps/cms/analytics/content/index.vue index 285d84e..179460b 100644 --- a/backend/src/views/apps/cms/analytics/content/index.vue +++ b/backend/src/views/apps/cms/analytics/content/index.vue @@ -1,241 +1,241 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/articles/category.vue b/backend/src/views/apps/cms/articles/category.vue index ea496cd..b654ab0 100644 --- a/backend/src/views/apps/cms/articles/category.vue +++ b/backend/src/views/apps/cms/articles/category.vue @@ -1,493 +1,493 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/articles/components/CategoryNode.vue b/backend/src/views/apps/cms/articles/components/CategoryNode.vue index 587c276..b08db73 100644 --- a/backend/src/views/apps/cms/articles/components/CategoryNode.vue +++ b/backend/src/views/apps/cms/articles/components/CategoryNode.vue @@ -1,269 +1,269 @@ - - - - - \ No newline at end of file diff --git a/backend/src/views/apps/cms/articles/components/edit-cate.vue b/backend/src/views/apps/cms/articles/components/edit-cate.vue index 21aae19..c6d14e6 100644 --- a/backend/src/views/apps/cms/articles/components/edit-cate.vue +++ b/backend/src/views/apps/cms/articles/components/edit-cate.vue @@ -1,324 +1,324 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/articles/components/edit.vue b/backend/src/views/apps/cms/articles/components/edit.vue index 94d16d5..12bee9a 100644 --- a/backend/src/views/apps/cms/articles/components/edit.vue +++ b/backend/src/views/apps/cms/articles/components/edit.vue @@ -1,686 +1,686 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/articles/components/preview.vue b/backend/src/views/apps/cms/articles/components/preview.vue index a1f3093..baa5c6e 100644 --- a/backend/src/views/apps/cms/articles/components/preview.vue +++ b/backend/src/views/apps/cms/articles/components/preview.vue @@ -1,347 +1,347 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/articles/index.vue b/backend/src/views/apps/cms/articles/index.vue index 68defeb..29b6d36 100644 --- a/backend/src/views/apps/cms/articles/index.vue +++ b/backend/src/views/apps/cms/articles/index.vue @@ -1,670 +1,670 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/banner/components/edit.vue b/backend/src/views/apps/cms/banner/components/edit.vue index 12a5479..e41bbdd 100644 --- a/backend/src/views/apps/cms/banner/components/edit.vue +++ b/backend/src/views/apps/cms/banner/components/edit.vue @@ -1,332 +1,332 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/banner/index.vue b/backend/src/views/apps/cms/banner/index.vue index 7f6b53f..f55103c 100644 --- a/backend/src/views/apps/cms/banner/index.vue +++ b/backend/src/views/apps/cms/banner/index.vue @@ -1,301 +1,301 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/demand/components/edit.vue b/backend/src/views/apps/cms/demand/components/edit.vue index 962276f..bdfb493 100644 --- a/backend/src/views/apps/cms/demand/components/edit.vue +++ b/backend/src/views/apps/cms/demand/components/edit.vue @@ -1,150 +1,150 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/demand/index.vue b/backend/src/views/apps/cms/demand/index.vue index fb51f27..82b09d7 100644 --- a/backend/src/views/apps/cms/demand/index.vue +++ b/backend/src/views/apps/cms/demand/index.vue @@ -1,333 +1,333 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/domain/audit.vue b/backend/src/views/apps/cms/domain/audit.vue index 918193f..b43b1a9 100644 --- a/backend/src/views/apps/cms/domain/audit.vue +++ b/backend/src/views/apps/cms/domain/audit.vue @@ -1,185 +1,185 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/domain/index.vue b/backend/src/views/apps/cms/domain/index.vue index 6c5b98b..8054253 100644 --- a/backend/src/views/apps/cms/domain/index.vue +++ b/backend/src/views/apps/cms/domain/index.vue @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/domain/pool.vue b/backend/src/views/apps/cms/domain/pool.vue index 81cff16..4ebab5e 100644 --- a/backend/src/views/apps/cms/domain/pool.vue +++ b/backend/src/views/apps/cms/domain/pool.vue @@ -1,287 +1,287 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/friendlink/components/edit.vue b/backend/src/views/apps/cms/friendlink/components/edit.vue index 2f3c830..8631ce8 100644 --- a/backend/src/views/apps/cms/friendlink/components/edit.vue +++ b/backend/src/views/apps/cms/friendlink/components/edit.vue @@ -1,302 +1,302 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/friendlink/index.vue b/backend/src/views/apps/cms/friendlink/index.vue index f9ca38f..3ddd5e9 100644 --- a/backend/src/views/apps/cms/friendlink/index.vue +++ b/backend/src/views/apps/cms/friendlink/index.vue @@ -1,324 +1,324 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/frontMenu/components/edit.vue b/backend/src/views/apps/cms/frontMenu/components/edit.vue index 7f1ef80..20b5211 100644 --- a/backend/src/views/apps/cms/frontMenu/components/edit.vue +++ b/backend/src/views/apps/cms/frontMenu/components/edit.vue @@ -1,514 +1,514 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/frontMenu/index.vue b/backend/src/views/apps/cms/frontMenu/index.vue index 16489d6..94c39db 100644 --- a/backend/src/views/apps/cms/frontMenu/index.vue +++ b/backend/src/views/apps/cms/frontMenu/index.vue @@ -1,563 +1,563 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/index.vue b/backend/src/views/apps/cms/index.vue index 3fa5df9..8217bb8 100644 --- a/backend/src/views/apps/cms/index.vue +++ b/backend/src/views/apps/cms/index.vue @@ -1,7 +1,7 @@ - - - - + + + + \ No newline at end of file diff --git a/backend/src/views/apps/cms/onepage/components/edit.vue b/backend/src/views/apps/cms/onepage/components/edit.vue index b820ee7..fcc7ee3 100644 --- a/backend/src/views/apps/cms/onepage/components/edit.vue +++ b/backend/src/views/apps/cms/onepage/components/edit.vue @@ -1,245 +1,245 @@ - - - - - - + + + + + + diff --git a/backend/src/views/apps/cms/onepage/index.vue b/backend/src/views/apps/cms/onepage/index.vue index 1e53122..537b2b8 100644 --- a/backend/src/views/apps/cms/onepage/index.vue +++ b/backend/src/views/apps/cms/onepage/index.vue @@ -1,298 +1,298 @@ - - - - - - + + + + + + diff --git a/backend/src/views/apps/cms/products/components/edit.vue b/backend/src/views/apps/cms/products/components/edit.vue index 56f70cb..b63c183 100644 --- a/backend/src/views/apps/cms/products/components/edit.vue +++ b/backend/src/views/apps/cms/products/components/edit.vue @@ -1,321 +1,321 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/products/index.vue b/backend/src/views/apps/cms/products/index.vue index 215cbff..0d11ae8 100644 --- a/backend/src/views/apps/cms/products/index.vue +++ b/backend/src/views/apps/cms/products/index.vue @@ -1,295 +1,295 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/products/types/components/edit.vue b/backend/src/views/apps/cms/products/types/components/edit.vue index bc50820..ff330ca 100644 --- a/backend/src/views/apps/cms/products/types/components/edit.vue +++ b/backend/src/views/apps/cms/products/types/components/edit.vue @@ -1,211 +1,211 @@ - - - - - - + + + + + + diff --git a/backend/src/views/apps/cms/products/types/index.vue b/backend/src/views/apps/cms/products/types/index.vue index e3bc056..8a507d7 100644 --- a/backend/src/views/apps/cms/products/types/index.vue +++ b/backend/src/views/apps/cms/products/types/index.vue @@ -1,240 +1,240 @@ - - - - - + + + + + \ No newline at end of file diff --git a/backend/src/views/apps/cms/resoucres/category/index.vue b/backend/src/views/apps/cms/resoucres/category/index.vue index b4b3e83..b898c57 100644 --- a/backend/src/views/apps/cms/resoucres/category/index.vue +++ b/backend/src/views/apps/cms/resoucres/category/index.vue @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/backend/src/views/apps/cms/resoucres/list/index.vue b/backend/src/views/apps/cms/resoucres/list/index.vue index 2ea1635..51f36ed 100644 --- a/backend/src/views/apps/cms/resoucres/list/index.vue +++ b/backend/src/views/apps/cms/resoucres/list/index.vue @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/backend/src/views/apps/cms/services/components/edit.vue b/backend/src/views/apps/cms/services/components/edit.vue index 9921169..4d2a6b9 100644 --- a/backend/src/views/apps/cms/services/components/edit.vue +++ b/backend/src/views/apps/cms/services/components/edit.vue @@ -1,315 +1,315 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/services/index.vue b/backend/src/views/apps/cms/services/index.vue index ba82f59..b446af3 100644 --- a/backend/src/views/apps/cms/services/index.vue +++ b/backend/src/views/apps/cms/services/index.vue @@ -1,281 +1,281 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/templates/index.vue b/backend/src/views/apps/cms/templates/index.vue index aef49a1..e834ea6 100644 --- a/backend/src/views/apps/cms/templates/index.vue +++ b/backend/src/views/apps/cms/templates/index.vue @@ -1,281 +1,281 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/cms/workbench/index.vue b/backend/src/views/apps/cms/workbench/index.vue index 39ed412..b1273e6 100644 --- a/backend/src/views/apps/cms/workbench/index.vue +++ b/backend/src/views/apps/cms/workbench/index.vue @@ -1,278 +1,278 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/erp/dashboard/index.vue b/backend/src/views/apps/erp/dashboard/index.vue index b29aa5c..67be465 100644 --- a/backend/src/views/apps/erp/dashboard/index.vue +++ b/backend/src/views/apps/erp/dashboard/index.vue @@ -1,329 +1,329 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/erp/employee/components/changepass.vue b/backend/src/views/apps/erp/employee/components/changepass.vue index e1bbe46..5baba7a 100644 --- a/backend/src/views/apps/erp/employee/components/changepass.vue +++ b/backend/src/views/apps/erp/employee/components/changepass.vue @@ -1,122 +1,122 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/erp/employee/components/edit.vue b/backend/src/views/apps/erp/employee/components/edit.vue index d5131cc..f7f8452 100644 --- a/backend/src/views/apps/erp/employee/components/edit.vue +++ b/backend/src/views/apps/erp/employee/components/edit.vue @@ -1,583 +1,583 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/erp/employee/components/view.vue b/backend/src/views/apps/erp/employee/components/view.vue index c3dda23..858f3bb 100644 --- a/backend/src/views/apps/erp/employee/components/view.vue +++ b/backend/src/views/apps/erp/employee/components/view.vue @@ -1,97 +1,97 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/erp/employee/index.vue b/backend/src/views/apps/erp/employee/index.vue index 45e21b7..6329e21 100644 --- a/backend/src/views/apps/erp/employee/index.vue +++ b/backend/src/views/apps/erp/employee/index.vue @@ -1,201 +1,201 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/erp/index.vue b/backend/src/views/apps/erp/index.vue index 3ea5af6..1644a6c 100644 --- a/backend/src/views/apps/erp/index.vue +++ b/backend/src/views/apps/erp/index.vue @@ -1,8 +1,8 @@ - - - - - + + + + + \ No newline at end of file diff --git a/backend/src/views/apps/erp/organization/components/edit.vue b/backend/src/views/apps/erp/organization/components/edit.vue index 7986536..69dc988 100644 --- a/backend/src/views/apps/erp/organization/components/edit.vue +++ b/backend/src/views/apps/erp/organization/components/edit.vue @@ -1,252 +1,252 @@ - - + + diff --git a/backend/src/views/apps/erp/organization/index.vue b/backend/src/views/apps/erp/organization/index.vue index d64fe3d..0a9ecd6 100644 --- a/backend/src/views/apps/erp/organization/index.vue +++ b/backend/src/views/apps/erp/organization/index.vue @@ -1,358 +1,358 @@ - - - - - + + + + + diff --git a/backend/src/views/apps/index.vue b/backend/src/views/apps/index.vue index 2ce359b..c3e1b98 100644 --- a/backend/src/views/apps/index.vue +++ b/backend/src/views/apps/index.vue @@ -1,11 +1,11 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/index.vue b/backend/src/views/basicSettings/index.vue index 2fa6465..2d8f283 100644 --- a/backend/src/views/basicSettings/index.vue +++ b/backend/src/views/basicSettings/index.vue @@ -1,11 +1,11 @@ - - - - - \ No newline at end of file diff --git a/backend/src/views/basicSettings/roles/components/detail.vue b/backend/src/views/basicSettings/roles/components/detail.vue index c3651db..d7d1bed 100644 --- a/backend/src/views/basicSettings/roles/components/detail.vue +++ b/backend/src/views/basicSettings/roles/components/detail.vue @@ -1,167 +1,167 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/roles/components/edit.vue b/backend/src/views/basicSettings/roles/components/edit.vue index 9a77993..116db43 100644 --- a/backend/src/views/basicSettings/roles/components/edit.vue +++ b/backend/src/views/basicSettings/roles/components/edit.vue @@ -1,274 +1,274 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/roles/index.vue b/backend/src/views/basicSettings/roles/index.vue index 507d78b..d62422b 100644 --- a/backend/src/views/basicSettings/roles/index.vue +++ b/backend/src/views/basicSettings/roles/index.vue @@ -1,226 +1,226 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/siteSettings/components/contactSettings.vue b/backend/src/views/basicSettings/siteSettings/components/contactSettings.vue index 209d541..c58cf3c 100644 --- a/backend/src/views/basicSettings/siteSettings/components/contactSettings.vue +++ b/backend/src/views/basicSettings/siteSettings/components/contactSettings.vue @@ -1,92 +1,92 @@ - - - + + + diff --git a/backend/src/views/basicSettings/siteSettings/components/legalNotice.vue b/backend/src/views/basicSettings/siteSettings/components/legalNotice.vue index 320965c..950e1e1 100644 --- a/backend/src/views/basicSettings/siteSettings/components/legalNotice.vue +++ b/backend/src/views/basicSettings/siteSettings/components/legalNotice.vue @@ -1,102 +1,102 @@ - - - + + + diff --git a/backend/src/views/basicSettings/siteSettings/components/loginVerification.vue b/backend/src/views/basicSettings/siteSettings/components/loginVerification.vue index 3698994..30be346 100644 --- a/backend/src/views/basicSettings/siteSettings/components/loginVerification.vue +++ b/backend/src/views/basicSettings/siteSettings/components/loginVerification.vue @@ -1,81 +1,81 @@ - - - \ No newline at end of file diff --git a/backend/src/views/basicSettings/siteSettings/components/normalSettings.vue b/backend/src/views/basicSettings/siteSettings/components/normalSettings.vue index c7a9a37..4eabd5c 100644 --- a/backend/src/views/basicSettings/siteSettings/components/normalSettings.vue +++ b/backend/src/views/basicSettings/siteSettings/components/normalSettings.vue @@ -1,301 +1,301 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/siteSettings/components/otherSettings.vue b/backend/src/views/basicSettings/siteSettings/components/otherSettings.vue index c37f7b4..08c6a41 100644 --- a/backend/src/views/basicSettings/siteSettings/components/otherSettings.vue +++ b/backend/src/views/basicSettings/siteSettings/components/otherSettings.vue @@ -1,62 +1,62 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/siteSettings/components/seoSettings.vue b/backend/src/views/basicSettings/siteSettings/components/seoSettings.vue index d2388b6..f2aaa99 100644 --- a/backend/src/views/basicSettings/siteSettings/components/seoSettings.vue +++ b/backend/src/views/basicSettings/siteSettings/components/seoSettings.vue @@ -1,101 +1,101 @@ - - - + + + diff --git a/backend/src/views/basicSettings/siteSettings/index.vue b/backend/src/views/basicSettings/siteSettings/index.vue index e494be8..b18db65 100644 --- a/backend/src/views/basicSettings/siteSettings/index.vue +++ b/backend/src/views/basicSettings/siteSettings/index.vue @@ -1,98 +1,98 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/tenants/components/TenantUsersTab.vue b/backend/src/views/basicSettings/tenants/components/TenantUsersTab.vue index b5ea5ee..cdc9f07 100644 --- a/backend/src/views/basicSettings/tenants/components/TenantUsersTab.vue +++ b/backend/src/views/basicSettings/tenants/components/TenantUsersTab.vue @@ -1,244 +1,244 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/tenants/components/adduser.vue b/backend/src/views/basicSettings/tenants/components/adduser.vue index ed2cdb1..fe9b736 100644 --- a/backend/src/views/basicSettings/tenants/components/adduser.vue +++ b/backend/src/views/basicSettings/tenants/components/adduser.vue @@ -1,144 +1,144 @@ - - - + + + diff --git a/backend/src/views/basicSettings/tenants/components/detail.vue b/backend/src/views/basicSettings/tenants/components/detail.vue index e29ff3b..3fc0336 100644 --- a/backend/src/views/basicSettings/tenants/components/detail.vue +++ b/backend/src/views/basicSettings/tenants/components/detail.vue @@ -1,160 +1,160 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/tenants/components/edit.vue b/backend/src/views/basicSettings/tenants/components/edit.vue index 9f558b8..fd8c66c 100644 --- a/backend/src/views/basicSettings/tenants/components/edit.vue +++ b/backend/src/views/basicSettings/tenants/components/edit.vue @@ -1,194 +1,194 @@ - - - \ No newline at end of file diff --git a/backend/src/views/basicSettings/tenants/components/qualification.vue b/backend/src/views/basicSettings/tenants/components/qualification.vue index 96cc3e0..65dc94d 100644 --- a/backend/src/views/basicSettings/tenants/components/qualification.vue +++ b/backend/src/views/basicSettings/tenants/components/qualification.vue @@ -1,166 +1,166 @@ - - - - - \ No newline at end of file diff --git a/backend/src/views/basicSettings/tenants/domain.vue b/backend/src/views/basicSettings/tenants/domain.vue index 510478c..43d259a 100644 --- a/backend/src/views/basicSettings/tenants/domain.vue +++ b/backend/src/views/basicSettings/tenants/domain.vue @@ -1,258 +1,258 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/tenants/index.vue b/backend/src/views/basicSettings/tenants/index.vue index 7b02ac8..616cdd0 100644 --- a/backend/src/views/basicSettings/tenants/index.vue +++ b/backend/src/views/basicSettings/tenants/index.vue @@ -1,299 +1,299 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/users/components/changePassword.vue b/backend/src/views/basicSettings/users/components/changePassword.vue index 2577cac..8b64697 100644 --- a/backend/src/views/basicSettings/users/components/changePassword.vue +++ b/backend/src/views/basicSettings/users/components/changePassword.vue @@ -1,198 +1,198 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/users/components/preview.vue b/backend/src/views/basicSettings/users/components/preview.vue index 4b35c37..6bc25a3 100644 --- a/backend/src/views/basicSettings/users/components/preview.vue +++ b/backend/src/views/basicSettings/users/components/preview.vue @@ -1,190 +1,190 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/users/components/userEdit.vue b/backend/src/views/basicSettings/users/components/userEdit.vue index efc32e4..594efc5 100644 --- a/backend/src/views/basicSettings/users/components/userEdit.vue +++ b/backend/src/views/basicSettings/users/components/userEdit.vue @@ -1,408 +1,408 @@ - - - - - + + + + + diff --git a/backend/src/views/basicSettings/users/index.vue b/backend/src/views/basicSettings/users/index.vue index 0ef1af5..701f757 100644 --- a/backend/src/views/basicSettings/users/index.vue +++ b/backend/src/views/basicSettings/users/index.vue @@ -1,315 +1,315 @@ - - - - - + + + + + diff --git a/backend/src/views/components/WangEditor.vue b/backend/src/views/components/WangEditor.vue index df322df..e2d9ec1 100644 --- a/backend/src/views/components/WangEditor.vue +++ b/backend/src/views/components/WangEditor.vue @@ -1,582 +1,582 @@ - - - - - + + + + + diff --git a/backend/src/views/dashboard/index.vue b/backend/src/views/dashboard/index.vue index 5097a89..9e83654 100644 --- a/backend/src/views/dashboard/index.vue +++ b/backend/src/views/dashboard/index.vue @@ -1,790 +1,790 @@ - - - - - + + + + + diff --git a/backend/src/views/home/index.vue b/backend/src/views/home/index.vue index 618f982..008f994 100644 --- a/backend/src/views/home/index.vue +++ b/backend/src/views/home/index.vue @@ -1,680 +1,680 @@ - - - - - + + + + + diff --git a/backend/src/views/layouts/EmptyLayout.vue b/backend/src/views/layouts/EmptyLayout.vue index 953755f..5ec7b8e 100644 --- a/backend/src/views/layouts/EmptyLayout.vue +++ b/backend/src/views/layouts/EmptyLayout.vue @@ -1,7 +1,7 @@ - - - \ No newline at end of file diff --git a/backend/src/views/login/forget.vue b/backend/src/views/login/forget.vue index 6332023..bdbdbef 100644 --- a/backend/src/views/login/forget.vue +++ b/backend/src/views/login/forget.vue @@ -1,127 +1,127 @@ - - - - - - + + + + + + diff --git a/backend/src/views/login/index.vue b/backend/src/views/login/index.vue index d4fee27..770f8a1 100644 --- a/backend/src/views/login/index.vue +++ b/backend/src/views/login/index.vue @@ -1,905 +1,905 @@ - - - - - \ No newline at end of file diff --git a/backend/src/views/login/register.vue b/backend/src/views/login/register.vue index 59a887c..d2c4ce8 100644 --- a/backend/src/views/login/register.vue +++ b/backend/src/views/login/register.vue @@ -1,131 +1,131 @@ - - - - - - + + + + + + diff --git a/backend/src/views/moduleshop/category/index.vue b/backend/src/views/moduleshop/category/index.vue index d655bf5..71beb53 100644 --- a/backend/src/views/moduleshop/category/index.vue +++ b/backend/src/views/moduleshop/category/index.vue @@ -1,313 +1,313 @@ - - - - - + + + + + diff --git a/backend/src/views/moduleshop/center/index.vue b/backend/src/views/moduleshop/center/index.vue index 0c526aa..287623c 100644 --- a/backend/src/views/moduleshop/center/index.vue +++ b/backend/src/views/moduleshop/center/index.vue @@ -1,680 +1,680 @@ - - - - - + + + + + diff --git a/backend/src/views/moduleshop/components/createModules.vue b/backend/src/views/moduleshop/components/createModules.vue index 4418c18..8882d50 100644 --- a/backend/src/views/moduleshop/components/createModules.vue +++ b/backend/src/views/moduleshop/components/createModules.vue @@ -1,312 +1,312 @@ - - - - - + + + + + diff --git a/backend/src/views/moduleshop/index.vue b/backend/src/views/moduleshop/index.vue index 2fa6465..2d8f283 100644 --- a/backend/src/views/moduleshop/index.vue +++ b/backend/src/views/moduleshop/index.vue @@ -1,11 +1,11 @@ - - - - - \ No newline at end of file diff --git a/backend/src/views/moduleshop/publish/index.vue b/backend/src/views/moduleshop/publish/index.vue index b086e6b..952024c 100644 --- a/backend/src/views/moduleshop/publish/index.vue +++ b/backend/src/views/moduleshop/publish/index.vue @@ -1,455 +1,455 @@ - - - - - + + + + + diff --git a/backend/src/views/onepage/index.vue b/backend/src/views/onepage/index.vue index 549a0f9..2c502c7 100644 --- a/backend/src/views/onepage/index.vue +++ b/backend/src/views/onepage/index.vue @@ -1,170 +1,170 @@ - - - - - - + + + + + + diff --git a/backend/src/views/settings/index.vue b/backend/src/views/settings/index.vue index 6c91165..45f699c 100644 --- a/backend/src/views/settings/index.vue +++ b/backend/src/views/settings/index.vue @@ -1,11 +1,11 @@ - - - - - + + + + + diff --git a/backend/src/views/settings/systeminfo/index.vue b/backend/src/views/settings/systeminfo/index.vue index c559b76..f970468 100644 --- a/backend/src/views/settings/systeminfo/index.vue +++ b/backend/src/views/settings/systeminfo/index.vue @@ -1,420 +1,420 @@ - - - - - - + + + + + + diff --git a/backend/src/views/system/dict/components/DictItemEdit.vue b/backend/src/views/system/dict/components/DictItemEdit.vue index 8dc7994..ed57bd9 100644 --- a/backend/src/views/system/dict/components/DictItemEdit.vue +++ b/backend/src/views/system/dict/components/DictItemEdit.vue @@ -1,335 +1,335 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/backend/src/views/system/dict/components/DictItemEditDialog.vue b/backend/src/views/system/dict/components/DictItemEditDialog.vue index 47b3691..9717310 100644 --- a/backend/src/views/system/dict/components/DictItemEditDialog.vue +++ b/backend/src/views/system/dict/components/DictItemEditDialog.vue @@ -1,316 +1,316 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/backend/src/views/system/dict/components/DictItemList.vue b/backend/src/views/system/dict/components/DictItemList.vue index 730c27e..e7303d6 100644 --- a/backend/src/views/system/dict/components/DictItemList.vue +++ b/backend/src/views/system/dict/components/DictItemList.vue @@ -1,422 +1,422 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/backend/src/views/system/dict/components/DictTypeEdit.vue b/backend/src/views/system/dict/components/DictTypeEdit.vue index 4db050f..23a2f97 100644 --- a/backend/src/views/system/dict/components/DictTypeEdit.vue +++ b/backend/src/views/system/dict/components/DictTypeEdit.vue @@ -1,241 +1,241 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/backend/src/views/system/dict/components/DictTypeList.vue b/backend/src/views/system/dict/components/DictTypeList.vue index 216ea72..60ad101 100644 --- a/backend/src/views/system/dict/components/DictTypeList.vue +++ b/backend/src/views/system/dict/components/DictTypeList.vue @@ -1,316 +1,316 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/backend/src/views/system/dict/index.vue b/backend/src/views/system/dict/index.vue index 8f1634f..60273da 100644 --- a/backend/src/views/system/dict/index.vue +++ b/backend/src/views/system/dict/index.vue @@ -1,368 +1,368 @@ - - - - - + + + + + diff --git a/backend/src/views/system/fileManager/components/createCategory.vue b/backend/src/views/system/fileManager/components/createCategory.vue index 09ad8e5..569056c 100644 --- a/backend/src/views/system/fileManager/components/createCategory.vue +++ b/backend/src/views/system/fileManager/components/createCategory.vue @@ -1,155 +1,155 @@ - - - - - + + + + + diff --git a/backend/src/views/system/fileManager/components/moveFile.vue b/backend/src/views/system/fileManager/components/moveFile.vue index ba2b689..3268ded 100644 --- a/backend/src/views/system/fileManager/components/moveFile.vue +++ b/backend/src/views/system/fileManager/components/moveFile.vue @@ -1,124 +1,124 @@ - - - + + + diff --git a/backend/src/views/system/fileManager/components/renameCategory.vue b/backend/src/views/system/fileManager/components/renameCategory.vue index 0def892..de97f87 100644 --- a/backend/src/views/system/fileManager/components/renameCategory.vue +++ b/backend/src/views/system/fileManager/components/renameCategory.vue @@ -1,175 +1,175 @@ - - - - - + + + + + diff --git a/backend/src/views/system/fileManager/components/uploadFile.vue b/backend/src/views/system/fileManager/components/uploadFile.vue index 06208d5..13a6eb7 100644 --- a/backend/src/views/system/fileManager/components/uploadFile.vue +++ b/backend/src/views/system/fileManager/components/uploadFile.vue @@ -1,313 +1,313 @@ - - - - - - + + + + + + diff --git a/backend/src/views/system/fileManager/index.vue b/backend/src/views/system/fileManager/index.vue index 1f9daf6..ee2fc22 100644 --- a/backend/src/views/system/fileManager/index.vue +++ b/backend/src/views/system/fileManager/index.vue @@ -1,1610 +1,1610 @@ - - - - - + + + + + diff --git a/backend/src/views/system/index.vue b/backend/src/views/system/index.vue index 2fa6465..2d8f283 100644 --- a/backend/src/views/system/index.vue +++ b/backend/src/views/system/index.vue @@ -1,11 +1,11 @@ - - - - - \ No newline at end of file diff --git a/backend/src/views/system/menus/components/edit.vue b/backend/src/views/system/menus/components/edit.vue index a0e865b..3e03a2b 100644 --- a/backend/src/views/system/menus/components/edit.vue +++ b/backend/src/views/system/menus/components/edit.vue @@ -1,441 +1,441 @@ - - - - - \ No newline at end of file diff --git a/backend/src/views/system/menus/manager.vue b/backend/src/views/system/menus/manager.vue index 85b65e3..bc25411 100644 --- a/backend/src/views/system/menus/manager.vue +++ b/backend/src/views/system/menus/manager.vue @@ -1,565 +1,565 @@ - - - - - + + + + + diff --git a/backend/src/views/system/operationLog/components/detail.vue b/backend/src/views/system/operationLog/components/detail.vue index 465a602..992723a 100644 --- a/backend/src/views/system/operationLog/components/detail.vue +++ b/backend/src/views/system/operationLog/components/detail.vue @@ -1,272 +1,272 @@ - - - - - - + + + + + + diff --git a/backend/src/views/system/operationLog/index.vue b/backend/src/views/system/operationLog/index.vue index 5d9388c..b21f9fc 100644 --- a/backend/src/views/system/operationLog/index.vue +++ b/backend/src/views/system/operationLog/index.vue @@ -1,401 +1,401 @@ - - - - - + + + + + diff --git a/backend/src/views/system/permissions/index.vue b/backend/src/views/system/permissions/index.vue index 08885fb..5950f51 100644 --- a/backend/src/views/system/permissions/index.vue +++ b/backend/src/views/system/permissions/index.vue @@ -1,589 +1,589 @@ - - - - - + + + + + diff --git a/backend/src/views/template/index.vue b/backend/src/views/template/index.vue index 04d3b9a..12622ec 100644 --- a/backend/src/views/template/index.vue +++ b/backend/src/views/template/index.vue @@ -1,11 +1,11 @@ - - - - - + + + + + diff --git a/backend/src/views/user/components/BindEmailDialog.vue b/backend/src/views/user/components/BindEmailDialog.vue index 9900995..e4d1733 100644 --- a/backend/src/views/user/components/BindEmailDialog.vue +++ b/backend/src/views/user/components/BindEmailDialog.vue @@ -1,106 +1,106 @@ - - - + + + diff --git a/backend/src/views/user/components/BindPhoneDialog.vue b/backend/src/views/user/components/BindPhoneDialog.vue index 32a54c0..046753f 100644 --- a/backend/src/views/user/components/BindPhoneDialog.vue +++ b/backend/src/views/user/components/BindPhoneDialog.vue @@ -1,106 +1,106 @@ - - - + + + diff --git a/backend/src/views/user/components/ChangePasswordDialog.vue b/backend/src/views/user/components/ChangePasswordDialog.vue index fbbd89c..9ca604f 100644 --- a/backend/src/views/user/components/ChangePasswordDialog.vue +++ b/backend/src/views/user/components/ChangePasswordDialog.vue @@ -1,125 +1,125 @@ - - - + + + diff --git a/backend/src/views/user/components/DetailCard.vue b/backend/src/views/user/components/DetailCard.vue index a64c100..859097a 100644 --- a/backend/src/views/user/components/DetailCard.vue +++ b/backend/src/views/user/components/DetailCard.vue @@ -1,91 +1,91 @@ - - - - - + + + + + diff --git a/backend/src/views/user/components/EditProfileDialog.vue b/backend/src/views/user/components/EditProfileDialog.vue index f8c5fa4..413059f 100644 --- a/backend/src/views/user/components/EditProfileDialog.vue +++ b/backend/src/views/user/components/EditProfileDialog.vue @@ -1,137 +1,137 @@ - - - + + + diff --git a/backend/src/views/user/components/ProfileCard.vue b/backend/src/views/user/components/ProfileCard.vue index e3accc3..8b2507a 100644 --- a/backend/src/views/user/components/ProfileCard.vue +++ b/backend/src/views/user/components/ProfileCard.vue @@ -1,130 +1,130 @@ - - - - - + + + + + diff --git a/backend/src/views/user/components/QuickActions.vue b/backend/src/views/user/components/QuickActions.vue index 08244e3..1b37116 100644 --- a/backend/src/views/user/components/QuickActions.vue +++ b/backend/src/views/user/components/QuickActions.vue @@ -1,83 +1,83 @@ - - - - - + + + + + diff --git a/backend/src/views/user/components/SecurityCard.vue b/backend/src/views/user/components/SecurityCard.vue index 3192625..5d8f143 100644 --- a/backend/src/views/user/components/SecurityCard.vue +++ b/backend/src/views/user/components/SecurityCard.vue @@ -1,150 +1,150 @@ - - - - - + + + + + diff --git a/backend/src/views/user/userProfile.vue b/backend/src/views/user/userProfile.vue index fae94aa..01bfafe 100644 --- a/backend/src/views/user/userProfile.vue +++ b/backend/src/views/user/userProfile.vue @@ -1,200 +1,200 @@ - - - - - + + + + + diff --git a/backend/src/vite-env.d.ts b/backend/src/vite-env.d.ts index d326d43..ea3b2e4 100644 --- a/backend/src/vite-env.d.ts +++ b/backend/src/vite-env.d.ts @@ -1,10 +1,10 @@ -/// - -interface ImportMetaEnv { - readonly VITE_API_BASE_URL: string -} - -interface ImportMeta { - readonly env: ImportMetaEnv -} - +/// + +interface ImportMetaEnv { + readonly VITE_API_BASE_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + diff --git a/backend/vite.config.js b/backend/vite.config.js index a26cc4b..12ca94e 100644 --- a/backend/vite.config.js +++ b/backend/vite.config.js @@ -1,27 +1,27 @@ -import { defineConfig } from "vite"; -import vue from "@vitejs/plugin-vue"; -import { resolve } from "path"; -import AutoImport from "unplugin-auto-import/vite"; -import Components from "unplugin-vue-components/vite"; -import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [ - vue(), - AutoImport({ - resolvers: [ElementPlusResolver()], - }), - Components({ - resolvers: [ElementPlusResolver()], - }), - ], - resolve: { - alias: { - "@": resolve(__dirname, "./src"), - }, - }, - server: { - port: 5000, - }, -}); +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import { resolve } from "path"; +import AutoImport from "unplugin-auto-import/vite"; +import Components from "unplugin-vue-components/vite"; +import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + ], + resolve: { + alias: { + "@": resolve(__dirname, "./src"), + }, + }, + server: { + port: 5000, + }, +}); diff --git a/docs/改造.md b/docs/改造.md index 2068a04..53b6760 100644 --- a/docs/改造.md +++ b/docs/改造.md @@ -1,81 +1,81 @@ -# 【可直接投喂AI·优化版需求说明】 -你好,我现在需要对我的项目进行**多租户二级域名绑定官网系统**的整体改造,请根据我的现有项目结构和需求,帮我完成所有代码修改。 - -## 一、现有项目结构 -我有三个独立项目: -1. **frontend**:前端官网展示,使用 Vite 构建 -2. **backend**:后台管理系统,使用 Vite 构建 -3. **tp**:后端 API 服务,使用 ThinkPHP 框架 - -整套平台是**多租户 SaaS 模式**,为企业用户建设官网使用,**所有租户共用一个数据库,不独立分库**,通过租户 ID 做数据隔离。 - -## 二、要实现的核心功能 -1. 我在后台配置一批我已购买备案的**主域名**,形成域名池。 -2. 企业用户登录租户后台,可以**申请二级域名**。 -3. 租户选择主域名 + 自定义二级前缀,提交申请,管理员审核。 -4. 租户访问自己申请的**二级域名**时,系统自动识别对应租户,并展示该租户在 CMS 中选择的官网页面。 -5. 访问规则: - - `admin.xxx.com` → 后台管理系统 - - `www.xxx.com` / 裸域名 → 平台官网 - - 其他二级域名 → 对应租户的官网 - -## 三、数据库表结构(已建好) -我已经建好两张域名相关表,结构如下,你必须严格按这两张表开发: - -### 1. 主域名池表:`mete_system_domain_pool` -```sql -CREATE TABLE `mete_system_domain_pool` ( - `id` int(11) NOT NULL COMMENT 'id', - `main_domain` varchar(255) DEFAULT NULL COMMENT '主域名', - `status` int(11) DEFAULT NULL COMMENT '状态 0-禁用 1-启用', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 -``` - -### 2. 租户域名绑定表:`mete_tenant_domain` -```sql -CREATE TABLE `mete_tenant_domain` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', - `tenant_id` int(11) DEFAULT NULL COMMENT '租户 ID', - `sub_domain` varchar(50) DEFAULT NULL COMMENT '二级域名前缀', - `main_domain` varchar(255) DEFAULT NULL COMMENT '绑定的主域名', - `full_domain` varchar(255) DEFAULT NULL COMMENT '完整域名', - `status` int(11) DEFAULT NULL COMMENT '状态(1 已生效 / 0 审核中 / 2 禁用)', - `create_time` datetime NOT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 -``` - -## 四、你需要帮我完成的改造内容 -1. **ThinkPHP 后端** - - 编写**全局域名解析中间件**:通过访问域名自动识别租户。 - - 实现主域名池管理接口(增删改查、启用禁用)。 - - 实现租户二级域名申请、列表查询接口。 - - 实现管理员审核租户域名接口。 - - 所有租户相关接口必须自动带上 `tenant_id` 做数据隔离。 - -2. **backend 后台(Vite + Vue)** - - 管理员端:主域名池管理页面。 - - 管理员端:租户域名审核页面。 - - 租户端:二级域名申请页面、我的域名列表页面。 - -3. **nginx 配置** - - 给出可直接使用的泛域名解析、路由转发配置。 - - 区分后台、官网、租户二级域名。 - -4. **整体逻辑要求** - - 多租户共用数据库,不独立库,只按 `tenant_id` 隔离。 - - 二级域名唯一,不能重复。 - - 支持软删除 `delete_time`。 - - 域名状态严格按我给的字段逻辑。 - -## 五、你的输出要求 -- 直接按我现有项目结构**给出完整可替换的文件与代码**。 -- 标明每个文件路径、作用、代码内容。 -- 不要解释原理,直接输出可使用代码。 -- 严格使用我提供的表名与字段,不要新增或修改字段。 +# 【可直接投喂AI·优化版需求说明】 +你好,我现在需要对我的项目进行**多租户二级域名绑定官网系统**的整体改造,请根据我的现有项目结构和需求,帮我完成所有代码修改。 + +## 一、现有项目结构 +我有三个独立项目: +1. **frontend**:前端官网展示,使用 Vite 构建 +2. **backend**:后台管理系统,使用 Vite 构建 +3. **tp**:后端 API 服务,使用 ThinkPHP 框架 + +整套平台是**多租户 SaaS 模式**,为企业用户建设官网使用,**所有租户共用一个数据库,不独立分库**,通过租户 ID 做数据隔离。 + +## 二、要实现的核心功能 +1. 我在后台配置一批我已购买备案的**主域名**,形成域名池。 +2. 企业用户登录租户后台,可以**申请二级域名**。 +3. 租户选择主域名 + 自定义二级前缀,提交申请,管理员审核。 +4. 租户访问自己申请的**二级域名**时,系统自动识别对应租户,并展示该租户在 CMS 中选择的官网页面。 +5. 访问规则: + - `admin.xxx.com` → 后台管理系统 + - `www.xxx.com` / 裸域名 → 平台官网 + - 其他二级域名 → 对应租户的官网 + +## 三、数据库表结构(已建好) +我已经建好两张域名相关表,结构如下,你必须严格按这两张表开发: + +### 1. 主域名池表:`mete_system_domain_pool` +```sql +CREATE TABLE `mete_system_domain_pool` ( + `id` int(11) NOT NULL COMMENT 'id', + `main_domain` varchar(255) DEFAULT NULL COMMENT '主域名', + `status` int(11) DEFAULT NULL COMMENT '状态 0-禁用 1-启用', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +``` + +### 2. 租户域名绑定表:`mete_tenant_domain` +```sql +CREATE TABLE `mete_tenant_domain` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', + `tenant_id` int(11) DEFAULT NULL COMMENT '租户 ID', + `sub_domain` varchar(50) DEFAULT NULL COMMENT '二级域名前缀', + `main_domain` varchar(255) DEFAULT NULL COMMENT '绑定的主域名', + `full_domain` varchar(255) DEFAULT NULL COMMENT '完整域名', + `status` int(11) DEFAULT NULL COMMENT '状态(1 已生效 / 0 审核中 / 2 禁用)', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 +``` + +## 四、你需要帮我完成的改造内容 +1. **ThinkPHP 后端** + - 编写**全局域名解析中间件**:通过访问域名自动识别租户。 + - 实现主域名池管理接口(增删改查、启用禁用)。 + - 实现租户二级域名申请、列表查询接口。 + - 实现管理员审核租户域名接口。 + - 所有租户相关接口必须自动带上 `tenant_id` 做数据隔离。 + +2. **backend 后台(Vite + Vue)** + - 管理员端:主域名池管理页面。 + - 管理员端:租户域名审核页面。 + - 租户端:二级域名申请页面、我的域名列表页面。 + +3. **nginx 配置** + - 给出可直接使用的泛域名解析、路由转发配置。 + - 区分后台、官网、租户二级域名。 + +4. **整体逻辑要求** + - 多租户共用数据库,不独立库,只按 `tenant_id` 隔离。 + - 二级域名唯一,不能重复。 + - 支持软删除 `delete_time`。 + - 域名状态严格按我给的字段逻辑。 + +## 五、你的输出要求 +- 直接按我现有项目结构**给出完整可替换的文件与代码**。 +- 标明每个文件路径、作用、代码内容。 +- 不要解释原理,直接输出可使用代码。 +- 严格使用我提供的表名与字段,不要新增或修改字段。 diff --git a/go/.gitignore b/go/.gitignore index 28cf07b..0716027 100644 --- a/go/.gitignore +++ b/go/.gitignore @@ -1,2 +1,2 @@ -go-platform.zip +go-platform.zip server.exe \ No newline at end of file diff --git a/go/conf/app.conf b/go/conf/app.conf index 9dc2a71..e31286c 100644 --- a/go/conf/app.conf +++ b/go/conf/app.conf @@ -1,32 +1,32 @@ -appname = server -httpport = 8081 -runmode = dev - -# 启用请求体复制(允许多次读取请求体) -copyrequestbody = true - -# 服务器超时配置(支持大文件上传) -# 0 表示不设置超时限制 -ServerTimeOut = 0 -# 最大请求体大小(字节),0 表示不限制 -MaxMemory = 0 - -# 最大请求体大小(用于普通请求,10MB) -maxmemory = 10485760 - -# 数据库配置 -# MySQL - 远程连接配置 -mysqluser = go-platform -mysqlpass = FSmJCSJ5wk8pjjDC -mysqlurls = 212.64.112.158:3388 -mysqldb = go-platform - -# ORM配置 -orm = mysql - -# 配置静态文件目录 -# 映射 /static 路径到前端 dist 目录 -# StaticDir = /static:../front/dist - -# 映射 /uploads 路径到上传文件目录(项目根目录下的 uploads 文件夹) +appname = server +httpport = 8081 +runmode = dev + +# 启用请求体复制(允许多次读取请求体) +copyrequestbody = true + +# 服务器超时配置(支持大文件上传) +# 0 表示不设置超时限制 +ServerTimeOut = 0 +# 最大请求体大小(字节),0 表示不限制 +MaxMemory = 0 + +# 最大请求体大小(用于普通请求,10MB) +maxmemory = 10485760 + +# 数据库配置 +# MySQL - 远程连接配置 +mysqluser = go-platform +mysqlpass = FSmJCSJ5wk8pjjDC +mysqlurls = 212.64.112.158:3388 +mysqldb = go-platform + +# ORM配置 +orm = mysql + +# 配置静态文件目录 +# 映射 /static 路径到前端 dist 目录 +# StaticDir = /static:../front/dist + +# 映射 /uploads 路径到上传文件目录(项目根目录下的 uploads 文件夹) StaticDir = /uploads:../uploads \ No newline at end of file diff --git a/go/controllers/api_cursor_detect.go b/go/controllers/api_cursor_detect.go index c0608d6..03f376c 100644 --- a/go/controllers/api_cursor_detect.go +++ b/go/controllers/api_cursor_detect.go @@ -1,154 +1,154 @@ -package controllers - -import ( - "fmt" - "strconv" - "strings" - "time" - - "server/models" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// ApiCursorDetectController Cursor Token 顺序读取接口(不改变号池状态) -// -// 用途: -// - 前端传入 start_id/current_id/id,直接读取该 ID 对应的 Cursor Token。 -// - 只读取记录,不更新 is_extracted、extracted_time、extracted_platform、is_used 等任何状态。 -// - 不再跳过已提取的记录,输入什么 ID 就提取什么 ID。 -// - 返回 next_id,前端下一次点击时传 next_id,即可实现 11 -> 12 -> 13 递增读取。 -// -// 示例: -// -// GET /api/cursor/token/peek?id=11 -// GET /api/cursor/token/peek?start_id=11 -// GET /api/cursor/token/peek?current_id=11 -// -// 可选参数: -// - data_type=tk/account/account_tk,默认 tk -type ApiCursorDetectController struct { - beego.Controller -} - -func (c *ApiCursorDetectController) cursorDetectJSONErr(httpStatus, code int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{ - "code": code, - "msg": msg, - } - _ = c.ServeJSON() -} - -func (c *ApiCursorDetectController) setTokenUsableByID(isUsed int8) { - id, err := c.readStartID() - if err != nil { - c.cursorDetectJSONErr(400, 400, err.Error()) - return - } - - now := time.Now() - updated, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(orm.Params{ - "is_used": isUsed, - "update_time": now, - }) - if err != nil { - c.cursorDetectJSONErr(500, 500, "更新失败: "+err.Error()) - return - } - if updated == 0 { - c.cursorDetectJSONErr(404, 404, "当前 ID 数据不存在或已删除") - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "id": id, - "is_used": isUsed, - "is_available": isUsed == 1, - "state_changed": true, - "update_time": now, - }, - } - _ = c.ServeJSON() -} - -// MarkTokenAvailable 将当前 ID 的 Cursor Token 标记为可用。 -func (c *ApiCursorDetectController) MarkTokenAvailable() { - c.setTokenUsableByID(1) -} - -// MarkTokenUnavailable 将当前 ID 的 Cursor Token 标记为不可用/用完。 -func (c *ApiCursorDetectController) MarkTokenUnavailable() { - c.setTokenUsableByID(0) -} - -// PeekToken 按 ID 顺序读取 Cursor Token,不改变任何状态。 -func (c *ApiCursorDetectController) PeekToken() { - startID, err := c.readStartID() - if err != nil { - c.cursorDetectJSONErr(400, 400, err.Error()) - return - } - - var row models.PlatformAccountPoolCursor - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - Filter("id", startID). - Filter("delete_time__isnull", true). - Exclude("token", "") - - err = qs.One(&row) - if err != nil { - if err == orm.ErrNoRows { - c.cursorDetectJSONErr(404, 404, "指定 ID 的 Cursor Token 不存在或已删除") - return - } - c.cursorDetectJSONErr(500, 500, "查询失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "id": row.ID, - "next_id": row.ID + 1, - "data_type": row.DataType, - "account": row.Account, - "password": row.Password, - "token": row.Token, - "remark": row.Remark, - "is_used": row.IsUsed, - "create_time": row.CreateTime, - - // 明确告诉前端:本接口只是读取,没有更新号池状态。 - "state_changed": false, - }, - } - _ = c.ServeJSON() -} - -func (c *ApiCursorDetectController) readStartID() (uint64, error) { - raw := strings.TrimSpace(c.GetString("id")) - if raw == "" { - raw = strings.TrimSpace(c.GetString("start_id")) - } - if raw == "" { - raw = strings.TrimSpace(c.GetString("current_id")) - } - if raw == "" { - return 0, fmt.Errorf("缺少参数 id/start_id/current_id") - } - - id, err := strconv.ParseUint(raw, 10, 64) - if err != nil || id == 0 { - return 0, fmt.Errorf("id 必须是大于 0 的整数") - } - return id, nil -} +package controllers + +import ( + "fmt" + "strconv" + "strings" + "time" + + "server/models" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// ApiCursorDetectController Cursor Token 顺序读取接口(不改变号池状态) +// +// 用途: +// - 前端传入 start_id/current_id/id,直接读取该 ID 对应的 Cursor Token。 +// - 只读取记录,不更新 is_extracted、extracted_time、extracted_platform、is_used 等任何状态。 +// - 不再跳过已提取的记录,输入什么 ID 就提取什么 ID。 +// - 返回 next_id,前端下一次点击时传 next_id,即可实现 11 -> 12 -> 13 递增读取。 +// +// 示例: +// +// GET /api/cursor/token/peek?id=11 +// GET /api/cursor/token/peek?start_id=11 +// GET /api/cursor/token/peek?current_id=11 +// +// 可选参数: +// - data_type=tk/account/account_tk,默认 tk +type ApiCursorDetectController struct { + beego.Controller +} + +func (c *ApiCursorDetectController) cursorDetectJSONErr(httpStatus, code int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{ + "code": code, + "msg": msg, + } + _ = c.ServeJSON() +} + +func (c *ApiCursorDetectController) setTokenUsableByID(isUsed int8) { + id, err := c.readStartID() + if err != nil { + c.cursorDetectJSONErr(400, 400, err.Error()) + return + } + + now := time.Now() + updated, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(orm.Params{ + "is_used": isUsed, + "update_time": now, + }) + if err != nil { + c.cursorDetectJSONErr(500, 500, "更新失败: "+err.Error()) + return + } + if updated == 0 { + c.cursorDetectJSONErr(404, 404, "当前 ID 数据不存在或已删除") + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "id": id, + "is_used": isUsed, + "is_available": isUsed == 1, + "state_changed": true, + "update_time": now, + }, + } + _ = c.ServeJSON() +} + +// MarkTokenAvailable 将当前 ID 的 Cursor Token 标记为可用。 +func (c *ApiCursorDetectController) MarkTokenAvailable() { + c.setTokenUsableByID(1) +} + +// MarkTokenUnavailable 将当前 ID 的 Cursor Token 标记为不可用/用完。 +func (c *ApiCursorDetectController) MarkTokenUnavailable() { + c.setTokenUsableByID(0) +} + +// PeekToken 按 ID 顺序读取 Cursor Token,不改变任何状态。 +func (c *ApiCursorDetectController) PeekToken() { + startID, err := c.readStartID() + if err != nil { + c.cursorDetectJSONErr(400, 400, err.Error()) + return + } + + var row models.PlatformAccountPoolCursor + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("id", startID). + Filter("delete_time__isnull", true). + Exclude("token", "") + + err = qs.One(&row) + if err != nil { + if err == orm.ErrNoRows { + c.cursorDetectJSONErr(404, 404, "指定 ID 的 Cursor Token 不存在或已删除") + return + } + c.cursorDetectJSONErr(500, 500, "查询失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "id": row.ID, + "next_id": row.ID + 1, + "data_type": row.DataType, + "account": row.Account, + "password": row.Password, + "token": row.Token, + "remark": row.Remark, + "is_used": row.IsUsed, + "create_time": row.CreateTime, + + // 明确告诉前端:本接口只是读取,没有更新号池状态。 + "state_changed": false, + }, + } + _ = c.ServeJSON() +} + +func (c *ApiCursorDetectController) readStartID() (uint64, error) { + raw := strings.TrimSpace(c.GetString("id")) + if raw == "" { + raw = strings.TrimSpace(c.GetString("start_id")) + } + if raw == "" { + raw = strings.TrimSpace(c.GetString("current_id")) + } + if raw == "" { + return 0, fmt.Errorf("缺少参数 id/start_id/current_id") + } + + id, err := strconv.ParseUint(raw, 10, 64) + if err != nil || id == 0 { + return 0, fmt.Errorf("id 必须是大于 0 的整数") + } + return id, nil +} diff --git a/go/controllers/api_cursor_equipment.go b/go/controllers/api_cursor_equipment.go index 1c63c31..571c3b9 100644 --- a/go/controllers/api_cursor_equipment.go +++ b/go/controllers/api_cursor_equipment.go @@ -1,720 +1,720 @@ -package controllers - -import ( - "encoding/json" - "strings" - "time" - - "server/models" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// ApiCursorEquipmentController 开放接口:登录器上报 Cursor 设备信息(无需登录) -type ApiCursorEquipmentController struct { - beego.Controller -} - -// cursorIpInfo 对应 ip-api.com 返回的 JSON 结构 -type cursorIpInfo struct { - Status string `json:"status"` - Country string `json:"country"` - CountryCode string `json:"countryCode"` - Region string `json:"region"` - RegionName string `json:"regionName"` - City string `json:"city"` - Zip string `json:"zip"` - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` - Timezone string `json:"timezone"` - ISP string `json:"isp"` - Org string `json:"org"` - As string `json:"as"` - Query string `json:"query"` -} - -type cursorEquipmentReportPayload struct { - DeviceInfo string `json:"deviceInfo"` - DeviceInfoSnake string `json:"device_info"` - MachineCode string `json:"machineCode"` - MachineCodeSnake string `json:"machine_code"` - Status *int8 `json:"status"` - System string `json:"system"` - Version string `json:"version"` - BindAccount string `json:"bindAccount"` - BindAccountSnake string `json:"bind_account"` - OwnerUserID *uint64 `json:"ownerUserId"` - OwnerUserIDSnake *uint64 `json:"owner_user_id"` - OwnerUserName string `json:"ownerUserName"` - OwnerUserNameSnake string `json:"owner_user_name"` - ActivationTime string `json:"activationTime"` - ActivationTimeSnake string `json:"activation_time"` - ExpireTime string `json:"expireTime"` - ExpireTimeSnake string `json:"expire_time"` - Remark string `json:"remark"` - IpInfo *cursorIpInfo `json:"ipInfo"` -} - -type cursorEquipmentActivatePayload struct { - Code string `json:"code"` - ActivationCode string `json:"activationCode"` - ActivationCodeSnake string `json:"activation_code"` - DeviceInfo string `json:"deviceInfo"` - DeviceInfoSnake string `json:"device_info"` - MachineCode string `json:"machineCode"` - MachineCodeSnake string `json:"machine_code"` - System string `json:"system"` - Version string `json:"version"` - BindAccount string `json:"bindAccount"` - BindAccountSnake string `json:"bind_account"` - OwnerUserID *uint64 `json:"ownerUserId"` - OwnerUserIDSnake *uint64 `json:"owner_user_id"` - OwnerUserName string `json:"ownerUserName"` - OwnerUserNameSnake string `json:"owner_user_name"` - Remark string `json:"remark"` - IpInfo *cursorIpInfo `json:"ipInfo"` -} - -// cursorSaveIpLog 将 ipInfo 写入设备 IP 日志表(异步,失败不影响主流程) -func cursorSaveIpLog(equipmentID uint64, machineCode, source string, ip *cursorIpInfo) { - if ip == nil { - return - } - log := &models.PlatformCursorEquipmentIpLog{ - EquipmentID: equipmentID, - MachineCode: machineCode, - Source: source, - Status: ip.Status, - Country: ip.Country, - CountryCode: ip.CountryCode, - Region: ip.Region, - RegionName: ip.RegionName, - City: ip.City, - Zip: ip.Zip, - Lat: ip.Lat, - Lon: ip.Lon, - Timezone: ip.Timezone, - ISP: ip.ISP, - Org: ip.Org, - AsInfo: ip.As, - Query: ip.Query, - } - _, _ = models.Orm.Insert(log) -} - -func cursorFirstNonEmpty(values ...string) string { - for _, v := range values { - if s := strings.TrimSpace(v); s != "" { - return s - } - } - return "" -} - -func cursorStringPtr(value string) *string { - value = strings.TrimSpace(value) - if value == "" { - return nil - } - return &value -} - -func cursorParseTimePtr(value string) *time.Time { - value = strings.TrimSpace(value) - if value == "" { - return nil - } - - layouts := []string{ - time.RFC3339, - "2006-01-02 15:04:05", - "2006-01-02 15:04", - "2006-01-02", - } - for _, layout := range layouts { - if t, err := time.ParseInLocation(layout, value, time.Local); err == nil { - return &t - } - } - return nil -} - -func cursorValidStatus(status int8) bool { - return status == 0 || status == 1 || status == 2 || status == 3 -} - -func (c *ApiCursorEquipmentController) jsonResult(code int, msg string, data interface{}) { - resp := map[string]interface{}{"code": code, "msg": msg} - if data != nil { - resp["data"] = data - } - c.Data["json"] = resp - _ = c.ServeJSON() -} - -// Report POST /api/cursor/equipment/report -// -// JSON 示例: -// -// { -// "machineCode": "ABC-123", -// "deviceInfo": "CPU/RAM/磁盘等设备信息", -// "system": "Windows", -// "version": "1.0.0", -// "bindAccount": "user@example.com", -// "ownerUserId": 1, -// "ownerUserName": "张三", -// "activationTime": "2026-06-15 22:00:00", -// "expireTime": "2026-07-15 22:00:00", -// "remark": "登录器上报" -// } -// -// 兼容 snake_case 字段,例如 machine_code、device_info、bind_account。 -func (c *ApiCursorEquipmentController) Report() { - var p cursorEquipmentReportPayload - body := c.Ctx.Input.RequestBody - if len(body) > 0 { - if err := json.Unmarshal(body, &p); err != nil { - c.jsonResult(400, "参数错误", nil) - return - } - } - // 兼容 query string 参数 - if p.MachineCode == "" { - p.MachineCode = c.GetString("machineCode") - } - if p.MachineCodeSnake == "" { - p.MachineCodeSnake = c.GetString("machine_code") - } - if p.DeviceInfo == "" { - p.DeviceInfo = c.GetString("deviceInfo") - } - if p.DeviceInfoSnake == "" { - p.DeviceInfoSnake = c.GetString("device_info") - } - if p.System == "" { - p.System = c.GetString("system") - } - if p.Version == "" { - p.Version = c.GetString("version") - } - if p.BindAccount == "" { - p.BindAccount = c.GetString("bindAccount") - } - if p.BindAccountSnake == "" { - p.BindAccountSnake = c.GetString("bind_account") - } - if p.OwnerUserName == "" { - p.OwnerUserName = c.GetString("ownerUserName") - } - if p.OwnerUserNameSnake == "" { - p.OwnerUserNameSnake = c.GetString("owner_user_name") - } - if p.ActivationTime == "" { - p.ActivationTime = c.GetString("activationTime") - } - if p.ActivationTimeSnake == "" { - p.ActivationTimeSnake = c.GetString("activation_time") - } - if p.ExpireTime == "" { - p.ExpireTime = c.GetString("expireTime") - } - if p.ExpireTimeSnake == "" { - p.ExpireTimeSnake = c.GetString("expire_time") - } - if p.Remark == "" { - p.Remark = c.GetString("remark") - } - if p.Status == nil { - if s, err := c.GetInt8("status"); err == nil { - p.Status = &s - } - } - if p.OwnerUserID == nil { - if uid, err := c.GetUint64("ownerUserId"); err == nil && uid > 0 { - p.OwnerUserID = &uid - } - } - - machineCode := cursorFirstNonEmpty(p.MachineCode, p.MachineCodeSnake) - if machineCode == "" { - c.jsonResult(400, "缺少参数 machineCode/machine_code(机器码)", nil) - return - } - if len(machineCode) > 128 { - c.jsonResult(400, "机器码长度不能超过 128 个字符", nil) - return - } - - status := int8(0) - statusProvided := p.Status != nil - if statusProvided { - status = *p.Status - if !cursorValidStatus(status) { - c.jsonResult(400, "状态不合法,支持:0 未激活、1 激活中、2 已过期、3 已禁用", nil) - return - } - } - - deviceInfo := cursorFirstNonEmpty(p.DeviceInfo, p.DeviceInfoSnake) - system := cursorFirstNonEmpty(p.System) - version := cursorFirstNonEmpty(p.Version) - bindAccount := cursorFirstNonEmpty(p.BindAccount, p.BindAccountSnake) - ownerUserID := p.OwnerUserID - if ownerUserID == nil { - ownerUserID = p.OwnerUserIDSnake - } - ownerUserName := cursorFirstNonEmpty(p.OwnerUserName, p.OwnerUserNameSnake) - activationTime := cursorParseTimePtr(cursorFirstNonEmpty(p.ActivationTime, p.ActivationTimeSnake)) - expireTime := cursorParseTimePtr(cursorFirstNonEmpty(p.ExpireTime, p.ExpireTimeSnake)) - remark := cursorFirstNonEmpty(p.Remark) - - now := time.Now() - var row models.PlatformCursorEquipment - err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("machine_code", machineCode). - Filter("delete_time__isnull", true). - One(&row) - - created := false - if err == orm.ErrNoRows { - row = models.PlatformCursorEquipment{ - MachineCode: machineCode, - Status: status, - DeviceInfo: cursorStringPtr(deviceInfo), - System: cursorStringPtr(system), - Version: cursorStringPtr(version), - BindAccount: cursorStringPtr(bindAccount), - OwnerUserID: ownerUserID, - OwnerUserName: cursorStringPtr(ownerUserName), - ActivationTime: activationTime, - ExpireTime: expireTime, - Remark: cursorStringPtr(remark), - CreateTime: now, - } - id, insertErr := models.Orm.Insert(&row) - if insertErr != nil { - c.jsonResult(500, "设备信息保存失败", nil) - return - } - row.ID = uint64(id) - created = true - } else if err != nil { - c.jsonResult(500, "设备信息查询失败", nil) - return - } else { - update := map[string]interface{}{ - "device_info": cursorStringPtr(deviceInfo), - "system": cursorStringPtr(system), - "version": cursorStringPtr(version), - "bind_account": cursorStringPtr(bindAccount), - "owner_user_id": ownerUserID, - "owner_user_name": cursorStringPtr(ownerUserName), - "activation_time": activationTime, - "expire_time": expireTime, - "remark": cursorStringPtr(remark), - "update_time": now, - } - if statusProvided { - update["status"] = status - row.Status = status - } - - if _, updateErr := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", row.ID). - Update(update); updateErr != nil { - c.jsonResult(500, "设备信息更新失败", nil) - return - } - - row.DeviceInfo = cursorStringPtr(deviceInfo) - row.System = cursorStringPtr(system) - row.Version = cursorStringPtr(version) - row.BindAccount = cursorStringPtr(bindAccount) - row.OwnerUserID = ownerUserID - row.OwnerUserName = cursorStringPtr(ownerUserName) - row.ActivationTime = activationTime - row.ExpireTime = expireTime - row.Remark = cursorStringPtr(remark) - row.UpdateTime = &now - } - - // 记录 IP 日志 - cursorSaveIpLog(row.ID, machineCode, "report", p.IpInfo) - - // 查询该设备最新激活码,补全激活时间和到期时间 - var retActivationTime interface{} = row.ActivationTime - var retExpireTime interface{} = row.ExpireTime - - var latestCode models.PlatformCursorActivationCode - cond := orm.NewCondition(). - And("delete_time__isnull", true). - AndCond(orm.NewCondition(). - Or("bind_device_id", row.ID). - Or("machine_code", row.MachineCode)) - if err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - SetCond(cond). - OrderBy("-activated_at", "-id"). - One(&latestCode); err == nil { - if latestCode.ActivatedAt != nil { - retActivationTime = latestCode.ActivatedAt - } - if latestCode.ExpiredAt != nil { - retExpireTime = latestCode.ExpiredAt - } - } - - c.jsonResult(200, "success", map[string]interface{}{ - "id": row.ID, - "machineCode": row.MachineCode, - "status": row.Status, - "created": created, - "activationTime": retActivationTime, - "expireTime": retExpireTime, - }) -} - -// ActivateByCode POST /api/cursor/equipment/activateByCode -// -// 设备端使用激活码激活/续期 Cursor 设备(无需登录)。 -// -// JSON 示例: -// -// { -// "activationCode": "CUR-XXXXXXXX", -// "machineCode": "ABC-123", -// "deviceInfo": "CPU/RAM/磁盘等设备信息", -// "system": "Windows", -// "version": "1.0.0", -// "bindAccount": "user@example.com", -// "ownerUserId": 1, -// "ownerUserName": "张三", -// "remark": "登录器激活" -// } -// -// 兼容字段: -// - 激活码:activationCode / activation_code / code -// - 机器码:machineCode / machine_code -// - 设备信息:deviceInfo / device_info -// - 绑定账号:bindAccount / bind_account -func (c *ApiCursorEquipmentController) ActivateByCode() { - var p cursorEquipmentActivatePayload - if err := json.Unmarshal(c.Ctx.Input.RequestBody, &p); err != nil { - c.jsonResult(400, "参数错误", nil) - return - } - - code := cursorFirstNonEmpty(p.ActivationCode, p.ActivationCodeSnake, p.Code) - if code == "" { - c.jsonResult(400, "缺少参数 activationCode/activation_code/code(激活码)", nil) - return - } - if len(code) > 128 { - c.jsonResult(400, "激活码长度不能超过 128 个字符", nil) - return - } - - machineCode := cursorFirstNonEmpty(p.MachineCode, p.MachineCodeSnake) - if machineCode == "" { - c.jsonResult(400, "缺少参数 machineCode/machine_code(机器码)", nil) - return - } - if len(machineCode) > 128 { - c.jsonResult(400, "机器码长度不能超过 128 个字符", nil) - return - } - - deviceInfo := cursorFirstNonEmpty(p.DeviceInfo, p.DeviceInfoSnake) - system := cursorFirstNonEmpty(p.System) - version := cursorFirstNonEmpty(p.Version) - bindAccount := cursorFirstNonEmpty(p.BindAccount, p.BindAccountSnake) - ownerUserID := p.OwnerUserID - if ownerUserID == nil { - ownerUserID = p.OwnerUserIDSnake - } - ownerUserName := cursorFirstNonEmpty(p.OwnerUserName, p.OwnerUserNameSnake) - remark := cursorFirstNonEmpty(p.Remark) - - now := time.Now() - - var activationCode models.PlatformCursorActivationCode - err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - Filter("code", code). - Filter("delete_time__isnull", true). - One(&activationCode) - if err == orm.ErrNoRows { - c.jsonResult(404, "激活码不存在", nil) - return - } - if err != nil { - c.jsonResult(500, "激活码查询失败", nil) - return - } - - if activationCode.Status == 3 { - c.jsonResult(403, "激活码已禁用", nil) - return - } - if activationCode.Status == 2 || (activationCode.ExpiredAt != nil && activationCode.ExpiredAt.Before(now)) { - _, _ = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - Filter("id", activationCode.ID). - Update(map[string]interface{}{"status": int8(2), "update_time": now}) - c.jsonResult(410, "激活码已过期", nil) - return - } - - if activationCode.Status == 1 { - if activationCode.MachineCode == nil || strings.TrimSpace(*activationCode.MachineCode) != machineCode { - c.jsonResult(409, "激活码已被其他设备使用", nil) - return - } - - if activationCode.ExpiredAt != nil && activationCode.ExpiredAt.Before(now) { - _, _ = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - Filter("id", activationCode.ID). - Update(map[string]interface{}{"status": int8(2), "update_time": now}) - c.jsonResult(410, "激活码已过期", nil) - return - } - - c.jsonResult(200, "success", map[string]interface{}{ - "activated": true, - "reused": true, - "activationId": activationCode.ID, - "deviceId": activationCode.BindDeviceID, - "machineCode": machineCode, - "status": 1, - "durationDays": activationCode.DurationDays, - "activatedAt": activationCode.ActivatedAt, - "expireTime": activationCode.ExpiredAt, - "expiredAt": activationCode.ExpiredAt, - }) - return - } - - var device models.PlatformCursorEquipment - deviceErr := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("machine_code", machineCode). - Filter("delete_time__isnull", true). - One(&device) - - created := false - if deviceErr != nil && deviceErr != orm.ErrNoRows { - c.jsonResult(500, "设备信息查询失败", nil) - return - } - if deviceErr == nil && device.Status == 3 { - c.jsonResult(403, "设备已禁用,无法激活", nil) - return - } - - baseTime := now - if deviceErr == nil && device.ExpireTime != nil && device.ExpireTime.After(now) { - baseTime = *device.ExpireTime - } - - var expireTime *time.Time - if activationCode.DurationDays > 0 { - t := baseTime.AddDate(0, 0, activationCode.DurationDays) - expireTime = &t - } - - txOrm, err := models.Orm.Begin() - if err != nil { - c.jsonResult(500, "开启事务失败", nil) - return - } - - rollback := true - defer func() { - if rollback { - _ = txOrm.Rollback() - } - }() - - if deviceErr == orm.ErrNoRows { - device = models.PlatformCursorEquipment{ - MachineCode: machineCode, - Status: 1, - DeviceInfo: cursorStringPtr(deviceInfo), - System: cursorStringPtr(system), - Version: cursorStringPtr(version), - BindAccount: cursorStringPtr(bindAccount), - OwnerUserID: ownerUserID, - OwnerUserName: cursorStringPtr(ownerUserName), - ActivationTime: &now, - ExpireTime: expireTime, - Remark: cursorStringPtr(remark), - CreateTime: now, - } - id, insertErr := txOrm.Insert(&device) - if insertErr != nil { - c.jsonResult(500, "设备信息保存失败", nil) - return - } - device.ID = uint64(id) - created = true - } else { - if bindAccount == "" && device.BindAccount != nil { - bindAccount = *device.BindAccount - } - if ownerUserID == nil { - ownerUserID = device.OwnerUserID - } - if ownerUserName == "" && device.OwnerUserName != nil { - ownerUserName = *device.OwnerUserName - } - - _, updateErr := txOrm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", device.ID). - Update(map[string]interface{}{ - "device_info": cursorStringPtr(deviceInfo), - "system": cursorStringPtr(system), - "version": cursorStringPtr(version), - "bind_account": cursorStringPtr(bindAccount), - "owner_user_id": ownerUserID, - "owner_user_name": cursorStringPtr(ownerUserName), - "activation_time": now, - "expire_time": expireTime, - "status": int8(1), - "remark": cursorStringPtr(remark), - "update_time": now, - }) - if updateErr != nil { - c.jsonResult(500, "设备信息更新失败", nil) - return - } - } - - codeUpdateCount, updateCodeErr := txOrm.QueryTable(new(models.PlatformCursorActivationCode)). - Filter("id", activationCode.ID). - Filter("status", int8(0)). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{ - "status": int8(1), - "bind_account": cursorStringPtr(bindAccount), - "bind_device_id": device.ID, - "machine_code": machineCode, - "device_info": cursorStringPtr(deviceInfo), - "owner_user_id": ownerUserID, - "owner_user_name": cursorStringPtr(ownerUserName), - "activated_at": now, - "expired_at": expireTime, - "remark": cursorStringPtr(remark), - "update_time": now, - }) - if updateCodeErr != nil { - c.jsonResult(500, "激活码绑定失败", nil) - return - } - if codeUpdateCount == 0 { - c.jsonResult(409, "激活码状态已变化,请重新查询后再试", nil) - return - } - - if err := txOrm.Commit(); err != nil { - c.jsonResult(500, "提交事务失败", nil) - return - } - rollback = false - - // 记录 IP 日志 - cursorSaveIpLog(device.ID, machineCode, "activateByCode", p.IpInfo) - - c.jsonResult(200, "success", map[string]interface{}{ - "activated": true, - "reused": false, - "created": created, - "activationId": activationCode.ID, - "deviceId": device.ID, - "machineCode": machineCode, - "status": 1, - "durationDays": activationCode.DurationDays, - "activationAt": now, - "activatedAt": now, - "expireTime": expireTime, - "expiredAt": expireTime, - }) -} - -type cursorHeartbeatPayload struct { - MachineCode string `json:"machineCode"` - MachineCodeSnake string `json:"machine_code"` -} - -// Heartbeat POST /api/cursor/equipment/heartbeat -// -// 客户端心跳接口(无需登录),用于上报在线状态。 -// -// JSON 示例: -// -// { -// "machineCode": "ABC-123" -// } -func (c *ApiCursorEquipmentController) Heartbeat() { - var p cursorHeartbeatPayload - body := c.Ctx.Input.RequestBody - if len(body) > 0 { - if err := json.Unmarshal(body, &p); err != nil { - c.jsonResult(400, "参数错误", nil) - return - } - } - - machineCode := cursorFirstNonEmpty(p.MachineCode, p.MachineCodeSnake) - if machineCode == "" { - machineCode = c.GetString("machineCode") - } - if machineCode == "" { - machineCode = c.GetString("machine_code") - } - - if machineCode == "" { - c.jsonResult(400, "缺少参数 machineCode/machine_code(机器码)", nil) - return - } - - now := time.Now() - // 查询设备是否存在 - var row models.PlatformCursorEquipment - err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("machine_code", machineCode). - Filter("delete_time__isnull", true). - One(&row) - - if err == orm.ErrNoRows { - // 设备不存在,可能是第一次运行心跳,也可以允许在此处静默创建,或者返回 404 让客户端先进行 report - // 为了鲁棒性,如果设备未上报过,我们可以直接创建一个基础设备记录 - row = models.PlatformCursorEquipment{ - MachineCode: machineCode, - Status: 0, // 未激活 - LastHeartbeatAt: &now, - CreateTime: now, - } - if _, insertErr := models.Orm.Insert(&row); insertErr != nil { - c.jsonResult(500, "保存设备心跳失败", nil) - return - } - } else if err != nil { - c.jsonResult(500, "设备查询失败", nil) - return - } else { - // 更新最后心跳时间 - if _, updateErr := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", row.ID). - Update(map[string]interface{}{ - "last_heartbeat_at": &now, - "update_time": now, - }); updateErr != nil { - c.jsonResult(500, "更新设备心跳失败", nil) - return - } - } - - c.jsonResult(200, "success", map[string]interface{}{ - "machineCode": machineCode, - "online": true, - }) -} - +package controllers + +import ( + "encoding/json" + "strings" + "time" + + "server/models" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// ApiCursorEquipmentController 开放接口:登录器上报 Cursor 设备信息(无需登录) +type ApiCursorEquipmentController struct { + beego.Controller +} + +// cursorIpInfo 对应 ip-api.com 返回的 JSON 结构 +type cursorIpInfo struct { + Status string `json:"status"` + Country string `json:"country"` + CountryCode string `json:"countryCode"` + Region string `json:"region"` + RegionName string `json:"regionName"` + City string `json:"city"` + Zip string `json:"zip"` + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + Timezone string `json:"timezone"` + ISP string `json:"isp"` + Org string `json:"org"` + As string `json:"as"` + Query string `json:"query"` +} + +type cursorEquipmentReportPayload struct { + DeviceInfo string `json:"deviceInfo"` + DeviceInfoSnake string `json:"device_info"` + MachineCode string `json:"machineCode"` + MachineCodeSnake string `json:"machine_code"` + Status *int8 `json:"status"` + System string `json:"system"` + Version string `json:"version"` + BindAccount string `json:"bindAccount"` + BindAccountSnake string `json:"bind_account"` + OwnerUserID *uint64 `json:"ownerUserId"` + OwnerUserIDSnake *uint64 `json:"owner_user_id"` + OwnerUserName string `json:"ownerUserName"` + OwnerUserNameSnake string `json:"owner_user_name"` + ActivationTime string `json:"activationTime"` + ActivationTimeSnake string `json:"activation_time"` + ExpireTime string `json:"expireTime"` + ExpireTimeSnake string `json:"expire_time"` + Remark string `json:"remark"` + IpInfo *cursorIpInfo `json:"ipInfo"` +} + +type cursorEquipmentActivatePayload struct { + Code string `json:"code"` + ActivationCode string `json:"activationCode"` + ActivationCodeSnake string `json:"activation_code"` + DeviceInfo string `json:"deviceInfo"` + DeviceInfoSnake string `json:"device_info"` + MachineCode string `json:"machineCode"` + MachineCodeSnake string `json:"machine_code"` + System string `json:"system"` + Version string `json:"version"` + BindAccount string `json:"bindAccount"` + BindAccountSnake string `json:"bind_account"` + OwnerUserID *uint64 `json:"ownerUserId"` + OwnerUserIDSnake *uint64 `json:"owner_user_id"` + OwnerUserName string `json:"ownerUserName"` + OwnerUserNameSnake string `json:"owner_user_name"` + Remark string `json:"remark"` + IpInfo *cursorIpInfo `json:"ipInfo"` +} + +// cursorSaveIpLog 将 ipInfo 写入设备 IP 日志表(异步,失败不影响主流程) +func cursorSaveIpLog(equipmentID uint64, machineCode, source string, ip *cursorIpInfo) { + if ip == nil { + return + } + log := &models.PlatformCursorEquipmentIpLog{ + EquipmentID: equipmentID, + MachineCode: machineCode, + Source: source, + Status: ip.Status, + Country: ip.Country, + CountryCode: ip.CountryCode, + Region: ip.Region, + RegionName: ip.RegionName, + City: ip.City, + Zip: ip.Zip, + Lat: ip.Lat, + Lon: ip.Lon, + Timezone: ip.Timezone, + ISP: ip.ISP, + Org: ip.Org, + AsInfo: ip.As, + Query: ip.Query, + } + _, _ = models.Orm.Insert(log) +} + +func cursorFirstNonEmpty(values ...string) string { + for _, v := range values { + if s := strings.TrimSpace(v); s != "" { + return s + } + } + return "" +} + +func cursorStringPtr(value string) *string { + value = strings.TrimSpace(value) + if value == "" { + return nil + } + return &value +} + +func cursorParseTimePtr(value string) *time.Time { + value = strings.TrimSpace(value) + if value == "" { + return nil + } + + layouts := []string{ + time.RFC3339, + "2006-01-02 15:04:05", + "2006-01-02 15:04", + "2006-01-02", + } + for _, layout := range layouts { + if t, err := time.ParseInLocation(layout, value, time.Local); err == nil { + return &t + } + } + return nil +} + +func cursorValidStatus(status int8) bool { + return status == 0 || status == 1 || status == 2 || status == 3 +} + +func (c *ApiCursorEquipmentController) jsonResult(code int, msg string, data interface{}) { + resp := map[string]interface{}{"code": code, "msg": msg} + if data != nil { + resp["data"] = data + } + c.Data["json"] = resp + _ = c.ServeJSON() +} + +// Report POST /api/cursor/equipment/report +// +// JSON 示例: +// +// { +// "machineCode": "ABC-123", +// "deviceInfo": "CPU/RAM/磁盘等设备信息", +// "system": "Windows", +// "version": "1.0.0", +// "bindAccount": "user@example.com", +// "ownerUserId": 1, +// "ownerUserName": "张三", +// "activationTime": "2026-06-15 22:00:00", +// "expireTime": "2026-07-15 22:00:00", +// "remark": "登录器上报" +// } +// +// 兼容 snake_case 字段,例如 machine_code、device_info、bind_account。 +func (c *ApiCursorEquipmentController) Report() { + var p cursorEquipmentReportPayload + body := c.Ctx.Input.RequestBody + if len(body) > 0 { + if err := json.Unmarshal(body, &p); err != nil { + c.jsonResult(400, "参数错误", nil) + return + } + } + // 兼容 query string 参数 + if p.MachineCode == "" { + p.MachineCode = c.GetString("machineCode") + } + if p.MachineCodeSnake == "" { + p.MachineCodeSnake = c.GetString("machine_code") + } + if p.DeviceInfo == "" { + p.DeviceInfo = c.GetString("deviceInfo") + } + if p.DeviceInfoSnake == "" { + p.DeviceInfoSnake = c.GetString("device_info") + } + if p.System == "" { + p.System = c.GetString("system") + } + if p.Version == "" { + p.Version = c.GetString("version") + } + if p.BindAccount == "" { + p.BindAccount = c.GetString("bindAccount") + } + if p.BindAccountSnake == "" { + p.BindAccountSnake = c.GetString("bind_account") + } + if p.OwnerUserName == "" { + p.OwnerUserName = c.GetString("ownerUserName") + } + if p.OwnerUserNameSnake == "" { + p.OwnerUserNameSnake = c.GetString("owner_user_name") + } + if p.ActivationTime == "" { + p.ActivationTime = c.GetString("activationTime") + } + if p.ActivationTimeSnake == "" { + p.ActivationTimeSnake = c.GetString("activation_time") + } + if p.ExpireTime == "" { + p.ExpireTime = c.GetString("expireTime") + } + if p.ExpireTimeSnake == "" { + p.ExpireTimeSnake = c.GetString("expire_time") + } + if p.Remark == "" { + p.Remark = c.GetString("remark") + } + if p.Status == nil { + if s, err := c.GetInt8("status"); err == nil { + p.Status = &s + } + } + if p.OwnerUserID == nil { + if uid, err := c.GetUint64("ownerUserId"); err == nil && uid > 0 { + p.OwnerUserID = &uid + } + } + + machineCode := cursorFirstNonEmpty(p.MachineCode, p.MachineCodeSnake) + if machineCode == "" { + c.jsonResult(400, "缺少参数 machineCode/machine_code(机器码)", nil) + return + } + if len(machineCode) > 128 { + c.jsonResult(400, "机器码长度不能超过 128 个字符", nil) + return + } + + status := int8(0) + statusProvided := p.Status != nil + if statusProvided { + status = *p.Status + if !cursorValidStatus(status) { + c.jsonResult(400, "状态不合法,支持:0 未激活、1 激活中、2 已过期、3 已禁用", nil) + return + } + } + + deviceInfo := cursorFirstNonEmpty(p.DeviceInfo, p.DeviceInfoSnake) + system := cursorFirstNonEmpty(p.System) + version := cursorFirstNonEmpty(p.Version) + bindAccount := cursorFirstNonEmpty(p.BindAccount, p.BindAccountSnake) + ownerUserID := p.OwnerUserID + if ownerUserID == nil { + ownerUserID = p.OwnerUserIDSnake + } + ownerUserName := cursorFirstNonEmpty(p.OwnerUserName, p.OwnerUserNameSnake) + activationTime := cursorParseTimePtr(cursorFirstNonEmpty(p.ActivationTime, p.ActivationTimeSnake)) + expireTime := cursorParseTimePtr(cursorFirstNonEmpty(p.ExpireTime, p.ExpireTimeSnake)) + remark := cursorFirstNonEmpty(p.Remark) + + now := time.Now() + var row models.PlatformCursorEquipment + err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("machine_code", machineCode). + Filter("delete_time__isnull", true). + One(&row) + + created := false + if err == orm.ErrNoRows { + row = models.PlatformCursorEquipment{ + MachineCode: machineCode, + Status: status, + DeviceInfo: cursorStringPtr(deviceInfo), + System: cursorStringPtr(system), + Version: cursorStringPtr(version), + BindAccount: cursorStringPtr(bindAccount), + OwnerUserID: ownerUserID, + OwnerUserName: cursorStringPtr(ownerUserName), + ActivationTime: activationTime, + ExpireTime: expireTime, + Remark: cursorStringPtr(remark), + CreateTime: now, + } + id, insertErr := models.Orm.Insert(&row) + if insertErr != nil { + c.jsonResult(500, "设备信息保存失败", nil) + return + } + row.ID = uint64(id) + created = true + } else if err != nil { + c.jsonResult(500, "设备信息查询失败", nil) + return + } else { + update := map[string]interface{}{ + "device_info": cursorStringPtr(deviceInfo), + "system": cursorStringPtr(system), + "version": cursorStringPtr(version), + "bind_account": cursorStringPtr(bindAccount), + "owner_user_id": ownerUserID, + "owner_user_name": cursorStringPtr(ownerUserName), + "activation_time": activationTime, + "expire_time": expireTime, + "remark": cursorStringPtr(remark), + "update_time": now, + } + if statusProvided { + update["status"] = status + row.Status = status + } + + if _, updateErr := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", row.ID). + Update(update); updateErr != nil { + c.jsonResult(500, "设备信息更新失败", nil) + return + } + + row.DeviceInfo = cursorStringPtr(deviceInfo) + row.System = cursorStringPtr(system) + row.Version = cursorStringPtr(version) + row.BindAccount = cursorStringPtr(bindAccount) + row.OwnerUserID = ownerUserID + row.OwnerUserName = cursorStringPtr(ownerUserName) + row.ActivationTime = activationTime + row.ExpireTime = expireTime + row.Remark = cursorStringPtr(remark) + row.UpdateTime = &now + } + + // 记录 IP 日志 + cursorSaveIpLog(row.ID, machineCode, "report", p.IpInfo) + + // 查询该设备最新激活码,补全激活时间和到期时间 + var retActivationTime interface{} = row.ActivationTime + var retExpireTime interface{} = row.ExpireTime + + var latestCode models.PlatformCursorActivationCode + cond := orm.NewCondition(). + And("delete_time__isnull", true). + AndCond(orm.NewCondition(). + Or("bind_device_id", row.ID). + Or("machine_code", row.MachineCode)) + if err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + SetCond(cond). + OrderBy("-activated_at", "-id"). + One(&latestCode); err == nil { + if latestCode.ActivatedAt != nil { + retActivationTime = latestCode.ActivatedAt + } + if latestCode.ExpiredAt != nil { + retExpireTime = latestCode.ExpiredAt + } + } + + c.jsonResult(200, "success", map[string]interface{}{ + "id": row.ID, + "machineCode": row.MachineCode, + "status": row.Status, + "created": created, + "activationTime": retActivationTime, + "expireTime": retExpireTime, + }) +} + +// ActivateByCode POST /api/cursor/equipment/activateByCode +// +// 设备端使用激活码激活/续期 Cursor 设备(无需登录)。 +// +// JSON 示例: +// +// { +// "activationCode": "CUR-XXXXXXXX", +// "machineCode": "ABC-123", +// "deviceInfo": "CPU/RAM/磁盘等设备信息", +// "system": "Windows", +// "version": "1.0.0", +// "bindAccount": "user@example.com", +// "ownerUserId": 1, +// "ownerUserName": "张三", +// "remark": "登录器激活" +// } +// +// 兼容字段: +// - 激活码:activationCode / activation_code / code +// - 机器码:machineCode / machine_code +// - 设备信息:deviceInfo / device_info +// - 绑定账号:bindAccount / bind_account +func (c *ApiCursorEquipmentController) ActivateByCode() { + var p cursorEquipmentActivatePayload + if err := json.Unmarshal(c.Ctx.Input.RequestBody, &p); err != nil { + c.jsonResult(400, "参数错误", nil) + return + } + + code := cursorFirstNonEmpty(p.ActivationCode, p.ActivationCodeSnake, p.Code) + if code == "" { + c.jsonResult(400, "缺少参数 activationCode/activation_code/code(激活码)", nil) + return + } + if len(code) > 128 { + c.jsonResult(400, "激活码长度不能超过 128 个字符", nil) + return + } + + machineCode := cursorFirstNonEmpty(p.MachineCode, p.MachineCodeSnake) + if machineCode == "" { + c.jsonResult(400, "缺少参数 machineCode/machine_code(机器码)", nil) + return + } + if len(machineCode) > 128 { + c.jsonResult(400, "机器码长度不能超过 128 个字符", nil) + return + } + + deviceInfo := cursorFirstNonEmpty(p.DeviceInfo, p.DeviceInfoSnake) + system := cursorFirstNonEmpty(p.System) + version := cursorFirstNonEmpty(p.Version) + bindAccount := cursorFirstNonEmpty(p.BindAccount, p.BindAccountSnake) + ownerUserID := p.OwnerUserID + if ownerUserID == nil { + ownerUserID = p.OwnerUserIDSnake + } + ownerUserName := cursorFirstNonEmpty(p.OwnerUserName, p.OwnerUserNameSnake) + remark := cursorFirstNonEmpty(p.Remark) + + now := time.Now() + + var activationCode models.PlatformCursorActivationCode + err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + Filter("code", code). + Filter("delete_time__isnull", true). + One(&activationCode) + if err == orm.ErrNoRows { + c.jsonResult(404, "激活码不存在", nil) + return + } + if err != nil { + c.jsonResult(500, "激活码查询失败", nil) + return + } + + if activationCode.Status == 3 { + c.jsonResult(403, "激活码已禁用", nil) + return + } + if activationCode.Status == 2 || (activationCode.ExpiredAt != nil && activationCode.ExpiredAt.Before(now)) { + _, _ = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + Filter("id", activationCode.ID). + Update(map[string]interface{}{"status": int8(2), "update_time": now}) + c.jsonResult(410, "激活码已过期", nil) + return + } + + if activationCode.Status == 1 { + if activationCode.MachineCode == nil || strings.TrimSpace(*activationCode.MachineCode) != machineCode { + c.jsonResult(409, "激活码已被其他设备使用", nil) + return + } + + if activationCode.ExpiredAt != nil && activationCode.ExpiredAt.Before(now) { + _, _ = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + Filter("id", activationCode.ID). + Update(map[string]interface{}{"status": int8(2), "update_time": now}) + c.jsonResult(410, "激活码已过期", nil) + return + } + + c.jsonResult(200, "success", map[string]interface{}{ + "activated": true, + "reused": true, + "activationId": activationCode.ID, + "deviceId": activationCode.BindDeviceID, + "machineCode": machineCode, + "status": 1, + "durationDays": activationCode.DurationDays, + "activatedAt": activationCode.ActivatedAt, + "expireTime": activationCode.ExpiredAt, + "expiredAt": activationCode.ExpiredAt, + }) + return + } + + var device models.PlatformCursorEquipment + deviceErr := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("machine_code", machineCode). + Filter("delete_time__isnull", true). + One(&device) + + created := false + if deviceErr != nil && deviceErr != orm.ErrNoRows { + c.jsonResult(500, "设备信息查询失败", nil) + return + } + if deviceErr == nil && device.Status == 3 { + c.jsonResult(403, "设备已禁用,无法激活", nil) + return + } + + baseTime := now + if deviceErr == nil && device.ExpireTime != nil && device.ExpireTime.After(now) { + baseTime = *device.ExpireTime + } + + var expireTime *time.Time + if activationCode.DurationDays > 0 { + t := baseTime.AddDate(0, 0, activationCode.DurationDays) + expireTime = &t + } + + txOrm, err := models.Orm.Begin() + if err != nil { + c.jsonResult(500, "开启事务失败", nil) + return + } + + rollback := true + defer func() { + if rollback { + _ = txOrm.Rollback() + } + }() + + if deviceErr == orm.ErrNoRows { + device = models.PlatformCursorEquipment{ + MachineCode: machineCode, + Status: 1, + DeviceInfo: cursorStringPtr(deviceInfo), + System: cursorStringPtr(system), + Version: cursorStringPtr(version), + BindAccount: cursorStringPtr(bindAccount), + OwnerUserID: ownerUserID, + OwnerUserName: cursorStringPtr(ownerUserName), + ActivationTime: &now, + ExpireTime: expireTime, + Remark: cursorStringPtr(remark), + CreateTime: now, + } + id, insertErr := txOrm.Insert(&device) + if insertErr != nil { + c.jsonResult(500, "设备信息保存失败", nil) + return + } + device.ID = uint64(id) + created = true + } else { + if bindAccount == "" && device.BindAccount != nil { + bindAccount = *device.BindAccount + } + if ownerUserID == nil { + ownerUserID = device.OwnerUserID + } + if ownerUserName == "" && device.OwnerUserName != nil { + ownerUserName = *device.OwnerUserName + } + + _, updateErr := txOrm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", device.ID). + Update(map[string]interface{}{ + "device_info": cursorStringPtr(deviceInfo), + "system": cursorStringPtr(system), + "version": cursorStringPtr(version), + "bind_account": cursorStringPtr(bindAccount), + "owner_user_id": ownerUserID, + "owner_user_name": cursorStringPtr(ownerUserName), + "activation_time": now, + "expire_time": expireTime, + "status": int8(1), + "remark": cursorStringPtr(remark), + "update_time": now, + }) + if updateErr != nil { + c.jsonResult(500, "设备信息更新失败", nil) + return + } + } + + codeUpdateCount, updateCodeErr := txOrm.QueryTable(new(models.PlatformCursorActivationCode)). + Filter("id", activationCode.ID). + Filter("status", int8(0)). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{ + "status": int8(1), + "bind_account": cursorStringPtr(bindAccount), + "bind_device_id": device.ID, + "machine_code": machineCode, + "device_info": cursorStringPtr(deviceInfo), + "owner_user_id": ownerUserID, + "owner_user_name": cursorStringPtr(ownerUserName), + "activated_at": now, + "expired_at": expireTime, + "remark": cursorStringPtr(remark), + "update_time": now, + }) + if updateCodeErr != nil { + c.jsonResult(500, "激活码绑定失败", nil) + return + } + if codeUpdateCount == 0 { + c.jsonResult(409, "激活码状态已变化,请重新查询后再试", nil) + return + } + + if err := txOrm.Commit(); err != nil { + c.jsonResult(500, "提交事务失败", nil) + return + } + rollback = false + + // 记录 IP 日志 + cursorSaveIpLog(device.ID, machineCode, "activateByCode", p.IpInfo) + + c.jsonResult(200, "success", map[string]interface{}{ + "activated": true, + "reused": false, + "created": created, + "activationId": activationCode.ID, + "deviceId": device.ID, + "machineCode": machineCode, + "status": 1, + "durationDays": activationCode.DurationDays, + "activationAt": now, + "activatedAt": now, + "expireTime": expireTime, + "expiredAt": expireTime, + }) +} + +type cursorHeartbeatPayload struct { + MachineCode string `json:"machineCode"` + MachineCodeSnake string `json:"machine_code"` +} + +// Heartbeat POST /api/cursor/equipment/heartbeat +// +// 客户端心跳接口(无需登录),用于上报在线状态。 +// +// JSON 示例: +// +// { +// "machineCode": "ABC-123" +// } +func (c *ApiCursorEquipmentController) Heartbeat() { + var p cursorHeartbeatPayload + body := c.Ctx.Input.RequestBody + if len(body) > 0 { + if err := json.Unmarshal(body, &p); err != nil { + c.jsonResult(400, "参数错误", nil) + return + } + } + + machineCode := cursorFirstNonEmpty(p.MachineCode, p.MachineCodeSnake) + if machineCode == "" { + machineCode = c.GetString("machineCode") + } + if machineCode == "" { + machineCode = c.GetString("machine_code") + } + + if machineCode == "" { + c.jsonResult(400, "缺少参数 machineCode/machine_code(机器码)", nil) + return + } + + now := time.Now() + // 查询设备是否存在 + var row models.PlatformCursorEquipment + err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("machine_code", machineCode). + Filter("delete_time__isnull", true). + One(&row) + + if err == orm.ErrNoRows { + // 设备不存在,可能是第一次运行心跳,也可以允许在此处静默创建,或者返回 404 让客户端先进行 report + // 为了鲁棒性,如果设备未上报过,我们可以直接创建一个基础设备记录 + row = models.PlatformCursorEquipment{ + MachineCode: machineCode, + Status: 0, // 未激活 + LastHeartbeatAt: &now, + CreateTime: now, + } + if _, insertErr := models.Orm.Insert(&row); insertErr != nil { + c.jsonResult(500, "保存设备心跳失败", nil) + return + } + } else if err != nil { + c.jsonResult(500, "设备查询失败", nil) + return + } else { + // 更新最后心跳时间 + if _, updateErr := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", row.ID). + Update(map[string]interface{}{ + "last_heartbeat_at": &now, + "update_time": now, + }); updateErr != nil { + c.jsonResult(500, "更新设备心跳失败", nil) + return + } + } + + c.jsonResult(200, "success", map[string]interface{}{ + "machineCode": machineCode, + "online": true, + }) +} + diff --git a/go/controllers/api_getcard.go b/go/controllers/api_getcard.go index 2abb825..5ab9220 100644 --- a/go/controllers/api_getcard.go +++ b/go/controllers/api_getcard.go @@ -1,312 +1,312 @@ -package controllers - -import ( - "fmt" - "strconv" - "strings" - "time" - - "server/models" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// ApiGetCardController 对外提卡接口(无需登录) -// GET /api/getcard?type=xianyu&module=cursor -type ApiGetCardController struct { - beego.Controller -} - -// validPlatformTypes 支持的来源平台 -var validPlatformTypes = map[string]bool{ - "xianyu": true, - "pinduoduo": true, - "jingdong": true, - "douyin": true, - "local": true, - "xubei": true, -} - -// validModules 支持的号池模块 -var validModules = map[string]bool{ - "cursor": true, - "windsurf": true, - "krio": true, - "codex": true, -} - -func (c *ApiGetCardController) cardErr(_ int, _ int, msg string) { - c.Ctx.Output.SetStatus(200) - c.Ctx.Output.Header("Content-Type", "text/plain; charset=utf-8") - _ = c.Ctx.Output.Body([]byte("error:" + msg)) -} - -func (c *ApiGetCardController) cardOK(text string) { - c.Ctx.Output.SetStatus(200) - c.Ctx.Output.Header("Content-Type", "text/plain; charset=utf-8") - _ = c.Ctx.Output.Body([]byte(text)) -} - -// GetCard 提取一张卡(不可重复提取) -// GET /api/getcard?type=xianyu&module=cursor&data_type=tk -// -// 参数: -// - type (必填) 来源平台:xianyu / taobao / pinduoduo / jingdong / local / xubei -// - module (必填) 号池模块:cursor / windsurf / krio / codex -// - data_type (可选) 账号类型:account / tk / account_tk,不传则取任意未提取的 -// - id/start_id/current_id (可选) 起始 ID:从该 ID 开始向后提取,避免传 896 却取到 892 -func (c *ApiGetCardController) GetCard() { - platform := c.GetString("type") - module := c.GetString("module") - dataType := c.GetString("data_type") - startID, err := c.readOptionalStartID() - if err != nil { - c.cardErr(400, 400, err.Error()) - return - } - - // 读取机器码/MAC - machineCode := strings.TrimSpace(c.GetString("machine_code")) - if machineCode == "" { - machineCode = strings.TrimSpace(c.GetString("machineCode")) - } - if machineCode == "" { - machineCode = strings.TrimSpace(c.GetString("mac")) - } - - // 参数校验 - if platform == "" { - c.cardErr(400, 400, "缺少参数 type(来源平台)") - return - } - if !validPlatformTypes[platform] { - c.cardErr(400, 400, fmt.Sprintf("不支持的平台类型: %s,支持: xianyu/taobao/pinduoduo/jingdong/local/xubei", platform)) - return - } - if module == "" { - c.cardErr(400, 400, "缺少参数 module(号池模块)") - return - } - if !validModules[module] { - c.cardErr(400, 400, fmt.Sprintf("不支持的模块: %s,支持: cursor/windsurf/krio/codex", module)) - return - } - if dataType != "" && !isValidPoolType(dataType) { - c.cardErr(400, 400, "data_type 不合法,支持: account/tk/account_tk") - return - } - - now := time.Now() - - switch module { - case "cursor": - c.extractCursor(platform, dataType, startID, now, machineCode) - case "windsurf": - c.extractWindsurf(platform, dataType, startID, now) - case "krio": - c.extractKrio(platform, dataType, startID, now) - case "codex": - c.extractCodex(platform, dataType, startID, now) - } -} - -func (c *ApiGetCardController) readOptionalStartID() (uint64, error) { - raw := strings.TrimSpace(c.GetString("id")) - if raw == "" { - raw = strings.TrimSpace(c.GetString("start_id")) - } - if raw == "" { - raw = strings.TrimSpace(c.GetString("current_id")) - } - if raw == "" { - return 0, nil - } - - id, err := strconv.ParseUint(raw, 10, 64) - if err != nil || id == 0 { - return 0, fmt.Errorf("id/start_id/current_id 必须是大于 0 的整数") - } - return id, nil -} - -func (c *ApiGetCardController) extractCursor(platform, dataType string, startID uint64, now time.Time, machineCode string) { - // 优先查询该机器码是否已经绑定过未删除的卡密 - if machineCode != "" { - var existing models.PlatformAccountPoolCursor - err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - Filter("machine_code", machineCode). - Filter("delete_time__isnull", true). - Exclude("is_used", 0). - OrderBy("-id"). - Limit(1). - One(&existing) - if err == nil { - // 直接返回已绑定的卡密信息 - c.cardOK(buildCardResult(&existing.Account, &existing.Password, existing.Token, existing.DataType)) - return - } - } - - c.extractWithProbe("cursor", platform, dataType, now, machineCode, func() (uint64, *string, *string, string, string, *int8, error) { - var row models.PlatformAccountPoolCursor - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - Filter("is_extracted", 0). - Filter("delete_time__isnull", true) - if startID > 0 { - qs = qs.Filter("id__gte", startID) - } - if dataType != "" { - qs = qs.Filter("data_type", dataType) - } - if err := qs.OrderBy("id").One(&row); err != nil { - return 0, nil, nil, "", "", nil, err - } - return row.ID, &row.Account, &row.Password, row.Token, row.DataType, row.IsUsed, nil - }) -} - -func (c *ApiGetCardController) extractWindsurf(platform, dataType string, startID uint64, now time.Time) { - c.extractWithProbe("windsurf", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { - var row models.PlatformAccountPoolWindsurf - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)). - Filter("is_extracted", 0). - Filter("delete_time__isnull", true) - if startID > 0 { - qs = qs.Filter("id__gte", startID) - } - if dataType != "" { - qs = qs.Filter("data_type", dataType) - } - if err := qs.OrderBy("id").One(&row); err != nil { - return 0, nil, nil, "", "", nil, err - } - return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil - }) -} - -func (c *ApiGetCardController) extractKrio(platform, dataType string, startID uint64, now time.Time) { - c.extractWithProbe("krio", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { - var row models.PlatformAccountPoolKiro - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)). - Filter("is_extracted", 0). - Filter("delete_time__isnull", true) - if startID > 0 { - qs = qs.Filter("id__gte", startID) - } - if dataType != "" { - qs = qs.Filter("data_type", dataType) - } - if err := qs.OrderBy("id").One(&row); err != nil { - return 0, nil, nil, "", "", nil, err - } - return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil - }) -} - -func (c *ApiGetCardController) extractCodex(platform, dataType string, startID uint64, now time.Time) { - c.extractWithProbe("codex", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { - var row models.PlatformAccountPoolCodex - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)). - Filter("is_extracted", 0). - Filter("delete_time__isnull", true) - if startID > 0 { - qs = qs.Filter("id__gte", startID) - } - if dataType != "" { - qs = qs.Filter("data_type", dataType) - } - if err := qs.OrderBy("id").One(&row); err != nil { - return 0, nil, nil, "", "", nil, err - } - return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil - }) -} - -type poolRowFetcher func() (id uint64, account, password *string, token, rowDataType string, isUsed *int8, err error) - -// extractWithProbe 按 id 顺序提取并探测 Token 可用性;不可用则标记已提取并继续下一条。 -func (c *ApiGetCardController) extractWithProbe( - module, platform, dataType string, - now time.Time, - machineCode string, - fetch poolRowFetcher, -) { - for { - id, account, password, token, rowDataType, isUsed, err := fetch() - if err != nil { - if err == orm.ErrNoRows { - c.cardErr(404, 404, "暂无可用卡密") - } else { - c.cardErr(500, 500, "查询失败") - } - return - } - - tableName := poolTableName(module) - if tableName == "" { - c.cardErr(500, 500, "无效模块") - return - } - - params := map[string]interface{}{ - "is_extracted": 1, - "extracted_time": now, - "extracted_platform": platform, - "update_time": now, - } - if module == "cursor" && machineCode != "" { - params["machine_code"] = machineCode - } - - _, err = models.Orm.QueryTable(tableName). - Filter("id", id). - Update(params) - if err != nil { - c.cardErr(500, 500, "提取失败") - return - } - - // 已有探测结论:可用则直接返回,不可用则继续下一条。 - if known, available := poolIsUsedAvailable(isUsed); known { - if available { - c.cardOK(buildCardResult(account, password, token, rowDataType)) - return - } - if module == "cursor" && machineCode != "" { - _, _ = models.Orm.QueryTable(tableName).Filter("id", id).Update(map[string]interface{}{"machine_code": "", "update_time": time.Now()}) - } - continue - } - - if !poolProbeToken(module, rowDataType, token, id) { - if module == "cursor" && machineCode != "" { - _, _ = models.Orm.QueryTable(tableName).Filter("id", id).Update(map[string]interface{}{"machine_code": "", "update_time": time.Now()}) - } - continue - } - - c.cardOK(buildCardResult(account, password, token, rowDataType)) - return - } -} - -// buildCardResult 根据账号类型返回格式化字符串 -func buildCardResult(account, password *string, token string, dataType string) string { - acc := "" - pwd := "" - if account != nil { - acc = *account - } - if password != nil { - pwd = *password - } - switch dataType { - case "account": - return fmt.Sprintf("账号:%s / 密码:%s", acc, pwd) - case "account_tk": - return fmt.Sprintf("账号:%s / 密码:%s / Token:%s", acc, pwd, token) - default: // tk - return token - } -} +package controllers + +import ( + "fmt" + "strconv" + "strings" + "time" + + "server/models" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// ApiGetCardController 对外提卡接口(无需登录) +// GET /api/getcard?type=xianyu&module=cursor +type ApiGetCardController struct { + beego.Controller +} + +// validPlatformTypes 支持的来源平台 +var validPlatformTypes = map[string]bool{ + "xianyu": true, + "pinduoduo": true, + "jingdong": true, + "douyin": true, + "local": true, + "xubei": true, +} + +// validModules 支持的号池模块 +var validModules = map[string]bool{ + "cursor": true, + "windsurf": true, + "krio": true, + "codex": true, +} + +func (c *ApiGetCardController) cardErr(_ int, _ int, msg string) { + c.Ctx.Output.SetStatus(200) + c.Ctx.Output.Header("Content-Type", "text/plain; charset=utf-8") + _ = c.Ctx.Output.Body([]byte("error:" + msg)) +} + +func (c *ApiGetCardController) cardOK(text string) { + c.Ctx.Output.SetStatus(200) + c.Ctx.Output.Header("Content-Type", "text/plain; charset=utf-8") + _ = c.Ctx.Output.Body([]byte(text)) +} + +// GetCard 提取一张卡(不可重复提取) +// GET /api/getcard?type=xianyu&module=cursor&data_type=tk +// +// 参数: +// - type (必填) 来源平台:xianyu / taobao / pinduoduo / jingdong / local / xubei +// - module (必填) 号池模块:cursor / windsurf / krio / codex +// - data_type (可选) 账号类型:account / tk / account_tk,不传则取任意未提取的 +// - id/start_id/current_id (可选) 起始 ID:从该 ID 开始向后提取,避免传 896 却取到 892 +func (c *ApiGetCardController) GetCard() { + platform := c.GetString("type") + module := c.GetString("module") + dataType := c.GetString("data_type") + startID, err := c.readOptionalStartID() + if err != nil { + c.cardErr(400, 400, err.Error()) + return + } + + // 读取机器码/MAC + machineCode := strings.TrimSpace(c.GetString("machine_code")) + if machineCode == "" { + machineCode = strings.TrimSpace(c.GetString("machineCode")) + } + if machineCode == "" { + machineCode = strings.TrimSpace(c.GetString("mac")) + } + + // 参数校验 + if platform == "" { + c.cardErr(400, 400, "缺少参数 type(来源平台)") + return + } + if !validPlatformTypes[platform] { + c.cardErr(400, 400, fmt.Sprintf("不支持的平台类型: %s,支持: xianyu/taobao/pinduoduo/jingdong/local/xubei", platform)) + return + } + if module == "" { + c.cardErr(400, 400, "缺少参数 module(号池模块)") + return + } + if !validModules[module] { + c.cardErr(400, 400, fmt.Sprintf("不支持的模块: %s,支持: cursor/windsurf/krio/codex", module)) + return + } + if dataType != "" && !isValidPoolType(dataType) { + c.cardErr(400, 400, "data_type 不合法,支持: account/tk/account_tk") + return + } + + now := time.Now() + + switch module { + case "cursor": + c.extractCursor(platform, dataType, startID, now, machineCode) + case "windsurf": + c.extractWindsurf(platform, dataType, startID, now) + case "krio": + c.extractKrio(platform, dataType, startID, now) + case "codex": + c.extractCodex(platform, dataType, startID, now) + } +} + +func (c *ApiGetCardController) readOptionalStartID() (uint64, error) { + raw := strings.TrimSpace(c.GetString("id")) + if raw == "" { + raw = strings.TrimSpace(c.GetString("start_id")) + } + if raw == "" { + raw = strings.TrimSpace(c.GetString("current_id")) + } + if raw == "" { + return 0, nil + } + + id, err := strconv.ParseUint(raw, 10, 64) + if err != nil || id == 0 { + return 0, fmt.Errorf("id/start_id/current_id 必须是大于 0 的整数") + } + return id, nil +} + +func (c *ApiGetCardController) extractCursor(platform, dataType string, startID uint64, now time.Time, machineCode string) { + // 优先查询该机器码是否已经绑定过未删除的卡密 + if machineCode != "" { + var existing models.PlatformAccountPoolCursor + err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("machine_code", machineCode). + Filter("delete_time__isnull", true). + Exclude("is_used", 0). + OrderBy("-id"). + Limit(1). + One(&existing) + if err == nil { + // 直接返回已绑定的卡密信息 + c.cardOK(buildCardResult(&existing.Account, &existing.Password, existing.Token, existing.DataType)) + return + } + } + + c.extractWithProbe("cursor", platform, dataType, now, machineCode, func() (uint64, *string, *string, string, string, *int8, error) { + var row models.PlatformAccountPoolCursor + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("is_extracted", 0). + Filter("delete_time__isnull", true) + if startID > 0 { + qs = qs.Filter("id__gte", startID) + } + if dataType != "" { + qs = qs.Filter("data_type", dataType) + } + if err := qs.OrderBy("id").One(&row); err != nil { + return 0, nil, nil, "", "", nil, err + } + return row.ID, &row.Account, &row.Password, row.Token, row.DataType, row.IsUsed, nil + }) +} + +func (c *ApiGetCardController) extractWindsurf(platform, dataType string, startID uint64, now time.Time) { + c.extractWithProbe("windsurf", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { + var row models.PlatformAccountPoolWindsurf + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)). + Filter("is_extracted", 0). + Filter("delete_time__isnull", true) + if startID > 0 { + qs = qs.Filter("id__gte", startID) + } + if dataType != "" { + qs = qs.Filter("data_type", dataType) + } + if err := qs.OrderBy("id").One(&row); err != nil { + return 0, nil, nil, "", "", nil, err + } + return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil + }) +} + +func (c *ApiGetCardController) extractKrio(platform, dataType string, startID uint64, now time.Time) { + c.extractWithProbe("krio", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { + var row models.PlatformAccountPoolKiro + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)). + Filter("is_extracted", 0). + Filter("delete_time__isnull", true) + if startID > 0 { + qs = qs.Filter("id__gte", startID) + } + if dataType != "" { + qs = qs.Filter("data_type", dataType) + } + if err := qs.OrderBy("id").One(&row); err != nil { + return 0, nil, nil, "", "", nil, err + } + return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil + }) +} + +func (c *ApiGetCardController) extractCodex(platform, dataType string, startID uint64, now time.Time) { + c.extractWithProbe("codex", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { + var row models.PlatformAccountPoolCodex + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)). + Filter("is_extracted", 0). + Filter("delete_time__isnull", true) + if startID > 0 { + qs = qs.Filter("id__gte", startID) + } + if dataType != "" { + qs = qs.Filter("data_type", dataType) + } + if err := qs.OrderBy("id").One(&row); err != nil { + return 0, nil, nil, "", "", nil, err + } + return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil + }) +} + +type poolRowFetcher func() (id uint64, account, password *string, token, rowDataType string, isUsed *int8, err error) + +// extractWithProbe 按 id 顺序提取并探测 Token 可用性;不可用则标记已提取并继续下一条。 +func (c *ApiGetCardController) extractWithProbe( + module, platform, dataType string, + now time.Time, + machineCode string, + fetch poolRowFetcher, +) { + for { + id, account, password, token, rowDataType, isUsed, err := fetch() + if err != nil { + if err == orm.ErrNoRows { + c.cardErr(404, 404, "暂无可用卡密") + } else { + c.cardErr(500, 500, "查询失败") + } + return + } + + tableName := poolTableName(module) + if tableName == "" { + c.cardErr(500, 500, "无效模块") + return + } + + params := map[string]interface{}{ + "is_extracted": 1, + "extracted_time": now, + "extracted_platform": platform, + "update_time": now, + } + if module == "cursor" && machineCode != "" { + params["machine_code"] = machineCode + } + + _, err = models.Orm.QueryTable(tableName). + Filter("id", id). + Update(params) + if err != nil { + c.cardErr(500, 500, "提取失败") + return + } + + // 已有探测结论:可用则直接返回,不可用则继续下一条。 + if known, available := poolIsUsedAvailable(isUsed); known { + if available { + c.cardOK(buildCardResult(account, password, token, rowDataType)) + return + } + if module == "cursor" && machineCode != "" { + _, _ = models.Orm.QueryTable(tableName).Filter("id", id).Update(map[string]interface{}{"machine_code": "", "update_time": time.Now()}) + } + continue + } + + if !poolProbeToken(module, rowDataType, token, id) { + if module == "cursor" && machineCode != "" { + _, _ = models.Orm.QueryTable(tableName).Filter("id", id).Update(map[string]interface{}{"machine_code": "", "update_time": time.Now()}) + } + continue + } + + c.cardOK(buildCardResult(account, password, token, rowDataType)) + return + } +} + +// buildCardResult 根据账号类型返回格式化字符串 +func buildCardResult(account, password *string, token string, dataType string) string { + acc := "" + pwd := "" + if account != nil { + acc = *account + } + if password != nil { + pwd = *password + } + switch dataType { + case "account": + return fmt.Sprintf("账号:%s / 密码:%s", acc, pwd) + case "account_tk": + return fmt.Sprintf("账号:%s / 密码:%s / Token:%s", acc, pwd, token) + default: // tk + return token + } +} diff --git a/go/controllers/api_reminder.go b/go/controllers/api_reminder.go index 3230026..7ac822b 100644 --- a/go/controllers/api_reminder.go +++ b/go/controllers/api_reminder.go @@ -1,109 +1,109 @@ -package controllers - -import ( - "time" - - "server/models" - - beego "github.com/beego/beego/v2/server/web" -) - -type ApiReminderController struct { - beego.Controller -} - -// AckReminder GET /api/schedule/reminder/ack -// 邮件/Bark 客户端访问此接口进行提醒确认 -func (c *ApiReminderController) AckReminder() { - token := c.GetString("token") - if token == "" { - c.Ctx.Output.SetStatus(400) - _ = c.Ctx.Output.Body([]byte("Invalid request: missing token")) - return - } - - var reminder models.PlatformScheduleReminder - err := models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("ack_token", token). - Filter("is_deleted", 0). - One(&reminder) - if err != nil { - c.Ctx.Output.SetStatus(404) - _ = c.Ctx.Output.Body([]byte("Error: reminder task not found or token has expired")) - return - } - - if reminder.AckStatus == 1 { - // 已经确认过了,直接显示已确认成功的 HTML - c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8") - _ = c.Ctx.Output.Body([]byte(` - - - - - 确认收到提醒 - - - -
-

提示

-

该日程提醒在此之前已确认过了。

-

无需重复点击,感谢您的使用!

-
- - - `)) - return - } - - // 更新确认状态为已确认,置 remind_status 为已结束(2) - now := time.Now() - reminder.AckStatus = 1 - reminder.AckTime = &now - reminder.RemindStatus = 2 - reminder.UpdateTime = now - - _, err = models.Orm.Update(&reminder, "AckStatus", "AckTime", "RemindStatus", "UpdateTime") - if err != nil { - c.Ctx.Output.SetStatus(500) - _ = c.Ctx.Output.Body([]byte("Database error, please try again later")) - return - } - - // 统一关闭该日程下的所有其他待提醒/提醒中渠道,防止重复打扰 - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("ScheduleID", reminder.ScheduleID). - Filter("RemindStatus__in", 0, 1). - Update(map[string]interface{}{ - "RemindStatus": int8(2), - "UpdateTime": now, - }) - - // 成功确认 - c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8") - _ = c.Ctx.Output.Body([]byte(` - - - - - 确认成功 - - - -
-

确认成功

-

您已成功确认收到该日程提醒!

-

系统已停止向您重复推送,感谢您的配合。

-
- - - `)) -} +package controllers + +import ( + "time" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +type ApiReminderController struct { + beego.Controller +} + +// AckReminder GET /api/schedule/reminder/ack +// 邮件/Bark 客户端访问此接口进行提醒确认 +func (c *ApiReminderController) AckReminder() { + token := c.GetString("token") + if token == "" { + c.Ctx.Output.SetStatus(400) + _ = c.Ctx.Output.Body([]byte("Invalid request: missing token")) + return + } + + var reminder models.PlatformScheduleReminder + err := models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("ack_token", token). + Filter("is_deleted", 0). + One(&reminder) + if err != nil { + c.Ctx.Output.SetStatus(404) + _ = c.Ctx.Output.Body([]byte("Error: reminder task not found or token has expired")) + return + } + + if reminder.AckStatus == 1 { + // 已经确认过了,直接显示已确认成功的 HTML + c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8") + _ = c.Ctx.Output.Body([]byte(` + + + + + 确认收到提醒 + + + +
+

提示

+

该日程提醒在此之前已确认过了。

+

无需重复点击,感谢您的使用!

+
+ + + `)) + return + } + + // 更新确认状态为已确认,置 remind_status 为已结束(2) + now := time.Now() + reminder.AckStatus = 1 + reminder.AckTime = &now + reminder.RemindStatus = 2 + reminder.UpdateTime = now + + _, err = models.Orm.Update(&reminder, "AckStatus", "AckTime", "RemindStatus", "UpdateTime") + if err != nil { + c.Ctx.Output.SetStatus(500) + _ = c.Ctx.Output.Body([]byte("Database error, please try again later")) + return + } + + // 统一关闭该日程下的所有其他待提醒/提醒中渠道,防止重复打扰 + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("ScheduleID", reminder.ScheduleID). + Filter("RemindStatus__in", 0, 1). + Update(map[string]interface{}{ + "RemindStatus": int8(2), + "UpdateTime": now, + }) + + // 成功确认 + c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8") + _ = c.Ctx.Output.Body([]byte(` + + + + + 确认成功 + + + +
+

确认成功

+

您已成功确认收到该日程提醒!

+

系统已停止向您重复推送,感谢您的配合。

+
+ + + `)) +} diff --git a/go/controllers/api_software_upgrade.go b/go/controllers/api_software_upgrade.go index c4422ac..1d81655 100644 --- a/go/controllers/api_software_upgrade.go +++ b/go/controllers/api_software_upgrade.go @@ -42,10 +42,17 @@ func (c *ApiSoftwareUpgradeController) Check() { scheme, host := services.PublicRequestBaseURL(&c.Controller) dl := services.ResolveSoftwareDownloadURL(scheme, host, row.DownloadURL, row.FileID) + dls := services.ResolveSoftwareDownloadURLs(scheme, host, row.DownloadURLs) + if platform := strings.ToLower(strings.TrimSpace(c.GetString("platform"))); platform != "" { + if platformURL := strings.TrimSpace(dls[platform]); platformURL != "" { + dl = platformURL + } + } data := map[string]interface{}{ "latestVersion": latest, "downloadUrl": dl, + "downloadUrls": dls, "forceUpdate": row.ForceUpdate == 1, "releaseNotes": "", } diff --git a/go/controllers/backend_admin_user.go b/go/controllers/backend_admin_user.go index 179a645..e98e68a 100644 --- a/go/controllers/backend_admin_user.go +++ b/go/controllers/backend_admin_user.go @@ -1,605 +1,605 @@ -package controllers - -import ( - "encoding/json" - "io" - "strconv" - "strings" - - "server/models" - "server/pkg/passwordutil" - "server/services" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -type BackendAdminUserController struct { - beego.Controller -} - -type backendUserInfoDTO struct { - ID uint64 `json:"id"` - Tid uint64 `json:"tid"` - Uid uint64 `json:"uid"` - Account *string `json:"account"` - Name *string `json:"name"` - Phone *string `json:"phone"` - Email *string `json:"email"` - Sex uint8 `json:"sex"` - Birth *string `json:"birth"` - IsDefault int8 `json:"is_default"` - Status int8 `json:"status"` - Remark *string `json:"remark"` - CreateTime string `json:"create_time"` - UpdateTime *string `json:"update_time"` - TenantName string `json:"tenant_name"` - TenantCode string `json:"tenant_code"` -} - -type backendTenantUserPayload struct { - Tid uint64 `json:"tid"` - Uid uint64 `json:"uid"` - Account *string `json:"account"` - Name *string `json:"name"` - Phone *string `json:"phone"` - Email *string `json:"email"` - Sex *uint8 `json:"sex"` - Birth *string `json:"birth"` - Password *string `json:"password"` - IsDefault *int8 `json:"is_default"` - Status *int8 `json:"status"` - Remark *string `json:"remark"` -} - -type backendChangePasswordPayload struct { - ID uint64 `json:"id"` - Password string `json:"password"` -} - -func formatBackendBirth(birth *string) *string { - if birth == nil { - return nil - } - - s := strings.TrimSpace(*birth) - if s == "" { - return nil - } - - if len(s) >= 10 { - date := s[:10] - return &date - } - - return &s -} - -func toBackendUserInfoDTO(u models.SystemTenantUser) backendUserInfoDTO { - var updateTime *string - if u.UpdateTime != nil { - s := u.UpdateTime.Format("2006-01-02 15:04:05") - updateTime = &s - } - - tenantName := "未知租户" - tenantCode := "" - - tenant, err := services.GetTenantByID(u.Tid) - if err == nil && tenant != nil { - tenantName = tenant.TenantName - tenantCode = tenant.TenantCode - } - - return backendUserInfoDTO{ - ID: u.ID, - Tid: u.Tid, - Uid: u.Uid, - Account: u.Account, - Name: u.Name, - Phone: u.Phone, - Email: u.Email, - Sex: u.Sex, - Birth: formatBackendBirth(u.Birth), - IsDefault: u.IsDefault, - Status: u.Status, - Remark: u.Remark, - CreateTime: u.CreateTime.Format("2006-01-02 15:04:05"), - UpdateTime: updateTime, - TenantName: tenantName, - TenantCode: tenantCode, - } -} - -func (c *BackendAdminUserController) getJWTUidTid() (uint64, uint64) { - var uid uint64 - var tid uint64 - - data := c.Ctx.Input.Data() - - if jwtUid := data["uid"]; jwtUid != nil { - if v, ok := jwtUid.(uint64); ok { - uid = v - } - } - - if jwtTid := data["tid"]; jwtTid != nil { - if v, ok := jwtTid.(uint64); ok { - tid = v - } - } - - return uid, tid -} - -func (c *BackendAdminUserController) parseTenantUserPayload() (backendTenantUserPayload, bool) { - var p backendTenantUserPayload - - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return backendTenantUserPayload{}, false - } - - return p, true -} - -func findBackendTenantUser(idOrUid uint64, jwtTid uint64) (*models.SystemTenantUser, error) { - var row models.SystemTenantUser - - qs := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("delete_time__isnull", true) - - if jwtTid > 0 { - qs = qs.Filter("tid", jwtTid) - } - - err := qs.Filter("id", idOrUid).One(&row) - if err == nil { - return &row, nil - } - - qs = models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("delete_time__isnull", true) - - if jwtTid > 0 { - qs = qs.Filter("tid", jwtTid) - } - - err = qs.Filter("uid", idOrUid).One(&row) - if err != nil { - return nil, err - } - - return &row, nil -} - -// GetAllUsers 获取当前租户后台用户列表 -// GET /backend/getAllUsers -func (c *BackendAdminUserController) GetAllUsers() { - _, jwtTid := c.getJWTUidTid() - - keyword := strings.TrimSpace(c.GetString("keyword")) - tid, _ := c.GetUint64("tid") - - if jwtTid > 0 { - tid = jwtTid - } - - cond := orm.NewCondition().And("delete_time__isnull", true) - - if tid > 0 { - cond = cond.And("tid", tid) - } - - if keyword != "" { - kwCond := orm.NewCondition(). - Or("name__icontains", keyword). - Or("phone__icontains", keyword). - Or("email__icontains", keyword). - Or("account__icontains", keyword) - cond = cond.AndCond(kwCond) - } - - var rows []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). - SetCond(cond). - OrderBy("-is_default", "-id"). - All(&rows) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - list := make([]backendUserInfoDTO, 0, len(rows)) - for _, row := range rows { - list = append(list, toBackendUserInfoDTO(row)) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": len(list), - }, - } - _ = c.ServeJSON() -} - -// GetTenantUsers 获取指定租户后台用户 -// GET /backend/getTenantUsers/:tid -func (c *BackendAdminUserController) GetTenantUsers() { - tidStr := c.Ctx.Input.Param(":tid") - tid, _ := strconv.ParseUint(tidStr, 10, 64) - - _, jwtTid := c.getJWTUidTid() - if jwtTid > 0 { - tid = jwtTid - } - - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} - _ = c.ServeJSON() - return - } - - var rows []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - OrderBy("-is_default", "-id"). - All(&rows) - - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - list := make([]backendUserInfoDTO, 0, len(rows)) - for _, row := range rows { - list = append(list, toBackendUserInfoDTO(row)) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": len(list), - }, - } - _ = c.ServeJSON() -} - -// GetUserInfo 获取后台租户用户详情 -// GET /backend/getUserInfo/:id -func (c *BackendAdminUserController) GetUserInfo() { - jwtUid, jwtTid := c.getJWTUidTid() - - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - - if id == 0 { - id = jwtUid - } - - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"} - _ = c.ServeJSON() - return - } - - u, err := findBackendTenantUser(id, jwtTid) - if err != nil || u == nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "后台用户信息不存在"} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": toBackendUserInfoDTO(*u), - } - _ = c.ServeJSON() -} - -// AddUser 添加后台租户用户 -// POST /backend/addUser -func (c *BackendAdminUserController) AddUser() { - p, ok := c.parseTenantUserPayload() - if !ok { - return - } - - _, jwtTid := c.getJWTUidTid() - if jwtTid > 0 { - p.Tid = jwtTid - } - - if p.Tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} - _ = c.ServeJSON() - return - } - - if p.Account == nil || strings.TrimSpace(*p.Account) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} - _ = c.ServeJSON() - return - } - - if p.Password == nil || strings.TrimSpace(*p.Password) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} - _ = c.ServeJSON() - return - } - - account := strings.TrimSpace(*p.Account) - p.Account = &account - - hashed, err := passwordutil.Hash(strings.TrimSpace(*p.Password)) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} - _ = c.ServeJSON() - return - } - p.Password = &hashed - - if p.Uid == 0 { - uid, err := generateTenantUID(p.Tid) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "生成用户ID失败"} - _ = c.ServeJSON() - return - } - p.Uid = uid - } - - isDefault := int8(0) - if p.IsDefault != nil { - isDefault = *p.IsDefault - } - - status := int8(1) - if p.Status != nil { - status = *p.Status - } - - id, err := services.BindTenantUser( - p.Tid, - p.Uid, - p.Account, - p.Name, - p.Phone, - p.Email, - p.Sex, - p.Birth, - p.Password, - isDefault, - status, - p.Remark, - ) - - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - if isDefault == 1 { - _ = services.SetDefaultTenant(p.Uid, p.Tid) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"id": id}, - } - _ = c.ServeJSON() -} - -// EditUser 编辑后台租户用户 -// POST /backend/editUser/:id -func (c *BackendAdminUserController) EditUser() { - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - - p, ok := c.parseTenantUserPayload() - if !ok { - return - } - - _, jwtTid := c.getJWTUidTid() - - row, err := findBackendTenantUser(id, jwtTid) - if err != nil || row == nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} - _ = c.ServeJSON() - return - } - - update := map[string]interface{}{} - - if p.Tid > 0 && jwtTid == 0 { - update["tid"] = p.Tid - } - - if p.Uid > 0 { - update["uid"] = p.Uid - } - - if p.Account != nil { - account := strings.TrimSpace(*p.Account) - if account != "" { - update["account"] = account - } - } - - if p.Name != nil { - update["name"] = *p.Name - } - - if p.Phone != nil { - update["phone"] = *p.Phone - } - - if p.Email != nil { - update["email"] = *p.Email - } - - if p.Sex != nil { - update["sex"] = *p.Sex - } - - if p.Birth != nil { - birth := strings.TrimSpace(*p.Birth) - if birth == "" { - update["birth"] = nil - } else { - update["birth"] = birth - } - } - - if p.Password != nil && strings.TrimSpace(*p.Password) != "" { - hashed, err := passwordutil.Hash(strings.TrimSpace(*p.Password)) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} - _ = c.ServeJSON() - return - } - update["password"] = hashed - } - - if p.IsDefault != nil { - update["is_default"] = *p.IsDefault - } - - if p.Status != nil { - update["status"] = *p.Status - } - - if p.Remark != nil { - update["remark"] = *p.Remark - } - - if len(update) == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"} - _ = c.ServeJSON() - return - } - - _, err = models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("id", row.ID). - Update(update) - - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "编辑失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - if p.IsDefault != nil && *p.IsDefault == 1 { - _ = services.SetDefaultTenant(row.Uid, row.Tid) - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// DeleteUser 删除后台租户用户 -// DELETE /backend/deleteUser/:id -func (c *BackendAdminUserController) DeleteUser() { - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - - _, jwtTid := c.getJWTUidTid() - - row, err := findBackendTenantUser(id, jwtTid) - if err != nil || row == nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} - _ = c.ServeJSON() - return - } - - if err := services.UnbindTenantUser(row.ID); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// ChangePassword 修改后台租户用户密码 -// POST /backend/changePassword -func (c *BackendAdminUserController) ChangePassword() { - var p backendChangePasswordPayload - - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - - if p.ID == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - - if strings.TrimSpace(p.Password) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} - _ = c.ServeJSON() - return - } - - _, jwtTid := c.getJWTUidTid() - - row, err := findBackendTenantUser(p.ID, jwtTid) - if err != nil || row == nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} - _ = c.ServeJSON() - return - } - - hashed, err := passwordutil.Hash(strings.TrimSpace(p.Password)) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} - _ = c.ServeJSON() - return - } - - _, err = models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("id", row.ID). - Update(map[string]interface{}{ - "password": hashed, - }) - - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "修改失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "io" + "strconv" + "strings" + + "server/models" + "server/pkg/passwordutil" + "server/services" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +type BackendAdminUserController struct { + beego.Controller +} + +type backendUserInfoDTO struct { + ID uint64 `json:"id"` + Tid uint64 `json:"tid"` + Uid uint64 `json:"uid"` + Account *string `json:"account"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Sex uint8 `json:"sex"` + Birth *string `json:"birth"` + IsDefault int8 `json:"is_default"` + Status int8 `json:"status"` + Remark *string `json:"remark"` + CreateTime string `json:"create_time"` + UpdateTime *string `json:"update_time"` + TenantName string `json:"tenant_name"` + TenantCode string `json:"tenant_code"` +} + +type backendTenantUserPayload struct { + Tid uint64 `json:"tid"` + Uid uint64 `json:"uid"` + Account *string `json:"account"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Sex *uint8 `json:"sex"` + Birth *string `json:"birth"` + Password *string `json:"password"` + IsDefault *int8 `json:"is_default"` + Status *int8 `json:"status"` + Remark *string `json:"remark"` +} + +type backendChangePasswordPayload struct { + ID uint64 `json:"id"` + Password string `json:"password"` +} + +func formatBackendBirth(birth *string) *string { + if birth == nil { + return nil + } + + s := strings.TrimSpace(*birth) + if s == "" { + return nil + } + + if len(s) >= 10 { + date := s[:10] + return &date + } + + return &s +} + +func toBackendUserInfoDTO(u models.SystemTenantUser) backendUserInfoDTO { + var updateTime *string + if u.UpdateTime != nil { + s := u.UpdateTime.Format("2006-01-02 15:04:05") + updateTime = &s + } + + tenantName := "未知租户" + tenantCode := "" + + tenant, err := services.GetTenantByID(u.Tid) + if err == nil && tenant != nil { + tenantName = tenant.TenantName + tenantCode = tenant.TenantCode + } + + return backendUserInfoDTO{ + ID: u.ID, + Tid: u.Tid, + Uid: u.Uid, + Account: u.Account, + Name: u.Name, + Phone: u.Phone, + Email: u.Email, + Sex: u.Sex, + Birth: formatBackendBirth(u.Birth), + IsDefault: u.IsDefault, + Status: u.Status, + Remark: u.Remark, + CreateTime: u.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: updateTime, + TenantName: tenantName, + TenantCode: tenantCode, + } +} + +func (c *BackendAdminUserController) getJWTUidTid() (uint64, uint64) { + var uid uint64 + var tid uint64 + + data := c.Ctx.Input.Data() + + if jwtUid := data["uid"]; jwtUid != nil { + if v, ok := jwtUid.(uint64); ok { + uid = v + } + } + + if jwtTid := data["tid"]; jwtTid != nil { + if v, ok := jwtTid.(uint64); ok { + tid = v + } + } + + return uid, tid +} + +func (c *BackendAdminUserController) parseTenantUserPayload() (backendTenantUserPayload, bool) { + var p backendTenantUserPayload + + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return backendTenantUserPayload{}, false + } + + return p, true +} + +func findBackendTenantUser(idOrUid uint64, jwtTid uint64) (*models.SystemTenantUser, error) { + var row models.SystemTenantUser + + qs := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("delete_time__isnull", true) + + if jwtTid > 0 { + qs = qs.Filter("tid", jwtTid) + } + + err := qs.Filter("id", idOrUid).One(&row) + if err == nil { + return &row, nil + } + + qs = models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("delete_time__isnull", true) + + if jwtTid > 0 { + qs = qs.Filter("tid", jwtTid) + } + + err = qs.Filter("uid", idOrUid).One(&row) + if err != nil { + return nil, err + } + + return &row, nil +} + +// GetAllUsers 获取当前租户后台用户列表 +// GET /backend/getAllUsers +func (c *BackendAdminUserController) GetAllUsers() { + _, jwtTid := c.getJWTUidTid() + + keyword := strings.TrimSpace(c.GetString("keyword")) + tid, _ := c.GetUint64("tid") + + if jwtTid > 0 { + tid = jwtTid + } + + cond := orm.NewCondition().And("delete_time__isnull", true) + + if tid > 0 { + cond = cond.And("tid", tid) + } + + if keyword != "" { + kwCond := orm.NewCondition(). + Or("name__icontains", keyword). + Or("phone__icontains", keyword). + Or("email__icontains", keyword). + Or("account__icontains", keyword) + cond = cond.AndCond(kwCond) + } + + var rows []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). + SetCond(cond). + OrderBy("-is_default", "-id"). + All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + list := make([]backendUserInfoDTO, 0, len(rows)) + for _, row := range rows { + list = append(list, toBackendUserInfoDTO(row)) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": len(list), + }, + } + _ = c.ServeJSON() +} + +// GetTenantUsers 获取指定租户后台用户 +// GET /backend/getTenantUsers/:tid +func (c *BackendAdminUserController) GetTenantUsers() { + tidStr := c.Ctx.Input.Param(":tid") + tid, _ := strconv.ParseUint(tidStr, 10, 64) + + _, jwtTid := c.getJWTUidTid() + if jwtTid > 0 { + tid = jwtTid + } + + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} + _ = c.ServeJSON() + return + } + + var rows []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + OrderBy("-is_default", "-id"). + All(&rows) + + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + list := make([]backendUserInfoDTO, 0, len(rows)) + for _, row := range rows { + list = append(list, toBackendUserInfoDTO(row)) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": len(list), + }, + } + _ = c.ServeJSON() +} + +// GetUserInfo 获取后台租户用户详情 +// GET /backend/getUserInfo/:id +func (c *BackendAdminUserController) GetUserInfo() { + jwtUid, jwtTid := c.getJWTUidTid() + + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + + if id == 0 { + id = jwtUid + } + + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"} + _ = c.ServeJSON() + return + } + + u, err := findBackendTenantUser(id, jwtTid) + if err != nil || u == nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "后台用户信息不存在"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": toBackendUserInfoDTO(*u), + } + _ = c.ServeJSON() +} + +// AddUser 添加后台租户用户 +// POST /backend/addUser +func (c *BackendAdminUserController) AddUser() { + p, ok := c.parseTenantUserPayload() + if !ok { + return + } + + _, jwtTid := c.getJWTUidTid() + if jwtTid > 0 { + p.Tid = jwtTid + } + + if p.Tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} + _ = c.ServeJSON() + return + } + + if p.Account == nil || strings.TrimSpace(*p.Account) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} + _ = c.ServeJSON() + return + } + + if p.Password == nil || strings.TrimSpace(*p.Password) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + + account := strings.TrimSpace(*p.Account) + p.Account = &account + + hashed, err := passwordutil.Hash(strings.TrimSpace(*p.Password)) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + p.Password = &hashed + + if p.Uid == 0 { + uid, err := generateTenantUID(p.Tid) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "生成用户ID失败"} + _ = c.ServeJSON() + return + } + p.Uid = uid + } + + isDefault := int8(0) + if p.IsDefault != nil { + isDefault = *p.IsDefault + } + + status := int8(1) + if p.Status != nil { + status = *p.Status + } + + id, err := services.BindTenantUser( + p.Tid, + p.Uid, + p.Account, + p.Name, + p.Phone, + p.Email, + p.Sex, + p.Birth, + p.Password, + isDefault, + status, + p.Remark, + ) + + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + if isDefault == 1 { + _ = services.SetDefaultTenant(p.Uid, p.Tid) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"id": id}, + } + _ = c.ServeJSON() +} + +// EditUser 编辑后台租户用户 +// POST /backend/editUser/:id +func (c *BackendAdminUserController) EditUser() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + + p, ok := c.parseTenantUserPayload() + if !ok { + return + } + + _, jwtTid := c.getJWTUidTid() + + row, err := findBackendTenantUser(id, jwtTid) + if err != nil || row == nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} + _ = c.ServeJSON() + return + } + + update := map[string]interface{}{} + + if p.Tid > 0 && jwtTid == 0 { + update["tid"] = p.Tid + } + + if p.Uid > 0 { + update["uid"] = p.Uid + } + + if p.Account != nil { + account := strings.TrimSpace(*p.Account) + if account != "" { + update["account"] = account + } + } + + if p.Name != nil { + update["name"] = *p.Name + } + + if p.Phone != nil { + update["phone"] = *p.Phone + } + + if p.Email != nil { + update["email"] = *p.Email + } + + if p.Sex != nil { + update["sex"] = *p.Sex + } + + if p.Birth != nil { + birth := strings.TrimSpace(*p.Birth) + if birth == "" { + update["birth"] = nil + } else { + update["birth"] = birth + } + } + + if p.Password != nil && strings.TrimSpace(*p.Password) != "" { + hashed, err := passwordutil.Hash(strings.TrimSpace(*p.Password)) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + update["password"] = hashed + } + + if p.IsDefault != nil { + update["is_default"] = *p.IsDefault + } + + if p.Status != nil { + update["status"] = *p.Status + } + + if p.Remark != nil { + update["remark"] = *p.Remark + } + + if len(update) == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"} + _ = c.ServeJSON() + return + } + + _, err = models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("id", row.ID). + Update(update) + + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "编辑失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + if p.IsDefault != nil && *p.IsDefault == 1 { + _ = services.SetDefaultTenant(row.Uid, row.Tid) + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteUser 删除后台租户用户 +// DELETE /backend/deleteUser/:id +func (c *BackendAdminUserController) DeleteUser() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + + _, jwtTid := c.getJWTUidTid() + + row, err := findBackendTenantUser(id, jwtTid) + if err != nil || row == nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} + _ = c.ServeJSON() + return + } + + if err := services.UnbindTenantUser(row.ID); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// ChangePassword 修改后台租户用户密码 +// POST /backend/changePassword +func (c *BackendAdminUserController) ChangePassword() { + var p backendChangePasswordPayload + + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + if p.ID == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + + if strings.TrimSpace(p.Password) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + + _, jwtTid := c.getJWTUidTid() + + row, err := findBackendTenantUser(p.ID, jwtTid) + if err != nil || row == nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} + _ = c.ServeJSON() + return + } + + hashed, err := passwordutil.Hash(strings.TrimSpace(p.Password)) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + + _, err = models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("id", row.ID). + Update(map[string]interface{}{ + "password": hashed, + }) + + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "修改失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/backend_article.go b/go/controllers/backend_article.go index 7c3b7a6..d9e070b 100644 --- a/go/controllers/backend_article.go +++ b/go/controllers/backend_article.go @@ -1,954 +1,954 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// BackendArticleController CMS 文章管理 -type BackendArticleController struct { - beego.Controller -} - -// BackendArticleCategoryController CMS 文章分类管理 -type BackendArticleCategoryController struct { - beego.Controller -} - -func (c *BackendArticleController) cmsClaims() (*jwtutil.Claims, error) { - return cmsBackendClaims(&c.Controller) -} - -func (c *BackendArticleCategoryController) cmsClaims() (*jwtutil.Claims, error) { - return cmsBackendClaims(&c.Controller) -} - -func cmsBackendClaims(c *beego.Controller) (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func cmsEffectiveTid(c *beego.Controller, claims *jwtutil.Claims) uint64 { - _ = c.ParseForm(1 << 20) - if tid, err := c.GetUint64("tid"); err == nil && tid > 0 { - return tid - } - if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" { - if v, e := strconv.ParseUint(h, 10, 64); e == nil { - return v - } - } - if claims != nil && claims.TenantId > 0 { - return uint64(claims.TenantId) - } - return 0 -} - -func (c *BackendArticleController) cmsJSONErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *BackendArticleCategoryController) cmsJSONErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func cmsEnsureTables(c *beego.Controller) bool { - if err := models.EnsureCmsArticleTables(); err != nil { - c.Ctx.Output.SetStatus(500) - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "初始化文章表失败: " + err.Error()} - _ = c.ServeJSON() - return false - } - return true -} - -func cmsParseUintArg(v interface{}) uint64 { - switch x := v.(type) { - case float64: - if x > 0 { - return uint64(x) - } - case string: - if n, err := strconv.ParseUint(strings.TrimSpace(x), 10, 64); err == nil { - return n - } - } - return 0 -} - -func cmsArticleToListItem(row models.CmsArticle, cateName string) map[string]interface{} { - return map[string]interface{}{ - "id": row.ID, - "title": row.Title, - "author": row.Author, - "cate": cateName, - "cate_id": row.CateID, - "status": row.Status, - "views": row.Views, - "likes": row.Likes, - "top": row.Top, - "recommend": row.Recommend, - "publish_date": models.CmsFormatTime(row.PublishTime), - "update_time": models.CmsFormatTime(row.UpdateTime), - } -} - -func cmsArticleToDetail(row models.CmsArticle, cateName string) map[string]interface{} { - pub := models.CmsFormatTime(row.PublishTime) - return map[string]interface{}{ - "id": row.ID, - "title": row.Title, - "author": row.Author, - "cate": cateName, - "cate_id": row.CateID, - "content": row.Content, - "desc": row.Desc, - "image": row.Image, - "is_trans": row.IsTrans, - "transurl": row.TransURL, - "status": row.Status, - "views": row.Views, - "view_count": row.Views, - "likes": row.Likes, - "top": row.Top, - "recommend": row.Recommend, - "publish_time": pub, - "publish_date": pub, - "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), - "update_time": models.CmsFormatTime(row.UpdateTime), - } -} - -func cmsCategoryToMap(row models.CmsArticleCategory) map[string]interface{} { - return map[string]interface{}{ - "id": row.ID, - "name": row.Name, - "label": row.Name, - "cid": row.Cid, - "parentId": row.Cid, - "image": row.Image, - "desc": row.Desc, - "remark": row.Desc, - "sort": row.Sort, - "status": row.Status, - } -} - -// List GET /backend/articlesList -func (c *BackendArticleController) List() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - if tid == 0 { - c.cmsJSONErr(400, 400, "tid不能为空") - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 10 - } - if pageSize > 200 { - pageSize = 200 - } - - keyword := strings.TrimSpace(c.GetString("keyword")) - cateFilter := strings.TrimSpace(c.GetString("cate")) - - qs := models.Orm.QueryTable(new(models.CmsArticle)). - Filter("tid", tid). - Filter("delete_time__isnull", true) - if keyword != "" { - qs = qs.Filter("title__icontains", keyword) - } - if cateFilter != "" { - if cid, err := strconv.ParseUint(cateFilter, 10, 64); err == nil && cid > 0 { - qs = qs.Filter("cate_id", cid) - } - } - - total, _ := qs.Count() - var rows []models.CmsArticle - offset := (page - 1) * pageSize - _, err = qs.OrderBy("-top", "-id").Limit(pageSize, offset).All(&rows) - if err != nil && err != orm.ErrNoRows { - c.cmsJSONErr(500, 500, "获取文章列表失败") - return - } - - cateIDs := make([]uint64, 0, len(rows)) - for _, r := range rows { - if r.CateID > 0 { - cateIDs = append(cateIDs, r.CateID) - } - } - cateNames := models.CmsCategoryNameMap(tid, cateIDs) - - list := make([]map[string]interface{}, 0, len(rows)) - for _, r := range rows { - list = append(list, cmsArticleToListItem(r, cateNames[r.CateID])) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"list": list, "total": total}, - } - _ = c.ServeJSON() -} - -// ListAll GET /backend/allarticles -func (c *BackendArticleController) ListAll() { - c.List() -} - -// Detail GET /backend/articles/:id -func (c *BackendArticleController) Detail() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - var row models.CmsArticle - err = models.Orm.QueryTable(new(models.CmsArticle)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - One(&row) - if err == orm.ErrNoRows { - c.cmsJSONErr(404, 404, "文章不存在") - return - } - if err != nil { - c.cmsJSONErr(500, 500, "查询失败") - return - } - - cateName := "" - if row.CateID > 0 { - names := models.CmsCategoryNameMap(tid, []uint64{row.CateID}) - cateName = names[row.CateID] - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": cmsArticleToDetail(row, cateName), - } - _ = c.ServeJSON() -} - -type cmsArticlePayload struct { - Title string `json:"title"` - Author string `json:"author"` - Cate interface{} `json:"cate"` - Content string `json:"content"` - Desc string `json:"desc"` - Image string `json:"image"` - IsTrans int8 `json:"is_trans"` - TransURL *string `json:"transurl"` - Status int8 `json:"status"` - IgnoreSimilarity int `json:"ignore_similarity"` -} - -// Create POST /backend/createarticle -func (c *BackendArticleController) Create() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - if tid == 0 { - c.cmsJSONErr(400, 400, "tid不能为空") - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - var p cmsArticlePayload - if err := json.Unmarshal(raw, &p); err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - title := strings.TrimSpace(p.Title) - if title == "" { - c.cmsJSONErr(400, 400, "标题不能为空") - return - } - - if p.IgnoreSimilarity != 1 { - similar, serr := models.CmsSimilarArticles(tid, title, 5) - if serr == nil && len(similar) > 0 { - c.Ctx.Output.SetStatus(409) - c.Data["json"] = map[string]interface{}{ - "code": 409, - "msg": "检测到相似标题", - "data": map[string]interface{}{"similar_articles": similar}, - } - _ = c.ServeJSON() - return - } - } - - now := time.Now() - cateID := cmsParseUintArg(p.Cate) - row := models.CmsArticle{ - Tid: tid, - Title: title, - Author: strings.TrimSpace(p.Author), - CateID: cateID, - Content: p.Content, - Desc: strings.TrimSpace(p.Desc), - Image: strings.TrimSpace(p.Image), - IsTrans: p.IsTrans, - TransURL: p.TransURL, - Status: p.Status, - CreateTime: now, - UpdateTime: &now, - } - id, err := models.Orm.Insert(&row) - if err != nil { - c.cmsJSONErr(500, 500, "创建失败") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} - _ = c.ServeJSON() -} - -// Update POST /backend/editarticle/:id -func (c *BackendArticleController) Update() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - var p cmsArticlePayload - if err := json.Unmarshal(raw, &p); err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - - now := time.Now() - fields := map[string]interface{}{ - "title": strings.TrimSpace(p.Title), - "author": strings.TrimSpace(p.Author), - "cate_id": cmsParseUintArg(p.Cate), - "content": p.Content, - "desc": strings.TrimSpace(p.Desc), - "image": strings.TrimSpace(p.Image), - "is_trans": p.IsTrans, - "transurl": p.TransURL, - "status": p.Status, - "update_time": now, - } - n, err := models.Orm.QueryTable(new(models.CmsArticle)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(fields) - if err != nil { - c.cmsJSONErr(500, 500, "更新失败") - return - } - if n == 0 { - c.cmsJSONErr(404, 404, "文章不存在") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} - _ = c.ServeJSON() -} - -// Delete DELETE /backend/deletearticle/:id -func (c *BackendArticleController) Delete() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.CmsArticle)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - c.cmsJSONErr(500, 500, "删除失败") - return - } - if n == 0 { - c.cmsJSONErr(404, 404, "文章不存在") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -func (c *BackendArticleController) setArticleFlag(field string, value int8) { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - now := time.Now() - fields := map[string]interface{}{field: value, "update_time": now} - n, err := models.Orm.QueryTable(new(models.CmsArticle)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(fields) - if err != nil { - c.cmsJSONErr(500, 500, "操作失败") - return - } - if n == 0 { - c.cmsJSONErr(404, 404, "文章不存在") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -func (c *BackendArticleController) Publish() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - var uid uint64 - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if len(raw) > 0 { - var body struct { - UID uint64 `json:"uid"` - } - _ = json.Unmarshal(raw, &body) - uid = body.UID - } - if uid == 0 && claims != nil { - uid = uint64(claims.UserID) - } - - now := time.Now() - fields := map[string]interface{}{ - "status": int8(2), - "publish_time": now, - "publisher_id": uid, - "update_time": now, - } - n, err := models.Orm.QueryTable(new(models.CmsArticle)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(fields) - if err != nil { - c.cmsJSONErr(500, 500, "发布失败") - return - } - if n == 0 { - c.cmsJSONErr(404, 404, "文章不存在") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发布成功"} - _ = c.ServeJSON() -} - -func (c *BackendArticleController) Unpublish() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.CmsArticle)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"status": int8(3), "update_time": now}) - if err != nil { - c.cmsJSONErr(500, 500, "下架失败") - return - } - if n == 0 { - c.cmsJSONErr(404, 404, "文章不存在") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "下架成功"} - _ = c.ServeJSON() -} - -func (c *BackendArticleController) Recommend() { c.setArticleFlag("recommend", 1) } -func (c *BackendArticleController) Unrecommend() { c.setArticleFlag("recommend", 0) } -func (c *BackendArticleController) Top() { c.setArticleFlag("top", 1) } -func (c *BackendArticleController) Untop() { c.setArticleFlag("top", 0) } - -// List GET /backend/categories -func (c *BackendArticleCategoryController) List() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - if tid == 0 { - c.cmsJSONErr(400, 400, "tid不能为空") - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 0) - if pageSize == 0 { - pageSize, _ = c.GetInt("limit", 1000) - } - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 1000 - } - - keyword := strings.TrimSpace(c.GetString("keyword")) - qs := models.Orm.QueryTable(new(models.CmsArticleCategory)). - Filter("tid", tid). - Filter("delete_time__isnull", true) - if keyword != "" { - qs = qs.Filter("name__icontains", keyword) - } - - total, _ := qs.Count() - var rows []models.CmsArticleCategory - offset := (page - 1) * pageSize - _, err = qs.OrderBy("sort", "id").Limit(pageSize, offset).All(&rows) - if err != nil && err != orm.ErrNoRows { - c.cmsJSONErr(500, 500, "获取分类失败") - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for _, r := range rows { - list = append(list, cmsCategoryToMap(r)) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"list": list, "total": total, "records": list}, - } - _ = c.ServeJSON() -} - -// ListAll GET /backend/allcategories -func (c *BackendArticleCategoryController) ListAll() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - if tid == 0 { - c.cmsJSONErr(400, 400, "tid不能为空") - return - } - - keyword := strings.TrimSpace(c.GetString("keyword")) - qs := models.Orm.QueryTable(new(models.CmsArticleCategory)). - Filter("tid", tid). - Filter("delete_time__isnull", true) - if keyword != "" { - qs = qs.Filter("name__icontains", keyword) - } - - var rows []models.CmsArticleCategory - _, err = qs.OrderBy("sort", "id").All(&rows) - if err != nil && err != orm.ErrNoRows { - c.cmsJSONErr(500, 500, "获取分类失败") - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for _, r := range rows { - list = append(list, cmsCategoryToMap(r)) - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} - _ = c.ServeJSON() -} - -// Detail GET /backend/categories/:id -func (c *BackendArticleCategoryController) Detail() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - var row models.CmsArticleCategory - err = models.Orm.QueryTable(new(models.CmsArticleCategory)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - One(&row) - if err == orm.ErrNoRows { - c.cmsJSONErr(404, 404, "分类不存在") - return - } - if err != nil { - c.cmsJSONErr(500, 500, "查询失败") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": cmsCategoryToMap(row)} - _ = c.ServeJSON() -} - -type cmsCategoryPayload struct { - Name string `json:"name"` - Image string `json:"image"` - Desc string `json:"desc"` - Sort int `json:"sort"` - Status int8 `json:"status"` - Cid uint64 `json:"cid"` -} - -// Create POST /backend/createCategory -func (c *BackendArticleCategoryController) Create() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - if tid == 0 { - c.cmsJSONErr(400, 400, "tid不能为空") - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - var p cmsCategoryPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - name := strings.TrimSpace(p.Name) - if name == "" { - c.cmsJSONErr(400, 400, "分类名称不能为空") - return - } - - now := time.Now() - row := models.CmsArticleCategory{ - Tid: tid, - Cid: p.Cid, - Name: name, - Image: strings.TrimSpace(p.Image), - Desc: strings.TrimSpace(p.Desc), - Sort: p.Sort, - Status: p.Status, - CreateTime: now, - UpdateTime: &now, - } - id, err := models.Orm.Insert(&row) - if err != nil { - c.cmsJSONErr(500, 500, "创建失败") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} - _ = c.ServeJSON() -} - -// Update POST /backend/editCategory/:id -func (c *BackendArticleCategoryController) Update() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - var p cmsCategoryPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{ - "name": strings.TrimSpace(p.Name), - "image": strings.TrimSpace(p.Image), - "desc": strings.TrimSpace(p.Desc), - "sort": p.Sort, - "status": p.Status, - "cid": p.Cid, - "update_time": now, - }) - if err != nil { - c.cmsJSONErr(500, 500, "更新失败") - return - } - if n == 0 { - c.cmsJSONErr(404, 404, "分类不存在") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} - _ = c.ServeJSON() -} - -// Delete DELETE /backend/categories/:id -func (c *BackendArticleCategoryController) Delete() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - childCnt, _ := models.Orm.QueryTable(new(models.CmsArticleCategory)). - Filter("tid", tid). - Filter("cid", id). - Filter("delete_time__isnull", true). - Count() - if childCnt > 0 { - c.cmsJSONErr(400, 400, "请先删除子分类") - return - } - - articleCnt, _ := models.Orm.QueryTable(new(models.CmsArticle)). - Filter("tid", tid). - Filter("cate_id", id). - Filter("delete_time__isnull", true). - Count() - if articleCnt > 0 { - c.cmsJSONErr(400, 400, "该分类下还有文章,无法删除") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - c.cmsJSONErr(500, 500, "删除失败") - return - } - if n == 0 { - c.cmsJSONErr(404, 404, "分类不存在") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -// UpdateStatus PATCH /backend/categories/:id/status -func (c *BackendArticleCategoryController) UpdateStatus() { - if !cmsEnsureTables(&c.Controller) { - return - } - claims, err := c.cmsClaims() - if err != nil { - c.cmsJSONErr(401, 401, err.Error()) - return - } - tid := cmsEffectiveTid(&c.Controller, claims) - id, _ := c.GetUint64(":id") - if id == 0 { - c.cmsJSONErr(400, 400, "无效ID") - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - var p struct { - Status int8 `json:"status"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.cmsJSONErr(400, 400, "参数错误") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"status": p.Status, "update_time": now}) - if err != nil { - c.cmsJSONErr(500, 500, "更新失败") - return - } - if n == 0 { - c.cmsJSONErr(404, 404, "分类不存在") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// BackendArticleController CMS 文章管理 +type BackendArticleController struct { + beego.Controller +} + +// BackendArticleCategoryController CMS 文章分类管理 +type BackendArticleCategoryController struct { + beego.Controller +} + +func (c *BackendArticleController) cmsClaims() (*jwtutil.Claims, error) { + return cmsBackendClaims(&c.Controller) +} + +func (c *BackendArticleCategoryController) cmsClaims() (*jwtutil.Claims, error) { + return cmsBackendClaims(&c.Controller) +} + +func cmsBackendClaims(c *beego.Controller) (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func cmsEffectiveTid(c *beego.Controller, claims *jwtutil.Claims) uint64 { + _ = c.ParseForm(1 << 20) + if tid, err := c.GetUint64("tid"); err == nil && tid > 0 { + return tid + } + if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" { + if v, e := strconv.ParseUint(h, 10, 64); e == nil { + return v + } + } + if claims != nil && claims.TenantId > 0 { + return uint64(claims.TenantId) + } + return 0 +} + +func (c *BackendArticleController) cmsJSONErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *BackendArticleCategoryController) cmsJSONErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func cmsEnsureTables(c *beego.Controller) bool { + if err := models.EnsureCmsArticleTables(); err != nil { + c.Ctx.Output.SetStatus(500) + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "初始化文章表失败: " + err.Error()} + _ = c.ServeJSON() + return false + } + return true +} + +func cmsParseUintArg(v interface{}) uint64 { + switch x := v.(type) { + case float64: + if x > 0 { + return uint64(x) + } + case string: + if n, err := strconv.ParseUint(strings.TrimSpace(x), 10, 64); err == nil { + return n + } + } + return 0 +} + +func cmsArticleToListItem(row models.CmsArticle, cateName string) map[string]interface{} { + return map[string]interface{}{ + "id": row.ID, + "title": row.Title, + "author": row.Author, + "cate": cateName, + "cate_id": row.CateID, + "status": row.Status, + "views": row.Views, + "likes": row.Likes, + "top": row.Top, + "recommend": row.Recommend, + "publish_date": models.CmsFormatTime(row.PublishTime), + "update_time": models.CmsFormatTime(row.UpdateTime), + } +} + +func cmsArticleToDetail(row models.CmsArticle, cateName string) map[string]interface{} { + pub := models.CmsFormatTime(row.PublishTime) + return map[string]interface{}{ + "id": row.ID, + "title": row.Title, + "author": row.Author, + "cate": cateName, + "cate_id": row.CateID, + "content": row.Content, + "desc": row.Desc, + "image": row.Image, + "is_trans": row.IsTrans, + "transurl": row.TransURL, + "status": row.Status, + "views": row.Views, + "view_count": row.Views, + "likes": row.Likes, + "top": row.Top, + "recommend": row.Recommend, + "publish_time": pub, + "publish_date": pub, + "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), + "update_time": models.CmsFormatTime(row.UpdateTime), + } +} + +func cmsCategoryToMap(row models.CmsArticleCategory) map[string]interface{} { + return map[string]interface{}{ + "id": row.ID, + "name": row.Name, + "label": row.Name, + "cid": row.Cid, + "parentId": row.Cid, + "image": row.Image, + "desc": row.Desc, + "remark": row.Desc, + "sort": row.Sort, + "status": row.Status, + } +} + +// List GET /backend/articlesList +func (c *BackendArticleController) List() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + if tid == 0 { + c.cmsJSONErr(400, 400, "tid不能为空") + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + if pageSize > 200 { + pageSize = 200 + } + + keyword := strings.TrimSpace(c.GetString("keyword")) + cateFilter := strings.TrimSpace(c.GetString("cate")) + + qs := models.Orm.QueryTable(new(models.CmsArticle)). + Filter("tid", tid). + Filter("delete_time__isnull", true) + if keyword != "" { + qs = qs.Filter("title__icontains", keyword) + } + if cateFilter != "" { + if cid, err := strconv.ParseUint(cateFilter, 10, 64); err == nil && cid > 0 { + qs = qs.Filter("cate_id", cid) + } + } + + total, _ := qs.Count() + var rows []models.CmsArticle + offset := (page - 1) * pageSize + _, err = qs.OrderBy("-top", "-id").Limit(pageSize, offset).All(&rows) + if err != nil && err != orm.ErrNoRows { + c.cmsJSONErr(500, 500, "获取文章列表失败") + return + } + + cateIDs := make([]uint64, 0, len(rows)) + for _, r := range rows { + if r.CateID > 0 { + cateIDs = append(cateIDs, r.CateID) + } + } + cateNames := models.CmsCategoryNameMap(tid, cateIDs) + + list := make([]map[string]interface{}, 0, len(rows)) + for _, r := range rows { + list = append(list, cmsArticleToListItem(r, cateNames[r.CateID])) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"list": list, "total": total}, + } + _ = c.ServeJSON() +} + +// ListAll GET /backend/allarticles +func (c *BackendArticleController) ListAll() { + c.List() +} + +// Detail GET /backend/articles/:id +func (c *BackendArticleController) Detail() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + var row models.CmsArticle + err = models.Orm.QueryTable(new(models.CmsArticle)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&row) + if err == orm.ErrNoRows { + c.cmsJSONErr(404, 404, "文章不存在") + return + } + if err != nil { + c.cmsJSONErr(500, 500, "查询失败") + return + } + + cateName := "" + if row.CateID > 0 { + names := models.CmsCategoryNameMap(tid, []uint64{row.CateID}) + cateName = names[row.CateID] + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": cmsArticleToDetail(row, cateName), + } + _ = c.ServeJSON() +} + +type cmsArticlePayload struct { + Title string `json:"title"` + Author string `json:"author"` + Cate interface{} `json:"cate"` + Content string `json:"content"` + Desc string `json:"desc"` + Image string `json:"image"` + IsTrans int8 `json:"is_trans"` + TransURL *string `json:"transurl"` + Status int8 `json:"status"` + IgnoreSimilarity int `json:"ignore_similarity"` +} + +// Create POST /backend/createarticle +func (c *BackendArticleController) Create() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + if tid == 0 { + c.cmsJSONErr(400, 400, "tid不能为空") + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + var p cmsArticlePayload + if err := json.Unmarshal(raw, &p); err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + title := strings.TrimSpace(p.Title) + if title == "" { + c.cmsJSONErr(400, 400, "标题不能为空") + return + } + + if p.IgnoreSimilarity != 1 { + similar, serr := models.CmsSimilarArticles(tid, title, 5) + if serr == nil && len(similar) > 0 { + c.Ctx.Output.SetStatus(409) + c.Data["json"] = map[string]interface{}{ + "code": 409, + "msg": "检测到相似标题", + "data": map[string]interface{}{"similar_articles": similar}, + } + _ = c.ServeJSON() + return + } + } + + now := time.Now() + cateID := cmsParseUintArg(p.Cate) + row := models.CmsArticle{ + Tid: tid, + Title: title, + Author: strings.TrimSpace(p.Author), + CateID: cateID, + Content: p.Content, + Desc: strings.TrimSpace(p.Desc), + Image: strings.TrimSpace(p.Image), + IsTrans: p.IsTrans, + TransURL: p.TransURL, + Status: p.Status, + CreateTime: now, + UpdateTime: &now, + } + id, err := models.Orm.Insert(&row) + if err != nil { + c.cmsJSONErr(500, 500, "创建失败") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} + _ = c.ServeJSON() +} + +// Update POST /backend/editarticle/:id +func (c *BackendArticleController) Update() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + var p cmsArticlePayload + if err := json.Unmarshal(raw, &p); err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + + now := time.Now() + fields := map[string]interface{}{ + "title": strings.TrimSpace(p.Title), + "author": strings.TrimSpace(p.Author), + "cate_id": cmsParseUintArg(p.Cate), + "content": p.Content, + "desc": strings.TrimSpace(p.Desc), + "image": strings.TrimSpace(p.Image), + "is_trans": p.IsTrans, + "transurl": p.TransURL, + "status": p.Status, + "update_time": now, + } + n, err := models.Orm.QueryTable(new(models.CmsArticle)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(fields) + if err != nil { + c.cmsJSONErr(500, 500, "更新失败") + return + } + if n == 0 { + c.cmsJSONErr(404, 404, "文章不存在") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +// Delete DELETE /backend/deletearticle/:id +func (c *BackendArticleController) Delete() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.CmsArticle)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + c.cmsJSONErr(500, 500, "删除失败") + return + } + if n == 0 { + c.cmsJSONErr(404, 404, "文章不存在") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +func (c *BackendArticleController) setArticleFlag(field string, value int8) { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + now := time.Now() + fields := map[string]interface{}{field: value, "update_time": now} + n, err := models.Orm.QueryTable(new(models.CmsArticle)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(fields) + if err != nil { + c.cmsJSONErr(500, 500, "操作失败") + return + } + if n == 0 { + c.cmsJSONErr(404, 404, "文章不存在") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +func (c *BackendArticleController) Publish() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + var uid uint64 + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if len(raw) > 0 { + var body struct { + UID uint64 `json:"uid"` + } + _ = json.Unmarshal(raw, &body) + uid = body.UID + } + if uid == 0 && claims != nil { + uid = uint64(claims.UserID) + } + + now := time.Now() + fields := map[string]interface{}{ + "status": int8(2), + "publish_time": now, + "publisher_id": uid, + "update_time": now, + } + n, err := models.Orm.QueryTable(new(models.CmsArticle)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(fields) + if err != nil { + c.cmsJSONErr(500, 500, "发布失败") + return + } + if n == 0 { + c.cmsJSONErr(404, 404, "文章不存在") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发布成功"} + _ = c.ServeJSON() +} + +func (c *BackendArticleController) Unpublish() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.CmsArticle)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"status": int8(3), "update_time": now}) + if err != nil { + c.cmsJSONErr(500, 500, "下架失败") + return + } + if n == 0 { + c.cmsJSONErr(404, 404, "文章不存在") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "下架成功"} + _ = c.ServeJSON() +} + +func (c *BackendArticleController) Recommend() { c.setArticleFlag("recommend", 1) } +func (c *BackendArticleController) Unrecommend() { c.setArticleFlag("recommend", 0) } +func (c *BackendArticleController) Top() { c.setArticleFlag("top", 1) } +func (c *BackendArticleController) Untop() { c.setArticleFlag("top", 0) } + +// List GET /backend/categories +func (c *BackendArticleCategoryController) List() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + if tid == 0 { + c.cmsJSONErr(400, 400, "tid不能为空") + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 0) + if pageSize == 0 { + pageSize, _ = c.GetInt("limit", 1000) + } + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 1000 + } + + keyword := strings.TrimSpace(c.GetString("keyword")) + qs := models.Orm.QueryTable(new(models.CmsArticleCategory)). + Filter("tid", tid). + Filter("delete_time__isnull", true) + if keyword != "" { + qs = qs.Filter("name__icontains", keyword) + } + + total, _ := qs.Count() + var rows []models.CmsArticleCategory + offset := (page - 1) * pageSize + _, err = qs.OrderBy("sort", "id").Limit(pageSize, offset).All(&rows) + if err != nil && err != orm.ErrNoRows { + c.cmsJSONErr(500, 500, "获取分类失败") + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for _, r := range rows { + list = append(list, cmsCategoryToMap(r)) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"list": list, "total": total, "records": list}, + } + _ = c.ServeJSON() +} + +// ListAll GET /backend/allcategories +func (c *BackendArticleCategoryController) ListAll() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + if tid == 0 { + c.cmsJSONErr(400, 400, "tid不能为空") + return + } + + keyword := strings.TrimSpace(c.GetString("keyword")) + qs := models.Orm.QueryTable(new(models.CmsArticleCategory)). + Filter("tid", tid). + Filter("delete_time__isnull", true) + if keyword != "" { + qs = qs.Filter("name__icontains", keyword) + } + + var rows []models.CmsArticleCategory + _, err = qs.OrderBy("sort", "id").All(&rows) + if err != nil && err != orm.ErrNoRows { + c.cmsJSONErr(500, 500, "获取分类失败") + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for _, r := range rows { + list = append(list, cmsCategoryToMap(r)) + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} + _ = c.ServeJSON() +} + +// Detail GET /backend/categories/:id +func (c *BackendArticleCategoryController) Detail() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + var row models.CmsArticleCategory + err = models.Orm.QueryTable(new(models.CmsArticleCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&row) + if err == orm.ErrNoRows { + c.cmsJSONErr(404, 404, "分类不存在") + return + } + if err != nil { + c.cmsJSONErr(500, 500, "查询失败") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": cmsCategoryToMap(row)} + _ = c.ServeJSON() +} + +type cmsCategoryPayload struct { + Name string `json:"name"` + Image string `json:"image"` + Desc string `json:"desc"` + Sort int `json:"sort"` + Status int8 `json:"status"` + Cid uint64 `json:"cid"` +} + +// Create POST /backend/createCategory +func (c *BackendArticleCategoryController) Create() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + if tid == 0 { + c.cmsJSONErr(400, 400, "tid不能为空") + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + var p cmsCategoryPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + name := strings.TrimSpace(p.Name) + if name == "" { + c.cmsJSONErr(400, 400, "分类名称不能为空") + return + } + + now := time.Now() + row := models.CmsArticleCategory{ + Tid: tid, + Cid: p.Cid, + Name: name, + Image: strings.TrimSpace(p.Image), + Desc: strings.TrimSpace(p.Desc), + Sort: p.Sort, + Status: p.Status, + CreateTime: now, + UpdateTime: &now, + } + id, err := models.Orm.Insert(&row) + if err != nil { + c.cmsJSONErr(500, 500, "创建失败") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} + _ = c.ServeJSON() +} + +// Update POST /backend/editCategory/:id +func (c *BackendArticleCategoryController) Update() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + var p cmsCategoryPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{ + "name": strings.TrimSpace(p.Name), + "image": strings.TrimSpace(p.Image), + "desc": strings.TrimSpace(p.Desc), + "sort": p.Sort, + "status": p.Status, + "cid": p.Cid, + "update_time": now, + }) + if err != nil { + c.cmsJSONErr(500, 500, "更新失败") + return + } + if n == 0 { + c.cmsJSONErr(404, 404, "分类不存在") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +// Delete DELETE /backend/categories/:id +func (c *BackendArticleCategoryController) Delete() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + childCnt, _ := models.Orm.QueryTable(new(models.CmsArticleCategory)). + Filter("tid", tid). + Filter("cid", id). + Filter("delete_time__isnull", true). + Count() + if childCnt > 0 { + c.cmsJSONErr(400, 400, "请先删除子分类") + return + } + + articleCnt, _ := models.Orm.QueryTable(new(models.CmsArticle)). + Filter("tid", tid). + Filter("cate_id", id). + Filter("delete_time__isnull", true). + Count() + if articleCnt > 0 { + c.cmsJSONErr(400, 400, "该分类下还有文章,无法删除") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + c.cmsJSONErr(500, 500, "删除失败") + return + } + if n == 0 { + c.cmsJSONErr(404, 404, "分类不存在") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// UpdateStatus PATCH /backend/categories/:id/status +func (c *BackendArticleCategoryController) UpdateStatus() { + if !cmsEnsureTables(&c.Controller) { + return + } + claims, err := c.cmsClaims() + if err != nil { + c.cmsJSONErr(401, 401, err.Error()) + return + } + tid := cmsEffectiveTid(&c.Controller, claims) + id, _ := c.GetUint64(":id") + if id == 0 { + c.cmsJSONErr(400, 400, "无效ID") + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + var p struct { + Status int8 `json:"status"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.cmsJSONErr(400, 400, "参数错误") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"status": p.Status, "update_time": now}) + if err != nil { + c.cmsJSONErr(500, 500, "更新失败") + return + } + if n == 0 { + c.cmsJSONErr(404, 404, "分类不存在") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/backend_auth.go b/go/controllers/backend_auth.go index 0ad1747..b45c15a 100644 --- a/go/controllers/backend_auth.go +++ b/go/controllers/backend_auth.go @@ -1,322 +1,322 @@ -package controllers - -import ( - "encoding/json" - "io" - "strings" - - "server/models" - "server/pkg/jwtutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -type backendAuthLoginRequest struct { - TenantName string `json:"tenant_name"` - Account string `json:"account"` - Password string `json:"password"` - Code string `json:"code"` - // 极验4验证参数 - CaptchaID string `json:"captcha_id"` - LotNumber string `json:"lot_number"` - PassToken string `json:"pass_token"` - GenTime string `json:"gen_time"` - CaptchaOutput string `json:"captcha_output"` -} - -// BackendAuthController backend 端认证控制器 -type BackendAuthController struct { - beego.Controller -} - -func (c *BackendAuthController) serveJSON(data map[string]interface{}) { - c.Data["json"] = data - _ = c.ServeJSON() -} - -// LoginBackend backend 登录(需要租户) -func (c *BackendAuthController) LoginBackend() { - var req backendAuthLoginRequest - - body := c.Ctx.Input.RequestBody - if len(body) == 0 { - var err error - body, err = io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) - return - } - } - if len(body) == 0 { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) - return - } - if err := json.Unmarshal(body, &req); err != nil { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) - return - } - - req.TenantName = strings.TrimSpace(req.TenantName) - req.Account = strings.TrimSpace(req.Account) - req.Password = strings.TrimSpace(req.Password) - if req.TenantName == "" || req.Account == "" || req.Password == "" { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "租户名称、用户名或密码不能为空"}) - return - } - - cfg, _ := models.GetPlatformLoginVerify() - if cfg.OpenVerifyEnabled == 1 { - if cfg.VerifyType == "geetest4" { - if req.LotNumber == "" || req.PassToken == "" || req.GenTime == "" || req.CaptchaOutput == "" { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "请完成人机验证"}) - return - } - // TODO: 集成极验4服务端 SDK 后在这里进行二次校验 - } else if cfg.VerifyType == "geetest3" { - if req.CaptchaOutput == "" { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "请完成人机验证"}) - return - } - // TODO: 集成极验3服务端 SDK 后在这里进行二次校验 - } else if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { - if strings.TrimSpace(req.Code) == "" { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "请输入验证码"}) - return - } - if err := services.VerifyBackendLoginCode(req.TenantName, req.Account, cfg.VerifyType, req.Code); err != nil { - c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()}) - return - } - } - } - - token, loginUser, err := services.BackendLogin(req.TenantName, req.Account, req.Password) - if err != nil { - c.serveJSON(map[string]interface{}{"code": 401, "msg": err.Error()}) - return - } - - c.serveJSON(map[string]interface{}{ - "code": 200, - "msg": "登录成功", - "data": map[string]interface{}{ - "token": token, - "user": map[string]interface{}{ - "id": loginUser.ID, - "account": loginUser.Account, - "name": loginUser.Name, - "tid": loginUser.Tid, - "rid": loginUser.Rid, - "avatar": loginUser.Avatar, - "role_name": loginUser.RoleName, - }, - }, - }) -} - -// GetCurrentUser 当前登录 backend 用户信息,需 Bearer Token -func (c *BackendAuthController) GetCurrentUser() { - authHeader := c.Ctx.Request.Header.Get("Authorization") - if authHeader == "" { - c.serveJSON(map[string]interface{}{"code": 401, "msg": "未登录"}) - return - } - authParts := strings.SplitN(authHeader, " ", 2) - if len(authParts) != 2 || authParts[0] != "Bearer" { - c.serveJSON(map[string]interface{}{"code": 401, "msg": "认证信息格式错误"}) - return - } - claims, err := jwtutil.ParseToken(authParts[1]) - if err != nil { - c.serveJSON(map[string]interface{}{"code": 401, "msg": "无效的token"}) - return - } - if claims.UserType != "backend" { - c.serveJSON(map[string]interface{}{"code": 403, "msg": "无权访问"}) - return - } - - var tenantUser models.SystemTenantUser - err = models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("uid", claims.UserID). - Filter("tid", claims.TenantId). - One(&tenantUser) - if err != nil { - c.serveJSON(map[string]interface{}{"code": 401, "msg": "用户不存在"}) - return - } - if tenantUser.Status == 0 { - c.serveJSON(map[string]interface{}{"code": 401, "msg": "账号已禁用"}) - return - } - - account := "" - if tenantUser.Account != nil { - account = strings.TrimSpace(*tenantUser.Account) - } - name := "" - if tenantUser.Name != nil { - name = strings.TrimSpace(*tenantUser.Name) - } - - c.serveJSON(map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "id": tenantUser.Uid, - "account": account, - "name": name, - "tid": tenantUser.Tid, - "rid": 0, - "avatar": "", - "role_name": "", - }, - }) -} - -// SendLoginCode 发送 backend 登录验证码 -func (c *BackendAuthController) SendLoginCode() { - var req struct { - Account string `json:"account"` - TenantName string `json:"tenant_name"` - Channel string `json:"channel"` - } - body := c.Ctx.Input.RequestBody - if len(body) == 0 { - var err error - body, err = io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) - return - } - } - if err := json.Unmarshal(body, &req); err != nil { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) - return - } - - cfg, _ := models.GetPlatformLoginVerify() - if cfg.OpenVerifyEnabled != 1 { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "当前未开启验证"}) - return - } - channel := strings.TrimSpace(req.Channel) - if channel == "" { - channel = cfg.VerifyType - } - if channel != "sms" && channel != "email" { - c.serveJSON(map[string]interface{}{"code": 400, "msg": "仅支持短信/邮箱验证码"}) - return - } - if err := services.SendBackendLoginCode(req.TenantName, req.Account, channel); err != nil { - c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()}) - return - } - c.serveJSON(map[string]interface{}{"code": 200, "msg": "验证码已发送"}) -} - -// LoginBySms 手机号验证码登录(占位实现) -func (c *BackendAuthController) LoginBySms() { - c.serveJSON(map[string]interface{}{ - "code": 501, - "msg": "手机号验证码登录暂未实现", - }) -} - -// Logout backend 退出登录(当前为无状态直接返回成功) -func (c *BackendAuthController) Logout() { - c.serveJSON(map[string]interface{}{ - "code": 200, - "msg": "退出成功", - }) -} - -// GetGeetest3Infos 获取 backend 极验3.0配置 -func (c *BackendAuthController) GetGeetest3Infos() { - cfg, _ := models.GetPlatformLoginVerify() - if cfg.Geetest3ID == nil || cfg.Geetest3Key == nil { - c.serveJSON(map[string]interface{}{"code": 404, "msg": "未配置极验3参数"}) - return - } - c.serveJSON(map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "captcha_id": *cfg.Geetest3ID, - "captcha_key": *cfg.Geetest3Key, - }, - }) -} - -// GetGeetest4Infos 获取 backend 极验4.0配置 -func (c *BackendAuthController) GetGeetest4Infos() { - cfg, _ := models.GetPlatformLoginVerify() - if cfg.Geetest4ID == nil || cfg.Geetest4Key == nil { - c.serveJSON(map[string]interface{}{"code": 404, "msg": "未配置极验4参数"}) - return - } - c.serveJSON(map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "captcha_id": *cfg.Geetest4ID, - "captcha_key": *cfg.Geetest4Key, - }, - }) -} - -// GetOpenVerify 判断是否开启 backend 登录验证 -func (c *BackendAuthController) GetOpenVerify() { - cfg, _ := models.GetPlatformLoginVerify() - openVerify := "0" - if cfg.OpenVerifyEnabled == 1 { - openVerify = "1" - } - c.serveJSON(map[string]interface{}{ - "code": 200, - "msg": "ok", - "data": []map[string]string{ - { - "label": "openVerify", - "value": openVerify, - }, - { - "label": "verifyType", - "value": cfg.VerifyType, - }, - }, - }) -} - -// Register 注册(占位实现) -func (c *BackendAuthController) Register() { - c.serveJSON(map[string]interface{}{ - "code": 501, - "msg": "注册暂未实现", - }) -} - -// SendRegisterCode 发送注册验证码(占位实现) -func (c *BackendAuthController) SendRegisterCode() { - c.serveJSON(map[string]interface{}{ - "code": 501, - "msg": "发送注册验证码暂未实现", - }) -} - -// ResetPassword 忘记密码重置(占位实现) -func (c *BackendAuthController) ResetPassword() { - c.serveJSON(map[string]interface{}{ - "code": 501, - "msg": "重置密码暂未实现", - }) -} - -// SendResetCode 发送找回密码验证码(占位实现) -func (c *BackendAuthController) SendResetCode() { - c.serveJSON(map[string]interface{}{ - "code": 501, - "msg": "发送找回密码验证码暂未实现", - }) -} +package controllers + +import ( + "encoding/json" + "io" + "strings" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +type backendAuthLoginRequest struct { + TenantName string `json:"tenant_name"` + Account string `json:"account"` + Password string `json:"password"` + Code string `json:"code"` + // 极验4验证参数 + CaptchaID string `json:"captcha_id"` + LotNumber string `json:"lot_number"` + PassToken string `json:"pass_token"` + GenTime string `json:"gen_time"` + CaptchaOutput string `json:"captcha_output"` +} + +// BackendAuthController backend 端认证控制器 +type BackendAuthController struct { + beego.Controller +} + +func (c *BackendAuthController) serveJSON(data map[string]interface{}) { + c.Data["json"] = data + _ = c.ServeJSON() +} + +// LoginBackend backend 登录(需要租户) +func (c *BackendAuthController) LoginBackend() { + var req backendAuthLoginRequest + + body := c.Ctx.Input.RequestBody + if len(body) == 0 { + var err error + body, err = io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + } + if len(body) == 0 { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + if err := json.Unmarshal(body, &req); err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + + req.TenantName = strings.TrimSpace(req.TenantName) + req.Account = strings.TrimSpace(req.Account) + req.Password = strings.TrimSpace(req.Password) + if req.TenantName == "" || req.Account == "" || req.Password == "" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "租户名称、用户名或密码不能为空"}) + return + } + + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled == 1 { + if cfg.VerifyType == "geetest4" { + if req.LotNumber == "" || req.PassToken == "" || req.GenTime == "" || req.CaptchaOutput == "" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "请完成人机验证"}) + return + } + // TODO: 集成极验4服务端 SDK 后在这里进行二次校验 + } else if cfg.VerifyType == "geetest3" { + if req.CaptchaOutput == "" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "请完成人机验证"}) + return + } + // TODO: 集成极验3服务端 SDK 后在这里进行二次校验 + } else if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { + if strings.TrimSpace(req.Code) == "" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "请输入验证码"}) + return + } + if err := services.VerifyBackendLoginCode(req.TenantName, req.Account, cfg.VerifyType, req.Code); err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()}) + return + } + } + } + + token, loginUser, err := services.BackendLogin(req.TenantName, req.Account, req.Password) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 401, "msg": err.Error()}) + return + } + + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "登录成功", + "data": map[string]interface{}{ + "token": token, + "user": map[string]interface{}{ + "id": loginUser.ID, + "account": loginUser.Account, + "name": loginUser.Name, + "tid": loginUser.Tid, + "rid": loginUser.Rid, + "avatar": loginUser.Avatar, + "role_name": loginUser.RoleName, + }, + }, + }) +} + +// GetCurrentUser 当前登录 backend 用户信息,需 Bearer Token +func (c *BackendAuthController) GetCurrentUser() { + authHeader := c.Ctx.Request.Header.Get("Authorization") + if authHeader == "" { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "未登录"}) + return + } + authParts := strings.SplitN(authHeader, " ", 2) + if len(authParts) != 2 || authParts[0] != "Bearer" { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "认证信息格式错误"}) + return + } + claims, err := jwtutil.ParseToken(authParts[1]) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "无效的token"}) + return + } + if claims.UserType != "backend" { + c.serveJSON(map[string]interface{}{"code": 403, "msg": "无权访问"}) + return + } + + var tenantUser models.SystemTenantUser + err = models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("uid", claims.UserID). + Filter("tid", claims.TenantId). + One(&tenantUser) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "用户不存在"}) + return + } + if tenantUser.Status == 0 { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "账号已禁用"}) + return + } + + account := "" + if tenantUser.Account != nil { + account = strings.TrimSpace(*tenantUser.Account) + } + name := "" + if tenantUser.Name != nil { + name = strings.TrimSpace(*tenantUser.Name) + } + + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "id": tenantUser.Uid, + "account": account, + "name": name, + "tid": tenantUser.Tid, + "rid": 0, + "avatar": "", + "role_name": "", + }, + }) +} + +// SendLoginCode 发送 backend 登录验证码 +func (c *BackendAuthController) SendLoginCode() { + var req struct { + Account string `json:"account"` + TenantName string `json:"tenant_name"` + Channel string `json:"channel"` + } + body := c.Ctx.Input.RequestBody + if len(body) == 0 { + var err error + body, err = io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + } + if err := json.Unmarshal(body, &req); err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled != 1 { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "当前未开启验证"}) + return + } + channel := strings.TrimSpace(req.Channel) + if channel == "" { + channel = cfg.VerifyType + } + if channel != "sms" && channel != "email" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "仅支持短信/邮箱验证码"}) + return + } + if err := services.SendBackendLoginCode(req.TenantName, req.Account, channel); err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()}) + return + } + c.serveJSON(map[string]interface{}{"code": 200, "msg": "验证码已发送"}) +} + +// LoginBySms 手机号验证码登录(占位实现) +func (c *BackendAuthController) LoginBySms() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "手机号验证码登录暂未实现", + }) +} + +// Logout backend 退出登录(当前为无状态直接返回成功) +func (c *BackendAuthController) Logout() { + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "退出成功", + }) +} + +// GetGeetest3Infos 获取 backend 极验3.0配置 +func (c *BackendAuthController) GetGeetest3Infos() { + cfg, _ := models.GetPlatformLoginVerify() + if cfg.Geetest3ID == nil || cfg.Geetest3Key == nil { + c.serveJSON(map[string]interface{}{"code": 404, "msg": "未配置极验3参数"}) + return + } + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "captcha_id": *cfg.Geetest3ID, + "captcha_key": *cfg.Geetest3Key, + }, + }) +} + +// GetGeetest4Infos 获取 backend 极验4.0配置 +func (c *BackendAuthController) GetGeetest4Infos() { + cfg, _ := models.GetPlatformLoginVerify() + if cfg.Geetest4ID == nil || cfg.Geetest4Key == nil { + c.serveJSON(map[string]interface{}{"code": 404, "msg": "未配置极验4参数"}) + return + } + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "captcha_id": *cfg.Geetest4ID, + "captcha_key": *cfg.Geetest4Key, + }, + }) +} + +// GetOpenVerify 判断是否开启 backend 登录验证 +func (c *BackendAuthController) GetOpenVerify() { + cfg, _ := models.GetPlatformLoginVerify() + openVerify := "0" + if cfg.OpenVerifyEnabled == 1 { + openVerify = "1" + } + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "ok", + "data": []map[string]string{ + { + "label": "openVerify", + "value": openVerify, + }, + { + "label": "verifyType", + "value": cfg.VerifyType, + }, + }, + }) +} + +// Register 注册(占位实现) +func (c *BackendAuthController) Register() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "注册暂未实现", + }) +} + +// SendRegisterCode 发送注册验证码(占位实现) +func (c *BackendAuthController) SendRegisterCode() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "发送注册验证码暂未实现", + }) +} + +// ResetPassword 忘记密码重置(占位实现) +func (c *BackendAuthController) ResetPassword() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "重置密码暂未实现", + }) +} + +// SendResetCode 发送找回密码验证码(占位实现) +func (c *BackendAuthController) SendResetCode() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "发送找回密码验证码暂未实现", + }) +} diff --git a/go/controllers/backend_domain.go b/go/controllers/backend_domain.go index c3e701b..1d70e1e 100644 --- a/go/controllers/backend_domain.go +++ b/go/controllers/backend_domain.go @@ -1,600 +1,600 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// BackendDomainPoolController 主域名池管理 -type BackendDomainPoolController struct { - beego.Controller -} - -// BackendTenantDomainController 租户域名管理 -type BackendTenantDomainController struct { - beego.Controller -} - -func requireBackend(c *beego.Controller) (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -// ===== 主域名池 ===== - -// Index GET /backend/domain/pool/index?page=&pageSize=&main_domain=&status= -func (c *BackendDomainPoolController) Index() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 10 - } - if pageSize > 200 { - pageSize = 200 - } - - mainDomain := strings.TrimSpace(c.GetString("main_domain")) - statusStr := strings.TrimSpace(c.GetString("status")) - - qs := models.Orm.QueryTable(new(models.SystemDomainPool)).Filter("delete_time__isnull", true) - if mainDomain != "" { - qs = qs.Filter("main_domain__icontains", mainDomain) - } - if statusStr != "" { - if st, err := strconv.Atoi(statusStr); err == nil { - qs = qs.Filter("status", st) - } - } - - total, err := qs.Count() - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error()) - return - } - var rows []models.SystemDomainPool - _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - item := map[string]interface{}{ - "id": rows[i].ID, - "main_domain": rows[i].MainDomain, - "status": rows[i].Status, - "create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"), - "update_time": "", - } - if rows[i].UpdateTime != nil { - item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") - } - list = append(list, item) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -// GetEnabledDomains GET /backend/domain/pool/getEnabledDomains -func (c *BackendDomainPoolController) GetEnabledDomains() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - var rows []models.SystemDomainPool - _, err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("status", 1). - Filter("delete_time__isnull", true). - OrderBy("-id"). - All(&rows) - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取主域名失败: "+err.Error()) - return - } - out := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - out = append(out, map[string]interface{}{ - "id": rows[i].ID, - "main_domain": rows[i].MainDomain, - "status": rows[i].Status, - }) - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -// Create POST /backend/domain/pool/create -func (c *BackendDomainPoolController) Create() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p domainPoolPayload - if err := json.Unmarshal(raw, &p); err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - md := strings.TrimSpace(p.MainDomain) - if md == "" { - jsonErr(&c.Controller, 400, 400, "主域名不能为空") - return - } - if p.Status != 0 && p.Status != 1 { - p.Status = 1 - } - // 简单去重 - cnt, _ := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("main_domain", md). - Filter("delete_time__isnull", true). - Count() - if cnt > 0 { - jsonErr(&c.Controller, 400, 400, "主域名已存在") - return - } - row := &models.SystemDomainPool{MainDomain: md, Status: p.Status} - if _, err := models.Orm.Insert(row); err != nil { - jsonErr(&c.Controller, 500, 500, "创建失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功"} - _ = c.ServeJSON() -} - -// Update POST /backend/domain/pool/update -func (c *BackendDomainPoolController) Update() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p domainPoolPayload - if err := json.Unmarshal(raw, &p); err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - if p.ID == 0 { - jsonErr(&c.Controller, 400, 400, "id 不能为空") - return - } - md := strings.TrimSpace(p.MainDomain) - if md == "" { - jsonErr(&c.Controller, 400, 400, "主域名不能为空") - return - } - if p.Status != 0 && p.Status != 1 { - p.Status = 1 - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("id", p.ID). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"main_domain": md, "status": p.Status, "update_time": now}) - if err != nil { - jsonErr(&c.Controller, 500, 500, "更新失败: "+err.Error()) - return - } - if n == 0 { - jsonErr(&c.Controller, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} - _ = c.ServeJSON() -} - -// Delete DELETE /backend/domain/pool/delete/:id -func (c *BackendDomainPoolController) Delete() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - jsonErr(&c.Controller, 400, 400, "无效ID") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - jsonErr(&c.Controller, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -// ToggleStatus POST /backend/domain/pool/toggleStatus body:{id} -func (c *BackendDomainPoolController) ToggleStatus() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var row models.SystemDomainPool - if err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("id", p.ID). - Filter("delete_time__isnull", true). - One(&row); err != nil { - jsonErr(&c.Controller, 404, 404, "记录不存在") - return - } - newStatus := int8(1) - if row.Status == 1 { - newStatus = 0 - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("id", p.ID). - Update(map[string]interface{}{"status": newStatus, "update_time": now}) - if err != nil { - jsonErr(&c.Controller, 500, 500, "切换失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// ===== 租户域名 ===== - -// Index GET /backend/domain/tenant/index?page=&pageSize=&tid=&status=&sub_domain= -func (c *BackendTenantDomainController) Index() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 10 - } - if pageSize > 200 { - pageSize = 200 - } - - tid, _ := c.GetUint64("tid") - statusStr := strings.TrimSpace(c.GetString("status")) - subDomain := strings.TrimSpace(c.GetString("sub_domain")) - - qs := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("delete_time__isnull", true) - if tid > 0 { - qs = qs.Filter("tid", tid) - } - if statusStr != "" { - if st, err := strconv.Atoi(statusStr); err == nil { - qs = qs.Filter("status", st) - } - } - if subDomain != "" { - qs = qs.Filter("sub_domain__icontains", subDomain) - } - - total, err := qs.Count() - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error()) - return - } - var rows []models.SystemTenantDomain - _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error()) - return - } - list := make([]models.SystemTenantDomain, 0, len(rows)) - list = append(list, rows...) - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"list": list, "total": total}, - } - _ = c.ServeJSON() -} - -// MyDomains GET /backend/domain/tenant/myDomains?tid=1 -func (c *BackendTenantDomainController) MyDomains() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - tid, _ := c.GetUint64("tid") - if tid == 0 { - jsonErr(&c.Controller, 400, 400, "租户ID不能为空") - return - } - var rows []models.SystemTenantDomain - _, err := models.Orm.QueryTable(new(models.SystemTenantDomain)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - OrderBy("-id"). - All(&rows) - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows} - _ = c.ServeJSON() -} - -// Apply POST /backend/domain/tenant/apply body:{tid,sub_domain,main_domain} -func (c *BackendTenantDomainController) Apply() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p struct { - Tid uint64 `json:"tid"` - SubDomain string `json:"sub_domain"` - MainDomain string `json:"main_domain"` - } - if err := json.Unmarshal(raw, &p); err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - if p.Tid == 0 { - jsonErr(&c.Controller, 400, 400, "租户ID不能为空") - return - } - sub := strings.TrimSpace(p.SubDomain) - main := strings.TrimSpace(p.MainDomain) - if sub == "" { - jsonErr(&c.Controller, 400, 400, "二级域名前缀不能为空") - return - } - if main == "" { - jsonErr(&c.Controller, 400, 400, "请选择主域名") - return - } - if !subDomainRe.MatchString(sub) { - jsonErr(&c.Controller, 400, 400, "二级域名前缀格式不正确") - return - } - - // 该租户是否已有域名 - cnt, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)). - Filter("tid", p.Tid). - Filter("delete_time__isnull", true). - Count() - if cnt > 0 { - jsonErr(&c.Controller, 400, 400, "该租户已有域名,请删除后再次申请") - return - } - - // 主域名存在且启用 - var pool models.SystemDomainPool - if err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("main_domain", main). - Filter("status", 1). - Filter("delete_time__isnull", true). - One(&pool); err != nil { - jsonErr(&c.Controller, 400, 400, "主域名不存在或已禁用") - return - } - - // 二级域名是否已被使用(同主域名下) - used, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)). - Filter("sub_domain", sub). - Filter("main_domain", main). - Filter("delete_time__isnull", true). - Count() - if used > 0 { - jsonErr(&c.Controller, 400, 400, "该二级域名已被使用") - return - } - - full := sub + "." + main - now := time.Now() - tid := p.Tid - row := &models.SystemTenantDomain{ - Tid: &tid, - SubDomain: &sub, - MainDomain: &main, - FullDomain: &full, - Status: 0, - CreateTime: now, - UpdateTime: &now, - } - id, err := models.Orm.Insert(row) - if err != nil { - jsonErr(&c.Controller, 500, 500, "申请失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "申请提交成功,等待审核", "data": map[string]interface{}{"id": uint64(id)}} - _ = c.ServeJSON() -} - -// Audit POST /backend/domain/tenant/audit body:{id,action} action=approve/reject -func (c *BackendTenantDomainController) Audit() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - Action string `json:"action"` - } - if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var row models.SystemTenantDomain - if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil { - jsonErr(&c.Controller, 404, 404, "域名不存在") - return - } - if row.Status != 0 { - jsonErr(&c.Controller, 400, 400, "该域名已审核过了") - return - } - newStatus := 2 - msg := "已拒绝" - if strings.ToLower(strings.TrimSpace(p.Action)) == "approve" { - newStatus = 1 - msg = "审核通过" - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{ - "status": newStatus, - "update_time": now, - }) - if err != nil { - jsonErr(&c.Controller, 500, 500, "审核失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg} - _ = c.ServeJSON() -} - -// ToggleStatus POST /backend/domain/tenant/toggleStatus body:{id} -func (c *BackendTenantDomainController) ToggleStatus() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var row models.SystemTenantDomain - if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil { - jsonErr(&c.Controller, 404, 404, "域名不存在") - return - } - if row.Status == 0 { - jsonErr(&c.Controller, 400, 400, "审核中不可操作") - return - } - newStatus := 2 - if row.Status == 2 { - newStatus = 1 - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{ - "status": newStatus, - "update_time": now, - }) - if err != nil { - jsonErr(&c.Controller, 500, 500, "操作失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// Delete DELETE /backend/domain/tenant/delete/:id -func (c *BackendTenantDomainController) Delete() { - if _, err := requireBackend(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemTenantDomain)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - jsonErr(&c.Controller, 404, 404, "域名不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -// 用于复杂筛选时可扩展:当前保留 orm.Condition import,避免被 gofmt 删除 -var _ = orm.NewCondition +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// BackendDomainPoolController 主域名池管理 +type BackendDomainPoolController struct { + beego.Controller +} + +// BackendTenantDomainController 租户域名管理 +type BackendTenantDomainController struct { + beego.Controller +} + +func requireBackend(c *beego.Controller) (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +// ===== 主域名池 ===== + +// Index GET /backend/domain/pool/index?page=&pageSize=&main_domain=&status= +func (c *BackendDomainPoolController) Index() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + if pageSize > 200 { + pageSize = 200 + } + + mainDomain := strings.TrimSpace(c.GetString("main_domain")) + statusStr := strings.TrimSpace(c.GetString("status")) + + qs := models.Orm.QueryTable(new(models.SystemDomainPool)).Filter("delete_time__isnull", true) + if mainDomain != "" { + qs = qs.Filter("main_domain__icontains", mainDomain) + } + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + qs = qs.Filter("status", st) + } + } + + total, err := qs.Count() + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error()) + return + } + var rows []models.SystemDomainPool + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + item := map[string]interface{}{ + "id": rows[i].ID, + "main_domain": rows[i].MainDomain, + "status": rows[i].Status, + "create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"), + "update_time": "", + } + if rows[i].UpdateTime != nil { + item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") + } + list = append(list, item) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// GetEnabledDomains GET /backend/domain/pool/getEnabledDomains +func (c *BackendDomainPoolController) GetEnabledDomains() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + var rows []models.SystemDomainPool + _, err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("status", 1). + Filter("delete_time__isnull", true). + OrderBy("-id"). + All(&rows) + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取主域名失败: "+err.Error()) + return + } + out := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + out = append(out, map[string]interface{}{ + "id": rows[i].ID, + "main_domain": rows[i].MainDomain, + "status": rows[i].Status, + }) + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +// Create POST /backend/domain/pool/create +func (c *BackendDomainPoolController) Create() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p domainPoolPayload + if err := json.Unmarshal(raw, &p); err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + md := strings.TrimSpace(p.MainDomain) + if md == "" { + jsonErr(&c.Controller, 400, 400, "主域名不能为空") + return + } + if p.Status != 0 && p.Status != 1 { + p.Status = 1 + } + // 简单去重 + cnt, _ := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("main_domain", md). + Filter("delete_time__isnull", true). + Count() + if cnt > 0 { + jsonErr(&c.Controller, 400, 400, "主域名已存在") + return + } + row := &models.SystemDomainPool{MainDomain: md, Status: p.Status} + if _, err := models.Orm.Insert(row); err != nil { + jsonErr(&c.Controller, 500, 500, "创建失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功"} + _ = c.ServeJSON() +} + +// Update POST /backend/domain/pool/update +func (c *BackendDomainPoolController) Update() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p domainPoolPayload + if err := json.Unmarshal(raw, &p); err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + if p.ID == 0 { + jsonErr(&c.Controller, 400, 400, "id 不能为空") + return + } + md := strings.TrimSpace(p.MainDomain) + if md == "" { + jsonErr(&c.Controller, 400, 400, "主域名不能为空") + return + } + if p.Status != 0 && p.Status != 1 { + p.Status = 1 + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("id", p.ID). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"main_domain": md, "status": p.Status, "update_time": now}) + if err != nil { + jsonErr(&c.Controller, 500, 500, "更新失败: "+err.Error()) + return + } + if n == 0 { + jsonErr(&c.Controller, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +// Delete DELETE /backend/domain/pool/delete/:id +func (c *BackendDomainPoolController) Delete() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + jsonErr(&c.Controller, 400, 400, "无效ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + jsonErr(&c.Controller, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// ToggleStatus POST /backend/domain/pool/toggleStatus body:{id} +func (c *BackendDomainPoolController) ToggleStatus() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var row models.SystemDomainPool + if err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("id", p.ID). + Filter("delete_time__isnull", true). + One(&row); err != nil { + jsonErr(&c.Controller, 404, 404, "记录不存在") + return + } + newStatus := int8(1) + if row.Status == 1 { + newStatus = 0 + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("id", p.ID). + Update(map[string]interface{}{"status": newStatus, "update_time": now}) + if err != nil { + jsonErr(&c.Controller, 500, 500, "切换失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// ===== 租户域名 ===== + +// Index GET /backend/domain/tenant/index?page=&pageSize=&tid=&status=&sub_domain= +func (c *BackendTenantDomainController) Index() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + if pageSize > 200 { + pageSize = 200 + } + + tid, _ := c.GetUint64("tid") + statusStr := strings.TrimSpace(c.GetString("status")) + subDomain := strings.TrimSpace(c.GetString("sub_domain")) + + qs := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("delete_time__isnull", true) + if tid > 0 { + qs = qs.Filter("tid", tid) + } + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + qs = qs.Filter("status", st) + } + } + if subDomain != "" { + qs = qs.Filter("sub_domain__icontains", subDomain) + } + + total, err := qs.Count() + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error()) + return + } + var rows []models.SystemTenantDomain + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error()) + return + } + list := make([]models.SystemTenantDomain, 0, len(rows)) + list = append(list, rows...) + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"list": list, "total": total}, + } + _ = c.ServeJSON() +} + +// MyDomains GET /backend/domain/tenant/myDomains?tid=1 +func (c *BackendTenantDomainController) MyDomains() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + tid, _ := c.GetUint64("tid") + if tid == 0 { + jsonErr(&c.Controller, 400, 400, "租户ID不能为空") + return + } + var rows []models.SystemTenantDomain + _, err := models.Orm.QueryTable(new(models.SystemTenantDomain)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + OrderBy("-id"). + All(&rows) + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows} + _ = c.ServeJSON() +} + +// Apply POST /backend/domain/tenant/apply body:{tid,sub_domain,main_domain} +func (c *BackendTenantDomainController) Apply() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p struct { + Tid uint64 `json:"tid"` + SubDomain string `json:"sub_domain"` + MainDomain string `json:"main_domain"` + } + if err := json.Unmarshal(raw, &p); err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + if p.Tid == 0 { + jsonErr(&c.Controller, 400, 400, "租户ID不能为空") + return + } + sub := strings.TrimSpace(p.SubDomain) + main := strings.TrimSpace(p.MainDomain) + if sub == "" { + jsonErr(&c.Controller, 400, 400, "二级域名前缀不能为空") + return + } + if main == "" { + jsonErr(&c.Controller, 400, 400, "请选择主域名") + return + } + if !subDomainRe.MatchString(sub) { + jsonErr(&c.Controller, 400, 400, "二级域名前缀格式不正确") + return + } + + // 该租户是否已有域名 + cnt, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)). + Filter("tid", p.Tid). + Filter("delete_time__isnull", true). + Count() + if cnt > 0 { + jsonErr(&c.Controller, 400, 400, "该租户已有域名,请删除后再次申请") + return + } + + // 主域名存在且启用 + var pool models.SystemDomainPool + if err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("main_domain", main). + Filter("status", 1). + Filter("delete_time__isnull", true). + One(&pool); err != nil { + jsonErr(&c.Controller, 400, 400, "主域名不存在或已禁用") + return + } + + // 二级域名是否已被使用(同主域名下) + used, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)). + Filter("sub_domain", sub). + Filter("main_domain", main). + Filter("delete_time__isnull", true). + Count() + if used > 0 { + jsonErr(&c.Controller, 400, 400, "该二级域名已被使用") + return + } + + full := sub + "." + main + now := time.Now() + tid := p.Tid + row := &models.SystemTenantDomain{ + Tid: &tid, + SubDomain: &sub, + MainDomain: &main, + FullDomain: &full, + Status: 0, + CreateTime: now, + UpdateTime: &now, + } + id, err := models.Orm.Insert(row) + if err != nil { + jsonErr(&c.Controller, 500, 500, "申请失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "申请提交成功,等待审核", "data": map[string]interface{}{"id": uint64(id)}} + _ = c.ServeJSON() +} + +// Audit POST /backend/domain/tenant/audit body:{id,action} action=approve/reject +func (c *BackendTenantDomainController) Audit() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + Action string `json:"action"` + } + if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var row models.SystemTenantDomain + if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil { + jsonErr(&c.Controller, 404, 404, "域名不存在") + return + } + if row.Status != 0 { + jsonErr(&c.Controller, 400, 400, "该域名已审核过了") + return + } + newStatus := 2 + msg := "已拒绝" + if strings.ToLower(strings.TrimSpace(p.Action)) == "approve" { + newStatus = 1 + msg = "审核通过" + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{ + "status": newStatus, + "update_time": now, + }) + if err != nil { + jsonErr(&c.Controller, 500, 500, "审核失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg} + _ = c.ServeJSON() +} + +// ToggleStatus POST /backend/domain/tenant/toggleStatus body:{id} +func (c *BackendTenantDomainController) ToggleStatus() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var row models.SystemTenantDomain + if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil { + jsonErr(&c.Controller, 404, 404, "域名不存在") + return + } + if row.Status == 0 { + jsonErr(&c.Controller, 400, 400, "审核中不可操作") + return + } + newStatus := 2 + if row.Status == 2 { + newStatus = 1 + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{ + "status": newStatus, + "update_time": now, + }) + if err != nil { + jsonErr(&c.Controller, 500, 500, "操作失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// Delete DELETE /backend/domain/tenant/delete/:id +func (c *BackendTenantDomainController) Delete() { + if _, err := requireBackend(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemTenantDomain)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + jsonErr(&c.Controller, 404, 404, "域名不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// 用于复杂筛选时可扩展:当前保留 orm.Condition import,避免被 gofmt 删除 +var _ = orm.NewCondition diff --git a/go/controllers/backend_erp.go b/go/controllers/backend_erp.go index 1b8b1c5..79a7607 100644 --- a/go/controllers/backend_erp.go +++ b/go/controllers/backend_erp.go @@ -1,1078 +1,1078 @@ -package controllers - -import ( - "crypto/sha256" - "encoding/hex" - "encoding/json" - "strconv" - "strings" - "time" - - "server/models" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// BackendErpController 兼容 backend 前端 /admin/erp/* 组织机构、员工、职位接口。 -type BackendErpController struct { - beego.Controller -} - -type erpOrganizationDTO struct { - ID uint64 `json:"id"` - Tid uint64 `json:"tid"` - TenantID uint64 `json:"tenant_id"` - OrgName string `json:"org_name"` - OrgCode string `json:"org_code"` - ParentID uint64 `json:"parent_id"` - ParentName string `json:"parent_name"` - LeaderID uint64 `json:"leader_id"` - LeaderName string `json:"leader_name"` - IsCompany int `json:"is_company"` - Sort uint `json:"sort"` - Status int8 `json:"status"` - Remark string `json:"remark"` -} - -type erpEmployeeDTO struct { - ID uint `json:"id"` - Tid int `json:"tid"` - TenantID int `json:"tenant_id"` - Account string `json:"account"` - Name string `json:"name"` - Gender int8 `json:"gender"` - Sex int8 `json:"sex"` - Birthday string `json:"birthday"` - AffiliateUnit string `json:"affiliate_unit"` - AffiliateUnitName string `json:"affiliate_unit_name"` - Department string `json:"department"` - DepartmentName string `json:"department_name"` - Position string `json:"position"` - Education string `json:"education"` - Nation string `json:"nation"` - Phone string `json:"phone"` - Wechat string `json:"wechat"` - Email string `json:"email"` - HomeAddress string `json:"home_address"` - AccountStatus int8 `json:"account_status"` - Status int8 `json:"status"` -} - -type erpPositionDTO struct { - ID uint64 `json:"id"` - TenantID uint64 `json:"tenant_id"` - Tid uint64 `json:"tid"` - DepartmentID uint64 `json:"department_id"` - PositionCode string `json:"position_code"` - PositionName string `json:"position_name"` - PositionType int8 `json:"position_type"` - Status int8 `json:"status"` - Sort uint `json:"sort"` -} - -// GetOrganization 获取组织机构列表。 -// GET /admin/erp/getOrganization -func (c *BackendErpController) GetOrganization() { - tid, _ := c.GetInt("tid") - - qs := models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("delete_time__isnull", true). - Exclude("status", 0) - if tid > 0 { - qs = qs.Filter("tid", tid) - } - - var rows []models.BackendErpOrganization - _, err := qs.OrderBy("sort", "id").All(&rows) - if err != nil { - c.jsonError(500, "查询组织机构失败: "+err.Error()) - return - } - - list := make([]erpOrganizationDTO, 0, len(rows)) - for _, row := range rows { - list = append(list, c.organizationDTO(row)) - } - - c.jsonOK(list) -} - -// GetOrganizationDetail 获取组织机构详情。 -// GET /admin/erp/getOrganizationDetail/:id -func (c *BackendErpController) GetOrganizationDetail() { - id, ok := c.pathUint64(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - var row models.BackendErpOrganization - err := models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("id", id). - Filter("delete_time__isnull", true). - Exclude("status", 0). - One(&row) - if err != nil { - c.jsonError(404, "组织机构不存在") - return - } - - c.jsonOK(c.organizationDTO(row)) -} - -// CreateOrganization 创建组织机构。 -// POST /admin/erp/createOrganization -func (c *BackendErpController) CreateOrganization() { - body := c.parseJSONBody() - - orgName, _ := c.getStringValue(body, "org_name", "name") - orgName = strings.TrimSpace(orgName) - if orgName == "" { - c.jsonError(400, "组织名称不能为空") - return - } - - orgCode, _ := c.getStringValue(body, "org_code", "code") - orgCode = strings.TrimSpace(orgCode) - if orgCode == "" { - orgCode = "ORG" + c.nowCompactString() - } - - tid, _ := c.getUint64Value(body, "tid", "tenant_id") - parentID, _ := c.getUint64Value(body, "parent_id") - leaderID, hasLeader := c.getUint64Value(body, "leader_id") - sortVal, _ := c.getUintValue(body, "sort") - isCompany, hasCompany := c.getIntValue(body, "is_company") - status, hasStatus := c.getIntValue(body, "status") - remark, _ := c.getStringValue(body, "remark") - - row := models.BackendErpOrganization{ - Tid: tid, - OrgName: orgName, - OrgCode: orgCode, - ParentID: parentID, - Sort: sortVal, - IsCompany: boolInt(parentID == 0), - Status: 1, - Remark: strPtrIfNotEmpty(remark), - } - if hasLeader && leaderID > 0 { - row.LeaderID = &leaderID - } - if hasCompany { - row.IsCompany = isCompany - } - if hasStatus { - row.Status = int8(status) - } - - id, err := models.Orm.Insert(&row) - if err != nil { - c.jsonError(500, "创建组织机构失败: "+err.Error()) - return - } - - c.jsonOK(map[string]interface{}{"id": id}) -} - -// EditOrganization 更新组织机构。 -// POST /admin/erp/editOrganization/:id -func (c *BackendErpController) EditOrganization() { - id, ok := c.pathUint64(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - body := c.parseJSONBody() - update := orm.Params{} - - if v, has := c.getStringValue(body, "org_name", "name"); has { - v = strings.TrimSpace(v) - if v == "" { - c.jsonError(400, "组织名称不能为空") - return - } - update["org_name"] = v - } - if v, has := c.getStringValue(body, "org_code", "code"); has { - update["org_code"] = strings.TrimSpace(v) - } - if v, has := c.getUint64Value(body, "parent_id"); has { - update["parent_id"] = v - } - if v, has := c.getUint64Value(body, "tid", "tenant_id"); has { - update["tid"] = v - } - if v, has := c.getUint64Value(body, "leader_id"); has { - update["leader_id"] = nullableUint64(v) - } - if v, has := c.getUintValue(body, "sort"); has { - update["sort"] = v - } - if v, has := c.getIntValue(body, "is_company"); has { - update["is_company"] = v - } - if v, has := c.getIntValue(body, "status"); has { - update["status"] = int8(v) - } - if v, has := c.getStringValue(body, "remark"); has { - update["remark"] = nullableString(v) - } - - if len(update) == 0 { - c.jsonError(400, "无更新字段") - return - } - - num, err := models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(update) - if err != nil { - c.jsonError(500, "更新组织机构失败: "+err.Error()) - return - } - if num == 0 { - c.jsonError(404, "组织机构不存在") - return - } - - c.jsonOK(nil) -} - -// DeleteOrganization 删除组织机构。 -// DELETE /admin/erp/deleteOrganization/:id -func (c *BackendErpController) DeleteOrganization() { - id, ok := c.pathUint64(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - childCount, err := models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("parent_id", id). - Filter("delete_time__isnull", true). - Exclude("status", 0). - Count() - if err != nil { - c.jsonError(500, "检查子组织失败: "+err.Error()) - return - } - if childCount > 0 { - c.jsonError(400, "请先删除下级组织") - return - } - - now := c.nowString() - num, err := models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(orm.Params{"delete_time": now, "status": int8(0)}) - if err != nil { - c.jsonError(500, "删除组织机构失败: "+err.Error()) - return - } - if num == 0 { - c.jsonError(404, "组织机构不存在") - return - } - - c.jsonOK(nil) -} - -// GetCompanys 获取企业单位列表。 -// GET /admin/erp/getCompanys -func (c *BackendErpController) GetCompanys() { - tid, _ := c.GetInt("tid") - - qs := models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("delete_time__isnull", true). - Exclude("status", 0). - Filter("is_company", 1) - if tid > 0 { - qs = qs.Filter("tid", tid) - } - - var rows []models.BackendErpOrganization - _, err := qs.OrderBy("sort", "id").All(&rows) - if err != nil { - c.jsonError(500, "查询企业单位失败: "+err.Error()) - return - } - - list := make([]erpOrganizationDTO, 0, len(rows)) - for _, row := range rows { - list = append(list, c.organizationDTO(row)) - } - - c.jsonOK(list) -} - -// GetDepartments 获取部门列表。 -// GET /admin/erp/getDepartments?parent_id=1 -func (c *BackendErpController) GetDepartments() { - parentID, _ := c.GetInt("parent_id") - tid, _ := c.GetInt("tid") - - qs := models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("delete_time__isnull", true). - Exclude("status", 0) - if parentID > 0 { - qs = qs.Filter("parent_id", parentID) - } else { - qs = qs.Filter("is_company", 0) - } - if tid > 0 { - qs = qs.Filter("tid", tid) - } - - var rows []models.BackendErpOrganization - _, err := qs.OrderBy("sort", "id").All(&rows) - if err != nil { - c.jsonError(500, "查询部门失败: "+err.Error()) - return - } - - list := make([]erpOrganizationDTO, 0, len(rows)) - for _, row := range rows { - list = append(list, c.organizationDTO(row)) - } - - c.jsonOK(list) -} - -// GetEmployee 获取员工列表。 -// GET /admin/erp/getEmployee?tid=1 -func (c *BackendErpController) GetEmployee() { - tid, _ := c.GetInt("tid") - - qs := models.Orm.QueryTable(new(models.BackendErpEmployee)).Filter("delete_time__isnull", true) - if tid > 0 { - qs = qs.Filter("tid", tid) - } - - var rows []models.BackendErpEmployee - _, err := qs.OrderBy("-id").All(&rows) - if err != nil { - c.jsonError(500, "查询员工失败: "+err.Error()) - return - } - - list := make([]erpEmployeeDTO, 0, len(rows)) - for _, row := range rows { - list = append(list, c.employeeDTO(row)) - } - - c.jsonOK(list) -} - -// GetEmployeeDetail 获取员工详情。 -// GET /admin/erp/getEmployeeDetail/:id -func (c *BackendErpController) GetEmployeeDetail() { - id, ok := c.pathUint(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - var row models.BackendErpEmployee - err := models.Orm.QueryTable(new(models.BackendErpEmployee)). - Filter("id", id). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.jsonError(404, "员工不存在") - return - } - - c.jsonOK(c.employeeDTO(row)) -} - -// CreateEmployee 创建员工。 -// POST /admin/erp/createEmployee -func (c *BackendErpController) CreateEmployee() { - body := c.parseJSONBody() - - name, _ := c.getStringValue(body, "name") - name = strings.TrimSpace(name) - if name == "" { - c.jsonError(400, "姓名不能为空") - return - } - - account, _ := c.getStringValue(body, "account") - account = strings.TrimSpace(account) - if account == "" { - account = "EMP" + c.nowCompactString() - } - - tid, _ := c.getIntValue(body, "tid", "tenant_id") - gender, hasGender := c.getIntValue(body, "gender", "sex") - status, hasStatus := c.getIntValue(body, "account_status", "status") - password, _ := c.getStringValue(body, "password") - birthday, _ := c.getStringValue(body, "birthday") - affiliateUnit, _ := c.getStringValue(body, "affiliate_unit") - department, _ := c.getStringValue(body, "department") - position, _ := c.getStringValue(body, "position") - education, _ := c.getStringValue(body, "education") - nation, _ := c.getStringValue(body, "nation") - phone, _ := c.getStringValue(body, "phone") - wechat, _ := c.getStringValue(body, "wechat") - email, _ := c.getStringValue(body, "email") - homeAddress, _ := c.getStringValue(body, "home_address") - - row := models.BackendErpEmployee{ - Tid: nullableIntPtr(tid), - Account: account, - Password: hashEmployeePassword(password), - Name: name, - Gender: 0, - Birthday: parseDatePtr(birthday), - AffiliateUnit: strPtrIfNotEmpty(affiliateUnit), - Department: strPtrIfNotEmpty(department), - Position: strPtrIfNotEmpty(position), - Education: strPtrIfNotEmpty(education), - Nation: strPtrIfNotEmpty(nation), - Phone: strPtrIfNotEmpty(phone), - Wechat: strPtrIfNotEmpty(wechat), - Email: strPtrIfNotEmpty(email), - HomeAddress: strPtrIfNotEmpty(homeAddress), - AccountStatus: 1, - } - if hasGender { - row.Gender = int8(gender) - } - if hasStatus { - row.AccountStatus = int8(status) - } - - id, err := models.Orm.Insert(&row) - if err != nil { - c.jsonError(500, "创建员工失败: "+err.Error()) - return - } - - c.jsonOK(map[string]interface{}{"id": id}) -} - -// EditEmployee 更新员工。 -// POST /admin/erp/editEmployee/:id -func (c *BackendErpController) EditEmployee() { - id, ok := c.pathUint(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - body := c.parseJSONBody() - update := orm.Params{} - - if v, has := c.getStringValue(body, "account"); has && strings.TrimSpace(v) != "" { - update["account"] = strings.TrimSpace(v) - } - if v, has := c.getStringValue(body, "name"); has { - v = strings.TrimSpace(v) - if v == "" { - c.jsonError(400, "姓名不能为空") - return - } - update["name"] = v - } - if v, has := c.getIntValue(body, "tid", "tenant_id"); has { - update["tid"] = nullableInt(v) - } - if v, has := c.getIntValue(body, "gender", "sex"); has { - update["gender"] = int8(v) - } - if v, has := c.getStringValue(body, "birthday"); has { - update["birthday"] = parseDatePtr(v) - } - if v, has := c.getStringValue(body, "affiliate_unit"); has { - update["affiliate_unit"] = nullableString(v) - } - if v, has := c.getStringValue(body, "department"); has { - update["department"] = nullableString(v) - } - if v, has := c.getStringValue(body, "position"); has { - update["position"] = nullableString(v) - } - if v, has := c.getStringValue(body, "education"); has { - update["education"] = nullableString(v) - } - if v, has := c.getStringValue(body, "nation"); has { - update["nation"] = nullableString(v) - } - if v, has := c.getStringValue(body, "phone"); has { - update["phone"] = nullableString(v) - } - if v, has := c.getStringValue(body, "wechat"); has { - update["wechat"] = nullableString(v) - } - if v, has := c.getStringValue(body, "email"); has { - update["email"] = nullableString(v) - } - if v, has := c.getStringValue(body, "home_address"); has { - update["home_address"] = nullableString(v) - } - if v, has := c.getIntValue(body, "account_status", "status"); has { - update["account_status"] = int8(v) - } - if v, has := c.getStringValue(body, "password"); has && strings.TrimSpace(v) != "" { - update["password"] = hashEmployeePassword(v) - } - - if len(update) == 0 { - c.jsonError(400, "无更新字段") - return - } - - num, err := models.Orm.QueryTable(new(models.BackendErpEmployee)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(update) - if err != nil { - c.jsonError(500, "更新员工失败: "+err.Error()) - return - } - if num == 0 { - c.jsonError(404, "员工不存在") - return - } - - c.jsonOK(nil) -} - -// DeleteEmployee 删除员工。 -// DELETE /admin/erp/deleteEmployee/:id -func (c *BackendErpController) DeleteEmployee() { - id, ok := c.pathUint(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - num, err := models.Orm.QueryTable(new(models.BackendErpEmployee)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(orm.Params{"delete_time": c.nowString(), "account_status": int8(2)}) - if err != nil { - c.jsonError(500, "删除员工失败: "+err.Error()) - return - } - if num == 0 { - c.jsonError(404, "员工不存在") - return - } - - c.jsonOK(nil) -} - -// GetPosition 获取职位列表。 -// GET /admin/erp/getPosition -func (c *BackendErpController) GetPosition() { - tid, _ := c.GetInt("tid") - departmentID, _ := c.GetInt("department_id") - - qs := models.Orm.QueryTable(new(models.BackendErpPosition)) - if tid > 0 { - qs = qs.Filter("tenant_id", tid) - } - if departmentID > 0 { - qs = qs.Filter("department_id", departmentID) - } - - var rows []models.BackendErpPosition - _, err := qs.OrderBy("sort", "id").All(&rows) - if err != nil { - c.jsonError(500, "查询职位失败: "+err.Error()) - return - } - - list := make([]erpPositionDTO, 0, len(rows)) - for _, row := range rows { - list = append(list, c.positionDTO(row)) - } - - c.jsonOK(list) -} - -// GetPositionDetail 获取职位详情。 -// GET /admin/erp/getPositionDetail/:id -func (c *BackendErpController) GetPositionDetail() { - id, ok := c.pathUint64(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - var row models.BackendErpPosition - err := models.Orm.QueryTable(new(models.BackendErpPosition)).Filter("id", id).One(&row) - if err != nil { - c.jsonError(404, "职位不存在") - return - } - - c.jsonOK(c.positionDTO(row)) -} - -// CreatePosition 创建职位。 -// POST /admin/erp/createPosition -func (c *BackendErpController) CreatePosition() { - body := c.parseJSONBody() - - tenantID, _ := c.getUint64Value(body, "tenant_id", "tid") - departmentID, _ := c.getUint64Value(body, "department_id") - positionName, _ := c.getStringValue(body, "position_name", "name") - positionName = strings.TrimSpace(positionName) - if positionName == "" { - c.jsonError(400, "职位名称不能为空") - return - } - - positionCode, _ := c.getStringValue(body, "position_code", "code") - positionCode = strings.TrimSpace(positionCode) - if positionCode == "" { - positionCode = "POS" + c.nowCompactString() - } - positionType, _ := c.getIntValue(body, "position_type") - status, hasStatus := c.getIntValue(body, "status") - sortVal, _ := c.getUintValue(body, "sort") - - row := models.BackendErpPosition{ - TenantID: tenantID, - DepartmentID: departmentID, - PositionCode: positionCode, - PositionName: positionName, - PositionType: int8(positionType), - Status: 1, - Sort: sortVal, - } - if hasStatus { - row.Status = int8(status) - } - - id, err := models.Orm.Insert(&row) - if err != nil { - c.jsonError(500, "创建职位失败: "+err.Error()) - return - } - - c.jsonOK(map[string]interface{}{"id": id}) -} - -// EditPosition 更新职位。 -// POST /admin/erp/editPosition/:id -func (c *BackendErpController) EditPosition() { - id, ok := c.pathUint64(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - body := c.parseJSONBody() - update := orm.Params{} - - if v, has := c.getUint64Value(body, "tenant_id", "tid"); has { - update["tenant_id"] = v - } - if v, has := c.getUint64Value(body, "department_id"); has { - update["department_id"] = v - } - if v, has := c.getStringValue(body, "position_code", "code"); has { - update["position_code"] = strings.TrimSpace(v) - } - if v, has := c.getStringValue(body, "position_name", "name"); has { - v = strings.TrimSpace(v) - if v == "" { - c.jsonError(400, "职位名称不能为空") - return - } - update["position_name"] = v - } - if v, has := c.getIntValue(body, "position_type"); has { - update["position_type"] = int8(v) - } - if v, has := c.getIntValue(body, "status"); has { - update["status"] = int8(v) - } - if v, has := c.getUintValue(body, "sort"); has { - update["sort"] = v - } - - if len(update) == 0 { - c.jsonError(400, "无更新字段") - return - } - - num, err := models.Orm.QueryTable(new(models.BackendErpPosition)).Filter("id", id).Update(update) - if err != nil { - c.jsonError(500, "更新职位失败: "+err.Error()) - return - } - if num == 0 { - c.jsonError(404, "职位不存在") - return - } - - c.jsonOK(nil) -} - -// DeletePosition 删除职位。 -// DELETE /admin/erp/deletePosition/:id -func (c *BackendErpController) DeletePosition() { - id, ok := c.pathUint64(":id") - if !ok { - c.jsonError(400, "无效ID") - return - } - - num, err := models.Orm.QueryTable(new(models.BackendErpPosition)).Filter("id", id).Delete() - if err != nil { - c.jsonError(500, "删除职位失败: "+err.Error()) - return - } - if num == 0 { - c.jsonError(404, "职位不存在") - return - } - - c.jsonOK(nil) -} - -func (c *BackendErpController) organizationDTO(row models.BackendErpOrganization) erpOrganizationDTO { - parentName := "" - if row.ParentID > 0 { - var parent models.BackendErpOrganization - if err := models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("id", row.ParentID). - One(&parent); err == nil { - parentName = parent.OrgName - } - } - - leaderID := uint64(0) - leaderName := "" - if row.LeaderID != nil { - leaderID = *row.LeaderID - var employee models.BackendErpEmployee - if err := models.Orm.QueryTable(new(models.BackendErpEmployee)). - Filter("id", leaderID). - One(&employee); err == nil { - leaderName = employee.Name - } - } - - return erpOrganizationDTO{ - ID: row.ID, - Tid: row.Tid, - TenantID: row.Tid, - OrgName: row.OrgName, - OrgCode: row.OrgCode, - ParentID: row.ParentID, - ParentName: parentName, - LeaderID: leaderID, - LeaderName: leaderName, - IsCompany: row.IsCompany, - Sort: row.Sort, - Status: row.Status, - Remark: derefString(row.Remark), - } -} - -func (c *BackendErpController) employeeDTO(row models.BackendErpEmployee) erpEmployeeDTO { - tid := 0 - if row.Tid != nil { - tid = *row.Tid - } - - birthday := "" - if row.Birthday != nil { - birthday = row.Birthday.Format("2006-01-02") - } - - affiliateUnit := derefString(row.AffiliateUnit) - department := derefString(row.Department) - affiliateUnitName := c.organizationNameByIDString(affiliateUnit) - departmentName := c.organizationNameByIDString(department) - - return erpEmployeeDTO{ - ID: row.ID, - Tid: tid, - TenantID: tid, - Account: row.Account, - Name: row.Name, - Gender: row.Gender, - Sex: row.Gender, - Birthday: birthday, - AffiliateUnit: affiliateUnit, - AffiliateUnitName: affiliateUnitName, - Department: department, - DepartmentName: departmentName, - Position: derefString(row.Position), - Education: derefString(row.Education), - Nation: derefString(row.Nation), - Phone: derefString(row.Phone), - Wechat: derefString(row.Wechat), - Email: derefString(row.Email), - HomeAddress: derefString(row.HomeAddress), - AccountStatus: row.AccountStatus, - Status: row.AccountStatus, - } -} - -func (c *BackendErpController) organizationNameByIDString(idValue string) string { - idValue = strings.TrimSpace(idValue) - if idValue == "" { - return "" - } - - id, err := strconv.ParseUint(idValue, 10, 64) - if err != nil || id == 0 { - return "" - } - - var org models.BackendErpOrganization - err = models.Orm.QueryTable(new(models.BackendErpOrganization)). - Filter("id", id). - Filter("delete_time__isnull", true). - One(&org) - if err != nil { - return "" - } - - return org.OrgName -} - -func (c *BackendErpController) positionDTO(row models.BackendErpPosition) erpPositionDTO { - return erpPositionDTO{ - ID: row.ID, - TenantID: row.TenantID, - Tid: row.TenantID, - DepartmentID: row.DepartmentID, - PositionCode: row.PositionCode, - PositionName: row.PositionName, - PositionType: row.PositionType, - Status: row.Status, - Sort: row.Sort, - } -} - -func (c *BackendErpController) parseJSONBody() map[string]interface{} { - body := map[string]interface{}{} - contentType := strings.ToLower(c.Ctx.Input.Header("Content-Type")) - if !strings.Contains(contentType, "json") { - return body - } - if len(c.Ctx.Input.RequestBody) == 0 { - return body - } - _ = json.Unmarshal(c.Ctx.Input.RequestBody, &body) - return body -} - -func (c *BackendErpController) getStringValue(body map[string]interface{}, keys ...string) (string, bool) { - for _, key := range keys { - if v, ok := body[key]; ok { - switch val := v.(type) { - case string: - return val, true - case float64: - return strconv.FormatFloat(val, 'f', -1, 64), true - case bool: - return strconv.FormatBool(val), true - default: - return strings.TrimSpace(strings.Trim(strings.ReplaceAll(strings.ReplaceAll(toJSON(val), "\n", ""), "\r", ""), "\"")), true - } - } - if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { - _ = c.Ctx.Request.ParseMultipartForm(32 << 20) - } - if val := c.GetString(key); val != "" { - return val, true - } - } - return "", false -} - -func (c *BackendErpController) getIntValue(body map[string]interface{}, keys ...string) (int, bool) { - for _, key := range keys { - if v, ok := body[key]; ok { - switch val := v.(type) { - case float64: - return int(val), true - case int: - return val, true - case string: - if strings.TrimSpace(val) == "" { - return 0, true - } - parsed, err := strconv.Atoi(strings.TrimSpace(val)) - return parsed, err == nil - } - } - if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { - _ = c.Ctx.Request.ParseMultipartForm(32 << 20) - } - if val := c.GetString(key); val != "" { - parsed, err := strconv.Atoi(strings.TrimSpace(val)) - return parsed, err == nil - } - } - return 0, false -} - -func (c *BackendErpController) getUintValue(body map[string]interface{}, keys ...string) (uint, bool) { - v, ok := c.getIntValue(body, keys...) - if !ok || v < 0 { - return 0, ok - } - return uint(v), true -} - -func (c *BackendErpController) getUint64Value(body map[string]interface{}, keys ...string) (uint64, bool) { - for _, key := range keys { - if v, ok := body[key]; ok { - switch val := v.(type) { - case float64: - if val < 0 { - return 0, false - } - return uint64(val), true - case int: - if val < 0 { - return 0, false - } - return uint64(val), true - case string: - if strings.TrimSpace(val) == "" { - return 0, true - } - parsed, err := strconv.ParseUint(strings.TrimSpace(val), 10, 64) - return parsed, err == nil - } - } - if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { - _ = c.Ctx.Request.ParseMultipartForm(32 << 20) - } - if val := c.GetString(key); val != "" { - parsed, err := strconv.ParseUint(strings.TrimSpace(val), 10, 64) - return parsed, err == nil - } - } - return 0, false -} - -func (c *BackendErpController) pathUint(name string) (uint, bool) { - id, err := strconv.ParseUint(c.Ctx.Input.Param(name), 10, 64) - return uint(id), err == nil && id > 0 -} - -func (c *BackendErpController) pathUint64(name string) (uint64, bool) { - id, err := strconv.ParseUint(c.Ctx.Input.Param(name), 10, 64) - return id, err == nil && id > 0 -} - -func (c *BackendErpController) jsonOK(data interface{}) { - resp := map[string]interface{}{"code": 200, "msg": "success"} - if data != nil { - resp["data"] = data - } - c.Data["json"] = resp - _ = c.ServeJSON() -} - -func (c *BackendErpController) jsonError(code int, msg string) { - c.Data["json"] = map[string]interface{}{"code": code, "msg": msg} - _ = c.ServeJSON() -} - -func (c *BackendErpController) nowString() string { - return time.Now().Format("2006-01-02 15:04:05") -} - -func (c *BackendErpController) nowCompactString() string { - return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(c.nowString(), "-", ""), ":", ""), " ", "") -} - -func strPtrIfNotEmpty(v string) *string { - v = strings.TrimSpace(v) - if v == "" { - return nil - } - return &v -} - -func nullableString(v string) interface{} { - v = strings.TrimSpace(v) - if v == "" { - return nil - } - return v -} - -func nullableInt(v int) interface{} { - if v <= 0 { - return nil - } - return v -} - -func nullableIntPtr(v int) *int { - if v <= 0 { - return nil - } - return &v -} - -func nullableUint64(v uint64) interface{} { - if v == 0 { - return nil - } - return v -} - -func derefString(v *string) string { - if v == nil { - return "" - } - return *v -} - -func boolInt(v bool) int { - if v { - return 1 - } - return 0 -} - -func toJSON(v interface{}) string { - b, _ := json.Marshal(v) - return string(b) -} - -func parseDatePtr(v string) *time.Time { - v = strings.TrimSpace(v) - if v == "" { - return nil - } - if t, err := time.Parse("2006-01-02", v); err == nil { - return &t - } - if t, err := time.Parse("2006-01-02 15:04:05", v); err == nil { - return &t - } - return nil -} - -// hashEmployeePassword 适配 yz_backend_erp_employee.password varchar(64),使用 sha256 hex。 -// 如果密码为空则返回空字符串,符合表默认值。 -func hashEmployeePassword(plain string) string { - plain = strings.TrimSpace(plain) - if plain == "" { - return "" - } - sum := sha256.Sum256([]byte(plain)) - return hex.EncodeToString(sum[:]) -} +package controllers + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "strconv" + "strings" + "time" + + "server/models" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// BackendErpController 兼容 backend 前端 /admin/erp/* 组织机构、员工、职位接口。 +type BackendErpController struct { + beego.Controller +} + +type erpOrganizationDTO struct { + ID uint64 `json:"id"` + Tid uint64 `json:"tid"` + TenantID uint64 `json:"tenant_id"` + OrgName string `json:"org_name"` + OrgCode string `json:"org_code"` + ParentID uint64 `json:"parent_id"` + ParentName string `json:"parent_name"` + LeaderID uint64 `json:"leader_id"` + LeaderName string `json:"leader_name"` + IsCompany int `json:"is_company"` + Sort uint `json:"sort"` + Status int8 `json:"status"` + Remark string `json:"remark"` +} + +type erpEmployeeDTO struct { + ID uint `json:"id"` + Tid int `json:"tid"` + TenantID int `json:"tenant_id"` + Account string `json:"account"` + Name string `json:"name"` + Gender int8 `json:"gender"` + Sex int8 `json:"sex"` + Birthday string `json:"birthday"` + AffiliateUnit string `json:"affiliate_unit"` + AffiliateUnitName string `json:"affiliate_unit_name"` + Department string `json:"department"` + DepartmentName string `json:"department_name"` + Position string `json:"position"` + Education string `json:"education"` + Nation string `json:"nation"` + Phone string `json:"phone"` + Wechat string `json:"wechat"` + Email string `json:"email"` + HomeAddress string `json:"home_address"` + AccountStatus int8 `json:"account_status"` + Status int8 `json:"status"` +} + +type erpPositionDTO struct { + ID uint64 `json:"id"` + TenantID uint64 `json:"tenant_id"` + Tid uint64 `json:"tid"` + DepartmentID uint64 `json:"department_id"` + PositionCode string `json:"position_code"` + PositionName string `json:"position_name"` + PositionType int8 `json:"position_type"` + Status int8 `json:"status"` + Sort uint `json:"sort"` +} + +// GetOrganization 获取组织机构列表。 +// GET /admin/erp/getOrganization +func (c *BackendErpController) GetOrganization() { + tid, _ := c.GetInt("tid") + + qs := models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("delete_time__isnull", true). + Exclude("status", 0) + if tid > 0 { + qs = qs.Filter("tid", tid) + } + + var rows []models.BackendErpOrganization + _, err := qs.OrderBy("sort", "id").All(&rows) + if err != nil { + c.jsonError(500, "查询组织机构失败: "+err.Error()) + return + } + + list := make([]erpOrganizationDTO, 0, len(rows)) + for _, row := range rows { + list = append(list, c.organizationDTO(row)) + } + + c.jsonOK(list) +} + +// GetOrganizationDetail 获取组织机构详情。 +// GET /admin/erp/getOrganizationDetail/:id +func (c *BackendErpController) GetOrganizationDetail() { + id, ok := c.pathUint64(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + var row models.BackendErpOrganization + err := models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("id", id). + Filter("delete_time__isnull", true). + Exclude("status", 0). + One(&row) + if err != nil { + c.jsonError(404, "组织机构不存在") + return + } + + c.jsonOK(c.organizationDTO(row)) +} + +// CreateOrganization 创建组织机构。 +// POST /admin/erp/createOrganization +func (c *BackendErpController) CreateOrganization() { + body := c.parseJSONBody() + + orgName, _ := c.getStringValue(body, "org_name", "name") + orgName = strings.TrimSpace(orgName) + if orgName == "" { + c.jsonError(400, "组织名称不能为空") + return + } + + orgCode, _ := c.getStringValue(body, "org_code", "code") + orgCode = strings.TrimSpace(orgCode) + if orgCode == "" { + orgCode = "ORG" + c.nowCompactString() + } + + tid, _ := c.getUint64Value(body, "tid", "tenant_id") + parentID, _ := c.getUint64Value(body, "parent_id") + leaderID, hasLeader := c.getUint64Value(body, "leader_id") + sortVal, _ := c.getUintValue(body, "sort") + isCompany, hasCompany := c.getIntValue(body, "is_company") + status, hasStatus := c.getIntValue(body, "status") + remark, _ := c.getStringValue(body, "remark") + + row := models.BackendErpOrganization{ + Tid: tid, + OrgName: orgName, + OrgCode: orgCode, + ParentID: parentID, + Sort: sortVal, + IsCompany: boolInt(parentID == 0), + Status: 1, + Remark: strPtrIfNotEmpty(remark), + } + if hasLeader && leaderID > 0 { + row.LeaderID = &leaderID + } + if hasCompany { + row.IsCompany = isCompany + } + if hasStatus { + row.Status = int8(status) + } + + id, err := models.Orm.Insert(&row) + if err != nil { + c.jsonError(500, "创建组织机构失败: "+err.Error()) + return + } + + c.jsonOK(map[string]interface{}{"id": id}) +} + +// EditOrganization 更新组织机构。 +// POST /admin/erp/editOrganization/:id +func (c *BackendErpController) EditOrganization() { + id, ok := c.pathUint64(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + body := c.parseJSONBody() + update := orm.Params{} + + if v, has := c.getStringValue(body, "org_name", "name"); has { + v = strings.TrimSpace(v) + if v == "" { + c.jsonError(400, "组织名称不能为空") + return + } + update["org_name"] = v + } + if v, has := c.getStringValue(body, "org_code", "code"); has { + update["org_code"] = strings.TrimSpace(v) + } + if v, has := c.getUint64Value(body, "parent_id"); has { + update["parent_id"] = v + } + if v, has := c.getUint64Value(body, "tid", "tenant_id"); has { + update["tid"] = v + } + if v, has := c.getUint64Value(body, "leader_id"); has { + update["leader_id"] = nullableUint64(v) + } + if v, has := c.getUintValue(body, "sort"); has { + update["sort"] = v + } + if v, has := c.getIntValue(body, "is_company"); has { + update["is_company"] = v + } + if v, has := c.getIntValue(body, "status"); has { + update["status"] = int8(v) + } + if v, has := c.getStringValue(body, "remark"); has { + update["remark"] = nullableString(v) + } + + if len(update) == 0 { + c.jsonError(400, "无更新字段") + return + } + + num, err := models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(update) + if err != nil { + c.jsonError(500, "更新组织机构失败: "+err.Error()) + return + } + if num == 0 { + c.jsonError(404, "组织机构不存在") + return + } + + c.jsonOK(nil) +} + +// DeleteOrganization 删除组织机构。 +// DELETE /admin/erp/deleteOrganization/:id +func (c *BackendErpController) DeleteOrganization() { + id, ok := c.pathUint64(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + childCount, err := models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("parent_id", id). + Filter("delete_time__isnull", true). + Exclude("status", 0). + Count() + if err != nil { + c.jsonError(500, "检查子组织失败: "+err.Error()) + return + } + if childCount > 0 { + c.jsonError(400, "请先删除下级组织") + return + } + + now := c.nowString() + num, err := models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(orm.Params{"delete_time": now, "status": int8(0)}) + if err != nil { + c.jsonError(500, "删除组织机构失败: "+err.Error()) + return + } + if num == 0 { + c.jsonError(404, "组织机构不存在") + return + } + + c.jsonOK(nil) +} + +// GetCompanys 获取企业单位列表。 +// GET /admin/erp/getCompanys +func (c *BackendErpController) GetCompanys() { + tid, _ := c.GetInt("tid") + + qs := models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("delete_time__isnull", true). + Exclude("status", 0). + Filter("is_company", 1) + if tid > 0 { + qs = qs.Filter("tid", tid) + } + + var rows []models.BackendErpOrganization + _, err := qs.OrderBy("sort", "id").All(&rows) + if err != nil { + c.jsonError(500, "查询企业单位失败: "+err.Error()) + return + } + + list := make([]erpOrganizationDTO, 0, len(rows)) + for _, row := range rows { + list = append(list, c.organizationDTO(row)) + } + + c.jsonOK(list) +} + +// GetDepartments 获取部门列表。 +// GET /admin/erp/getDepartments?parent_id=1 +func (c *BackendErpController) GetDepartments() { + parentID, _ := c.GetInt("parent_id") + tid, _ := c.GetInt("tid") + + qs := models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("delete_time__isnull", true). + Exclude("status", 0) + if parentID > 0 { + qs = qs.Filter("parent_id", parentID) + } else { + qs = qs.Filter("is_company", 0) + } + if tid > 0 { + qs = qs.Filter("tid", tid) + } + + var rows []models.BackendErpOrganization + _, err := qs.OrderBy("sort", "id").All(&rows) + if err != nil { + c.jsonError(500, "查询部门失败: "+err.Error()) + return + } + + list := make([]erpOrganizationDTO, 0, len(rows)) + for _, row := range rows { + list = append(list, c.organizationDTO(row)) + } + + c.jsonOK(list) +} + +// GetEmployee 获取员工列表。 +// GET /admin/erp/getEmployee?tid=1 +func (c *BackendErpController) GetEmployee() { + tid, _ := c.GetInt("tid") + + qs := models.Orm.QueryTable(new(models.BackendErpEmployee)).Filter("delete_time__isnull", true) + if tid > 0 { + qs = qs.Filter("tid", tid) + } + + var rows []models.BackendErpEmployee + _, err := qs.OrderBy("-id").All(&rows) + if err != nil { + c.jsonError(500, "查询员工失败: "+err.Error()) + return + } + + list := make([]erpEmployeeDTO, 0, len(rows)) + for _, row := range rows { + list = append(list, c.employeeDTO(row)) + } + + c.jsonOK(list) +} + +// GetEmployeeDetail 获取员工详情。 +// GET /admin/erp/getEmployeeDetail/:id +func (c *BackendErpController) GetEmployeeDetail() { + id, ok := c.pathUint(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + var row models.BackendErpEmployee + err := models.Orm.QueryTable(new(models.BackendErpEmployee)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.jsonError(404, "员工不存在") + return + } + + c.jsonOK(c.employeeDTO(row)) +} + +// CreateEmployee 创建员工。 +// POST /admin/erp/createEmployee +func (c *BackendErpController) CreateEmployee() { + body := c.parseJSONBody() + + name, _ := c.getStringValue(body, "name") + name = strings.TrimSpace(name) + if name == "" { + c.jsonError(400, "姓名不能为空") + return + } + + account, _ := c.getStringValue(body, "account") + account = strings.TrimSpace(account) + if account == "" { + account = "EMP" + c.nowCompactString() + } + + tid, _ := c.getIntValue(body, "tid", "tenant_id") + gender, hasGender := c.getIntValue(body, "gender", "sex") + status, hasStatus := c.getIntValue(body, "account_status", "status") + password, _ := c.getStringValue(body, "password") + birthday, _ := c.getStringValue(body, "birthday") + affiliateUnit, _ := c.getStringValue(body, "affiliate_unit") + department, _ := c.getStringValue(body, "department") + position, _ := c.getStringValue(body, "position") + education, _ := c.getStringValue(body, "education") + nation, _ := c.getStringValue(body, "nation") + phone, _ := c.getStringValue(body, "phone") + wechat, _ := c.getStringValue(body, "wechat") + email, _ := c.getStringValue(body, "email") + homeAddress, _ := c.getStringValue(body, "home_address") + + row := models.BackendErpEmployee{ + Tid: nullableIntPtr(tid), + Account: account, + Password: hashEmployeePassword(password), + Name: name, + Gender: 0, + Birthday: parseDatePtr(birthday), + AffiliateUnit: strPtrIfNotEmpty(affiliateUnit), + Department: strPtrIfNotEmpty(department), + Position: strPtrIfNotEmpty(position), + Education: strPtrIfNotEmpty(education), + Nation: strPtrIfNotEmpty(nation), + Phone: strPtrIfNotEmpty(phone), + Wechat: strPtrIfNotEmpty(wechat), + Email: strPtrIfNotEmpty(email), + HomeAddress: strPtrIfNotEmpty(homeAddress), + AccountStatus: 1, + } + if hasGender { + row.Gender = int8(gender) + } + if hasStatus { + row.AccountStatus = int8(status) + } + + id, err := models.Orm.Insert(&row) + if err != nil { + c.jsonError(500, "创建员工失败: "+err.Error()) + return + } + + c.jsonOK(map[string]interface{}{"id": id}) +} + +// EditEmployee 更新员工。 +// POST /admin/erp/editEmployee/:id +func (c *BackendErpController) EditEmployee() { + id, ok := c.pathUint(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + body := c.parseJSONBody() + update := orm.Params{} + + if v, has := c.getStringValue(body, "account"); has && strings.TrimSpace(v) != "" { + update["account"] = strings.TrimSpace(v) + } + if v, has := c.getStringValue(body, "name"); has { + v = strings.TrimSpace(v) + if v == "" { + c.jsonError(400, "姓名不能为空") + return + } + update["name"] = v + } + if v, has := c.getIntValue(body, "tid", "tenant_id"); has { + update["tid"] = nullableInt(v) + } + if v, has := c.getIntValue(body, "gender", "sex"); has { + update["gender"] = int8(v) + } + if v, has := c.getStringValue(body, "birthday"); has { + update["birthday"] = parseDatePtr(v) + } + if v, has := c.getStringValue(body, "affiliate_unit"); has { + update["affiliate_unit"] = nullableString(v) + } + if v, has := c.getStringValue(body, "department"); has { + update["department"] = nullableString(v) + } + if v, has := c.getStringValue(body, "position"); has { + update["position"] = nullableString(v) + } + if v, has := c.getStringValue(body, "education"); has { + update["education"] = nullableString(v) + } + if v, has := c.getStringValue(body, "nation"); has { + update["nation"] = nullableString(v) + } + if v, has := c.getStringValue(body, "phone"); has { + update["phone"] = nullableString(v) + } + if v, has := c.getStringValue(body, "wechat"); has { + update["wechat"] = nullableString(v) + } + if v, has := c.getStringValue(body, "email"); has { + update["email"] = nullableString(v) + } + if v, has := c.getStringValue(body, "home_address"); has { + update["home_address"] = nullableString(v) + } + if v, has := c.getIntValue(body, "account_status", "status"); has { + update["account_status"] = int8(v) + } + if v, has := c.getStringValue(body, "password"); has && strings.TrimSpace(v) != "" { + update["password"] = hashEmployeePassword(v) + } + + if len(update) == 0 { + c.jsonError(400, "无更新字段") + return + } + + num, err := models.Orm.QueryTable(new(models.BackendErpEmployee)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(update) + if err != nil { + c.jsonError(500, "更新员工失败: "+err.Error()) + return + } + if num == 0 { + c.jsonError(404, "员工不存在") + return + } + + c.jsonOK(nil) +} + +// DeleteEmployee 删除员工。 +// DELETE /admin/erp/deleteEmployee/:id +func (c *BackendErpController) DeleteEmployee() { + id, ok := c.pathUint(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + num, err := models.Orm.QueryTable(new(models.BackendErpEmployee)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(orm.Params{"delete_time": c.nowString(), "account_status": int8(2)}) + if err != nil { + c.jsonError(500, "删除员工失败: "+err.Error()) + return + } + if num == 0 { + c.jsonError(404, "员工不存在") + return + } + + c.jsonOK(nil) +} + +// GetPosition 获取职位列表。 +// GET /admin/erp/getPosition +func (c *BackendErpController) GetPosition() { + tid, _ := c.GetInt("tid") + departmentID, _ := c.GetInt("department_id") + + qs := models.Orm.QueryTable(new(models.BackendErpPosition)) + if tid > 0 { + qs = qs.Filter("tenant_id", tid) + } + if departmentID > 0 { + qs = qs.Filter("department_id", departmentID) + } + + var rows []models.BackendErpPosition + _, err := qs.OrderBy("sort", "id").All(&rows) + if err != nil { + c.jsonError(500, "查询职位失败: "+err.Error()) + return + } + + list := make([]erpPositionDTO, 0, len(rows)) + for _, row := range rows { + list = append(list, c.positionDTO(row)) + } + + c.jsonOK(list) +} + +// GetPositionDetail 获取职位详情。 +// GET /admin/erp/getPositionDetail/:id +func (c *BackendErpController) GetPositionDetail() { + id, ok := c.pathUint64(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + var row models.BackendErpPosition + err := models.Orm.QueryTable(new(models.BackendErpPosition)).Filter("id", id).One(&row) + if err != nil { + c.jsonError(404, "职位不存在") + return + } + + c.jsonOK(c.positionDTO(row)) +} + +// CreatePosition 创建职位。 +// POST /admin/erp/createPosition +func (c *BackendErpController) CreatePosition() { + body := c.parseJSONBody() + + tenantID, _ := c.getUint64Value(body, "tenant_id", "tid") + departmentID, _ := c.getUint64Value(body, "department_id") + positionName, _ := c.getStringValue(body, "position_name", "name") + positionName = strings.TrimSpace(positionName) + if positionName == "" { + c.jsonError(400, "职位名称不能为空") + return + } + + positionCode, _ := c.getStringValue(body, "position_code", "code") + positionCode = strings.TrimSpace(positionCode) + if positionCode == "" { + positionCode = "POS" + c.nowCompactString() + } + positionType, _ := c.getIntValue(body, "position_type") + status, hasStatus := c.getIntValue(body, "status") + sortVal, _ := c.getUintValue(body, "sort") + + row := models.BackendErpPosition{ + TenantID: tenantID, + DepartmentID: departmentID, + PositionCode: positionCode, + PositionName: positionName, + PositionType: int8(positionType), + Status: 1, + Sort: sortVal, + } + if hasStatus { + row.Status = int8(status) + } + + id, err := models.Orm.Insert(&row) + if err != nil { + c.jsonError(500, "创建职位失败: "+err.Error()) + return + } + + c.jsonOK(map[string]interface{}{"id": id}) +} + +// EditPosition 更新职位。 +// POST /admin/erp/editPosition/:id +func (c *BackendErpController) EditPosition() { + id, ok := c.pathUint64(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + body := c.parseJSONBody() + update := orm.Params{} + + if v, has := c.getUint64Value(body, "tenant_id", "tid"); has { + update["tenant_id"] = v + } + if v, has := c.getUint64Value(body, "department_id"); has { + update["department_id"] = v + } + if v, has := c.getStringValue(body, "position_code", "code"); has { + update["position_code"] = strings.TrimSpace(v) + } + if v, has := c.getStringValue(body, "position_name", "name"); has { + v = strings.TrimSpace(v) + if v == "" { + c.jsonError(400, "职位名称不能为空") + return + } + update["position_name"] = v + } + if v, has := c.getIntValue(body, "position_type"); has { + update["position_type"] = int8(v) + } + if v, has := c.getIntValue(body, "status"); has { + update["status"] = int8(v) + } + if v, has := c.getUintValue(body, "sort"); has { + update["sort"] = v + } + + if len(update) == 0 { + c.jsonError(400, "无更新字段") + return + } + + num, err := models.Orm.QueryTable(new(models.BackendErpPosition)).Filter("id", id).Update(update) + if err != nil { + c.jsonError(500, "更新职位失败: "+err.Error()) + return + } + if num == 0 { + c.jsonError(404, "职位不存在") + return + } + + c.jsonOK(nil) +} + +// DeletePosition 删除职位。 +// DELETE /admin/erp/deletePosition/:id +func (c *BackendErpController) DeletePosition() { + id, ok := c.pathUint64(":id") + if !ok { + c.jsonError(400, "无效ID") + return + } + + num, err := models.Orm.QueryTable(new(models.BackendErpPosition)).Filter("id", id).Delete() + if err != nil { + c.jsonError(500, "删除职位失败: "+err.Error()) + return + } + if num == 0 { + c.jsonError(404, "职位不存在") + return + } + + c.jsonOK(nil) +} + +func (c *BackendErpController) organizationDTO(row models.BackendErpOrganization) erpOrganizationDTO { + parentName := "" + if row.ParentID > 0 { + var parent models.BackendErpOrganization + if err := models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("id", row.ParentID). + One(&parent); err == nil { + parentName = parent.OrgName + } + } + + leaderID := uint64(0) + leaderName := "" + if row.LeaderID != nil { + leaderID = *row.LeaderID + var employee models.BackendErpEmployee + if err := models.Orm.QueryTable(new(models.BackendErpEmployee)). + Filter("id", leaderID). + One(&employee); err == nil { + leaderName = employee.Name + } + } + + return erpOrganizationDTO{ + ID: row.ID, + Tid: row.Tid, + TenantID: row.Tid, + OrgName: row.OrgName, + OrgCode: row.OrgCode, + ParentID: row.ParentID, + ParentName: parentName, + LeaderID: leaderID, + LeaderName: leaderName, + IsCompany: row.IsCompany, + Sort: row.Sort, + Status: row.Status, + Remark: derefString(row.Remark), + } +} + +func (c *BackendErpController) employeeDTO(row models.BackendErpEmployee) erpEmployeeDTO { + tid := 0 + if row.Tid != nil { + tid = *row.Tid + } + + birthday := "" + if row.Birthday != nil { + birthday = row.Birthday.Format("2006-01-02") + } + + affiliateUnit := derefString(row.AffiliateUnit) + department := derefString(row.Department) + affiliateUnitName := c.organizationNameByIDString(affiliateUnit) + departmentName := c.organizationNameByIDString(department) + + return erpEmployeeDTO{ + ID: row.ID, + Tid: tid, + TenantID: tid, + Account: row.Account, + Name: row.Name, + Gender: row.Gender, + Sex: row.Gender, + Birthday: birthday, + AffiliateUnit: affiliateUnit, + AffiliateUnitName: affiliateUnitName, + Department: department, + DepartmentName: departmentName, + Position: derefString(row.Position), + Education: derefString(row.Education), + Nation: derefString(row.Nation), + Phone: derefString(row.Phone), + Wechat: derefString(row.Wechat), + Email: derefString(row.Email), + HomeAddress: derefString(row.HomeAddress), + AccountStatus: row.AccountStatus, + Status: row.AccountStatus, + } +} + +func (c *BackendErpController) organizationNameByIDString(idValue string) string { + idValue = strings.TrimSpace(idValue) + if idValue == "" { + return "" + } + + id, err := strconv.ParseUint(idValue, 10, 64) + if err != nil || id == 0 { + return "" + } + + var org models.BackendErpOrganization + err = models.Orm.QueryTable(new(models.BackendErpOrganization)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&org) + if err != nil { + return "" + } + + return org.OrgName +} + +func (c *BackendErpController) positionDTO(row models.BackendErpPosition) erpPositionDTO { + return erpPositionDTO{ + ID: row.ID, + TenantID: row.TenantID, + Tid: row.TenantID, + DepartmentID: row.DepartmentID, + PositionCode: row.PositionCode, + PositionName: row.PositionName, + PositionType: row.PositionType, + Status: row.Status, + Sort: row.Sort, + } +} + +func (c *BackendErpController) parseJSONBody() map[string]interface{} { + body := map[string]interface{}{} + contentType := strings.ToLower(c.Ctx.Input.Header("Content-Type")) + if !strings.Contains(contentType, "json") { + return body + } + if len(c.Ctx.Input.RequestBody) == 0 { + return body + } + _ = json.Unmarshal(c.Ctx.Input.RequestBody, &body) + return body +} + +func (c *BackendErpController) getStringValue(body map[string]interface{}, keys ...string) (string, bool) { + for _, key := range keys { + if v, ok := body[key]; ok { + switch val := v.(type) { + case string: + return val, true + case float64: + return strconv.FormatFloat(val, 'f', -1, 64), true + case bool: + return strconv.FormatBool(val), true + default: + return strings.TrimSpace(strings.Trim(strings.ReplaceAll(strings.ReplaceAll(toJSON(val), "\n", ""), "\r", ""), "\"")), true + } + } + if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { + _ = c.Ctx.Request.ParseMultipartForm(32 << 20) + } + if val := c.GetString(key); val != "" { + return val, true + } + } + return "", false +} + +func (c *BackendErpController) getIntValue(body map[string]interface{}, keys ...string) (int, bool) { + for _, key := range keys { + if v, ok := body[key]; ok { + switch val := v.(type) { + case float64: + return int(val), true + case int: + return val, true + case string: + if strings.TrimSpace(val) == "" { + return 0, true + } + parsed, err := strconv.Atoi(strings.TrimSpace(val)) + return parsed, err == nil + } + } + if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { + _ = c.Ctx.Request.ParseMultipartForm(32 << 20) + } + if val := c.GetString(key); val != "" { + parsed, err := strconv.Atoi(strings.TrimSpace(val)) + return parsed, err == nil + } + } + return 0, false +} + +func (c *BackendErpController) getUintValue(body map[string]interface{}, keys ...string) (uint, bool) { + v, ok := c.getIntValue(body, keys...) + if !ok || v < 0 { + return 0, ok + } + return uint(v), true +} + +func (c *BackendErpController) getUint64Value(body map[string]interface{}, keys ...string) (uint64, bool) { + for _, key := range keys { + if v, ok := body[key]; ok { + switch val := v.(type) { + case float64: + if val < 0 { + return 0, false + } + return uint64(val), true + case int: + if val < 0 { + return 0, false + } + return uint64(val), true + case string: + if strings.TrimSpace(val) == "" { + return 0, true + } + parsed, err := strconv.ParseUint(strings.TrimSpace(val), 10, 64) + return parsed, err == nil + } + } + if c.Ctx.Request.Form == nil && c.Ctx.Request.PostForm == nil && c.Ctx.Request.MultipartForm == nil { + _ = c.Ctx.Request.ParseMultipartForm(32 << 20) + } + if val := c.GetString(key); val != "" { + parsed, err := strconv.ParseUint(strings.TrimSpace(val), 10, 64) + return parsed, err == nil + } + } + return 0, false +} + +func (c *BackendErpController) pathUint(name string) (uint, bool) { + id, err := strconv.ParseUint(c.Ctx.Input.Param(name), 10, 64) + return uint(id), err == nil && id > 0 +} + +func (c *BackendErpController) pathUint64(name string) (uint64, bool) { + id, err := strconv.ParseUint(c.Ctx.Input.Param(name), 10, 64) + return id, err == nil && id > 0 +} + +func (c *BackendErpController) jsonOK(data interface{}) { + resp := map[string]interface{}{"code": 200, "msg": "success"} + if data != nil { + resp["data"] = data + } + c.Data["json"] = resp + _ = c.ServeJSON() +} + +func (c *BackendErpController) jsonError(code int, msg string) { + c.Data["json"] = map[string]interface{}{"code": code, "msg": msg} + _ = c.ServeJSON() +} + +func (c *BackendErpController) nowString() string { + return time.Now().Format("2006-01-02 15:04:05") +} + +func (c *BackendErpController) nowCompactString() string { + return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(c.nowString(), "-", ""), ":", ""), " ", "") +} + +func strPtrIfNotEmpty(v string) *string { + v = strings.TrimSpace(v) + if v == "" { + return nil + } + return &v +} + +func nullableString(v string) interface{} { + v = strings.TrimSpace(v) + if v == "" { + return nil + } + return v +} + +func nullableInt(v int) interface{} { + if v <= 0 { + return nil + } + return v +} + +func nullableIntPtr(v int) *int { + if v <= 0 { + return nil + } + return &v +} + +func nullableUint64(v uint64) interface{} { + if v == 0 { + return nil + } + return v +} + +func derefString(v *string) string { + if v == nil { + return "" + } + return *v +} + +func boolInt(v bool) int { + if v { + return 1 + } + return 0 +} + +func toJSON(v interface{}) string { + b, _ := json.Marshal(v) + return string(b) +} + +func parseDatePtr(v string) *time.Time { + v = strings.TrimSpace(v) + if v == "" { + return nil + } + if t, err := time.Parse("2006-01-02", v); err == nil { + return &t + } + if t, err := time.Parse("2006-01-02 15:04:05", v); err == nil { + return &t + } + return nil +} + +// hashEmployeePassword 适配 yz_backend_erp_employee.password varchar(64),使用 sha256 hex。 +// 如果密码为空则返回空字符串,符合表默认值。 +func hashEmployeePassword(plain string) string { + plain = strings.TrimSpace(plain) + if plain == "" { + return "" + } + sum := sha256.Sum256([]byte(plain)) + return hex.EncodeToString(sum[:]) +} diff --git a/go/controllers/backend_file.go b/go/controllers/backend_file.go index e7217b6..2d54c34 100644 --- a/go/controllers/backend_file.go +++ b/go/controllers/backend_file.go @@ -1,907 +1,907 @@ -package controllers - -import ( - "crypto/md5" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "os" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -// BackendFileController 平台端文件管理(yz_system_files / yz_system_files_category) -type BackendFileController struct { - beego.Controller -} - -const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 -const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 - -var fileTypeByCategory = map[string]uint8{ - "image": 1, - "document": 2, - "video": 3, - "audio": 4, - "appsupgrade": 2, -} - -var allowedExtByCategory = map[string][]string{ - "image": {"jpg", "jpeg", "png", "gif", "bmp", "webp"}, - "document": {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"}, - "video": {"mp4", "webm", "mov"}, - "audio": {"mp3", "wav", "ogg"}, - // 安装包 / 软件升级(上传时 cate 选 appsupgrade 分类即可,扩展名在此放行) - "appsupgrade": {"zip", "exe", "dmg", "msi", "msix", "apk", "deb", "rpm", "7z", "tar", "gz", "pkg"}, -} - -func (c *BackendFileController) backendClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *BackendFileController) effectiveTid(claims *jwtutil.Claims) uint64 { - _ = c.ParseForm(1 << 20) - if tid, err := c.GetUint64("tid"); err == nil && tid > 0 { - return tid - } - if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" { - if v, e := strconv.ParseUint(h, 10, 64); e == nil { - return v - } - } - if claims != nil && claims.TenantId > 0 { - return uint64(claims.TenantId) - } - return 0 -} - -func (c *BackendFileController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *BackendFileController) jsonOK(data interface{}) { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} - _ = c.ServeJSON() -} - -func detectFileType(ext string) uint8 { - ext = strings.ToLower(strings.TrimPrefix(ext, ".")) - for cat, exts := range allowedExtByCategory { - for _, e := range exts { - if e == ext { - if t, ok := fileTypeByCategory[cat]; ok { - return t - } - return 2 - } - } - } - return 2 -} - -func fileExt(name string) string { - name = strings.TrimSpace(name) - if i := strings.LastIndex(name, "."); i >= 0 && i < len(name)-1 { - return strings.ToLower(name[i+1:]) - } - return "" -} - -func fileToMap(f *models.SystemFile) map[string]interface{} { - ct := f.CreateTime.Format("2006-01-02 15:04:05") - m := map[string]interface{}{ - "id": f.ID, - "tid": f.Tid, - "name": f.Name, - "type": f.Type, - "cate": f.Cate, - "size": f.Size, - "src": f.Src, - "uploader": f.Uploader, - "md5": f.Md5, - "create_time": ct, - "createTime": ct, - "groupId": f.Cate, - "url": f.Src, - } - if f.Uid != nil { - m["uid"] = *f.Uid - } - if f.Tuid != nil { - m["tuid"] = *f.Tuid - } - return m -} - -func removePhysicalBySrc(webSrc string) { - webSrc = strings.TrimSpace(webSrc) - if webSrc == "" { - return - } - webSrc = strings.TrimPrefix(webSrc, "/") - _ = os.Remove(webSrc) -} - -// GetAllFiles GET /backend/allfiles -func (c *BackendFileController) GetAllFiles() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 10 - } - cate, _ := c.GetUint64("cate") - keyword := strings.TrimSpace(c.GetString("keyword")) - - qs := models.Orm.QueryTable(new(models.SystemFile)). - Filter("tid", tid). - Filter("delete_time__isnull", true) - if cate > 0 { - qs = qs.Filter("cate", cate) - } - if keyword != "" { - qs = qs.Filter("name__icontains", keyword) - } - total, err := qs.Count() - if err != nil { - c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) - return - } - var rows []models.SystemFile - _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) - return - } - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - list = append(list, fileToMap(&rows[i])) - } - c.jsonOK(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} - -// GetUserCate GET /backend/usercate -func (c *BackendFileController) GetUserCate() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - - var cates []models.SystemFilesCategory - _, err = models.Orm.QueryTable(new(models.SystemFilesCategory)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - OrderBy("id"). - All(&cates) - if err != nil { - c.jsonErr(500, 500, "获取用户分类失败: "+err.Error()) - return - } - out := make([]map[string]interface{}, 0, len(cates)) - for i := range cates { - cnt, _ := models.Orm.QueryTable(new(models.SystemFile)). - Filter("tid", tid). - Filter("cate", cates[i].ID). - Filter("delete_time__isnull", true). - Count() - out = append(out, map[string]interface{}{ - "id": cates[i].ID, - "name": cates[i].Name, - "total": cnt, - }) - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type createCateBody struct { - Name string `json:"name"` - Tuid *uint64 `json:"tuid"` -} - -// CreateFileCate POST /backend/createfilecate -func (c *BackendFileController) CreateFileCate() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body createCateBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - name := strings.TrimSpace(body.Name) - if name == "" { - c.jsonErr(400, 400, "分组名称不能为空") - return - } - uid := uint64(claims.UserID) - row := &models.SystemFilesCategory{ - Tid: tid, - Name: name, - Uid: &uid, - Tuid: body.Tuid, - } - id, err := models.Orm.Insert(row) - if err != nil { - c.jsonErr(500, 500, "新建文件分组失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "新建文件分组成功", - "data": map[string]interface{}{"id": uint64(id)}, - } - _ = c.ServeJSON() -} - -type renameCateBody struct { - Name string `json:"name"` -} - -// RenameFileCate POST /backend/renamefilecate/:id -func (c *BackendFileController) RenameFileCate() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的分组ID") - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body renameCateBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - name := strings.TrimSpace(body.Name) - if name == "" { - c.jsonErr(400, 400, "分组名称不能为空") - return - } - n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"name": name}) - if err != nil { - c.jsonErr(500, 500, "重命名文件分组失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "分组不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "重命名文件分组成功"} - _ = c.ServeJSON() -} - -// DeleteFileCate DELETE /backend/deletefilecate/:id -func (c *BackendFileController) DeleteFileCate() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的分组ID") - return - } - cnt, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("cate", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Count() - if err != nil { - c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) - return - } - if cnt > 0 { - c.jsonErr(400, 400, fmt.Sprintf("该分组下还有 %d 个文件,请先删除分组内文件!", cnt)) - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "分组不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除文件分组成功"} - _ = c.ServeJSON() -} - -// GetCateFiles GET /backend/catefiles/:id -func (c *BackendFileController) GetCateFiles() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - cateID, err := strconv.ParseUint(idStr, 10, 64) - if err != nil { - c.jsonErr(400, 400, "无效的分类ID") - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 24) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 24 - } - keyword := strings.TrimSpace(c.GetString("keyword")) - - qs := models.Orm.QueryTable(new(models.SystemFile)). - Filter("tid", tid). - Filter("cate", cateID). - Filter("delete_time__isnull", true) - if keyword != "" { - qs = qs.Filter("name__icontains", keyword) - } - total, err := qs.Count() - if err != nil { - c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) - return - } - var rows []models.SystemFile - _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) - return - } - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - list = append(list, fileToMap(&rows[i])) - } - c.jsonOK(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - "categoryId": cateID, - }) -} - -// GetFileByID GET /backend/file/:id -func (c *BackendFileController) GetFileByID() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - var f models.SystemFile - err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - One(&f) - if err != nil { - c.jsonErr(404, 404, "文件不存在") - return - } - c.jsonOK(fileToMap(&f)) -} - -// UploadFile POST /backend/uploadfile -func (c *BackendFileController) UploadFile() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - if err := c.Ctx.Request.ParseMultipartForm(fileUploadMaxBytes); err != nil { - c.jsonErr(400, 400, "解析上传失败: "+err.Error()) - return - } - fh, header, err := c.GetFile("file") - if err != nil || fh == nil { - c.jsonErr(400, 400, "请选择要上传的文件") - return - } - defer fh.Close() - - if header != nil && header.Size > fileUploadMaxBytes { - c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", fileUploadMaxMB)) - return - } - - ext := fileExt(header.Filename) - if ext == "" { - c.jsonErr(400, 400, "无法识别文件扩展名") - return - } - - // 获取存储服务 - storageService, err := services.GetStorageService() - if err != nil { - c.jsonErr(500, 500, "获取存储服务失败: "+err.Error()) - return - } - - // 上传文件 - result, err := storageService.Upload(fh, header) - if err != nil { - c.jsonErr(500, 500, "上传文件失败: "+err.Error()) - return - } - - // 检查文件是否已存在(通过MD5) - var exist models.SystemFile - err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("md5", result.MD5). - Filter("tid", tid). - Filter("delete_time__isnull", true). - One(&exist) - if err == nil { - // 文件已存在,返回已有记录 - c.Data["json"] = map[string]interface{}{ - "code": 201, - "msg": "文件已存在", - "data": map[string]interface{}{ - "url": exist.Src, - "id": exist.ID, - "name": exist.Name, - }, - } - _ = c.ServeJSON() - return - } - - // 获取分类 - cateStr := c.GetString("cate") - var cate uint64 - if cateStr != "" { - cate, _ = strconv.ParseUint(cateStr, 10, 64) - } - - adminID := uint64(claims.UserID) - var tuidPtr *uint64 - if ts := strings.TrimSpace(c.GetString("tuid")); ts != "" { - if v, e := strconv.ParseUint(ts, 10, 64); e == nil { - tuidPtr = &v - } - } - - // 保存文件记录到数据库 - row := &models.SystemFile{ - Tid: tid, - Uid: &adminID, - Tuid: tuidPtr, - Name: header.Filename, - Type: detectFileType(ext), - Cate: cate, - Size: uint64(result.Size), - Src: result.URL, - Uploader: adminID, - Md5: result.MD5, - } - id, err := models.Orm.Insert(row) - if err != nil { - // 数据库插入失败,尝试删除已上传的文件 - _ = storageService.Delete(result.Key) - c.jsonErr(500, 500, "上传失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "上传成功", - "data": map[string]interface{}{ - "url": result.URL, - "id": uint64(id), - "name": header.Filename, - }, - } - _ = c.ServeJSON() -} - -func md5HashFile(path string) (string, error) { - f, err := os.Open(path) - if err != nil { - return "", err - } - defer f.Close() - h := md5.New() - if _, err := io.Copy(h, f); err != nil { - return "", err - } - return hex.EncodeToString(h.Sum(nil)), nil -} - -type updateFileBody struct { - Name *string `json:"name"` - Cate *uint64 `json:"cate"` -} - -// UpdateFile POST /backend/updatefile/:id -func (c *BackendFileController) UpdateFile() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body updateFileBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - up := map[string]interface{}{} - if body.Name != nil { - up["name"] = strings.TrimSpace(*body.Name) - } - if body.Cate != nil { - up["cate"] = *body.Cate - } - if len(up) == 0 { - c.jsonErr(400, 400, "无更新数据") - return - } - now := time.Now() - up["update_time"] = now - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - c.jsonErr(500, 500, "更新失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} - _ = c.ServeJSON() -} - -// DeleteFile DELETE /backend/deletefile/:id -func (c *BackendFileController) DeleteFile() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -// DeleteFilePermanently DELETE /backend/deletefilepermanently/:id -func (c *BackendFileController) DeleteFilePermanently() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - var f models.SystemFile - err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - One(&f) - if err != nil { - c.jsonErr(404, 404, "文件不存在") - return - } - removePhysicalBySrc(f.Src) - _, err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Delete() - if err != nil { - c.jsonErr(500, 500, "永久删除失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "永久删除成功"} - _ = c.ServeJSON() -} - -// MoveFile GET /backend/movefile/:id -func (c *BackendFileController) MoveFile() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - cate, _ := c.GetUint64("cate") - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"cate": cate, "update_time": now}) - if err != nil { - c.jsonErr(500, 500, "移动失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "移动成功"} - _ = c.ServeJSON() -} - -type idsBody struct { - IDs []uint64 `json:"ids"` - Cate *uint64 `json:"cate"` -} - -// BatchDeleteFiles POST /backend/batchdeletefiles -func (c *BackendFileController) BatchDeleteFiles() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body idsBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if len(body.IDs) == 0 { - c.jsonErr(400, 400, "请选择要删除的文件") - return - } - now := time.Now() - for _, id := range body.IDs { - var f models.SystemFile - e := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - One(&f) - if e == nil && f.Src != "" { - removePhysicalBySrc(f.Src) - } - } - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id__in", body.IDs). - Filter("tid", tid). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "批量删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} - _ = c.ServeJSON() -} - -// BatchDeleteFilesPermanently POST /backend/batchDeleteFilesPermanently -func (c *BackendFileController) BatchDeleteFilesPermanently() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body idsBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if len(body.IDs) == 0 { - c.jsonErr(400, 400, "请选择要彻底删除的文件") - return - } - var rows []models.SystemFile - _, err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id__in", body.IDs). - Filter("tid", tid). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) - return - } - for i := range rows { - removePhysicalBySrc(rows[i].Src) - } - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id__in", body.IDs). - Filter("tid", tid). - Delete() - if err != nil { - c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量彻底删除成功"} - _ = c.ServeJSON() -} - -// UploadAvatar POST /backend/uploadavatar(占位) -func (c *BackendFileController) UploadAvatar() { - c.Data["json"] = map[string]interface{}{"code": 501, "msg": "上传头像暂未实现"} - _ = c.ServeJSON() -} - -// UpdateAvatar POST /backend/uploadavatar/:id(占位) -func (c *BackendFileController) UpdateAvatar() { - c.Data["json"] = map[string]interface{}{"code": 501, "msg": "更新头像暂未实现"} - _ = c.ServeJSON() -} - -// BatchMoveFiles POST /backend/batchMoveFiles -func (c *BackendFileController) BatchMoveFiles() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body idsBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if len(body.IDs) == 0 { - c.jsonErr(400, 400, "请选择要移动的文件") - return - } - if body.Cate == nil { - c.jsonErr(400, 400, "缺少目标分类") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id__in", body.IDs). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"cate": *body.Cate, "update_time": now}) - if err != nil { - c.jsonErr(500, 500, "批量移动失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量移动成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendFileController 平台端文件管理(yz_system_files / yz_system_files_category) +type BackendFileController struct { + beego.Controller +} + +const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 +const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 + +var fileTypeByCategory = map[string]uint8{ + "image": 1, + "document": 2, + "video": 3, + "audio": 4, + "appsupgrade": 2, +} + +var allowedExtByCategory = map[string][]string{ + "image": {"jpg", "jpeg", "png", "gif", "bmp", "webp"}, + "document": {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"}, + "video": {"mp4", "webm", "mov"}, + "audio": {"mp3", "wav", "ogg"}, + // 安装包 / 软件升级(上传时 cate 选 appsupgrade 分类即可,扩展名在此放行) + "appsupgrade": {"zip", "exe", "dmg", "msi", "msix", "apk", "deb", "rpm", "7z", "tar", "gz", "pkg"}, +} + +func (c *BackendFileController) backendClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *BackendFileController) effectiveTid(claims *jwtutil.Claims) uint64 { + _ = c.ParseForm(1 << 20) + if tid, err := c.GetUint64("tid"); err == nil && tid > 0 { + return tid + } + if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" { + if v, e := strconv.ParseUint(h, 10, 64); e == nil { + return v + } + } + if claims != nil && claims.TenantId > 0 { + return uint64(claims.TenantId) + } + return 0 +} + +func (c *BackendFileController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *BackendFileController) jsonOK(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +func detectFileType(ext string) uint8 { + ext = strings.ToLower(strings.TrimPrefix(ext, ".")) + for cat, exts := range allowedExtByCategory { + for _, e := range exts { + if e == ext { + if t, ok := fileTypeByCategory[cat]; ok { + return t + } + return 2 + } + } + } + return 2 +} + +func fileExt(name string) string { + name = strings.TrimSpace(name) + if i := strings.LastIndex(name, "."); i >= 0 && i < len(name)-1 { + return strings.ToLower(name[i+1:]) + } + return "" +} + +func fileToMap(f *models.SystemFile) map[string]interface{} { + ct := f.CreateTime.Format("2006-01-02 15:04:05") + m := map[string]interface{}{ + "id": f.ID, + "tid": f.Tid, + "name": f.Name, + "type": f.Type, + "cate": f.Cate, + "size": f.Size, + "src": f.Src, + "uploader": f.Uploader, + "md5": f.Md5, + "create_time": ct, + "createTime": ct, + "groupId": f.Cate, + "url": f.Src, + } + if f.Uid != nil { + m["uid"] = *f.Uid + } + if f.Tuid != nil { + m["tuid"] = *f.Tuid + } + return m +} + +func removePhysicalBySrc(webSrc string) { + webSrc = strings.TrimSpace(webSrc) + if webSrc == "" { + return + } + webSrc = strings.TrimPrefix(webSrc, "/") + _ = os.Remove(webSrc) +} + +// GetAllFiles GET /backend/allfiles +func (c *BackendFileController) GetAllFiles() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + cate, _ := c.GetUint64("cate") + keyword := strings.TrimSpace(c.GetString("keyword")) + + qs := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("delete_time__isnull", true) + if cate > 0 { + qs = qs.Filter("cate", cate) + } + if keyword != "" { + qs = qs.Filter("name__icontains", keyword) + } + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) + return + } + var rows []models.SystemFile + _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) + return + } + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, fileToMap(&rows[i])) + } + c.jsonOK(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// GetUserCate GET /backend/usercate +func (c *BackendFileController) GetUserCate() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + + var cates []models.SystemFilesCategory + _, err = models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + OrderBy("id"). + All(&cates) + if err != nil { + c.jsonErr(500, 500, "获取用户分类失败: "+err.Error()) + return + } + out := make([]map[string]interface{}, 0, len(cates)) + for i := range cates { + cnt, _ := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("cate", cates[i].ID). + Filter("delete_time__isnull", true). + Count() + out = append(out, map[string]interface{}{ + "id": cates[i].ID, + "name": cates[i].Name, + "total": cnt, + }) + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type createCateBody struct { + Name string `json:"name"` + Tuid *uint64 `json:"tuid"` +} + +// CreateFileCate POST /backend/createfilecate +func (c *BackendFileController) CreateFileCate() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body createCateBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + name := strings.TrimSpace(body.Name) + if name == "" { + c.jsonErr(400, 400, "分组名称不能为空") + return + } + uid := uint64(claims.UserID) + row := &models.SystemFilesCategory{ + Tid: tid, + Name: name, + Uid: &uid, + Tuid: body.Tuid, + } + id, err := models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "新建文件分组失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "新建文件分组成功", + "data": map[string]interface{}{"id": uint64(id)}, + } + _ = c.ServeJSON() +} + +type renameCateBody struct { + Name string `json:"name"` +} + +// RenameFileCate POST /backend/renamefilecate/:id +func (c *BackendFileController) RenameFileCate() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的分组ID") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body renameCateBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + name := strings.TrimSpace(body.Name) + if name == "" { + c.jsonErr(400, 400, "分组名称不能为空") + return + } + n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"name": name}) + if err != nil { + c.jsonErr(500, 500, "重命名文件分组失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "分组不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "重命名文件分组成功"} + _ = c.ServeJSON() +} + +// DeleteFileCate DELETE /backend/deletefilecate/:id +func (c *BackendFileController) DeleteFileCate() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的分组ID") + return + } + cnt, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("cate", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Count() + if err != nil { + c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) + return + } + if cnt > 0 { + c.jsonErr(400, 400, fmt.Sprintf("该分组下还有 %d 个文件,请先删除分组内文件!", cnt)) + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "分组不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除文件分组成功"} + _ = c.ServeJSON() +} + +// GetCateFiles GET /backend/catefiles/:id +func (c *BackendFileController) GetCateFiles() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + cateID, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.jsonErr(400, 400, "无效的分类ID") + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 24) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 24 + } + keyword := strings.TrimSpace(c.GetString("keyword")) + + qs := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("cate", cateID). + Filter("delete_time__isnull", true) + if keyword != "" { + qs = qs.Filter("name__icontains", keyword) + } + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) + return + } + var rows []models.SystemFile + _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) + return + } + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, fileToMap(&rows[i])) + } + c.jsonOK(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + "categoryId": cateID, + }) +} + +// GetFileByID GET /backend/file/:id +func (c *BackendFileController) GetFileByID() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + var f models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&f) + if err != nil { + c.jsonErr(404, 404, "文件不存在") + return + } + c.jsonOK(fileToMap(&f)) +} + +// UploadFile POST /backend/uploadfile +func (c *BackendFileController) UploadFile() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + if err := c.Ctx.Request.ParseMultipartForm(fileUploadMaxBytes); err != nil { + c.jsonErr(400, 400, "解析上传失败: "+err.Error()) + return + } + fh, header, err := c.GetFile("file") + if err != nil || fh == nil { + c.jsonErr(400, 400, "请选择要上传的文件") + return + } + defer fh.Close() + + if header != nil && header.Size > fileUploadMaxBytes { + c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", fileUploadMaxMB)) + return + } + + ext := fileExt(header.Filename) + if ext == "" { + c.jsonErr(400, 400, "无法识别文件扩展名") + return + } + + // 获取存储服务 + storageService, err := services.GetStorageService() + if err != nil { + c.jsonErr(500, 500, "获取存储服务失败: "+err.Error()) + return + } + + // 上传文件 + result, err := storageService.Upload(fh, header) + if err != nil { + c.jsonErr(500, 500, "上传文件失败: "+err.Error()) + return + } + + // 检查文件是否已存在(通过MD5) + var exist models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("md5", result.MD5). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&exist) + if err == nil { + // 文件已存在,返回已有记录 + c.Data["json"] = map[string]interface{}{ + "code": 201, + "msg": "文件已存在", + "data": map[string]interface{}{ + "url": exist.Src, + "id": exist.ID, + "name": exist.Name, + }, + } + _ = c.ServeJSON() + return + } + + // 获取分类 + cateStr := c.GetString("cate") + var cate uint64 + if cateStr != "" { + cate, _ = strconv.ParseUint(cateStr, 10, 64) + } + + adminID := uint64(claims.UserID) + var tuidPtr *uint64 + if ts := strings.TrimSpace(c.GetString("tuid")); ts != "" { + if v, e := strconv.ParseUint(ts, 10, 64); e == nil { + tuidPtr = &v + } + } + + // 保存文件记录到数据库 + row := &models.SystemFile{ + Tid: tid, + Uid: &adminID, + Tuid: tuidPtr, + Name: header.Filename, + Type: detectFileType(ext), + Cate: cate, + Size: uint64(result.Size), + Src: result.URL, + Uploader: adminID, + Md5: result.MD5, + } + id, err := models.Orm.Insert(row) + if err != nil { + // 数据库插入失败,尝试删除已上传的文件 + _ = storageService.Delete(result.Key) + c.jsonErr(500, 500, "上传失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "上传成功", + "data": map[string]interface{}{ + "url": result.URL, + "id": uint64(id), + "name": header.Filename, + }, + } + _ = c.ServeJSON() +} + +func md5HashFile(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + h := md5.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + +type updateFileBody struct { + Name *string `json:"name"` + Cate *uint64 `json:"cate"` +} + +// UpdateFile POST /backend/updatefile/:id +func (c *BackendFileController) UpdateFile() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body updateFileBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + up := map[string]interface{}{} + if body.Name != nil { + up["name"] = strings.TrimSpace(*body.Name) + } + if body.Cate != nil { + up["cate"] = *body.Cate + } + if len(up) == 0 { + c.jsonErr(400, 400, "无更新数据") + return + } + now := time.Now() + up["update_time"] = now + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "更新失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +// DeleteFile DELETE /backend/deletefile/:id +func (c *BackendFileController) DeleteFile() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// DeleteFilePermanently DELETE /backend/deletefilepermanently/:id +func (c *BackendFileController) DeleteFilePermanently() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + var f models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + One(&f) + if err != nil { + c.jsonErr(404, 404, "文件不存在") + return + } + removePhysicalBySrc(f.Src) + _, err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Delete() + if err != nil { + c.jsonErr(500, 500, "永久删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "永久删除成功"} + _ = c.ServeJSON() +} + +// MoveFile GET /backend/movefile/:id +func (c *BackendFileController) MoveFile() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + cate, _ := c.GetUint64("cate") + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"cate": cate, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "移动失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "移动成功"} + _ = c.ServeJSON() +} + +type idsBody struct { + IDs []uint64 `json:"ids"` + Cate *uint64 `json:"cate"` +} + +// BatchDeleteFiles POST /backend/batchdeletefiles +func (c *BackendFileController) BatchDeleteFiles() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body idsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要删除的文件") + return + } + now := time.Now() + for _, id := range body.IDs { + var f models.SystemFile + e := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + One(&f) + if e == nil && f.Src != "" { + removePhysicalBySrc(f.Src) + } + } + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} + _ = c.ServeJSON() +} + +// BatchDeleteFilesPermanently POST /backend/batchDeleteFilesPermanently +func (c *BackendFileController) BatchDeleteFilesPermanently() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body idsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要彻底删除的文件") + return + } + var rows []models.SystemFile + _, err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) + return + } + for i := range rows { + removePhysicalBySrc(rows[i].Src) + } + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Delete() + if err != nil { + c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量彻底删除成功"} + _ = c.ServeJSON() +} + +// UploadAvatar POST /backend/uploadavatar(占位) +func (c *BackendFileController) UploadAvatar() { + c.Data["json"] = map[string]interface{}{"code": 501, "msg": "上传头像暂未实现"} + _ = c.ServeJSON() +} + +// UpdateAvatar POST /backend/uploadavatar/:id(占位) +func (c *BackendFileController) UpdateAvatar() { + c.Data["json"] = map[string]interface{}{"code": 501, "msg": "更新头像暂未实现"} + _ = c.ServeJSON() +} + +// BatchMoveFiles POST /backend/batchMoveFiles +func (c *BackendFileController) BatchMoveFiles() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body idsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要移动的文件") + return + } + if body.Cate == nil { + c.jsonErr(400, 400, "缺少目标分类") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"cate": *body.Cate, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量移动失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量移动成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/backend_login_verify.go b/go/controllers/backend_login_verify.go index ed9684c..77142ef 100644 --- a/go/controllers/backend_login_verify.go +++ b/go/controllers/backend_login_verify.go @@ -1,226 +1,226 @@ -package controllers - -import ( - "encoding/json" - "io" - "strings" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -// BackendLoginVerifyController 后台登录验证配置 -// 对应前端 backend/src/api/sitesettings.js: -// - GET /backend/loginVerifyInfos -// - POST /backend/saveloginVerifyInfos -type BackendLoginVerifyController struct { - beego.Controller -} - -func (c *BackendLoginVerifyController) backendLoginVerifyClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, errBackendLoginVerify("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, errBackendLoginVerify("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, errBackendLoginVerify("无效的token") - } - if claims.UserType != "backend" { - return nil, errBackendLoginVerify("无权访问") - } - return claims, nil -} - -type backendLoginVerifyError string - -func (e backendLoginVerifyError) Error() string { - return string(e) -} - -func errBackendLoginVerify(msg string) error { - return backendLoginVerifyError(msg) -} - -func (c *BackendLoginVerifyController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -type backendLoginVerifyPayload struct { - OpenVerify *bool `json:"openVerify"` - OpenVerifyInt *int8 `json:"openVerify_enabled"` - VerifyModel string `json:"verifyModel"` - UseGeetest string `json:"use_geetest"` - Geetest3ID *string `json:"geetest3ID"` - Geetest3IDSnake *string `json:"geetest3_id"` - Geetest3Key *string `json:"geetest3KEY"` - Geetest3KeySnake *string `json:"geetest3_key"` - Geetest4ID *string `json:"geetest4ID"` - Geetest4IDSnake *string `json:"geetest4_id"` - Geetest4Key *string `json:"geetest4KEY"` - Geetest4KeySnake *string `json:"geetest4_key"` -} - -func backendVerifyTypeToModel(v string) string { - switch strings.TrimSpace(v) { - case "captcha": - return "1" - case "sms": - return "2" - case "email": - return "3" - case "geetest3": - return "4" - case "geetest", "geetest4": - return "5" - default: - return "1" - } -} - -func backendVerifyModelToType(v string) string { - switch strings.TrimSpace(v) { - case "1": - return "captcha" - case "2": - return "sms" - case "3": - return "email" - case "4": - return "geetest3" - case "5": - return "geetest4" - default: - switch strings.TrimSpace(v) { - case "captcha", "sms", "email", "geetest", "geetest3", "geetest4": - return strings.TrimSpace(v) - default: - return "captcha" - } - } -} - -func backendStringPtrValue(primary, fallback *string) string { - if primary != nil { - return *primary - } - if fallback != nil { - return *fallback - } - return "" -} - -func backendStringPtrOrNil(primary, fallback *string) *string { - value := strings.TrimSpace(backendStringPtrValue(primary, fallback)) - if value == "" { - return nil - } - return &value -} - -// GetLoginVerifyInfos GET /backend/loginVerifyInfos -func (c *BackendLoginVerifyController) GetLoginVerifyInfos() { - if _, err := c.backendLoginVerifyClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - cfg, err := models.GetPlatformLoginVerify() - if err != nil { - c.jsonErr(500, 500, "获取配置失败") - return - } - - openVerify := "0" - if cfg.OpenVerifyEnabled == 1 { - openVerify = "1" - } - - data := []map[string]string{ - {"label": "openVerify", "value": openVerify}, - {"label": "verifyModel", "value": backendVerifyTypeToModel(cfg.VerifyType)}, - {"label": "geetest3ID", "value": backendStringPtrValue(cfg.Geetest3ID, nil)}, - {"label": "geetest3KEY", "value": backendStringPtrValue(cfg.Geetest3Key, nil)}, - {"label": "geetest4ID", "value": backendStringPtrValue(cfg.Geetest4ID, nil)}, - {"label": "geetest4KEY", "value": backendStringPtrValue(cfg.Geetest4Key, nil)}, - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} - _ = c.ServeJSON() -} - -// SaveLoginVerifyInfos POST /backend/saveloginVerifyInfos -func (c *BackendLoginVerifyController) SaveLoginVerifyInfos() { - if _, err := c.backendLoginVerifyClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - var p backendLoginVerifyPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - openVerifyEnabled := int8(0) - if p.OpenVerify != nil && *p.OpenVerify { - openVerifyEnabled = 1 - } - if p.OpenVerifyInt != nil { - openVerifyEnabled = *p.OpenVerifyInt - } - - verifyModel := p.VerifyModel - if strings.TrimSpace(verifyModel) == "" { - verifyModel = p.UseGeetest - } - verifyType := backendVerifyModelToType(verifyModel) - - geetest3ID := backendStringPtrOrNil(p.Geetest3ID, p.Geetest3IDSnake) - geetest3Key := backendStringPtrOrNil(p.Geetest3Key, p.Geetest3KeySnake) - geetest4ID := backendStringPtrOrNil(p.Geetest4ID, p.Geetest4IDSnake) - geetest4Key := backendStringPtrOrNil(p.Geetest4Key, p.Geetest4KeySnake) - - if verifyType == "geetest3" { - if geetest3ID == nil || geetest3Key == nil { - c.jsonErr(400, 400, "极验3.0 ID和KEY不能为空") - return - } - } - if verifyType == "geetest4" || verifyType == "geetest" { - if geetest4ID == nil || geetest4Key == nil { - c.jsonErr(400, 400, "极验4.0 ID和KEY不能为空") - return - } - } - - err = models.SavePlatformLoginVerify(&models.PlatformLoginVerify{ - OpenVerifyEnabled: openVerifyEnabled, - VerifyType: verifyType, - Geetest3ID: geetest3ID, - Geetest3Key: geetest3Key, - Geetest4ID: geetest4ID, - Geetest4Key: geetest4Key, - }) - if err != nil { - c.jsonErr(500, 500, "保存失败") - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "io" + "strings" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendLoginVerifyController 后台登录验证配置 +// 对应前端 backend/src/api/sitesettings.js: +// - GET /backend/loginVerifyInfos +// - POST /backend/saveloginVerifyInfos +type BackendLoginVerifyController struct { + beego.Controller +} + +func (c *BackendLoginVerifyController) backendLoginVerifyClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, errBackendLoginVerify("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, errBackendLoginVerify("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, errBackendLoginVerify("无效的token") + } + if claims.UserType != "backend" { + return nil, errBackendLoginVerify("无权访问") + } + return claims, nil +} + +type backendLoginVerifyError string + +func (e backendLoginVerifyError) Error() string { + return string(e) +} + +func errBackendLoginVerify(msg string) error { + return backendLoginVerifyError(msg) +} + +func (c *BackendLoginVerifyController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +type backendLoginVerifyPayload struct { + OpenVerify *bool `json:"openVerify"` + OpenVerifyInt *int8 `json:"openVerify_enabled"` + VerifyModel string `json:"verifyModel"` + UseGeetest string `json:"use_geetest"` + Geetest3ID *string `json:"geetest3ID"` + Geetest3IDSnake *string `json:"geetest3_id"` + Geetest3Key *string `json:"geetest3KEY"` + Geetest3KeySnake *string `json:"geetest3_key"` + Geetest4ID *string `json:"geetest4ID"` + Geetest4IDSnake *string `json:"geetest4_id"` + Geetest4Key *string `json:"geetest4KEY"` + Geetest4KeySnake *string `json:"geetest4_key"` +} + +func backendVerifyTypeToModel(v string) string { + switch strings.TrimSpace(v) { + case "captcha": + return "1" + case "sms": + return "2" + case "email": + return "3" + case "geetest3": + return "4" + case "geetest", "geetest4": + return "5" + default: + return "1" + } +} + +func backendVerifyModelToType(v string) string { + switch strings.TrimSpace(v) { + case "1": + return "captcha" + case "2": + return "sms" + case "3": + return "email" + case "4": + return "geetest3" + case "5": + return "geetest4" + default: + switch strings.TrimSpace(v) { + case "captcha", "sms", "email", "geetest", "geetest3", "geetest4": + return strings.TrimSpace(v) + default: + return "captcha" + } + } +} + +func backendStringPtrValue(primary, fallback *string) string { + if primary != nil { + return *primary + } + if fallback != nil { + return *fallback + } + return "" +} + +func backendStringPtrOrNil(primary, fallback *string) *string { + value := strings.TrimSpace(backendStringPtrValue(primary, fallback)) + if value == "" { + return nil + } + return &value +} + +// GetLoginVerifyInfos GET /backend/loginVerifyInfos +func (c *BackendLoginVerifyController) GetLoginVerifyInfos() { + if _, err := c.backendLoginVerifyClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + cfg, err := models.GetPlatformLoginVerify() + if err != nil { + c.jsonErr(500, 500, "获取配置失败") + return + } + + openVerify := "0" + if cfg.OpenVerifyEnabled == 1 { + openVerify = "1" + } + + data := []map[string]string{ + {"label": "openVerify", "value": openVerify}, + {"label": "verifyModel", "value": backendVerifyTypeToModel(cfg.VerifyType)}, + {"label": "geetest3ID", "value": backendStringPtrValue(cfg.Geetest3ID, nil)}, + {"label": "geetest3KEY", "value": backendStringPtrValue(cfg.Geetest3Key, nil)}, + {"label": "geetest4ID", "value": backendStringPtrValue(cfg.Geetest4ID, nil)}, + {"label": "geetest4KEY", "value": backendStringPtrValue(cfg.Geetest4Key, nil)}, + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +// SaveLoginVerifyInfos POST /backend/saveloginVerifyInfos +func (c *BackendLoginVerifyController) SaveLoginVerifyInfos() { + if _, err := c.backendLoginVerifyClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + var p backendLoginVerifyPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + openVerifyEnabled := int8(0) + if p.OpenVerify != nil && *p.OpenVerify { + openVerifyEnabled = 1 + } + if p.OpenVerifyInt != nil { + openVerifyEnabled = *p.OpenVerifyInt + } + + verifyModel := p.VerifyModel + if strings.TrimSpace(verifyModel) == "" { + verifyModel = p.UseGeetest + } + verifyType := backendVerifyModelToType(verifyModel) + + geetest3ID := backendStringPtrOrNil(p.Geetest3ID, p.Geetest3IDSnake) + geetest3Key := backendStringPtrOrNil(p.Geetest3Key, p.Geetest3KeySnake) + geetest4ID := backendStringPtrOrNil(p.Geetest4ID, p.Geetest4IDSnake) + geetest4Key := backendStringPtrOrNil(p.Geetest4Key, p.Geetest4KeySnake) + + if verifyType == "geetest3" { + if geetest3ID == nil || geetest3Key == nil { + c.jsonErr(400, 400, "极验3.0 ID和KEY不能为空") + return + } + } + if verifyType == "geetest4" || verifyType == "geetest" { + if geetest4ID == nil || geetest4Key == nil { + c.jsonErr(400, 400, "极验4.0 ID和KEY不能为空") + return + } + } + + err = models.SavePlatformLoginVerify(&models.PlatformLoginVerify{ + OpenVerifyEnabled: openVerifyEnabled, + VerifyType: verifyType, + Geetest3ID: geetest3ID, + Geetest3Key: geetest3Key, + Geetest4ID: geetest4ID, + Geetest4Key: geetest4Key, + }) + if err != nil { + c.jsonErr(500, 500, "保存失败") + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/backend_menu.go b/go/controllers/backend_menu.go index 21cca8f..a0541f4 100644 --- a/go/controllers/backend_menu.go +++ b/go/controllers/backend_menu.go @@ -1,360 +1,360 @@ -package controllers - -import ( - "encoding/json" - "server/models" - "strconv" - "strings" - - beego "github.com/beego/beego/v2/server/web" -) - -type BackendMenuController struct { - beego.Controller -} - -type AdminMenuController = BackendMenuController - -type menuPayload struct { - Pid *int64 `json:"pid"` - Title *string `json:"title"` - Path *string `json:"path"` - ComponentPath *string `json:"component_path"` - Icon *string `json:"icon"` - Sort *int64 `json:"sort"` - Status *int8 `json:"status"` - IsVisible *int8 `json:"is_visible"` - Views []int `json:"views"` - Type *int8 `json:"type"` - Permission *string `json:"permission"` -} - -func parseViews(raw *string) []int { - if raw == nil || strings.TrimSpace(*raw) == "" { - return nil - } - var arr []int - if err := json.Unmarshal([]byte(*raw), &arr); err != nil { - return nil - } - return arr -} - -func hasView(arr []int, v int) bool { - for _, n := range arr { - if n == v { - return true - } - } - return false -} - -func filterMenusByView(menus []models.SystemMenu, v int) []models.SystemMenu { - out := make([]models.SystemMenu, 0, len(menus)) - for _, m := range menus { - views := parseViews(m.Views) - if v == 1 && len(views) == 0 { - out = append(out, m) - continue - } - if hasView(views, v) { - out = append(out, m) - } - } - return out -} - -func (c *BackendMenuController) GetMenu() { - var menus []models.SystemMenu - _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 1), 0)} - _ = c.ServeJSON() -} - -func (c *BackendMenuController) GetBackendMenu() { - var menus []models.SystemMenu - _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 2), 0)} - _ = c.ServeJSON() -} - -func (c *BackendMenuController) GetTenantList() { - var tid uint64 - if jwtTid := c.Ctx.Input.GetData("tid"); jwtTid != nil { - tid = jwtTid.(uint64) - } - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"} - _ = c.ServeJSON() - return - } - - var menus []models.SystemMenu - if _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取失败:" + err.Error()} - _ = c.ServeJSON() - return - } - - tree := buildMenuTree(filterMenusByView(menus, 2), 0) - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "获取成功", - "data": map[string]interface{}{"list": tree, "total": len(tree)}, - } - _ = c.ServeJSON() -} - -func (c *BackendMenuController) GetAllMenus() { - var menus []models.SystemMenu - cid, _ := c.GetInt("cid") - - if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} - _ = c.ServeJSON() - return - } - if cid == 1 { - menus = filterMenusByView(menus, 1) - } else if cid == 2 { - menus = filterMenusByView(menus, 2) - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(menus, 0)} - _ = c.ServeJSON() -} - -func (c *BackendMenuController) GetAllBackendMenus() { - var menus []models.SystemMenu - if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 2), 0)} - _ = c.ServeJSON() -} - -type menuNode struct { - ID uint64 `json:"id"` - Pid int64 `json:"pid"` - Title string `json:"title"` - Path string `json:"path,omitempty"` - ComponentPath string `json:"component_path,omitempty"` - Icon string `json:"icon,omitempty"` - Sort int64 `json:"sort"` - Status int8 `json:"status"` - IsVisible *int8 `json:"is_visible,omitempty"` - Views []int `json:"views,omitempty"` - Type int8 `json:"type"` - Permission string `json:"permission,omitempty"` - Children []*menuNode `json:"children,omitempty"` -} - -func buildMenuTree(menus []models.SystemMenu, pid int64) []*menuNode { - var tree []*menuNode - for _, m := range menus { - if m.Pid == pid { - node := &menuNode{ - ID: m.ID, - Pid: m.Pid, - Title: m.Title, - Sort: m.Sort, - Status: m.Status, - IsVisible: m.IsVisible, - Views: parseViews(m.Views), - Type: m.Type, - } - if m.Path != nil { - node.Path = *m.Path - } - if m.ComponentPath != nil { - node.ComponentPath = *m.ComponentPath - } - if m.Icon != nil { - node.Icon = *m.Icon - } - if m.Permission != nil { - node.Permission = *m.Permission - } - if children := buildMenuTree(menus, int64(m.ID)); len(children) > 0 { - node.Children = children - } - tree = append(tree, node) - } - } - return tree -} - -func (c *BackendMenuController) UpdateMenuStatus() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} - _ = c.ServeJSON() - return - } - - var body struct { - Status *int8 `json:"status"` - } - if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil || body.Status == nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - - if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Update(map[string]interface{}{"status": *body.Status}); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "success": true} - _ = c.ServeJSON() -} - -func (c *BackendMenuController) CreateMenu() { - payload, ok := c.parseMenuPayload(true) - if !ok { - return - } - - var viewsStr string - views := payload.Views - if len(views) == 0 { - views = []int{1} - } - if b, err := json.Marshal(views); err == nil { - viewsStr = string(b) - } - - menu := models.SystemMenu{ - Pid: valueInt64(payload.Pid, 0), - Title: strings.TrimSpace(valueString(payload.Title, "")), - Sort: valueInt64(payload.Sort, 0), - Status: valueInt8(payload.Status, 1), - IsVisible: ptrInt8(valueInt8(payload.IsVisible, 1)), - Views: &viewsStr, - Type: valueInt8(payload.Type, 1), - Path: ptrString(valueString(payload.Path, "")), - ComponentPath: ptrString(valueString(payload.ComponentPath, "")), - Icon: ptrString(valueString(payload.Icon, "")), - Permission: ptrString(valueString(payload.Permission, "")), - } - - id, err := models.Orm.Insert(&menu) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} - _ = c.ServeJSON() -} - -func (c *BackendMenuController) UpdateMenu() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} - _ = c.ServeJSON() - return - } - - payload, ok := c.parseMenuPayload(false) - if !ok { - return - } - - views := payload.Views - if len(views) == 0 { - views = []int{1} - } - viewsBytes, _ := json.Marshal(views) - - update := map[string]interface{}{ - "pid": valueInt64(payload.Pid, 0), - "title": strings.TrimSpace(valueString(payload.Title, "")), - "path": valueString(payload.Path, ""), - "component_path": valueString(payload.ComponentPath, ""), - "icon": valueString(payload.Icon, ""), - "sort": valueInt64(payload.Sort, 0), - "status": valueInt8(payload.Status, 1), - "is_visible": valueInt8(payload.IsVisible, 1), - "views": string(viewsBytes), - "type": valueInt8(payload.Type, 1), - "permission": valueString(payload.Permission, ""), - } - - if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Update(update); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} - _ = c.ServeJSON() -} - -func (c *BackendMenuController) DeleteMenu() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} - _ = c.ServeJSON() - return - } - - if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Delete(); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功", "success": true} - _ = c.ServeJSON() -} - -func (c *BackendMenuController) parseMenuPayload(needTitle bool) (*menuPayload, bool) { - var payload menuPayload - if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return nil, false - } - if needTitle && strings.TrimSpace(valueString(payload.Title, "")) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "菜单名称不能为空"} - _ = c.ServeJSON() - return nil, false - } - return &payload, true -} - -func valueString(v *string, def string) string { - if v == nil { - return def - } - return *v -} - -func valueInt8(v *int8, def int8) int8 { - if v == nil { - return def - } - return *v -} - -func valueInt64(v *int64, def int64) int64 { - if v == nil { - return def - } - return *v -} - -func ptrString(v string) *string { return &v } -func ptrInt8(v int8) *int8 { return &v } +package controllers + +import ( + "encoding/json" + "server/models" + "strconv" + "strings" + + beego "github.com/beego/beego/v2/server/web" +) + +type BackendMenuController struct { + beego.Controller +} + +type AdminMenuController = BackendMenuController + +type menuPayload struct { + Pid *int64 `json:"pid"` + Title *string `json:"title"` + Path *string `json:"path"` + ComponentPath *string `json:"component_path"` + Icon *string `json:"icon"` + Sort *int64 `json:"sort"` + Status *int8 `json:"status"` + IsVisible *int8 `json:"is_visible"` + Views []int `json:"views"` + Type *int8 `json:"type"` + Permission *string `json:"permission"` +} + +func parseViews(raw *string) []int { + if raw == nil || strings.TrimSpace(*raw) == "" { + return nil + } + var arr []int + if err := json.Unmarshal([]byte(*raw), &arr); err != nil { + return nil + } + return arr +} + +func hasView(arr []int, v int) bool { + for _, n := range arr { + if n == v { + return true + } + } + return false +} + +func filterMenusByView(menus []models.SystemMenu, v int) []models.SystemMenu { + out := make([]models.SystemMenu, 0, len(menus)) + for _, m := range menus { + views := parseViews(m.Views) + if v == 1 && len(views) == 0 { + out = append(out, m) + continue + } + if hasView(views, v) { + out = append(out, m) + } + } + return out +} + +func (c *BackendMenuController) GetMenu() { + var menus []models.SystemMenu + _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 1), 0)} + _ = c.ServeJSON() +} + +func (c *BackendMenuController) GetBackendMenu() { + var menus []models.SystemMenu + _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 2), 0)} + _ = c.ServeJSON() +} + +func (c *BackendMenuController) GetTenantList() { + var tid uint64 + if jwtTid := c.Ctx.Input.GetData("tid"); jwtTid != nil { + tid = jwtTid.(uint64) + } + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"} + _ = c.ServeJSON() + return + } + + var menus []models.SystemMenu + if _, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取失败:" + err.Error()} + _ = c.ServeJSON() + return + } + + tree := buildMenuTree(filterMenusByView(menus, 2), 0) + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "获取成功", + "data": map[string]interface{}{"list": tree, "total": len(tree)}, + } + _ = c.ServeJSON() +} + +func (c *BackendMenuController) GetAllMenus() { + var menus []models.SystemMenu + cid, _ := c.GetInt("cid") + + if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} + _ = c.ServeJSON() + return + } + if cid == 1 { + menus = filterMenusByView(menus, 1) + } else if cid == 2 { + menus = filterMenusByView(menus, 2) + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(menus, 0)} + _ = c.ServeJSON() +} + +func (c *BackendMenuController) GetAllBackendMenus() { + var menus []models.SystemMenu + if _, err := models.Orm.QueryTable(new(models.SystemMenu)).All(&menus); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取菜单失败: " + err.Error(), "data": nil} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 2), 0)} + _ = c.ServeJSON() +} + +type menuNode struct { + ID uint64 `json:"id"` + Pid int64 `json:"pid"` + Title string `json:"title"` + Path string `json:"path,omitempty"` + ComponentPath string `json:"component_path,omitempty"` + Icon string `json:"icon,omitempty"` + Sort int64 `json:"sort"` + Status int8 `json:"status"` + IsVisible *int8 `json:"is_visible,omitempty"` + Views []int `json:"views,omitempty"` + Type int8 `json:"type"` + Permission string `json:"permission,omitempty"` + Children []*menuNode `json:"children,omitempty"` +} + +func buildMenuTree(menus []models.SystemMenu, pid int64) []*menuNode { + var tree []*menuNode + for _, m := range menus { + if m.Pid == pid { + node := &menuNode{ + ID: m.ID, + Pid: m.Pid, + Title: m.Title, + Sort: m.Sort, + Status: m.Status, + IsVisible: m.IsVisible, + Views: parseViews(m.Views), + Type: m.Type, + } + if m.Path != nil { + node.Path = *m.Path + } + if m.ComponentPath != nil { + node.ComponentPath = *m.ComponentPath + } + if m.Icon != nil { + node.Icon = *m.Icon + } + if m.Permission != nil { + node.Permission = *m.Permission + } + if children := buildMenuTree(menus, int64(m.ID)); len(children) > 0 { + node.Children = children + } + tree = append(tree, node) + } + } + return tree +} + +func (c *BackendMenuController) UpdateMenuStatus() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} + _ = c.ServeJSON() + return + } + + var body struct { + Status *int8 `json:"status"` + } + if err := json.Unmarshal(c.Ctx.Input.RequestBody, &body); err != nil || body.Status == nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Update(map[string]interface{}{"status": *body.Status}); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "success": true} + _ = c.ServeJSON() +} + +func (c *BackendMenuController) CreateMenu() { + payload, ok := c.parseMenuPayload(true) + if !ok { + return + } + + var viewsStr string + views := payload.Views + if len(views) == 0 { + views = []int{1} + } + if b, err := json.Marshal(views); err == nil { + viewsStr = string(b) + } + + menu := models.SystemMenu{ + Pid: valueInt64(payload.Pid, 0), + Title: strings.TrimSpace(valueString(payload.Title, "")), + Sort: valueInt64(payload.Sort, 0), + Status: valueInt8(payload.Status, 1), + IsVisible: ptrInt8(valueInt8(payload.IsVisible, 1)), + Views: &viewsStr, + Type: valueInt8(payload.Type, 1), + Path: ptrString(valueString(payload.Path, "")), + ComponentPath: ptrString(valueString(payload.ComponentPath, "")), + Icon: ptrString(valueString(payload.Icon, "")), + Permission: ptrString(valueString(payload.Permission, "")), + } + + id, err := models.Orm.Insert(&menu) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} + _ = c.ServeJSON() +} + +func (c *BackendMenuController) UpdateMenu() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} + _ = c.ServeJSON() + return + } + + payload, ok := c.parseMenuPayload(false) + if !ok { + return + } + + views := payload.Views + if len(views) == 0 { + views = []int{1} + } + viewsBytes, _ := json.Marshal(views) + + update := map[string]interface{}{ + "pid": valueInt64(payload.Pid, 0), + "title": strings.TrimSpace(valueString(payload.Title, "")), + "path": valueString(payload.Path, ""), + "component_path": valueString(payload.ComponentPath, ""), + "icon": valueString(payload.Icon, ""), + "sort": valueInt64(payload.Sort, 0), + "status": valueInt8(payload.Status, 1), + "is_visible": valueInt8(payload.IsVisible, 1), + "views": string(viewsBytes), + "type": valueInt8(payload.Type, 1), + "permission": valueString(payload.Permission, ""), + } + + if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Update(update); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +func (c *BackendMenuController) DeleteMenu() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效菜单ID"} + _ = c.ServeJSON() + return + } + + if _, err = models.Orm.QueryTable(new(models.SystemMenu)).Filter("id", id).Delete(); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功", "success": true} + _ = c.ServeJSON() +} + +func (c *BackendMenuController) parseMenuPayload(needTitle bool) (*menuPayload, bool) { + var payload menuPayload + if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return nil, false + } + if needTitle && strings.TrimSpace(valueString(payload.Title, "")) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "菜单名称不能为空"} + _ = c.ServeJSON() + return nil, false + } + return &payload, true +} + +func valueString(v *string, def string) string { + if v == nil { + return def + } + return *v +} + +func valueInt8(v *int8, def int8) int8 { + if v == nil { + return def + } + return *v +} + +func valueInt64(v *int64, def int64) int64 { + if v == nil { + return def + } + return *v +} + +func ptrString(v string) *string { return &v } +func ptrInt8(v int8) *int8 { return &v } diff --git a/go/controllers/backend_modules.go b/go/controllers/backend_modules.go index 973e07c..d2d7e59 100644 --- a/go/controllers/backend_modules.go +++ b/go/controllers/backend_modules.go @@ -1,70 +1,70 @@ -package controllers - -import ( - "fmt" - "strings" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -// BackendModulesController backend 模块接口(yz_system_modules) -type BackendModulesController struct { - beego.Controller -} - -func (c *BackendModulesController) backendModulesClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *BackendModulesController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// GetTenantList GET /backend/modules/getTenantList -// 返回当前 backend 账号可见的模块。当前实现:返回 status=1 且 is_show=1 的全部模块。 -func (c *BackendModulesController) GetTenantList() { - if _, err := c.backendModulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - var rows []models.SystemModules - _, err := models.Orm.QueryTable(new(models.SystemModules)). - Filter("delete_time__isnull", true). - Filter("status", 1). - Filter("is_show", 1). - OrderBy("sort", "id"). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败:"+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "获取成功", - "data": map[string]interface{}{ - "list": rows, - "total": len(rows), - }, - } - _ = c.ServeJSON() -} +package controllers + +import ( + "fmt" + "strings" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendModulesController backend 模块接口(yz_system_modules) +type BackendModulesController struct { + beego.Controller +} + +func (c *BackendModulesController) backendModulesClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *BackendModulesController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// GetTenantList GET /backend/modules/getTenantList +// 返回当前 backend 账号可见的模块。当前实现:返回 status=1 且 is_show=1 的全部模块。 +func (c *BackendModulesController) GetTenantList() { + if _, err := c.backendModulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + var rows []models.SystemModules + _, err := models.Orm.QueryTable(new(models.SystemModules)). + Filter("delete_time__isnull", true). + Filter("status", 1). + Filter("is_show", 1). + OrderBy("sort", "id"). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败:"+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "获取成功", + "data": map[string]interface{}{ + "list": rows, + "total": len(rows), + }, + } + _ = c.ServeJSON() +} diff --git a/go/controllers/backend_operation_log.go b/go/controllers/backend_operation_log.go index 837e48d..6b003f0 100644 --- a/go/controllers/backend_operation_log.go +++ b/go/controllers/backend_operation_log.go @@ -1,332 +1,332 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// BackendOperationLogController 操作日志(yz_system_operation_log) -type BackendOperationLogController struct { - beego.Controller -} - -func (c *BackendOperationLogController) backendClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *BackendOperationLogController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// List GET /backend/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime= -func (c *BackendOperationLogController) List() { - if _, err := c.backendClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 200 { - pageSize = 200 - } - - keyword := strings.TrimSpace(c.GetString("keyword")) - module := strings.TrimSpace(c.GetString("module")) - action := strings.TrimSpace(c.GetString("action")) - statusStr := strings.TrimSpace(c.GetString("status")) - startTimeStr := strings.TrimSpace(c.GetString("startTime")) - endTimeStr := strings.TrimSpace(c.GetString("endTime")) - - qs := models.Orm.QueryTable(new(models.SystemOperationLog)).Filter("delete_time__isnull", true) - - // 条件拼装 - cond := orm.NewCondition() - needCond := false - - if module != "" { - cond = cond.And("module", module) - needCond = true - } - if action != "" { - cond = cond.And("action", action) - needCond = true - } - if statusStr != "" { - if st, err := strconv.Atoi(statusStr); err == nil { - cond = cond.And("status", st) - needCond = true - } - } - if keyword != "" { - kw := orm.NewCondition(). - Or("module__icontains", keyword). - Or("action__icontains", keyword). - Or("method__icontains", keyword). - Or("url__icontains", keyword). - Or("ip__icontains", keyword). - Or("user_agent__icontains", keyword) - if uid, err := strconv.ParseUint(keyword, 10, 64); err == nil && uid > 0 { - kw = kw.Or("user_id", uid) - } - cond = cond.AndCond(kw) - needCond = true - } - if t, err := parseTimeFlexible(startTimeStr); err == nil && !t.IsZero() { - cond = cond.And("create_time__gte", t) - needCond = true - } - if t, err := parseTimeFlexible(endTimeStr); err == nil && !t.IsZero() { - cond = cond.And("create_time__lte", t) - needCond = true - } - - if needCond { - qs = qs.SetCond(cond) - } - - total, err := qs.Count() - if err != nil { - c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) - return - } - - var rows []models.SystemOperationLog - _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - item := map[string]interface{}{ - "id": rows[i].ID, - "tid": rows[i].Tid, - "user_id": rows[i].UserID, - "module": rows[i].Module, - "action": rows[i].Action, - "method": rows[i].Method, - "url": rows[i].URL, - "ip": rows[i].IP, - "user_agent": rows[i].UserAgent, - "request_data": rows[i].RequestData, - "response_data": rows[i].ResponseData, - "status": rows[i].Status, - "error_message": rows[i].ErrorMessage, - "execution_time": rows[i].ExecutionTime, - "create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"), - "update_time": "", - } - if rows[i].UpdateTime != nil { - item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") - } - list = append(list, item) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -// Detail GET /backend/operationLogs/:id -func (c *BackendOperationLogController) Detail() { - if _, err := c.backendClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - var row models.SystemOperationLog - err = models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("id", id). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.jsonErr(404, 404, "记录不存在") - return - } - out := map[string]interface{}{ - "id": row.ID, - "tid": row.Tid, - "user_id": row.UserID, - "module": row.Module, - "action": row.Action, - "method": row.Method, - "url": row.URL, - "ip": row.IP, - "user_agent": row.UserAgent, - "request_data": row.RequestData, - "response_data": row.ResponseData, - "status": row.Status, - "error_message": row.ErrorMessage, - "execution_time": row.ExecutionTime, - "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -// Delete DELETE /backend/operationLogs/:id -func (c *BackendOperationLogController) Delete() { - if _, err := c.backendClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -type backendBatchDeletePayload struct { - IDs []uint64 `json:"ids"` -} - -// BatchDelete POST /backend/operationLogs/batchDelete -func (c *BackendOperationLogController) BatchDelete() { - if _, err := c.backendClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p backendBatchDeletePayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if len(p.IDs) == 0 { - c.jsonErr(400, 400, "请选择要删除的日志") - return - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("id__in", p.IDs). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "批量删除失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} - _ = c.ServeJSON() -} - -// Statistics GET /backend/operationLogs/statistics -// 供前端筛选项:modules/actions -func (c *BackendOperationLogController) Statistics() { - if _, err := c.backendClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - var moduleRows []models.SystemOperationLog - _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("delete_time__isnull", true). - Filter("module__isnull", false). - Limit(1000). - All(&moduleRows, "Module") - modSet := map[string]struct{}{} - for i := range moduleRows { - m := strings.TrimSpace(moduleRows[i].Module) - if m != "" { - modSet[m] = struct{}{} - } - } - modules := make([]string, 0, len(modSet)) - for k := range modSet { - modules = append(modules, k) - } - - var actionRows []models.SystemOperationLog - _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("delete_time__isnull", true). - Filter("action__isnull", false). - Limit(1000). - All(&actionRows, "Action") - actSet := map[string]struct{}{} - for i := range actionRows { - a := strings.TrimSpace(actionRows[i].Action) - if a != "" { - actSet[a] = struct{}{} - } - } - actions := make([]string, 0, len(actSet)) - for k := range actSet { - actions = append(actions, k) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "modules": modules, - "actions": actions, - }, - } - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// BackendOperationLogController 操作日志(yz_system_operation_log) +type BackendOperationLogController struct { + beego.Controller +} + +func (c *BackendOperationLogController) backendClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *BackendOperationLogController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// List GET /backend/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime= +func (c *BackendOperationLogController) List() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + + keyword := strings.TrimSpace(c.GetString("keyword")) + module := strings.TrimSpace(c.GetString("module")) + action := strings.TrimSpace(c.GetString("action")) + statusStr := strings.TrimSpace(c.GetString("status")) + startTimeStr := strings.TrimSpace(c.GetString("startTime")) + endTimeStr := strings.TrimSpace(c.GetString("endTime")) + + qs := models.Orm.QueryTable(new(models.SystemOperationLog)).Filter("delete_time__isnull", true) + + // 条件拼装 + cond := orm.NewCondition() + needCond := false + + if module != "" { + cond = cond.And("module", module) + needCond = true + } + if action != "" { + cond = cond.And("action", action) + needCond = true + } + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + cond = cond.And("status", st) + needCond = true + } + } + if keyword != "" { + kw := orm.NewCondition(). + Or("module__icontains", keyword). + Or("action__icontains", keyword). + Or("method__icontains", keyword). + Or("url__icontains", keyword). + Or("ip__icontains", keyword). + Or("user_agent__icontains", keyword) + if uid, err := strconv.ParseUint(keyword, 10, 64); err == nil && uid > 0 { + kw = kw.Or("user_id", uid) + } + cond = cond.AndCond(kw) + needCond = true + } + if t, err := parseTimeFlexible(startTimeStr); err == nil && !t.IsZero() { + cond = cond.And("create_time__gte", t) + needCond = true + } + if t, err := parseTimeFlexible(endTimeStr); err == nil && !t.IsZero() { + cond = cond.And("create_time__lte", t) + needCond = true + } + + if needCond { + qs = qs.SetCond(cond) + } + + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) + return + } + + var rows []models.SystemOperationLog + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + item := map[string]interface{}{ + "id": rows[i].ID, + "tid": rows[i].Tid, + "user_id": rows[i].UserID, + "module": rows[i].Module, + "action": rows[i].Action, + "method": rows[i].Method, + "url": rows[i].URL, + "ip": rows[i].IP, + "user_agent": rows[i].UserAgent, + "request_data": rows[i].RequestData, + "response_data": rows[i].ResponseData, + "status": rows[i].Status, + "error_message": rows[i].ErrorMessage, + "execution_time": rows[i].ExecutionTime, + "create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"), + "update_time": "", + } + if rows[i].UpdateTime != nil { + item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") + } + list = append(list, item) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// Detail GET /backend/operationLogs/:id +func (c *BackendOperationLogController) Detail() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + var row models.SystemOperationLog + err = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.jsonErr(404, 404, "记录不存在") + return + } + out := map[string]interface{}{ + "id": row.ID, + "tid": row.Tid, + "user_id": row.UserID, + "module": row.Module, + "action": row.Action, + "method": row.Method, + "url": row.URL, + "ip": row.IP, + "user_agent": row.UserAgent, + "request_data": row.RequestData, + "response_data": row.ResponseData, + "status": row.Status, + "error_message": row.ErrorMessage, + "execution_time": row.ExecutionTime, + "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +// Delete DELETE /backend/operationLogs/:id +func (c *BackendOperationLogController) Delete() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +type backendBatchDeletePayload struct { + IDs []uint64 `json:"ids"` +} + +// BatchDelete POST /backend/operationLogs/batchDelete +func (c *BackendOperationLogController) BatchDelete() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendBatchDeletePayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(p.IDs) == 0 { + c.jsonErr(400, 400, "请选择要删除的日志") + return + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id__in", p.IDs). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} + _ = c.ServeJSON() +} + +// Statistics GET /backend/operationLogs/statistics +// 供前端筛选项:modules/actions +func (c *BackendOperationLogController) Statistics() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + var moduleRows []models.SystemOperationLog + _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("delete_time__isnull", true). + Filter("module__isnull", false). + Limit(1000). + All(&moduleRows, "Module") + modSet := map[string]struct{}{} + for i := range moduleRows { + m := strings.TrimSpace(moduleRows[i].Module) + if m != "" { + modSet[m] = struct{}{} + } + } + modules := make([]string, 0, len(modSet)) + for k := range modSet { + modules = append(modules, k) + } + + var actionRows []models.SystemOperationLog + _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("delete_time__isnull", true). + Filter("action__isnull", false). + Limit(1000). + All(&actionRows, "Action") + actSet := map[string]struct{}{} + for i := range actionRows { + a := strings.TrimSpace(actionRows[i].Action) + if a != "" { + actSet[a] = struct{}{} + } + } + actions := make([]string, 0, len(actSet)) + for k := range actSet { + actions = append(actions, k) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "modules": modules, + "actions": actions, + }, + } + _ = c.ServeJSON() +} diff --git a/go/controllers/backend_site_settings.go b/go/controllers/backend_site_settings.go index e3ee990..860568c 100644 --- a/go/controllers/backend_site_settings.go +++ b/go/controllers/backend_site_settings.go @@ -1,607 +1,607 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -// BackendSiteSettingsController 租户站点设置(站点基本信息) -// 对应前端 normalSettings.vue 的: -// - GET /backend/normalInfos -// - POST /backend/saveNormalInfos -// - GET /platform/normalInfos -// - POST /platform/saveNormalInfos -type BackendSiteSettingsController struct { - beego.Controller -} - -func (c *BackendSiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *BackendSiteSettingsController) claimsByPath() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - - path := strings.ToLower(c.Ctx.Request.URL.Path) - if strings.HasPrefix(path, "/platform/") { - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - } else if strings.HasPrefix(path, "/backend/") { - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - } - - return claims, nil -} - -func parseBackendUint64Flexible(v interface{}) uint64 { - if v == nil { - return 0 - } - switch x := v.(type) { - case float64: - if x <= 0 { - return 0 - } - return uint64(x) - case string: - s := strings.TrimSpace(x) - if s == "" { - return 0 - } - n, err := strconv.ParseUint(s, 10, 64) - if err != nil || n == 0 { - return 0 - } - return n - default: - return 0 - } -} - -type backendNormalInfosOutput struct { - Sitename string `json:"sitename"` - Companyintroduction string `json:"companyintroduction"` - Description string `json:"description"` - Copyright string `json:"copyright"` - Companyname string `json:"companyname"` - Icp string `json:"icp"` - Logo string `json:"logo"` - Logow string `json:"logow"` - Ico string `json:"ico"` -} - -// GetNormalInfos GET /backend/normalInfos 或 /platform/normalInfos -func (c *BackendSiteSettingsController) GetNormalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - // 优先使用 token 中的租户 id;若为 0,则允许前端通过查询参数传入(兼容历史/平台端)。 - tid := uint64(claims.TenantId) - if tid == 0 { - tidStr := strings.TrimSpace(c.GetString("tid")) - if tidStr != "" { - if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { - tid = n - } - } - } - - out := backendNormalInfosOutput{ - Sitename: "", - Companyintroduction: "", - Description: "", - Copyright: "", - Companyname: "", - Icp: "", - Logo: "", - Logow: "", - Ico: "", - } - - // tid 缺失时不报错,直接返回空对象给前端渲染(避免 UI 直接崩)。 - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - var rows []models.TenantSiteSetting - _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Limit(1). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - if len(rows) > 0 { - r := rows[0] - out.Sitename = r.Sitename - out.Companyintroduction = r.Companyintroduction - out.Logo = r.Logo - out.Logow = r.Logow - out.Ico = r.Ico - out.Description = r.Description - out.Copyright = r.Copyright - out.Companyname = r.Companyname - out.Icp = r.Icp - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type backendNormalInfosPayload struct { - // 前端会传 tid(但我们仍优先使用 token 的 tenant_id) - Tid interface{} `json:"tid"` - - Sitename string `json:"sitename"` - Companyintroduction string `json:"companyintroduction"` - Logo string `json:"logo"` - Logow string `json:"logow"` - Ico string `json:"ico"` - Description string `json:"description"` - Copyright string `json:"copyright"` - Companyname string `json:"companyname"` - Icp string `json:"icp"` -} - -// SaveNormalInfos POST /backend/saveNormalInfos 或 /platform/saveNormalInfos -func (c *BackendSiteSettingsController) SaveNormalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - var p backendNormalInfosPayload - if uerr := json.Unmarshal(raw, &p); uerr != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := uint64(claims.TenantId) - if tid == 0 { - tid = parseBackendUint64Flexible(p.Tid) - } - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - sitename := strings.TrimSpace(p.Sitename) - if sitename == "" { - c.jsonErr(400, 400, "站点名称不能为空") - return - } - - now := time.Now() - - up := map[string]interface{}{ - "tid": tid, - "sitename": sitename, - "companyintroduction": strings.TrimSpace(p.Companyintroduction), - "logo": strings.TrimSpace(p.Logo), - "logow": strings.TrimSpace(p.Logow), - "ico": strings.TrimSpace(p.Ico), - "description": strings.TrimSpace(p.Description), - "copyright": strings.TrimSpace(p.Copyright), - "companyname": strings.TrimSpace(p.Companyname), - "icp": strings.TrimSpace(p.Icp), - "update_time": now, - } - - cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Count() - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - if cnt == 0 { - row := &models.TenantSiteSetting{ - Tid: tid, - Sitename: sitename, - Companyintroduction: strings.TrimSpace(p.Companyintroduction), - Logo: strings.TrimSpace(p.Logo), - Logow: strings.TrimSpace(p.Logow), - Ico: strings.TrimSpace(p.Ico), - Description: strings.TrimSpace(p.Description), - Copyright: strings.TrimSpace(p.Copyright), - Companyname: strings.TrimSpace(p.Companyname), - Icp: strings.TrimSpace(p.Icp), - CreateTime: now, - UpdateTime: &now, - } - _, err = models.Orm.Insert(row) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } else { - _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -func (c *BackendSiteSettingsController) resolveBackendTenantID(claims *jwtutil.Claims, payloadTid interface{}) uint64 { - tid := uint64(claims.TenantId) - if tid == 0 { - tid = parseBackendUint64Flexible(payloadTid) - } - if tid == 0 { - tidStr := strings.TrimSpace(c.GetString("tid")) - if tidStr != "" { - if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { - tid = n - } - } - } - return tid -} - -func (c *BackendSiteSettingsController) ensureBackendSettingItemsTable() error { - _, err := models.Orm.Raw(` -CREATE TABLE IF NOT EXISTS yz_system_tenant_setting_items ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - tid BIGINT UNSIGNED NOT NULL DEFAULT 0, - setting_key VARCHAR(64) NOT NULL DEFAULT '', - setting_value LONGTEXT NULL, - create_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP, - update_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - delete_time DATETIME NULL DEFAULT NULL, - PRIMARY KEY (id), - UNIQUE KEY uk_tid_key (tid, setting_key), - KEY idx_tid (tid), - KEY idx_delete_time (delete_time) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户站点扩展设置'; -`).Exec() - return err -} - -func (c *BackendSiteSettingsController) getBackendSettingItems(tid uint64, keys []string) (map[string]string, error) { - out := make(map[string]string, len(keys)) - for _, key := range keys { - out[key] = "" - } - - if err := c.ensureBackendSettingItemsTable(); err != nil { - return out, err - } - - type rowItem struct { - SettingKey string - SettingValue string - } - var rows []rowItem - _, err := models.Orm.Raw( - "SELECT setting_key, IFNULL(setting_value, '') AS setting_value FROM yz_system_tenant_setting_items WHERE tid = ? AND setting_key IN ('"+strings.Join(keys, "','")+"') AND delete_time IS NULL", - tid, - ).QueryRows(&rows) - if err != nil { - return out, err - } - - for _, row := range rows { - out[row.SettingKey] = row.SettingValue - } - return out, nil -} - -func (c *BackendSiteSettingsController) saveBackendSettingItems(tid uint64, values map[string]string) error { - if err := c.ensureBackendSettingItemsTable(); err != nil { - return err - } - for key, value := range values { - _, err := models.Orm.Raw(` -INSERT INTO yz_system_tenant_setting_items (tid, setting_key, setting_value, create_time, update_time) -VALUES (?, ?, ?, NOW(), NOW()) -ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), update_time = NOW(), delete_time = NULL -`, tid, key, value).Exec() - if err != nil { - return err - } - } - return nil -} - -// GetLegalInfos GET /backend/legalInfos -func (c *BackendSiteSettingsController) GetLegalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - tid := c.resolveBackendTenantID(claims, nil) - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ - {"label": "legalNotice", "value": ""}, - {"label": "privacyTerms", "value": ""}, - }} - _ = c.ServeJSON() - return - } - - values, err := c.getBackendSettingItems(tid, []string{"legalNotice", "privacyTerms"}) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ - {"label": "legalNotice", "value": values["legalNotice"]}, - {"label": "privacyTerms", "value": values["privacyTerms"]}, - }} - _ = c.ServeJSON() -} - -type backendLegalInfosPayload struct { - Tid interface{} `json:"tid"` - LegalNotice string `json:"legalNotice"` - PrivacyTerms string `json:"privacyTerms"` -} - -// SaveLegalInfos POST /backend/saveLegalInfos -func (c *BackendSiteSettingsController) SaveLegalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p backendLegalInfosPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := c.resolveBackendTenantID(claims, p.Tid) - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - err = c.saveBackendSettingItems(tid, map[string]string{ - "legalNotice": strings.TrimSpace(p.LegalNotice), - "privacyTerms": strings.TrimSpace(p.PrivacyTerms), - }) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -// GetCompanyInfos GET /backend/companyInfos -func (c *BackendSiteSettingsController) GetCompanyInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - tid := c.resolveBackendTenantID(claims, nil) - out := map[string]interface{}{ - "contact_phone": "", - "contact_email": "", - "address": "", - "worktime": "", - } - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - var row models.SystemTenant - err = models.Orm.QueryTable(new(models.SystemTenant)). - Filter("id", tid). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - if row.ContactPhone != nil { - out["contact_phone"] = *row.ContactPhone - } - if row.ContactEmail != nil { - out["contact_email"] = *row.ContactEmail - } - if row.Address != nil { - out["address"] = *row.Address - } - if row.Worktime != nil { - out["worktime"] = *row.Worktime - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type backendCompanyInfosPayload struct { - Tid interface{} `json:"tid"` - ContactPhone string `json:"contact_phone"` - ContactEmail string `json:"contact_email"` - Address string `json:"address"` - Worktime string `json:"worktime"` -} - -// SaveCompanyInfos POST /backend/saveCompanyInfos -func (c *BackendSiteSettingsController) SaveCompanyInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p backendCompanyInfosPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := c.resolveBackendTenantID(claims, p.Tid) - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - _, err = models.Orm.QueryTable(new(models.SystemTenant)). - Filter("id", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{ - "contact_phone": strings.TrimSpace(p.ContactPhone), - "contact_email": strings.TrimSpace(p.ContactEmail), - "address": strings.TrimSpace(p.Address), - "worktime": strings.TrimSpace(p.Worktime), - "update_time": time.Now(), - }) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -// GetCompanySeo GET /backend/companySeo -func (c *BackendSiteSettingsController) GetCompanySeo() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - tid := c.resolveBackendTenantID(claims, nil) - out := map[string]string{ - "seoTitle": "", - "seoKeywords": "", - "seoDescription": "", - } - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - values, err := c.getBackendSettingItems(tid, []string{"seoTitle", "seoKeywords", "seoDescription"}) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - out["seoTitle"] = values["seoTitle"] - out["seoKeywords"] = values["seoKeywords"] - out["seoDescription"] = values["seoDescription"] - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type backendCompanySeoPayload struct { - Tid interface{} `json:"tid"` - SeoTitle string `json:"seoTitle"` - SeoKeywords string `json:"seoKeywords"` - SeoDescription string `json:"seoDescription"` -} - -// SaveCompanySeo POST /backend/saveCompanySeo -func (c *BackendSiteSettingsController) SaveCompanySeo() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p backendCompanySeoPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := c.resolveBackendTenantID(claims, p.Tid) - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - err = c.saveBackendSettingItems(tid, map[string]string{ - "seoTitle": strings.TrimSpace(p.SeoTitle), - "seoKeywords": strings.TrimSpace(p.SeoKeywords), - "seoDescription": strings.TrimSpace(p.SeoDescription), - }) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendSiteSettingsController 租户站点设置(站点基本信息) +// 对应前端 normalSettings.vue 的: +// - GET /backend/normalInfos +// - POST /backend/saveNormalInfos +// - GET /platform/normalInfos +// - POST /platform/saveNormalInfos +type BackendSiteSettingsController struct { + beego.Controller +} + +func (c *BackendSiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *BackendSiteSettingsController) claimsByPath() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + + path := strings.ToLower(c.Ctx.Request.URL.Path) + if strings.HasPrefix(path, "/platform/") { + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + } else if strings.HasPrefix(path, "/backend/") { + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + } + + return claims, nil +} + +func parseBackendUint64Flexible(v interface{}) uint64 { + if v == nil { + return 0 + } + switch x := v.(type) { + case float64: + if x <= 0 { + return 0 + } + return uint64(x) + case string: + s := strings.TrimSpace(x) + if s == "" { + return 0 + } + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || n == 0 { + return 0 + } + return n + default: + return 0 + } +} + +type backendNormalInfosOutput struct { + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + Icp string `json:"icp"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` +} + +// GetNormalInfos GET /backend/normalInfos 或 /platform/normalInfos +func (c *BackendSiteSettingsController) GetNormalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + // 优先使用 token 中的租户 id;若为 0,则允许前端通过查询参数传入(兼容历史/平台端)。 + tid := uint64(claims.TenantId) + if tid == 0 { + tidStr := strings.TrimSpace(c.GetString("tid")) + if tidStr != "" { + if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { + tid = n + } + } + } + + out := backendNormalInfosOutput{ + Sitename: "", + Companyintroduction: "", + Description: "", + Copyright: "", + Companyname: "", + Icp: "", + Logo: "", + Logow: "", + Ico: "", + } + + // tid 缺失时不报错,直接返回空对象给前端渲染(避免 UI 直接崩)。 + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + var rows []models.TenantSiteSetting + _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Limit(1). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + if len(rows) > 0 { + r := rows[0] + out.Sitename = r.Sitename + out.Companyintroduction = r.Companyintroduction + out.Logo = r.Logo + out.Logow = r.Logow + out.Ico = r.Ico + out.Description = r.Description + out.Copyright = r.Copyright + out.Companyname = r.Companyname + out.Icp = r.Icp + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendNormalInfosPayload struct { + // 前端会传 tid(但我们仍优先使用 token 的 tenant_id) + Tid interface{} `json:"tid"` + + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + Icp string `json:"icp"` +} + +// SaveNormalInfos POST /backend/saveNormalInfos 或 /platform/saveNormalInfos +func (c *BackendSiteSettingsController) SaveNormalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + var p backendNormalInfosPayload + if uerr := json.Unmarshal(raw, &p); uerr != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := uint64(claims.TenantId) + if tid == 0 { + tid = parseBackendUint64Flexible(p.Tid) + } + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + sitename := strings.TrimSpace(p.Sitename) + if sitename == "" { + c.jsonErr(400, 400, "站点名称不能为空") + return + } + + now := time.Now() + + up := map[string]interface{}{ + "tid": tid, + "sitename": sitename, + "companyintroduction": strings.TrimSpace(p.Companyintroduction), + "logo": strings.TrimSpace(p.Logo), + "logow": strings.TrimSpace(p.Logow), + "ico": strings.TrimSpace(p.Ico), + "description": strings.TrimSpace(p.Description), + "copyright": strings.TrimSpace(p.Copyright), + "companyname": strings.TrimSpace(p.Companyname), + "icp": strings.TrimSpace(p.Icp), + "update_time": now, + } + + cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Count() + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + if cnt == 0 { + row := &models.TenantSiteSetting{ + Tid: tid, + Sitename: sitename, + Companyintroduction: strings.TrimSpace(p.Companyintroduction), + Logo: strings.TrimSpace(p.Logo), + Logow: strings.TrimSpace(p.Logow), + Ico: strings.TrimSpace(p.Ico), + Description: strings.TrimSpace(p.Description), + Copyright: strings.TrimSpace(p.Copyright), + Companyname: strings.TrimSpace(p.Companyname), + Icp: strings.TrimSpace(p.Icp), + CreateTime: now, + UpdateTime: &now, + } + _, err = models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } else { + _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +func (c *BackendSiteSettingsController) resolveBackendTenantID(claims *jwtutil.Claims, payloadTid interface{}) uint64 { + tid := uint64(claims.TenantId) + if tid == 0 { + tid = parseBackendUint64Flexible(payloadTid) + } + if tid == 0 { + tidStr := strings.TrimSpace(c.GetString("tid")) + if tidStr != "" { + if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { + tid = n + } + } + } + return tid +} + +func (c *BackendSiteSettingsController) ensureBackendSettingItemsTable() error { + _, err := models.Orm.Raw(` +CREATE TABLE IF NOT EXISTS yz_system_tenant_setting_items ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + tid BIGINT UNSIGNED NOT NULL DEFAULT 0, + setting_key VARCHAR(64) NOT NULL DEFAULT '', + setting_value LONGTEXT NULL, + create_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + delete_time DATETIME NULL DEFAULT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_tid_key (tid, setting_key), + KEY idx_tid (tid), + KEY idx_delete_time (delete_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户站点扩展设置'; +`).Exec() + return err +} + +func (c *BackendSiteSettingsController) getBackendSettingItems(tid uint64, keys []string) (map[string]string, error) { + out := make(map[string]string, len(keys)) + for _, key := range keys { + out[key] = "" + } + + if err := c.ensureBackendSettingItemsTable(); err != nil { + return out, err + } + + type rowItem struct { + SettingKey string + SettingValue string + } + var rows []rowItem + _, err := models.Orm.Raw( + "SELECT setting_key, IFNULL(setting_value, '') AS setting_value FROM yz_system_tenant_setting_items WHERE tid = ? AND setting_key IN ('"+strings.Join(keys, "','")+"') AND delete_time IS NULL", + tid, + ).QueryRows(&rows) + if err != nil { + return out, err + } + + for _, row := range rows { + out[row.SettingKey] = row.SettingValue + } + return out, nil +} + +func (c *BackendSiteSettingsController) saveBackendSettingItems(tid uint64, values map[string]string) error { + if err := c.ensureBackendSettingItemsTable(); err != nil { + return err + } + for key, value := range values { + _, err := models.Orm.Raw(` +INSERT INTO yz_system_tenant_setting_items (tid, setting_key, setting_value, create_time, update_time) +VALUES (?, ?, ?, NOW(), NOW()) +ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), update_time = NOW(), delete_time = NULL +`, tid, key, value).Exec() + if err != nil { + return err + } + } + return nil +} + +// GetLegalInfos GET /backend/legalInfos +func (c *BackendSiteSettingsController) GetLegalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ + {"label": "legalNotice", "value": ""}, + {"label": "privacyTerms", "value": ""}, + }} + _ = c.ServeJSON() + return + } + + values, err := c.getBackendSettingItems(tid, []string{"legalNotice", "privacyTerms"}) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ + {"label": "legalNotice", "value": values["legalNotice"]}, + {"label": "privacyTerms", "value": values["privacyTerms"]}, + }} + _ = c.ServeJSON() +} + +type backendLegalInfosPayload struct { + Tid interface{} `json:"tid"` + LegalNotice string `json:"legalNotice"` + PrivacyTerms string `json:"privacyTerms"` +} + +// SaveLegalInfos POST /backend/saveLegalInfos +func (c *BackendSiteSettingsController) SaveLegalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendLegalInfosPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + err = c.saveBackendSettingItems(tid, map[string]string{ + "legalNotice": strings.TrimSpace(p.LegalNotice), + "privacyTerms": strings.TrimSpace(p.PrivacyTerms), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +// GetCompanyInfos GET /backend/companyInfos +func (c *BackendSiteSettingsController) GetCompanyInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + out := map[string]interface{}{ + "contact_phone": "", + "contact_email": "", + "address": "", + "worktime": "", + } + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + var row models.SystemTenant + err = models.Orm.QueryTable(new(models.SystemTenant)). + Filter("id", tid). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + if row.ContactPhone != nil { + out["contact_phone"] = *row.ContactPhone + } + if row.ContactEmail != nil { + out["contact_email"] = *row.ContactEmail + } + if row.Address != nil { + out["address"] = *row.Address + } + if row.Worktime != nil { + out["worktime"] = *row.Worktime + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendCompanyInfosPayload struct { + Tid interface{} `json:"tid"` + ContactPhone string `json:"contact_phone"` + ContactEmail string `json:"contact_email"` + Address string `json:"address"` + Worktime string `json:"worktime"` +} + +// SaveCompanyInfos POST /backend/saveCompanyInfos +func (c *BackendSiteSettingsController) SaveCompanyInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendCompanyInfosPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + _, err = models.Orm.QueryTable(new(models.SystemTenant)). + Filter("id", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{ + "contact_phone": strings.TrimSpace(p.ContactPhone), + "contact_email": strings.TrimSpace(p.ContactEmail), + "address": strings.TrimSpace(p.Address), + "worktime": strings.TrimSpace(p.Worktime), + "update_time": time.Now(), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +// GetCompanySeo GET /backend/companySeo +func (c *BackendSiteSettingsController) GetCompanySeo() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + out := map[string]string{ + "seoTitle": "", + "seoKeywords": "", + "seoDescription": "", + } + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + values, err := c.getBackendSettingItems(tid, []string{"seoTitle", "seoKeywords", "seoDescription"}) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + out["seoTitle"] = values["seoTitle"] + out["seoKeywords"] = values["seoKeywords"] + out["seoDescription"] = values["seoDescription"] + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendCompanySeoPayload struct { + Tid interface{} `json:"tid"` + SeoTitle string `json:"seoTitle"` + SeoKeywords string `json:"seoKeywords"` + SeoDescription string `json:"seoDescription"` +} + +// SaveCompanySeo POST /backend/saveCompanySeo +func (c *BackendSiteSettingsController) SaveCompanySeo() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendCompanySeoPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + err = c.saveBackendSettingItems(tid, map[string]string{ + "seoTitle": strings.TrimSpace(p.SeoTitle), + "seoKeywords": strings.TrimSpace(p.SeoKeywords), + "seoDescription": strings.TrimSpace(p.SeoDescription), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/backend_sitereminder.go b/go/controllers/backend_sitereminder.go index d33ac3f..9a49462 100644 --- a/go/controllers/backend_sitereminder.go +++ b/go/controllers/backend_sitereminder.go @@ -1,151 +1,151 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - - "server/pkg/jwtutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -type BackendSiteReminderController struct { - beego.Controller -} - -func (c *BackendSiteReminderController) backendClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *BackendSiteReminderController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// GetMyList GET /backend/sitereminder/myList -func (c *BackendSiteReminderController) GetMyList() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - var isRead *int8 - if isReadStr := c.GetString("isRead"); isReadStr != "" { - if val, err := strconv.Atoi(isReadStr); err == nil { - v := int8(val) - isRead = &v - } - } - - list, total, err := services.ListReminders(uint64(claims.UserID), "tenant", page, pageSize, isRead) - if err != nil { - c.jsonErr(500, 500, "获取消息列表失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -// MarkRead POST /backend/sitereminder/read -func (c *BackendSiteReminderController) MarkRead() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - err = services.MarkReminderRead(p.ID, uint64(claims.UserID), "tenant") - if err != nil { - c.jsonErr(500, 500, "操作失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// MarkAllRead POST /backend/sitereminder/readall -func (c *BackendSiteReminderController) MarkAllRead() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - err = services.MarkAllRemindersRead(uint64(claims.UserID), "tenant") - if err != nil { - c.jsonErr(500, 500, "操作失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// Delete POST /backend/sitereminder/delete -func (c *BackendSiteReminderController) Delete() { - claims, err := c.backendClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - err = services.DeleteReminder(p.ID, uint64(claims.UserID), "tenant") - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +type BackendSiteReminderController struct { + beego.Controller +} + +func (c *BackendSiteReminderController) backendClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *BackendSiteReminderController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// GetMyList GET /backend/sitereminder/myList +func (c *BackendSiteReminderController) GetMyList() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + var isRead *int8 + if isReadStr := c.GetString("isRead"); isReadStr != "" { + if val, err := strconv.Atoi(isReadStr); err == nil { + v := int8(val) + isRead = &v + } + } + + list, total, err := services.ListReminders(uint64(claims.UserID), "tenant", page, pageSize, isRead) + if err != nil { + c.jsonErr(500, 500, "获取消息列表失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// MarkRead POST /backend/sitereminder/read +func (c *BackendSiteReminderController) MarkRead() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + err = services.MarkReminderRead(p.ID, uint64(claims.UserID), "tenant") + if err != nil { + c.jsonErr(500, 500, "操作失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// MarkAllRead POST /backend/sitereminder/readall +func (c *BackendSiteReminderController) MarkAllRead() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + err = services.MarkAllRemindersRead(uint64(claims.UserID), "tenant") + if err != nil { + c.jsonErr(500, 500, "操作失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// Delete POST /backend/sitereminder/delete +func (c *BackendSiteReminderController) Delete() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + err = services.DeleteReminder(p.ID, uint64(claims.UserID), "tenant") + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} diff --git a/go/controllers/domain_common.go b/go/controllers/domain_common.go index defeac1..370aefd 100644 --- a/go/controllers/domain_common.go +++ b/go/controllers/domain_common.go @@ -1,21 +1,21 @@ -package controllers - -import ( - "regexp" - - beego "github.com/beego/beego/v2/server/web" -) - -func jsonErr(c *beego.Controller, httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -type domainPoolPayload struct { - ID uint64 `json:"id"` - MainDomain string `json:"main_domain"` - Status int8 `json:"status"` -} - -var subDomainRe = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$`) +package controllers + +import ( + "regexp" + + beego "github.com/beego/beego/v2/server/web" +) + +func jsonErr(c *beego.Controller, httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +type domainPoolPayload struct { + ID uint64 `json:"id"` + MainDomain string `json:"main_domain"` + Status int8 `json:"status"` +} + +var subDomainRe = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$`) diff --git a/go/controllers/platform_account_pool.go b/go/controllers/platform_account_pool.go index 93e27f2..c1514dd 100644 --- a/go/controllers/platform_account_pool.go +++ b/go/controllers/platform_account_pool.go @@ -1,1318 +1,1318 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - "server/pkg/tokenprobe" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -type PlatformAccountPoolCursorController struct{ beego.Controller } -type PlatformAccountPoolWindsurfController struct{ beego.Controller } -type PlatformAccountPoolKrioController struct{ beego.Controller } -type PlatformAccountPoolCodexController struct{ beego.Controller } - -type accountPoolCreateRow struct { - DataType string `json:"type"` - Account string `json:"account"` - Password string `json:"password"` - Token string `json:"token"` - Remark string `json:"remark"` -} - -func requirePlatformAuth(c *beego.Controller) (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func poolJSONErr(c *beego.Controller, httpStatus, code int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": code, "msg": msg} - _ = c.ServeJSON() -} - -func isValidPoolType(t string) bool { - return t == "account" || t == "tk" || t == "account_tk" -} - -func validateCreateRow(row accountPoolCreateRow) error { - if !isValidPoolType(row.DataType) { - return fmt.Errorf("账号类型不正确") - } - row.Account = strings.TrimSpace(row.Account) - row.Password = strings.TrimSpace(row.Password) - row.Token = strings.TrimSpace(row.Token) - if row.DataType == "account" && (row.Account == "" || row.Password == "") { - return fmt.Errorf("账号密码类型必须填写账号和密码") - } - if row.DataType == "tk" && row.Token == "" { - return fmt.Errorf("token类型必须填写token") - } - if row.DataType == "account_tk" && (row.Account == "" || row.Token == "") { - return fmt.Errorf("账号密码+token类型必须填写账号和token,密码可为空") - } - return nil -} - -// accountPoolListWhere 列表筛选(与各号池表字段一致) -func accountPoolListWhere(dataType, status, platform, keyword, account, token, remark string) (where string, args []interface{}) { - var parts []string - if dataType != "" && isValidPoolType(dataType) { - parts = append(parts, "data_type = ?") - args = append(args, dataType) - } - switch status { - case "unused": - parts = append(parts, "is_extracted = ?") - args = append(args, int8(0)) - case "extracted": - parts = append(parts, "is_extracted = ?") - args = append(args, int8(1)) - case "replenished": - parts = append(parts, "is_extracted = ?") - args = append(args, int8(2)) - case "renewed": - parts = append(parts, "is_extracted = ?") - args = append(args, int8(3)) - } - if p := strings.TrimSpace(platform); p != "" { - parts = append(parts, "extracted_platform = ?") - args = append(args, p) - } - if acc := strings.TrimSpace(account); acc != "" { - parts = append(parts, "account LIKE ?") - args = append(args, "%"+acc+"%") - } - if tk := strings.TrimSpace(token); tk != "" { - parts = append(parts, "token LIKE ?") - args = append(args, "%"+tk+"%") - } - if rm := strings.TrimSpace(remark); rm != "" { - parts = append(parts, "remark LIKE ?") - args = append(args, "%"+rm+"%") - } - // 兼容旧版前端 keyword 参数:仅作为账号查询,不再合并 token/备注。 - if kw := strings.TrimSpace(keyword); kw != "" { - parts = append(parts, "account LIKE ?") - args = append(args, "%"+kw+"%") - } - if len(parts) == 0 { - return "1=1", args - } - return strings.Join(parts, " AND "), args -} - -func accountPoolCountMySQL(table, where string, whereArgs []interface{}) (int64, error) { - sqlStr := fmt.Sprintf("SELECT COUNT(*) AS cnt FROM `%s` WHERE %s", table, where) - var maps []orm.Params - _, err := models.Orm.Raw(sqlStr, whereArgs...).Values(&maps) - if err != nil { - return 0, err - } - if len(maps) == 0 { - return 0, nil - } - return paramsCellToInt64(maps[0]["cnt"]), nil -} - -func paramsCellToInt64(v interface{}) int64 { - if v == nil { - return 0 - } - switch x := v.(type) { - case []byte: - n, _ := strconv.ParseInt(strings.TrimSpace(string(x)), 10, 64) - return n - case int64: - return x - case int32: - return int64(x) - case int: - return int64(x) - default: - n, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(x)), 10, 64) - return n - } -} - -func listPoolRows(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 30) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 30 - } - if pageSize > 200 { - pageSize = 200 - } - keyword := strings.TrimSpace(c.GetString("keyword")) - account := strings.TrimSpace(c.GetString("account")) - token := strings.TrimSpace(c.GetString("token")) - remark := strings.TrimSpace(c.GetString("remark")) - dataType := strings.TrimSpace(c.GetString("type")) - status := strings.TrimSpace(c.GetString("status")) - platform := strings.TrimSpace(c.GetString("platform")) - - where, whereArgs := accountPoolListWhere(dataType, status, platform, keyword, account, token, remark) - if module == "cursor" { - u := strings.TrimSpace(c.GetString("usable")) - if u == "1" || u == "0" { - if v, err := strconv.ParseInt(u, 10, 8); err == nil { - where = "(" + where + ") AND is_used = ?" - whereArgs = append(whereArgs, int8(v)) - } - } - } - offset := (page - 1) * pageSize - - var list interface{} - var total int64 - var err error - - switch module { - case "cursor": - table := (&models.PlatformAccountPoolCursor{}).TableName() - total, err = accountPoolCountMySQL(table, where, whereArgs) - if err != nil { - poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) - return - } - sqlStr := fmt.Sprintf( - "SELECT * FROM `%s` WHERE %s ORDER BY FIELD(is_extracted, 0, 2, 3, 1) ASC, id DESC LIMIT ? OFFSET ?", - table, where, - ) - args := append(append([]interface{}{}, whereArgs...), pageSize, offset) - var rows []models.PlatformAccountPoolCursor - _, err = models.Orm.Raw(sqlStr, args...).QueryRows(&rows) - if err != nil && err != orm.ErrNoRows { - poolJSONErr(c, 500, 500, "cursor查询失败: "+err.Error()) - return - } - if rows == nil { - rows = []models.PlatformAccountPoolCursor{} - } - list = rows - case "windsurf": - table := (&models.PlatformAccountPoolWindsurf{}).TableName() - total, err = accountPoolCountMySQL(table, where, whereArgs) - if err != nil { - poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) - return - } - sqlStr := fmt.Sprintf( - "SELECT * FROM `%s` WHERE %s ORDER BY FIELD(is_extracted, 0, 2, 3, 1) ASC, id DESC LIMIT ? OFFSET ?", - table, where, - ) - args := append(append([]interface{}{}, whereArgs...), pageSize, offset) - var rows []models.PlatformAccountPoolWindsurf - _, err = models.Orm.Raw(sqlStr, args...).QueryRows(&rows) - if err != nil && err != orm.ErrNoRows { - poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) - return - } - if rows == nil { - rows = []models.PlatformAccountPoolWindsurf{} - } - list = rows - case "krio": - table := (&models.PlatformAccountPoolKiro{}).TableName() - total, err = accountPoolCountMySQL(table, where, whereArgs) - if err != nil { - poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) - return - } - sqlStr := fmt.Sprintf( - "SELECT * FROM `%s` WHERE %s ORDER BY FIELD(is_extracted, 0, 2, 3, 1) ASC, id DESC LIMIT ? OFFSET ?", - table, where, - ) - args := append(append([]interface{}{}, whereArgs...), pageSize, offset) - var rows []models.PlatformAccountPoolKiro - _, err = models.Orm.Raw(sqlStr, args...).QueryRows(&rows) - if err != nil && err != orm.ErrNoRows { - poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) - return - } - if rows == nil { - rows = []models.PlatformAccountPoolKiro{} - } - list = rows - case "codex": - table := (&models.PlatformAccountPoolCodex{}).TableName() - total, err = accountPoolCountMySQL(table, where, whereArgs) - if err != nil { - poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) - return - } - sqlStr := fmt.Sprintf( - "SELECT * FROM `%s` WHERE %s ORDER BY FIELD(is_extracted, 0, 2, 3, 1) ASC, id DESC LIMIT ? OFFSET ?", - table, where, - ) - args := append(append([]interface{}{}, whereArgs...), pageSize, offset) - var rows []models.PlatformAccountPoolCodex - _, err = models.Orm.Raw(sqlStr, args...).QueryRows(&rows) - if err != nil && err != orm.ErrNoRows { - poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) - return - } - if rows == nil { - rows = []models.PlatformAccountPoolCodex{} - } - list = rows - default: - poolJSONErr(c, 400, 400, "无效模块") - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -func addPoolRow(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var row accountPoolCreateRow - if err := json.Unmarshal(raw, &row); err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - if err := validateCreateRow(row); err != nil { - poolJSONErr(c, 400, 400, err.Error()) - return - } - - switch module { - case "cursor": - r := &models.PlatformAccountPoolCursor{ - DataType: strings.TrimSpace(row.DataType), - Account: strings.TrimSpace(row.Account), - Password: strings.TrimSpace(row.Password), - Token: strings.TrimSpace(row.Token), - Remark: strings.TrimSpace(row.Remark), - IsExtracted: 0, - } - _, err = models.Orm.Insert(r) - case "windsurf": - r := &models.PlatformAccountPoolWindsurf{ - DataType: strings.TrimSpace(row.DataType), - Account: strings.TrimSpace(row.Account), - Password: strings.TrimSpace(row.Password), - Token: strings.TrimSpace(row.Token), - Remark: strings.TrimSpace(row.Remark), - IsExtracted: 0, - } - _, err = models.Orm.Insert(r) - case "krio": - r := &models.PlatformAccountPoolKiro{ - DataType: strings.TrimSpace(row.DataType), - Account: strings.TrimSpace(row.Account), - Password: strings.TrimSpace(row.Password), - Token: strings.TrimSpace(row.Token), - Remark: strings.TrimSpace(row.Remark), - IsExtracted: 0, - } - _, err = models.Orm.Insert(r) - case "codex": - r := &models.PlatformAccountPoolCodex{ - DataType: strings.TrimSpace(row.DataType), - Account: strings.TrimSpace(row.Account), - Password: strings.TrimSpace(row.Password), - Token: strings.TrimSpace(row.Token), - Remark: strings.TrimSpace(row.Remark), - IsExtracted: 0, - } - _, err = models.Orm.Insert(r) - default: - poolJSONErr(c, 400, 400, "无效模块") - return - } - if err != nil { - poolJSONErr(c, 500, 500, "添加失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "添加成功"} - _ = c.ServeJSON() -} - -func batchAddPoolRows(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - Rows []accountPoolCreateRow `json:"rows"` - } - if err := json.Unmarshal(raw, &payload); err != nil || len(payload.Rows) == 0 { - poolJSONErr(c, 400, 400, "参数错误") - return - } - for _, row := range payload.Rows { - if err := validateCreateRow(row); err != nil { - poolJSONErr(c, 400, 400, err.Error()) - return - } - } - for _, row := range payload.Rows { - switch module { - case "cursor": - _, err = models.Orm.Insert(&models.PlatformAccountPoolCursor{ - DataType: strings.TrimSpace(row.DataType), - Account: strings.TrimSpace(row.Account), - Password: strings.TrimSpace(row.Password), - Token: strings.TrimSpace(row.Token), - Remark: strings.TrimSpace(row.Remark), - IsExtracted: 0, - }) - case "windsurf": - _, err = models.Orm.Insert(&models.PlatformAccountPoolWindsurf{ - DataType: strings.TrimSpace(row.DataType), - Account: strings.TrimSpace(row.Account), - Password: strings.TrimSpace(row.Password), - Token: strings.TrimSpace(row.Token), - Remark: strings.TrimSpace(row.Remark), - IsExtracted: 0, - }) - case "krio": - _, err = models.Orm.Insert(&models.PlatformAccountPoolKiro{ - DataType: strings.TrimSpace(row.DataType), - Account: strings.TrimSpace(row.Account), - Password: strings.TrimSpace(row.Password), - Token: strings.TrimSpace(row.Token), - Remark: strings.TrimSpace(row.Remark), - IsExtracted: 0, - }) - case "codex": - _, err = models.Orm.Insert(&models.PlatformAccountPoolCodex{ - DataType: strings.TrimSpace(row.DataType), - Account: strings.TrimSpace(row.Account), - Password: strings.TrimSpace(row.Password), - Token: strings.TrimSpace(row.Token), - Remark: strings.TrimSpace(row.Remark), - IsExtracted: 0, - }) - default: - poolJSONErr(c, 400, 400, "无效模块") - return - } - if err != nil { - poolJSONErr(c, 500, 500, "批量添加失败: "+err.Error()) - return - } - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量添加成功"} - _ = c.ServeJSON() -} - -func getPoolDetail(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - poolJSONErr(c, 400, 400, "无效ID") - return - } - - switch module { - case "cursor": - var row models.PlatformAccountPoolCursor - if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", id).One(&row); err != nil { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} - case "windsurf": - var row models.PlatformAccountPoolWindsurf - if err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", id).One(&row); err != nil { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} - case "krio": - var row models.PlatformAccountPoolKiro - if err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", id).One(&row); err != nil { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} - case "codex": - var row models.PlatformAccountPoolCodex - if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", id).One(&row); err != nil { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} - default: - poolJSONErr(c, 400, 400, "无效模块") - return - } - _ = c.ServeJSON() -} - -func extractPoolRow(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - ID uint64 `json:"id"` - Type string `json:"type"` - Platform string `json:"platform"` // local | xianyu | taobao | pinduoduo | jingdong | douyin | ziyoushangcheng | xubei - Remark string `json:"remark"` - Replenish bool `json:"replenish"` // true 时写入 is_extracted=2(补号),否则为 1(已提取) - } - if err := json.Unmarshal(raw, &payload); err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - if !validExtractPlatform(payload.Platform) { - poolJSONErr(c, 400, 400, "提取平台错误") - return - } - if payload.Type != "" && !isValidPoolType(payload.Type) { - poolJSONErr(c, 400, 400, "提取类型错误") - return - } - - now := time.Now() - platform := payload.Platform - remark := strings.TrimSpace(payload.Remark) - extractStatus := int8(1) - if payload.Replenish { - extractStatus = 2 - } - - switch module { - case "cursor": - var row models.PlatformAccountPoolCursor - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("is_extracted", 0) - if payload.ID > 0 { - qs = qs.Filter("id", payload.ID) - } else if payload.Type != "" { - qs = qs.Filter("data_type", payload.Type) - } - if err := qs.OrderBy("id").One(&row); err != nil { - poolJSONErr(c, 404, 404, "没有可提取数据") - return - } - _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", row.ID).Update(map[string]interface{}{ - "is_extracted": extractStatus, - "extracted_time": now, - "extracted_platform": platform, - "remark": remark, - }) - if err != nil { - poolJSONErr(c, 500, 500, "提取失败: "+err.Error()) - return - } - row.IsExtracted = extractStatus - row.ExtractedTime = &now - pf := platform - row.ExtractedPlatform = &pf - row.Remark = remark - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "提取成功", "data": row} - case "windsurf": - var row models.PlatformAccountPoolWindsurf - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("is_extracted", 0) - if payload.ID > 0 { - qs = qs.Filter("id", payload.ID) - } else if payload.Type != "" { - qs = qs.Filter("data_type", payload.Type) - } - if err := qs.OrderBy("id").One(&row); err != nil { - poolJSONErr(c, 404, 404, "没有可提取数据") - return - } - _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", row.ID).Update(map[string]interface{}{ - "is_extracted": extractStatus, - "extracted_time": now, - "extracted_platform": platform, - "remark": remark, - }) - if err != nil { - poolJSONErr(c, 500, 500, "提取失败: "+err.Error()) - return - } - row.IsExtracted = extractStatus - row.ExtractedTime = &now - pf := platform - row.ExtractedPlatform = &pf - row.Remark = remark - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "提取成功", "data": row} - case "krio": - var row models.PlatformAccountPoolKiro - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("is_extracted", 0) - if payload.ID > 0 { - qs = qs.Filter("id", payload.ID) - } else if payload.Type != "" { - qs = qs.Filter("data_type", payload.Type) - } - if err := qs.OrderBy("id").One(&row); err != nil { - poolJSONErr(c, 404, 404, "没有可提取数据") - return - } - _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", row.ID).Update(map[string]interface{}{ - "is_extracted": extractStatus, - "extracted_time": now, - "extracted_platform": platform, - "remark": remark, - }) - if err != nil { - poolJSONErr(c, 500, 500, "提取失败: "+err.Error()) - return - } - row.IsExtracted = extractStatus - row.ExtractedTime = &now - pf := platform - row.ExtractedPlatform = &pf - row.Remark = remark - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "提取成功", "data": row} - case "codex": - var row models.PlatformAccountPoolCodex - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("is_extracted", 0) - if payload.ID > 0 { - qs = qs.Filter("id", payload.ID) - } else if payload.Type != "" { - qs = qs.Filter("data_type", payload.Type) - } - if err := qs.OrderBy("id").One(&row); err != nil { - poolJSONErr(c, 404, 404, "没有可提取数据") - return - } - _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", row.ID).Update(map[string]interface{}{ - "is_extracted": extractStatus, - "extracted_time": now, - "extracted_platform": platform, - "remark": remark, - }) - if err != nil { - poolJSONErr(c, 500, 500, "提取失败: "+err.Error()) - return - } - row.IsExtracted = extractStatus - row.ExtractedTime = &now - pf := platform - row.ExtractedPlatform = &pf - row.Remark = remark - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "提取成功", "data": row} - default: - poolJSONErr(c, 400, 400, "无效模块") - return - } - _ = c.ServeJSON() -} - -func replenishPoolRow(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - Type string `json:"type"` - Platform string `json:"platform"` - Remark string `json:"remark"` - } - if err := json.Unmarshal(raw, &payload); err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - if !isValidPoolType(payload.Type) { - poolJSONErr(c, 400, 400, "账号类型不正确") - return - } - validPlatforms := map[string]bool{"local": true, "xianyu": true, "pinduoduo": true, "jingdong": true, "douyin": true, "xubei": true} - if !validPlatforms[payload.Platform] { - poolJSONErr(c, 400, 400, "提取平台错误") - return - } - - now := time.Now() - platform := payload.Platform - remark := strings.TrimSpace(payload.Remark) - - replenishWithProbe(c, module, payload.Type, platform, remark, now) -} - -type poolReplenishCandidate struct { - id uint64 - dataType string - token string - isUsed *int8 - row interface{} -} - -type poolReplenishFetcher func() (*poolReplenishCandidate, error) - -// replenishWithProbe 按 id 顺序补号并探测;不可用则标记 is_extracted=2 后继续下一条。 -func replenishWithProbe(c *beego.Controller, module, dataType, platform, remark string, now time.Time) { - var fetch poolReplenishFetcher - switch module { - case "cursor": - fetch = func() (*poolReplenishCandidate, error) { - var row models.PlatformAccountPoolCursor - err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - Filter("is_extracted", 0). - Filter("data_type", dataType). - Filter("delete_time__isnull", true). - OrderBy("id"). - One(&row) - if err != nil { - return nil, err - } - return &poolReplenishCandidate{ - id: row.ID, dataType: row.DataType, token: row.Token, isUsed: row.IsUsed, row: row, - }, nil - } - case "windsurf": - fetch = func() (*poolReplenishCandidate, error) { - var row models.PlatformAccountPoolWindsurf - err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)). - Filter("is_extracted", 0). - Filter("data_type", dataType). - Filter("delete_time__isnull", true). - OrderBy("id"). - One(&row) - if err != nil { - return nil, err - } - return &poolReplenishCandidate{ - id: row.ID, dataType: row.DataType, token: row.Token, row: row, - }, nil - } - case "krio": - fetch = func() (*poolReplenishCandidate, error) { - var row models.PlatformAccountPoolKiro - err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)). - Filter("is_extracted", 0). - Filter("data_type", dataType). - Filter("delete_time__isnull", true). - OrderBy("id"). - One(&row) - if err != nil { - return nil, err - } - return &poolReplenishCandidate{ - id: row.ID, dataType: row.DataType, token: row.Token, row: row, - }, nil - } - case "codex": - fetch = func() (*poolReplenishCandidate, error) { - var row models.PlatformAccountPoolCodex - err := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)). - Filter("is_extracted", 0). - Filter("data_type", dataType). - Filter("delete_time__isnull", true). - OrderBy("id"). - One(&row) - if err != nil { - return nil, err - } - return &poolReplenishCandidate{ - id: row.ID, dataType: row.DataType, token: row.Token, row: row, - }, nil - } - default: - poolJSONErr(c, 400, 400, "无效模块") - return - } - - tableName := poolTableName(module) - if tableName == "" { - poolJSONErr(c, 400, 400, "无效模块") - return - } - - for { - candidate, err := fetch() - if err != nil { - if err == orm.ErrNoRows { - poolJSONErr(c, 404, 404, "暂无可用账号") - } else { - poolJSONErr(c, 500, 500, "查询失败") - } - return - } - - updateFields := map[string]interface{}{ - "is_extracted": int8(2), - "extracted_time": now, - "extracted_platform": platform, - "remark": remark, - "update_time": now, - } - if _, err = models.Orm.QueryTable(tableName). - Filter("id", candidate.id). - Update(updateFields); err != nil { - poolJSONErr(c, 500, 500, "补号失败: "+err.Error()) - return - } - - if known, available := poolIsUsedAvailable(candidate.isUsed); known { - if !available { - continue - } - } else if !poolProbeToken(module, candidate.dataType, candidate.token, candidate.id) { - continue - } - - data := replenishApplyResponse(candidate.row, platform, remark, now) - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": data} - _ = c.ServeJSON() - return - } -} - -func replenishApplyResponse(row interface{}, platform, remark string, now time.Time) interface{} { - pf := platform - switch r := row.(type) { - case models.PlatformAccountPoolCursor: - r.IsExtracted = 2 - r.ExtractedTime = &now - r.ExtractedPlatform = &pf - r.Remark = remark - if r.IsUsed == nil || *r.IsUsed != 1 { - used := int8(1) - r.IsUsed = &used - } - return r - case models.PlatformAccountPoolWindsurf: - r.IsExtracted = 2 - r.ExtractedTime = &now - r.ExtractedPlatform = &pf - r.Remark = remark - return r - case models.PlatformAccountPoolKiro: - r.IsExtracted = 2 - r.ExtractedTime = &now - r.ExtractedPlatform = &pf - r.Remark = remark - return r - case models.PlatformAccountPoolCodex: - r.IsExtracted = 2 - r.ExtractedTime = &now - r.ExtractedPlatform = &pf - r.Remark = remark - return r - default: - return row - } -} - -func updatePoolRemark(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - ID uint64 `json:"id"` - Remark string `json:"remark"` - } - if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { - poolJSONErr(c, 400, 400, "参数错误") - return - } - remark := strings.TrimSpace(payload.Remark) - - var updated int64 - switch module { - case "cursor": - updated, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).Update(map[string]interface{}{ - "remark": remark, - }) - case "windsurf": - updated, err = models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", payload.ID).Update(map[string]interface{}{ - "remark": remark, - }) - case "krio": - updated, err = models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", payload.ID).Update(map[string]interface{}{ - "remark": remark, - }) - case "codex": - updated, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", payload.ID).Update(map[string]interface{}{ - "remark": remark, - }) - default: - poolJSONErr(c, 400, 400, "无效模块") - return - } - if err != nil { - poolJSONErr(c, 500, 500, "备注更新失败: "+err.Error()) - return - } - if updated == 0 { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "备注更新成功"} - _ = c.ServeJSON() -} - -func validExtractPlatform(platform string) bool { - switch platform { - case "local", "xianyu", "taobao", "pinduoduo", "jingdong", "douyin", "ziyoushangcheng", "xubei": - return true - default: - return false - } -} - -func updatePoolExtractFields(module string, id uint64, fields map[string]interface{}) (int64, error) { - switch module { - case "cursor": - return models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", id).Update(fields) - case "windsurf": - return models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", id).Update(fields) - case "krio": - return models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", id).Update(fields) - case "codex": - return models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", id).Update(fields) - default: - return 0, fmt.Errorf("无效模块") - } -} - -func setPoolUnavailable(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { - poolJSONErr(c, 400, 400, "参数错误") - return - } - - now := time.Now() - fields := map[string]interface{}{ - "is_extracted": int8(1), - "extracted_time": now, - "extracted_platform": "local", - "update_time": now, - } - if module == "cursor" { - fields["is_used"] = int8(0) - } - updated, err := updatePoolExtractFields(module, payload.ID, fields) - if err != nil { - poolJSONErr(c, 500, 500, "改不可用失败: "+err.Error()) - return - } - if updated == 0 { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "已标记不可用"} - _ = c.ServeJSON() -} - -func updatePoolUsable(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - if module != "cursor" { - poolJSONErr(c, 400, 400, "该模块不支持可用状态修改") - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - ID uint64 `json:"id"` - Usable int `json:"usable"` - } - if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { - poolJSONErr(c, 400, 400, "参数错误") - return - } - if payload.Usable != 0 && payload.Usable != 1 { - poolJSONErr(c, 400, 400, "可用状态参数错误") - return - } - - now := time.Now() - updated, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).Update(orm.Params{ - "is_used": int8(payload.Usable), - "update_time": now, - }) - if err != nil { - poolJSONErr(c, 500, 500, "可用状态更新失败: "+err.Error()) - return - } - if updated == 0 { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - msg := "已标记不可用" - if payload.Usable == 1 { - msg = "已标记可用" - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg} - _ = c.ServeJSON() -} - -func updatePoolPlatform(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - ID uint64 `json:"id"` - Platform string `json:"platform"` - } - if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { - poolJSONErr(c, 400, 400, "参数错误") - return - } - platform := strings.TrimSpace(payload.Platform) - if !validExtractPlatform(platform) { - poolJSONErr(c, 400, 400, "提取平台错误") - return - } - - now := time.Now() - updated, err := updatePoolExtractFields(module, payload.ID, map[string]interface{}{ - "extracted_platform": platform, - "update_time": now, - }) - if err != nil { - poolJSONErr(c, 500, 500, "平台更新失败: "+err.Error()) - return - } - if updated == 0 { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "平台更新成功"} - _ = c.ServeJSON() -} - -func unextractPoolRow(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { - poolJSONErr(c, 400, 400, "参数错误") - return - } - - now := time.Now() - updated, err := updatePoolExtractFields(module, payload.ID, map[string]interface{}{ - "is_extracted": int8(0), - "extracted_time": nil, - "extracted_platform": nil, - "update_time": now, - }) - if err != nil { - poolJSONErr(c, 500, 500, "反提取失败: "+err.Error()) - return - } - if updated == 0 { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "反提取成功"} - _ = c.ServeJSON() -} - -func probePoolToken(c *beego.Controller, module string) { - if _, err := requirePlatformAuth(c); err != nil { - poolJSONErr(c, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - var payload struct { - ID uint64 `json:"id"` - AccessToken string `json:"accessToken"` - Token string `json:"token"` - } - if err := json.Unmarshal(raw, &payload); err != nil { - poolJSONErr(c, 400, 400, "参数错误") - return - } - - var token string - switch module { - case "cursor": - token = strings.TrimSpace(payload.AccessToken) - if token == "" { - token = strings.TrimSpace(payload.Token) - } - if token == "" { - if payload.ID == 0 { - poolJSONErr(c, 400, 400, "请传入 Cursor 的 accessToken(会话 JWT),或传 id 从库中读取") - return - } - var row models.PlatformAccountPoolCursor - if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).One(&row); err != nil { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - token = strings.TrimSpace(row.Token) - } - case "windsurf": - if payload.ID == 0 { - poolJSONErr(c, 400, 400, "缺少有效 id") - return - } - var row models.PlatformAccountPoolWindsurf - if err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", payload.ID).One(&row); err != nil { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - token = strings.TrimSpace(row.Token) - case "krio": - if payload.ID == 0 { - poolJSONErr(c, 400, 400, "缺少有效 id") - return - } - var row models.PlatformAccountPoolKiro - if err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", payload.ID).One(&row); err != nil { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - token = strings.TrimSpace(row.Token) - case "codex": - if payload.ID == 0 { - poolJSONErr(c, 400, 400, "缺少有效 id") - return - } - var row models.PlatformAccountPoolCodex - if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", payload.ID).One(&row); err != nil { - poolJSONErr(c, 404, 404, "记录不存在") - return - } - token = strings.TrimSpace(row.Token) - default: - poolJSONErr(c, 400, 400, "无效模块") - return - } - - if token == "" { - poolJSONErr(c, 400, 400, "该记录无 Token,无法探测") - return - } - - r := tokenprobe.ProbeOfficial(module, token) - data := map[string]interface{}{ - "ok": r.OK, - "detail": r.Detail, - "httpStatus": r.HTTPStatus, - } - if r.ProbeMessage != "" { - data["probeMessage"] = r.ProbeMessage - } - if r.Endpoint != "" { - data["endpoint"] = r.Endpoint - } - if r.BytesRead > 0 { - data["bytesRead"] = r.BytesRead - } - if r.RawPreview != "" { - data["rawPreview"] = r.RawPreview - } - if r.RequestBodyPrefixHex != "" { - data["requestBodyPrefixHex"] = r.RequestBodyPrefixHex - } - if r.StreamProtocol != "" { - data["streamProtocol"] = r.StreamProtocol - } - if r.StreamNote != "" { - data["streamNote"] = r.StreamNote - } - if module == "cursor" && payload.ID > 0 && r.HTTPStatus == http.StatusOK { - var isUsed int8 - if r.OK { - isUsed = 1 - } else { - isUsed = 0 - } - if _, uerr := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).Update(orm.Params{ - "is_used": isUsed, - "update_time": time.Now(), - }); uerr == nil { - data["is_used"] = int(isUsed) - } - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": data, - } - _ = c.ServeJSON() -} - -func (c *PlatformAccountPoolCursorController) List() { listPoolRows(&c.Controller, "cursor") } -func (c *PlatformAccountPoolCursorController) Add() { addPoolRow(&c.Controller, "cursor") } -func (c *PlatformAccountPoolCursorController) BatchAdd() { batchAddPoolRows(&c.Controller, "cursor") } -func (c *PlatformAccountPoolCursorController) Detail() { getPoolDetail(&c.Controller, "cursor") } -func (c *PlatformAccountPoolCursorController) Extract() { extractPoolRow(&c.Controller, "cursor") } -func (c *PlatformAccountPoolCursorController) Replenish() { replenishPoolRow(&c.Controller, "cursor") } -func (c *PlatformAccountPoolCursorController) UpdateRemark() { - updatePoolRemark(&c.Controller, "cursor") -} -func (c *PlatformAccountPoolCursorController) SetUnavailable() { - setPoolUnavailable(&c.Controller, "cursor") -} -func (c *PlatformAccountPoolCursorController) UpdateUsable() { - updatePoolUsable(&c.Controller, "cursor") -} -func (c *PlatformAccountPoolCursorController) UpdatePlatform() { - updatePoolPlatform(&c.Controller, "cursor") -} -func (c *PlatformAccountPoolCursorController) Unextract() { unextractPoolRow(&c.Controller, "cursor") } -func (c *PlatformAccountPoolCursorController) ProbeToken() { probePoolToken(&c.Controller, "cursor") } - -func (c *PlatformAccountPoolWindsurfController) List() { listPoolRows(&c.Controller, "windsurf") } -func (c *PlatformAccountPoolWindsurfController) Add() { addPoolRow(&c.Controller, "windsurf") } -func (c *PlatformAccountPoolWindsurfController) BatchAdd() { - batchAddPoolRows(&c.Controller, "windsurf") -} -func (c *PlatformAccountPoolWindsurfController) Detail() { getPoolDetail(&c.Controller, "windsurf") } -func (c *PlatformAccountPoolWindsurfController) Extract() { extractPoolRow(&c.Controller, "windsurf") } -func (c *PlatformAccountPoolWindsurfController) Replenish() { - replenishPoolRow(&c.Controller, "windsurf") -} -func (c *PlatformAccountPoolWindsurfController) UpdateRemark() { - updatePoolRemark(&c.Controller, "windsurf") -} -func (c *PlatformAccountPoolWindsurfController) SetUnavailable() { - setPoolUnavailable(&c.Controller, "windsurf") -} -func (c *PlatformAccountPoolWindsurfController) UpdatePlatform() { - updatePoolPlatform(&c.Controller, "windsurf") -} -func (c *PlatformAccountPoolWindsurfController) Unextract() { - unextractPoolRow(&c.Controller, "windsurf") -} -func (c *PlatformAccountPoolWindsurfController) ProbeToken() { - probePoolToken(&c.Controller, "windsurf") -} - -func (c *PlatformAccountPoolKrioController) List() { listPoolRows(&c.Controller, "krio") } -func (c *PlatformAccountPoolKrioController) Add() { addPoolRow(&c.Controller, "krio") } -func (c *PlatformAccountPoolKrioController) BatchAdd() { batchAddPoolRows(&c.Controller, "krio") } -func (c *PlatformAccountPoolKrioController) Detail() { getPoolDetail(&c.Controller, "krio") } -func (c *PlatformAccountPoolKrioController) Extract() { extractPoolRow(&c.Controller, "krio") } -func (c *PlatformAccountPoolKrioController) Replenish() { replenishPoolRow(&c.Controller, "krio") } -func (c *PlatformAccountPoolKrioController) UpdateRemark() { - updatePoolRemark(&c.Controller, "krio") -} -func (c *PlatformAccountPoolKrioController) SetUnavailable() { - setPoolUnavailable(&c.Controller, "krio") -} -func (c *PlatformAccountPoolKrioController) UpdatePlatform() { - updatePoolPlatform(&c.Controller, "krio") -} -func (c *PlatformAccountPoolKrioController) Unextract() { unextractPoolRow(&c.Controller, "krio") } -func (c *PlatformAccountPoolKrioController) ProbeToken() { probePoolToken(&c.Controller, "krio") } - -func (c *PlatformAccountPoolCodexController) List() { listPoolRows(&c.Controller, "codex") } -func (c *PlatformAccountPoolCodexController) Add() { addPoolRow(&c.Controller, "codex") } -func (c *PlatformAccountPoolCodexController) BatchAdd() { batchAddPoolRows(&c.Controller, "codex") } -func (c *PlatformAccountPoolCodexController) Detail() { getPoolDetail(&c.Controller, "codex") } -func (c *PlatformAccountPoolCodexController) Extract() { extractPoolRow(&c.Controller, "codex") } -func (c *PlatformAccountPoolCodexController) Replenish() { replenishPoolRow(&c.Controller, "codex") } -func (c *PlatformAccountPoolCodexController) UpdateRemark() { - updatePoolRemark(&c.Controller, "codex") -} -func (c *PlatformAccountPoolCodexController) SetUnavailable() { - setPoolUnavailable(&c.Controller, "codex") -} -func (c *PlatformAccountPoolCodexController) UpdatePlatform() { - updatePoolPlatform(&c.Controller, "codex") -} -func (c *PlatformAccountPoolCodexController) Unextract() { unextractPoolRow(&c.Controller, "codex") } -func (c *PlatformAccountPoolCodexController) ProbeToken() { probePoolToken(&c.Controller, "codex") } +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + "server/pkg/tokenprobe" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformAccountPoolCursorController struct{ beego.Controller } +type PlatformAccountPoolWindsurfController struct{ beego.Controller } +type PlatformAccountPoolKrioController struct{ beego.Controller } +type PlatformAccountPoolCodexController struct{ beego.Controller } + +type accountPoolCreateRow struct { + DataType string `json:"type"` + Account string `json:"account"` + Password string `json:"password"` + Token string `json:"token"` + Remark string `json:"remark"` +} + +func requirePlatformAuth(c *beego.Controller) (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func poolJSONErr(c *beego.Controller, httpStatus, code int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": code, "msg": msg} + _ = c.ServeJSON() +} + +func isValidPoolType(t string) bool { + return t == "account" || t == "tk" || t == "account_tk" +} + +func validateCreateRow(row accountPoolCreateRow) error { + if !isValidPoolType(row.DataType) { + return fmt.Errorf("账号类型不正确") + } + row.Account = strings.TrimSpace(row.Account) + row.Password = strings.TrimSpace(row.Password) + row.Token = strings.TrimSpace(row.Token) + if row.DataType == "account" && (row.Account == "" || row.Password == "") { + return fmt.Errorf("账号密码类型必须填写账号和密码") + } + if row.DataType == "tk" && row.Token == "" { + return fmt.Errorf("token类型必须填写token") + } + if row.DataType == "account_tk" && (row.Account == "" || row.Token == "") { + return fmt.Errorf("账号密码+token类型必须填写账号和token,密码可为空") + } + return nil +} + +// accountPoolListWhere 列表筛选(与各号池表字段一致) +func accountPoolListWhere(dataType, status, platform, keyword, account, token, remark string) (where string, args []interface{}) { + var parts []string + if dataType != "" && isValidPoolType(dataType) { + parts = append(parts, "data_type = ?") + args = append(args, dataType) + } + switch status { + case "unused": + parts = append(parts, "is_extracted = ?") + args = append(args, int8(0)) + case "extracted": + parts = append(parts, "is_extracted = ?") + args = append(args, int8(1)) + case "replenished": + parts = append(parts, "is_extracted = ?") + args = append(args, int8(2)) + case "renewed": + parts = append(parts, "is_extracted = ?") + args = append(args, int8(3)) + } + if p := strings.TrimSpace(platform); p != "" { + parts = append(parts, "extracted_platform = ?") + args = append(args, p) + } + if acc := strings.TrimSpace(account); acc != "" { + parts = append(parts, "account LIKE ?") + args = append(args, "%"+acc+"%") + } + if tk := strings.TrimSpace(token); tk != "" { + parts = append(parts, "token LIKE ?") + args = append(args, "%"+tk+"%") + } + if rm := strings.TrimSpace(remark); rm != "" { + parts = append(parts, "remark LIKE ?") + args = append(args, "%"+rm+"%") + } + // 兼容旧版前端 keyword 参数:仅作为账号查询,不再合并 token/备注。 + if kw := strings.TrimSpace(keyword); kw != "" { + parts = append(parts, "account LIKE ?") + args = append(args, "%"+kw+"%") + } + if len(parts) == 0 { + return "1=1", args + } + return strings.Join(parts, " AND "), args +} + +func accountPoolCountMySQL(table, where string, whereArgs []interface{}) (int64, error) { + sqlStr := fmt.Sprintf("SELECT COUNT(*) AS cnt FROM `%s` WHERE %s", table, where) + var maps []orm.Params + _, err := models.Orm.Raw(sqlStr, whereArgs...).Values(&maps) + if err != nil { + return 0, err + } + if len(maps) == 0 { + return 0, nil + } + return paramsCellToInt64(maps[0]["cnt"]), nil +} + +func paramsCellToInt64(v interface{}) int64 { + if v == nil { + return 0 + } + switch x := v.(type) { + case []byte: + n, _ := strconv.ParseInt(strings.TrimSpace(string(x)), 10, 64) + return n + case int64: + return x + case int32: + return int64(x) + case int: + return int64(x) + default: + n, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(x)), 10, 64) + return n + } +} + +func listPoolRows(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 30) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 30 + } + if pageSize > 200 { + pageSize = 200 + } + keyword := strings.TrimSpace(c.GetString("keyword")) + account := strings.TrimSpace(c.GetString("account")) + token := strings.TrimSpace(c.GetString("token")) + remark := strings.TrimSpace(c.GetString("remark")) + dataType := strings.TrimSpace(c.GetString("type")) + status := strings.TrimSpace(c.GetString("status")) + platform := strings.TrimSpace(c.GetString("platform")) + + where, whereArgs := accountPoolListWhere(dataType, status, platform, keyword, account, token, remark) + if module == "cursor" { + u := strings.TrimSpace(c.GetString("usable")) + if u == "1" || u == "0" { + if v, err := strconv.ParseInt(u, 10, 8); err == nil { + where = "(" + where + ") AND is_used = ?" + whereArgs = append(whereArgs, int8(v)) + } + } + } + offset := (page - 1) * pageSize + + var list interface{} + var total int64 + var err error + + switch module { + case "cursor": + table := (&models.PlatformAccountPoolCursor{}).TableName() + total, err = accountPoolCountMySQL(table, where, whereArgs) + if err != nil { + poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) + return + } + sqlStr := fmt.Sprintf( + "SELECT * FROM `%s` WHERE %s ORDER BY FIELD(is_extracted, 0, 2, 3, 1) ASC, id DESC LIMIT ? OFFSET ?", + table, where, + ) + args := append(append([]interface{}{}, whereArgs...), pageSize, offset) + var rows []models.PlatformAccountPoolCursor + _, err = models.Orm.Raw(sqlStr, args...).QueryRows(&rows) + if err != nil && err != orm.ErrNoRows { + poolJSONErr(c, 500, 500, "cursor查询失败: "+err.Error()) + return + } + if rows == nil { + rows = []models.PlatformAccountPoolCursor{} + } + list = rows + case "windsurf": + table := (&models.PlatformAccountPoolWindsurf{}).TableName() + total, err = accountPoolCountMySQL(table, where, whereArgs) + if err != nil { + poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) + return + } + sqlStr := fmt.Sprintf( + "SELECT * FROM `%s` WHERE %s ORDER BY FIELD(is_extracted, 0, 2, 3, 1) ASC, id DESC LIMIT ? OFFSET ?", + table, where, + ) + args := append(append([]interface{}{}, whereArgs...), pageSize, offset) + var rows []models.PlatformAccountPoolWindsurf + _, err = models.Orm.Raw(sqlStr, args...).QueryRows(&rows) + if err != nil && err != orm.ErrNoRows { + poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) + return + } + if rows == nil { + rows = []models.PlatformAccountPoolWindsurf{} + } + list = rows + case "krio": + table := (&models.PlatformAccountPoolKiro{}).TableName() + total, err = accountPoolCountMySQL(table, where, whereArgs) + if err != nil { + poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) + return + } + sqlStr := fmt.Sprintf( + "SELECT * FROM `%s` WHERE %s ORDER BY FIELD(is_extracted, 0, 2, 3, 1) ASC, id DESC LIMIT ? OFFSET ?", + table, where, + ) + args := append(append([]interface{}{}, whereArgs...), pageSize, offset) + var rows []models.PlatformAccountPoolKiro + _, err = models.Orm.Raw(sqlStr, args...).QueryRows(&rows) + if err != nil && err != orm.ErrNoRows { + poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) + return + } + if rows == nil { + rows = []models.PlatformAccountPoolKiro{} + } + list = rows + case "codex": + table := (&models.PlatformAccountPoolCodex{}).TableName() + total, err = accountPoolCountMySQL(table, where, whereArgs) + if err != nil { + poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) + return + } + sqlStr := fmt.Sprintf( + "SELECT * FROM `%s` WHERE %s ORDER BY FIELD(is_extracted, 0, 2, 3, 1) ASC, id DESC LIMIT ? OFFSET ?", + table, where, + ) + args := append(append([]interface{}{}, whereArgs...), pageSize, offset) + var rows []models.PlatformAccountPoolCodex + _, err = models.Orm.Raw(sqlStr, args...).QueryRows(&rows) + if err != nil && err != orm.ErrNoRows { + poolJSONErr(c, 500, 500, "获取列表失败: "+err.Error()) + return + } + if rows == nil { + rows = []models.PlatformAccountPoolCodex{} + } + list = rows + default: + poolJSONErr(c, 400, 400, "无效模块") + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +func addPoolRow(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var row accountPoolCreateRow + if err := json.Unmarshal(raw, &row); err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + if err := validateCreateRow(row); err != nil { + poolJSONErr(c, 400, 400, err.Error()) + return + } + + switch module { + case "cursor": + r := &models.PlatformAccountPoolCursor{ + DataType: strings.TrimSpace(row.DataType), + Account: strings.TrimSpace(row.Account), + Password: strings.TrimSpace(row.Password), + Token: strings.TrimSpace(row.Token), + Remark: strings.TrimSpace(row.Remark), + IsExtracted: 0, + } + _, err = models.Orm.Insert(r) + case "windsurf": + r := &models.PlatformAccountPoolWindsurf{ + DataType: strings.TrimSpace(row.DataType), + Account: strings.TrimSpace(row.Account), + Password: strings.TrimSpace(row.Password), + Token: strings.TrimSpace(row.Token), + Remark: strings.TrimSpace(row.Remark), + IsExtracted: 0, + } + _, err = models.Orm.Insert(r) + case "krio": + r := &models.PlatformAccountPoolKiro{ + DataType: strings.TrimSpace(row.DataType), + Account: strings.TrimSpace(row.Account), + Password: strings.TrimSpace(row.Password), + Token: strings.TrimSpace(row.Token), + Remark: strings.TrimSpace(row.Remark), + IsExtracted: 0, + } + _, err = models.Orm.Insert(r) + case "codex": + r := &models.PlatformAccountPoolCodex{ + DataType: strings.TrimSpace(row.DataType), + Account: strings.TrimSpace(row.Account), + Password: strings.TrimSpace(row.Password), + Token: strings.TrimSpace(row.Token), + Remark: strings.TrimSpace(row.Remark), + IsExtracted: 0, + } + _, err = models.Orm.Insert(r) + default: + poolJSONErr(c, 400, 400, "无效模块") + return + } + if err != nil { + poolJSONErr(c, 500, 500, "添加失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "添加成功"} + _ = c.ServeJSON() +} + +func batchAddPoolRows(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + Rows []accountPoolCreateRow `json:"rows"` + } + if err := json.Unmarshal(raw, &payload); err != nil || len(payload.Rows) == 0 { + poolJSONErr(c, 400, 400, "参数错误") + return + } + for _, row := range payload.Rows { + if err := validateCreateRow(row); err != nil { + poolJSONErr(c, 400, 400, err.Error()) + return + } + } + for _, row := range payload.Rows { + switch module { + case "cursor": + _, err = models.Orm.Insert(&models.PlatformAccountPoolCursor{ + DataType: strings.TrimSpace(row.DataType), + Account: strings.TrimSpace(row.Account), + Password: strings.TrimSpace(row.Password), + Token: strings.TrimSpace(row.Token), + Remark: strings.TrimSpace(row.Remark), + IsExtracted: 0, + }) + case "windsurf": + _, err = models.Orm.Insert(&models.PlatformAccountPoolWindsurf{ + DataType: strings.TrimSpace(row.DataType), + Account: strings.TrimSpace(row.Account), + Password: strings.TrimSpace(row.Password), + Token: strings.TrimSpace(row.Token), + Remark: strings.TrimSpace(row.Remark), + IsExtracted: 0, + }) + case "krio": + _, err = models.Orm.Insert(&models.PlatformAccountPoolKiro{ + DataType: strings.TrimSpace(row.DataType), + Account: strings.TrimSpace(row.Account), + Password: strings.TrimSpace(row.Password), + Token: strings.TrimSpace(row.Token), + Remark: strings.TrimSpace(row.Remark), + IsExtracted: 0, + }) + case "codex": + _, err = models.Orm.Insert(&models.PlatformAccountPoolCodex{ + DataType: strings.TrimSpace(row.DataType), + Account: strings.TrimSpace(row.Account), + Password: strings.TrimSpace(row.Password), + Token: strings.TrimSpace(row.Token), + Remark: strings.TrimSpace(row.Remark), + IsExtracted: 0, + }) + default: + poolJSONErr(c, 400, 400, "无效模块") + return + } + if err != nil { + poolJSONErr(c, 500, 500, "批量添加失败: "+err.Error()) + return + } + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量添加成功"} + _ = c.ServeJSON() +} + +func getPoolDetail(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + poolJSONErr(c, 400, 400, "无效ID") + return + } + + switch module { + case "cursor": + var row models.PlatformAccountPoolCursor + if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", id).One(&row); err != nil { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} + case "windsurf": + var row models.PlatformAccountPoolWindsurf + if err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", id).One(&row); err != nil { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} + case "krio": + var row models.PlatformAccountPoolKiro + if err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", id).One(&row); err != nil { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} + case "codex": + var row models.PlatformAccountPoolCodex + if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", id).One(&row); err != nil { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} + default: + poolJSONErr(c, 400, 400, "无效模块") + return + } + _ = c.ServeJSON() +} + +func extractPoolRow(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + ID uint64 `json:"id"` + Type string `json:"type"` + Platform string `json:"platform"` // local | xianyu | taobao | pinduoduo | jingdong | douyin | ziyoushangcheng | xubei + Remark string `json:"remark"` + Replenish bool `json:"replenish"` // true 时写入 is_extracted=2(补号),否则为 1(已提取) + } + if err := json.Unmarshal(raw, &payload); err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + if !validExtractPlatform(payload.Platform) { + poolJSONErr(c, 400, 400, "提取平台错误") + return + } + if payload.Type != "" && !isValidPoolType(payload.Type) { + poolJSONErr(c, 400, 400, "提取类型错误") + return + } + + now := time.Now() + platform := payload.Platform + remark := strings.TrimSpace(payload.Remark) + extractStatus := int8(1) + if payload.Replenish { + extractStatus = 2 + } + + switch module { + case "cursor": + var row models.PlatformAccountPoolCursor + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("is_extracted", 0) + if payload.ID > 0 { + qs = qs.Filter("id", payload.ID) + } else if payload.Type != "" { + qs = qs.Filter("data_type", payload.Type) + } + if err := qs.OrderBy("id").One(&row); err != nil { + poolJSONErr(c, 404, 404, "没有可提取数据") + return + } + _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", row.ID).Update(map[string]interface{}{ + "is_extracted": extractStatus, + "extracted_time": now, + "extracted_platform": platform, + "remark": remark, + }) + if err != nil { + poolJSONErr(c, 500, 500, "提取失败: "+err.Error()) + return + } + row.IsExtracted = extractStatus + row.ExtractedTime = &now + pf := platform + row.ExtractedPlatform = &pf + row.Remark = remark + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "提取成功", "data": row} + case "windsurf": + var row models.PlatformAccountPoolWindsurf + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("is_extracted", 0) + if payload.ID > 0 { + qs = qs.Filter("id", payload.ID) + } else if payload.Type != "" { + qs = qs.Filter("data_type", payload.Type) + } + if err := qs.OrderBy("id").One(&row); err != nil { + poolJSONErr(c, 404, 404, "没有可提取数据") + return + } + _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", row.ID).Update(map[string]interface{}{ + "is_extracted": extractStatus, + "extracted_time": now, + "extracted_platform": platform, + "remark": remark, + }) + if err != nil { + poolJSONErr(c, 500, 500, "提取失败: "+err.Error()) + return + } + row.IsExtracted = extractStatus + row.ExtractedTime = &now + pf := platform + row.ExtractedPlatform = &pf + row.Remark = remark + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "提取成功", "data": row} + case "krio": + var row models.PlatformAccountPoolKiro + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("is_extracted", 0) + if payload.ID > 0 { + qs = qs.Filter("id", payload.ID) + } else if payload.Type != "" { + qs = qs.Filter("data_type", payload.Type) + } + if err := qs.OrderBy("id").One(&row); err != nil { + poolJSONErr(c, 404, 404, "没有可提取数据") + return + } + _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", row.ID).Update(map[string]interface{}{ + "is_extracted": extractStatus, + "extracted_time": now, + "extracted_platform": platform, + "remark": remark, + }) + if err != nil { + poolJSONErr(c, 500, 500, "提取失败: "+err.Error()) + return + } + row.IsExtracted = extractStatus + row.ExtractedTime = &now + pf := platform + row.ExtractedPlatform = &pf + row.Remark = remark + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "提取成功", "data": row} + case "codex": + var row models.PlatformAccountPoolCodex + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("is_extracted", 0) + if payload.ID > 0 { + qs = qs.Filter("id", payload.ID) + } else if payload.Type != "" { + qs = qs.Filter("data_type", payload.Type) + } + if err := qs.OrderBy("id").One(&row); err != nil { + poolJSONErr(c, 404, 404, "没有可提取数据") + return + } + _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", row.ID).Update(map[string]interface{}{ + "is_extracted": extractStatus, + "extracted_time": now, + "extracted_platform": platform, + "remark": remark, + }) + if err != nil { + poolJSONErr(c, 500, 500, "提取失败: "+err.Error()) + return + } + row.IsExtracted = extractStatus + row.ExtractedTime = &now + pf := platform + row.ExtractedPlatform = &pf + row.Remark = remark + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "提取成功", "data": row} + default: + poolJSONErr(c, 400, 400, "无效模块") + return + } + _ = c.ServeJSON() +} + +func replenishPoolRow(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + Type string `json:"type"` + Platform string `json:"platform"` + Remark string `json:"remark"` + } + if err := json.Unmarshal(raw, &payload); err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + if !isValidPoolType(payload.Type) { + poolJSONErr(c, 400, 400, "账号类型不正确") + return + } + validPlatforms := map[string]bool{"local": true, "xianyu": true, "pinduoduo": true, "jingdong": true, "douyin": true, "xubei": true} + if !validPlatforms[payload.Platform] { + poolJSONErr(c, 400, 400, "提取平台错误") + return + } + + now := time.Now() + platform := payload.Platform + remark := strings.TrimSpace(payload.Remark) + + replenishWithProbe(c, module, payload.Type, platform, remark, now) +} + +type poolReplenishCandidate struct { + id uint64 + dataType string + token string + isUsed *int8 + row interface{} +} + +type poolReplenishFetcher func() (*poolReplenishCandidate, error) + +// replenishWithProbe 按 id 顺序补号并探测;不可用则标记 is_extracted=2 后继续下一条。 +func replenishWithProbe(c *beego.Controller, module, dataType, platform, remark string, now time.Time) { + var fetch poolReplenishFetcher + switch module { + case "cursor": + fetch = func() (*poolReplenishCandidate, error) { + var row models.PlatformAccountPoolCursor + err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("is_extracted", 0). + Filter("data_type", dataType). + Filter("delete_time__isnull", true). + OrderBy("id"). + One(&row) + if err != nil { + return nil, err + } + return &poolReplenishCandidate{ + id: row.ID, dataType: row.DataType, token: row.Token, isUsed: row.IsUsed, row: row, + }, nil + } + case "windsurf": + fetch = func() (*poolReplenishCandidate, error) { + var row models.PlatformAccountPoolWindsurf + err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)). + Filter("is_extracted", 0). + Filter("data_type", dataType). + Filter("delete_time__isnull", true). + OrderBy("id"). + One(&row) + if err != nil { + return nil, err + } + return &poolReplenishCandidate{ + id: row.ID, dataType: row.DataType, token: row.Token, row: row, + }, nil + } + case "krio": + fetch = func() (*poolReplenishCandidate, error) { + var row models.PlatformAccountPoolKiro + err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)). + Filter("is_extracted", 0). + Filter("data_type", dataType). + Filter("delete_time__isnull", true). + OrderBy("id"). + One(&row) + if err != nil { + return nil, err + } + return &poolReplenishCandidate{ + id: row.ID, dataType: row.DataType, token: row.Token, row: row, + }, nil + } + case "codex": + fetch = func() (*poolReplenishCandidate, error) { + var row models.PlatformAccountPoolCodex + err := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)). + Filter("is_extracted", 0). + Filter("data_type", dataType). + Filter("delete_time__isnull", true). + OrderBy("id"). + One(&row) + if err != nil { + return nil, err + } + return &poolReplenishCandidate{ + id: row.ID, dataType: row.DataType, token: row.Token, row: row, + }, nil + } + default: + poolJSONErr(c, 400, 400, "无效模块") + return + } + + tableName := poolTableName(module) + if tableName == "" { + poolJSONErr(c, 400, 400, "无效模块") + return + } + + for { + candidate, err := fetch() + if err != nil { + if err == orm.ErrNoRows { + poolJSONErr(c, 404, 404, "暂无可用账号") + } else { + poolJSONErr(c, 500, 500, "查询失败") + } + return + } + + updateFields := map[string]interface{}{ + "is_extracted": int8(2), + "extracted_time": now, + "extracted_platform": platform, + "remark": remark, + "update_time": now, + } + if _, err = models.Orm.QueryTable(tableName). + Filter("id", candidate.id). + Update(updateFields); err != nil { + poolJSONErr(c, 500, 500, "补号失败: "+err.Error()) + return + } + + if known, available := poolIsUsedAvailable(candidate.isUsed); known { + if !available { + continue + } + } else if !poolProbeToken(module, candidate.dataType, candidate.token, candidate.id) { + continue + } + + data := replenishApplyResponse(candidate.row, platform, remark, now) + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": data} + _ = c.ServeJSON() + return + } +} + +func replenishApplyResponse(row interface{}, platform, remark string, now time.Time) interface{} { + pf := platform + switch r := row.(type) { + case models.PlatformAccountPoolCursor: + r.IsExtracted = 2 + r.ExtractedTime = &now + r.ExtractedPlatform = &pf + r.Remark = remark + if r.IsUsed == nil || *r.IsUsed != 1 { + used := int8(1) + r.IsUsed = &used + } + return r + case models.PlatformAccountPoolWindsurf: + r.IsExtracted = 2 + r.ExtractedTime = &now + r.ExtractedPlatform = &pf + r.Remark = remark + return r + case models.PlatformAccountPoolKiro: + r.IsExtracted = 2 + r.ExtractedTime = &now + r.ExtractedPlatform = &pf + r.Remark = remark + return r + case models.PlatformAccountPoolCodex: + r.IsExtracted = 2 + r.ExtractedTime = &now + r.ExtractedPlatform = &pf + r.Remark = remark + return r + default: + return row + } +} + +func updatePoolRemark(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + ID uint64 `json:"id"` + Remark string `json:"remark"` + } + if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { + poolJSONErr(c, 400, 400, "参数错误") + return + } + remark := strings.TrimSpace(payload.Remark) + + var updated int64 + switch module { + case "cursor": + updated, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).Update(map[string]interface{}{ + "remark": remark, + }) + case "windsurf": + updated, err = models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", payload.ID).Update(map[string]interface{}{ + "remark": remark, + }) + case "krio": + updated, err = models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", payload.ID).Update(map[string]interface{}{ + "remark": remark, + }) + case "codex": + updated, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", payload.ID).Update(map[string]interface{}{ + "remark": remark, + }) + default: + poolJSONErr(c, 400, 400, "无效模块") + return + } + if err != nil { + poolJSONErr(c, 500, 500, "备注更新失败: "+err.Error()) + return + } + if updated == 0 { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "备注更新成功"} + _ = c.ServeJSON() +} + +func validExtractPlatform(platform string) bool { + switch platform { + case "local", "xianyu", "taobao", "pinduoduo", "jingdong", "douyin", "ziyoushangcheng", "xubei": + return true + default: + return false + } +} + +func updatePoolExtractFields(module string, id uint64, fields map[string]interface{}) (int64, error) { + switch module { + case "cursor": + return models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", id).Update(fields) + case "windsurf": + return models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", id).Update(fields) + case "krio": + return models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", id).Update(fields) + case "codex": + return models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", id).Update(fields) + default: + return 0, fmt.Errorf("无效模块") + } +} + +func setPoolUnavailable(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { + poolJSONErr(c, 400, 400, "参数错误") + return + } + + now := time.Now() + fields := map[string]interface{}{ + "is_extracted": int8(1), + "extracted_time": now, + "extracted_platform": "local", + "update_time": now, + } + if module == "cursor" { + fields["is_used"] = int8(0) + } + updated, err := updatePoolExtractFields(module, payload.ID, fields) + if err != nil { + poolJSONErr(c, 500, 500, "改不可用失败: "+err.Error()) + return + } + if updated == 0 { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "已标记不可用"} + _ = c.ServeJSON() +} + +func updatePoolUsable(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + if module != "cursor" { + poolJSONErr(c, 400, 400, "该模块不支持可用状态修改") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + ID uint64 `json:"id"` + Usable int `json:"usable"` + } + if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { + poolJSONErr(c, 400, 400, "参数错误") + return + } + if payload.Usable != 0 && payload.Usable != 1 { + poolJSONErr(c, 400, 400, "可用状态参数错误") + return + } + + now := time.Now() + updated, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).Update(orm.Params{ + "is_used": int8(payload.Usable), + "update_time": now, + }) + if err != nil { + poolJSONErr(c, 500, 500, "可用状态更新失败: "+err.Error()) + return + } + if updated == 0 { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + msg := "已标记不可用" + if payload.Usable == 1 { + msg = "已标记可用" + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg} + _ = c.ServeJSON() +} + +func updatePoolPlatform(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + ID uint64 `json:"id"` + Platform string `json:"platform"` + } + if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { + poolJSONErr(c, 400, 400, "参数错误") + return + } + platform := strings.TrimSpace(payload.Platform) + if !validExtractPlatform(platform) { + poolJSONErr(c, 400, 400, "提取平台错误") + return + } + + now := time.Now() + updated, err := updatePoolExtractFields(module, payload.ID, map[string]interface{}{ + "extracted_platform": platform, + "update_time": now, + }) + if err != nil { + poolJSONErr(c, 500, 500, "平台更新失败: "+err.Error()) + return + } + if updated == 0 { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "平台更新成功"} + _ = c.ServeJSON() +} + +func unextractPoolRow(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 { + poolJSONErr(c, 400, 400, "参数错误") + return + } + + now := time.Now() + updated, err := updatePoolExtractFields(module, payload.ID, map[string]interface{}{ + "is_extracted": int8(0), + "extracted_time": nil, + "extracted_platform": nil, + "update_time": now, + }) + if err != nil { + poolJSONErr(c, 500, 500, "反提取失败: "+err.Error()) + return + } + if updated == 0 { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "反提取成功"} + _ = c.ServeJSON() +} + +func probePoolToken(c *beego.Controller, module string) { + if _, err := requirePlatformAuth(c); err != nil { + poolJSONErr(c, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + var payload struct { + ID uint64 `json:"id"` + AccessToken string `json:"accessToken"` + Token string `json:"token"` + } + if err := json.Unmarshal(raw, &payload); err != nil { + poolJSONErr(c, 400, 400, "参数错误") + return + } + + var token string + switch module { + case "cursor": + token = strings.TrimSpace(payload.AccessToken) + if token == "" { + token = strings.TrimSpace(payload.Token) + } + if token == "" { + if payload.ID == 0 { + poolJSONErr(c, 400, 400, "请传入 Cursor 的 accessToken(会话 JWT),或传 id 从库中读取") + return + } + var row models.PlatformAccountPoolCursor + if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).One(&row); err != nil { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + token = strings.TrimSpace(row.Token) + } + case "windsurf": + if payload.ID == 0 { + poolJSONErr(c, 400, 400, "缺少有效 id") + return + } + var row models.PlatformAccountPoolWindsurf + if err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", payload.ID).One(&row); err != nil { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + token = strings.TrimSpace(row.Token) + case "krio": + if payload.ID == 0 { + poolJSONErr(c, 400, 400, "缺少有效 id") + return + } + var row models.PlatformAccountPoolKiro + if err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", payload.ID).One(&row); err != nil { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + token = strings.TrimSpace(row.Token) + case "codex": + if payload.ID == 0 { + poolJSONErr(c, 400, 400, "缺少有效 id") + return + } + var row models.PlatformAccountPoolCodex + if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).Filter("id", payload.ID).One(&row); err != nil { + poolJSONErr(c, 404, 404, "记录不存在") + return + } + token = strings.TrimSpace(row.Token) + default: + poolJSONErr(c, 400, 400, "无效模块") + return + } + + if token == "" { + poolJSONErr(c, 400, 400, "该记录无 Token,无法探测") + return + } + + r := tokenprobe.ProbeOfficial(module, token) + data := map[string]interface{}{ + "ok": r.OK, + "detail": r.Detail, + "httpStatus": r.HTTPStatus, + } + if r.ProbeMessage != "" { + data["probeMessage"] = r.ProbeMessage + } + if r.Endpoint != "" { + data["endpoint"] = r.Endpoint + } + if r.BytesRead > 0 { + data["bytesRead"] = r.BytesRead + } + if r.RawPreview != "" { + data["rawPreview"] = r.RawPreview + } + if r.RequestBodyPrefixHex != "" { + data["requestBodyPrefixHex"] = r.RequestBodyPrefixHex + } + if r.StreamProtocol != "" { + data["streamProtocol"] = r.StreamProtocol + } + if r.StreamNote != "" { + data["streamNote"] = r.StreamNote + } + if module == "cursor" && payload.ID > 0 && r.HTTPStatus == http.StatusOK { + var isUsed int8 + if r.OK { + isUsed = 1 + } else { + isUsed = 0 + } + if _, uerr := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).Update(orm.Params{ + "is_used": isUsed, + "update_time": time.Now(), + }); uerr == nil { + data["is_used"] = int(isUsed) + } + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": data, + } + _ = c.ServeJSON() +} + +func (c *PlatformAccountPoolCursorController) List() { listPoolRows(&c.Controller, "cursor") } +func (c *PlatformAccountPoolCursorController) Add() { addPoolRow(&c.Controller, "cursor") } +func (c *PlatformAccountPoolCursorController) BatchAdd() { batchAddPoolRows(&c.Controller, "cursor") } +func (c *PlatformAccountPoolCursorController) Detail() { getPoolDetail(&c.Controller, "cursor") } +func (c *PlatformAccountPoolCursorController) Extract() { extractPoolRow(&c.Controller, "cursor") } +func (c *PlatformAccountPoolCursorController) Replenish() { replenishPoolRow(&c.Controller, "cursor") } +func (c *PlatformAccountPoolCursorController) UpdateRemark() { + updatePoolRemark(&c.Controller, "cursor") +} +func (c *PlatformAccountPoolCursorController) SetUnavailable() { + setPoolUnavailable(&c.Controller, "cursor") +} +func (c *PlatformAccountPoolCursorController) UpdateUsable() { + updatePoolUsable(&c.Controller, "cursor") +} +func (c *PlatformAccountPoolCursorController) UpdatePlatform() { + updatePoolPlatform(&c.Controller, "cursor") +} +func (c *PlatformAccountPoolCursorController) Unextract() { unextractPoolRow(&c.Controller, "cursor") } +func (c *PlatformAccountPoolCursorController) ProbeToken() { probePoolToken(&c.Controller, "cursor") } + +func (c *PlatformAccountPoolWindsurfController) List() { listPoolRows(&c.Controller, "windsurf") } +func (c *PlatformAccountPoolWindsurfController) Add() { addPoolRow(&c.Controller, "windsurf") } +func (c *PlatformAccountPoolWindsurfController) BatchAdd() { + batchAddPoolRows(&c.Controller, "windsurf") +} +func (c *PlatformAccountPoolWindsurfController) Detail() { getPoolDetail(&c.Controller, "windsurf") } +func (c *PlatformAccountPoolWindsurfController) Extract() { extractPoolRow(&c.Controller, "windsurf") } +func (c *PlatformAccountPoolWindsurfController) Replenish() { + replenishPoolRow(&c.Controller, "windsurf") +} +func (c *PlatformAccountPoolWindsurfController) UpdateRemark() { + updatePoolRemark(&c.Controller, "windsurf") +} +func (c *PlatformAccountPoolWindsurfController) SetUnavailable() { + setPoolUnavailable(&c.Controller, "windsurf") +} +func (c *PlatformAccountPoolWindsurfController) UpdatePlatform() { + updatePoolPlatform(&c.Controller, "windsurf") +} +func (c *PlatformAccountPoolWindsurfController) Unextract() { + unextractPoolRow(&c.Controller, "windsurf") +} +func (c *PlatformAccountPoolWindsurfController) ProbeToken() { + probePoolToken(&c.Controller, "windsurf") +} + +func (c *PlatformAccountPoolKrioController) List() { listPoolRows(&c.Controller, "krio") } +func (c *PlatformAccountPoolKrioController) Add() { addPoolRow(&c.Controller, "krio") } +func (c *PlatformAccountPoolKrioController) BatchAdd() { batchAddPoolRows(&c.Controller, "krio") } +func (c *PlatformAccountPoolKrioController) Detail() { getPoolDetail(&c.Controller, "krio") } +func (c *PlatformAccountPoolKrioController) Extract() { extractPoolRow(&c.Controller, "krio") } +func (c *PlatformAccountPoolKrioController) Replenish() { replenishPoolRow(&c.Controller, "krio") } +func (c *PlatformAccountPoolKrioController) UpdateRemark() { + updatePoolRemark(&c.Controller, "krio") +} +func (c *PlatformAccountPoolKrioController) SetUnavailable() { + setPoolUnavailable(&c.Controller, "krio") +} +func (c *PlatformAccountPoolKrioController) UpdatePlatform() { + updatePoolPlatform(&c.Controller, "krio") +} +func (c *PlatformAccountPoolKrioController) Unextract() { unextractPoolRow(&c.Controller, "krio") } +func (c *PlatformAccountPoolKrioController) ProbeToken() { probePoolToken(&c.Controller, "krio") } + +func (c *PlatformAccountPoolCodexController) List() { listPoolRows(&c.Controller, "codex") } +func (c *PlatformAccountPoolCodexController) Add() { addPoolRow(&c.Controller, "codex") } +func (c *PlatformAccountPoolCodexController) BatchAdd() { batchAddPoolRows(&c.Controller, "codex") } +func (c *PlatformAccountPoolCodexController) Detail() { getPoolDetail(&c.Controller, "codex") } +func (c *PlatformAccountPoolCodexController) Extract() { extractPoolRow(&c.Controller, "codex") } +func (c *PlatformAccountPoolCodexController) Replenish() { replenishPoolRow(&c.Controller, "codex") } +func (c *PlatformAccountPoolCodexController) UpdateRemark() { + updatePoolRemark(&c.Controller, "codex") +} +func (c *PlatformAccountPoolCodexController) SetUnavailable() { + setPoolUnavailable(&c.Controller, "codex") +} +func (c *PlatformAccountPoolCodexController) UpdatePlatform() { + updatePoolPlatform(&c.Controller, "codex") +} +func (c *PlatformAccountPoolCodexController) Unextract() { unextractPoolRow(&c.Controller, "codex") } +func (c *PlatformAccountPoolCodexController) ProbeToken() { probePoolToken(&c.Controller, "codex") } diff --git a/go/controllers/platform_admin_user.go b/go/controllers/platform_admin_user.go index 42016e2..405f701 100644 --- a/go/controllers/platform_admin_user.go +++ b/go/controllers/platform_admin_user.go @@ -1,303 +1,303 @@ -package controllers - -import ( - "encoding/json" - "io" - "strconv" - "strings" - - "server/models" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformAdminUserController 平台管理员用户管理(yz_system_admin_user) -type PlatformAdminUserController struct { - beego.Controller -} - -type adminUserDTO struct { - ID uint64 `json:"id"` - Account string `json:"account"` - Name *string `json:"name"` - Phone *string `json:"phone"` - Email *string `json:"email"` - Qq *string `json:"qq"` - Sex uint8 `json:"sex"` - Avatar *string `json:"avatar"` - Rid uint64 `json:"rid"` - LoginCount uint64 `json:"login_count"` - LastLoginIP *string `json:"last_login_ip"` - Status uint8 `json:"status"` - CreateTime string `json:"create_time"` - UpdateTime *string `json:"update_time"` -} - -func toAdminUserDTO(u models.AdminUser) adminUserDTO { - var updateTime *string - if u.UpdateTime != nil { - s := u.UpdateTime.Format("2006-01-02 15:04:05") - updateTime = &s - } - return adminUserDTO{ - ID: u.ID, - Account: u.Account, - Name: u.Name, - Phone: u.Phone, - Email: u.Email, - Qq: u.Qq, - Sex: u.Sex, - Avatar: u.Avatar, - Rid: u.RoleID, - LoginCount: u.LoginCount, - LastLoginIP: u.LastLoginIP, - Status: u.Status, - CreateTime: u.CreateTime.Format("2006-01-02 15:04:05"), - UpdateTime: updateTime, - } -} - -// GetAllUsers 获取全部平台管理员用户 -// GET /platform/getAllUsers -func (c *PlatformAdminUserController) GetAllUsers() { - rows, total, err := services.ListAdminUsers() - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"} - _ = c.ServeJSON() - return - } - list := make([]adminUserDTO, 0, len(rows)) - for _, u := range rows { - list = append(list, toAdminUserDTO(u)) - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"list": list, "total": total}, - } - _ = c.ServeJSON() -} - -// GetUserInfo 获取用户详情 -// GET /platform/getUserInfo/:id -func (c *PlatformAdminUserController) GetUserInfo() { - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - u, err := services.GetAdminUserByID(id) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": toAdminUserDTO(*u), - } - _ = c.ServeJSON() -} - -type adminAddUserPayload struct { - Account string `json:"account"` - Password string `json:"password"` - Name *string `json:"name"` - Phone *string `json:"phone"` - Email *string `json:"email"` - Qq *string `json:"qq"` - Sex *uint8 `json:"sex"` - Avatar *string `json:"avatar"` - Rid *uint64 `json:"rid"` - Status *uint8 `json:"status"` -} - -// AddUser 添加平台管理员用户(仅写 yz_system_admin_user,不处理 tid) -// POST /platform/addUser -func (c *PlatformAdminUserController) AddUser() { - var p adminAddUserPayload - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - p.Account = strings.TrimSpace(p.Account) - p.Password = strings.TrimSpace(p.Password) - if p.Account == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} - _ = c.ServeJSON() - return - } - if p.Password == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} - _ = c.ServeJSON() - return - } - - status := uint8(1) - if p.Status != nil { - status = *p.Status - } - sex := uint8(0) - if p.Sex != nil { - sex = *p.Sex - } - roleID := uint64(1) - if p.Rid != nil && *p.Rid != 0 { - roleID = *p.Rid - } - - id, err := services.CreateAdminUser(p.Account, p.Password, p.Name, p.Phone, p.Email, p.Qq, p.Avatar, sex, roleID, status) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败"} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"id": id}, - } - _ = c.ServeJSON() -} - -type editUserPayload struct { - Account *string `json:"account"` - Password *string `json:"password"` - Name *string `json:"name"` - Phone *string `json:"phone"` - Email *string `json:"email"` - Qq *string `json:"qq"` - Sex *uint8 `json:"sex"` - Avatar *string `json:"avatar"` - Rid *uint64 `json:"rid"` - Status *uint8 `json:"status"` -} - -// EditUser 编辑用户信息(password 可选,存在则修改) -// POST /platform/editUser/:id -func (c *PlatformAdminUserController) EditUser() { - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - var p editUserPayload - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - - fields := map[string]interface{}{} - if p.Account != nil { - acc := strings.TrimSpace(*p.Account) - if acc != "" { - fields["account"] = acc - } - } - if p.Name != nil { - fields["name"] = *p.Name - } - if p.Phone != nil { - fields["phone"] = *p.Phone - } - if p.Email != nil { - fields["email"] = *p.Email - } - if p.Qq != nil { - fields["qq"] = *p.Qq - } - if p.Sex != nil { - fields["sex"] = *p.Sex - } - if p.Avatar != nil { - fields["avatar"] = *p.Avatar - } - if p.Rid != nil && *p.Rid != 0 { - fields["role_id"] = *p.Rid - } - if p.Status != nil { - fields["status"] = *p.Status - } - - if len(fields) > 0 { - if err := services.UpdateAdminUser(id, fields); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "编辑失败"} - _ = c.ServeJSON() - return - } - } - if p.Password != nil && strings.TrimSpace(*p.Password) != "" { - if err := services.ChangeAdminUserPassword(id, *p.Password); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "密码修改失败"} - _ = c.ServeJSON() - return - } - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// DeleteUser 删除用户 -// DELETE /platform/deleteUser/:id -func (c *PlatformAdminUserController) DeleteUser() { - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - if err := services.DeleteAdminUser(id); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -type changePasswordPayload struct { - ID uint64 `json:"id"` - Password string `json:"password"` -} - -// ChangePassword 修改密码 -// POST /platform/changePassword -func (c *PlatformAdminUserController) ChangePassword() { - var p changePasswordPayload - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - if p.ID == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - if strings.TrimSpace(p.Password) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} - _ = c.ServeJSON() - return - } - if err := services.ChangeAdminUserPassword(p.ID, p.Password); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "修改失败"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "io" + "strconv" + "strings" + + "server/models" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformAdminUserController 平台管理员用户管理(yz_system_admin_user) +type PlatformAdminUserController struct { + beego.Controller +} + +type adminUserDTO struct { + ID uint64 `json:"id"` + Account string `json:"account"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Qq *string `json:"qq"` + Sex uint8 `json:"sex"` + Avatar *string `json:"avatar"` + Rid uint64 `json:"rid"` + LoginCount uint64 `json:"login_count"` + LastLoginIP *string `json:"last_login_ip"` + Status uint8 `json:"status"` + CreateTime string `json:"create_time"` + UpdateTime *string `json:"update_time"` +} + +func toAdminUserDTO(u models.AdminUser) adminUserDTO { + var updateTime *string + if u.UpdateTime != nil { + s := u.UpdateTime.Format("2006-01-02 15:04:05") + updateTime = &s + } + return adminUserDTO{ + ID: u.ID, + Account: u.Account, + Name: u.Name, + Phone: u.Phone, + Email: u.Email, + Qq: u.Qq, + Sex: u.Sex, + Avatar: u.Avatar, + Rid: u.RoleID, + LoginCount: u.LoginCount, + LastLoginIP: u.LastLoginIP, + Status: u.Status, + CreateTime: u.CreateTime.Format("2006-01-02 15:04:05"), + UpdateTime: updateTime, + } +} + +// GetAllUsers 获取全部平台管理员用户 +// GET /platform/getAllUsers +func (c *PlatformAdminUserController) GetAllUsers() { + rows, total, err := services.ListAdminUsers() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"} + _ = c.ServeJSON() + return + } + list := make([]adminUserDTO, 0, len(rows)) + for _, u := range rows { + list = append(list, toAdminUserDTO(u)) + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"list": list, "total": total}, + } + _ = c.ServeJSON() +} + +// GetUserInfo 获取用户详情 +// GET /platform/getUserInfo/:id +func (c *PlatformAdminUserController) GetUserInfo() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + u, err := services.GetAdminUserByID(id) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": toAdminUserDTO(*u), + } + _ = c.ServeJSON() +} + +type adminAddUserPayload struct { + Account string `json:"account"` + Password string `json:"password"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Qq *string `json:"qq"` + Sex *uint8 `json:"sex"` + Avatar *string `json:"avatar"` + Rid *uint64 `json:"rid"` + Status *uint8 `json:"status"` +} + +// AddUser 添加平台管理员用户(仅写 yz_system_admin_user,不处理 tid) +// POST /platform/addUser +func (c *PlatformAdminUserController) AddUser() { + var p adminAddUserPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + p.Account = strings.TrimSpace(p.Account) + p.Password = strings.TrimSpace(p.Password) + if p.Account == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} + _ = c.ServeJSON() + return + } + if p.Password == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + + status := uint8(1) + if p.Status != nil { + status = *p.Status + } + sex := uint8(0) + if p.Sex != nil { + sex = *p.Sex + } + roleID := uint64(1) + if p.Rid != nil && *p.Rid != 0 { + roleID = *p.Rid + } + + id, err := services.CreateAdminUser(p.Account, p.Password, p.Name, p.Phone, p.Email, p.Qq, p.Avatar, sex, roleID, status) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"id": id}, + } + _ = c.ServeJSON() +} + +type editUserPayload struct { + Account *string `json:"account"` + Password *string `json:"password"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Qq *string `json:"qq"` + Sex *uint8 `json:"sex"` + Avatar *string `json:"avatar"` + Rid *uint64 `json:"rid"` + Status *uint8 `json:"status"` +} + +// EditUser 编辑用户信息(password 可选,存在则修改) +// POST /platform/editUser/:id +func (c *PlatformAdminUserController) EditUser() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + var p editUserPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + fields := map[string]interface{}{} + if p.Account != nil { + acc := strings.TrimSpace(*p.Account) + if acc != "" { + fields["account"] = acc + } + } + if p.Name != nil { + fields["name"] = *p.Name + } + if p.Phone != nil { + fields["phone"] = *p.Phone + } + if p.Email != nil { + fields["email"] = *p.Email + } + if p.Qq != nil { + fields["qq"] = *p.Qq + } + if p.Sex != nil { + fields["sex"] = *p.Sex + } + if p.Avatar != nil { + fields["avatar"] = *p.Avatar + } + if p.Rid != nil && *p.Rid != 0 { + fields["role_id"] = *p.Rid + } + if p.Status != nil { + fields["status"] = *p.Status + } + + if len(fields) > 0 { + if err := services.UpdateAdminUser(id, fields); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "编辑失败"} + _ = c.ServeJSON() + return + } + } + if p.Password != nil && strings.TrimSpace(*p.Password) != "" { + if err := services.ChangeAdminUserPassword(id, *p.Password); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "密码修改失败"} + _ = c.ServeJSON() + return + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteUser 删除用户 +// DELETE /platform/deleteUser/:id +func (c *PlatformAdminUserController) DeleteUser() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + if err := services.DeleteAdminUser(id); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +type changePasswordPayload struct { + ID uint64 `json:"id"` + Password string `json:"password"` +} + +// ChangePassword 修改密码 +// POST /platform/changePassword +func (c *PlatformAdminUserController) ChangePassword() { + var p changePasswordPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + if p.ID == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + if strings.TrimSpace(p.Password) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + if err := services.ChangeAdminUserPassword(p.ID, p.Password); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "修改失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_auth.go b/go/controllers/platform_auth.go index c772f87..fd12675 100644 --- a/go/controllers/platform_auth.go +++ b/go/controllers/platform_auth.go @@ -1,425 +1,425 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strings" - - "server/models" - "server/pkg/jwtutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -type platformLoginRequest struct { - Account string `json:"account"` - Password string `json:"password"` - Code string `json:"code"` - // 极验4验证参数 - CaptchaID string `json:"captcha_id"` - LotNumber string `json:"lot_number"` - PassToken string `json:"pass_token"` - GenTime string `json:"gen_time"` - CaptchaOutput string `json:"captcha_output"` -} - -type backendLoginRequest struct { - TenantName string `json:"tenant_name"` - Account string `json:"account"` - Password string `json:"password"` - Code string `json:"code"` -} - -// PlatformAuthController 平台端认证控制器 -type PlatformAuthController struct { - beego.Controller -} - -// LoginPlatform 平台端登录(不需要租户) -func (c *PlatformAuthController) LoginPlatform() { - var req platformLoginRequest - - // 先尝试从缓存读取 - body := c.Ctx.Input.RequestBody - - // 如果缓存为空,直接从请求体读取 - if len(body) == 0 { - var err error - body, err = io.ReadAll(c.Ctx.Request.Body) - if err != nil { - fmt.Println("读取请求体失败:", err) - c.Data["json"] = map[string]interface{}{ - "code": 400, - "msg": "参数错误", - } - _ = c.ServeJSON() - return - } - } - - if len(body) == 0 { - fmt.Println("请求体为空") - c.Data["json"] = map[string]interface{}{ - "code": 400, - "msg": "参数错误", - } - _ = c.ServeJSON() - return - } - - fmt.Println("登录请求体:", string(body)) - - if err := json.Unmarshal(body, &req); err != nil { - fmt.Println("JSON解析失败:", err, "body:", string(body)) - c.Data["json"] = map[string]interface{}{ - "code": 400, - "msg": "参数错误: " + err.Error(), - } - _ = c.ServeJSON() - return - } - - fmt.Printf("解析后的请求: %+v\n", req) - - if req.Account == "" || req.Password == "" { - fmt.Println("账号或密码为空, account:", req.Account, "password:", req.Password) - c.Data["json"] = map[string]interface{}{ - "code": 400, - "msg": "用户名或密码不能为空", - } - _ = c.ServeJSON() - return - } - - cfg, _ := models.GetPlatformLoginVerify() - if cfg.OpenVerifyEnabled == 1 { - // 极验验证 - if cfg.VerifyType == "geetest4" { - if req.LotNumber == "" || req.PassToken == "" || req.GenTime == "" || req.CaptchaOutput == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请完成人机验证"} - _ = c.ServeJSON() - return - } - // TODO: 这里应该调用极验服务端SDK验证,暂时跳过验证 - // 如果需要严格验证,需要集成极验服务端SDK - } else if cfg.VerifyType == "geetest3" { - // 极验3验证 - if req.CaptchaOutput == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请完成人机验证"} - _ = c.ServeJSON() - return - } - // TODO: 这里应该调用极验服务端SDK验证,暂时跳过验证 - } else if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { - if strings.TrimSpace(req.Code) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请输入验证码"} - _ = c.ServeJSON() - return - } - if err := services.VerifyPlatformLoginCode(req.Account, cfg.VerifyType, req.Code); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} - _ = c.ServeJSON() - return - } - } - } - - // 控制器只做 HTTP 解析与响应编排,业务逻辑放 services 层 - token, loginUser, err := services.PlatformAdminLogin(req.Account, req.Password) - if err != nil { - c.Data["json"] = map[string]interface{}{ - "code": 401, - "msg": err.Error(), - } - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "登录成功", - "data": map[string]interface{}{ - "token": token, - "user": map[string]interface{}{ - "id": loginUser.ID, - "account": loginUser.Account, - "name": loginUser.Name, - "rid": loginUser.Rid, - "avatar": loginUser.Avatar, - "role_name": loginUser.RoleName, - }, - }, - } - _ = c.ServeJSON() -} - -// LoginBackend backend 登录(需要租户) -func (c *PlatformAuthController) LoginBackend() { - var req backendLoginRequest - - body := c.Ctx.Input.RequestBody - if len(body) == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - if err := json.Unmarshal(body, &req); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - if req.TenantName == "" || req.Account == "" || req.Password == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户名称、用户名或密码不能为空"} - _ = c.ServeJSON() - return - } - cfg, _ := models.GetPlatformLoginVerify() - if cfg.OpenVerifyEnabled == 1 { - if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { - if strings.TrimSpace(req.Code) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请输入验证码"} - _ = c.ServeJSON() - return - } - if err := services.VerifyBackendLoginCode(req.TenantName, req.Account, cfg.VerifyType, req.Code); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} - _ = c.ServeJSON() - return - } - } - } - - token, loginUser, err := services.BackendLogin(req.TenantName, req.Account, req.Password) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 401, "msg": err.Error()} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "登录成功", - "data": map[string]interface{}{ - "token": token, - "user": map[string]interface{}{ - "id": loginUser.ID, - "account": loginUser.Account, - "name": loginUser.Name, - "tid": loginUser.Tid, - "rid": loginUser.Rid, - "avatar": loginUser.Avatar, - "role_name": loginUser.RoleName, - }, - }, - } - _ = c.ServeJSON() -} - -// GetCurrentUser 当前登录平台用户信息(含角色名称),需 Bearer Token -func (c *PlatformAuthController) GetCurrentUser() { - authHeader := c.Ctx.Request.Header.Get("Authorization") - if authHeader == "" { - c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录"} - _ = c.ServeJSON() - return - } - authParts := strings.SplitN(authHeader, " ", 2) - if len(authParts) != 2 || authParts[0] != "Bearer" { - c.Data["json"] = map[string]interface{}{"code": 401, "msg": "认证信息格式错误"} - _ = c.ServeJSON() - return - } - claims, err := jwtutil.ParseToken(authParts[1]) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 401, "msg": "无效的token"} - _ = c.ServeJSON() - return - } - if claims.UserType != "platform" { - c.Data["json"] = map[string]interface{}{"code": 403, "msg": "无权访问"} - _ = c.ServeJSON() - return - } - loginUser, err := services.PlatformGetCurrentUser(uint64(claims.UserID)) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 401, "msg": err.Error()} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "id": loginUser.ID, - "account": loginUser.Account, - "name": loginUser.Name, - "rid": loginUser.Rid, - "avatar": loginUser.Avatar, - "role_name": loginUser.RoleName, - }, - } - _ = c.ServeJSON() -} - -// SendLoginCode 发送登录验证码(占位实现) -func (c *PlatformAuthController) SendLoginCode() { - var req struct { - Account string `json:"account"` - TenantName string `json:"tenant_name"` - Channel string `json:"channel"` - } - body := c.Ctx.Input.RequestBody - if err := json.Unmarshal(body, &req); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - cfg, _ := models.GetPlatformLoginVerify() - if cfg.OpenVerifyEnabled != 1 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "当前未开启验证"} - _ = c.ServeJSON() - return - } - channel := strings.TrimSpace(req.Channel) - if channel == "" { - channel = cfg.VerifyType - } - if channel != "sms" && channel != "email" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "仅支持短信/邮箱验证码"} - _ = c.ServeJSON() - return - } - path := strings.ToLower(c.Ctx.Request.URL.Path) - var sendErr error - if strings.HasPrefix(path, "/backend/") { - sendErr = services.SendBackendLoginCode(req.TenantName, req.Account, channel) - } else { - sendErr = services.SendPlatformLoginCode(req.Account, channel) - } - if sendErr != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": sendErr.Error()} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "验证码已发送"} - _ = c.ServeJSON() -} - -// LoginBySms 手机号验证码登录(占位实现) -func (c *PlatformAuthController) LoginBySms() { - c.Data["json"] = map[string]interface{}{ - "code": 501, - "msg": "手机号验证码登录暂未实现", - } - _ = c.ServeJSON() -} - -// Logout 平台退出登录(占位实现,当前为无状态直接返回成功) -func (c *PlatformAuthController) Logout() { - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "退出成功", - } - _ = c.ServeJSON() -} - -// GetGeetest3Infos 获取极验3.0配置(占位实现) -func (c *PlatformAuthController) GetGeetest3Infos() { - cfg, _ := models.GetPlatformLoginVerify() - if cfg.Geetest3ID == nil || cfg.Geetest3Key == nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "未配置极验3参数"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "captcha_id": *cfg.Geetest3ID, - "captcha_key": *cfg.Geetest3Key, - }, - } - _ = c.ServeJSON() -} - -// GetGeetest4Infos 获取极验4.0配置(占位实现) -func (c *PlatformAuthController) GetGeetest4Infos() { - cfg, _ := models.GetPlatformLoginVerify() - if cfg.Geetest4ID == nil || cfg.Geetest4Key == nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "未配置极验4参数"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "captcha_id": *cfg.Geetest4ID, - "captcha_key": *cfg.Geetest4Key, - }, - } - _ = c.ServeJSON() -} - -// GetOpenVerify 判断是否开启登录验证(占位实现) -func (c *PlatformAuthController) GetOpenVerify() { - cfg, _ := models.GetPlatformLoginVerify() - openVerify := "0" - if cfg.OpenVerifyEnabled == 1 { - openVerify = "1" - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "ok", - "data": []map[string]string{ - { - "label": "openVerify", - "value": openVerify, - }, - { - "label": "verifyType", - "value": cfg.VerifyType, - }, - }, - } - _ = c.ServeJSON() -} - -// Register 注册(占位实现) -func (c *PlatformAuthController) Register() { - c.Data["json"] = map[string]interface{}{ - "code": 501, - "msg": "注册暂未实现", - } - _ = c.ServeJSON() -} - -// SendRegisterCode 发送注册验证码(占位实现) -func (c *PlatformAuthController) SendRegisterCode() { - c.Data["json"] = map[string]interface{}{ - "code": 501, - "msg": "发送注册验证码暂未实现", - } - _ = c.ServeJSON() -} - -// ResetPassword 忘记密码重置(占位实现) -func (c *PlatformAuthController) ResetPassword() { - c.Data["json"] = map[string]interface{}{ - "code": 501, - "msg": "重置密码暂未实现", - } - _ = c.ServeJSON() -} - -// SendResetCode 发送找回密码验证码(占位实现) -func (c *PlatformAuthController) SendResetCode() { - c.Data["json"] = map[string]interface{}{ - "code": 501, - "msg": "发送找回密码验证码暂未实现", - } - _ = c.ServeJSON() -} - +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strings" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +type platformLoginRequest struct { + Account string `json:"account"` + Password string `json:"password"` + Code string `json:"code"` + // 极验4验证参数 + CaptchaID string `json:"captcha_id"` + LotNumber string `json:"lot_number"` + PassToken string `json:"pass_token"` + GenTime string `json:"gen_time"` + CaptchaOutput string `json:"captcha_output"` +} + +type backendLoginRequest struct { + TenantName string `json:"tenant_name"` + Account string `json:"account"` + Password string `json:"password"` + Code string `json:"code"` +} + +// PlatformAuthController 平台端认证控制器 +type PlatformAuthController struct { + beego.Controller +} + +// LoginPlatform 平台端登录(不需要租户) +func (c *PlatformAuthController) LoginPlatform() { + var req platformLoginRequest + + // 先尝试从缓存读取 + body := c.Ctx.Input.RequestBody + + // 如果缓存为空,直接从请求体读取 + if len(body) == 0 { + var err error + body, err = io.ReadAll(c.Ctx.Request.Body) + if err != nil { + fmt.Println("读取请求体失败:", err) + c.Data["json"] = map[string]interface{}{ + "code": 400, + "msg": "参数错误", + } + _ = c.ServeJSON() + return + } + } + + if len(body) == 0 { + fmt.Println("请求体为空") + c.Data["json"] = map[string]interface{}{ + "code": 400, + "msg": "参数错误", + } + _ = c.ServeJSON() + return + } + + fmt.Println("登录请求体:", string(body)) + + if err := json.Unmarshal(body, &req); err != nil { + fmt.Println("JSON解析失败:", err, "body:", string(body)) + c.Data["json"] = map[string]interface{}{ + "code": 400, + "msg": "参数错误: " + err.Error(), + } + _ = c.ServeJSON() + return + } + + fmt.Printf("解析后的请求: %+v\n", req) + + if req.Account == "" || req.Password == "" { + fmt.Println("账号或密码为空, account:", req.Account, "password:", req.Password) + c.Data["json"] = map[string]interface{}{ + "code": 400, + "msg": "用户名或密码不能为空", + } + _ = c.ServeJSON() + return + } + + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled == 1 { + // 极验验证 + if cfg.VerifyType == "geetest4" { + if req.LotNumber == "" || req.PassToken == "" || req.GenTime == "" || req.CaptchaOutput == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请完成人机验证"} + _ = c.ServeJSON() + return + } + // TODO: 这里应该调用极验服务端SDK验证,暂时跳过验证 + // 如果需要严格验证,需要集成极验服务端SDK + } else if cfg.VerifyType == "geetest3" { + // 极验3验证 + if req.CaptchaOutput == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请完成人机验证"} + _ = c.ServeJSON() + return + } + // TODO: 这里应该调用极验服务端SDK验证,暂时跳过验证 + } else if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { + if strings.TrimSpace(req.Code) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请输入验证码"} + _ = c.ServeJSON() + return + } + if err := services.VerifyPlatformLoginCode(req.Account, cfg.VerifyType, req.Code); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + } + } + + // 控制器只做 HTTP 解析与响应编排,业务逻辑放 services 层 + token, loginUser, err := services.PlatformAdminLogin(req.Account, req.Password) + if err != nil { + c.Data["json"] = map[string]interface{}{ + "code": 401, + "msg": err.Error(), + } + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "登录成功", + "data": map[string]interface{}{ + "token": token, + "user": map[string]interface{}{ + "id": loginUser.ID, + "account": loginUser.Account, + "name": loginUser.Name, + "rid": loginUser.Rid, + "avatar": loginUser.Avatar, + "role_name": loginUser.RoleName, + }, + }, + } + _ = c.ServeJSON() +} + +// LoginBackend backend 登录(需要租户) +func (c *PlatformAuthController) LoginBackend() { + var req backendLoginRequest + + body := c.Ctx.Input.RequestBody + if len(body) == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + if err := json.Unmarshal(body, &req); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + if req.TenantName == "" || req.Account == "" || req.Password == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户名称、用户名或密码不能为空"} + _ = c.ServeJSON() + return + } + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled == 1 { + if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { + if strings.TrimSpace(req.Code) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请输入验证码"} + _ = c.ServeJSON() + return + } + if err := services.VerifyBackendLoginCode(req.TenantName, req.Account, cfg.VerifyType, req.Code); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + } + } + + token, loginUser, err := services.BackendLogin(req.TenantName, req.Account, req.Password) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 401, "msg": err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "登录成功", + "data": map[string]interface{}{ + "token": token, + "user": map[string]interface{}{ + "id": loginUser.ID, + "account": loginUser.Account, + "name": loginUser.Name, + "tid": loginUser.Tid, + "rid": loginUser.Rid, + "avatar": loginUser.Avatar, + "role_name": loginUser.RoleName, + }, + }, + } + _ = c.ServeJSON() +} + +// GetCurrentUser 当前登录平台用户信息(含角色名称),需 Bearer Token +func (c *PlatformAuthController) GetCurrentUser() { + authHeader := c.Ctx.Request.Header.Get("Authorization") + if authHeader == "" { + c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录"} + _ = c.ServeJSON() + return + } + authParts := strings.SplitN(authHeader, " ", 2) + if len(authParts) != 2 || authParts[0] != "Bearer" { + c.Data["json"] = map[string]interface{}{"code": 401, "msg": "认证信息格式错误"} + _ = c.ServeJSON() + return + } + claims, err := jwtutil.ParseToken(authParts[1]) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 401, "msg": "无效的token"} + _ = c.ServeJSON() + return + } + if claims.UserType != "platform" { + c.Data["json"] = map[string]interface{}{"code": 403, "msg": "无权访问"} + _ = c.ServeJSON() + return + } + loginUser, err := services.PlatformGetCurrentUser(uint64(claims.UserID)) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 401, "msg": err.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "id": loginUser.ID, + "account": loginUser.Account, + "name": loginUser.Name, + "rid": loginUser.Rid, + "avatar": loginUser.Avatar, + "role_name": loginUser.RoleName, + }, + } + _ = c.ServeJSON() +} + +// SendLoginCode 发送登录验证码(占位实现) +func (c *PlatformAuthController) SendLoginCode() { + var req struct { + Account string `json:"account"` + TenantName string `json:"tenant_name"` + Channel string `json:"channel"` + } + body := c.Ctx.Input.RequestBody + if err := json.Unmarshal(body, &req); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled != 1 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "当前未开启验证"} + _ = c.ServeJSON() + return + } + channel := strings.TrimSpace(req.Channel) + if channel == "" { + channel = cfg.VerifyType + } + if channel != "sms" && channel != "email" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "仅支持短信/邮箱验证码"} + _ = c.ServeJSON() + return + } + path := strings.ToLower(c.Ctx.Request.URL.Path) + var sendErr error + if strings.HasPrefix(path, "/backend/") { + sendErr = services.SendBackendLoginCode(req.TenantName, req.Account, channel) + } else { + sendErr = services.SendPlatformLoginCode(req.Account, channel) + } + if sendErr != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": sendErr.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "验证码已发送"} + _ = c.ServeJSON() +} + +// LoginBySms 手机号验证码登录(占位实现) +func (c *PlatformAuthController) LoginBySms() { + c.Data["json"] = map[string]interface{}{ + "code": 501, + "msg": "手机号验证码登录暂未实现", + } + _ = c.ServeJSON() +} + +// Logout 平台退出登录(占位实现,当前为无状态直接返回成功) +func (c *PlatformAuthController) Logout() { + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "退出成功", + } + _ = c.ServeJSON() +} + +// GetGeetest3Infos 获取极验3.0配置(占位实现) +func (c *PlatformAuthController) GetGeetest3Infos() { + cfg, _ := models.GetPlatformLoginVerify() + if cfg.Geetest3ID == nil || cfg.Geetest3Key == nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "未配置极验3参数"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "captcha_id": *cfg.Geetest3ID, + "captcha_key": *cfg.Geetest3Key, + }, + } + _ = c.ServeJSON() +} + +// GetGeetest4Infos 获取极验4.0配置(占位实现) +func (c *PlatformAuthController) GetGeetest4Infos() { + cfg, _ := models.GetPlatformLoginVerify() + if cfg.Geetest4ID == nil || cfg.Geetest4Key == nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "未配置极验4参数"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "captcha_id": *cfg.Geetest4ID, + "captcha_key": *cfg.Geetest4Key, + }, + } + _ = c.ServeJSON() +} + +// GetOpenVerify 判断是否开启登录验证(占位实现) +func (c *PlatformAuthController) GetOpenVerify() { + cfg, _ := models.GetPlatformLoginVerify() + openVerify := "0" + if cfg.OpenVerifyEnabled == 1 { + openVerify = "1" + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "ok", + "data": []map[string]string{ + { + "label": "openVerify", + "value": openVerify, + }, + { + "label": "verifyType", + "value": cfg.VerifyType, + }, + }, + } + _ = c.ServeJSON() +} + +// Register 注册(占位实现) +func (c *PlatformAuthController) Register() { + c.Data["json"] = map[string]interface{}{ + "code": 501, + "msg": "注册暂未实现", + } + _ = c.ServeJSON() +} + +// SendRegisterCode 发送注册验证码(占位实现) +func (c *PlatformAuthController) SendRegisterCode() { + c.Data["json"] = map[string]interface{}{ + "code": 501, + "msg": "发送注册验证码暂未实现", + } + _ = c.ServeJSON() +} + +// ResetPassword 忘记密码重置(占位实现) +func (c *PlatformAuthController) ResetPassword() { + c.Data["json"] = map[string]interface{}{ + "code": 501, + "msg": "重置密码暂未实现", + } + _ = c.ServeJSON() +} + +// SendResetCode 发送找回密码验证码(占位实现) +func (c *PlatformAuthController) SendResetCode() { + c.Data["json"] = map[string]interface{}{ + "code": 501, + "msg": "发送找回密码验证码暂未实现", + } + _ = c.ServeJSON() +} + diff --git a/go/controllers/platform_bark.go b/go/controllers/platform_bark.go index bd3231b..b9c36ff 100644 --- a/go/controllers/platform_bark.go +++ b/go/controllers/platform_bark.go @@ -1,215 +1,215 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -type PlatformBarkController struct { - beego.Controller -} - -func (c *PlatformBarkController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformBarkController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// GetBarkInfo GET /platform/bark/info -func (c *PlatformBarkController) GetBarkInfo() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - enabledStr := models.GetPlatformSettingValue("bark_enabled", "0") - serverURL := models.GetPlatformSettingValue("bark_server_url", "https://api.day.app") - deviceKey := models.GetPlatformSettingValue("bark_device_key", "") - - enabled := false - if enabledStr == "1" { - enabled = true - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "enabled": enabled, - "server_url": serverURL, - "device_key": deviceKey, - }, - } - _ = c.ServeJSON() -} - -type barkEditPayload struct { - Enabled bool `json:"enabled"` - ServerUrl string `json:"server_url"` - DeviceKey string `json:"device_key"` -} - -// EditBarkInfo POST /platform/bark/editinfo -func (c *PlatformBarkController) EditBarkInfo() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p barkEditPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - enabledStr := "0" - if p.Enabled { - enabledStr = "1" - } - - serverURL := strings.TrimSpace(p.ServerUrl) - if serverURL == "" { - serverURL = "https://api.day.app" - } - deviceKey := strings.TrimSpace(p.DeviceKey) - - settings := []struct { - code string - name string - value string - remark string - }{ - {"bark_enabled", "Bark推送启用状态", enabledStr, "0为关闭,1为开启"}, - {"bark_server_url", "Bark推送服务器地址", serverURL, ""}, - {"bark_device_key", "Bark设备Key", deviceKey, ""}, - } - - for _, item := range settings { - var setting models.PlatformNormalSetting - err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). - Filter("code", item.code). - Filter("delete_time__isnull", true). - One(&setting) - if err == nil { - setting.Value = item.value - setting.Name = item.name - setting.Remark = item.remark - now := time.Now() - setting.UpdateTime = &now - _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } else { - newSetting := models.PlatformNormalSetting{ - Name: item.name, - Code: item.code, - Value: item.value, - Remark: item.remark, - CreateTime: time.Now(), - } - _, err = models.Orm.Insert(&newSetting) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -type barkTestPayload struct { - ServerUrl string `json:"server_url"` - DeviceKey string `json:"device_key"` -} - -// SendTestBark POST /platform/bark/sendtest -func (c *PlatformBarkController) SendTestBark() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p barkTestPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - serverURL := strings.TrimSpace(p.ServerUrl) - if serverURL == "" { - serverURL = models.GetPlatformSettingValue("bark_server_url", "https://api.day.app") - } - deviceKey := strings.TrimSpace(p.DeviceKey) - if deviceKey == "" { - deviceKey = models.GetPlatformSettingValue("bark_device_key", "") - } - - if deviceKey == "" { - c.jsonErr(400, 400, "设备 Key 不能为空") - return - } - - // 拼接发送 URL,注意去除多余斜杠 - baseURL := strings.TrimRight(serverURL, "/") - // Bark 的格式是: base_url/device_key/title/body - testURL := fmt.Sprintf("%s/%s/测试通知/您配置的 Bark 推送服务已连接成功!", baseURL, deviceKey) - - client := &http.Client{Timeout: 10 * time.Second} - resp, err := client.Get(testURL) - if err != nil { - c.jsonErr(500, 500, "发送失败: "+err.Error()) - return - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := io.ReadAll(resp.Body) - c.jsonErr(500, 500, fmt.Sprintf("发送失败,HTTP 状态码: %d, 返回内容: %s", resp.StatusCode, string(bodyBytes))) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "测试推送已发出,请注意查收"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformBarkController struct { + beego.Controller +} + +func (c *PlatformBarkController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformBarkController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// GetBarkInfo GET /platform/bark/info +func (c *PlatformBarkController) GetBarkInfo() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + enabledStr := models.GetPlatformSettingValue("bark_enabled", "0") + serverURL := models.GetPlatformSettingValue("bark_server_url", "https://api.day.app") + deviceKey := models.GetPlatformSettingValue("bark_device_key", "") + + enabled := false + if enabledStr == "1" { + enabled = true + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "enabled": enabled, + "server_url": serverURL, + "device_key": deviceKey, + }, + } + _ = c.ServeJSON() +} + +type barkEditPayload struct { + Enabled bool `json:"enabled"` + ServerUrl string `json:"server_url"` + DeviceKey string `json:"device_key"` +} + +// EditBarkInfo POST /platform/bark/editinfo +func (c *PlatformBarkController) EditBarkInfo() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p barkEditPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + enabledStr := "0" + if p.Enabled { + enabledStr = "1" + } + + serverURL := strings.TrimSpace(p.ServerUrl) + if serverURL == "" { + serverURL = "https://api.day.app" + } + deviceKey := strings.TrimSpace(p.DeviceKey) + + settings := []struct { + code string + name string + value string + remark string + }{ + {"bark_enabled", "Bark推送启用状态", enabledStr, "0为关闭,1为开启"}, + {"bark_server_url", "Bark推送服务器地址", serverURL, ""}, + {"bark_device_key", "Bark设备Key", deviceKey, ""}, + } + + for _, item := range settings { + var setting models.PlatformNormalSetting + err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } else { + newSetting := models.PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = models.Orm.Insert(&newSetting) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +type barkTestPayload struct { + ServerUrl string `json:"server_url"` + DeviceKey string `json:"device_key"` +} + +// SendTestBark POST /platform/bark/sendtest +func (c *PlatformBarkController) SendTestBark() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p barkTestPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + serverURL := strings.TrimSpace(p.ServerUrl) + if serverURL == "" { + serverURL = models.GetPlatformSettingValue("bark_server_url", "https://api.day.app") + } + deviceKey := strings.TrimSpace(p.DeviceKey) + if deviceKey == "" { + deviceKey = models.GetPlatformSettingValue("bark_device_key", "") + } + + if deviceKey == "" { + c.jsonErr(400, 400, "设备 Key 不能为空") + return + } + + // 拼接发送 URL,注意去除多余斜杠 + baseURL := strings.TrimRight(serverURL, "/") + // Bark 的格式是: base_url/device_key/title/body + testURL := fmt.Sprintf("%s/%s/测试通知/您配置的 Bark 推送服务已连接成功!", baseURL, deviceKey) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Get(testURL) + if err != nil { + c.jsonErr(500, 500, "发送失败: "+err.Error()) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + c.jsonErr(500, 500, fmt.Sprintf("发送失败,HTTP 状态码: %d, 返回内容: %s", resp.StatusCode, string(bodyBytes))) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "测试推送已发出,请注意查收"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_complaint.go b/go/controllers/platform_complaint.go index ba59223..fedac95 100644 --- a/go/controllers/platform_complaint.go +++ b/go/controllers/platform_complaint.go @@ -1,363 +1,363 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -type PlatformComplaintController struct { - beego.Controller -} - -func (c *PlatformComplaintController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformComplaintController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *PlatformComplaintController) ok(data interface{}) { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} - _ = c.ServeJSON() -} - -func categoryNameMap(ids []uint64) map[uint64]string { - m := make(map[uint64]string) - if len(ids) == 0 { - return m - } - seen := make(map[uint64]bool) - var uniq []uint64 - for _, id := range ids { - if id > 0 && !seen[id] { - seen[id] = true - uniq = append(uniq, id) - } - } - if len(uniq) == 0 { - return m - } - var cats []models.ComplaintCategory - _, _ = models.Orm.QueryTable(new(models.ComplaintCategory)). - Filter("id__in", uniq). - Filter("delete_time__isnull", true). - All(&cats) - for _, x := range cats { - m[x.ID] = x.Name - } - return m -} - -// List GET /platform/complaint/list?page=1&pageSize=20&categoryId=&status=&keyword= -func (c *PlatformComplaintController) List() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 200 { - pageSize = 200 - } - var categoryID uint64 - if s := strings.TrimSpace(c.GetString("categoryId")); s != "" { - if v, err := strconv.ParseUint(s, 10, 64); err == nil { - categoryID = v - } - } - statusStr := strings.TrimSpace(c.GetString("status")) - keyword := strings.TrimSpace(c.GetString("keyword")) - - qs := models.Orm.QueryTable(new(models.PlatformComplaint)).Filter("delete_time__isnull", true) - if categoryID > 0 { - qs = qs.Filter("category_id", categoryID) - } - if statusStr != "" { - if st, err := strconv.Atoi(statusStr); err == nil { - qs = qs.Filter("status", st) - } - } - if keyword != "" { - cond := orm.NewCondition(). - Or("title__icontains", keyword). - Or("content__icontains", keyword). - Or("contact_name__icontains", keyword). - Or("contact_phone__icontains", keyword). - Or("contact_email__icontains", keyword) - qs = qs.SetCond(cond) - } - - total, _ := qs.Count() - var rows []models.PlatformComplaint - _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - ids := make([]uint64, 0, len(rows)) - for _, r := range rows { - ids = append(ids, r.CategoryID) - } - names := categoryNameMap(ids) - list := make([]map[string]interface{}, 0, len(rows)) - for _, r := range rows { - list = append(list, map[string]interface{}{ - "id": r.ID, - "categoryId": r.CategoryID, - "categoryName": names[r.CategoryID], - "title": r.Title, - "content": r.Content, - "contactName": r.ContactName, - "contactPhone": r.ContactPhone, - "contactEmail": r.ContactEmail, - "status": r.Status, - "replyContent": r.ReplyContent, - "replyTime": r.ReplyTime, - "tid": r.Tid, - "remark": r.Remark, - "createTime": r.CreateTime, - "updateTime": r.UpdateTime, - }) - } - c.ok(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} - -// Detail GET /platform/complaint/:id -func (c *PlatformComplaintController) Detail() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - var row models.PlatformComplaint - err = models.Orm.QueryTable(new(models.PlatformComplaint)). - Filter("id", id). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.jsonErr(404, 404, "记录不存在") - return - } - names := categoryNameMap([]uint64{row.CategoryID}) - c.ok(map[string]interface{}{ - "id": row.ID, - "categoryId": row.CategoryID, - "categoryName": names[row.CategoryID], - "title": row.Title, - "content": row.Content, - "contactName": row.ContactName, - "contactPhone": row.ContactPhone, - "contactEmail": row.ContactEmail, - "status": row.Status, - "replyContent": row.ReplyContent, - "replyTime": row.ReplyTime, - "tid": row.Tid, - "remark": row.Remark, - "createTime": row.CreateTime, - "updateTime": row.UpdateTime, - }) -} - -type complaintPayload struct { - CategoryID *uint64 `json:"categoryId"` - Title *string `json:"title"` - Content *string `json:"content"` - ContactName *string `json:"contactName"` - ContactPhone *string `json:"contactPhone"` - ContactEmail *string `json:"contactEmail"` - Status *int8 `json:"status"` - ReplyContent *string `json:"replyContent"` - Tid *uint64 `json:"tid"` - Remark *string `json:"remark"` -} - -// Create POST /platform/complaint -func (c *PlatformComplaintController) Create() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - body, _ := io.ReadAll(c.Ctx.Request.Body) - var p complaintPayload - if err := json.Unmarshal(body, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if p.CategoryID == nil || *p.CategoryID == 0 || p.Title == nil || strings.TrimSpace(*p.Title) == "" || - p.Content == nil || strings.TrimSpace(*p.Content) == "" { - c.jsonErr(400, 400, "分类、标题、内容不能为空") - return - } - row := models.PlatformComplaint{ - CategoryID: *p.CategoryID, - Title: strings.TrimSpace(*p.Title), - Content: strings.TrimSpace(*p.Content), - Status: 0, - } - if p.ContactName != nil { - row.ContactName = p.ContactName - } - if p.ContactPhone != nil { - row.ContactPhone = p.ContactPhone - } - if p.ContactEmail != nil { - row.ContactEmail = p.ContactEmail - } - if p.Tid != nil { - row.Tid = p.Tid - } - if p.Status != nil { - row.Status = *p.Status - } - if p.Remark != nil { - row.Remark = p.Remark - } - id, err := models.Orm.Insert(&row) - if err != nil { - c.jsonErr(500, 500, "创建失败: "+err.Error()) - return - } - c.ok(map[string]interface{}{"id": id}) -} - -// Update POST /platform/complaint/:id -func (c *PlatformComplaintController) Update() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - body, _ := io.ReadAll(c.Ctx.Request.Body) - var p complaintPayload - if err := json.Unmarshal(body, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - up := map[string]interface{}{} - if p.CategoryID != nil && *p.CategoryID > 0 { - up["category_id"] = *p.CategoryID - } - if p.Title != nil { - up["title"] = strings.TrimSpace(*p.Title) - } - if p.Content != nil { - up["content"] = strings.TrimSpace(*p.Content) - } - if p.ContactName != nil { - up["contact_name"] = p.ContactName - } - if p.ContactPhone != nil { - up["contact_phone"] = p.ContactPhone - } - if p.ContactEmail != nil { - up["contact_email"] = p.ContactEmail - } - if p.Status != nil { - up["status"] = *p.Status - } - if p.ReplyContent != nil { - s := strings.TrimSpace(*p.ReplyContent) - up["reply_content"] = s - if s != "" { - now := time.Now() - up["reply_time"] = now - } - } - if p.Tid != nil { - up["tid"] = p.Tid - } - if p.Remark != nil { - up["remark"] = p.Remark - } - if len(up) == 0 { - c.jsonErr(400, 400, "无更新字段") - return - } - n, err := models.Orm.QueryTable(new(models.PlatformComplaint)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - c.jsonErr(500, 500, "更新失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "记录不存在") - return - } - c.ok(nil) -} - -// Delete DELETE /platform/complaint/:id -func (c *PlatformComplaintController) Delete() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.PlatformComplaint)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "记录不存在") - return - } - c.ok(nil) -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformComplaintController struct { + beego.Controller +} + +func (c *PlatformComplaintController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformComplaintController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *PlatformComplaintController) ok(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +func categoryNameMap(ids []uint64) map[uint64]string { + m := make(map[uint64]string) + if len(ids) == 0 { + return m + } + seen := make(map[uint64]bool) + var uniq []uint64 + for _, id := range ids { + if id > 0 && !seen[id] { + seen[id] = true + uniq = append(uniq, id) + } + } + if len(uniq) == 0 { + return m + } + var cats []models.ComplaintCategory + _, _ = models.Orm.QueryTable(new(models.ComplaintCategory)). + Filter("id__in", uniq). + Filter("delete_time__isnull", true). + All(&cats) + for _, x := range cats { + m[x.ID] = x.Name + } + return m +} + +// List GET /platform/complaint/list?page=1&pageSize=20&categoryId=&status=&keyword= +func (c *PlatformComplaintController) List() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + var categoryID uint64 + if s := strings.TrimSpace(c.GetString("categoryId")); s != "" { + if v, err := strconv.ParseUint(s, 10, 64); err == nil { + categoryID = v + } + } + statusStr := strings.TrimSpace(c.GetString("status")) + keyword := strings.TrimSpace(c.GetString("keyword")) + + qs := models.Orm.QueryTable(new(models.PlatformComplaint)).Filter("delete_time__isnull", true) + if categoryID > 0 { + qs = qs.Filter("category_id", categoryID) + } + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + qs = qs.Filter("status", st) + } + } + if keyword != "" { + cond := orm.NewCondition(). + Or("title__icontains", keyword). + Or("content__icontains", keyword). + Or("contact_name__icontains", keyword). + Or("contact_phone__icontains", keyword). + Or("contact_email__icontains", keyword) + qs = qs.SetCond(cond) + } + + total, _ := qs.Count() + var rows []models.PlatformComplaint + _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + ids := make([]uint64, 0, len(rows)) + for _, r := range rows { + ids = append(ids, r.CategoryID) + } + names := categoryNameMap(ids) + list := make([]map[string]interface{}, 0, len(rows)) + for _, r := range rows { + list = append(list, map[string]interface{}{ + "id": r.ID, + "categoryId": r.CategoryID, + "categoryName": names[r.CategoryID], + "title": r.Title, + "content": r.Content, + "contactName": r.ContactName, + "contactPhone": r.ContactPhone, + "contactEmail": r.ContactEmail, + "status": r.Status, + "replyContent": r.ReplyContent, + "replyTime": r.ReplyTime, + "tid": r.Tid, + "remark": r.Remark, + "createTime": r.CreateTime, + "updateTime": r.UpdateTime, + }) + } + c.ok(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// Detail GET /platform/complaint/:id +func (c *PlatformComplaintController) Detail() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + var row models.PlatformComplaint + err = models.Orm.QueryTable(new(models.PlatformComplaint)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.jsonErr(404, 404, "记录不存在") + return + } + names := categoryNameMap([]uint64{row.CategoryID}) + c.ok(map[string]interface{}{ + "id": row.ID, + "categoryId": row.CategoryID, + "categoryName": names[row.CategoryID], + "title": row.Title, + "content": row.Content, + "contactName": row.ContactName, + "contactPhone": row.ContactPhone, + "contactEmail": row.ContactEmail, + "status": row.Status, + "replyContent": row.ReplyContent, + "replyTime": row.ReplyTime, + "tid": row.Tid, + "remark": row.Remark, + "createTime": row.CreateTime, + "updateTime": row.UpdateTime, + }) +} + +type complaintPayload struct { + CategoryID *uint64 `json:"categoryId"` + Title *string `json:"title"` + Content *string `json:"content"` + ContactName *string `json:"contactName"` + ContactPhone *string `json:"contactPhone"` + ContactEmail *string `json:"contactEmail"` + Status *int8 `json:"status"` + ReplyContent *string `json:"replyContent"` + Tid *uint64 `json:"tid"` + Remark *string `json:"remark"` +} + +// Create POST /platform/complaint +func (c *PlatformComplaintController) Create() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + body, _ := io.ReadAll(c.Ctx.Request.Body) + var p complaintPayload + if err := json.Unmarshal(body, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if p.CategoryID == nil || *p.CategoryID == 0 || p.Title == nil || strings.TrimSpace(*p.Title) == "" || + p.Content == nil || strings.TrimSpace(*p.Content) == "" { + c.jsonErr(400, 400, "分类、标题、内容不能为空") + return + } + row := models.PlatformComplaint{ + CategoryID: *p.CategoryID, + Title: strings.TrimSpace(*p.Title), + Content: strings.TrimSpace(*p.Content), + Status: 0, + } + if p.ContactName != nil { + row.ContactName = p.ContactName + } + if p.ContactPhone != nil { + row.ContactPhone = p.ContactPhone + } + if p.ContactEmail != nil { + row.ContactEmail = p.ContactEmail + } + if p.Tid != nil { + row.Tid = p.Tid + } + if p.Status != nil { + row.Status = *p.Status + } + if p.Remark != nil { + row.Remark = p.Remark + } + id, err := models.Orm.Insert(&row) + if err != nil { + c.jsonErr(500, 500, "创建失败: "+err.Error()) + return + } + c.ok(map[string]interface{}{"id": id}) +} + +// Update POST /platform/complaint/:id +func (c *PlatformComplaintController) Update() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + body, _ := io.ReadAll(c.Ctx.Request.Body) + var p complaintPayload + if err := json.Unmarshal(body, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + up := map[string]interface{}{} + if p.CategoryID != nil && *p.CategoryID > 0 { + up["category_id"] = *p.CategoryID + } + if p.Title != nil { + up["title"] = strings.TrimSpace(*p.Title) + } + if p.Content != nil { + up["content"] = strings.TrimSpace(*p.Content) + } + if p.ContactName != nil { + up["contact_name"] = p.ContactName + } + if p.ContactPhone != nil { + up["contact_phone"] = p.ContactPhone + } + if p.ContactEmail != nil { + up["contact_email"] = p.ContactEmail + } + if p.Status != nil { + up["status"] = *p.Status + } + if p.ReplyContent != nil { + s := strings.TrimSpace(*p.ReplyContent) + up["reply_content"] = s + if s != "" { + now := time.Now() + up["reply_time"] = now + } + } + if p.Tid != nil { + up["tid"] = p.Tid + } + if p.Remark != nil { + up["remark"] = p.Remark + } + if len(up) == 0 { + c.jsonErr(400, 400, "无更新字段") + return + } + n, err := models.Orm.QueryTable(new(models.PlatformComplaint)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "更新失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "记录不存在") + return + } + c.ok(nil) +} + +// Delete DELETE /platform/complaint/:id +func (c *PlatformComplaintController) Delete() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.PlatformComplaint)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "记录不存在") + return + } + c.ok(nil) +} diff --git a/go/controllers/platform_complaint_category.go b/go/controllers/platform_complaint_category.go index 42cfbe3..6ac00cd 100644 --- a/go/controllers/platform_complaint_category.go +++ b/go/controllers/platform_complaint_category.go @@ -1,203 +1,203 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -type PlatformComplaintCategoryController struct { - beego.Controller -} - -func (c *PlatformComplaintCategoryController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformComplaintCategoryController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *PlatformComplaintCategoryController) ok(data interface{}) { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} - _ = c.ServeJSON() -} - -// List GET /platform/complaintCategory/list -func (c *PlatformComplaintCategoryController) List() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - var rows []models.ComplaintCategory - _, err := models.Orm.QueryTable(new(models.ComplaintCategory)). - Filter("delete_time__isnull", true). - OrderBy("sort", "id"). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - c.ok(rows) -} - -// SelectList GET /platform/complaintCategory/select — 仅启用,供下拉 -func (c *PlatformComplaintCategoryController) SelectList() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - var rows []models.ComplaintCategory - _, err := models.Orm.QueryTable(new(models.ComplaintCategory)). - Filter("delete_time__isnull", true). - Filter("status", 1). - OrderBy("sort", "id"). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - c.ok(rows) -} - -type complaintCategoryPayload struct { - Name *string `json:"name"` - Code *string `json:"code"` - Sort *int `json:"sort"` - Status *int8 `json:"status"` -} - -// Create POST /platform/complaintCategory -func (c *PlatformComplaintCategoryController) Create() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - body, _ := io.ReadAll(c.Ctx.Request.Body) - var p complaintCategoryPayload - if err := json.Unmarshal(body, &p); err != nil || p.Name == nil || strings.TrimSpace(*p.Name) == "" { - c.jsonErr(400, 400, "分类名称不能为空") - return - } - sort := 0 - if p.Sort != nil { - sort = *p.Sort - } - st := int8(1) - if p.Status != nil { - st = *p.Status - } - row := models.ComplaintCategory{ - Name: strings.TrimSpace(*p.Name), - Code: p.Code, - Sort: sort, - Status: st, - } - id, err := models.Orm.Insert(&row) - if err != nil { - c.jsonErr(500, 500, "创建失败: "+err.Error()) - return - } - c.ok(map[string]interface{}{"id": id}) -} - -// Update POST /platform/complaintCategory/:id -func (c *PlatformComplaintCategoryController) Update() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - body, _ := io.ReadAll(c.Ctx.Request.Body) - var p complaintCategoryPayload - if err := json.Unmarshal(body, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - up := map[string]interface{}{} - if p.Name != nil { - up["name"] = strings.TrimSpace(*p.Name) - } - if p.Code != nil { - up["code"] = strings.TrimSpace(*p.Code) - } - if p.Sort != nil { - up["sort"] = *p.Sort - } - if p.Status != nil { - up["status"] = *p.Status - } - if len(up) == 0 { - c.jsonErr(400, 400, "无更新字段") - return - } - n, err := models.Orm.QueryTable(new(models.ComplaintCategory)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - c.jsonErr(500, 500, "更新失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "记录不存在") - return - } - c.ok(nil) -} - -// Delete DELETE /platform/complaintCategory/:id -func (c *PlatformComplaintCategoryController) Delete() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.ComplaintCategory)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "记录不存在") - return - } - c.ok(nil) -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformComplaintCategoryController struct { + beego.Controller +} + +func (c *PlatformComplaintCategoryController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformComplaintCategoryController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *PlatformComplaintCategoryController) ok(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +// List GET /platform/complaintCategory/list +func (c *PlatformComplaintCategoryController) List() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + var rows []models.ComplaintCategory + _, err := models.Orm.QueryTable(new(models.ComplaintCategory)). + Filter("delete_time__isnull", true). + OrderBy("sort", "id"). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + c.ok(rows) +} + +// SelectList GET /platform/complaintCategory/select — 仅启用,供下拉 +func (c *PlatformComplaintCategoryController) SelectList() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + var rows []models.ComplaintCategory + _, err := models.Orm.QueryTable(new(models.ComplaintCategory)). + Filter("delete_time__isnull", true). + Filter("status", 1). + OrderBy("sort", "id"). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + c.ok(rows) +} + +type complaintCategoryPayload struct { + Name *string `json:"name"` + Code *string `json:"code"` + Sort *int `json:"sort"` + Status *int8 `json:"status"` +} + +// Create POST /platform/complaintCategory +func (c *PlatformComplaintCategoryController) Create() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + body, _ := io.ReadAll(c.Ctx.Request.Body) + var p complaintCategoryPayload + if err := json.Unmarshal(body, &p); err != nil || p.Name == nil || strings.TrimSpace(*p.Name) == "" { + c.jsonErr(400, 400, "分类名称不能为空") + return + } + sort := 0 + if p.Sort != nil { + sort = *p.Sort + } + st := int8(1) + if p.Status != nil { + st = *p.Status + } + row := models.ComplaintCategory{ + Name: strings.TrimSpace(*p.Name), + Code: p.Code, + Sort: sort, + Status: st, + } + id, err := models.Orm.Insert(&row) + if err != nil { + c.jsonErr(500, 500, "创建失败: "+err.Error()) + return + } + c.ok(map[string]interface{}{"id": id}) +} + +// Update POST /platform/complaintCategory/:id +func (c *PlatformComplaintCategoryController) Update() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + body, _ := io.ReadAll(c.Ctx.Request.Body) + var p complaintCategoryPayload + if err := json.Unmarshal(body, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + up := map[string]interface{}{} + if p.Name != nil { + up["name"] = strings.TrimSpace(*p.Name) + } + if p.Code != nil { + up["code"] = strings.TrimSpace(*p.Code) + } + if p.Sort != nil { + up["sort"] = *p.Sort + } + if p.Status != nil { + up["status"] = *p.Status + } + if len(up) == 0 { + c.jsonErr(400, 400, "无更新字段") + return + } + n, err := models.Orm.QueryTable(new(models.ComplaintCategory)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "更新失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "记录不存在") + return + } + c.ok(nil) +} + +// Delete DELETE /platform/complaintCategory/:id +func (c *PlatformComplaintCategoryController) Delete() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.ComplaintCategory)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "记录不存在") + return + } + c.ok(nil) +} diff --git a/go/controllers/platform_cursor_activation_code.go b/go/controllers/platform_cursor_activation_code.go index 110f0f6..9168760 100644 --- a/go/controllers/platform_cursor_activation_code.go +++ b/go/controllers/platform_cursor_activation_code.go @@ -1,750 +1,750 @@ -package controllers - -import ( - "crypto/rand" - "encoding/csv" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformCursorActivationCodeController 平台端 Cursor 激活码管理 -type PlatformCursorActivationCodeController struct { - beego.Controller -} - -func (c *PlatformCursorActivationCodeController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformCursorActivationCodeController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *PlatformCursorActivationCodeController) ok(data interface{}) { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} - _ = c.ServeJSON() -} - -func cursorActivationCodeTrimPtr(value *string) *string { - if value == nil { - return nil - } - v := strings.TrimSpace(*value) - if v == "" { - return nil - } - return &v -} - -func cursorActivationCodeTimePtr(value *string) *time.Time { - if value == nil { - return nil - } - v := strings.TrimSpace(*value) - if v == "" { - return nil - } - layouts := []string{ - time.RFC3339, - "2006-01-02 15:04:05", - "2006-01-02 15:04", - "2006-01-02", - } - for _, layout := range layouts { - if t, err := time.ParseInLocation(layout, v, time.Local); err == nil { - return &t - } - } - return nil -} - -func cursorActivationCodeStatusValid(status int8) bool { - return status == 0 || status == 1 || status == 2 || status == 3 -} - -func cursorActivationCodeTypeName(cardType int) string { - switch cardType { - case 1: - return "天卡" - case 7: - return "周卡" - case 30: - return "月卡" - case 90: - return "季卡" - case 365: - return "年卡" - case 0: - return "自定义" - default: - return fmt.Sprintf("%d天", cardType) - } -} - -func (c *PlatformCursorActivationCodeController) rowToMap(row *models.PlatformCursorActivationCode) map[string]interface{} { - bindStatus := 0 - if row.BindAccount != nil || row.BindDeviceID != nil || row.MachineCode != nil { - bindStatus = 1 - } - - return map[string]interface{}{ - "id": row.ID, - "code": row.Code, - "type": row.Type, - "typeName": cursorActivationCodeTypeName(row.Type), - "status": row.Status, - "durationDays": row.DurationDays, - "bindAccount": row.BindAccount, - "bindDeviceId": row.BindDeviceID, - "bindStatus": bindStatus, - "machineCode": row.MachineCode, - "deviceInfo": row.DeviceInfo, - "ownerUserId": row.OwnerUserID, - "ownerUserName": row.OwnerUserName, - "activatedAt": row.ActivatedAt, - "expiredAt": row.ExpiredAt, - "createdAt": row.CreateTime, - "updatedAt": row.UpdateTime, - "createTime": row.CreateTime, - "updateTime": row.UpdateTime, - "remark": row.Remark, - } -} - -func (c *PlatformCursorActivationCodeController) filteredQuery() orm.QuerySeter { - keyword := strings.TrimSpace(c.GetString("keyword")) - statusText := strings.TrimSpace(c.GetString("status")) - typeText := strings.TrimSpace(c.GetString("type")) - bindStatusText := strings.TrimSpace(c.GetString("bindStatus")) - - qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).Filter("delete_time__isnull", true) - - if keyword != "" { - cond := orm.NewCondition(). - Or("code__icontains", keyword). - Or("bind_account__icontains", keyword). - Or("machine_code__icontains", keyword). - Or("device_info__icontains", keyword). - Or("owner_user_name__icontains", keyword). - Or("remark__icontains", keyword) - qs = qs.SetCond(cond) - qs = qs.Filter("delete_time__isnull", true) - } - - if statusText != "" { - status, err := strconv.ParseInt(statusText, 10, 8) - if err == nil && cursorActivationCodeStatusValid(int8(status)) { - qs = qs.Filter("status", int8(status)) - } - } - - if typeText != "" { - cardType, err := strconv.Atoi(typeText) - if err == nil { - qs = qs.Filter("type", cardType) - } - } - - if bindStatusText != "" { - bindStatus, err := strconv.Atoi(bindStatusText) - if err == nil { - if bindStatus == 0 { - qs = qs.Filter("bind_account__isnull", true).Filter("bind_device_id__isnull", true).Filter("machine_code__isnull", true) - } else if bindStatus == 1 { - cond := orm.NewCondition(). - Or("bind_account__isnull", false). - Or("bind_device_id__isnull", false). - Or("machine_code__isnull", false) - qs = qs.SetCond(cond) - qs = qs.Filter("delete_time__isnull", true) - if statusText != "" { - status, err := strconv.ParseInt(statusText, 10, 8) - if err == nil && cursorActivationCodeStatusValid(int8(status)) { - qs = qs.Filter("status", int8(status)) - } - } - if typeText != "" { - cardType, err := strconv.Atoi(typeText) - if err == nil { - qs = qs.Filter("type", cardType) - } - } - } - } - } - - return qs -} - -// List GET /platform/cursor/activationcode/list -func (c *PlatformCursorActivationCodeController) List() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 200 { - pageSize = 200 - } - - qs := c.filteredQuery() - total, _ := qs.Count() - - var rows []models.PlatformCursorActivationCode - _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取激活码列表失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - list = append(list, c.rowToMap(&rows[i])) - } - - c.ok(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} - -// Detail GET /platform/cursor/activationcode/detail/:id -func (c *PlatformCursorActivationCodeController) Detail() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - - var row models.PlatformCursorActivationCode - err = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - Filter("id", id). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.jsonErr(404, 404, "激活码不存在") - return - } - - c.ok(c.rowToMap(&row)) -} - -type platformCursorActivationCodePayload struct { - ID *uint64 `json:"id"` - Code *string `json:"code"` - Type *int `json:"type"` - Status *int8 `json:"status"` - DurationDays *int `json:"durationDays"` - BindAccount *string `json:"bindAccount"` - BindDeviceID *uint64 `json:"bindDeviceId"` - OwnerUserID *uint64 `json:"ownerUserId"` - OwnerUserName *string `json:"ownerUserName"` - ActivatedAt *string `json:"activatedAt"` - ExpiredAt *string `json:"expiredAt"` - Remark *string `json:"remark"` -} - -func (c *PlatformCursorActivationCodeController) readPayload() (*platformCursorActivationCodePayload, error) { - body, _ := io.ReadAll(c.Ctx.Request.Body) - var p platformCursorActivationCodePayload - if err := json.Unmarshal(body, &p); err != nil { - return nil, err - } - return &p, nil -} - -func (c *PlatformCursorActivationCodeController) fillDeviceSnapshot(up map[string]interface{}, bindDeviceID *uint64) { - if bindDeviceID == nil || *bindDeviceID == 0 { - up["bind_device_id"] = nil - up["machine_code"] = nil - up["device_info"] = nil - return - } - - var device models.PlatformCursorEquipment - err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", *bindDeviceID). - Filter("delete_time__isnull", true). - One(&device) - if err == nil { - up["bind_device_id"] = *bindDeviceID - up["machine_code"] = device.MachineCode - up["device_info"] = device.DeviceInfo - return - } - - up["bind_device_id"] = *bindDeviceID -} - -func (c *PlatformCursorActivationCodeController) payloadToUpdateMap(p *platformCursorActivationCodePayload, includeCode bool) (map[string]interface{}, error) { - up := map[string]interface{}{} - - if includeCode { - if p.Code == nil || strings.TrimSpace(*p.Code) == "" { - return nil, fmt.Errorf("激活码不能为空") - } - up["code"] = strings.TrimSpace(*p.Code) - } else if p.Code != nil { - if strings.TrimSpace(*p.Code) == "" { - return nil, fmt.Errorf("激活码不能为空") - } - up["code"] = strings.TrimSpace(*p.Code) - } - - if p.Type != nil { - if *p.Type < 0 { - return nil, fmt.Errorf("卡密类型不合法") - } - up["type"] = *p.Type - } - if p.Status != nil { - if !cursorActivationCodeStatusValid(*p.Status) { - return nil, fmt.Errorf("状态不合法,支持:0 未使用、1 已使用、2 已过期、3 已禁用") - } - up["status"] = *p.Status - } - if p.DurationDays != nil { - if *p.DurationDays < 0 || *p.DurationDays > 9999 { - return nil, fmt.Errorf("有效天数范围为 0-9999") - } - up["duration_days"] = *p.DurationDays - } - if p.BindAccount != nil { - up["bind_account"] = cursorActivationCodeTrimPtr(p.BindAccount) - } - if p.BindDeviceID != nil { - c.fillDeviceSnapshot(up, p.BindDeviceID) - } - if p.OwnerUserID != nil { - if *p.OwnerUserID == 0 { - up["owner_user_id"] = nil - } else { - up["owner_user_id"] = *p.OwnerUserID - } - } - if p.OwnerUserName != nil { - up["owner_user_name"] = cursorActivationCodeTrimPtr(p.OwnerUserName) - } - if p.ActivatedAt != nil { - up["activated_at"] = cursorActivationCodeTimePtr(p.ActivatedAt) - } - if p.ExpiredAt != nil { - up["expired_at"] = cursorActivationCodeTimePtr(p.ExpiredAt) - } - if p.Remark != nil { - up["remark"] = cursorActivationCodeTrimPtr(p.Remark) - } - - return up, nil -} - -// Add POST /platform/cursor/activationcode/add -func (c *PlatformCursorActivationCodeController) Add() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - p, err := c.readPayload() - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - up, err := c.payloadToUpdateMap(p, true) - if err != nil { - c.jsonErr(400, 400, err.Error()) - return - } - - row := models.PlatformCursorActivationCode{ - Code: up["code"].(string), - Type: 30, - Status: 0, - DurationDays: 30, - BindAccount: cursorActivationCodeTrimPtr(p.BindAccount), - BindDeviceID: p.BindDeviceID, - OwnerUserID: p.OwnerUserID, - OwnerUserName: cursorActivationCodeTrimPtr(p.OwnerUserName), - ActivatedAt: cursorActivationCodeTimePtr(p.ActivatedAt), - ExpiredAt: cursorActivationCodeTimePtr(p.ExpiredAt), - Remark: cursorActivationCodeTrimPtr(p.Remark), - CreateTime: time.Now(), - } - - if p.Type != nil { - row.Type = *p.Type - } - if p.Status != nil { - row.Status = *p.Status - } - if p.DurationDays != nil { - row.DurationDays = *p.DurationDays - } - if row.BindDeviceID != nil && *row.BindDeviceID == 0 { - row.BindDeviceID = nil - } - if row.OwnerUserID != nil && *row.OwnerUserID == 0 { - row.OwnerUserID = nil - } - if row.BindDeviceID != nil { - var device models.PlatformCursorEquipment - if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", *row.BindDeviceID). - Filter("delete_time__isnull", true). - One(&device); err == nil { - row.MachineCode = &device.MachineCode - row.DeviceInfo = device.DeviceInfo - } - } - - id, err := models.Orm.Insert(&row) - if err != nil { - if strings.Contains(strings.ToLower(err.Error()), "duplicate") { - c.jsonErr(400, 400, "激活码已存在") - return - } - c.jsonErr(500, 500, "新增激活码失败: "+err.Error()) - return - } - - c.ok(map[string]interface{}{"id": id}) -} - -// Update POST /platform/cursor/activationcode/update -func (c *PlatformCursorActivationCodeController) Update() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - p, err := c.readPayload() - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if p.ID == nil || *p.ID == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - - up, err := c.payloadToUpdateMap(p, false) - if err != nil { - c.jsonErr(400, 400, err.Error()) - return - } - if len(up) == 0 { - c.jsonErr(400, 400, "无更新字段") - return - } - - now := time.Now() - up["update_time"] = now - - n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - Filter("id", *p.ID). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - if strings.Contains(strings.ToLower(err.Error()), "duplicate") { - c.jsonErr(400, 400, "激活码已存在") - return - } - c.jsonErr(500, 500, "更新激活码失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "激活码不存在") - return - } - - c.ok(nil) -} - -// Delete POST /platform/cursor/activationcode/delete/:id -func (c *PlatformCursorActivationCodeController) Delete() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除激活码失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "激活码不存在") - return - } - - c.ok(nil) -} - -type platformCursorActivationCodeGeneratePayload struct { - Count int `json:"count"` - Type int `json:"type"` - DurationDays int `json:"durationDays"` - OwnerUserID *uint64 `json:"ownerUserId"` - OwnerUserName *string `json:"ownerUserName"` - Remark *string `json:"remark"` -} - -func randomCursorActivationCode() (string, error) { - b := make([]byte, 12) - if _, err := rand.Read(b); err != nil { - return "", err - } - return "CUR-" + strings.ToUpper(hex.EncodeToString(b)), nil -} - -// Generate POST /platform/cursor/activationcode/generate -func (c *PlatformCursorActivationCodeController) Generate() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - body, _ := io.ReadAll(c.Ctx.Request.Body) - var p platformCursorActivationCodeGeneratePayload - if err := json.Unmarshal(body, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - if p.Count < 1 { - p.Count = 1 - } - if p.Count > 10000 { - c.jsonErr(400, 400, "单次最多生成 10000 个激活码") - return - } - if p.Type < 0 { - c.jsonErr(400, 400, "卡密类型不合法") - return - } - if p.DurationDays < 0 || p.DurationDays > 9999 { - c.jsonErr(400, 400, "有效天数范围为 0-9999") - return - } - if p.Type == 0 && p.DurationDays == 0 { - p.DurationDays = 30 - } - if p.Type > 0 && p.DurationDays == 0 { - p.DurationDays = p.Type - } - - createdIDs := make([]int64, 0, p.Count) - codes := make([]string, 0, p.Count) - now := time.Now() - - for len(createdIDs) < p.Count { - code, err := randomCursorActivationCode() - if err != nil { - c.jsonErr(500, 500, "生成激活码失败: "+err.Error()) - return - } - - row := models.PlatformCursorActivationCode{ - Code: code, - Type: p.Type, - Status: 0, - DurationDays: p.DurationDays, - OwnerUserID: p.OwnerUserID, - OwnerUserName: cursorActivationCodeTrimPtr(p.OwnerUserName), - Remark: cursorActivationCodeTrimPtr(p.Remark), - CreateTime: now, - } - if row.OwnerUserID != nil && *row.OwnerUserID == 0 { - row.OwnerUserID = nil - } - - id, err := models.Orm.Insert(&row) - if err != nil { - if strings.Contains(strings.ToLower(err.Error()), "duplicate") { - continue - } - c.jsonErr(500, 500, "生成激活码失败: "+err.Error()) - return - } - - createdIDs = append(createdIDs, id) - codes = append(codes, code) - } - - c.ok(map[string]interface{}{ - "count": len(createdIDs), - "ids": createdIDs, - "codes": codes, - }) -} - -// Enable POST /platform/cursor/activationcode/enable/:id -func (c *PlatformCursorActivationCodeController) Enable() { - c.changeStatus(0, "启用激活码失败") -} - -// Disable POST /platform/cursor/activationcode/disable/:id -func (c *PlatformCursorActivationCodeController) Disable() { - c.changeStatus(3, "禁用激活码失败") -} - -func (c *PlatformCursorActivationCodeController) changeStatus(status int8, failMsg string) { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{ - "status": status, - "update_time": now, - }) - if err != nil { - c.jsonErr(500, 500, failMsg+": "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "激活码不存在") - return - } - - c.ok(nil) -} - -// Export GET /platform/cursor/activationcode/export -func (c *PlatformCursorActivationCodeController) Export() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - var rows []models.PlatformCursorActivationCode - _, err := c.filteredQuery().OrderBy("-id").Limit(50000).All(&rows) - if err != nil { - c.jsonErr(500, 500, "导出激活码失败: "+err.Error()) - return - } - - filename := fmt.Sprintf("cursor-activation-code-%s.csv", time.Now().Format("20060102150405")) - c.Ctx.Output.Header("Content-Type", "text/csv; charset=utf-8") - c.Ctx.Output.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) - - _, _ = c.Ctx.ResponseWriter.Write([]byte{0xEF, 0xBB, 0xBF}) - writer := csv.NewWriter(c.Ctx.ResponseWriter) - _ = writer.Write([]string{ - "ID", "激活码", "类型", "有效天数", "状态", "绑定账号", "绑定设备ID", "机器码", "归属用户ID", "归属用户", "激活时间", "过期时间", "创建时间", "备注", - }) - - statusText := map[int8]string{ - 0: "未使用", - 1: "已使用", - 2: "已过期", - 3: "已禁用", - } - - for i := range rows { - row := rows[i] - _ = writer.Write([]string{ - strconv.FormatUint(row.ID, 10), - row.Code, - cursorActivationCodeTypeName(row.Type), - strconv.Itoa(row.DurationDays), - statusText[row.Status], - stringPtrValue(row.BindAccount), - uint64PtrValue(row.BindDeviceID), - stringPtrValue(row.MachineCode), - uint64PtrValue(row.OwnerUserID), - stringPtrValue(row.OwnerUserName), - timePtrValue(row.ActivatedAt), - timePtrValue(row.ExpiredAt), - row.CreateTime.Format("2006-01-02 15:04:05"), - stringPtrValue(row.Remark), - }) - } - - writer.Flush() -} - -func stringPtrValue(value *string) string { - if value == nil { - return "" - } - return *value -} - -func uint64PtrValue(value *uint64) string { - if value == nil { - return "" - } - return strconv.FormatUint(*value, 10) -} - -func timePtrValue(value *time.Time) string { - if value == nil { - return "" - } - return value.Format("2006-01-02 15:04:05") -} +package controllers + +import ( + "crypto/rand" + "encoding/csv" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformCursorActivationCodeController 平台端 Cursor 激活码管理 +type PlatformCursorActivationCodeController struct { + beego.Controller +} + +func (c *PlatformCursorActivationCodeController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformCursorActivationCodeController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *PlatformCursorActivationCodeController) ok(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +func cursorActivationCodeTrimPtr(value *string) *string { + if value == nil { + return nil + } + v := strings.TrimSpace(*value) + if v == "" { + return nil + } + return &v +} + +func cursorActivationCodeTimePtr(value *string) *time.Time { + if value == nil { + return nil + } + v := strings.TrimSpace(*value) + if v == "" { + return nil + } + layouts := []string{ + time.RFC3339, + "2006-01-02 15:04:05", + "2006-01-02 15:04", + "2006-01-02", + } + for _, layout := range layouts { + if t, err := time.ParseInLocation(layout, v, time.Local); err == nil { + return &t + } + } + return nil +} + +func cursorActivationCodeStatusValid(status int8) bool { + return status == 0 || status == 1 || status == 2 || status == 3 +} + +func cursorActivationCodeTypeName(cardType int) string { + switch cardType { + case 1: + return "天卡" + case 7: + return "周卡" + case 30: + return "月卡" + case 90: + return "季卡" + case 365: + return "年卡" + case 0: + return "自定义" + default: + return fmt.Sprintf("%d天", cardType) + } +} + +func (c *PlatformCursorActivationCodeController) rowToMap(row *models.PlatformCursorActivationCode) map[string]interface{} { + bindStatus := 0 + if row.BindAccount != nil || row.BindDeviceID != nil || row.MachineCode != nil { + bindStatus = 1 + } + + return map[string]interface{}{ + "id": row.ID, + "code": row.Code, + "type": row.Type, + "typeName": cursorActivationCodeTypeName(row.Type), + "status": row.Status, + "durationDays": row.DurationDays, + "bindAccount": row.BindAccount, + "bindDeviceId": row.BindDeviceID, + "bindStatus": bindStatus, + "machineCode": row.MachineCode, + "deviceInfo": row.DeviceInfo, + "ownerUserId": row.OwnerUserID, + "ownerUserName": row.OwnerUserName, + "activatedAt": row.ActivatedAt, + "expiredAt": row.ExpiredAt, + "createdAt": row.CreateTime, + "updatedAt": row.UpdateTime, + "createTime": row.CreateTime, + "updateTime": row.UpdateTime, + "remark": row.Remark, + } +} + +func (c *PlatformCursorActivationCodeController) filteredQuery() orm.QuerySeter { + keyword := strings.TrimSpace(c.GetString("keyword")) + statusText := strings.TrimSpace(c.GetString("status")) + typeText := strings.TrimSpace(c.GetString("type")) + bindStatusText := strings.TrimSpace(c.GetString("bindStatus")) + + qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).Filter("delete_time__isnull", true) + + if keyword != "" { + cond := orm.NewCondition(). + Or("code__icontains", keyword). + Or("bind_account__icontains", keyword). + Or("machine_code__icontains", keyword). + Or("device_info__icontains", keyword). + Or("owner_user_name__icontains", keyword). + Or("remark__icontains", keyword) + qs = qs.SetCond(cond) + qs = qs.Filter("delete_time__isnull", true) + } + + if statusText != "" { + status, err := strconv.ParseInt(statusText, 10, 8) + if err == nil && cursorActivationCodeStatusValid(int8(status)) { + qs = qs.Filter("status", int8(status)) + } + } + + if typeText != "" { + cardType, err := strconv.Atoi(typeText) + if err == nil { + qs = qs.Filter("type", cardType) + } + } + + if bindStatusText != "" { + bindStatus, err := strconv.Atoi(bindStatusText) + if err == nil { + if bindStatus == 0 { + qs = qs.Filter("bind_account__isnull", true).Filter("bind_device_id__isnull", true).Filter("machine_code__isnull", true) + } else if bindStatus == 1 { + cond := orm.NewCondition(). + Or("bind_account__isnull", false). + Or("bind_device_id__isnull", false). + Or("machine_code__isnull", false) + qs = qs.SetCond(cond) + qs = qs.Filter("delete_time__isnull", true) + if statusText != "" { + status, err := strconv.ParseInt(statusText, 10, 8) + if err == nil && cursorActivationCodeStatusValid(int8(status)) { + qs = qs.Filter("status", int8(status)) + } + } + if typeText != "" { + cardType, err := strconv.Atoi(typeText) + if err == nil { + qs = qs.Filter("type", cardType) + } + } + } + } + } + + return qs +} + +// List GET /platform/cursor/activationcode/list +func (c *PlatformCursorActivationCodeController) List() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + + qs := c.filteredQuery() + total, _ := qs.Count() + + var rows []models.PlatformCursorActivationCode + _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取激活码列表失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, c.rowToMap(&rows[i])) + } + + c.ok(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// Detail GET /platform/cursor/activationcode/detail/:id +func (c *PlatformCursorActivationCodeController) Detail() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + + var row models.PlatformCursorActivationCode + err = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.jsonErr(404, 404, "激活码不存在") + return + } + + c.ok(c.rowToMap(&row)) +} + +type platformCursorActivationCodePayload struct { + ID *uint64 `json:"id"` + Code *string `json:"code"` + Type *int `json:"type"` + Status *int8 `json:"status"` + DurationDays *int `json:"durationDays"` + BindAccount *string `json:"bindAccount"` + BindDeviceID *uint64 `json:"bindDeviceId"` + OwnerUserID *uint64 `json:"ownerUserId"` + OwnerUserName *string `json:"ownerUserName"` + ActivatedAt *string `json:"activatedAt"` + ExpiredAt *string `json:"expiredAt"` + Remark *string `json:"remark"` +} + +func (c *PlatformCursorActivationCodeController) readPayload() (*platformCursorActivationCodePayload, error) { + body, _ := io.ReadAll(c.Ctx.Request.Body) + var p platformCursorActivationCodePayload + if err := json.Unmarshal(body, &p); err != nil { + return nil, err + } + return &p, nil +} + +func (c *PlatformCursorActivationCodeController) fillDeviceSnapshot(up map[string]interface{}, bindDeviceID *uint64) { + if bindDeviceID == nil || *bindDeviceID == 0 { + up["bind_device_id"] = nil + up["machine_code"] = nil + up["device_info"] = nil + return + } + + var device models.PlatformCursorEquipment + err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", *bindDeviceID). + Filter("delete_time__isnull", true). + One(&device) + if err == nil { + up["bind_device_id"] = *bindDeviceID + up["machine_code"] = device.MachineCode + up["device_info"] = device.DeviceInfo + return + } + + up["bind_device_id"] = *bindDeviceID +} + +func (c *PlatformCursorActivationCodeController) payloadToUpdateMap(p *platformCursorActivationCodePayload, includeCode bool) (map[string]interface{}, error) { + up := map[string]interface{}{} + + if includeCode { + if p.Code == nil || strings.TrimSpace(*p.Code) == "" { + return nil, fmt.Errorf("激活码不能为空") + } + up["code"] = strings.TrimSpace(*p.Code) + } else if p.Code != nil { + if strings.TrimSpace(*p.Code) == "" { + return nil, fmt.Errorf("激活码不能为空") + } + up["code"] = strings.TrimSpace(*p.Code) + } + + if p.Type != nil { + if *p.Type < 0 { + return nil, fmt.Errorf("卡密类型不合法") + } + up["type"] = *p.Type + } + if p.Status != nil { + if !cursorActivationCodeStatusValid(*p.Status) { + return nil, fmt.Errorf("状态不合法,支持:0 未使用、1 已使用、2 已过期、3 已禁用") + } + up["status"] = *p.Status + } + if p.DurationDays != nil { + if *p.DurationDays < 0 || *p.DurationDays > 9999 { + return nil, fmt.Errorf("有效天数范围为 0-9999") + } + up["duration_days"] = *p.DurationDays + } + if p.BindAccount != nil { + up["bind_account"] = cursorActivationCodeTrimPtr(p.BindAccount) + } + if p.BindDeviceID != nil { + c.fillDeviceSnapshot(up, p.BindDeviceID) + } + if p.OwnerUserID != nil { + if *p.OwnerUserID == 0 { + up["owner_user_id"] = nil + } else { + up["owner_user_id"] = *p.OwnerUserID + } + } + if p.OwnerUserName != nil { + up["owner_user_name"] = cursorActivationCodeTrimPtr(p.OwnerUserName) + } + if p.ActivatedAt != nil { + up["activated_at"] = cursorActivationCodeTimePtr(p.ActivatedAt) + } + if p.ExpiredAt != nil { + up["expired_at"] = cursorActivationCodeTimePtr(p.ExpiredAt) + } + if p.Remark != nil { + up["remark"] = cursorActivationCodeTrimPtr(p.Remark) + } + + return up, nil +} + +// Add POST /platform/cursor/activationcode/add +func (c *PlatformCursorActivationCodeController) Add() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + p, err := c.readPayload() + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + up, err := c.payloadToUpdateMap(p, true) + if err != nil { + c.jsonErr(400, 400, err.Error()) + return + } + + row := models.PlatformCursorActivationCode{ + Code: up["code"].(string), + Type: 30, + Status: 0, + DurationDays: 30, + BindAccount: cursorActivationCodeTrimPtr(p.BindAccount), + BindDeviceID: p.BindDeviceID, + OwnerUserID: p.OwnerUserID, + OwnerUserName: cursorActivationCodeTrimPtr(p.OwnerUserName), + ActivatedAt: cursorActivationCodeTimePtr(p.ActivatedAt), + ExpiredAt: cursorActivationCodeTimePtr(p.ExpiredAt), + Remark: cursorActivationCodeTrimPtr(p.Remark), + CreateTime: time.Now(), + } + + if p.Type != nil { + row.Type = *p.Type + } + if p.Status != nil { + row.Status = *p.Status + } + if p.DurationDays != nil { + row.DurationDays = *p.DurationDays + } + if row.BindDeviceID != nil && *row.BindDeviceID == 0 { + row.BindDeviceID = nil + } + if row.OwnerUserID != nil && *row.OwnerUserID == 0 { + row.OwnerUserID = nil + } + if row.BindDeviceID != nil { + var device models.PlatformCursorEquipment + if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", *row.BindDeviceID). + Filter("delete_time__isnull", true). + One(&device); err == nil { + row.MachineCode = &device.MachineCode + row.DeviceInfo = device.DeviceInfo + } + } + + id, err := models.Orm.Insert(&row) + if err != nil { + if strings.Contains(strings.ToLower(err.Error()), "duplicate") { + c.jsonErr(400, 400, "激活码已存在") + return + } + c.jsonErr(500, 500, "新增激活码失败: "+err.Error()) + return + } + + c.ok(map[string]interface{}{"id": id}) +} + +// Update POST /platform/cursor/activationcode/update +func (c *PlatformCursorActivationCodeController) Update() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + p, err := c.readPayload() + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if p.ID == nil || *p.ID == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + + up, err := c.payloadToUpdateMap(p, false) + if err != nil { + c.jsonErr(400, 400, err.Error()) + return + } + if len(up) == 0 { + c.jsonErr(400, 400, "无更新字段") + return + } + + now := time.Now() + up["update_time"] = now + + n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + Filter("id", *p.ID). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + if strings.Contains(strings.ToLower(err.Error()), "duplicate") { + c.jsonErr(400, 400, "激活码已存在") + return + } + c.jsonErr(500, 500, "更新激活码失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "激活码不存在") + return + } + + c.ok(nil) +} + +// Delete POST /platform/cursor/activationcode/delete/:id +func (c *PlatformCursorActivationCodeController) Delete() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除激活码失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "激活码不存在") + return + } + + c.ok(nil) +} + +type platformCursorActivationCodeGeneratePayload struct { + Count int `json:"count"` + Type int `json:"type"` + DurationDays int `json:"durationDays"` + OwnerUserID *uint64 `json:"ownerUserId"` + OwnerUserName *string `json:"ownerUserName"` + Remark *string `json:"remark"` +} + +func randomCursorActivationCode() (string, error) { + b := make([]byte, 12) + if _, err := rand.Read(b); err != nil { + return "", err + } + return "CUR-" + strings.ToUpper(hex.EncodeToString(b)), nil +} + +// Generate POST /platform/cursor/activationcode/generate +func (c *PlatformCursorActivationCodeController) Generate() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + body, _ := io.ReadAll(c.Ctx.Request.Body) + var p platformCursorActivationCodeGeneratePayload + if err := json.Unmarshal(body, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + if p.Count < 1 { + p.Count = 1 + } + if p.Count > 10000 { + c.jsonErr(400, 400, "单次最多生成 10000 个激活码") + return + } + if p.Type < 0 { + c.jsonErr(400, 400, "卡密类型不合法") + return + } + if p.DurationDays < 0 || p.DurationDays > 9999 { + c.jsonErr(400, 400, "有效天数范围为 0-9999") + return + } + if p.Type == 0 && p.DurationDays == 0 { + p.DurationDays = 30 + } + if p.Type > 0 && p.DurationDays == 0 { + p.DurationDays = p.Type + } + + createdIDs := make([]int64, 0, p.Count) + codes := make([]string, 0, p.Count) + now := time.Now() + + for len(createdIDs) < p.Count { + code, err := randomCursorActivationCode() + if err != nil { + c.jsonErr(500, 500, "生成激活码失败: "+err.Error()) + return + } + + row := models.PlatformCursorActivationCode{ + Code: code, + Type: p.Type, + Status: 0, + DurationDays: p.DurationDays, + OwnerUserID: p.OwnerUserID, + OwnerUserName: cursorActivationCodeTrimPtr(p.OwnerUserName), + Remark: cursorActivationCodeTrimPtr(p.Remark), + CreateTime: now, + } + if row.OwnerUserID != nil && *row.OwnerUserID == 0 { + row.OwnerUserID = nil + } + + id, err := models.Orm.Insert(&row) + if err != nil { + if strings.Contains(strings.ToLower(err.Error()), "duplicate") { + continue + } + c.jsonErr(500, 500, "生成激活码失败: "+err.Error()) + return + } + + createdIDs = append(createdIDs, id) + codes = append(codes, code) + } + + c.ok(map[string]interface{}{ + "count": len(createdIDs), + "ids": createdIDs, + "codes": codes, + }) +} + +// Enable POST /platform/cursor/activationcode/enable/:id +func (c *PlatformCursorActivationCodeController) Enable() { + c.changeStatus(0, "启用激活码失败") +} + +// Disable POST /platform/cursor/activationcode/disable/:id +func (c *PlatformCursorActivationCodeController) Disable() { + c.changeStatus(3, "禁用激活码失败") +} + +func (c *PlatformCursorActivationCodeController) changeStatus(status int8, failMsg string) { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{ + "status": status, + "update_time": now, + }) + if err != nil { + c.jsonErr(500, 500, failMsg+": "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "激活码不存在") + return + } + + c.ok(nil) +} + +// Export GET /platform/cursor/activationcode/export +func (c *PlatformCursorActivationCodeController) Export() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + var rows []models.PlatformCursorActivationCode + _, err := c.filteredQuery().OrderBy("-id").Limit(50000).All(&rows) + if err != nil { + c.jsonErr(500, 500, "导出激活码失败: "+err.Error()) + return + } + + filename := fmt.Sprintf("cursor-activation-code-%s.csv", time.Now().Format("20060102150405")) + c.Ctx.Output.Header("Content-Type", "text/csv; charset=utf-8") + c.Ctx.Output.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) + + _, _ = c.Ctx.ResponseWriter.Write([]byte{0xEF, 0xBB, 0xBF}) + writer := csv.NewWriter(c.Ctx.ResponseWriter) + _ = writer.Write([]string{ + "ID", "激活码", "类型", "有效天数", "状态", "绑定账号", "绑定设备ID", "机器码", "归属用户ID", "归属用户", "激活时间", "过期时间", "创建时间", "备注", + }) + + statusText := map[int8]string{ + 0: "未使用", + 1: "已使用", + 2: "已过期", + 3: "已禁用", + } + + for i := range rows { + row := rows[i] + _ = writer.Write([]string{ + strconv.FormatUint(row.ID, 10), + row.Code, + cursorActivationCodeTypeName(row.Type), + strconv.Itoa(row.DurationDays), + statusText[row.Status], + stringPtrValue(row.BindAccount), + uint64PtrValue(row.BindDeviceID), + stringPtrValue(row.MachineCode), + uint64PtrValue(row.OwnerUserID), + stringPtrValue(row.OwnerUserName), + timePtrValue(row.ActivatedAt), + timePtrValue(row.ExpiredAt), + row.CreateTime.Format("2006-01-02 15:04:05"), + stringPtrValue(row.Remark), + }) + } + + writer.Flush() +} + +func stringPtrValue(value *string) string { + if value == nil { + return "" + } + return *value +} + +func uint64PtrValue(value *uint64) string { + if value == nil { + return "" + } + return strconv.FormatUint(*value, 10) +} + +func timePtrValue(value *time.Time) string { + if value == nil { + return "" + } + return value.Format("2006-01-02 15:04:05") +} diff --git a/go/controllers/platform_cursor_equipment.go b/go/controllers/platform_cursor_equipment.go index ddc9e29..88bbaac 100644 --- a/go/controllers/platform_cursor_equipment.go +++ b/go/controllers/platform_cursor_equipment.go @@ -1,1043 +1,1043 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformCursorEquipmentController 平台端 Cursor 设备管理 -type PlatformCursorEquipmentController struct { - beego.Controller -} - -func (c *PlatformCursorEquipmentController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformCursorEquipmentController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *PlatformCursorEquipmentController) ok(data interface{}) { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} - _ = c.ServeJSON() -} - -func cursorEquipmentTrimPtr(value *string) *string { - if value == nil { - return nil - } - v := strings.TrimSpace(*value) - if v == "" { - return nil - } - return &v -} - -func cursorEquipmentTimePtr(value *string) *time.Time { - if value == nil { - return nil - } - v := strings.TrimSpace(*value) - if v == "" { - return nil - } - layouts := []string{ - time.RFC3339, - "2006-01-02 15:04:05", - "2006-01-02 15:04", - "2006-01-02", - } - for _, layout := range layouts { - if t, err := time.ParseInLocation(layout, v, time.Local); err == nil { - return &t - } - } - return nil -} - -func cursorEquipmentStatusValid(status int8) bool { - return status == 0 || status == 1 || status == 2 || status == 3 -} - -func (c *PlatformCursorEquipmentController) cursorActivationSummary(row *models.PlatformCursorEquipment) (int64, *models.PlatformCursorActivationCode) { - cond := orm.NewCondition(). - And("delete_time__isnull", true). - AndCond(orm.NewCondition(). - Or("bind_device_id", row.ID). - Or("machine_code", row.MachineCode)) - - qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).SetCond(cond) - count, _ := qs.Count() - - var latest models.PlatformCursorActivationCode - if err := qs.OrderBy("-activated_at", "-id").One(&latest); err != nil { - return count, nil - } - - return count, &latest -} - -func (c *PlatformCursorEquipmentController) cursorExtractSummary(machineCode string) (int64, *models.PlatformAccountPoolCursor) { - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - Filter("delete_time__isnull", true). - Filter("is_extracted__gt", 0). - Filter("machine_code", machineCode) - - count, _ := qs.Count() - - var latest models.PlatformAccountPoolCursor - if err := qs.OrderBy("-extracted_time", "-id").One(&latest); err != nil { - return count, nil - } - - return count, &latest -} - -func (c *PlatformCursorEquipmentController) rowToMap(row *models.PlatformCursorEquipment) map[string]interface{} { - activationCount, latestActivation := c.cursorActivationSummary(row) - extractCount, latestExtract := c.cursorExtractSummary(row.MachineCode) - - var bindActivationCode interface{} - var activationCodeId interface{} - var lastActivatedAt interface{} = row.ActivationTime - var expireTime interface{} = row.ExpireTime - var lastExtractedAt interface{} - if latestActivation != nil { - bindActivationCode = latestActivation.Code - activationCodeId = latestActivation.ID - if latestActivation.ActivatedAt != nil { - lastActivatedAt = latestActivation.ActivatedAt - } - if latestActivation.ExpiredAt != nil { - expireTime = latestActivation.ExpiredAt - } - } - if latestExtract != nil { - lastExtractedAt = latestExtract.ExtractedTime - } - - // 查询该设备最近一条 IP 日志 - var lastLoginIp interface{} - var lastLoginIpInfo interface{} - var latestIpLog models.PlatformCursorEquipmentIpLog - ipCond := orm.NewCondition(). - AndCond(orm.NewCondition(). - Or("equipment_id", row.ID). - Or("machine_code", row.MachineCode)) - if err := models.Orm.QueryTable(new(models.PlatformCursorEquipmentIpLog)). - SetCond(ipCond). - OrderBy("-id"). - One(&latestIpLog); err == nil { - lastLoginIp = latestIpLog.Query - lastLoginIpInfo = map[string]interface{}{ - "id": latestIpLog.ID, - "source": latestIpLog.Source, - "status": latestIpLog.Status, - "country": latestIpLog.Country, - "countryCode": latestIpLog.CountryCode, - "region": latestIpLog.Region, - "regionName": latestIpLog.RegionName, - "city": latestIpLog.City, - "zip": latestIpLog.Zip, - "lat": latestIpLog.Lat, - "lon": latestIpLog.Lon, - "timezone": latestIpLog.Timezone, - "isp": latestIpLog.ISP, - "org": latestIpLog.Org, - "asInfo": latestIpLog.AsInfo, - "query": latestIpLog.Query, - "createTime": latestIpLog.CreateTime, - } - } - - // 在线状态计算:若最后心跳时间在 5 分钟内,则认为在线 - isOnline := false - if row.LastHeartbeatAt != nil && time.Since(*row.LastHeartbeatAt) < 5*time.Minute { - isOnline = true - } - - return map[string]interface{}{ - "id": row.ID, - "deviceInfo": row.DeviceInfo, - "machineCode": row.MachineCode, - "status": row.Status, - "isOnline": isOnline, - "lastHeartbeatAt": row.LastHeartbeatAt, - "system": row.System, - "os": row.System, - "version": row.Version, - "bindAccount": row.BindAccount, - "bindActivationCode": bindActivationCode, - "activationCode": bindActivationCode, - "activationCodeId": activationCodeId, - "ownerUserId": row.OwnerUserID, - "ownerUserName": row.OwnerUserName, - "activationTime": lastActivatedAt, - "lastActivatedAt": lastActivatedAt, - "expireTime": expireTime, - "expiredAt": expireTime, - "activationCount": activationCount, - "extractCount": extractCount, - "lastExtractedAt": lastExtractedAt, - "lastLoginIp": lastLoginIp, - "lastLoginIpInfo": lastLoginIpInfo, - "remark": row.Remark, - "createTime": row.CreateTime, - "updateTime": row.UpdateTime, - } -} - -// List GET /platform/cursor/equipment/list -// 优化:用 3 条批量 SQL 替代 N+1,列表只返回必要字段,详情由 Detail 接口按需加载完整数据。 -func (c *PlatformCursorEquipmentController) List() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 200 { - pageSize = 200 - } - - keyword := strings.TrimSpace(c.GetString("keyword")) - statusText := strings.TrimSpace(c.GetString("status")) - system := strings.TrimSpace(c.GetString("system")) - if system == "" { - system = strings.TrimSpace(c.GetString("os")) - } - - qs := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).Filter("delete_time__isnull", true) - - if keyword != "" { - cond := orm.NewCondition(). - Or("machine_code__icontains", keyword). - Or("device_info__icontains", keyword). - Or("bind_account__icontains", keyword). - Or("owner_user_name__icontains", keyword). - Or("remark__icontains", keyword) - qs = qs.SetCond(cond) - } - - if statusText != "" { - status, err := strconv.ParseInt(statusText, 10, 8) - if err == nil && cursorEquipmentStatusValid(int8(status)) { - qs = qs.Filter("status", int8(status)) - } - } - - if system != "" { - qs = qs.Filter("system__icontains", system) - } - - total, _ := qs.Count() - - var rows []models.PlatformCursorEquipment - _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取设备列表失败: "+err.Error()) - return - } - - if len(rows) == 0 { - c.ok(map[string]interface{}{ - "list": []interface{}{}, - "total": total, - "page": page, - "pageSize": pageSize, - }) - return - } - - // ── 收集本页所有设备的 ID 和机器码 ────────────────────────────────── - deviceIDs := make([]uint64, 0, len(rows)) - machineCodes := make([]string, 0, len(rows)) - codeToID := make(map[string]uint64, len(rows)) // machineCode -> equipmentID - for _, r := range rows { - deviceIDs = append(deviceIDs, r.ID) - machineCodes = append(machineCodes, r.MachineCode) - codeToID[r.MachineCode] = r.ID - } - - // ── 批量查激活码(1 条 SQL)────────────────────────────────────────── - // 只取本页设备相关的所有激活码,按 id 降序,在内存里取每个设备的 latest - type actSummary struct { - code string - codeID uint64 - count int64 - lastActivatedAt interface{} - expiredAt interface{} - } - actMap := make(map[uint64]*actSummary, len(rows)) - - var allActs []models.PlatformCursorActivationCode - if len(deviceIDs) > 0 { - // Beego ORM 的 __in 过滤 - ids := make([]interface{}, len(deviceIDs)) - for i, id := range deviceIDs { - ids[i] = id - } - codes := make([]interface{}, len(machineCodes)) - for i, mc := range machineCodes { - codes[i] = mc - } - actCond := orm.NewCondition(). - And("delete_time__isnull", true). - AndCond(orm.NewCondition(). - Or("bind_device_id__in", ids). - Or("machine_code__in", codes)) - models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). - SetCond(actCond). - OrderBy("-activated_at", "-id"). - All(&allActs) - } - for i := range allActs { - a := &allActs[i] - // 确定归属设备 ID - var devID uint64 - if a.BindDeviceID != nil && *a.BindDeviceID != 0 { - devID = *a.BindDeviceID - } else if a.MachineCode != nil { - devID = codeToID[*a.MachineCode] - } - if devID == 0 { - continue - } - s, exists := actMap[devID] - if !exists { - s = &actSummary{} - actMap[devID] = s - } - s.count++ - // 第一条就是 latest(已按 -activated_at,-id 排序) - if s.codeID == 0 { - s.code = a.Code - s.codeID = a.ID - if a.ActivatedAt != nil { - s.lastActivatedAt = a.ActivatedAt - } - if a.ExpiredAt != nil { - s.expiredAt = a.ExpiredAt - } - } - } - - // ── 批量查提取记录(1 条 SQL)────────────────────────────────────────── - type extractSummary struct { - count int64 - lastExtracted interface{} - } - extractMap := make(map[uint64]*extractSummary, len(rows)) - - type extractRow struct { - MachineCode string - Cnt int64 - LastExtracted *string - } - var extractRows []extractRow - if len(machineCodes) > 0 { - inPlaceholders := make([]string, len(machineCodes)) - inArgs := make([]interface{}, len(machineCodes)) - for i, mc := range machineCodes { - inPlaceholders[i] = "?" - inArgs[i] = mc - } - sql := "SELECT machine_code, COUNT(*) AS cnt, MAX(extracted_time) AS last_extracted " + - "FROM yz_platform_account_pool_cursor " + - "WHERE is_extracted > 0 AND delete_time IS NULL " + - "AND machine_code IN (" + strings.Join(inPlaceholders, ",") + ") " + - "GROUP BY machine_code" - models.Orm.Raw(sql, inArgs...).QueryRows(&extractRows) - } - for _, er := range extractRows { - devID := codeToID[er.MachineCode] - if devID == 0 { - continue - } - es := &extractSummary{count: er.Cnt} - if er.LastExtracted != nil { - es.lastExtracted = *er.LastExtracted - } - extractMap[devID] = es - } - - // ── 批量查最后登录 IP(1 条 SQL)──────────────────────────────────────── - // 每个设备取 id 最大的一条 - type ipRow struct { - EquipmentID uint64 - MachineCode string - Query string - } - ipMap := make(map[uint64]string, len(rows)) - if len(deviceIDs) > 0 { - inPlaceholders := make([]string, len(deviceIDs)) - inArgs := make([]interface{}, len(deviceIDs)) - for i, id := range deviceIDs { - inPlaceholders[i] = "?" - inArgs[i] = id - } - codePlaceholders := make([]string, len(machineCodes)) - codeArgs := make([]interface{}, len(machineCodes)) - for i, mc := range machineCodes { - codePlaceholders[i] = "?" - codeArgs[i] = mc - } - allArgs := append(inArgs, codeArgs...) - ipSQL := "SELECT equipment_id, machine_code, query FROM yz_platform_cursor_equipment_ip_log " + - "WHERE id IN (" + - " SELECT MAX(id) FROM yz_platform_cursor_equipment_ip_log " + - " WHERE equipment_id IN (" + strings.Join(inPlaceholders, ",") + ")" + - " OR machine_code IN (" + strings.Join(codePlaceholders, ",") + ")" + - " GROUP BY COALESCE(NULLIF(equipment_id,0), machine_code)" + - ")" - var ipRows []ipRow - models.Orm.Raw(ipSQL, allArgs...).QueryRows(&ipRows) - for _, ir := range ipRows { - devID := ir.EquipmentID - if devID == 0 { - devID = codeToID[ir.MachineCode] - } - if devID != 0 { - ipMap[devID] = ir.Query - } - } - } - - // ── 组装列表(纯内存操作)──────────────────────────────────────────── - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - r := &rows[i] - - // 激活码信息 - var bindActivationCode interface{} - var activationCodeId interface{} - var lastActivatedAt interface{} = r.ActivationTime - var expiredAt interface{} = r.ExpireTime - var activationCount int64 - if s, ok := actMap[r.ID]; ok { - activationCount = s.count - bindActivationCode = s.code - activationCodeId = s.codeID - if s.lastActivatedAt != nil { - lastActivatedAt = s.lastActivatedAt - } - if s.expiredAt != nil { - expiredAt = s.expiredAt - } - } - - // 提取记录 - var extractCount int64 - var lastExtractedAt interface{} - if es, ok := extractMap[r.ID]; ok { - extractCount = es.count - lastExtractedAt = es.lastExtracted - } - - // 最后登录 IP - var lastLoginIp interface{} - if ip, ok := ipMap[r.ID]; ok && ip != "" { - lastLoginIp = ip - } - - // 在线状态计算:若最后心跳时间在 5 分钟内,则认为在线 - isOnline := false - if r.LastHeartbeatAt != nil && time.Since(*r.LastHeartbeatAt) < 5*time.Minute { - isOnline = true - } - - list = append(list, map[string]interface{}{ - "id": r.ID, - "deviceInfo": r.DeviceInfo, - "machineCode": r.MachineCode, - "status": r.Status, - "isOnline": isOnline, - "lastHeartbeatAt": r.LastHeartbeatAt, - "system": r.System, - "os": r.System, - "version": r.Version, - "bindAccount": r.BindAccount, - "bindActivationCode": bindActivationCode, - "activationCode": bindActivationCode, - "activationCodeId": activationCodeId, - "ownerUserId": r.OwnerUserID, - "ownerUserName": r.OwnerUserName, - "activationTime": lastActivatedAt, - "lastActivatedAt": lastActivatedAt, - "expireTime": expiredAt, - "expiredAt": expiredAt, - "activationCount": activationCount, - "extractCount": extractCount, - "lastExtractedAt": lastExtractedAt, - "lastLoginIp": lastLoginIp, - // 列表不返回 lastLoginIpInfo,点详情时由 Detail 接口加载完整 IP 信息 - "remark": r.Remark, - "createTime": r.CreateTime, - "updateTime": r.UpdateTime, - }) - } - - c.ok(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} - -// Detail GET /platform/cursor/equipment/detail/:id -func (c *PlatformCursorEquipmentController) Detail() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - - var row models.PlatformCursorEquipment - err = models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", id). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.jsonErr(404, 404, "设备不存在") - return - } - - c.ok(c.rowToMap(&row)) -} - -type platformCursorEquipmentPayload struct { - ID *uint64 `json:"id"` - DeviceInfo *string `json:"deviceInfo"` - MachineCode *string `json:"machineCode"` - Status *int8 `json:"status"` - System *string `json:"system"` - Version *string `json:"version"` - BindAccount *string `json:"bindAccount"` - OwnerUserID *uint64 `json:"ownerUserId"` - OwnerUserName *string `json:"ownerUserName"` - ActivationTime *string `json:"activationTime"` - ExpireTime *string `json:"expireTime"` - Remark *string `json:"remark"` -} - -func (c *PlatformCursorEquipmentController) readPayload() (*platformCursorEquipmentPayload, error) { - body, _ := io.ReadAll(c.Ctx.Request.Body) - var p platformCursorEquipmentPayload - if err := json.Unmarshal(body, &p); err != nil { - return nil, err - } - return &p, nil -} - -func (c *PlatformCursorEquipmentController) payloadToUpdateMap(p *platformCursorEquipmentPayload, includeMachineCode bool) (map[string]interface{}, error) { - up := map[string]interface{}{} - - if includeMachineCode { - if p.MachineCode == nil || strings.TrimSpace(*p.MachineCode) == "" { - return nil, fmt.Errorf("机器码不能为空") - } - up["machine_code"] = strings.TrimSpace(*p.MachineCode) - } else if p.MachineCode != nil { - if strings.TrimSpace(*p.MachineCode) == "" { - return nil, fmt.Errorf("机器码不能为空") - } - up["machine_code"] = strings.TrimSpace(*p.MachineCode) - } - - if p.DeviceInfo != nil { - up["device_info"] = cursorEquipmentTrimPtr(p.DeviceInfo) - } - if p.Status != nil { - if !cursorEquipmentStatusValid(*p.Status) { - return nil, fmt.Errorf("状态不合法,支持:0 未激活、1 激活中、2 已过期、3 已禁用") - } - up["status"] = *p.Status - } - if p.System != nil { - up["system"] = cursorEquipmentTrimPtr(p.System) - } - if p.Version != nil { - up["version"] = cursorEquipmentTrimPtr(p.Version) - } - if p.BindAccount != nil { - up["bind_account"] = cursorEquipmentTrimPtr(p.BindAccount) - } - if p.OwnerUserID != nil { - if *p.OwnerUserID == 0 { - up["owner_user_id"] = nil - } else { - up["owner_user_id"] = *p.OwnerUserID - } - } - if p.OwnerUserName != nil { - up["owner_user_name"] = cursorEquipmentTrimPtr(p.OwnerUserName) - } - if p.ActivationTime != nil { - up["activation_time"] = cursorEquipmentTimePtr(p.ActivationTime) - } - if p.ExpireTime != nil { - up["expire_time"] = cursorEquipmentTimePtr(p.ExpireTime) - } - if p.Remark != nil { - up["remark"] = cursorEquipmentTrimPtr(p.Remark) - } - - return up, nil -} - -// Add POST /platform/cursor/equipment/add -func (c *PlatformCursorEquipmentController) Add() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - p, err := c.readPayload() - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - up, err := c.payloadToUpdateMap(p, true) - if err != nil { - c.jsonErr(400, 400, err.Error()) - return - } - - status := int8(0) - if value, ok := up["status"]; ok { - status = value.(int8) - } - - row := models.PlatformCursorEquipment{ - MachineCode: up["machine_code"].(string), - Status: status, - DeviceInfo: cursorEquipmentTrimPtr(p.DeviceInfo), - System: cursorEquipmentTrimPtr(p.System), - Version: cursorEquipmentTrimPtr(p.Version), - BindAccount: cursorEquipmentTrimPtr(p.BindAccount), - OwnerUserID: p.OwnerUserID, - OwnerUserName: cursorEquipmentTrimPtr(p.OwnerUserName), - ActivationTime: cursorEquipmentTimePtr(p.ActivationTime), - ExpireTime: cursorEquipmentTimePtr(p.ExpireTime), - Remark: cursorEquipmentTrimPtr(p.Remark), - CreateTime: time.Now(), - } - - if row.OwnerUserID != nil && *row.OwnerUserID == 0 { - row.OwnerUserID = nil - } - - id, err := models.Orm.Insert(&row) - if err != nil { - if strings.Contains(strings.ToLower(err.Error()), "duplicate") { - c.jsonErr(400, 400, "机器码已存在") - return - } - c.jsonErr(500, 500, "新增设备失败: "+err.Error()) - return - } - - c.ok(map[string]interface{}{"id": id}) -} - -// Update POST /platform/cursor/equipment/update -func (c *PlatformCursorEquipmentController) Update() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - p, err := c.readPayload() - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if p.ID == nil || *p.ID == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - - up, err := c.payloadToUpdateMap(p, false) - if err != nil { - c.jsonErr(400, 400, err.Error()) - return - } - if len(up) == 0 { - c.jsonErr(400, 400, "无更新字段") - return - } - now := time.Now() - up["update_time"] = now - - n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", *p.ID). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - if strings.Contains(strings.ToLower(err.Error()), "duplicate") { - c.jsonErr(400, 400, "机器码已存在") - return - } - c.jsonErr(500, 500, "更新设备失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "设备不存在") - return - } - - c.ok(nil) -} - -// Delete POST /platform/cursor/equipment/delete/:id -func (c *PlatformCursorEquipmentController) Delete() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除设备失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "设备不存在") - return - } - - c.ok(nil) -} - -type platformCursorEquipmentActivatePayload struct { - ID uint64 `json:"id"` -} - -// Activate POST /platform/cursor/equipment/activate -func (c *PlatformCursorEquipmentController) Activate() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - body, _ := io.ReadAll(c.Ctx.Request.Body) - var p platformCursorEquipmentActivatePayload - if err := json.Unmarshal(body, &p); err != nil || p.ID == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - - now := time.Now() - n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", p.ID). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{ - "status": int8(1), - "activation_time": now, - "update_time": now, - }) - if err != nil { - c.jsonErr(500, 500, "激活设备失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "设备不存在") - return - } - - c.ok(nil) -} - -// ActivationRecords GET /platform/cursor/equipment/activationRecords -func (c *PlatformCursorEquipmentController) ActivationRecords() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - equipmentID, _ := c.GetUint64("equipmentId") - if equipmentID == 0 { - equipmentID, _ = c.GetUint64("id") - } - if equipmentID == 0 { - c.jsonErr(400, 400, "缺少设备ID") - return - } - - var equipment models.PlatformCursorEquipment - if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", equipmentID). - Filter("delete_time__isnull", true). - One(&equipment); err != nil { - c.jsonErr(404, 404, "设备不存在") - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 200 { - pageSize = 200 - } - - cond := orm.NewCondition(). - And("delete_time__isnull", true). - AndCond(orm.NewCondition(). - Or("bind_device_id", equipment.ID). - Or("machine_code", equipment.MachineCode)) - - qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).SetCond(cond) - total, _ := qs.Count() - - var rows []models.PlatformCursorActivationCode - if _, err := qs.OrderBy("-activated_at", "-id").Limit(pageSize, (page-1)*pageSize).All(&rows); err != nil { - c.jsonErr(500, 500, "获取激活记录失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - row := rows[i] - list = append(list, map[string]interface{}{ - "id": row.ID, - "code": row.Code, - "activationCode": row.Code, - "status": row.Status, - "durationDays": row.DurationDays, - "machineCode": row.MachineCode, - "deviceInfo": row.DeviceInfo, - "ownerUserId": row.OwnerUserID, - "ownerUserName": row.OwnerUserName, - "activatedAt": row.ActivatedAt, - "expiredAt": row.ExpiredAt, - "createdAt": row.CreateTime, - "remark": row.Remark, - }) - } - - c.ok(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} - -// ExtractRecords GET /platform/cursor/equipment/extractRecords -func (c *PlatformCursorEquipmentController) ExtractRecords() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - equipmentID, _ := c.GetUint64("equipmentId") - if equipmentID == 0 { - equipmentID, _ = c.GetUint64("id") - } - if equipmentID == 0 { - c.jsonErr(400, 400, "缺少设备ID") - return - } - - var equipment models.PlatformCursorEquipment - if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", equipmentID). - Filter("delete_time__isnull", true). - One(&equipment); err != nil { - c.jsonErr(404, 404, "设备不存在") - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 200 { - pageSize = 200 - } - - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - Filter("delete_time__isnull", true). - Filter("is_extracted__gt", 0). - Filter("machine_code", equipment.MachineCode) - - total, _ := qs.Count() - - var rows []models.PlatformAccountPoolCursor - if _, err := qs.OrderBy("-extracted_time", "-id").Limit(pageSize, (page-1)*pageSize).All(&rows); err != nil { - c.jsonErr(500, 500, "获取提取记录失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - row := rows[i] - content := buildCardResult(&row.Account, &row.Password, row.Token, row.DataType) - list = append(list, map[string]interface{}{ - "id": row.ID, - "status": row.IsExtracted, - "isExtracted": row.IsExtracted, - "platform": row.ExtractedPlatform, - "extractedPlatform": row.ExtractedPlatform, - "dataType": row.DataType, - "type": row.DataType, - "account": row.Account, - "password": row.Password, - "token": row.Token, - "content": content, - "extractedAt": row.ExtractedTime, - "createdAt": row.ExtractedTime, - "remark": row.Remark, - }) - } - - c.ok(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} - -// IpLogs GET /platform/cursor/equipment/ipLogs?equipmentId=1 -func (c *PlatformCursorEquipmentController) IpLogs() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - equipmentID, _ := c.GetUint64("equipmentId") - if equipmentID == 0 { - equipmentID, _ = c.GetUint64("id") - } - if equipmentID == 0 { - c.jsonErr(400, 400, "缺少设备ID") - return - } - - var equipment models.PlatformCursorEquipment - if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). - Filter("id", equipmentID). - Filter("delete_time__isnull", true). - One(&equipment); err != nil { - c.jsonErr(404, 404, "设备不存在") - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 200 { - pageSize = 200 - } - - ipCond := orm.NewCondition(). - AndCond(orm.NewCondition(). - Or("equipment_id", equipment.ID). - Or("machine_code", equipment.MachineCode)) - - qs := models.Orm.QueryTable(new(models.PlatformCursorEquipmentIpLog)).SetCond(ipCond) - total, _ := qs.Count() - - var rows []models.PlatformCursorEquipmentIpLog - if _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows); err != nil { - c.jsonErr(500, 500, "获取 IP 日志失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for _, row := range rows { - list = append(list, map[string]interface{}{ - "id": row.ID, - "source": row.Source, - "status": row.Status, - "country": row.Country, - "countryCode": row.CountryCode, - "region": row.Region, - "regionName": row.RegionName, - "city": row.City, - "zip": row.Zip, - "lat": row.Lat, - "lon": row.Lon, - "timezone": row.Timezone, - "isp": row.ISP, - "org": row.Org, - "asInfo": row.AsInfo, - "query": row.Query, - "createTime": row.CreateTime, - }) - } - - c.ok(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformCursorEquipmentController 平台端 Cursor 设备管理 +type PlatformCursorEquipmentController struct { + beego.Controller +} + +func (c *PlatformCursorEquipmentController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformCursorEquipmentController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *PlatformCursorEquipmentController) ok(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +func cursorEquipmentTrimPtr(value *string) *string { + if value == nil { + return nil + } + v := strings.TrimSpace(*value) + if v == "" { + return nil + } + return &v +} + +func cursorEquipmentTimePtr(value *string) *time.Time { + if value == nil { + return nil + } + v := strings.TrimSpace(*value) + if v == "" { + return nil + } + layouts := []string{ + time.RFC3339, + "2006-01-02 15:04:05", + "2006-01-02 15:04", + "2006-01-02", + } + for _, layout := range layouts { + if t, err := time.ParseInLocation(layout, v, time.Local); err == nil { + return &t + } + } + return nil +} + +func cursorEquipmentStatusValid(status int8) bool { + return status == 0 || status == 1 || status == 2 || status == 3 +} + +func (c *PlatformCursorEquipmentController) cursorActivationSummary(row *models.PlatformCursorEquipment) (int64, *models.PlatformCursorActivationCode) { + cond := orm.NewCondition(). + And("delete_time__isnull", true). + AndCond(orm.NewCondition(). + Or("bind_device_id", row.ID). + Or("machine_code", row.MachineCode)) + + qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).SetCond(cond) + count, _ := qs.Count() + + var latest models.PlatformCursorActivationCode + if err := qs.OrderBy("-activated_at", "-id").One(&latest); err != nil { + return count, nil + } + + return count, &latest +} + +func (c *PlatformCursorEquipmentController) cursorExtractSummary(machineCode string) (int64, *models.PlatformAccountPoolCursor) { + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("delete_time__isnull", true). + Filter("is_extracted__gt", 0). + Filter("machine_code", machineCode) + + count, _ := qs.Count() + + var latest models.PlatformAccountPoolCursor + if err := qs.OrderBy("-extracted_time", "-id").One(&latest); err != nil { + return count, nil + } + + return count, &latest +} + +func (c *PlatformCursorEquipmentController) rowToMap(row *models.PlatformCursorEquipment) map[string]interface{} { + activationCount, latestActivation := c.cursorActivationSummary(row) + extractCount, latestExtract := c.cursorExtractSummary(row.MachineCode) + + var bindActivationCode interface{} + var activationCodeId interface{} + var lastActivatedAt interface{} = row.ActivationTime + var expireTime interface{} = row.ExpireTime + var lastExtractedAt interface{} + if latestActivation != nil { + bindActivationCode = latestActivation.Code + activationCodeId = latestActivation.ID + if latestActivation.ActivatedAt != nil { + lastActivatedAt = latestActivation.ActivatedAt + } + if latestActivation.ExpiredAt != nil { + expireTime = latestActivation.ExpiredAt + } + } + if latestExtract != nil { + lastExtractedAt = latestExtract.ExtractedTime + } + + // 查询该设备最近一条 IP 日志 + var lastLoginIp interface{} + var lastLoginIpInfo interface{} + var latestIpLog models.PlatformCursorEquipmentIpLog + ipCond := orm.NewCondition(). + AndCond(orm.NewCondition(). + Or("equipment_id", row.ID). + Or("machine_code", row.MachineCode)) + if err := models.Orm.QueryTable(new(models.PlatformCursorEquipmentIpLog)). + SetCond(ipCond). + OrderBy("-id"). + One(&latestIpLog); err == nil { + lastLoginIp = latestIpLog.Query + lastLoginIpInfo = map[string]interface{}{ + "id": latestIpLog.ID, + "source": latestIpLog.Source, + "status": latestIpLog.Status, + "country": latestIpLog.Country, + "countryCode": latestIpLog.CountryCode, + "region": latestIpLog.Region, + "regionName": latestIpLog.RegionName, + "city": latestIpLog.City, + "zip": latestIpLog.Zip, + "lat": latestIpLog.Lat, + "lon": latestIpLog.Lon, + "timezone": latestIpLog.Timezone, + "isp": latestIpLog.ISP, + "org": latestIpLog.Org, + "asInfo": latestIpLog.AsInfo, + "query": latestIpLog.Query, + "createTime": latestIpLog.CreateTime, + } + } + + // 在线状态计算:若最后心跳时间在 5 分钟内,则认为在线 + isOnline := false + if row.LastHeartbeatAt != nil && time.Since(*row.LastHeartbeatAt) < 5*time.Minute { + isOnline = true + } + + return map[string]interface{}{ + "id": row.ID, + "deviceInfo": row.DeviceInfo, + "machineCode": row.MachineCode, + "status": row.Status, + "isOnline": isOnline, + "lastHeartbeatAt": row.LastHeartbeatAt, + "system": row.System, + "os": row.System, + "version": row.Version, + "bindAccount": row.BindAccount, + "bindActivationCode": bindActivationCode, + "activationCode": bindActivationCode, + "activationCodeId": activationCodeId, + "ownerUserId": row.OwnerUserID, + "ownerUserName": row.OwnerUserName, + "activationTime": lastActivatedAt, + "lastActivatedAt": lastActivatedAt, + "expireTime": expireTime, + "expiredAt": expireTime, + "activationCount": activationCount, + "extractCount": extractCount, + "lastExtractedAt": lastExtractedAt, + "lastLoginIp": lastLoginIp, + "lastLoginIpInfo": lastLoginIpInfo, + "remark": row.Remark, + "createTime": row.CreateTime, + "updateTime": row.UpdateTime, + } +} + +// List GET /platform/cursor/equipment/list +// 优化:用 3 条批量 SQL 替代 N+1,列表只返回必要字段,详情由 Detail 接口按需加载完整数据。 +func (c *PlatformCursorEquipmentController) List() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + + keyword := strings.TrimSpace(c.GetString("keyword")) + statusText := strings.TrimSpace(c.GetString("status")) + system := strings.TrimSpace(c.GetString("system")) + if system == "" { + system = strings.TrimSpace(c.GetString("os")) + } + + qs := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).Filter("delete_time__isnull", true) + + if keyword != "" { + cond := orm.NewCondition(). + Or("machine_code__icontains", keyword). + Or("device_info__icontains", keyword). + Or("bind_account__icontains", keyword). + Or("owner_user_name__icontains", keyword). + Or("remark__icontains", keyword) + qs = qs.SetCond(cond) + } + + if statusText != "" { + status, err := strconv.ParseInt(statusText, 10, 8) + if err == nil && cursorEquipmentStatusValid(int8(status)) { + qs = qs.Filter("status", int8(status)) + } + } + + if system != "" { + qs = qs.Filter("system__icontains", system) + } + + total, _ := qs.Count() + + var rows []models.PlatformCursorEquipment + _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取设备列表失败: "+err.Error()) + return + } + + if len(rows) == 0 { + c.ok(map[string]interface{}{ + "list": []interface{}{}, + "total": total, + "page": page, + "pageSize": pageSize, + }) + return + } + + // ── 收集本页所有设备的 ID 和机器码 ────────────────────────────────── + deviceIDs := make([]uint64, 0, len(rows)) + machineCodes := make([]string, 0, len(rows)) + codeToID := make(map[string]uint64, len(rows)) // machineCode -> equipmentID + for _, r := range rows { + deviceIDs = append(deviceIDs, r.ID) + machineCodes = append(machineCodes, r.MachineCode) + codeToID[r.MachineCode] = r.ID + } + + // ── 批量查激活码(1 条 SQL)────────────────────────────────────────── + // 只取本页设备相关的所有激活码,按 id 降序,在内存里取每个设备的 latest + type actSummary struct { + code string + codeID uint64 + count int64 + lastActivatedAt interface{} + expiredAt interface{} + } + actMap := make(map[uint64]*actSummary, len(rows)) + + var allActs []models.PlatformCursorActivationCode + if len(deviceIDs) > 0 { + // Beego ORM 的 __in 过滤 + ids := make([]interface{}, len(deviceIDs)) + for i, id := range deviceIDs { + ids[i] = id + } + codes := make([]interface{}, len(machineCodes)) + for i, mc := range machineCodes { + codes[i] = mc + } + actCond := orm.NewCondition(). + And("delete_time__isnull", true). + AndCond(orm.NewCondition(). + Or("bind_device_id__in", ids). + Or("machine_code__in", codes)) + models.Orm.QueryTable(new(models.PlatformCursorActivationCode)). + SetCond(actCond). + OrderBy("-activated_at", "-id"). + All(&allActs) + } + for i := range allActs { + a := &allActs[i] + // 确定归属设备 ID + var devID uint64 + if a.BindDeviceID != nil && *a.BindDeviceID != 0 { + devID = *a.BindDeviceID + } else if a.MachineCode != nil { + devID = codeToID[*a.MachineCode] + } + if devID == 0 { + continue + } + s, exists := actMap[devID] + if !exists { + s = &actSummary{} + actMap[devID] = s + } + s.count++ + // 第一条就是 latest(已按 -activated_at,-id 排序) + if s.codeID == 0 { + s.code = a.Code + s.codeID = a.ID + if a.ActivatedAt != nil { + s.lastActivatedAt = a.ActivatedAt + } + if a.ExpiredAt != nil { + s.expiredAt = a.ExpiredAt + } + } + } + + // ── 批量查提取记录(1 条 SQL)────────────────────────────────────────── + type extractSummary struct { + count int64 + lastExtracted interface{} + } + extractMap := make(map[uint64]*extractSummary, len(rows)) + + type extractRow struct { + MachineCode string + Cnt int64 + LastExtracted *string + } + var extractRows []extractRow + if len(machineCodes) > 0 { + inPlaceholders := make([]string, len(machineCodes)) + inArgs := make([]interface{}, len(machineCodes)) + for i, mc := range machineCodes { + inPlaceholders[i] = "?" + inArgs[i] = mc + } + sql := "SELECT machine_code, COUNT(*) AS cnt, MAX(extracted_time) AS last_extracted " + + "FROM yz_platform_account_pool_cursor " + + "WHERE is_extracted > 0 AND delete_time IS NULL " + + "AND machine_code IN (" + strings.Join(inPlaceholders, ",") + ") " + + "GROUP BY machine_code" + models.Orm.Raw(sql, inArgs...).QueryRows(&extractRows) + } + for _, er := range extractRows { + devID := codeToID[er.MachineCode] + if devID == 0 { + continue + } + es := &extractSummary{count: er.Cnt} + if er.LastExtracted != nil { + es.lastExtracted = *er.LastExtracted + } + extractMap[devID] = es + } + + // ── 批量查最后登录 IP(1 条 SQL)──────────────────────────────────────── + // 每个设备取 id 最大的一条 + type ipRow struct { + EquipmentID uint64 + MachineCode string + Query string + } + ipMap := make(map[uint64]string, len(rows)) + if len(deviceIDs) > 0 { + inPlaceholders := make([]string, len(deviceIDs)) + inArgs := make([]interface{}, len(deviceIDs)) + for i, id := range deviceIDs { + inPlaceholders[i] = "?" + inArgs[i] = id + } + codePlaceholders := make([]string, len(machineCodes)) + codeArgs := make([]interface{}, len(machineCodes)) + for i, mc := range machineCodes { + codePlaceholders[i] = "?" + codeArgs[i] = mc + } + allArgs := append(inArgs, codeArgs...) + ipSQL := "SELECT equipment_id, machine_code, query FROM yz_platform_cursor_equipment_ip_log " + + "WHERE id IN (" + + " SELECT MAX(id) FROM yz_platform_cursor_equipment_ip_log " + + " WHERE equipment_id IN (" + strings.Join(inPlaceholders, ",") + ")" + + " OR machine_code IN (" + strings.Join(codePlaceholders, ",") + ")" + + " GROUP BY COALESCE(NULLIF(equipment_id,0), machine_code)" + + ")" + var ipRows []ipRow + models.Orm.Raw(ipSQL, allArgs...).QueryRows(&ipRows) + for _, ir := range ipRows { + devID := ir.EquipmentID + if devID == 0 { + devID = codeToID[ir.MachineCode] + } + if devID != 0 { + ipMap[devID] = ir.Query + } + } + } + + // ── 组装列表(纯内存操作)──────────────────────────────────────────── + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + r := &rows[i] + + // 激活码信息 + var bindActivationCode interface{} + var activationCodeId interface{} + var lastActivatedAt interface{} = r.ActivationTime + var expiredAt interface{} = r.ExpireTime + var activationCount int64 + if s, ok := actMap[r.ID]; ok { + activationCount = s.count + bindActivationCode = s.code + activationCodeId = s.codeID + if s.lastActivatedAt != nil { + lastActivatedAt = s.lastActivatedAt + } + if s.expiredAt != nil { + expiredAt = s.expiredAt + } + } + + // 提取记录 + var extractCount int64 + var lastExtractedAt interface{} + if es, ok := extractMap[r.ID]; ok { + extractCount = es.count + lastExtractedAt = es.lastExtracted + } + + // 最后登录 IP + var lastLoginIp interface{} + if ip, ok := ipMap[r.ID]; ok && ip != "" { + lastLoginIp = ip + } + + // 在线状态计算:若最后心跳时间在 5 分钟内,则认为在线 + isOnline := false + if r.LastHeartbeatAt != nil && time.Since(*r.LastHeartbeatAt) < 5*time.Minute { + isOnline = true + } + + list = append(list, map[string]interface{}{ + "id": r.ID, + "deviceInfo": r.DeviceInfo, + "machineCode": r.MachineCode, + "status": r.Status, + "isOnline": isOnline, + "lastHeartbeatAt": r.LastHeartbeatAt, + "system": r.System, + "os": r.System, + "version": r.Version, + "bindAccount": r.BindAccount, + "bindActivationCode": bindActivationCode, + "activationCode": bindActivationCode, + "activationCodeId": activationCodeId, + "ownerUserId": r.OwnerUserID, + "ownerUserName": r.OwnerUserName, + "activationTime": lastActivatedAt, + "lastActivatedAt": lastActivatedAt, + "expireTime": expiredAt, + "expiredAt": expiredAt, + "activationCount": activationCount, + "extractCount": extractCount, + "lastExtractedAt": lastExtractedAt, + "lastLoginIp": lastLoginIp, + // 列表不返回 lastLoginIpInfo,点详情时由 Detail 接口加载完整 IP 信息 + "remark": r.Remark, + "createTime": r.CreateTime, + "updateTime": r.UpdateTime, + }) + } + + c.ok(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// Detail GET /platform/cursor/equipment/detail/:id +func (c *PlatformCursorEquipmentController) Detail() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + + var row models.PlatformCursorEquipment + err = models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.jsonErr(404, 404, "设备不存在") + return + } + + c.ok(c.rowToMap(&row)) +} + +type platformCursorEquipmentPayload struct { + ID *uint64 `json:"id"` + DeviceInfo *string `json:"deviceInfo"` + MachineCode *string `json:"machineCode"` + Status *int8 `json:"status"` + System *string `json:"system"` + Version *string `json:"version"` + BindAccount *string `json:"bindAccount"` + OwnerUserID *uint64 `json:"ownerUserId"` + OwnerUserName *string `json:"ownerUserName"` + ActivationTime *string `json:"activationTime"` + ExpireTime *string `json:"expireTime"` + Remark *string `json:"remark"` +} + +func (c *PlatformCursorEquipmentController) readPayload() (*platformCursorEquipmentPayload, error) { + body, _ := io.ReadAll(c.Ctx.Request.Body) + var p platformCursorEquipmentPayload + if err := json.Unmarshal(body, &p); err != nil { + return nil, err + } + return &p, nil +} + +func (c *PlatformCursorEquipmentController) payloadToUpdateMap(p *platformCursorEquipmentPayload, includeMachineCode bool) (map[string]interface{}, error) { + up := map[string]interface{}{} + + if includeMachineCode { + if p.MachineCode == nil || strings.TrimSpace(*p.MachineCode) == "" { + return nil, fmt.Errorf("机器码不能为空") + } + up["machine_code"] = strings.TrimSpace(*p.MachineCode) + } else if p.MachineCode != nil { + if strings.TrimSpace(*p.MachineCode) == "" { + return nil, fmt.Errorf("机器码不能为空") + } + up["machine_code"] = strings.TrimSpace(*p.MachineCode) + } + + if p.DeviceInfo != nil { + up["device_info"] = cursorEquipmentTrimPtr(p.DeviceInfo) + } + if p.Status != nil { + if !cursorEquipmentStatusValid(*p.Status) { + return nil, fmt.Errorf("状态不合法,支持:0 未激活、1 激活中、2 已过期、3 已禁用") + } + up["status"] = *p.Status + } + if p.System != nil { + up["system"] = cursorEquipmentTrimPtr(p.System) + } + if p.Version != nil { + up["version"] = cursorEquipmentTrimPtr(p.Version) + } + if p.BindAccount != nil { + up["bind_account"] = cursorEquipmentTrimPtr(p.BindAccount) + } + if p.OwnerUserID != nil { + if *p.OwnerUserID == 0 { + up["owner_user_id"] = nil + } else { + up["owner_user_id"] = *p.OwnerUserID + } + } + if p.OwnerUserName != nil { + up["owner_user_name"] = cursorEquipmentTrimPtr(p.OwnerUserName) + } + if p.ActivationTime != nil { + up["activation_time"] = cursorEquipmentTimePtr(p.ActivationTime) + } + if p.ExpireTime != nil { + up["expire_time"] = cursorEquipmentTimePtr(p.ExpireTime) + } + if p.Remark != nil { + up["remark"] = cursorEquipmentTrimPtr(p.Remark) + } + + return up, nil +} + +// Add POST /platform/cursor/equipment/add +func (c *PlatformCursorEquipmentController) Add() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + p, err := c.readPayload() + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + up, err := c.payloadToUpdateMap(p, true) + if err != nil { + c.jsonErr(400, 400, err.Error()) + return + } + + status := int8(0) + if value, ok := up["status"]; ok { + status = value.(int8) + } + + row := models.PlatformCursorEquipment{ + MachineCode: up["machine_code"].(string), + Status: status, + DeviceInfo: cursorEquipmentTrimPtr(p.DeviceInfo), + System: cursorEquipmentTrimPtr(p.System), + Version: cursorEquipmentTrimPtr(p.Version), + BindAccount: cursorEquipmentTrimPtr(p.BindAccount), + OwnerUserID: p.OwnerUserID, + OwnerUserName: cursorEquipmentTrimPtr(p.OwnerUserName), + ActivationTime: cursorEquipmentTimePtr(p.ActivationTime), + ExpireTime: cursorEquipmentTimePtr(p.ExpireTime), + Remark: cursorEquipmentTrimPtr(p.Remark), + CreateTime: time.Now(), + } + + if row.OwnerUserID != nil && *row.OwnerUserID == 0 { + row.OwnerUserID = nil + } + + id, err := models.Orm.Insert(&row) + if err != nil { + if strings.Contains(strings.ToLower(err.Error()), "duplicate") { + c.jsonErr(400, 400, "机器码已存在") + return + } + c.jsonErr(500, 500, "新增设备失败: "+err.Error()) + return + } + + c.ok(map[string]interface{}{"id": id}) +} + +// Update POST /platform/cursor/equipment/update +func (c *PlatformCursorEquipmentController) Update() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + p, err := c.readPayload() + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if p.ID == nil || *p.ID == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + + up, err := c.payloadToUpdateMap(p, false) + if err != nil { + c.jsonErr(400, 400, err.Error()) + return + } + if len(up) == 0 { + c.jsonErr(400, 400, "无更新字段") + return + } + now := time.Now() + up["update_time"] = now + + n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", *p.ID). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + if strings.Contains(strings.ToLower(err.Error()), "duplicate") { + c.jsonErr(400, 400, "机器码已存在") + return + } + c.jsonErr(500, 500, "更新设备失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "设备不存在") + return + } + + c.ok(nil) +} + +// Delete POST /platform/cursor/equipment/delete/:id +func (c *PlatformCursorEquipmentController) Delete() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除设备失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "设备不存在") + return + } + + c.ok(nil) +} + +type platformCursorEquipmentActivatePayload struct { + ID uint64 `json:"id"` +} + +// Activate POST /platform/cursor/equipment/activate +func (c *PlatformCursorEquipmentController) Activate() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + body, _ := io.ReadAll(c.Ctx.Request.Body) + var p platformCursorEquipmentActivatePayload + if err := json.Unmarshal(body, &p); err != nil || p.ID == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + + now := time.Now() + n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", p.ID). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{ + "status": int8(1), + "activation_time": now, + "update_time": now, + }) + if err != nil { + c.jsonErr(500, 500, "激活设备失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "设备不存在") + return + } + + c.ok(nil) +} + +// ActivationRecords GET /platform/cursor/equipment/activationRecords +func (c *PlatformCursorEquipmentController) ActivationRecords() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + equipmentID, _ := c.GetUint64("equipmentId") + if equipmentID == 0 { + equipmentID, _ = c.GetUint64("id") + } + if equipmentID == 0 { + c.jsonErr(400, 400, "缺少设备ID") + return + } + + var equipment models.PlatformCursorEquipment + if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", equipmentID). + Filter("delete_time__isnull", true). + One(&equipment); err != nil { + c.jsonErr(404, 404, "设备不存在") + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + + cond := orm.NewCondition(). + And("delete_time__isnull", true). + AndCond(orm.NewCondition(). + Or("bind_device_id", equipment.ID). + Or("machine_code", equipment.MachineCode)) + + qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).SetCond(cond) + total, _ := qs.Count() + + var rows []models.PlatformCursorActivationCode + if _, err := qs.OrderBy("-activated_at", "-id").Limit(pageSize, (page-1)*pageSize).All(&rows); err != nil { + c.jsonErr(500, 500, "获取激活记录失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + row := rows[i] + list = append(list, map[string]interface{}{ + "id": row.ID, + "code": row.Code, + "activationCode": row.Code, + "status": row.Status, + "durationDays": row.DurationDays, + "machineCode": row.MachineCode, + "deviceInfo": row.DeviceInfo, + "ownerUserId": row.OwnerUserID, + "ownerUserName": row.OwnerUserName, + "activatedAt": row.ActivatedAt, + "expiredAt": row.ExpiredAt, + "createdAt": row.CreateTime, + "remark": row.Remark, + }) + } + + c.ok(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// ExtractRecords GET /platform/cursor/equipment/extractRecords +func (c *PlatformCursorEquipmentController) ExtractRecords() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + equipmentID, _ := c.GetUint64("equipmentId") + if equipmentID == 0 { + equipmentID, _ = c.GetUint64("id") + } + if equipmentID == 0 { + c.jsonErr(400, 400, "缺少设备ID") + return + } + + var equipment models.PlatformCursorEquipment + if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", equipmentID). + Filter("delete_time__isnull", true). + One(&equipment); err != nil { + c.jsonErr(404, 404, "设备不存在") + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("delete_time__isnull", true). + Filter("is_extracted__gt", 0). + Filter("machine_code", equipment.MachineCode) + + total, _ := qs.Count() + + var rows []models.PlatformAccountPoolCursor + if _, err := qs.OrderBy("-extracted_time", "-id").Limit(pageSize, (page-1)*pageSize).All(&rows); err != nil { + c.jsonErr(500, 500, "获取提取记录失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + row := rows[i] + content := buildCardResult(&row.Account, &row.Password, row.Token, row.DataType) + list = append(list, map[string]interface{}{ + "id": row.ID, + "status": row.IsExtracted, + "isExtracted": row.IsExtracted, + "platform": row.ExtractedPlatform, + "extractedPlatform": row.ExtractedPlatform, + "dataType": row.DataType, + "type": row.DataType, + "account": row.Account, + "password": row.Password, + "token": row.Token, + "content": content, + "extractedAt": row.ExtractedTime, + "createdAt": row.ExtractedTime, + "remark": row.Remark, + }) + } + + c.ok(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// IpLogs GET /platform/cursor/equipment/ipLogs?equipmentId=1 +func (c *PlatformCursorEquipmentController) IpLogs() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + equipmentID, _ := c.GetUint64("equipmentId") + if equipmentID == 0 { + equipmentID, _ = c.GetUint64("id") + } + if equipmentID == 0 { + c.jsonErr(400, 400, "缺少设备ID") + return + } + + var equipment models.PlatformCursorEquipment + if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)). + Filter("id", equipmentID). + Filter("delete_time__isnull", true). + One(&equipment); err != nil { + c.jsonErr(404, 404, "设备不存在") + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + + ipCond := orm.NewCondition(). + AndCond(orm.NewCondition(). + Or("equipment_id", equipment.ID). + Or("machine_code", equipment.MachineCode)) + + qs := models.Orm.QueryTable(new(models.PlatformCursorEquipmentIpLog)).SetCond(ipCond) + total, _ := qs.Count() + + var rows []models.PlatformCursorEquipmentIpLog + if _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows); err != nil { + c.jsonErr(500, 500, "获取 IP 日志失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for _, row := range rows { + list = append(list, map[string]interface{}{ + "id": row.ID, + "source": row.Source, + "status": row.Status, + "country": row.Country, + "countryCode": row.CountryCode, + "region": row.Region, + "regionName": row.RegionName, + "city": row.City, + "zip": row.Zip, + "lat": row.Lat, + "lon": row.Lon, + "timezone": row.Timezone, + "isp": row.ISP, + "org": row.Org, + "asInfo": row.AsInfo, + "query": row.Query, + "createTime": row.CreateTime, + }) + } + + c.ok(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} diff --git a/go/controllers/platform_domain.go b/go/controllers/platform_domain.go index 13a826d..8839874 100644 --- a/go/controllers/platform_domain.go +++ b/go/controllers/platform_domain.go @@ -1,600 +1,600 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformDomainPoolController 主域名池管理 -type PlatformDomainPoolController struct { - beego.Controller -} - -// PlatformTenantDomainController 租户域名管理 -type PlatformTenantDomainController struct { - beego.Controller -} - -func requirePlatform(c *beego.Controller) (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -// ===== 主域名池 ===== - -// Index GET /platform/domain/pool/index?page=&pageSize=&main_domain=&status= -func (c *PlatformDomainPoolController) Index() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 10 - } - if pageSize > 200 { - pageSize = 200 - } - - mainDomain := strings.TrimSpace(c.GetString("main_domain")) - statusStr := strings.TrimSpace(c.GetString("status")) - - qs := models.Orm.QueryTable(new(models.SystemDomainPool)).Filter("delete_time__isnull", true) - if mainDomain != "" { - qs = qs.Filter("main_domain__icontains", mainDomain) - } - if statusStr != "" { - if st, err := strconv.Atoi(statusStr); err == nil { - qs = qs.Filter("status", st) - } - } - - total, err := qs.Count() - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error()) - return - } - var rows []models.SystemDomainPool - _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - item := map[string]interface{}{ - "id": rows[i].ID, - "main_domain": rows[i].MainDomain, - "status": rows[i].Status, - "create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"), - "update_time": "", - } - if rows[i].UpdateTime != nil { - item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") - } - list = append(list, item) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -// GetEnabledDomains GET /platform/domain/pool/getEnabledDomains -func (c *PlatformDomainPoolController) GetEnabledDomains() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - var rows []models.SystemDomainPool - _, err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("status", 1). - Filter("delete_time__isnull", true). - OrderBy("-id"). - All(&rows) - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取主域名失败: "+err.Error()) - return - } - out := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - out = append(out, map[string]interface{}{ - "id": rows[i].ID, - "main_domain": rows[i].MainDomain, - "status": rows[i].Status, - }) - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -// Create POST /platform/domain/pool/create -func (c *PlatformDomainPoolController) Create() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p domainPoolPayload - if err := json.Unmarshal(raw, &p); err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - md := strings.TrimSpace(p.MainDomain) - if md == "" { - jsonErr(&c.Controller, 400, 400, "主域名不能为空") - return - } - if p.Status != 0 && p.Status != 1 { - p.Status = 1 - } - // 简单去重 - cnt, _ := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("main_domain", md). - Filter("delete_time__isnull", true). - Count() - if cnt > 0 { - jsonErr(&c.Controller, 400, 400, "主域名已存在") - return - } - row := &models.SystemDomainPool{MainDomain: md, Status: p.Status} - if _, err := models.Orm.Insert(row); err != nil { - jsonErr(&c.Controller, 500, 500, "创建失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功"} - _ = c.ServeJSON() -} - -// Update POST /platform/domain/pool/update -func (c *PlatformDomainPoolController) Update() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p domainPoolPayload - if err := json.Unmarshal(raw, &p); err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - if p.ID == 0 { - jsonErr(&c.Controller, 400, 400, "id 不能为空") - return - } - md := strings.TrimSpace(p.MainDomain) - if md == "" { - jsonErr(&c.Controller, 400, 400, "主域名不能为空") - return - } - if p.Status != 0 && p.Status != 1 { - p.Status = 1 - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("id", p.ID). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"main_domain": md, "status": p.Status, "update_time": now}) - if err != nil { - jsonErr(&c.Controller, 500, 500, "更新失败: "+err.Error()) - return - } - if n == 0 { - jsonErr(&c.Controller, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} - _ = c.ServeJSON() -} - -// Delete DELETE /platform/domain/pool/delete/:id -func (c *PlatformDomainPoolController) Delete() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - jsonErr(&c.Controller, 400, 400, "无效ID") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - jsonErr(&c.Controller, 404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -// ToggleStatus POST /platform/domain/pool/toggleStatus body:{id} -func (c *PlatformDomainPoolController) ToggleStatus() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var row models.SystemDomainPool - if err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("id", p.ID). - Filter("delete_time__isnull", true). - One(&row); err != nil { - jsonErr(&c.Controller, 404, 404, "记录不存在") - return - } - newStatus := int8(1) - if row.Status == 1 { - newStatus = 0 - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("id", p.ID). - Update(map[string]interface{}{"status": newStatus, "update_time": now}) - if err != nil { - jsonErr(&c.Controller, 500, 500, "切换失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// ===== 租户域名 ===== - -// Index GET /platform/domain/tenant/index?page=&pageSize=&tid=&status=&sub_domain= -func (c *PlatformTenantDomainController) Index() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 10 - } - if pageSize > 200 { - pageSize = 200 - } - - tid, _ := c.GetUint64("tid") - statusStr := strings.TrimSpace(c.GetString("status")) - subDomain := strings.TrimSpace(c.GetString("sub_domain")) - - qs := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("delete_time__isnull", true) - if tid > 0 { - qs = qs.Filter("tid", tid) - } - if statusStr != "" { - if st, err := strconv.Atoi(statusStr); err == nil { - qs = qs.Filter("status", st) - } - } - if subDomain != "" { - qs = qs.Filter("sub_domain__icontains", subDomain) - } - - total, err := qs.Count() - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error()) - return - } - var rows []models.SystemTenantDomain - _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error()) - return - } - list := make([]models.SystemTenantDomain, 0, len(rows)) - list = append(list, rows...) - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"list": list, "total": total}, - } - _ = c.ServeJSON() -} - -// MyDomains GET /platform/domain/tenant/myDomains?tid=1 -func (c *PlatformTenantDomainController) MyDomains() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - tid, _ := c.GetUint64("tid") - if tid == 0 { - jsonErr(&c.Controller, 400, 400, "租户ID不能为空") - return - } - var rows []models.SystemTenantDomain - _, err := models.Orm.QueryTable(new(models.SystemTenantDomain)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - OrderBy("-id"). - All(&rows) - if err != nil { - jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows} - _ = c.ServeJSON() -} - -// Apply POST /platform/domain/tenant/apply body:{tid,sub_domain,main_domain} -func (c *PlatformTenantDomainController) Apply() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p struct { - Tid uint64 `json:"tid"` - SubDomain string `json:"sub_domain"` - MainDomain string `json:"main_domain"` - } - if err := json.Unmarshal(raw, &p); err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - if p.Tid == 0 { - jsonErr(&c.Controller, 400, 400, "租户ID不能为空") - return - } - sub := strings.TrimSpace(p.SubDomain) - main := strings.TrimSpace(p.MainDomain) - if sub == "" { - jsonErr(&c.Controller, 400, 400, "二级域名前缀不能为空") - return - } - if main == "" { - jsonErr(&c.Controller, 400, 400, "请选择主域名") - return - } - if !subDomainRe.MatchString(sub) { - jsonErr(&c.Controller, 400, 400, "二级域名前缀格式不正确") - return - } - - // 该租户是否已有域名 - cnt, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)). - Filter("tid", p.Tid). - Filter("delete_time__isnull", true). - Count() - if cnt > 0 { - jsonErr(&c.Controller, 400, 400, "该租户已有域名,请删除后再次申请") - return - } - - // 主域名存在且启用 - var pool models.SystemDomainPool - if err := models.Orm.QueryTable(new(models.SystemDomainPool)). - Filter("main_domain", main). - Filter("status", 1). - Filter("delete_time__isnull", true). - One(&pool); err != nil { - jsonErr(&c.Controller, 400, 400, "主域名不存在或已禁用") - return - } - - // 二级域名是否已被使用(同主域名下) - used, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)). - Filter("sub_domain", sub). - Filter("main_domain", main). - Filter("delete_time__isnull", true). - Count() - if used > 0 { - jsonErr(&c.Controller, 400, 400, "该二级域名已被使用") - return - } - - full := sub + "." + main - now := time.Now() - tid := p.Tid - row := &models.SystemTenantDomain{ - Tid: &tid, - SubDomain: &sub, - MainDomain: &main, - FullDomain: &full, - Status: 0, - CreateTime: now, - UpdateTime: &now, - } - id, err := models.Orm.Insert(row) - if err != nil { - jsonErr(&c.Controller, 500, 500, "申请失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "申请提交成功,等待审核", "data": map[string]interface{}{"id": uint64(id)}} - _ = c.ServeJSON() -} - -// Audit POST /platform/domain/tenant/audit body:{id,action} action=approve/reject -func (c *PlatformTenantDomainController) Audit() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - Action string `json:"action"` - } - if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var row models.SystemTenantDomain - if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil { - jsonErr(&c.Controller, 404, 404, "域名不存在") - return - } - if row.Status != 0 { - jsonErr(&c.Controller, 400, 400, "该域名已审核过了") - return - } - newStatus := 2 - msg := "已拒绝" - if strings.ToLower(strings.TrimSpace(p.Action)) == "approve" { - newStatus = 1 - msg = "审核通过" - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{ - "status": newStatus, - "update_time": now, - }) - if err != nil { - jsonErr(&c.Controller, 500, 500, "审核失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg} - _ = c.ServeJSON() -} - -// ToggleStatus POST /platform/domain/tenant/toggleStatus body:{id} -func (c *PlatformTenantDomainController) ToggleStatus() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - var row models.SystemTenantDomain - if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil { - jsonErr(&c.Controller, 404, 404, "域名不存在") - return - } - if row.Status == 0 { - jsonErr(&c.Controller, 400, 400, "审核中不可操作") - return - } - newStatus := 2 - if row.Status == 2 { - newStatus = 1 - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{ - "status": newStatus, - "update_time": now, - }) - if err != nil { - jsonErr(&c.Controller, 500, 500, "操作失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// Delete DELETE /platform/domain/tenant/delete/:id -func (c *PlatformTenantDomainController) Delete() { - if _, err := requirePlatform(&c.Controller); err != nil { - jsonErr(&c.Controller, 401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - jsonErr(&c.Controller, 400, 400, "参数错误") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemTenantDomain)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - jsonErr(&c.Controller, 404, 404, "域名不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -// 用于复杂筛选时可扩展:当前保留 orm.Condition import,避免被 gofmt 删除 -var _ = orm.NewCondition +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformDomainPoolController 主域名池管理 +type PlatformDomainPoolController struct { + beego.Controller +} + +// PlatformTenantDomainController 租户域名管理 +type PlatformTenantDomainController struct { + beego.Controller +} + +func requirePlatform(c *beego.Controller) (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +// ===== 主域名池 ===== + +// Index GET /platform/domain/pool/index?page=&pageSize=&main_domain=&status= +func (c *PlatformDomainPoolController) Index() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + if pageSize > 200 { + pageSize = 200 + } + + mainDomain := strings.TrimSpace(c.GetString("main_domain")) + statusStr := strings.TrimSpace(c.GetString("status")) + + qs := models.Orm.QueryTable(new(models.SystemDomainPool)).Filter("delete_time__isnull", true) + if mainDomain != "" { + qs = qs.Filter("main_domain__icontains", mainDomain) + } + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + qs = qs.Filter("status", st) + } + } + + total, err := qs.Count() + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error()) + return + } + var rows []models.SystemDomainPool + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + item := map[string]interface{}{ + "id": rows[i].ID, + "main_domain": rows[i].MainDomain, + "status": rows[i].Status, + "create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"), + "update_time": "", + } + if rows[i].UpdateTime != nil { + item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") + } + list = append(list, item) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// GetEnabledDomains GET /platform/domain/pool/getEnabledDomains +func (c *PlatformDomainPoolController) GetEnabledDomains() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + var rows []models.SystemDomainPool + _, err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("status", 1). + Filter("delete_time__isnull", true). + OrderBy("-id"). + All(&rows) + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取主域名失败: "+err.Error()) + return + } + out := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + out = append(out, map[string]interface{}{ + "id": rows[i].ID, + "main_domain": rows[i].MainDomain, + "status": rows[i].Status, + }) + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +// Create POST /platform/domain/pool/create +func (c *PlatformDomainPoolController) Create() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p domainPoolPayload + if err := json.Unmarshal(raw, &p); err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + md := strings.TrimSpace(p.MainDomain) + if md == "" { + jsonErr(&c.Controller, 400, 400, "主域名不能为空") + return + } + if p.Status != 0 && p.Status != 1 { + p.Status = 1 + } + // 简单去重 + cnt, _ := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("main_domain", md). + Filter("delete_time__isnull", true). + Count() + if cnt > 0 { + jsonErr(&c.Controller, 400, 400, "主域名已存在") + return + } + row := &models.SystemDomainPool{MainDomain: md, Status: p.Status} + if _, err := models.Orm.Insert(row); err != nil { + jsonErr(&c.Controller, 500, 500, "创建失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功"} + _ = c.ServeJSON() +} + +// Update POST /platform/domain/pool/update +func (c *PlatformDomainPoolController) Update() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p domainPoolPayload + if err := json.Unmarshal(raw, &p); err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + if p.ID == 0 { + jsonErr(&c.Controller, 400, 400, "id 不能为空") + return + } + md := strings.TrimSpace(p.MainDomain) + if md == "" { + jsonErr(&c.Controller, 400, 400, "主域名不能为空") + return + } + if p.Status != 0 && p.Status != 1 { + p.Status = 1 + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("id", p.ID). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"main_domain": md, "status": p.Status, "update_time": now}) + if err != nil { + jsonErr(&c.Controller, 500, 500, "更新失败: "+err.Error()) + return + } + if n == 0 { + jsonErr(&c.Controller, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +// Delete DELETE /platform/domain/pool/delete/:id +func (c *PlatformDomainPoolController) Delete() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + jsonErr(&c.Controller, 400, 400, "无效ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + jsonErr(&c.Controller, 404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// ToggleStatus POST /platform/domain/pool/toggleStatus body:{id} +func (c *PlatformDomainPoolController) ToggleStatus() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var row models.SystemDomainPool + if err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("id", p.ID). + Filter("delete_time__isnull", true). + One(&row); err != nil { + jsonErr(&c.Controller, 404, 404, "记录不存在") + return + } + newStatus := int8(1) + if row.Status == 1 { + newStatus = 0 + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("id", p.ID). + Update(map[string]interface{}{"status": newStatus, "update_time": now}) + if err != nil { + jsonErr(&c.Controller, 500, 500, "切换失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// ===== 租户域名 ===== + +// Index GET /platform/domain/tenant/index?page=&pageSize=&tid=&status=&sub_domain= +func (c *PlatformTenantDomainController) Index() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + if pageSize > 200 { + pageSize = 200 + } + + tid, _ := c.GetUint64("tid") + statusStr := strings.TrimSpace(c.GetString("status")) + subDomain := strings.TrimSpace(c.GetString("sub_domain")) + + qs := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("delete_time__isnull", true) + if tid > 0 { + qs = qs.Filter("tid", tid) + } + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + qs = qs.Filter("status", st) + } + } + if subDomain != "" { + qs = qs.Filter("sub_domain__icontains", subDomain) + } + + total, err := qs.Count() + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error()) + return + } + var rows []models.SystemTenantDomain + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error()) + return + } + list := make([]models.SystemTenantDomain, 0, len(rows)) + list = append(list, rows...) + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"list": list, "total": total}, + } + _ = c.ServeJSON() +} + +// MyDomains GET /platform/domain/tenant/myDomains?tid=1 +func (c *PlatformTenantDomainController) MyDomains() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + tid, _ := c.GetUint64("tid") + if tid == 0 { + jsonErr(&c.Controller, 400, 400, "租户ID不能为空") + return + } + var rows []models.SystemTenantDomain + _, err := models.Orm.QueryTable(new(models.SystemTenantDomain)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + OrderBy("-id"). + All(&rows) + if err != nil { + jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows} + _ = c.ServeJSON() +} + +// Apply POST /platform/domain/tenant/apply body:{tid,sub_domain,main_domain} +func (c *PlatformTenantDomainController) Apply() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p struct { + Tid uint64 `json:"tid"` + SubDomain string `json:"sub_domain"` + MainDomain string `json:"main_domain"` + } + if err := json.Unmarshal(raw, &p); err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + if p.Tid == 0 { + jsonErr(&c.Controller, 400, 400, "租户ID不能为空") + return + } + sub := strings.TrimSpace(p.SubDomain) + main := strings.TrimSpace(p.MainDomain) + if sub == "" { + jsonErr(&c.Controller, 400, 400, "二级域名前缀不能为空") + return + } + if main == "" { + jsonErr(&c.Controller, 400, 400, "请选择主域名") + return + } + if !subDomainRe.MatchString(sub) { + jsonErr(&c.Controller, 400, 400, "二级域名前缀格式不正确") + return + } + + // 该租户是否已有域名 + cnt, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)). + Filter("tid", p.Tid). + Filter("delete_time__isnull", true). + Count() + if cnt > 0 { + jsonErr(&c.Controller, 400, 400, "该租户已有域名,请删除后再次申请") + return + } + + // 主域名存在且启用 + var pool models.SystemDomainPool + if err := models.Orm.QueryTable(new(models.SystemDomainPool)). + Filter("main_domain", main). + Filter("status", 1). + Filter("delete_time__isnull", true). + One(&pool); err != nil { + jsonErr(&c.Controller, 400, 400, "主域名不存在或已禁用") + return + } + + // 二级域名是否已被使用(同主域名下) + used, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)). + Filter("sub_domain", sub). + Filter("main_domain", main). + Filter("delete_time__isnull", true). + Count() + if used > 0 { + jsonErr(&c.Controller, 400, 400, "该二级域名已被使用") + return + } + + full := sub + "." + main + now := time.Now() + tid := p.Tid + row := &models.SystemTenantDomain{ + Tid: &tid, + SubDomain: &sub, + MainDomain: &main, + FullDomain: &full, + Status: 0, + CreateTime: now, + UpdateTime: &now, + } + id, err := models.Orm.Insert(row) + if err != nil { + jsonErr(&c.Controller, 500, 500, "申请失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "申请提交成功,等待审核", "data": map[string]interface{}{"id": uint64(id)}} + _ = c.ServeJSON() +} + +// Audit POST /platform/domain/tenant/audit body:{id,action} action=approve/reject +func (c *PlatformTenantDomainController) Audit() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + Action string `json:"action"` + } + if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var row models.SystemTenantDomain + if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil { + jsonErr(&c.Controller, 404, 404, "域名不存在") + return + } + if row.Status != 0 { + jsonErr(&c.Controller, 400, 400, "该域名已审核过了") + return + } + newStatus := 2 + msg := "已拒绝" + if strings.ToLower(strings.TrimSpace(p.Action)) == "approve" { + newStatus = 1 + msg = "审核通过" + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{ + "status": newStatus, + "update_time": now, + }) + if err != nil { + jsonErr(&c.Controller, 500, 500, "审核失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg} + _ = c.ServeJSON() +} + +// ToggleStatus POST /platform/domain/tenant/toggleStatus body:{id} +func (c *PlatformTenantDomainController) ToggleStatus() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + var row models.SystemTenantDomain + if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil { + jsonErr(&c.Controller, 404, 404, "域名不存在") + return + } + if row.Status == 0 { + jsonErr(&c.Controller, 400, 400, "审核中不可操作") + return + } + newStatus := 2 + if row.Status == 2 { + newStatus = 1 + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{ + "status": newStatus, + "update_time": now, + }) + if err != nil { + jsonErr(&c.Controller, 500, 500, "操作失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// Delete DELETE /platform/domain/tenant/delete/:id +func (c *PlatformTenantDomainController) Delete() { + if _, err := requirePlatform(&c.Controller); err != nil { + jsonErr(&c.Controller, 401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + jsonErr(&c.Controller, 400, 400, "参数错误") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemTenantDomain)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + jsonErr(&c.Controller, 404, 404, "域名不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// 用于复杂筛选时可扩展:当前保留 orm.Condition import,避免被 gofmt 删除 +var _ = orm.NewCondition diff --git a/go/controllers/platform_email.go b/go/controllers/platform_email.go index 96ea816..af4904b 100644 --- a/go/controllers/platform_email.go +++ b/go/controllers/platform_email.go @@ -1,298 +1,298 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - - "server/models" - "server/pkg/jwtutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformEmailController 系统邮箱配置(yz_system_email) -type PlatformEmailController struct { - beego.Controller -} - -func (c *PlatformEmailController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformEmailController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func emailRowToMap(m models.SystemEmail) map[string]interface{} { - out := map[string]interface{}{ - "id": m.ID, - "from_address": m.FromAddress, - "host": m.Host, - "port": m.Port, - "password": m.Password, - "encryption": m.Encryption, - "timeout": m.Timeout, - "status": m.Status, - "create_time": m.CreateTime.Format("2006-01-02 15:04:05"), - "update_time": m.UpdateTime.Format("2006-01-02 15:04:05"), - } - if m.FromName != nil { - out["from_name"] = *m.FromName - } else { - out["from_name"] = "" - } - if m.Remark != nil { - out["remark"] = *m.Remark - } else { - out["remark"] = "" - } - return out -} - -// GetInfo GET /platform/email/info -func (c *PlatformEmailController) GetInfo() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - rows, err := services.ListSystemEmails() - if err != nil { - c.jsonErr(500, 500, "获取邮箱配置失败: "+err.Error()) - return - } - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - list = append(list, emailRowToMap(rows[i])) - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} - _ = c.ServeJSON() -} - -type emailFormPayload struct { - FromAddress string `json:"fromAddress"` - FromName string `json:"fromName"` - Host string `json:"host"` - Port interface{} `json:"port"` - Password string `json:"password"` - Encryption string `json:"encryption"` - Timeout interface{} `json:"timeout"` - Status interface{} `json:"status"` -} -type testEmailPayload struct { - emailFormPayload - TestEmail string `json:"testEmail"` -} - -func parseInt8Flexible(v interface{}) int8 { - if v == nil { - return 1 - } - switch x := v.(type) { - case bool: - if x { - return 1 - } - return 0 - case float64: - return int8(x) - case int: - return int8(x) - case int8: - return x - case string: - s := strings.TrimSpace(x) - if s == "" { - return 1 - } - n, err := strconv.ParseInt(s, 10, 8) - if err != nil { - return 1 - } - return int8(n) - default: - return 1 - } -} - -func parseUintFlexible(v interface{}) uint { - if v == nil { - return 0 - } - switch x := v.(type) { - case float64: - if x < 0 { - return 0 - } - return uint(x) - case string: - n, err := parseUintString(x) - if err != nil { - return 0 - } - return n - default: - return 0 - } -} - -func parseUintString(s string) (uint, error) { - s = strings.TrimSpace(s) - if s == "" { - return 0, fmt.Errorf("empty") - } - n, err := strconv.ParseUint(s, 10, 32) - if err != nil { - return 0, err - } - return uint(n), nil -} - -func normalizeEncryption(s string) string { - s = strings.ToLower(strings.TrimSpace(s)) - switch s { - case "ssl", "tls", "none": - return s - default: - return "ssl" - } -} - -// EditInfo POST /platform/email/editinfo -func (c *PlatformEmailController) EditInfo() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p emailFormPayload - if uerr := json.Unmarshal(raw, &p); uerr != nil { - c.jsonErr(400, 400, "参数错误") - return - } - from := strings.TrimSpace(p.FromAddress) - host := strings.TrimSpace(p.Host) - if from == "" || host == "" { - c.jsonErr(400, 400, "发件人邮箱与 SMTP 主机不能为空") - return - } - port := parseUintFlexible(p.Port) - if port == 0 { - port = 465 - } - timeout := parseUintFlexible(p.Timeout) - if timeout == 0 { - timeout = 30 - } - enc := normalizeEncryption(p.Encryption) - cnt, cerr := models.Orm.QueryTable(new(models.SystemEmail)).Count() - if cerr != nil { - c.jsonErr(500, 500, "读取邮箱配置失败: "+cerr.Error()) - return - } - if strings.TrimSpace(p.Password) == "" && cnt == 0 { - c.jsonErr(400, 400, "授权码/密码不能为空") - return - } - var fn *string - if strings.TrimSpace(p.FromName) != "" { - s := strings.TrimSpace(p.FromName) - fn = &s - } - status := parseInt8Flexible(p.Status) - err = services.UpsertFirstSystemEmail(from, fn, host, port, strings.TrimSpace(p.Password), enc, timeout, status, nil) - if err != nil { - c.jsonErr(500, 500, "保存邮箱配置失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -// SendTestEmail POST /platform/email/sendtestemail -func (c *PlatformEmailController) SendTestEmail() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p testEmailPayload - if uerr := json.Unmarshal(raw, &p); uerr != nil { - c.jsonErr(400, 400, "参数错误") - return - } - to := strings.TrimSpace(p.TestEmail) - if to == "" { - c.jsonErr(400, 400, "测试收件邮箱不能为空") - return - } - from := strings.TrimSpace(p.FromAddress) - host := strings.TrimSpace(p.Host) - if from == "" || host == "" { - c.jsonErr(400, 400, "发件人邮箱与 SMTP 主机不能为空") - return - } - port := parseUintFlexible(p.Port) - if port == 0 { - port = 465 - } - timeout := parseUintFlexible(p.Timeout) - if timeout == 0 { - timeout = 30 - } - enc := normalizeEncryption(p.Encryption) - pass := strings.TrimSpace(p.Password) - if pass == "" { - rows, lerr := services.ListSystemEmails() - if lerr == nil && len(rows) > 0 { - pass = rows[0].Password - } - } - if pass == "" { - c.jsonErr(400, 400, "授权码/密码不能为空(请填写或先保存配置)") - return - } - cfg := services.SMTPConfig{ - FromAddress: from, - FromName: strings.TrimSpace(p.FromName), - Host: host, - Port: port, - Password: pass, - Encryption: enc, - Timeout: timeout, - } - if err := services.SendTestEmailSMTP(cfg, to); err != nil { - c.jsonErr(500, 500, "发送失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发送成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformEmailController 系统邮箱配置(yz_system_email) +type PlatformEmailController struct { + beego.Controller +} + +func (c *PlatformEmailController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformEmailController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func emailRowToMap(m models.SystemEmail) map[string]interface{} { + out := map[string]interface{}{ + "id": m.ID, + "from_address": m.FromAddress, + "host": m.Host, + "port": m.Port, + "password": m.Password, + "encryption": m.Encryption, + "timeout": m.Timeout, + "status": m.Status, + "create_time": m.CreateTime.Format("2006-01-02 15:04:05"), + "update_time": m.UpdateTime.Format("2006-01-02 15:04:05"), + } + if m.FromName != nil { + out["from_name"] = *m.FromName + } else { + out["from_name"] = "" + } + if m.Remark != nil { + out["remark"] = *m.Remark + } else { + out["remark"] = "" + } + return out +} + +// GetInfo GET /platform/email/info +func (c *PlatformEmailController) GetInfo() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + rows, err := services.ListSystemEmails() + if err != nil { + c.jsonErr(500, 500, "获取邮箱配置失败: "+err.Error()) + return + } + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, emailRowToMap(rows[i])) + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} + _ = c.ServeJSON() +} + +type emailFormPayload struct { + FromAddress string `json:"fromAddress"` + FromName string `json:"fromName"` + Host string `json:"host"` + Port interface{} `json:"port"` + Password string `json:"password"` + Encryption string `json:"encryption"` + Timeout interface{} `json:"timeout"` + Status interface{} `json:"status"` +} +type testEmailPayload struct { + emailFormPayload + TestEmail string `json:"testEmail"` +} + +func parseInt8Flexible(v interface{}) int8 { + if v == nil { + return 1 + } + switch x := v.(type) { + case bool: + if x { + return 1 + } + return 0 + case float64: + return int8(x) + case int: + return int8(x) + case int8: + return x + case string: + s := strings.TrimSpace(x) + if s == "" { + return 1 + } + n, err := strconv.ParseInt(s, 10, 8) + if err != nil { + return 1 + } + return int8(n) + default: + return 1 + } +} + +func parseUintFlexible(v interface{}) uint { + if v == nil { + return 0 + } + switch x := v.(type) { + case float64: + if x < 0 { + return 0 + } + return uint(x) + case string: + n, err := parseUintString(x) + if err != nil { + return 0 + } + return n + default: + return 0 + } +} + +func parseUintString(s string) (uint, error) { + s = strings.TrimSpace(s) + if s == "" { + return 0, fmt.Errorf("empty") + } + n, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return 0, err + } + return uint(n), nil +} + +func normalizeEncryption(s string) string { + s = strings.ToLower(strings.TrimSpace(s)) + switch s { + case "ssl", "tls", "none": + return s + default: + return "ssl" + } +} + +// EditInfo POST /platform/email/editinfo +func (c *PlatformEmailController) EditInfo() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p emailFormPayload + if uerr := json.Unmarshal(raw, &p); uerr != nil { + c.jsonErr(400, 400, "参数错误") + return + } + from := strings.TrimSpace(p.FromAddress) + host := strings.TrimSpace(p.Host) + if from == "" || host == "" { + c.jsonErr(400, 400, "发件人邮箱与 SMTP 主机不能为空") + return + } + port := parseUintFlexible(p.Port) + if port == 0 { + port = 465 + } + timeout := parseUintFlexible(p.Timeout) + if timeout == 0 { + timeout = 30 + } + enc := normalizeEncryption(p.Encryption) + cnt, cerr := models.Orm.QueryTable(new(models.SystemEmail)).Count() + if cerr != nil { + c.jsonErr(500, 500, "读取邮箱配置失败: "+cerr.Error()) + return + } + if strings.TrimSpace(p.Password) == "" && cnt == 0 { + c.jsonErr(400, 400, "授权码/密码不能为空") + return + } + var fn *string + if strings.TrimSpace(p.FromName) != "" { + s := strings.TrimSpace(p.FromName) + fn = &s + } + status := parseInt8Flexible(p.Status) + err = services.UpsertFirstSystemEmail(from, fn, host, port, strings.TrimSpace(p.Password), enc, timeout, status, nil) + if err != nil { + c.jsonErr(500, 500, "保存邮箱配置失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +// SendTestEmail POST /platform/email/sendtestemail +func (c *PlatformEmailController) SendTestEmail() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p testEmailPayload + if uerr := json.Unmarshal(raw, &p); uerr != nil { + c.jsonErr(400, 400, "参数错误") + return + } + to := strings.TrimSpace(p.TestEmail) + if to == "" { + c.jsonErr(400, 400, "测试收件邮箱不能为空") + return + } + from := strings.TrimSpace(p.FromAddress) + host := strings.TrimSpace(p.Host) + if from == "" || host == "" { + c.jsonErr(400, 400, "发件人邮箱与 SMTP 主机不能为空") + return + } + port := parseUintFlexible(p.Port) + if port == 0 { + port = 465 + } + timeout := parseUintFlexible(p.Timeout) + if timeout == 0 { + timeout = 30 + } + enc := normalizeEncryption(p.Encryption) + pass := strings.TrimSpace(p.Password) + if pass == "" { + rows, lerr := services.ListSystemEmails() + if lerr == nil && len(rows) > 0 { + pass = rows[0].Password + } + } + if pass == "" { + c.jsonErr(400, 400, "授权码/密码不能为空(请填写或先保存配置)") + return + } + cfg := services.SMTPConfig{ + FromAddress: from, + FromName: strings.TrimSpace(p.FromName), + Host: host, + Port: port, + Password: pass, + Encryption: enc, + Timeout: timeout, + } + if err := services.SendTestEmailSMTP(cfg, to); err != nil { + c.jsonErr(500, 500, "发送失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发送成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_file.go b/go/controllers/platform_file.go index 43942c5..dd094dc 100644 --- a/go/controllers/platform_file.go +++ b/go/controllers/platform_file.go @@ -1,907 +1,907 @@ -package controllers - -import ( - "crypto/md5" - "encoding/hex" - "encoding/json" - "fmt" - "io" - "os" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformFileController 平台端文件管理(yz_system_files / yz_system_files_category) -type PlatformFileController struct { - beego.Controller -} - -const platformFileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 -const platformFileUploadMaxBytes = platformFileUploadMaxMB * 1024 * 1024 - -var platformFileTypeByCategory = map[string]uint8{ - "image": 1, - "document": 2, - "video": 3, - "audio": 4, - "appsupgrade": 2, -} - -var platformAllowedExtByCategory = map[string][]string{ - "image": {"jpg", "jpeg", "png", "gif", "bmp", "webp"}, - "document": {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"}, - "video": {"mp4", "webm", "mov"}, - "audio": {"mp3", "wav", "ogg"}, - // 安装包 / 软件升级(上传时 cate 选 appsupgrade 分类即可,扩展名在此放行) - "appsupgrade": {"zip", "exe", "dmg", "msi", "msix", "apk", "deb", "rpm", "7z", "tar", "gz", "pkg"}, -} - -func (c *PlatformFileController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformFileController) effectiveTid(claims *jwtutil.Claims) uint64 { - _ = c.ParseForm(1 << 20) - if tid, err := c.GetUint64("tid"); err == nil && tid > 0 { - return tid - } - if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" { - if v, e := strconv.ParseUint(h, 10, 64); e == nil { - return v - } - } - if claims != nil && claims.TenantId > 0 { - return uint64(claims.TenantId) - } - return 0 -} - -func (c *PlatformFileController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *PlatformFileController) jsonOK(data interface{}) { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} - _ = c.ServeJSON() -} - -func platformDetectFileType(ext string) uint8 { - ext = strings.ToLower(strings.TrimPrefix(ext, ".")) - for cat, exts := range platformAllowedExtByCategory { - for _, e := range exts { - if e == ext { - if t, ok := platformFileTypeByCategory[cat]; ok { - return t - } - return 2 - } - } - } - return 2 -} - -func platformFileExt(name string) string { - name = strings.TrimSpace(name) - if i := strings.LastIndex(name, "."); i >= 0 && i < len(name)-1 { - return strings.ToLower(name[i+1:]) - } - return "" -} - -func platformFileToMap(f *models.SystemFile) map[string]interface{} { - ct := f.CreateTime.Format("2006-01-02 15:04:05") - m := map[string]interface{}{ - "id": f.ID, - "tid": f.Tid, - "name": f.Name, - "type": f.Type, - "cate": f.Cate, - "size": f.Size, - "src": f.Src, - "uploader": f.Uploader, - "md5": f.Md5, - "create_time": ct, - "createTime": ct, - "groupId": f.Cate, - "url": f.Src, - } - if f.Uid != nil { - m["uid"] = *f.Uid - } - if f.Tuid != nil { - m["tuid"] = *f.Tuid - } - return m -} - -func platformRemovePhysicalBySrc(webSrc string) { - webSrc = strings.TrimSpace(webSrc) - if webSrc == "" { - return - } - webSrc = strings.TrimPrefix(webSrc, "/") - _ = os.Remove(webSrc) -} - -// GetAllFiles GET /platform/allfiles -func (c *PlatformFileController) GetAllFiles() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 10 - } - cate, _ := c.GetUint64("cate") - keyword := strings.TrimSpace(c.GetString("keyword")) - - qs := models.Orm.QueryTable(new(models.SystemFile)). - Filter("tid", tid). - Filter("delete_time__isnull", true) - if cate > 0 { - qs = qs.Filter("cate", cate) - } - if keyword != "" { - qs = qs.Filter("name__icontains", keyword) - } - total, err := qs.Count() - if err != nil { - c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) - return - } - var rows []models.SystemFile - _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) - return - } - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - list = append(list, platformFileToMap(&rows[i])) - } - c.jsonOK(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} - -// GetUserCate GET /platform/usercate -func (c *PlatformFileController) GetUserCate() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - - var cates []models.SystemFilesCategory - _, err = models.Orm.QueryTable(new(models.SystemFilesCategory)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - OrderBy("id"). - All(&cates) - if err != nil { - c.jsonErr(500, 500, "获取用户分类失败: "+err.Error()) - return - } - out := make([]map[string]interface{}, 0, len(cates)) - for i := range cates { - cnt, _ := models.Orm.QueryTable(new(models.SystemFile)). - Filter("tid", tid). - Filter("cate", cates[i].ID). - Filter("delete_time__isnull", true). - Count() - out = append(out, map[string]interface{}{ - "id": cates[i].ID, - "name": cates[i].Name, - "total": cnt, - }) - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type platformCreateCateBody struct { - Name string `json:"name"` - Tuid *uint64 `json:"tuid"` -} - -// CreateFileCate POST /platform/createfilecate -func (c *PlatformFileController) CreateFileCate() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body platformCreateCateBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - name := strings.TrimSpace(body.Name) - if name == "" { - c.jsonErr(400, 400, "分组名称不能为空") - return - } - uid := uint64(claims.UserID) - row := &models.SystemFilesCategory{ - Tid: tid, - Name: name, - Uid: &uid, - Tuid: body.Tuid, - } - id, err := models.Orm.Insert(row) - if err != nil { - c.jsonErr(500, 500, "新建文件分组失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "新建文件分组成功", - "data": map[string]interface{}{"id": uint64(id)}, - } - _ = c.ServeJSON() -} - -type platformRenameCateBody struct { - Name string `json:"name"` -} - -// RenameFileCate POST /platform/renamefilecate/:id -func (c *PlatformFileController) RenameFileCate() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的分组ID") - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body platformRenameCateBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - name := strings.TrimSpace(body.Name) - if name == "" { - c.jsonErr(400, 400, "分组名称不能为空") - return - } - n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"name": name}) - if err != nil { - c.jsonErr(500, 500, "重命名文件分组失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "分组不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "重命名文件分组成功"} - _ = c.ServeJSON() -} - -// DeleteFileCate DELETE /platform/deletefilecate/:id -func (c *PlatformFileController) DeleteFileCate() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的分组ID") - return - } - cnt, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("cate", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Count() - if err != nil { - c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) - return - } - if cnt > 0 { - c.jsonErr(400, 400, fmt.Sprintf("该分组下还有 %d 个文件,请先删除分组内文件!", cnt)) - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "分组不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除文件分组成功"} - _ = c.ServeJSON() -} - -// GetCateFiles GET /platform/catefiles/:id -func (c *PlatformFileController) GetCateFiles() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - cateID, err := strconv.ParseUint(idStr, 10, 64) - if err != nil { - c.jsonErr(400, 400, "无效的分类ID") - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 24) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 24 - } - keyword := strings.TrimSpace(c.GetString("keyword")) - - qs := models.Orm.QueryTable(new(models.SystemFile)). - Filter("tid", tid). - Filter("cate", cateID). - Filter("delete_time__isnull", true) - if keyword != "" { - qs = qs.Filter("name__icontains", keyword) - } - total, err := qs.Count() - if err != nil { - c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) - return - } - var rows []models.SystemFile - _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) - return - } - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - list = append(list, platformFileToMap(&rows[i])) - } - c.jsonOK(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - "categoryId": cateID, - }) -} - -// GetFileByID GET /platform/file/:id -func (c *PlatformFileController) GetFileByID() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - var f models.SystemFile - err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - One(&f) - if err != nil { - c.jsonErr(404, 404, "文件不存在") - return - } - c.jsonOK(platformFileToMap(&f)) -} - -// UploadFile POST /platform/uploadfile -func (c *PlatformFileController) UploadFile() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - if err := c.Ctx.Request.ParseMultipartForm(platformFileUploadMaxBytes); err != nil { - c.jsonErr(400, 400, "解析上传失败: "+err.Error()) - return - } - fh, header, err := c.GetFile("file") - if err != nil || fh == nil { - c.jsonErr(400, 400, "请选择要上传的文件") - return - } - defer fh.Close() - - if header != nil && header.Size > platformFileUploadMaxBytes { - c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", platformFileUploadMaxMB)) - return - } - - ext := platformFileExt(header.Filename) - if ext == "" { - c.jsonErr(400, 400, "无法识别文件扩展名") - return - } - - // 获取存储服务 - storageService, err := services.GetStorageService() - if err != nil { - c.jsonErr(500, 500, "获取存储服务失败: "+err.Error()) - return - } - - // 上传文件 - result, err := storageService.Upload(fh, header) - if err != nil { - c.jsonErr(500, 500, "上传文件失败: "+err.Error()) - return - } - - // 检查文件是否已存在(通过MD5) - var exist models.SystemFile - err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("md5", result.MD5). - Filter("tid", tid). - Filter("delete_time__isnull", true). - One(&exist) - if err == nil { - // 文件已存在,返回已有记录 - c.Data["json"] = map[string]interface{}{ - "code": 201, - "msg": "文件已存在", - "data": map[string]interface{}{ - "url": exist.Src, - "id": exist.ID, - "name": exist.Name, - }, - } - _ = c.ServeJSON() - return - } - - // 获取分类 - cateStr := c.GetString("cate") - var cate uint64 - if cateStr != "" { - cate, _ = strconv.ParseUint(cateStr, 10, 64) - } - - adminID := uint64(claims.UserID) - var tuidPtr *uint64 - if ts := strings.TrimSpace(c.GetString("tuid")); ts != "" { - if v, e := strconv.ParseUint(ts, 10, 64); e == nil { - tuidPtr = &v - } - } - - // 保存文件记录到数据库 - row := &models.SystemFile{ - Tid: tid, - Uid: &adminID, - Tuid: tuidPtr, - Name: header.Filename, - Type: platformDetectFileType(ext), - Cate: cate, - Size: uint64(result.Size), - Src: result.URL, - Uploader: adminID, - Md5: result.MD5, - } - id, err := models.Orm.Insert(row) - if err != nil { - // 数据库插入失败,尝试删除已上传的文件 - _ = storageService.Delete(result.Key) - c.jsonErr(500, 500, "上传失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "上传成功", - "data": map[string]interface{}{ - "url": result.URL, - "id": uint64(id), - "name": header.Filename, - }, - } - _ = c.ServeJSON() -} - -func platformMd5HashFile(path string) (string, error) { - f, err := os.Open(path) - if err != nil { - return "", err - } - defer f.Close() - h := md5.New() - if _, err := io.Copy(h, f); err != nil { - return "", err - } - return hex.EncodeToString(h.Sum(nil)), nil -} - -type platformUpdateFileBody struct { - Name *string `json:"name"` - Cate *uint64 `json:"cate"` -} - -// UpdateFile POST /platform/updatefile/:id -func (c *PlatformFileController) UpdateFile() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body platformUpdateFileBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - up := map[string]interface{}{} - if body.Name != nil { - up["name"] = strings.TrimSpace(*body.Name) - } - if body.Cate != nil { - up["cate"] = *body.Cate - } - if len(up) == 0 { - c.jsonErr(400, 400, "无更新数据") - return - } - now := time.Now() - up["update_time"] = now - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - c.jsonErr(500, 500, "更新失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} - _ = c.ServeJSON() -} - -// DeleteFile DELETE /platform/deletefile/:id -func (c *PlatformFileController) DeleteFile() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -// DeleteFilePermanently DELETE /platform/deletefilepermanently/:id -func (c *PlatformFileController) DeleteFilePermanently() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - var f models.SystemFile - err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - One(&f) - if err != nil { - c.jsonErr(404, 404, "文件不存在") - return - } - platformRemovePhysicalBySrc(f.Src) - _, err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Delete() - if err != nil { - c.jsonErr(500, 500, "永久删除失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "永久删除成功"} - _ = c.ServeJSON() -} - -// MoveFile GET /platform/movefile/:id -func (c *PlatformFileController) MoveFile() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效的文件ID") - return - } - cate, _ := c.GetUint64("cate") - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"cate": cate, "update_time": now}) - if err != nil { - c.jsonErr(500, 500, "移动失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "移动成功"} - _ = c.ServeJSON() -} - -type platformIdsBody struct { - IDs []uint64 `json:"ids"` - Cate *uint64 `json:"cate"` -} - -// BatchDeleteFiles POST /platform/batchdeletefiles -func (c *PlatformFileController) BatchDeleteFiles() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body platformIdsBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if len(body.IDs) == 0 { - c.jsonErr(400, 400, "请选择要删除的文件") - return - } - now := time.Now() - for _, id := range body.IDs { - var f models.SystemFile - e := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", id). - Filter("tid", tid). - One(&f) - if e == nil && f.Src != "" { - platformRemovePhysicalBySrc(f.Src) - } - } - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id__in", body.IDs). - Filter("tid", tid). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "批量删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} - _ = c.ServeJSON() -} - -// BatchDeleteFilesPermanently POST /platform/batchDeleteFilesPermanently -func (c *PlatformFileController) BatchDeleteFilesPermanently() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body platformIdsBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if len(body.IDs) == 0 { - c.jsonErr(400, 400, "请选择要彻底删除的文件") - return - } - var rows []models.SystemFile - _, err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id__in", body.IDs). - Filter("tid", tid). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) - return - } - for i := range rows { - platformRemovePhysicalBySrc(rows[i].Src) - } - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id__in", body.IDs). - Filter("tid", tid). - Delete() - if err != nil { - c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量彻底删除成功"} - _ = c.ServeJSON() -} - -// UploadAvatar POST /platform/uploadavatar(占位) -func (c *PlatformFileController) UploadAvatar() { - c.Data["json"] = map[string]interface{}{"code": 501, "msg": "上传头像暂未实现"} - _ = c.ServeJSON() -} - -// UpdateAvatar POST /platform/uploadavatar/:id(占位) -func (c *PlatformFileController) UpdateAvatar() { - c.Data["json"] = map[string]interface{}{"code": 501, "msg": "更新头像暂未实现"} - _ = c.ServeJSON() -} - -// BatchMoveFiles POST /platform/batchMoveFiles -func (c *PlatformFileController) BatchMoveFiles() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var body platformIdsBody - if err := json.Unmarshal(raw, &body); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if len(body.IDs) == 0 { - c.jsonErr(400, 400, "请选择要移动的文件") - return - } - if body.Cate == nil { - c.jsonErr(400, 400, "缺少目标分类") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("id__in", body.IDs). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"cate": *body.Cate, "update_time": now}) - if err != nil { - c.jsonErr(500, 500, "批量移动失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "文件不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量移动成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformFileController 平台端文件管理(yz_system_files / yz_system_files_category) +type PlatformFileController struct { + beego.Controller +} + +const platformFileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 +const platformFileUploadMaxBytes = platformFileUploadMaxMB * 1024 * 1024 + +var platformFileTypeByCategory = map[string]uint8{ + "image": 1, + "document": 2, + "video": 3, + "audio": 4, + "appsupgrade": 2, +} + +var platformAllowedExtByCategory = map[string][]string{ + "image": {"jpg", "jpeg", "png", "gif", "bmp", "webp"}, + "document": {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"}, + "video": {"mp4", "webm", "mov"}, + "audio": {"mp3", "wav", "ogg"}, + // 安装包 / 软件升级(上传时 cate 选 appsupgrade 分类即可,扩展名在此放行) + "appsupgrade": {"zip", "exe", "dmg", "msi", "msix", "apk", "deb", "rpm", "7z", "tar", "gz", "pkg"}, +} + +func (c *PlatformFileController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformFileController) effectiveTid(claims *jwtutil.Claims) uint64 { + _ = c.ParseForm(1 << 20) + if tid, err := c.GetUint64("tid"); err == nil && tid > 0 { + return tid + } + if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" { + if v, e := strconv.ParseUint(h, 10, 64); e == nil { + return v + } + } + if claims != nil && claims.TenantId > 0 { + return uint64(claims.TenantId) + } + return 0 +} + +func (c *PlatformFileController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *PlatformFileController) jsonOK(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +func platformDetectFileType(ext string) uint8 { + ext = strings.ToLower(strings.TrimPrefix(ext, ".")) + for cat, exts := range platformAllowedExtByCategory { + for _, e := range exts { + if e == ext { + if t, ok := platformFileTypeByCategory[cat]; ok { + return t + } + return 2 + } + } + } + return 2 +} + +func platformFileExt(name string) string { + name = strings.TrimSpace(name) + if i := strings.LastIndex(name, "."); i >= 0 && i < len(name)-1 { + return strings.ToLower(name[i+1:]) + } + return "" +} + +func platformFileToMap(f *models.SystemFile) map[string]interface{} { + ct := f.CreateTime.Format("2006-01-02 15:04:05") + m := map[string]interface{}{ + "id": f.ID, + "tid": f.Tid, + "name": f.Name, + "type": f.Type, + "cate": f.Cate, + "size": f.Size, + "src": f.Src, + "uploader": f.Uploader, + "md5": f.Md5, + "create_time": ct, + "createTime": ct, + "groupId": f.Cate, + "url": f.Src, + } + if f.Uid != nil { + m["uid"] = *f.Uid + } + if f.Tuid != nil { + m["tuid"] = *f.Tuid + } + return m +} + +func platformRemovePhysicalBySrc(webSrc string) { + webSrc = strings.TrimSpace(webSrc) + if webSrc == "" { + return + } + webSrc = strings.TrimPrefix(webSrc, "/") + _ = os.Remove(webSrc) +} + +// GetAllFiles GET /platform/allfiles +func (c *PlatformFileController) GetAllFiles() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + cate, _ := c.GetUint64("cate") + keyword := strings.TrimSpace(c.GetString("keyword")) + + qs := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("delete_time__isnull", true) + if cate > 0 { + qs = qs.Filter("cate", cate) + } + if keyword != "" { + qs = qs.Filter("name__icontains", keyword) + } + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) + return + } + var rows []models.SystemFile + _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) + return + } + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, platformFileToMap(&rows[i])) + } + c.jsonOK(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// GetUserCate GET /platform/usercate +func (c *PlatformFileController) GetUserCate() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + + var cates []models.SystemFilesCategory + _, err = models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + OrderBy("id"). + All(&cates) + if err != nil { + c.jsonErr(500, 500, "获取用户分类失败: "+err.Error()) + return + } + out := make([]map[string]interface{}, 0, len(cates)) + for i := range cates { + cnt, _ := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("cate", cates[i].ID). + Filter("delete_time__isnull", true). + Count() + out = append(out, map[string]interface{}{ + "id": cates[i].ID, + "name": cates[i].Name, + "total": cnt, + }) + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type platformCreateCateBody struct { + Name string `json:"name"` + Tuid *uint64 `json:"tuid"` +} + +// CreateFileCate POST /platform/createfilecate +func (c *PlatformFileController) CreateFileCate() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body platformCreateCateBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + name := strings.TrimSpace(body.Name) + if name == "" { + c.jsonErr(400, 400, "分组名称不能为空") + return + } + uid := uint64(claims.UserID) + row := &models.SystemFilesCategory{ + Tid: tid, + Name: name, + Uid: &uid, + Tuid: body.Tuid, + } + id, err := models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "新建文件分组失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "新建文件分组成功", + "data": map[string]interface{}{"id": uint64(id)}, + } + _ = c.ServeJSON() +} + +type platformRenameCateBody struct { + Name string `json:"name"` +} + +// RenameFileCate POST /platform/renamefilecate/:id +func (c *PlatformFileController) RenameFileCate() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的分组ID") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body platformRenameCateBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + name := strings.TrimSpace(body.Name) + if name == "" { + c.jsonErr(400, 400, "分组名称不能为空") + return + } + n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"name": name}) + if err != nil { + c.jsonErr(500, 500, "重命名文件分组失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "分组不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "重命名文件分组成功"} + _ = c.ServeJSON() +} + +// DeleteFileCate DELETE /platform/deletefilecate/:id +func (c *PlatformFileController) DeleteFileCate() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的分组ID") + return + } + cnt, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("cate", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Count() + if err != nil { + c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) + return + } + if cnt > 0 { + c.jsonErr(400, 400, fmt.Sprintf("该分组下还有 %d 个文件,请先删除分组内文件!", cnt)) + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "分组不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除文件分组成功"} + _ = c.ServeJSON() +} + +// GetCateFiles GET /platform/catefiles/:id +func (c *PlatformFileController) GetCateFiles() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + cateID, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.jsonErr(400, 400, "无效的分类ID") + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 24) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 24 + } + keyword := strings.TrimSpace(c.GetString("keyword")) + + qs := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("cate", cateID). + Filter("delete_time__isnull", true) + if keyword != "" { + qs = qs.Filter("name__icontains", keyword) + } + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) + return + } + var rows []models.SystemFile + _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) + return + } + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, platformFileToMap(&rows[i])) + } + c.jsonOK(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + "categoryId": cateID, + }) +} + +// GetFileByID GET /platform/file/:id +func (c *PlatformFileController) GetFileByID() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + var f models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&f) + if err != nil { + c.jsonErr(404, 404, "文件不存在") + return + } + c.jsonOK(platformFileToMap(&f)) +} + +// UploadFile POST /platform/uploadfile +func (c *PlatformFileController) UploadFile() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + if err := c.Ctx.Request.ParseMultipartForm(platformFileUploadMaxBytes); err != nil { + c.jsonErr(400, 400, "解析上传失败: "+err.Error()) + return + } + fh, header, err := c.GetFile("file") + if err != nil || fh == nil { + c.jsonErr(400, 400, "请选择要上传的文件") + return + } + defer fh.Close() + + if header != nil && header.Size > platformFileUploadMaxBytes { + c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", platformFileUploadMaxMB)) + return + } + + ext := platformFileExt(header.Filename) + if ext == "" { + c.jsonErr(400, 400, "无法识别文件扩展名") + return + } + + // 获取存储服务 + storageService, err := services.GetStorageService() + if err != nil { + c.jsonErr(500, 500, "获取存储服务失败: "+err.Error()) + return + } + + // 上传文件 + result, err := storageService.Upload(fh, header) + if err != nil { + c.jsonErr(500, 500, "上传文件失败: "+err.Error()) + return + } + + // 检查文件是否已存在(通过MD5) + var exist models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("md5", result.MD5). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&exist) + if err == nil { + // 文件已存在,返回已有记录 + c.Data["json"] = map[string]interface{}{ + "code": 201, + "msg": "文件已存在", + "data": map[string]interface{}{ + "url": exist.Src, + "id": exist.ID, + "name": exist.Name, + }, + } + _ = c.ServeJSON() + return + } + + // 获取分类 + cateStr := c.GetString("cate") + var cate uint64 + if cateStr != "" { + cate, _ = strconv.ParseUint(cateStr, 10, 64) + } + + adminID := uint64(claims.UserID) + var tuidPtr *uint64 + if ts := strings.TrimSpace(c.GetString("tuid")); ts != "" { + if v, e := strconv.ParseUint(ts, 10, 64); e == nil { + tuidPtr = &v + } + } + + // 保存文件记录到数据库 + row := &models.SystemFile{ + Tid: tid, + Uid: &adminID, + Tuid: tuidPtr, + Name: header.Filename, + Type: platformDetectFileType(ext), + Cate: cate, + Size: uint64(result.Size), + Src: result.URL, + Uploader: adminID, + Md5: result.MD5, + } + id, err := models.Orm.Insert(row) + if err != nil { + // 数据库插入失败,尝试删除已上传的文件 + _ = storageService.Delete(result.Key) + c.jsonErr(500, 500, "上传失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "上传成功", + "data": map[string]interface{}{ + "url": result.URL, + "id": uint64(id), + "name": header.Filename, + }, + } + _ = c.ServeJSON() +} + +func platformMd5HashFile(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + h := md5.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + +type platformUpdateFileBody struct { + Name *string `json:"name"` + Cate *uint64 `json:"cate"` +} + +// UpdateFile POST /platform/updatefile/:id +func (c *PlatformFileController) UpdateFile() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body platformUpdateFileBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + up := map[string]interface{}{} + if body.Name != nil { + up["name"] = strings.TrimSpace(*body.Name) + } + if body.Cate != nil { + up["cate"] = *body.Cate + } + if len(up) == 0 { + c.jsonErr(400, 400, "无更新数据") + return + } + now := time.Now() + up["update_time"] = now + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "更新失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +// DeleteFile DELETE /platform/deletefile/:id +func (c *PlatformFileController) DeleteFile() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// DeleteFilePermanently DELETE /platform/deletefilepermanently/:id +func (c *PlatformFileController) DeleteFilePermanently() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + var f models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + One(&f) + if err != nil { + c.jsonErr(404, 404, "文件不存在") + return + } + platformRemovePhysicalBySrc(f.Src) + _, err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Delete() + if err != nil { + c.jsonErr(500, 500, "永久删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "永久删除成功"} + _ = c.ServeJSON() +} + +// MoveFile GET /platform/movefile/:id +func (c *PlatformFileController) MoveFile() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + cate, _ := c.GetUint64("cate") + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"cate": cate, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "移动失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "移动成功"} + _ = c.ServeJSON() +} + +type platformIdsBody struct { + IDs []uint64 `json:"ids"` + Cate *uint64 `json:"cate"` +} + +// BatchDeleteFiles POST /platform/batchdeletefiles +func (c *PlatformFileController) BatchDeleteFiles() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body platformIdsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要删除的文件") + return + } + now := time.Now() + for _, id := range body.IDs { + var f models.SystemFile + e := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + One(&f) + if e == nil && f.Src != "" { + platformRemovePhysicalBySrc(f.Src) + } + } + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} + _ = c.ServeJSON() +} + +// BatchDeleteFilesPermanently POST /platform/batchDeleteFilesPermanently +func (c *PlatformFileController) BatchDeleteFilesPermanently() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body platformIdsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要彻底删除的文件") + return + } + var rows []models.SystemFile + _, err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) + return + } + for i := range rows { + platformRemovePhysicalBySrc(rows[i].Src) + } + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Delete() + if err != nil { + c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量彻底删除成功"} + _ = c.ServeJSON() +} + +// UploadAvatar POST /platform/uploadavatar(占位) +func (c *PlatformFileController) UploadAvatar() { + c.Data["json"] = map[string]interface{}{"code": 501, "msg": "上传头像暂未实现"} + _ = c.ServeJSON() +} + +// UpdateAvatar POST /platform/uploadavatar/:id(占位) +func (c *PlatformFileController) UpdateAvatar() { + c.Data["json"] = map[string]interface{}{"code": 501, "msg": "更新头像暂未实现"} + _ = c.ServeJSON() +} + +// BatchMoveFiles POST /platform/batchMoveFiles +func (c *PlatformFileController) BatchMoveFiles() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body platformIdsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要移动的文件") + return + } + if body.Cate == nil { + c.jsonErr(400, 400, "缺少目标分类") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"cate": *body.Cate, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量移动失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量移动成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_home.go b/go/controllers/platform_home.go index ebe9a3d..55e8978 100644 --- a/go/controllers/platform_home.go +++ b/go/controllers/platform_home.go @@ -1,258 +1,258 @@ -package controllers - -import ( - "fmt" - "strconv" - "strings" - "time" - - "server/models" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformHomeController 平台首页统计(需登录) -type PlatformHomeController struct { - beego.Controller -} - -func cellToDateKey(v interface{}) string { - if v == nil { - return "" - } - switch x := v.(type) { - case []byte: - s := strings.TrimSpace(string(x)) - if len(s) >= 10 { - return s[:10] - } - return s - case string: - s := strings.TrimSpace(x) - if len(s) >= 10 { - return s[:10] - } - return s - case time.Time: - if x.IsZero() { - return "" - } - return x.In(time.Local).Format("2006-01-02") - default: - s := strings.TrimSpace(fmt.Sprint(x)) - if len(s) >= 10 { - return s[:10] - } - return s - } -} - -func cellToInt64(v interface{}) int64 { - if v == nil { - return 0 - } - switch x := v.(type) { - case []byte: - n, _ := strconv.ParseInt(strings.TrimSpace(string(x)), 10, 64) - return n - case int64: - return x - case int32: - return int64(x) - case int: - return int64(x) - default: - n, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(x)), 10, 64) - return n - } -} - -func queryExtractedCountByDay(table string, start, endExclusive time.Time) (map[string]int64, error) { - // 不按 delete_time 过滤:部分库未删除行存 0000-00-00 或非 NULL,会导致统计全空。 - // Raw + QueryRows 对别名映射不稳定,改用 Values 解析 d/c。 - sql := fmt.Sprintf(` -SELECT DATE(extracted_time) AS d, COUNT(*) AS c -FROM %s -WHERE is_extracted IN (1, 2) - AND extracted_time IS NOT NULL - AND extracted_time >= ? - AND extracted_time < ? -GROUP BY DATE(extracted_time) -ORDER BY d -`, table) - var maps []orm.Params - _, err := models.Orm.Raw(sql, start, endExclusive).Values(&maps) - if err != nil { - return nil, err - } - out := make(map[string]int64, len(maps)) - for _, m := range maps { - var dk, ck interface{} - for _, k := range []string{"d", "D"} { - if v, ok := m[k]; ok { - dk = v - break - } - } - for _, k := range []string{"c", "C"} { - if v, ok := m[k]; ok { - ck = v - break - } - } - key := cellToDateKey(dk) - if key == "" { - continue - } - out[key] = cellToInt64(ck) - } - return out, nil -} - -// AccountPoolDailyExtract GET /platform/home/accountPoolDailyExtract?days=14 -// 按天统计各号池「已提取」数量,依据 extracted_time 落在当天的记录。 -func (c *PlatformHomeController) AccountPoolDailyExtract() { - if _, err := requirePlatformAuth(&c.Controller); err != nil { - poolJSONErr(&c.Controller, 401, 401, err.Error()) - return - } - n, _ := c.GetInt("days", 14) - if n < 1 { - n = 1 - } - if n > 90 { - n = 90 - } - - now := time.Now().In(time.Local) - today0 := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) - firstDay := today0.AddDate(0, 0, -(n - 1)) - endExclusive := today0.AddDate(0, 0, 1) - - cursorTable := (&models.PlatformAccountPoolCursor{}).TableName() - windsurfTable := (&models.PlatformAccountPoolWindsurf{}).TableName() - kiroTable := (&models.PlatformAccountPoolKiro{}).TableName() - - mCursor, err := queryExtractedCountByDay(cursorTable, firstDay, endExclusive) - if err != nil { - poolJSONErr(&c.Controller, 500, 500, "统计 Cursor 失败: "+err.Error()) - return - } - mWindsurf, err := queryExtractedCountByDay(windsurfTable, firstDay, endExclusive) - if err != nil { - poolJSONErr(&c.Controller, 500, 500, "统计 Windsurf 失败: "+err.Error()) - return - } - mKiro, err := queryExtractedCountByDay(kiroTable, firstDay, endExclusive) - if err != nil { - poolJSONErr(&c.Controller, 500, 500, "统计 Kiro 失败: "+err.Error()) - return - } - - dayKeys := make([]string, 0, n) - dayLabels := make([]string, 0, n) - cursorVals := make([]int64, 0, n) - windsurfVals := make([]int64, 0, n) - kiroVals := make([]int64, 0, n) - - for i := 0; i < n; i++ { - d := firstDay.AddDate(0, 0, i) - key := d.Format("2006-01-02") - dayKeys = append(dayKeys, key) - dayLabels = append(dayLabels, d.Format("01/02")) - cursorVals = append(cursorVals, mCursor[key]) - windsurfVals = append(windsurfVals, mWindsurf[key]) - kiroVals = append(kiroVals, mKiro[key]) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "days": dayLabels, - "dayKeys": dayKeys, - "cursor": int64SliceToInt(cursorVals), - "windsurf": int64SliceToInt(windsurfVals), - "kiro": int64SliceToInt(kiroVals), - "daysLength": n, - }, - } - _ = c.ServeJSON() -} - -func int64SliceToInt(in []int64) []int { - out := make([]int, len(in)) - for i, v := range in { - out[i] = int(v) - } - return out -} - -func countPoolInventory(mi interface{}, soldOnly bool) (int64, error) { - qs := models.Orm.QueryTable(mi).Filter("delete_time__isnull", true) - if soldOnly { - qs = qs.Filter("is_extracted__in", 1, 2) - } - n, err := qs.Count() - return n, err -} - -// AccountPoolInventoryTotals GET /platform/home/accountPoolInventoryTotals -// 各号池:账号总数(未删)、已售卖(is_extracted 为 1 或 2) -func (c *PlatformHomeController) AccountPoolInventoryTotals() { - if _, err := requirePlatformAuth(&c.Controller); err != nil { - poolJSONErr(&c.Controller, 401, 401, err.Error()) - return - } - - type invModule struct { - Key string `json:"key"` - Label string `json:"label"` - Total int64 `json:"total"` - Sold int64 `json:"sold"` - } - - modules := []invModule{ - {Key: "cursor", Label: "Cursor"}, - {Key: "krio", Label: "Kiro"}, - {Key: "windsurf", Label: "Windsurf"}, - {Key: "codex", Label: "Codex"}, - } - modelsList := []interface{}{ - new(models.PlatformAccountPoolCursor), - new(models.PlatformAccountPoolKiro), - new(models.PlatformAccountPoolWindsurf), - new(models.PlatformAccountPoolCodex), - } - - - var grandTotal, grandSold int64 - for i := range modules { - tot, err := countPoolInventory(modelsList[i], false) - if err != nil { - poolJSONErr(&c.Controller, 500, 500, "统计失败: "+err.Error()) - return - } - sd, err := countPoolInventory(modelsList[i], true) - if err != nil { - poolJSONErr(&c.Controller, 500, 500, "统计失败: "+err.Error()) - return - } - modules[i].Total = tot - modules[i].Sold = sd - grandTotal += tot - grandSold += sd - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "modules": modules, - "grandTotal": grandTotal, - "grandSold": grandSold, - }, - } - _ = c.ServeJSON() -} +package controllers + +import ( + "fmt" + "strconv" + "strings" + "time" + + "server/models" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformHomeController 平台首页统计(需登录) +type PlatformHomeController struct { + beego.Controller +} + +func cellToDateKey(v interface{}) string { + if v == nil { + return "" + } + switch x := v.(type) { + case []byte: + s := strings.TrimSpace(string(x)) + if len(s) >= 10 { + return s[:10] + } + return s + case string: + s := strings.TrimSpace(x) + if len(s) >= 10 { + return s[:10] + } + return s + case time.Time: + if x.IsZero() { + return "" + } + return x.In(time.Local).Format("2006-01-02") + default: + s := strings.TrimSpace(fmt.Sprint(x)) + if len(s) >= 10 { + return s[:10] + } + return s + } +} + +func cellToInt64(v interface{}) int64 { + if v == nil { + return 0 + } + switch x := v.(type) { + case []byte: + n, _ := strconv.ParseInt(strings.TrimSpace(string(x)), 10, 64) + return n + case int64: + return x + case int32: + return int64(x) + case int: + return int64(x) + default: + n, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(x)), 10, 64) + return n + } +} + +func queryExtractedCountByDay(table string, start, endExclusive time.Time) (map[string]int64, error) { + // 不按 delete_time 过滤:部分库未删除行存 0000-00-00 或非 NULL,会导致统计全空。 + // Raw + QueryRows 对别名映射不稳定,改用 Values 解析 d/c。 + sql := fmt.Sprintf(` +SELECT DATE(extracted_time) AS d, COUNT(*) AS c +FROM %s +WHERE is_extracted IN (1, 2) + AND extracted_time IS NOT NULL + AND extracted_time >= ? + AND extracted_time < ? +GROUP BY DATE(extracted_time) +ORDER BY d +`, table) + var maps []orm.Params + _, err := models.Orm.Raw(sql, start, endExclusive).Values(&maps) + if err != nil { + return nil, err + } + out := make(map[string]int64, len(maps)) + for _, m := range maps { + var dk, ck interface{} + for _, k := range []string{"d", "D"} { + if v, ok := m[k]; ok { + dk = v + break + } + } + for _, k := range []string{"c", "C"} { + if v, ok := m[k]; ok { + ck = v + break + } + } + key := cellToDateKey(dk) + if key == "" { + continue + } + out[key] = cellToInt64(ck) + } + return out, nil +} + +// AccountPoolDailyExtract GET /platform/home/accountPoolDailyExtract?days=14 +// 按天统计各号池「已提取」数量,依据 extracted_time 落在当天的记录。 +func (c *PlatformHomeController) AccountPoolDailyExtract() { + if _, err := requirePlatformAuth(&c.Controller); err != nil { + poolJSONErr(&c.Controller, 401, 401, err.Error()) + return + } + n, _ := c.GetInt("days", 14) + if n < 1 { + n = 1 + } + if n > 90 { + n = 90 + } + + now := time.Now().In(time.Local) + today0 := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + firstDay := today0.AddDate(0, 0, -(n - 1)) + endExclusive := today0.AddDate(0, 0, 1) + + cursorTable := (&models.PlatformAccountPoolCursor{}).TableName() + windsurfTable := (&models.PlatformAccountPoolWindsurf{}).TableName() + kiroTable := (&models.PlatformAccountPoolKiro{}).TableName() + + mCursor, err := queryExtractedCountByDay(cursorTable, firstDay, endExclusive) + if err != nil { + poolJSONErr(&c.Controller, 500, 500, "统计 Cursor 失败: "+err.Error()) + return + } + mWindsurf, err := queryExtractedCountByDay(windsurfTable, firstDay, endExclusive) + if err != nil { + poolJSONErr(&c.Controller, 500, 500, "统计 Windsurf 失败: "+err.Error()) + return + } + mKiro, err := queryExtractedCountByDay(kiroTable, firstDay, endExclusive) + if err != nil { + poolJSONErr(&c.Controller, 500, 500, "统计 Kiro 失败: "+err.Error()) + return + } + + dayKeys := make([]string, 0, n) + dayLabels := make([]string, 0, n) + cursorVals := make([]int64, 0, n) + windsurfVals := make([]int64, 0, n) + kiroVals := make([]int64, 0, n) + + for i := 0; i < n; i++ { + d := firstDay.AddDate(0, 0, i) + key := d.Format("2006-01-02") + dayKeys = append(dayKeys, key) + dayLabels = append(dayLabels, d.Format("01/02")) + cursorVals = append(cursorVals, mCursor[key]) + windsurfVals = append(windsurfVals, mWindsurf[key]) + kiroVals = append(kiroVals, mKiro[key]) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "days": dayLabels, + "dayKeys": dayKeys, + "cursor": int64SliceToInt(cursorVals), + "windsurf": int64SliceToInt(windsurfVals), + "kiro": int64SliceToInt(kiroVals), + "daysLength": n, + }, + } + _ = c.ServeJSON() +} + +func int64SliceToInt(in []int64) []int { + out := make([]int, len(in)) + for i, v := range in { + out[i] = int(v) + } + return out +} + +func countPoolInventory(mi interface{}, soldOnly bool) (int64, error) { + qs := models.Orm.QueryTable(mi).Filter("delete_time__isnull", true) + if soldOnly { + qs = qs.Filter("is_extracted__in", 1, 2) + } + n, err := qs.Count() + return n, err +} + +// AccountPoolInventoryTotals GET /platform/home/accountPoolInventoryTotals +// 各号池:账号总数(未删)、已售卖(is_extracted 为 1 或 2) +func (c *PlatformHomeController) AccountPoolInventoryTotals() { + if _, err := requirePlatformAuth(&c.Controller); err != nil { + poolJSONErr(&c.Controller, 401, 401, err.Error()) + return + } + + type invModule struct { + Key string `json:"key"` + Label string `json:"label"` + Total int64 `json:"total"` + Sold int64 `json:"sold"` + } + + modules := []invModule{ + {Key: "cursor", Label: "Cursor"}, + {Key: "krio", Label: "Kiro"}, + {Key: "windsurf", Label: "Windsurf"}, + {Key: "codex", Label: "Codex"}, + } + modelsList := []interface{}{ + new(models.PlatformAccountPoolCursor), + new(models.PlatformAccountPoolKiro), + new(models.PlatformAccountPoolWindsurf), + new(models.PlatformAccountPoolCodex), + } + + + var grandTotal, grandSold int64 + for i := range modules { + tot, err := countPoolInventory(modelsList[i], false) + if err != nil { + poolJSONErr(&c.Controller, 500, 500, "统计失败: "+err.Error()) + return + } + sd, err := countPoolInventory(modelsList[i], true) + if err != nil { + poolJSONErr(&c.Controller, 500, 500, "统计失败: "+err.Error()) + return + } + modules[i].Total = tot + modules[i].Sold = sd + grandTotal += tot + grandSold += sd + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "modules": modules, + "grandTotal": grandTotal, + "grandSold": grandSold, + }, + } + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_login_verify.go b/go/controllers/platform_login_verify.go index f016080..7c1781d 100644 --- a/go/controllers/platform_login_verify.go +++ b/go/controllers/platform_login_verify.go @@ -1,105 +1,105 @@ -package controllers - -import ( - "encoding/json" - "io" - "strings" - - "server/models" - - beego "github.com/beego/beego/v2/server/web" -) - -type PlatformLoginVerifyController struct { - beego.Controller -} - -type loginVerifyPayload struct { - OpenVerifyEnabled *int8 `json:"openVerify_enabled"` - VerifyType string `json:"use_geetest"` - Geetest3ID *string `json:"geetest3_id"` - Geetest3Key *string `json:"geetest3_key"` - Geetest4ID *string `json:"geetest4_id"` - Geetest4Key *string `json:"geetest4_key"` -} - -func normalizeVerifyType(v string) string { - switch strings.TrimSpace(v) { - case "sms", "geetest", "email", "captcha": - return strings.TrimSpace(v) - default: - return "captcha" - } -} - -// GetLoginVerifyInfos 获取登录验证配置 -// GET /platform/loginVerifyInfos -func (c *PlatformLoginVerifyController) GetLoginVerifyInfos() { - cfg, err := models.GetPlatformLoginVerify() - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取配置失败"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "openVerify_enabled": cfg.OpenVerifyEnabled, - "use_geetest": cfg.VerifyType, - "geetest3_id": cfg.Geetest3ID, - "geetest3_key": cfg.Geetest3Key, - "geetest4_id": cfg.Geetest4ID, - "geetest4_key": cfg.Geetest4Key, - }, - } - _ = c.ServeJSON() -} - -// SaveLoginVerifyInfos 保存登录验证配置 -// POST /platform/saveloginVerifyInfos -func (c *PlatformLoginVerifyController) SaveLoginVerifyInfos() { - var p loginVerifyPayload - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - - verifyType := normalizeVerifyType(p.VerifyType) - openVerifyEnabled := int8(1) - if p.OpenVerifyEnabled != nil { - openVerifyEnabled = *p.OpenVerifyEnabled - } - if verifyType == "geetest" { - if p.Geetest4ID == nil || strings.TrimSpace(*p.Geetest4ID) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "geetest4_id 不能为空"} - _ = c.ServeJSON() - return - } - if p.Geetest4Key == nil || strings.TrimSpace(*p.Geetest4Key) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "geetest4_key 不能为空"} - _ = c.ServeJSON() - return - } - } - - err := models.SavePlatformLoginVerify(&models.PlatformLoginVerify{ - OpenVerifyEnabled: openVerifyEnabled, - VerifyType: verifyType, - Geetest3ID: p.Geetest3ID, - Geetest3Key: p.Geetest3Key, - Geetest4ID: p.Geetest4ID, - Geetest4Key: p.Geetest4Key, - }) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - +package controllers + +import ( + "encoding/json" + "io" + "strings" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformLoginVerifyController struct { + beego.Controller +} + +type loginVerifyPayload struct { + OpenVerifyEnabled *int8 `json:"openVerify_enabled"` + VerifyType string `json:"use_geetest"` + Geetest3ID *string `json:"geetest3_id"` + Geetest3Key *string `json:"geetest3_key"` + Geetest4ID *string `json:"geetest4_id"` + Geetest4Key *string `json:"geetest4_key"` +} + +func normalizeVerifyType(v string) string { + switch strings.TrimSpace(v) { + case "sms", "geetest", "email", "captcha": + return strings.TrimSpace(v) + default: + return "captcha" + } +} + +// GetLoginVerifyInfos 获取登录验证配置 +// GET /platform/loginVerifyInfos +func (c *PlatformLoginVerifyController) GetLoginVerifyInfos() { + cfg, err := models.GetPlatformLoginVerify() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取配置失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "openVerify_enabled": cfg.OpenVerifyEnabled, + "use_geetest": cfg.VerifyType, + "geetest3_id": cfg.Geetest3ID, + "geetest3_key": cfg.Geetest3Key, + "geetest4_id": cfg.Geetest4ID, + "geetest4_key": cfg.Geetest4Key, + }, + } + _ = c.ServeJSON() +} + +// SaveLoginVerifyInfos 保存登录验证配置 +// POST /platform/saveloginVerifyInfos +func (c *PlatformLoginVerifyController) SaveLoginVerifyInfos() { + var p loginVerifyPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + verifyType := normalizeVerifyType(p.VerifyType) + openVerifyEnabled := int8(1) + if p.OpenVerifyEnabled != nil { + openVerifyEnabled = *p.OpenVerifyEnabled + } + if verifyType == "geetest" { + if p.Geetest4ID == nil || strings.TrimSpace(*p.Geetest4ID) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "geetest4_id 不能为空"} + _ = c.ServeJSON() + return + } + if p.Geetest4Key == nil || strings.TrimSpace(*p.Geetest4Key) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "geetest4_key 不能为空"} + _ = c.ServeJSON() + return + } + } + + err := models.SavePlatformLoginVerify(&models.PlatformLoginVerify{ + OpenVerifyEnabled: openVerifyEnabled, + VerifyType: verifyType, + Geetest3ID: p.Geetest3ID, + Geetest3Key: p.Geetest3Key, + Geetest4ID: p.Geetest4ID, + Geetest4Key: p.Geetest4Key, + }) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + diff --git a/go/controllers/platform_modules.go b/go/controllers/platform_modules.go index ff63919..6b5c684 100644 --- a/go/controllers/platform_modules.go +++ b/go/controllers/platform_modules.go @@ -1,400 +1,400 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformModulesController 模块管理(yz_system_modules) -type PlatformModulesController struct { - beego.Controller -} - -func (c *PlatformModulesController) modulesClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - // 语义更正: - // - /platform/* 只能 platform 访问 - // - /backend/* 只能 backend 访问 - // 兼容:历史 token 可能缺少 user_type(按 user 处理),此时都拒绝访问以避免越权。 - path := strings.ToLower(c.Ctx.Request.URL.Path) - if strings.HasPrefix(path, "/platform/") { - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - } else if strings.HasPrefix(path, "/backend/") { - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - } - return claims, nil -} - -func (c *PlatformModulesController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// GetList GET /platform/modules/list -func (c *PlatformModulesController) GetList() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - var rows []models.SystemModules - _, err := models.Orm.QueryTable(new(models.SystemModules)). - Filter("delete_time__isnull", true). - OrderBy("sort", "id"). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败:"+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "获取成功", - "data": map[string]interface{}{ - "list": rows, - "total": len(rows), - }, - } - _ = c.ServeJSON() -} - -// GetTenantList GET /platform/modules/getTenantList -// 兼容旧接口命名:返回当前账号可见的模块。当前实现:返回 status=1 且 is_show=1 的全部模块。 -func (c *PlatformModulesController) GetTenantList() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - var rows []models.SystemModules - _, err := models.Orm.QueryTable(new(models.SystemModules)). - Filter("delete_time__isnull", true). - Filter("status", 1). - Filter("is_show", 1). - OrderBy("sort", "id"). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败:"+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "获取成功", - "data": map[string]interface{}{ - "list": rows, - "total": len(rows), - }, - } - _ = c.ServeJSON() -} - -// GetDetail GET /platform/modules/:id -func (c *PlatformModulesController) GetDetail() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "参数错误") - return - } - var row models.SystemModules - err = models.Orm.QueryTable(new(models.SystemModules)). - Filter("id", id). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.jsonErr(404, 404, "模块不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "获取成功", "data": row} - _ = c.ServeJSON() -} - -type modulePayload struct { - Mid *uint64 `json:"mid"` - Name string `json:"name"` - Code string `json:"code"` - Path string `json:"path"` - Icon string `json:"icon"` - Description string `json:"description"` - Type int `json:"type"` - Sort int `json:"sort"` - Status int8 `json:"status"` - IsShow int8 `json:"is_show"` -} - -// Add POST /platform/modules -func (c *PlatformModulesController) Add() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p modulePayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - p.Name = strings.TrimSpace(p.Name) - p.Code = strings.TrimSpace(p.Code) - if p.Name == "" || p.Code == "" { - c.jsonErr(400, 400, "模块名称和编码不能为空") - return - } - // code 唯一(排除软删) - cnt, _ := models.Orm.QueryTable(new(models.SystemModules)). - Filter("code", p.Code). - Filter("delete_time__isnull", true). - Count() - if cnt > 0 { - c.jsonErr(400, 400, "模块编码已存在") - return - } - now := time.Now() - row := &models.SystemModules{ - Mid: p.Mid, - Name: p.Name, - Code: p.Code, - Path: strings.TrimSpace(p.Path), - Icon: strings.TrimSpace(p.Icon), - Description: strings.TrimSpace(p.Description), - Type: p.Type, - Sort: p.Sort, - Status: p.Status, - IsShow: p.IsShow, - CreateTime: &now, - UpdateTime: &now, - } - id, err := models.Orm.Insert(row) - if err != nil { - c.jsonErr(500, 500, "添加失败:"+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "添加成功", "data": map[string]interface{}{"id": uint64(id)}} - _ = c.ServeJSON() -} - -// Edit PUT /platform/modules/:id -func (c *PlatformModulesController) Edit() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "参数错误") - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p modulePayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - p.Name = strings.TrimSpace(p.Name) - p.Code = strings.TrimSpace(p.Code) - if p.Name == "" || p.Code == "" { - c.jsonErr(400, 400, "模块名称和编码不能为空") - return - } - // code 唯一(排除自身与软删) - cnt, _ := models.Orm.QueryTable(new(models.SystemModules)). - Filter("code", p.Code). - Filter("id__ne", id). - Filter("delete_time__isnull", true). - Count() - if cnt > 0 { - c.jsonErr(400, 400, "模块编码已存在") - return - } - now := time.Now() - up := map[string]interface{}{ - "mid": p.Mid, - "name": p.Name, - "code": p.Code, - "path": strings.TrimSpace(p.Path), - "icon": strings.TrimSpace(p.Icon), - "description": strings.TrimSpace(p.Description), - "type": p.Type, - "sort": p.Sort, - "status": p.Status, - "is_show": p.IsShow, - "update_time": now, - } - n, err := models.Orm.QueryTable(new(models.SystemModules)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - c.jsonErr(500, 500, "编辑失败:"+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "模块不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "编辑成功"} - _ = c.ServeJSON() -} - -// Delete DELETE /platform/modules/:id(软删) -func (c *PlatformModulesController) Delete() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "参数错误") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemModules)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除失败:"+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "模块不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -// BatchDelete POST /platform/modules/batchDelete body:{ids:[]} -func (c *PlatformModulesController) BatchDelete() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - IDs []uint64 `json:"ids"` - } - if err := json.Unmarshal(raw, &p); err != nil || len(p.IDs) == 0 { - c.jsonErr(400, 400, "请选择要删除的模块") - return - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemModules)). - Filter("id__in", p.IDs). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now, "update_time": now}) - if err != nil { - c.jsonErr(500, 500, "批量删除失败:"+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} - _ = c.ServeJSON() -} - -// ChangeStatus POST /platform/modules/status body:{id,status} -// 兼容前端:这里的 status 实际用于切换 is_show(显示开关)。 -func (c *PlatformModulesController) ChangeStatus() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - Status int8 `json:"status"` - } - if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { - c.jsonErr(400, 400, "参数错误") - return - } - if p.Status != 0 && p.Status != 1 { - p.Status = 1 - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemModules)). - Filter("id", p.ID). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"is_show": p.Status, "update_time": now}) - if err != nil { - c.jsonErr(500, 500, "状态修改失败:"+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "模块不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// GetSelectList GET /platform/modules/select/list -func (c *PlatformModulesController) GetSelectList() { - if _, err := c.modulesClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - var rows []models.SystemModules - _, err := models.Orm.QueryTable(new(models.SystemModules)). - Filter("delete_time__isnull", true). - Filter("status", 1). - OrderBy("sort", "id"). - All(&rows, "ID", "Name", "Code") - if err != nil { - c.jsonErr(500, 500, "获取失败:"+err.Error()) - return - } - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - list = append(list, map[string]interface{}{ - "id": rows[i].ID, - "name": rows[i].Name, - "code": rows[i].Code, - }) - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformModulesController 模块管理(yz_system_modules) +type PlatformModulesController struct { + beego.Controller +} + +func (c *PlatformModulesController) modulesClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + // 语义更正: + // - /platform/* 只能 platform 访问 + // - /backend/* 只能 backend 访问 + // 兼容:历史 token 可能缺少 user_type(按 user 处理),此时都拒绝访问以避免越权。 + path := strings.ToLower(c.Ctx.Request.URL.Path) + if strings.HasPrefix(path, "/platform/") { + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + } else if strings.HasPrefix(path, "/backend/") { + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + } + return claims, nil +} + +func (c *PlatformModulesController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// GetList GET /platform/modules/list +func (c *PlatformModulesController) GetList() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + var rows []models.SystemModules + _, err := models.Orm.QueryTable(new(models.SystemModules)). + Filter("delete_time__isnull", true). + OrderBy("sort", "id"). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败:"+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "获取成功", + "data": map[string]interface{}{ + "list": rows, + "total": len(rows), + }, + } + _ = c.ServeJSON() +} + +// GetTenantList GET /platform/modules/getTenantList +// 兼容旧接口命名:返回当前账号可见的模块。当前实现:返回 status=1 且 is_show=1 的全部模块。 +func (c *PlatformModulesController) GetTenantList() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + var rows []models.SystemModules + _, err := models.Orm.QueryTable(new(models.SystemModules)). + Filter("delete_time__isnull", true). + Filter("status", 1). + Filter("is_show", 1). + OrderBy("sort", "id"). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败:"+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "获取成功", + "data": map[string]interface{}{ + "list": rows, + "total": len(rows), + }, + } + _ = c.ServeJSON() +} + +// GetDetail GET /platform/modules/:id +func (c *PlatformModulesController) GetDetail() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "参数错误") + return + } + var row models.SystemModules + err = models.Orm.QueryTable(new(models.SystemModules)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.jsonErr(404, 404, "模块不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "获取成功", "data": row} + _ = c.ServeJSON() +} + +type modulePayload struct { + Mid *uint64 `json:"mid"` + Name string `json:"name"` + Code string `json:"code"` + Path string `json:"path"` + Icon string `json:"icon"` + Description string `json:"description"` + Type int `json:"type"` + Sort int `json:"sort"` + Status int8 `json:"status"` + IsShow int8 `json:"is_show"` +} + +// Add POST /platform/modules +func (c *PlatformModulesController) Add() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p modulePayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + p.Name = strings.TrimSpace(p.Name) + p.Code = strings.TrimSpace(p.Code) + if p.Name == "" || p.Code == "" { + c.jsonErr(400, 400, "模块名称和编码不能为空") + return + } + // code 唯一(排除软删) + cnt, _ := models.Orm.QueryTable(new(models.SystemModules)). + Filter("code", p.Code). + Filter("delete_time__isnull", true). + Count() + if cnt > 0 { + c.jsonErr(400, 400, "模块编码已存在") + return + } + now := time.Now() + row := &models.SystemModules{ + Mid: p.Mid, + Name: p.Name, + Code: p.Code, + Path: strings.TrimSpace(p.Path), + Icon: strings.TrimSpace(p.Icon), + Description: strings.TrimSpace(p.Description), + Type: p.Type, + Sort: p.Sort, + Status: p.Status, + IsShow: p.IsShow, + CreateTime: &now, + UpdateTime: &now, + } + id, err := models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "添加失败:"+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "添加成功", "data": map[string]interface{}{"id": uint64(id)}} + _ = c.ServeJSON() +} + +// Edit PUT /platform/modules/:id +func (c *PlatformModulesController) Edit() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "参数错误") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p modulePayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + p.Name = strings.TrimSpace(p.Name) + p.Code = strings.TrimSpace(p.Code) + if p.Name == "" || p.Code == "" { + c.jsonErr(400, 400, "模块名称和编码不能为空") + return + } + // code 唯一(排除自身与软删) + cnt, _ := models.Orm.QueryTable(new(models.SystemModules)). + Filter("code", p.Code). + Filter("id__ne", id). + Filter("delete_time__isnull", true). + Count() + if cnt > 0 { + c.jsonErr(400, 400, "模块编码已存在") + return + } + now := time.Now() + up := map[string]interface{}{ + "mid": p.Mid, + "name": p.Name, + "code": p.Code, + "path": strings.TrimSpace(p.Path), + "icon": strings.TrimSpace(p.Icon), + "description": strings.TrimSpace(p.Description), + "type": p.Type, + "sort": p.Sort, + "status": p.Status, + "is_show": p.IsShow, + "update_time": now, + } + n, err := models.Orm.QueryTable(new(models.SystemModules)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "编辑失败:"+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "模块不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "编辑成功"} + _ = c.ServeJSON() +} + +// Delete DELETE /platform/modules/:id(软删) +func (c *PlatformModulesController) Delete() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "参数错误") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemModules)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败:"+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "模块不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// BatchDelete POST /platform/modules/batchDelete body:{ids:[]} +func (c *PlatformModulesController) BatchDelete() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + IDs []uint64 `json:"ids"` + } + if err := json.Unmarshal(raw, &p); err != nil || len(p.IDs) == 0 { + c.jsonErr(400, 400, "请选择要删除的模块") + return + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemModules)). + Filter("id__in", p.IDs). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量删除失败:"+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} + _ = c.ServeJSON() +} + +// ChangeStatus POST /platform/modules/status body:{id,status} +// 兼容前端:这里的 status 实际用于切换 is_show(显示开关)。 +func (c *PlatformModulesController) ChangeStatus() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + Status int8 `json:"status"` + } + if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 { + c.jsonErr(400, 400, "参数错误") + return + } + if p.Status != 0 && p.Status != 1 { + p.Status = 1 + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemModules)). + Filter("id", p.ID). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"is_show": p.Status, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "状态修改失败:"+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "模块不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// GetSelectList GET /platform/modules/select/list +func (c *PlatformModulesController) GetSelectList() { + if _, err := c.modulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + var rows []models.SystemModules + _, err := models.Orm.QueryTable(new(models.SystemModules)). + Filter("delete_time__isnull", true). + Filter("status", 1). + OrderBy("sort", "id"). + All(&rows, "ID", "Name", "Code") + if err != nil { + c.jsonErr(500, 500, "获取失败:"+err.Error()) + return + } + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, map[string]interface{}{ + "id": rows[i].ID, + "name": rows[i].Name, + "code": rows[i].Code, + }) + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_notebook.go b/go/controllers/platform_notebook.go index 2e6c652..b1476f0 100644 --- a/go/controllers/platform_notebook.go +++ b/go/controllers/platform_notebook.go @@ -1,312 +1,312 @@ -package controllers - -import ( - "encoding/json" - "io" - "server/models" - "server/pkg/jwtutil" - "strconv" - "strings" - "time" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -type PlatformNotebookController struct { - beego.Controller -} - -// requireAuth 验证平台用户权限 -func requireNotebookAuth(c *beego.Controller) (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, orm.ErrNoRows - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, orm.ErrNoRows - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, err - } - if claims.UserType != "platform" { - return nil, orm.ErrNoRows - } - return claims, nil -} - -// jsonResponse 统一JSON响应 -func jsonResponse(c *beego.Controller, httpStatus, code int, msg string, data interface{}) { - c.Ctx.Output.SetStatus(httpStatus) - resp := map[string]interface{}{ - "code": code, - "msg": msg, - } - if data != nil { - resp["data"] = data - } - c.Data["json"] = resp - _ = c.ServeJSON() -} - -// List 获取笔记列表 -// GET /platform/notebook/list -func (c *PlatformNotebookController) List() { - claims, err := requireNotebookAuth(&c.Controller) - if err != nil { - jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - keyword := strings.TrimSpace(c.GetString("keyword")) - - if page < 1 { - page = 1 - } - if pageSize < 1 || pageSize > 100 { - pageSize = 20 - } - - qs := models.Orm.QueryTable(new(models.PlatformNotebook)). - Filter("is_deleted", 0). - Filter("user_id", claims.UserID) - - if keyword != "" { - qs = qs.Filter("title__icontains", keyword) - } - - total, err := qs.Count() - if err != nil { - jsonResponse(&c.Controller, 500, 500, "查询失败", nil) - return - } - - var list []models.PlatformNotebook - _, err = qs.OrderBy("-update_time", "-create_time"). - Limit(pageSize). - Offset((page - 1) * pageSize). - All(&list) - - if err != nil && err != orm.ErrNoRows { - jsonResponse(&c.Controller, 500, 500, "查询失败", nil) - return - } - - if list == nil { - list = []models.PlatformNotebook{} - } - - jsonResponse(&c.Controller, 200, 200, "success", map[string]interface{}{ - "list": list, - "total": total, - }) -} - -// Detail 获取笔记详情 -// GET /platform/notebook/detail/:id -func (c *PlatformNotebookController) Detail() { - claims, err := requireNotebookAuth(&c.Controller) - if err != nil { - jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) - return - } - - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - jsonResponse(&c.Controller, 400, 400, "无效ID", nil) - return - } - - var note models.PlatformNotebook - err = models.Orm.QueryTable(new(models.PlatformNotebook)). - Filter("id", id). - Filter("is_deleted", 0). - Filter("user_id", claims.UserID). - One(¬e) - - if err != nil { - if err == orm.ErrNoRows { - jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil) - } else { - jsonResponse(&c.Controller, 500, 500, "查询失败", nil) - } - return - } - - jsonResponse(&c.Controller, 200, 200, "success", note) -} - -// Create 创建笔记 -// POST /platform/notebook/create -func (c *PlatformNotebookController) Create() { - claims, err := requireNotebookAuth(&c.Controller) - if err != nil { - jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonResponse(&c.Controller, 400, 400, "参数错误", nil) - return - } - - var payload struct { - Title string `json:"title"` - Content string `json:"content"` - } - - if err := json.Unmarshal(raw, &payload); err != nil { - jsonResponse(&c.Controller, 400, 400, "参数错误", nil) - return - } - - payload.Title = strings.TrimSpace(payload.Title) - if payload.Title == "" { - payload.Title = "无标题" - } - - userID := uint64(claims.UserID) - note := &models.PlatformNotebook{ - Title: payload.Title, - Content: payload.Content, - UserID: &userID, - UserName: &claims.Username, - IsDeleted: 0, - } - - id, err := models.Orm.Insert(note) - if err != nil { - jsonResponse(&c.Controller, 500, 500, "创建失败", nil) - return - } - - note.ID = uint64(id) - jsonResponse(&c.Controller, 200, 200, "创建成功", note) -} - -// Update 更新笔记 -// POST /platform/notebook/update/:id -func (c *PlatformNotebookController) Update() { - claims, err := requireNotebookAuth(&c.Controller) - if err != nil { - jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) - return - } - - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - jsonResponse(&c.Controller, 400, 400, "无效ID", nil) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - jsonResponse(&c.Controller, 400, 400, "参数错误", nil) - return - } - - var payload struct { - Title string `json:"title"` - Content string `json:"content"` - } - - if err := json.Unmarshal(raw, &payload); err != nil { - jsonResponse(&c.Controller, 400, 400, "参数错误", nil) - return - } - - payload.Title = strings.TrimSpace(payload.Title) - if payload.Title == "" { - payload.Title = "无标题" - } - - // 验证笔记是否存在且属于当前用户 - var note models.PlatformNotebook - err = models.Orm.QueryTable(new(models.PlatformNotebook)). - Filter("id", id). - Filter("is_deleted", 0). - Filter("user_id", claims.UserID). - One(¬e) - - if err != nil { - if err == orm.ErrNoRows { - jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil) - } else { - jsonResponse(&c.Controller, 500, 500, "查询失败", nil) - } - return - } - - now := time.Now() - _, err = models.Orm.QueryTable(new(models.PlatformNotebook)). - Filter("id", id). - Update(map[string]interface{}{ - "title": payload.Title, - "content": payload.Content, - "update_time": now, - }) - - if err != nil { - jsonResponse(&c.Controller, 500, 500, "更新失败", nil) - return - } - - note.Title = payload.Title - note.Content = payload.Content - note.UpdateTime = &now - - jsonResponse(&c.Controller, 200, 200, "更新成功", note) -} - -// Delete 删除笔记(软删除) -// DELETE /platform/notebook/delete/:id -func (c *PlatformNotebookController) Delete() { - claims, err := requireNotebookAuth(&c.Controller) - if err != nil { - jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) - return - } - - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - jsonResponse(&c.Controller, 400, 400, "无效ID", nil) - return - } - - // 验证笔记是否存在且属于当前用户 - var note models.PlatformNotebook - err = models.Orm.QueryTable(new(models.PlatformNotebook)). - Filter("id", id). - Filter("is_deleted", 0). - Filter("user_id", claims.UserID). - One(¬e) - - if err != nil { - if err == orm.ErrNoRows { - jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil) - } else { - jsonResponse(&c.Controller, 500, 500, "查询失败", nil) - } - return - } - - now := time.Now() - _, err = models.Orm.QueryTable(new(models.PlatformNotebook)). - Filter("id", id). - Update(map[string]interface{}{ - "is_deleted": 1, - "delete_time": now, - }) - - if err != nil { - jsonResponse(&c.Controller, 500, 500, "删除失败", nil) - return - } - - jsonResponse(&c.Controller, 200, 200, "删除成功", nil) -} +package controllers + +import ( + "encoding/json" + "io" + "server/models" + "server/pkg/jwtutil" + "strconv" + "strings" + "time" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformNotebookController struct { + beego.Controller +} + +// requireAuth 验证平台用户权限 +func requireNotebookAuth(c *beego.Controller) (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, orm.ErrNoRows + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, orm.ErrNoRows + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, err + } + if claims.UserType != "platform" { + return nil, orm.ErrNoRows + } + return claims, nil +} + +// jsonResponse 统一JSON响应 +func jsonResponse(c *beego.Controller, httpStatus, code int, msg string, data interface{}) { + c.Ctx.Output.SetStatus(httpStatus) + resp := map[string]interface{}{ + "code": code, + "msg": msg, + } + if data != nil { + resp["data"] = data + } + c.Data["json"] = resp + _ = c.ServeJSON() +} + +// List 获取笔记列表 +// GET /platform/notebook/list +func (c *PlatformNotebookController) List() { + claims, err := requireNotebookAuth(&c.Controller) + if err != nil { + jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + keyword := strings.TrimSpace(c.GetString("keyword")) + + if page < 1 { + page = 1 + } + if pageSize < 1 || pageSize > 100 { + pageSize = 20 + } + + qs := models.Orm.QueryTable(new(models.PlatformNotebook)). + Filter("is_deleted", 0). + Filter("user_id", claims.UserID) + + if keyword != "" { + qs = qs.Filter("title__icontains", keyword) + } + + total, err := qs.Count() + if err != nil { + jsonResponse(&c.Controller, 500, 500, "查询失败", nil) + return + } + + var list []models.PlatformNotebook + _, err = qs.OrderBy("-update_time", "-create_time"). + Limit(pageSize). + Offset((page - 1) * pageSize). + All(&list) + + if err != nil && err != orm.ErrNoRows { + jsonResponse(&c.Controller, 500, 500, "查询失败", nil) + return + } + + if list == nil { + list = []models.PlatformNotebook{} + } + + jsonResponse(&c.Controller, 200, 200, "success", map[string]interface{}{ + "list": list, + "total": total, + }) +} + +// Detail 获取笔记详情 +// GET /platform/notebook/detail/:id +func (c *PlatformNotebookController) Detail() { + claims, err := requireNotebookAuth(&c.Controller) + if err != nil { + jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) + return + } + + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + jsonResponse(&c.Controller, 400, 400, "无效ID", nil) + return + } + + var note models.PlatformNotebook + err = models.Orm.QueryTable(new(models.PlatformNotebook)). + Filter("id", id). + Filter("is_deleted", 0). + Filter("user_id", claims.UserID). + One(¬e) + + if err != nil { + if err == orm.ErrNoRows { + jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil) + } else { + jsonResponse(&c.Controller, 500, 500, "查询失败", nil) + } + return + } + + jsonResponse(&c.Controller, 200, 200, "success", note) +} + +// Create 创建笔记 +// POST /platform/notebook/create +func (c *PlatformNotebookController) Create() { + claims, err := requireNotebookAuth(&c.Controller) + if err != nil { + jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonResponse(&c.Controller, 400, 400, "参数错误", nil) + return + } + + var payload struct { + Title string `json:"title"` + Content string `json:"content"` + } + + if err := json.Unmarshal(raw, &payload); err != nil { + jsonResponse(&c.Controller, 400, 400, "参数错误", nil) + return + } + + payload.Title = strings.TrimSpace(payload.Title) + if payload.Title == "" { + payload.Title = "无标题" + } + + userID := uint64(claims.UserID) + note := &models.PlatformNotebook{ + Title: payload.Title, + Content: payload.Content, + UserID: &userID, + UserName: &claims.Username, + IsDeleted: 0, + } + + id, err := models.Orm.Insert(note) + if err != nil { + jsonResponse(&c.Controller, 500, 500, "创建失败", nil) + return + } + + note.ID = uint64(id) + jsonResponse(&c.Controller, 200, 200, "创建成功", note) +} + +// Update 更新笔记 +// POST /platform/notebook/update/:id +func (c *PlatformNotebookController) Update() { + claims, err := requireNotebookAuth(&c.Controller) + if err != nil { + jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) + return + } + + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + jsonResponse(&c.Controller, 400, 400, "无效ID", nil) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + jsonResponse(&c.Controller, 400, 400, "参数错误", nil) + return + } + + var payload struct { + Title string `json:"title"` + Content string `json:"content"` + } + + if err := json.Unmarshal(raw, &payload); err != nil { + jsonResponse(&c.Controller, 400, 400, "参数错误", nil) + return + } + + payload.Title = strings.TrimSpace(payload.Title) + if payload.Title == "" { + payload.Title = "无标题" + } + + // 验证笔记是否存在且属于当前用户 + var note models.PlatformNotebook + err = models.Orm.QueryTable(new(models.PlatformNotebook)). + Filter("id", id). + Filter("is_deleted", 0). + Filter("user_id", claims.UserID). + One(¬e) + + if err != nil { + if err == orm.ErrNoRows { + jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil) + } else { + jsonResponse(&c.Controller, 500, 500, "查询失败", nil) + } + return + } + + now := time.Now() + _, err = models.Orm.QueryTable(new(models.PlatformNotebook)). + Filter("id", id). + Update(map[string]interface{}{ + "title": payload.Title, + "content": payload.Content, + "update_time": now, + }) + + if err != nil { + jsonResponse(&c.Controller, 500, 500, "更新失败", nil) + return + } + + note.Title = payload.Title + note.Content = payload.Content + note.UpdateTime = &now + + jsonResponse(&c.Controller, 200, 200, "更新成功", note) +} + +// Delete 删除笔记(软删除) +// DELETE /platform/notebook/delete/:id +func (c *PlatformNotebookController) Delete() { + claims, err := requireNotebookAuth(&c.Controller) + if err != nil { + jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil) + return + } + + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + jsonResponse(&c.Controller, 400, 400, "无效ID", nil) + return + } + + // 验证笔记是否存在且属于当前用户 + var note models.PlatformNotebook + err = models.Orm.QueryTable(new(models.PlatformNotebook)). + Filter("id", id). + Filter("is_deleted", 0). + Filter("user_id", claims.UserID). + One(¬e) + + if err != nil { + if err == orm.ErrNoRows { + jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil) + } else { + jsonResponse(&c.Controller, 500, 500, "查询失败", nil) + } + return + } + + now := time.Now() + _, err = models.Orm.QueryTable(new(models.PlatformNotebook)). + Filter("id", id). + Update(map[string]interface{}{ + "is_deleted": 1, + "delete_time": now, + }) + + if err != nil { + jsonResponse(&c.Controller, 500, 500, "删除失败", nil) + return + } + + jsonResponse(&c.Controller, 200, 200, "删除成功", nil) +} diff --git a/go/controllers/platform_operation_log.go b/go/controllers/platform_operation_log.go index cec78a9..4ef5ec7 100644 --- a/go/controllers/platform_operation_log.go +++ b/go/controllers/platform_operation_log.go @@ -1,351 +1,351 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformOperationLogController 操作日志(yz_system_operation_log) -type PlatformOperationLogController struct { - beego.Controller -} - -func (c *PlatformOperationLogController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformOperationLogController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// List GET /platform/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime= -func (c *PlatformOperationLogController) List() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - if pageSize > 200 { - pageSize = 200 - } - - keyword := strings.TrimSpace(c.GetString("keyword")) - module := strings.TrimSpace(c.GetString("module")) - action := strings.TrimSpace(c.GetString("action")) - statusStr := strings.TrimSpace(c.GetString("status")) - startTimeStr := strings.TrimSpace(c.GetString("startTime")) - endTimeStr := strings.TrimSpace(c.GetString("endTime")) - - qs := models.Orm.QueryTable(new(models.SystemOperationLog)).Filter("delete_time__isnull", true) - - // 条件拼装 - cond := orm.NewCondition() - needCond := false - - if module != "" { - cond = cond.And("module", module) - needCond = true - } - if action != "" { - cond = cond.And("action", action) - needCond = true - } - if statusStr != "" { - if st, err := strconv.Atoi(statusStr); err == nil { - cond = cond.And("status", st) - needCond = true - } - } - if keyword != "" { - kw := orm.NewCondition(). - Or("module__icontains", keyword). - Or("action__icontains", keyword). - Or("method__icontains", keyword). - Or("url__icontains", keyword). - Or("ip__icontains", keyword). - Or("user_agent__icontains", keyword) - if uid, err := strconv.ParseUint(keyword, 10, 64); err == nil && uid > 0 { - kw = kw.Or("user_id", uid) - } - cond = cond.AndCond(kw) - needCond = true - } - if t, err := parseTimeFlexible(startTimeStr); err == nil && !t.IsZero() { - cond = cond.And("create_time__gte", t) - needCond = true - } - if t, err := parseTimeFlexible(endTimeStr); err == nil && !t.IsZero() { - cond = cond.And("create_time__lte", t) - needCond = true - } - - if needCond { - qs = qs.SetCond(cond) - } - - total, err := qs.Count() - if err != nil { - c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) - return - } - - var rows []models.SystemOperationLog - _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - item := map[string]interface{}{ - "id": rows[i].ID, - "tid": rows[i].Tid, - "user_id": rows[i].UserID, - "module": rows[i].Module, - "action": rows[i].Action, - "method": rows[i].Method, - "url": rows[i].URL, - "ip": rows[i].IP, - "user_agent": rows[i].UserAgent, - "request_data": rows[i].RequestData, - "response_data": rows[i].ResponseData, - "status": rows[i].Status, - "error_message": rows[i].ErrorMessage, - "execution_time": rows[i].ExecutionTime, - "create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"), - "update_time": "", - } - if rows[i].UpdateTime != nil { - item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") - } - list = append(list, item) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -// Detail GET /platform/operationLogs/:id -func (c *PlatformOperationLogController) Detail() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - var row models.SystemOperationLog - err = models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("id", id). - Filter("delete_time__isnull", true). - One(&row) - if err != nil { - c.jsonErr(404, 404, "记录不存在") - return - } - out := map[string]interface{}{ - "id": row.ID, - "tid": row.Tid, - "user_id": row.UserID, - "module": row.Module, - "action": row.Action, - "method": row.Method, - "url": row.URL, - "ip": row.IP, - "user_agent": row.UserAgent, - "request_data": row.RequestData, - "response_data": row.ResponseData, - "status": row.Status, - "error_message": row.ErrorMessage, - "execution_time": row.ExecutionTime, - "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -// Delete DELETE /platform/operationLogs/:id -func (c *PlatformOperationLogController) Delete() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - now := time.Now() - n, err := models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("id", id). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} - -type batchDeletePayload struct { - IDs []uint64 `json:"ids"` -} - -// BatchDelete POST /platform/operationLogs/batchDelete -func (c *PlatformOperationLogController) BatchDelete() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p batchDeletePayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if len(p.IDs) == 0 { - c.jsonErr(400, 400, "请选择要删除的日志") - return - } - now := time.Now() - _, err = models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("id__in", p.IDs). - Filter("delete_time__isnull", true). - Update(map[string]interface{}{"delete_time": now}) - if err != nil { - c.jsonErr(500, 500, "批量删除失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} - _ = c.ServeJSON() -} - -// Statistics GET /platform/operationLogs/statistics -// 供前端筛选项:modules/actions -func (c *PlatformOperationLogController) Statistics() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - var moduleRows []models.SystemOperationLog - _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("delete_time__isnull", true). - Filter("module__isnull", false). - Limit(1000). - All(&moduleRows, "Module") - modSet := map[string]struct{}{} - for i := range moduleRows { - m := strings.TrimSpace(moduleRows[i].Module) - if m != "" { - modSet[m] = struct{}{} - } - } - modules := make([]string, 0, len(modSet)) - for k := range modSet { - modules = append(modules, k) - } - - var actionRows []models.SystemOperationLog - _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). - Filter("delete_time__isnull", true). - Filter("action__isnull", false). - Limit(1000). - All(&actionRows, "Action") - actSet := map[string]struct{}{} - for i := range actionRows { - a := strings.TrimSpace(actionRows[i].Action) - if a != "" { - actSet[a] = struct{}{} - } - } - actions := make([]string, 0, len(actSet)) - for k := range actSet { - actions = append(actions, k) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "modules": modules, - "actions": actions, - }, - } - _ = c.ServeJSON() -} - -func parseTimeFlexible(s string) (time.Time, error) { - s = strings.TrimSpace(s) - if s == "" { - return time.Time{}, fmt.Errorf("empty") - } - layouts := []string{ - "2006-01-02 15:04:05", - "2006-01-02 15:04", - "2006-01-02", - time.RFC3339, - } - for _, ly := range layouts { - if t, err := time.ParseInLocation(ly, s, time.Local); err == nil { - return t, nil - } - } - return time.Time{}, fmt.Errorf("invalid time") -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformOperationLogController 操作日志(yz_system_operation_log) +type PlatformOperationLogController struct { + beego.Controller +} + +func (c *PlatformOperationLogController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformOperationLogController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// List GET /platform/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime= +func (c *PlatformOperationLogController) List() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + + keyword := strings.TrimSpace(c.GetString("keyword")) + module := strings.TrimSpace(c.GetString("module")) + action := strings.TrimSpace(c.GetString("action")) + statusStr := strings.TrimSpace(c.GetString("status")) + startTimeStr := strings.TrimSpace(c.GetString("startTime")) + endTimeStr := strings.TrimSpace(c.GetString("endTime")) + + qs := models.Orm.QueryTable(new(models.SystemOperationLog)).Filter("delete_time__isnull", true) + + // 条件拼装 + cond := orm.NewCondition() + needCond := false + + if module != "" { + cond = cond.And("module", module) + needCond = true + } + if action != "" { + cond = cond.And("action", action) + needCond = true + } + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + cond = cond.And("status", st) + needCond = true + } + } + if keyword != "" { + kw := orm.NewCondition(). + Or("module__icontains", keyword). + Or("action__icontains", keyword). + Or("method__icontains", keyword). + Or("url__icontains", keyword). + Or("ip__icontains", keyword). + Or("user_agent__icontains", keyword) + if uid, err := strconv.ParseUint(keyword, 10, 64); err == nil && uid > 0 { + kw = kw.Or("user_id", uid) + } + cond = cond.AndCond(kw) + needCond = true + } + if t, err := parseTimeFlexible(startTimeStr); err == nil && !t.IsZero() { + cond = cond.And("create_time__gte", t) + needCond = true + } + if t, err := parseTimeFlexible(endTimeStr); err == nil && !t.IsZero() { + cond = cond.And("create_time__lte", t) + needCond = true + } + + if needCond { + qs = qs.SetCond(cond) + } + + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) + return + } + + var rows []models.SystemOperationLog + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + item := map[string]interface{}{ + "id": rows[i].ID, + "tid": rows[i].Tid, + "user_id": rows[i].UserID, + "module": rows[i].Module, + "action": rows[i].Action, + "method": rows[i].Method, + "url": rows[i].URL, + "ip": rows[i].IP, + "user_agent": rows[i].UserAgent, + "request_data": rows[i].RequestData, + "response_data": rows[i].ResponseData, + "status": rows[i].Status, + "error_message": rows[i].ErrorMessage, + "execution_time": rows[i].ExecutionTime, + "create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"), + "update_time": "", + } + if rows[i].UpdateTime != nil { + item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") + } + list = append(list, item) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// Detail GET /platform/operationLogs/:id +func (c *PlatformOperationLogController) Detail() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + var row models.SystemOperationLog + err = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.jsonErr(404, 404, "记录不存在") + return + } + out := map[string]interface{}{ + "id": row.ID, + "tid": row.Tid, + "user_id": row.UserID, + "module": row.Module, + "action": row.Action, + "method": row.Method, + "url": row.URL, + "ip": row.IP, + "user_agent": row.UserAgent, + "request_data": row.RequestData, + "response_data": row.ResponseData, + "status": row.Status, + "error_message": row.ErrorMessage, + "execution_time": row.ExecutionTime, + "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +// Delete DELETE /platform/operationLogs/:id +func (c *PlatformOperationLogController) Delete() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +type batchDeletePayload struct { + IDs []uint64 `json:"ids"` +} + +// BatchDelete POST /platform/operationLogs/batchDelete +func (c *PlatformOperationLogController) BatchDelete() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p batchDeletePayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(p.IDs) == 0 { + c.jsonErr(400, 400, "请选择要删除的日志") + return + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id__in", p.IDs). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} + _ = c.ServeJSON() +} + +// Statistics GET /platform/operationLogs/statistics +// 供前端筛选项:modules/actions +func (c *PlatformOperationLogController) Statistics() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + var moduleRows []models.SystemOperationLog + _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("delete_time__isnull", true). + Filter("module__isnull", false). + Limit(1000). + All(&moduleRows, "Module") + modSet := map[string]struct{}{} + for i := range moduleRows { + m := strings.TrimSpace(moduleRows[i].Module) + if m != "" { + modSet[m] = struct{}{} + } + } + modules := make([]string, 0, len(modSet)) + for k := range modSet { + modules = append(modules, k) + } + + var actionRows []models.SystemOperationLog + _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("delete_time__isnull", true). + Filter("action__isnull", false). + Limit(1000). + All(&actionRows, "Action") + actSet := map[string]struct{}{} + for i := range actionRows { + a := strings.TrimSpace(actionRows[i].Action) + if a != "" { + actSet[a] = struct{}{} + } + } + actions := make([]string, 0, len(actSet)) + for k := range actSet { + actions = append(actions, k) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "modules": modules, + "actions": actions, + }, + } + _ = c.ServeJSON() +} + +func parseTimeFlexible(s string) (time.Time, error) { + s = strings.TrimSpace(s) + if s == "" { + return time.Time{}, fmt.Errorf("empty") + } + layouts := []string{ + "2006-01-02 15:04:05", + "2006-01-02 15:04", + "2006-01-02", + time.RFC3339, + } + for _, ly := range layouts { + if t, err := time.ParseInLocation(ly, s, time.Local); err == nil { + return t, nil + } + } + return time.Time{}, fmt.Errorf("invalid time") +} diff --git a/go/controllers/platform_reminder.go b/go/controllers/platform_reminder.go index 97ed833..df7361f 100644 --- a/go/controllers/platform_reminder.go +++ b/go/controllers/platform_reminder.go @@ -1,631 +1,631 @@ -package controllers - -import ( - "context" - "crypto/rand" - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -type PlatformReminderController struct { - beego.Controller -} - -func (c *PlatformReminderController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformReminderController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *PlatformReminderController) ok(data interface{}) { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} - _ = c.ServeJSON() -} - -// generateToken 生成一个随机的 ack_token -func generateToken() string { - b := make([]byte, 16) - _, _ = rand.Read(b) - b[6] = (b[6] & 0x0f) | 0x40 - b[8] = (b[8] & 0x3f) | 0x80 - return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) -} - -type reminderFormPayload struct { - Title string `json:"title"` - Content string `json:"content"` - ScheduleTime string `json:"schedule_time"` - RemindChannels []string `json:"remind_channels"` // EMAIL, BARK, SMS, SITE_MSG - AdvanceMinutes int `json:"advance_minutes"` - RepeatIntervalMinutes int `json:"repeat_interval_minutes"` - MaxSendCount int `json:"max_send_count"` - ReceiverUserID uint64 `json:"receiver_user_id"` - ReceiverTargets map[string]string `json:"receiver_targets"` // "SMS": "1380...", "EMAIL": "...", "BARK": "..." -} - -// GetReminderList GET /platform/reminder/list -func (c *PlatformReminderController) GetReminderList() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 20) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 20 - } - - // 联表获取日程及提醒信息 - var schedules []models.PlatformSchedule - qs := models.Orm.QueryTable(new(models.PlatformSchedule)) - total, _ := qs.Count() - - _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&schedules) - if err != nil { - c.jsonErr(500, 500, "查询失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(schedules)) - for _, s := range schedules { - // 查询该日程关联的所有提醒记录 - var reminders []models.PlatformScheduleReminder - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("schedule_id", s.ID). - Filter("is_deleted", 0). - All(&reminders) - - channels := make([]string, 0, len(reminders)) - isFinished := true - if len(reminders) == 0 { - isFinished = false - } else { - for _, r := range reminders { - channels = append(channels, r.RemindChannel) - if r.RemindStatus != 2 { - isFinished = false - } - } - } - - item := map[string]interface{}{ - "id": s.ID, - "title": s.Title, - "content": s.Content, - "schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"), - "remind_channels": channels, - "user_id": s.UserID, - "is_finished": isFinished, - } - if len(reminders) > 0 { - first := reminders[0] - item["advance_minutes"] = first.AdvanceMinutes - item["repeat_interval_minutes"] = first.RepeatIntervalMinutes - item["max_send_count"] = first.MaxSendCount - item["receiver_user_id"] = first.ReceiverUserID - } - list = append(list, item) - } - - c.ok(map[string]interface{}{ - "list": list, - "total": total, - "page": page, - "pageSize": pageSize, - }) -} - -// GetReminderDetail GET /platform/reminder/:id -func (c *PlatformReminderController) GetReminderDetail() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.jsonErr(400, 400, "无效的ID") - return - } - - var schedule models.PlatformSchedule - err := models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).One(&schedule) - if err != nil { - c.jsonErr(404, 404, "日程未找到") - return - } - - var reminders []models.PlatformScheduleReminder - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("schedule_id", schedule.ID). - Filter("is_deleted", 0). - All(&reminders) - - channels := make([]string, 0, len(reminders)) - targets := make(map[string]string) - var first models.PlatformScheduleReminder - - for _, r := range reminders { - channels = append(channels, r.RemindChannel) - if r.ReceiverTarget != nil { - targets[r.RemindChannel] = *r.ReceiverTarget - } - first = r - } - - isFinished := true - if len(reminders) == 0 { - isFinished = false - } else { - for _, r := range reminders { - if r.RemindStatus != 2 { - isFinished = false - break - } - } - } - - data := map[string]interface{}{ - "id": schedule.ID, - "title": schedule.Title, - "content": schedule.Content, - "schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"), - "remind_channels": channels, - "receiver_targets": targets, - "is_finished": isFinished, - } - if first.ID > 0 { - data["advance_minutes"] = first.AdvanceMinutes - data["repeat_interval_minutes"] = first.RepeatIntervalMinutes - data["max_send_count"] = first.MaxSendCount - data["receiver_user_id"] = first.ReceiverUserID - } - - c.ok(data) -} - -// CreateReminder POST /platform/reminder -func (c *PlatformReminderController) CreateReminder() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p reminderFormPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - if strings.TrimSpace(p.ScheduleTime) == "" { - c.jsonErr(400, 400, "日程发生时间不能为空") - return - } - - schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local) - if err != nil { - c.jsonErr(400, 400, "日程时间格式不合法,支持 YYYY-MM-DD HH:mm:ss") - return - } - - // 1. 插入日程主表 - schedule := models.PlatformSchedule{ - Title: "日程提醒", - Content: p.Content, - ScheduleTime: schedTime, - UserID: uint64(claims.UserID), - } - schedID, err := models.Orm.Insert(&schedule) - if err != nil { - c.jsonErr(500, 500, "保存日程失败: "+err.Error()) - return - } - - // 2. 根据选中的渠道循环创建提醒 - for _, ch := range p.RemindChannels { - ch = strings.ToUpper(strings.TrimSpace(ch)) - if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" { - continue - } - - targetVal := p.ReceiverTargets[ch] - var target *string - if targetVal != "" { - target = &targetVal - } - - // 计算首次发送时间 - firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute) - - reminder := models.PlatformScheduleReminder{ - ScheduleID: uint64(schedID), - RemindChannel: ch, - AdvanceMinutes: p.AdvanceMinutes, - NextRemindTime: firstSendTime, - ReceiverUserID: uint64(claims.UserID), // 谁创建的就发给谁 - ReceiverTarget: target, - RemindStatus: 0, // 待提醒 - CreateTime: time.Now(), - UpdateTime: time.Now(), - } - - if ch == "EMAIL" || ch == "BARK" { - token := generateToken() - reminder.AckToken = &token - reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes - reminder.MaxSendCount = p.MaxSendCount - if reminder.MaxSendCount <= 0 { - reminder.MaxSendCount = 1 - } - } else { - // SMS 或 SITE_MSG - reminder.RepeatIntervalMinutes = 0 - reminder.MaxSendCount = 1 - } - - _, err = models.Orm.Insert(&reminder) - if err != nil { - c.jsonErr(500, 500, "创建提醒失败: "+err.Error()) - return - } - } - - c.ok(map[string]interface{}{"schedule_id": schedID}) -} - -// UpdateReminder PUT /platform/reminder/:id -func (c *PlatformReminderController) UpdateReminder() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.jsonErr(400, 400, "无效的ID") - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p reminderFormPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local) - if err != nil { - c.jsonErr(400, 400, "日程时间格式不合法") - return - } - - // 1. 更新日程详情 - var schedule models.PlatformSchedule - err = models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).One(&schedule) - if err != nil { - c.jsonErr(404, 404, "日程未找到") - return - } - - // 检查是否所有关联的提醒都已结束 - var reminders []models.PlatformScheduleReminder - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("schedule_id", id). - Filter("is_deleted", 0). - All(&reminders) - isFinished := true - if len(reminders) == 0 { - isFinished = false - } else { - for _, r := range reminders { - if r.RemindStatus != 2 { - isFinished = false - break - } - } - } - if isFinished { - c.jsonErr(400, 400, "该日程提醒已全部结束,无法编辑") - return - } - schedule.Title = "日程提醒" - schedule.Content = p.Content - schedule.ScheduleTime = schedTime - _, err = models.Orm.Update(&schedule, "Title", "Content", "ScheduleTime") - if err != nil { - c.jsonErr(500, 500, "更新失败") - return - } - - // 2. 软删除原本的所有提醒 - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("schedule_id", id). - Update(map[string]interface{}{ - "IsDeleted": 1, - "UpdateTime": time.Now(), - }) - - // 3. 重新建立提醒 - for _, ch := range p.RemindChannels { - ch = strings.ToUpper(strings.TrimSpace(ch)) - if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" { - continue - } - - targetVal := p.ReceiverTargets[ch] - var target *string - if targetVal != "" { - target = &targetVal - } - - firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute) - - reminder := models.PlatformScheduleReminder{ - ScheduleID: id, - RemindChannel: ch, - AdvanceMinutes: p.AdvanceMinutes, - NextRemindTime: firstSendTime, - ReceiverUserID: schedule.UserID, // 谁创建的就发给谁 - ReceiverTarget: target, - RemindStatus: 0, - CreateTime: time.Now(), - UpdateTime: time.Now(), - } - - if ch == "EMAIL" || ch == "BARK" { - token := generateToken() - reminder.AckToken = &token - reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes - reminder.MaxSendCount = p.MaxSendCount - if reminder.MaxSendCount <= 0 { - reminder.MaxSendCount = 1 - } - } else { - reminder.RepeatIntervalMinutes = 0 - reminder.MaxSendCount = 1 - } - - _, err = models.Orm.Insert(&reminder) - if err != nil { - c.jsonErr(500, 500, "重新创建提醒失败") - return - } - } - - c.ok(nil) -} - -// DeleteReminder DELETE /platform/reminder/:id -func (c *PlatformReminderController) DeleteReminder() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.jsonErr(400, 400, "无效的ID") - return - } - - // 检查是否所有关联的提醒都已结束 - var reminders []models.PlatformScheduleReminder - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("schedule_id", id). - Filter("is_deleted", 0). - All(&reminders) - isFinished := true - if len(reminders) == 0 { - isFinished = false - } else { - for _, r := range reminders { - if r.RemindStatus != 2 { - isFinished = false - break - } - } - } - if isFinished { - c.jsonErr(400, 400, "该日程提醒已全部结束,无法删除") - return - } - - // 软删除日程 - // 这里的 id 既可以是主表的 id,也可以是日程的 id - // 我们如果是管理页面,都是基于日程维度的,所以这里 id 指代 schedule_id - _, err := models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).Delete() - if err == nil { - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("schedule_id", id). - Update(map[string]interface{}{ - "IsDeleted": 1, - "UpdateTime": time.Now(), - }) - } - - c.ok(nil) -} - -type reminderBatchDeletePayload struct { - Ids []uint64 `json:"ids"` -} - -// BatchDeleteReminder POST /platform/reminder/batchDelete -func (c *PlatformReminderController) BatchDeleteReminder() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p reminderBatchDeletePayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - if len(p.Ids) == 0 { - c.ok(nil) - return - } - - // 检查选中的日程是否有任何一个是全部结束的,防误操作 - for _, scheduleID := range p.Ids { - var reminders []models.PlatformScheduleReminder - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("schedule_id", scheduleID). - Filter("is_deleted", 0). - All(&reminders) - isFinished := true - if len(reminders) == 0 { - isFinished = false - } else { - for _, r := range reminders { - if r.RemindStatus != 2 { - isFinished = false - break - } - } - } - if isFinished { - c.jsonErr(400, 400, fmt.Sprintf("选中的日程ID %d 的提醒已全部结束,无法删除", scheduleID)) - return - } - } - - // 批量软删除 - _, _ = models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id__in", p.Ids).Delete() - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("schedule_id__in", p.Ids). - Update(map[string]interface{}{ - "IsDeleted": 1, - "UpdateTime": time.Now(), - }) - - c.ok(nil) -} - -type reminderTestPayload struct { - Title string `json:"title"` - Content string `json:"content"` - RemindChannels []string `json:"remind_channels"` -} - -// TestReminder POST /platform/reminder/test -func (c *PlatformReminderController) TestReminder() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p reminderTestPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - if strings.TrimSpace(p.Title) == "" { - p.Title = "测试提醒" - } - if strings.TrimSpace(p.Content) == "" { - p.Content = "这是一条验证日程提醒配置的测试通知。" - } - - senders := map[string]services.ReminderSender{ - "SMS": &services.SMSSender{}, - "EMAIL": &services.EmailSender{}, - "BARK": &services.BarkSender{}, - "SITE_MSG": &services.SiteMsgSender{}, - } - - type TestResult struct { - Channel string `json:"channel"` - Success bool `json:"success"` - Msg string `json:"msg"` - } - results := make([]TestResult, 0) - - for _, ch := range p.RemindChannels { - ch = strings.ToUpper(strings.TrimSpace(ch)) - sender, ok := senders[ch] - if !ok { - results = append(results, TestResult{Channel: ch, Success: false, Msg: "不支持的提醒渠道"}) - continue - } - - dummyToken := "test-token-for-verification" - reminder := &models.PlatformScheduleReminder{ - RemindChannel: ch, - ReceiverUserID: uint64(claims.UserID), - AckToken: &dummyToken, - } - - success, sendErr := sender.Send(context.Background(), reminder, "[测试]"+p.Title, p.Content) - msg := "发送成功" - if !success { - msg = "发送失败" - if sendErr != nil { - msg = sendErr.Error() - } - } - results = append(results, TestResult{Channel: ch, Success: success, Msg: msg}) - } - - c.ok(results) -} +package controllers + +import ( + "context" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformReminderController struct { + beego.Controller +} + +func (c *PlatformReminderController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformReminderController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *PlatformReminderController) ok(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +// generateToken 生成一个随机的 ack_token +func generateToken() string { + b := make([]byte, 16) + _, _ = rand.Read(b) + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) +} + +type reminderFormPayload struct { + Title string `json:"title"` + Content string `json:"content"` + ScheduleTime string `json:"schedule_time"` + RemindChannels []string `json:"remind_channels"` // EMAIL, BARK, SMS, SITE_MSG + AdvanceMinutes int `json:"advance_minutes"` + RepeatIntervalMinutes int `json:"repeat_interval_minutes"` + MaxSendCount int `json:"max_send_count"` + ReceiverUserID uint64 `json:"receiver_user_id"` + ReceiverTargets map[string]string `json:"receiver_targets"` // "SMS": "1380...", "EMAIL": "...", "BARK": "..." +} + +// GetReminderList GET /platform/reminder/list +func (c *PlatformReminderController) GetReminderList() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + + // 联表获取日程及提醒信息 + var schedules []models.PlatformSchedule + qs := models.Orm.QueryTable(new(models.PlatformSchedule)) + total, _ := qs.Count() + + _, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&schedules) + if err != nil { + c.jsonErr(500, 500, "查询失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(schedules)) + for _, s := range schedules { + // 查询该日程关联的所有提醒记录 + var reminders []models.PlatformScheduleReminder + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id", s.ID). + Filter("is_deleted", 0). + All(&reminders) + + channels := make([]string, 0, len(reminders)) + isFinished := true + if len(reminders) == 0 { + isFinished = false + } else { + for _, r := range reminders { + channels = append(channels, r.RemindChannel) + if r.RemindStatus != 2 { + isFinished = false + } + } + } + + item := map[string]interface{}{ + "id": s.ID, + "title": s.Title, + "content": s.Content, + "schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"), + "remind_channels": channels, + "user_id": s.UserID, + "is_finished": isFinished, + } + if len(reminders) > 0 { + first := reminders[0] + item["advance_minutes"] = first.AdvanceMinutes + item["repeat_interval_minutes"] = first.RepeatIntervalMinutes + item["max_send_count"] = first.MaxSendCount + item["receiver_user_id"] = first.ReceiverUserID + } + list = append(list, item) + } + + c.ok(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// GetReminderDetail GET /platform/reminder/:id +func (c *PlatformReminderController) GetReminderDetail() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.jsonErr(400, 400, "无效的ID") + return + } + + var schedule models.PlatformSchedule + err := models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).One(&schedule) + if err != nil { + c.jsonErr(404, 404, "日程未找到") + return + } + + var reminders []models.PlatformScheduleReminder + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id", schedule.ID). + Filter("is_deleted", 0). + All(&reminders) + + channels := make([]string, 0, len(reminders)) + targets := make(map[string]string) + var first models.PlatformScheduleReminder + + for _, r := range reminders { + channels = append(channels, r.RemindChannel) + if r.ReceiverTarget != nil { + targets[r.RemindChannel] = *r.ReceiverTarget + } + first = r + } + + isFinished := true + if len(reminders) == 0 { + isFinished = false + } else { + for _, r := range reminders { + if r.RemindStatus != 2 { + isFinished = false + break + } + } + } + + data := map[string]interface{}{ + "id": schedule.ID, + "title": schedule.Title, + "content": schedule.Content, + "schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"), + "remind_channels": channels, + "receiver_targets": targets, + "is_finished": isFinished, + } + if first.ID > 0 { + data["advance_minutes"] = first.AdvanceMinutes + data["repeat_interval_minutes"] = first.RepeatIntervalMinutes + data["max_send_count"] = first.MaxSendCount + data["receiver_user_id"] = first.ReceiverUserID + } + + c.ok(data) +} + +// CreateReminder POST /platform/reminder +func (c *PlatformReminderController) CreateReminder() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p reminderFormPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + if strings.TrimSpace(p.ScheduleTime) == "" { + c.jsonErr(400, 400, "日程发生时间不能为空") + return + } + + schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local) + if err != nil { + c.jsonErr(400, 400, "日程时间格式不合法,支持 YYYY-MM-DD HH:mm:ss") + return + } + + // 1. 插入日程主表 + schedule := models.PlatformSchedule{ + Title: "日程提醒", + Content: p.Content, + ScheduleTime: schedTime, + UserID: uint64(claims.UserID), + } + schedID, err := models.Orm.Insert(&schedule) + if err != nil { + c.jsonErr(500, 500, "保存日程失败: "+err.Error()) + return + } + + // 2. 根据选中的渠道循环创建提醒 + for _, ch := range p.RemindChannels { + ch = strings.ToUpper(strings.TrimSpace(ch)) + if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" { + continue + } + + targetVal := p.ReceiverTargets[ch] + var target *string + if targetVal != "" { + target = &targetVal + } + + // 计算首次发送时间 + firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute) + + reminder := models.PlatformScheduleReminder{ + ScheduleID: uint64(schedID), + RemindChannel: ch, + AdvanceMinutes: p.AdvanceMinutes, + NextRemindTime: firstSendTime, + ReceiverUserID: uint64(claims.UserID), // 谁创建的就发给谁 + ReceiverTarget: target, + RemindStatus: 0, // 待提醒 + CreateTime: time.Now(), + UpdateTime: time.Now(), + } + + if ch == "EMAIL" || ch == "BARK" { + token := generateToken() + reminder.AckToken = &token + reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes + reminder.MaxSendCount = p.MaxSendCount + if reminder.MaxSendCount <= 0 { + reminder.MaxSendCount = 1 + } + } else { + // SMS 或 SITE_MSG + reminder.RepeatIntervalMinutes = 0 + reminder.MaxSendCount = 1 + } + + _, err = models.Orm.Insert(&reminder) + if err != nil { + c.jsonErr(500, 500, "创建提醒失败: "+err.Error()) + return + } + } + + c.ok(map[string]interface{}{"schedule_id": schedID}) +} + +// UpdateReminder PUT /platform/reminder/:id +func (c *PlatformReminderController) UpdateReminder() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.jsonErr(400, 400, "无效的ID") + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p reminderFormPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local) + if err != nil { + c.jsonErr(400, 400, "日程时间格式不合法") + return + } + + // 1. 更新日程详情 + var schedule models.PlatformSchedule + err = models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).One(&schedule) + if err != nil { + c.jsonErr(404, 404, "日程未找到") + return + } + + // 检查是否所有关联的提醒都已结束 + var reminders []models.PlatformScheduleReminder + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id", id). + Filter("is_deleted", 0). + All(&reminders) + isFinished := true + if len(reminders) == 0 { + isFinished = false + } else { + for _, r := range reminders { + if r.RemindStatus != 2 { + isFinished = false + break + } + } + } + if isFinished { + c.jsonErr(400, 400, "该日程提醒已全部结束,无法编辑") + return + } + schedule.Title = "日程提醒" + schedule.Content = p.Content + schedule.ScheduleTime = schedTime + _, err = models.Orm.Update(&schedule, "Title", "Content", "ScheduleTime") + if err != nil { + c.jsonErr(500, 500, "更新失败") + return + } + + // 2. 软删除原本的所有提醒 + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id", id). + Update(map[string]interface{}{ + "IsDeleted": 1, + "UpdateTime": time.Now(), + }) + + // 3. 重新建立提醒 + for _, ch := range p.RemindChannels { + ch = strings.ToUpper(strings.TrimSpace(ch)) + if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" { + continue + } + + targetVal := p.ReceiverTargets[ch] + var target *string + if targetVal != "" { + target = &targetVal + } + + firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute) + + reminder := models.PlatformScheduleReminder{ + ScheduleID: id, + RemindChannel: ch, + AdvanceMinutes: p.AdvanceMinutes, + NextRemindTime: firstSendTime, + ReceiverUserID: schedule.UserID, // 谁创建的就发给谁 + ReceiverTarget: target, + RemindStatus: 0, + CreateTime: time.Now(), + UpdateTime: time.Now(), + } + + if ch == "EMAIL" || ch == "BARK" { + token := generateToken() + reminder.AckToken = &token + reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes + reminder.MaxSendCount = p.MaxSendCount + if reminder.MaxSendCount <= 0 { + reminder.MaxSendCount = 1 + } + } else { + reminder.RepeatIntervalMinutes = 0 + reminder.MaxSendCount = 1 + } + + _, err = models.Orm.Insert(&reminder) + if err != nil { + c.jsonErr(500, 500, "重新创建提醒失败") + return + } + } + + c.ok(nil) +} + +// DeleteReminder DELETE /platform/reminder/:id +func (c *PlatformReminderController) DeleteReminder() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.jsonErr(400, 400, "无效的ID") + return + } + + // 检查是否所有关联的提醒都已结束 + var reminders []models.PlatformScheduleReminder + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id", id). + Filter("is_deleted", 0). + All(&reminders) + isFinished := true + if len(reminders) == 0 { + isFinished = false + } else { + for _, r := range reminders { + if r.RemindStatus != 2 { + isFinished = false + break + } + } + } + if isFinished { + c.jsonErr(400, 400, "该日程提醒已全部结束,无法删除") + return + } + + // 软删除日程 + // 这里的 id 既可以是主表的 id,也可以是日程的 id + // 我们如果是管理页面,都是基于日程维度的,所以这里 id 指代 schedule_id + _, err := models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).Delete() + if err == nil { + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id", id). + Update(map[string]interface{}{ + "IsDeleted": 1, + "UpdateTime": time.Now(), + }) + } + + c.ok(nil) +} + +type reminderBatchDeletePayload struct { + Ids []uint64 `json:"ids"` +} + +// BatchDeleteReminder POST /platform/reminder/batchDelete +func (c *PlatformReminderController) BatchDeleteReminder() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p reminderBatchDeletePayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + if len(p.Ids) == 0 { + c.ok(nil) + return + } + + // 检查选中的日程是否有任何一个是全部结束的,防误操作 + for _, scheduleID := range p.Ids { + var reminders []models.PlatformScheduleReminder + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id", scheduleID). + Filter("is_deleted", 0). + All(&reminders) + isFinished := true + if len(reminders) == 0 { + isFinished = false + } else { + for _, r := range reminders { + if r.RemindStatus != 2 { + isFinished = false + break + } + } + } + if isFinished { + c.jsonErr(400, 400, fmt.Sprintf("选中的日程ID %d 的提醒已全部结束,无法删除", scheduleID)) + return + } + } + + // 批量软删除 + _, _ = models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id__in", p.Ids).Delete() + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("schedule_id__in", p.Ids). + Update(map[string]interface{}{ + "IsDeleted": 1, + "UpdateTime": time.Now(), + }) + + c.ok(nil) +} + +type reminderTestPayload struct { + Title string `json:"title"` + Content string `json:"content"` + RemindChannels []string `json:"remind_channels"` +} + +// TestReminder POST /platform/reminder/test +func (c *PlatformReminderController) TestReminder() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p reminderTestPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + if strings.TrimSpace(p.Title) == "" { + p.Title = "测试提醒" + } + if strings.TrimSpace(p.Content) == "" { + p.Content = "这是一条验证日程提醒配置的测试通知。" + } + + senders := map[string]services.ReminderSender{ + "SMS": &services.SMSSender{}, + "EMAIL": &services.EmailSender{}, + "BARK": &services.BarkSender{}, + "SITE_MSG": &services.SiteMsgSender{}, + } + + type TestResult struct { + Channel string `json:"channel"` + Success bool `json:"success"` + Msg string `json:"msg"` + } + results := make([]TestResult, 0) + + for _, ch := range p.RemindChannels { + ch = strings.ToUpper(strings.TrimSpace(ch)) + sender, ok := senders[ch] + if !ok { + results = append(results, TestResult{Channel: ch, Success: false, Msg: "不支持的提醒渠道"}) + continue + } + + dummyToken := "test-token-for-verification" + reminder := &models.PlatformScheduleReminder{ + RemindChannel: ch, + ReceiverUserID: uint64(claims.UserID), + AckToken: &dummyToken, + } + + success, sendErr := sender.Send(context.Background(), reminder, "[测试]"+p.Title, p.Content) + msg := "发送成功" + if !success { + msg = "发送失败" + if sendErr != nil { + msg = sendErr.Error() + } + } + results = append(results, TestResult{Channel: ch, Success: success, Msg: msg}) + } + + c.ok(results) +} diff --git a/go/controllers/platform_role.go b/go/controllers/platform_role.go index 59472cd..b531773 100644 --- a/go/controllers/platform_role.go +++ b/go/controllers/platform_role.go @@ -1,201 +1,201 @@ -package controllers - -import ( - "encoding/json" - "io" - "strconv" - "strings" - - "server/models" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformRoleController 平台角色管理(yz_system_admin_role) -type PlatformRoleController struct { - beego.Controller -} - -type rolePayload struct { - Cid *uint8 `json:"cid"` - Name string `json:"name"` - Status *uint8 `json:"status"` - Rights interface{} `json:"rights"` -} - -func normalizeRights(v interface{}) *string { - if v == nil { - return nil - } - switch t := v.(type) { - case string: - s := strings.TrimSpace(t) - if s == "" { - return nil - } - return &s - default: - b, err := json.Marshal(v) - if err != nil { - return nil - } - s := string(b) - return &s - } -} - -// GetAllRoles 获取角色列表 -// GET /platform/allRoles -func (c *PlatformRoleController) GetAllRoles() { - var rows []models.AdminRole - _, err := models.Orm.QueryTable(new(models.AdminRole)). - OrderBy("-id"). - All(&rows) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows} - _ = c.ServeJSON() -} - -// GetRoleByID 获取角色详情 -// GET /platform/roles/:id -func (c *PlatformRoleController) GetRoleByID() { - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - role := models.AdminRole{ID: id} - if err := models.Orm.Read(&role); err != nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "角色不存在"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": role} - _ = c.ServeJSON() -} - -// CreateRole 创建角色 -// POST /platform/roles -func (c *PlatformRoleController) CreateRole() { - var p rolePayload - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - p.Name = strings.TrimSpace(p.Name) - if p.Name == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "name 不能为空"} - _ = c.ServeJSON() - return - } - - status := uint8(1) - if p.Status != nil { - status = *p.Status - } - cid := uint8(1) - if p.Cid != nil { - cid = *p.Cid - } - if cid != 1 && cid != 2 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "cid 仅支持 1/2"} - _ = c.ServeJSON() - return - } - rights := normalizeRights(p.Rights) - role := &models.AdminRole{ - Cid: cid, - Name: p.Name, - Status: status, - Rights: rights, - } - id, err := models.Orm.Insert(role) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}} - _ = c.ServeJSON() -} - -// UpdateRole 更新角色 -// PUT /platform/roles/:id -func (c *PlatformRoleController) UpdateRole() { - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - - var p rolePayload - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - - update := map[string]interface{}{} - if strings.TrimSpace(p.Name) != "" { - update["name"] = strings.TrimSpace(p.Name) - } - if p.Status != nil { - update["status"] = *p.Status - } - if p.Cid != nil { - if *p.Cid != 1 && *p.Cid != 2 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "cid 仅支持 1/2"} - _ = c.ServeJSON() - return - } - update["cid"] = *p.Cid - } - if p.Rights != nil { - update["rights"] = normalizeRights(p.Rights) - } - if len(update) == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"} - _ = c.ServeJSON() - return - } - - _, err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", id).Update(update) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// DeleteRole 删除角色 -// DELETE /platform/roles/:id -func (c *PlatformRoleController) DeleteRole() { - idStr := c.Ctx.Input.Param(":id") - id, _ := strconv.ParseUint(idStr, 10, 64) - if id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} - _ = c.ServeJSON() - return - } - _, err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", id).Delete() - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "io" + "strconv" + "strings" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformRoleController 平台角色管理(yz_system_admin_role) +type PlatformRoleController struct { + beego.Controller +} + +type rolePayload struct { + Cid *uint8 `json:"cid"` + Name string `json:"name"` + Status *uint8 `json:"status"` + Rights interface{} `json:"rights"` +} + +func normalizeRights(v interface{}) *string { + if v == nil { + return nil + } + switch t := v.(type) { + case string: + s := strings.TrimSpace(t) + if s == "" { + return nil + } + return &s + default: + b, err := json.Marshal(v) + if err != nil { + return nil + } + s := string(b) + return &s + } +} + +// GetAllRoles 获取角色列表 +// GET /platform/allRoles +func (c *PlatformRoleController) GetAllRoles() { + var rows []models.AdminRole + _, err := models.Orm.QueryTable(new(models.AdminRole)). + OrderBy("-id"). + All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows} + _ = c.ServeJSON() +} + +// GetRoleByID 获取角色详情 +// GET /platform/roles/:id +func (c *PlatformRoleController) GetRoleByID() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + role := models.AdminRole{ID: id} + if err := models.Orm.Read(&role); err != nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "角色不存在"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": role} + _ = c.ServeJSON() +} + +// CreateRole 创建角色 +// POST /platform/roles +func (c *PlatformRoleController) CreateRole() { + var p rolePayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + p.Name = strings.TrimSpace(p.Name) + if p.Name == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "name 不能为空"} + _ = c.ServeJSON() + return + } + + status := uint8(1) + if p.Status != nil { + status = *p.Status + } + cid := uint8(1) + if p.Cid != nil { + cid = *p.Cid + } + if cid != 1 && cid != 2 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "cid 仅支持 1/2"} + _ = c.ServeJSON() + return + } + rights := normalizeRights(p.Rights) + role := &models.AdminRole{ + Cid: cid, + Name: p.Name, + Status: status, + Rights: rights, + } + id, err := models.Orm.Insert(role) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}} + _ = c.ServeJSON() +} + +// UpdateRole 更新角色 +// PUT /platform/roles/:id +func (c *PlatformRoleController) UpdateRole() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + + var p rolePayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + update := map[string]interface{}{} + if strings.TrimSpace(p.Name) != "" { + update["name"] = strings.TrimSpace(p.Name) + } + if p.Status != nil { + update["status"] = *p.Status + } + if p.Cid != nil { + if *p.Cid != 1 && *p.Cid != 2 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "cid 仅支持 1/2"} + _ = c.ServeJSON() + return + } + update["cid"] = *p.Cid + } + if p.Rights != nil { + update["rights"] = normalizeRights(p.Rights) + } + if len(update) == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"} + _ = c.ServeJSON() + return + } + + _, err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", id).Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteRole 删除角色 +// DELETE /platform/roles/:id +func (c *PlatformRoleController) DeleteRole() { + idStr := c.Ctx.Input.Param(":id") + id, _ := strconv.ParseUint(idStr, 10, 64) + if id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"} + _ = c.ServeJSON() + return + } + _, err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", id).Delete() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_site_settings.go b/go/controllers/platform_site_settings.go index 28f8c23..2bcd722 100644 --- a/go/controllers/platform_site_settings.go +++ b/go/controllers/platform_site_settings.go @@ -1,271 +1,271 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformSiteSettingsController 租户站点设置(站点基本信息) -// 对应前端 normalSettings.vue 的: -// - GET /backend/normalInfos -// - POST /backend/saveNormalInfos -// - GET /platform/normalInfos -// - POST /platform/saveNormalInfos -type PlatformSiteSettingsController struct { - beego.Controller -} - -func (c *PlatformSiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -func (c *PlatformSiteSettingsController) claimsByPath() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - - path := strings.ToLower(c.Ctx.Request.URL.Path) - if strings.HasPrefix(path, "/platform/") { - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - } else if strings.HasPrefix(path, "/backend/") { - if claims.UserType != "backend" { - return nil, fmt.Errorf("无权访问") - } - } - - return claims, nil -} - -func parseUint64Flexible(v interface{}) uint64 { - if v == nil { - return 0 - } - switch x := v.(type) { - case float64: - if x <= 0 { - return 0 - } - return uint64(x) - case string: - s := strings.TrimSpace(x) - if s == "" { - return 0 - } - n, err := strconv.ParseUint(s, 10, 64) - if err != nil || n == 0 { - return 0 - } - return n - default: - return 0 - } -} - -type normalInfosOutput struct { - Sitename string `json:"sitename"` - Companyintroduction string `json:"companyintroduction"` - Description string `json:"description"` - Copyright string `json:"copyright"` - Companyname string `json:"companyname"` - Icp string `json:"icp"` - Logo string `json:"logo"` - Logow string `json:"logow"` - Ico string `json:"ico"` -} - -// GetNormalInfos GET /backend/normalInfos 或 /platform/normalInfos -func (c *PlatformSiteSettingsController) GetNormalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - // 优先使用 token 中的租户 id;若为 0,则允许前端通过查询参数传入(兼容历史/平台端)。 - tid := uint64(claims.TenantId) - if tid == 0 { - tidStr := strings.TrimSpace(c.GetString("tid")) - if tidStr != "" { - if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { - tid = n - } - } - } - - out := normalInfosOutput{ - Sitename: "", - Companyintroduction: "", - Description: "", - Copyright: "", - Companyname: "", - Icp: "", - Logo: "", - Logow: "", - Ico: "", - } - - // tid 缺失时不报错,直接返回空对象给前端渲染(避免 UI 直接崩)。 - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() - return - } - - var rows []models.TenantSiteSetting - _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Limit(1). - All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取失败: "+err.Error()) - return - } - if len(rows) > 0 { - r := rows[0] - out.Sitename = r.Sitename - out.Companyintroduction = r.Companyintroduction - out.Logo = r.Logo - out.Logow = r.Logow - out.Ico = r.Ico - out.Description = r.Description - out.Copyright = r.Copyright - out.Companyname = r.Companyname - out.Icp = r.Icp - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} - _ = c.ServeJSON() -} - -type normalInfosPayload struct { - // 前端会传 tid(但我们仍优先使用 token 的 tenant_id) - Tid interface{} `json:"tid"` - - Sitename string `json:"sitename"` - Companyintroduction string `json:"companyintroduction"` - Logo string `json:"logo"` - Logow string `json:"logow"` - Ico string `json:"ico"` - Description string `json:"description"` - Copyright string `json:"copyright"` - Companyname string `json:"companyname"` - Icp string `json:"icp"` -} - -// SaveNormalInfos POST /backend/saveNormalInfos 或 /platform/saveNormalInfos -func (c *PlatformSiteSettingsController) SaveNormalInfos() { - claims, err := c.claimsByPath() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - var p normalInfosPayload - if uerr := json.Unmarshal(raw, &p); uerr != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - tid := uint64(claims.TenantId) - if tid == 0 { - tid = parseUint64Flexible(p.Tid) - } - if tid == 0 { - c.jsonErr(400, 400, "tid不能为空") - return - } - - sitename := strings.TrimSpace(p.Sitename) - if sitename == "" { - c.jsonErr(400, 400, "站点名称不能为空") - return - } - - now := time.Now() - - up := map[string]interface{}{ - "tid": tid, - "sitename": sitename, - "companyintroduction": strings.TrimSpace(p.Companyintroduction), - "logo": strings.TrimSpace(p.Logo), - "logow": strings.TrimSpace(p.Logow), - "ico": strings.TrimSpace(p.Ico), - "description": strings.TrimSpace(p.Description), - "copyright": strings.TrimSpace(p.Copyright), - "companyname": strings.TrimSpace(p.Companyname), - "icp": strings.TrimSpace(p.Icp), - "update_time": now, - } - - cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Count() - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - - if cnt == 0 { - row := &models.TenantSiteSetting{ - Tid: tid, - Sitename: sitename, - Companyintroduction: strings.TrimSpace(p.Companyintroduction), - Logo: strings.TrimSpace(p.Logo), - Logow: strings.TrimSpace(p.Logow), - Ico: strings.TrimSpace(p.Ico), - Description: strings.TrimSpace(p.Description), - Copyright: strings.TrimSpace(p.Copyright), - Companyname: strings.TrimSpace(p.Companyname), - Icp: strings.TrimSpace(p.Icp), - CreateTime: now, - UpdateTime: &now, - } - _, err = models.Orm.Insert(row) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } else { - _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Update(up) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformSiteSettingsController 租户站点设置(站点基本信息) +// 对应前端 normalSettings.vue 的: +// - GET /backend/normalInfos +// - POST /backend/saveNormalInfos +// - GET /platform/normalInfos +// - POST /platform/saveNormalInfos +type PlatformSiteSettingsController struct { + beego.Controller +} + +func (c *PlatformSiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *PlatformSiteSettingsController) claimsByPath() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + + path := strings.ToLower(c.Ctx.Request.URL.Path) + if strings.HasPrefix(path, "/platform/") { + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + } else if strings.HasPrefix(path, "/backend/") { + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + } + + return claims, nil +} + +func parseUint64Flexible(v interface{}) uint64 { + if v == nil { + return 0 + } + switch x := v.(type) { + case float64: + if x <= 0 { + return 0 + } + return uint64(x) + case string: + s := strings.TrimSpace(x) + if s == "" { + return 0 + } + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || n == 0 { + return 0 + } + return n + default: + return 0 + } +} + +type normalInfosOutput struct { + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + Icp string `json:"icp"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` +} + +// GetNormalInfos GET /backend/normalInfos 或 /platform/normalInfos +func (c *PlatformSiteSettingsController) GetNormalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + // 优先使用 token 中的租户 id;若为 0,则允许前端通过查询参数传入(兼容历史/平台端)。 + tid := uint64(claims.TenantId) + if tid == 0 { + tidStr := strings.TrimSpace(c.GetString("tid")) + if tidStr != "" { + if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { + tid = n + } + } + } + + out := normalInfosOutput{ + Sitename: "", + Companyintroduction: "", + Description: "", + Copyright: "", + Companyname: "", + Icp: "", + Logo: "", + Logow: "", + Ico: "", + } + + // tid 缺失时不报错,直接返回空对象给前端渲染(避免 UI 直接崩)。 + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + var rows []models.TenantSiteSetting + _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Limit(1). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + if len(rows) > 0 { + r := rows[0] + out.Sitename = r.Sitename + out.Companyintroduction = r.Companyintroduction + out.Logo = r.Logo + out.Logow = r.Logow + out.Ico = r.Ico + out.Description = r.Description + out.Copyright = r.Copyright + out.Companyname = r.Companyname + out.Icp = r.Icp + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type normalInfosPayload struct { + // 前端会传 tid(但我们仍优先使用 token 的 tenant_id) + Tid interface{} `json:"tid"` + + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + Icp string `json:"icp"` +} + +// SaveNormalInfos POST /backend/saveNormalInfos 或 /platform/saveNormalInfos +func (c *PlatformSiteSettingsController) SaveNormalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + var p normalInfosPayload + if uerr := json.Unmarshal(raw, &p); uerr != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := uint64(claims.TenantId) + if tid == 0 { + tid = parseUint64Flexible(p.Tid) + } + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + sitename := strings.TrimSpace(p.Sitename) + if sitename == "" { + c.jsonErr(400, 400, "站点名称不能为空") + return + } + + now := time.Now() + + up := map[string]interface{}{ + "tid": tid, + "sitename": sitename, + "companyintroduction": strings.TrimSpace(p.Companyintroduction), + "logo": strings.TrimSpace(p.Logo), + "logow": strings.TrimSpace(p.Logow), + "ico": strings.TrimSpace(p.Ico), + "description": strings.TrimSpace(p.Description), + "copyright": strings.TrimSpace(p.Copyright), + "companyname": strings.TrimSpace(p.Companyname), + "icp": strings.TrimSpace(p.Icp), + "update_time": now, + } + + cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Count() + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + if cnt == 0 { + row := &models.TenantSiteSetting{ + Tid: tid, + Sitename: sitename, + Companyintroduction: strings.TrimSpace(p.Companyintroduction), + Logo: strings.TrimSpace(p.Logo), + Logow: strings.TrimSpace(p.Logow), + Ico: strings.TrimSpace(p.Ico), + Description: strings.TrimSpace(p.Description), + Copyright: strings.TrimSpace(p.Copyright), + Companyname: strings.TrimSpace(p.Companyname), + Icp: strings.TrimSpace(p.Icp), + CreateTime: now, + UpdateTime: &now, + } + _, err = models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } else { + _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_sitereminder.go b/go/controllers/platform_sitereminder.go index 3be3ba8..c97c03b 100644 --- a/go/controllers/platform_sitereminder.go +++ b/go/controllers/platform_sitereminder.go @@ -1,339 +1,339 @@ -package controllers - -import ( - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - - "server/pkg/jwtutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -type PlatformSiteReminderController struct { - beego.Controller -} - -func (c *PlatformSiteReminderController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformSiteReminderController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// GetConfig GET /platform/sitereminder/config -func (c *PlatformSiteReminderController) GetConfig() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - cfg, err := services.GetSiteReminderConfig() - if err != nil { - c.jsonErr(500, 500, "获取配置失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": cfg} - _ = c.ServeJSON() -} - -// SaveConfig POST /platform/sitereminder/config -func (c *PlatformSiteReminderController) SaveConfig() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - RetentionDays int `json:"retention_days"` - AutoRead int8 `json:"auto_read"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if err := services.SaveSiteReminderConfig(p.RetentionDays, p.AutoRead); err != nil { - c.jsonErr(500, 500, "保存配置失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -// Send POST /platform/sitereminder/send -func (c *PlatformSiteReminderController) Send() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - Title string `json:"title"` - Content string `json:"content"` - TargetType string `json:"target_type"` // platform, tenant_all, role, tenant - TargetRoleID uint64 `json:"target_role_id"` - TargetTenantID uint64 `json:"target_tenant_id"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - p.Title = strings.TrimSpace(p.Title) - p.Content = strings.TrimSpace(p.Content) - if p.Title == "" || p.Content == "" { - c.jsonErr(400, 400, "标题与内容不能为空") - return - } - if p.TargetType == "" { - c.jsonErr(400, 400, "发送目标类型不能为空") - return - } - - err = services.SendSiteReminder(p.Title, p.Content, uint64(claims.UserID), "platform", p.TargetType, p.TargetRoleID, p.TargetTenantID) - if err != nil { - c.jsonErr(500, 500, "发送失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发送成功"} - _ = c.ServeJSON() -} - -// GetMyList GET /platform/sitereminder/myList -func (c *PlatformSiteReminderController) GetMyList() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - var isRead *int8 - if isReadStr := c.GetString("isRead"); isReadStr != "" { - if val, err := strconv.Atoi(isReadStr); err == nil { - v := int8(val) - isRead = &v - } - } - - list, total, err := services.ListReminders(uint64(claims.UserID), "platform", page, pageSize, isRead) - if err != nil { - c.jsonErr(500, 500, "获取消息列表失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -// MarkRead POST /platform/sitereminder/read -func (c *PlatformSiteReminderController) MarkRead() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - err = services.MarkReminderRead(p.ID, uint64(claims.UserID), "platform") - if err != nil { - c.jsonErr(500, 500, "操作失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// MarkAllRead POST /platform/sitereminder/readall -func (c *PlatformSiteReminderController) MarkAllRead() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - err = services.MarkAllRemindersRead(uint64(claims.UserID), "platform") - if err != nil { - c.jsonErr(500, 500, "操作失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// Delete POST /platform/sitereminder/delete -func (c *PlatformSiteReminderController) Delete() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - ID uint64 `json:"id"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - err = services.DeleteReminder(p.ID, uint64(claims.UserID), "platform") - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// GetSentList GET /platform/sitereminder/sentList -func (c *PlatformSiteReminderController) GetSentList() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - - list, total, err := services.ListSentReminders(uint64(claims.UserID), page, pageSize) - if err != nil { - c.jsonErr(500, 500, "获取发送列表失败: "+err.Error()) - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -// UpdateSent POST /platform/sitereminder/updateSent -func (c *PlatformSiteReminderController) UpdateSent() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - BatchID string `json:"batch_id"` - Title string `json:"title"` - Content string `json:"content"` - TargetType string `json:"target_type"` - TargetRoleID uint64 `json:"target_role_id"` - TargetTenantID uint64 `json:"target_tenant_id"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - p.Title = strings.TrimSpace(p.Title) - p.Content = strings.TrimSpace(p.Content) - if p.BatchID == "" || p.Title == "" || p.Content == "" { - c.jsonErr(400, 400, "批次号、标题与内容不能为空") - return - } - if p.TargetType == "" { - c.jsonErr(400, 400, "发送目标类型不能为空") - return - } - - err = services.UpdateSentReminder(p.BatchID, p.Title, p.Content, p.TargetType, p.TargetRoleID, p.TargetTenantID) - if err != nil { - c.jsonErr(500, 500, "修改失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"} - _ = c.ServeJSON() -} - -// DeleteSentBatch POST /platform/sitereminder/deleteSent -func (c *PlatformSiteReminderController) DeleteSentBatch() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p struct { - BatchID string `json:"batch_id"` - } - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - if p.BatchID == "" { - c.jsonErr(400, 400, "批次号不能为空") - return - } - - err = services.DeleteSentReminderBatch(p.BatchID) - if err != nil { - c.jsonErr(500, 500, "删除失败: "+err.Error()) - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformSiteReminderController struct { + beego.Controller +} + +func (c *PlatformSiteReminderController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformSiteReminderController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// GetConfig GET /platform/sitereminder/config +func (c *PlatformSiteReminderController) GetConfig() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + cfg, err := services.GetSiteReminderConfig() + if err != nil { + c.jsonErr(500, 500, "获取配置失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": cfg} + _ = c.ServeJSON() +} + +// SaveConfig POST /platform/sitereminder/config +func (c *PlatformSiteReminderController) SaveConfig() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + RetentionDays int `json:"retention_days"` + AutoRead int8 `json:"auto_read"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if err := services.SaveSiteReminderConfig(p.RetentionDays, p.AutoRead); err != nil { + c.jsonErr(500, 500, "保存配置失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +// Send POST /platform/sitereminder/send +func (c *PlatformSiteReminderController) Send() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + Title string `json:"title"` + Content string `json:"content"` + TargetType string `json:"target_type"` // platform, tenant_all, role, tenant + TargetRoleID uint64 `json:"target_role_id"` + TargetTenantID uint64 `json:"target_tenant_id"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + p.Title = strings.TrimSpace(p.Title) + p.Content = strings.TrimSpace(p.Content) + if p.Title == "" || p.Content == "" { + c.jsonErr(400, 400, "标题与内容不能为空") + return + } + if p.TargetType == "" { + c.jsonErr(400, 400, "发送目标类型不能为空") + return + } + + err = services.SendSiteReminder(p.Title, p.Content, uint64(claims.UserID), "platform", p.TargetType, p.TargetRoleID, p.TargetTenantID) + if err != nil { + c.jsonErr(500, 500, "发送失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发送成功"} + _ = c.ServeJSON() +} + +// GetMyList GET /platform/sitereminder/myList +func (c *PlatformSiteReminderController) GetMyList() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + var isRead *int8 + if isReadStr := c.GetString("isRead"); isReadStr != "" { + if val, err := strconv.Atoi(isReadStr); err == nil { + v := int8(val) + isRead = &v + } + } + + list, total, err := services.ListReminders(uint64(claims.UserID), "platform", page, pageSize, isRead) + if err != nil { + c.jsonErr(500, 500, "获取消息列表失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// MarkRead POST /platform/sitereminder/read +func (c *PlatformSiteReminderController) MarkRead() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + err = services.MarkReminderRead(p.ID, uint64(claims.UserID), "platform") + if err != nil { + c.jsonErr(500, 500, "操作失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// MarkAllRead POST /platform/sitereminder/readall +func (c *PlatformSiteReminderController) MarkAllRead() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + err = services.MarkAllRemindersRead(uint64(claims.UserID), "platform") + if err != nil { + c.jsonErr(500, 500, "操作失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// Delete POST /platform/sitereminder/delete +func (c *PlatformSiteReminderController) Delete() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + ID uint64 `json:"id"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + err = services.DeleteReminder(p.ID, uint64(claims.UserID), "platform") + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// GetSentList GET /platform/sitereminder/sentList +func (c *PlatformSiteReminderController) GetSentList() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + + list, total, err := services.ListSentReminders(uint64(claims.UserID), page, pageSize) + if err != nil { + c.jsonErr(500, 500, "获取发送列表失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// UpdateSent POST /platform/sitereminder/updateSent +func (c *PlatformSiteReminderController) UpdateSent() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + BatchID string `json:"batch_id"` + Title string `json:"title"` + Content string `json:"content"` + TargetType string `json:"target_type"` + TargetRoleID uint64 `json:"target_role_id"` + TargetTenantID uint64 `json:"target_tenant_id"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + p.Title = strings.TrimSpace(p.Title) + p.Content = strings.TrimSpace(p.Content) + if p.BatchID == "" || p.Title == "" || p.Content == "" { + c.jsonErr(400, 400, "批次号、标题与内容不能为空") + return + } + if p.TargetType == "" { + c.jsonErr(400, 400, "发送目标类型不能为空") + return + } + + err = services.UpdateSentReminder(p.BatchID, p.Title, p.Content, p.TargetType, p.TargetRoleID, p.TargetTenantID) + if err != nil { + c.jsonErr(500, 500, "修改失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"} + _ = c.ServeJSON() +} + +// DeleteSentBatch POST /platform/sitereminder/deleteSent +func (c *PlatformSiteReminderController) DeleteSentBatch() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p struct { + BatchID string `json:"batch_id"` + } + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if p.BatchID == "" { + c.jsonErr(400, 400, "批次号不能为空") + return + } + + err = services.DeleteSentReminderBatch(p.BatchID) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_sms.go b/go/controllers/platform_sms.go index 3c4c5d1..41eb2af 100644 --- a/go/controllers/platform_sms.go +++ b/go/controllers/platform_sms.go @@ -1,498 +1,498 @@ -package controllers - -import ( - "bytes" - "crypto/rand" - "encoding/json" - "fmt" - "io" - "net/http" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformSMSController 短信配置(yz_system_sms),兼容旧前端 /platform/sms/* 接口 -type PlatformSMSController struct { - beego.Controller -} - -func (c *PlatformSMSController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.SplitN(auth, " ", 2) - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("认证信息格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("无效的token") - } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") - } - return claims, nil -} - -func (c *PlatformSMSController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// GetSmsInfo GET /platform/sms/info -// 返回 data[0],字段兼容 backend_url/api_key 与 backendUrl/apiKey(沿用旧前端) -func (c *PlatformSMSController) GetSmsInfo() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - backendURL := models.GetPlatformSettingValue("sms_custom_url", "") - apiKey := models.GetPlatformSettingValue("sms_custom_key", "") - - data := []map[string]interface{}{{ - "backend_url": backendURL, - "api_key": apiKey, - "backendUrl": backendURL, - "apiKey": apiKey, - }} - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "获取成功", "data": data} - _ = c.ServeJSON() -} - -type smsEditPayload struct { - BackendUrl string `json:"backendUrl"` - BackendURL string `json:"backend_url"` - ApiKey string `json:"apiKey"` - APIKey string `json:"api_key"` -} - -// EditSmsInfo POST /platform/sms/editinfo -// 将旧前端的 backendUrl/apiKey 落到 yz_platform_normal_setting 表中 -func (c *PlatformSMSController) EditSmsInfo() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p smsEditPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - backendURL := strings.TrimSpace(p.BackendUrl) - if backendURL == "" { - backendURL = strings.TrimSpace(p.BackendURL) - } - apiKey := strings.TrimSpace(p.ApiKey) - if apiKey == "" { - apiKey = strings.TrimSpace(p.APIKey) - } - if backendURL == "" { - c.jsonErr(400, 400, "请输入短信网关地址") - return - } - if apiKey == "" { - c.jsonErr(400, 400, "请输入API KEY") - return - } - - settings := []struct { - code string - name string - value string - remark string - }{ - {"sms_custom_url", "自定义短信网关地址", backendURL, ""}, - {"sms_custom_key", "自定义短信API KEY", apiKey, ""}, - } - - for _, item := range settings { - var setting models.PlatformNormalSetting - err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). - Filter("code", item.code). - Filter("delete_time__isnull", true). - One(&setting) - if err == nil { - setting.Value = item.value - setting.Name = item.name - setting.Remark = item.remark - now := time.Now() - setting.UpdateTime = &now - _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } else { - newSetting := models.PlatformNormalSetting{ - Name: item.name, - Code: item.code, - Value: item.value, - Remark: item.remark, - CreateTime: time.Now(), - } - _, err = models.Orm.Insert(&newSetting) - if err != nil { - c.jsonErr(500, 500, "保存失败: "+err.Error()) - return - } - } - } - - updated := []map[string]interface{}{{ - "backend_url": backendURL, - "api_key": apiKey, - }} - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功", "data": updated} - _ = c.ServeJSON() -} - -type smsTestPayload struct { - BackendUrl string `json:"backendUrl"` - BackendURL string `json:"backend_url"` - ApiKey string `json:"apiKey"` - APIKey string `json:"api_key"` - Tid *uint64 `json:"tid"` - Phone string `json:"phone"` - Content string `json:"content"` -} - -// SendTestSms POST /platform/sms/sendtest -// 调用短信网关入队接口:{backendUrl}/api/v1/business/outbound-tasks,header: X-Api-Key -func (c *PlatformSMSController) SendTestSms() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p smsTestPayload - if err := json.Unmarshal(raw, &p); err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - - phone := strings.TrimSpace(p.Phone) - if phone == "" { - c.jsonErr(400, 400, "缺少测试手机号") - return - } - if !strings.HasPrefix(phone, "+") { - c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)") - return - } - for _, ch := range phone[1:] { - if ch < '0' || ch > '9' { - c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)") - return - } - } - - backendURL := strings.TrimSpace(p.BackendUrl) - if backendURL == "" { - backendURL = strings.TrimSpace(p.BackendURL) - } - apiKey := strings.TrimSpace(p.ApiKey) - if apiKey == "" { - apiKey = strings.TrimSpace(p.APIKey) - } - - // 兜底:body 未带时从默认配置取 - if backendURL == "" || apiKey == "" { - if backendURL == "" { - backendURL = models.GetPlatformSettingValue("sms_custom_url", "") - } - if apiKey == "" { - apiKey = models.GetPlatformSettingValue("sms_custom_key", "") - } - } - if backendURL == "" { - c.jsonErr(400, 400, "请先配置短信网关地址 backendUrl") - return - } - if apiKey == "" { - c.jsonErr(400, 400, "请先配置短信网关 API KEY") - return - } - - content := strings.TrimSpace(p.Content) - code := randomDigits6() - if content == "" { - content = "短信测试验证码:" + code - } - - enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks" - payload := map[string]interface{}{ - "phone": phone, - "content": content, - } - bs, _ := json.Marshal(payload) - - client := &http.Client{Timeout: 10 * time.Second} - req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs)) - if err != nil { - c.jsonErr(500, 500, "创建请求失败: "+err.Error()) - return - } - req.Header.Set("X-Api-Key", apiKey) - req.Header.Set("Content-Type", "application/json; charset=utf-8") - req.Header.Set("Accept", "application/json") - - resp, err := client.Do(req) - if err != nil { - c.jsonErr(500, 500, "短信网关入队失败: "+err.Error()) - return - } - defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - msg := strings.TrimSpace(string(body)) - if msg == "" { - msg = resp.Status - } - c.jsonErr(500, 500, "短信网关入队失败: "+msg) - return - } - - bodyStr := string(body) - report := strings.TrimSpace(bodyStr) - var reportPtr *string - if report != "" { - reportPtr = &bodyStr - } - - // 网关 HTTP 2xx:平台侧视为「已受理并成功提交」;与前端 tasklist 中 status=3「发送成功」对齐 - taskStatus := 3 - - // 若网关返回 JSON 且含通用状态字段,则优先映射(便于以后网关回传异步状态) - var gw map[string]interface{} - if json.Unmarshal(body, &gw) == nil { - if v, ok := gw["status"]; ok { - switch x := v.(type) { - case float64: - taskStatus = mapGatewayStatus(int(x)) - case string: - if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil { - taskStatus = mapGatewayStatus(n) - } - } - } - } - - // 写入本地任务表(用于前端列表/对账) - now := time.Now() - task := &models.SystemSMSTask{ - Tid: p.Tid, // 测试可为空 - ApiKey: apiKey, - Phone: phone, - Content: &content, - Status: taskStatus, - Code: code, - ReportRaw: reportPtr, - CreateTime: &now, - UpdateTime: &now, - } - taskID, terr := models.Orm.Insert(task) - if terr != nil { - // 入队已成功,任务写库失败也不影响短信发送,只返回提示 - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "短信测试任务入队成功(任务写库失败)", - "data": map[string]interface{}{ - "taskId": nil, - "code": code, - "gatewayResp": json.RawMessage(body), - }, - } - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "短信测试任务入队成功", - "data": map[string]interface{}{ - "taskId": uint64(taskID), - "code": code, - "gatewayResp": json.RawMessage(body), - }, - } - _ = c.ServeJSON() -} - -// GetSmsTaskList GET /platform/sms/taskList -func (c *PlatformSMSController) GetSmsTaskList() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - statusStr := strings.TrimSpace(c.GetString("status")) - phoneKw := strings.TrimSpace(c.GetString("phone")) - tidStr := strings.TrimSpace(c.GetString("tid")) - - qs := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("delete_time__isnull", true) - if statusStr != "" { - if st, err := strconv.Atoi(statusStr); err == nil { - qs = qs.Filter("status", st) - } - } - if phoneKw != "" { - qs = qs.Filter("phone__icontains", phoneKw) - } - if tidStr != "" { - if tid, err := strconv.ParseUint(tidStr, 10, 64); err == nil && tid > 0 { - qs = qs.Filter("tid", tid) - } - } - - var rows []models.SystemSMSTask - _, err := qs.OrderBy("-id").All(&rows) - if err != nil { - c.jsonErr(500, 500, "获取短信任务列表失败: "+err.Error()) - return - } - - list := make([]map[string]interface{}, 0, len(rows)) - for i := range rows { - item := map[string]interface{}{ - "id": rows[i].ID, - "api_key": rows[i].ApiKey, - "phone": rows[i].Phone, - "content": "", - "status": rows[i].Status, - "code": rows[i].Code, - "report_raw": rows[i].ReportRaw, - "create_time": "", - "update_time": "", - } - if rows[i].Tid != nil { - item["tid"] = *rows[i].Tid - } - if rows[i].Content != nil { - item["content"] = *rows[i].Content - } - if rows[i].CreateTime != nil { - item["create_time"] = rows[i].CreateTime.Format("2006-01-02 15:04:05") - } - if rows[i].UpdateTime != nil { - item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") - } - list = append(list, item) - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "list": list} - _ = c.ServeJSON() -} - -// EditSmsTask POST /platform/sms/taskEdit/:id -func (c *PlatformSMSController) EditSmsTask() { - if _, err := c.platformClaims(); err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - idStr := c.Ctx.Input.Param(":id") - id, err := strconv.ParseUint(idStr, 10, 64) - if err != nil || id == 0 { - c.jsonErr(400, 400, "无效ID") - return - } - raw, err := io.ReadAll(c.Ctx.Request.Body) - if err != nil { - c.jsonErr(400, 400, "参数错误") - return - } - var p map[string]interface{} - _ = json.Unmarshal(raw, &p) - - up := map[string]interface{}{} - if v, ok := p["status"]; ok { - switch x := v.(type) { - case float64: - up["status"] = int(x) - case string: - if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil { - up["status"] = n - } - } - } - if v, ok := p["report_raw"]; ok { - if s, ok := v.(string); ok { - up["report_raw"] = s - } - } - if v, ok := p["content"]; ok { - if s, ok := v.(string); ok { - up["content"] = s - } - } - if len(up) == 0 { - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() - return - } - now := time.Now() - up["update_time"] = now - n, err := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("id", id).Update(up) - if err != nil { - c.jsonErr(500, 500, "更新失败: "+err.Error()) - return - } - if n == 0 { - c.jsonErr(404, 404, "记录不存在") - return - } - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -func randomDigits6() string { - // 生成 6 位数字字符串 - b := make([]byte, 4) - if _, err := rand.Read(b); err != nil { - return "123456" - } - n := int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3]) - if n < 0 { - n = -n - } - code := n%900000 + 100000 - return strconv.Itoa(code) -} - -// mapGatewayStatus 将网关侧 status 粗略映射到前端列表:0待发送 1发送中 2失败 3成功 -func mapGatewayStatus(st int) int { - switch st { - case 0: - return 0 - case 1, 4, 5: - return 1 - case 2, 6: - return 2 - case 3: - return 3 - default: - // 网关枚举未约定时:HTTP 已 2xx,按「已成功提交」显示为发送成功 - return 3 - } -} +package controllers + +import ( + "bytes" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformSMSController 短信配置(yz_system_sms),兼容旧前端 /platform/sms/* 接口 +type PlatformSMSController struct { + beego.Controller +} + +func (c *PlatformSMSController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformSMSController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// GetSmsInfo GET /platform/sms/info +// 返回 data[0],字段兼容 backend_url/api_key 与 backendUrl/apiKey(沿用旧前端) +func (c *PlatformSMSController) GetSmsInfo() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + backendURL := models.GetPlatformSettingValue("sms_custom_url", "") + apiKey := models.GetPlatformSettingValue("sms_custom_key", "") + + data := []map[string]interface{}{{ + "backend_url": backendURL, + "api_key": apiKey, + "backendUrl": backendURL, + "apiKey": apiKey, + }} + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "获取成功", "data": data} + _ = c.ServeJSON() +} + +type smsEditPayload struct { + BackendUrl string `json:"backendUrl"` + BackendURL string `json:"backend_url"` + ApiKey string `json:"apiKey"` + APIKey string `json:"api_key"` +} + +// EditSmsInfo POST /platform/sms/editinfo +// 将旧前端的 backendUrl/apiKey 落到 yz_platform_normal_setting 表中 +func (c *PlatformSMSController) EditSmsInfo() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p smsEditPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + backendURL := strings.TrimSpace(p.BackendUrl) + if backendURL == "" { + backendURL = strings.TrimSpace(p.BackendURL) + } + apiKey := strings.TrimSpace(p.ApiKey) + if apiKey == "" { + apiKey = strings.TrimSpace(p.APIKey) + } + if backendURL == "" { + c.jsonErr(400, 400, "请输入短信网关地址") + return + } + if apiKey == "" { + c.jsonErr(400, 400, "请输入API KEY") + return + } + + settings := []struct { + code string + name string + value string + remark string + }{ + {"sms_custom_url", "自定义短信网关地址", backendURL, ""}, + {"sms_custom_key", "自定义短信API KEY", apiKey, ""}, + } + + for _, item := range settings { + var setting models.PlatformNormalSetting + err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } else { + newSetting := models.PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = models.Orm.Insert(&newSetting) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } + } + + updated := []map[string]interface{}{{ + "backend_url": backendURL, + "api_key": apiKey, + }} + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功", "data": updated} + _ = c.ServeJSON() +} + +type smsTestPayload struct { + BackendUrl string `json:"backendUrl"` + BackendURL string `json:"backend_url"` + ApiKey string `json:"apiKey"` + APIKey string `json:"api_key"` + Tid *uint64 `json:"tid"` + Phone string `json:"phone"` + Content string `json:"content"` +} + +// SendTestSms POST /platform/sms/sendtest +// 调用短信网关入队接口:{backendUrl}/api/v1/business/outbound-tasks,header: X-Api-Key +func (c *PlatformSMSController) SendTestSms() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p smsTestPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + phone := strings.TrimSpace(p.Phone) + if phone == "" { + c.jsonErr(400, 400, "缺少测试手机号") + return + } + if !strings.HasPrefix(phone, "+") { + c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)") + return + } + for _, ch := range phone[1:] { + if ch < '0' || ch > '9' { + c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)") + return + } + } + + backendURL := strings.TrimSpace(p.BackendUrl) + if backendURL == "" { + backendURL = strings.TrimSpace(p.BackendURL) + } + apiKey := strings.TrimSpace(p.ApiKey) + if apiKey == "" { + apiKey = strings.TrimSpace(p.APIKey) + } + + // 兜底:body 未带时从默认配置取 + if backendURL == "" || apiKey == "" { + if backendURL == "" { + backendURL = models.GetPlatformSettingValue("sms_custom_url", "") + } + if apiKey == "" { + apiKey = models.GetPlatformSettingValue("sms_custom_key", "") + } + } + if backendURL == "" { + c.jsonErr(400, 400, "请先配置短信网关地址 backendUrl") + return + } + if apiKey == "" { + c.jsonErr(400, 400, "请先配置短信网关 API KEY") + return + } + + content := strings.TrimSpace(p.Content) + code := randomDigits6() + if content == "" { + content = "短信测试验证码:" + code + } + + enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks" + payload := map[string]interface{}{ + "phone": phone, + "content": content, + } + bs, _ := json.Marshal(payload) + + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs)) + if err != nil { + c.jsonErr(500, 500, "创建请求失败: "+err.Error()) + return + } + req.Header.Set("X-Api-Key", apiKey) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + c.jsonErr(500, 500, "短信网关入队失败: "+err.Error()) + return + } + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + msg := strings.TrimSpace(string(body)) + if msg == "" { + msg = resp.Status + } + c.jsonErr(500, 500, "短信网关入队失败: "+msg) + return + } + + bodyStr := string(body) + report := strings.TrimSpace(bodyStr) + var reportPtr *string + if report != "" { + reportPtr = &bodyStr + } + + // 网关 HTTP 2xx:平台侧视为「已受理并成功提交」;与前端 tasklist 中 status=3「发送成功」对齐 + taskStatus := 3 + + // 若网关返回 JSON 且含通用状态字段,则优先映射(便于以后网关回传异步状态) + var gw map[string]interface{} + if json.Unmarshal(body, &gw) == nil { + if v, ok := gw["status"]; ok { + switch x := v.(type) { + case float64: + taskStatus = mapGatewayStatus(int(x)) + case string: + if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil { + taskStatus = mapGatewayStatus(n) + } + } + } + } + + // 写入本地任务表(用于前端列表/对账) + now := time.Now() + task := &models.SystemSMSTask{ + Tid: p.Tid, // 测试可为空 + ApiKey: apiKey, + Phone: phone, + Content: &content, + Status: taskStatus, + Code: code, + ReportRaw: reportPtr, + CreateTime: &now, + UpdateTime: &now, + } + taskID, terr := models.Orm.Insert(task) + if terr != nil { + // 入队已成功,任务写库失败也不影响短信发送,只返回提示 + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "短信测试任务入队成功(任务写库失败)", + "data": map[string]interface{}{ + "taskId": nil, + "code": code, + "gatewayResp": json.RawMessage(body), + }, + } + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "短信测试任务入队成功", + "data": map[string]interface{}{ + "taskId": uint64(taskID), + "code": code, + "gatewayResp": json.RawMessage(body), + }, + } + _ = c.ServeJSON() +} + +// GetSmsTaskList GET /platform/sms/taskList +func (c *PlatformSMSController) GetSmsTaskList() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + statusStr := strings.TrimSpace(c.GetString("status")) + phoneKw := strings.TrimSpace(c.GetString("phone")) + tidStr := strings.TrimSpace(c.GetString("tid")) + + qs := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("delete_time__isnull", true) + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + qs = qs.Filter("status", st) + } + } + if phoneKw != "" { + qs = qs.Filter("phone__icontains", phoneKw) + } + if tidStr != "" { + if tid, err := strconv.ParseUint(tidStr, 10, 64); err == nil && tid > 0 { + qs = qs.Filter("tid", tid) + } + } + + var rows []models.SystemSMSTask + _, err := qs.OrderBy("-id").All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取短信任务列表失败: "+err.Error()) + return + } + + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + item := map[string]interface{}{ + "id": rows[i].ID, + "api_key": rows[i].ApiKey, + "phone": rows[i].Phone, + "content": "", + "status": rows[i].Status, + "code": rows[i].Code, + "report_raw": rows[i].ReportRaw, + "create_time": "", + "update_time": "", + } + if rows[i].Tid != nil { + item["tid"] = *rows[i].Tid + } + if rows[i].Content != nil { + item["content"] = *rows[i].Content + } + if rows[i].CreateTime != nil { + item["create_time"] = rows[i].CreateTime.Format("2006-01-02 15:04:05") + } + if rows[i].UpdateTime != nil { + item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05") + } + list = append(list, item) + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "list": list} + _ = c.ServeJSON() +} + +// EditSmsTask POST /platform/sms/taskEdit/:id +func (c *PlatformSMSController) EditSmsTask() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p map[string]interface{} + _ = json.Unmarshal(raw, &p) + + up := map[string]interface{}{} + if v, ok := p["status"]; ok { + switch x := v.(type) { + case float64: + up["status"] = int(x) + case string: + if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil { + up["status"] = n + } + } + } + if v, ok := p["report_raw"]; ok { + if s, ok := v.(string); ok { + up["report_raw"] = s + } + } + if v, ok := p["content"]; ok { + if s, ok := v.(string); ok { + up["content"] = s + } + } + if len(up) == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() + return + } + now := time.Now() + up["update_time"] = now + n, err := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("id", id).Update(up) + if err != nil { + c.jsonErr(500, 500, "更新失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +func randomDigits6() string { + // 生成 6 位数字字符串 + b := make([]byte, 4) + if _, err := rand.Read(b); err != nil { + return "123456" + } + n := int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3]) + if n < 0 { + n = -n + } + code := n%900000 + 100000 + return strconv.Itoa(code) +} + +// mapGatewayStatus 将网关侧 status 粗略映射到前端列表:0待发送 1发送中 2失败 3成功 +func mapGatewayStatus(st int) int { + switch st { + case 0: + return 0 + case 1, 4, 5: + return 1 + case 2, 6: + return 2 + case 3: + return 3 + default: + // 网关枚举未约定时:HTTP 已 2xx,按「已成功提交」显示为发送成功 + return 3 + } +} diff --git a/go/controllers/platform_software_upgrade.go b/go/controllers/platform_software_upgrade.go index 28cdf88..e2dc9ec 100644 --- a/go/controllers/platform_software_upgrade.go +++ b/go/controllers/platform_software_upgrade.go @@ -78,20 +78,23 @@ func (c *PlatformSoftwareUpgradeController) backfillDownloadURL(productID uint64 func (c *PlatformSoftwareUpgradeController) rowToMap(row *models.SystemSoftwareUpgrade) map[string]interface{} { scheme, host := services.PublicRequestBaseURL(&c.Controller) resolved := services.ResolveSoftwareDownloadURL(scheme, host, row.DownloadURL, row.FileID) + resolvedMulti := services.ResolveSoftwareDownloadURLs(scheme, host, row.DownloadURLs) return map[string]interface{}{ - "id": row.ID, - "name": row.Name, - "code": row.Code, - "latestVersion": row.LatestVersion, - "fileId": row.FileID, - "downloadUrl": row.DownloadURL, - "resolvedDownloadUrl": resolved, - "forceUpdate": row.ForceUpdate, - "releaseNotes": row.ReleaseNotes, - "status": row.Status, - "sort": row.Sort, - "createTime": row.CreateTime, - "updateTime": row.UpdateTime, + "id": row.ID, + "name": row.Name, + "code": row.Code, + "latestVersion": row.LatestVersion, + "fileId": row.FileID, + "downloadUrl": row.DownloadURL, + "downloadUrls": row.DownloadURLs, + "resolvedDownloadUrl": resolved, + "resolvedDownloadUrls": resolvedMulti, + "forceUpdate": row.ForceUpdate, + "releaseNotes": row.ReleaseNotes, + "status": row.Status, + "sort": row.Sort, + "createTime": row.CreateTime, + "updateTime": row.UpdateTime, } } @@ -158,15 +161,16 @@ func (c *PlatformSoftwareUpgradeController) Detail() { } type softwareUpgradePayload struct { - Name *string `json:"name"` - Code *string `json:"code"` - LatestVersion *string `json:"latestVersion"` - FileID *uint64 `json:"fileId"` - DownloadURL *string `json:"downloadUrl"` - ForceUpdate *int8 `json:"forceUpdate"` - ReleaseNotes *string `json:"releaseNotes"` - Status *int8 `json:"status"` - Sort *int `json:"sort"` + Name *string `json:"name"` + Code *string `json:"code"` + LatestVersion *string `json:"latestVersion"` + FileID *uint64 `json:"fileId"` + DownloadURL *string `json:"downloadUrl"` + DownloadURLs map[string]string `json:"downloadUrls"` + ForceUpdate *int8 `json:"forceUpdate"` + ReleaseNotes *string `json:"releaseNotes"` + Status *int8 `json:"status"` + Sort *int `json:"sort"` } // Create POST /platform/softwareupgrade @@ -198,6 +202,12 @@ func (c *PlatformSoftwareUpgradeController) Create() { Status: 1, Sort: 0, } + if len(p.DownloadURLs) > 0 { + if b, err := json.Marshal(p.DownloadURLs); err == nil { + s := string(b) + row.DownloadURLs = &s + } + } if p.ForceUpdate != nil { row.ForceUpdate = *p.ForceUpdate } @@ -263,6 +273,22 @@ func (c *PlatformSoftwareUpgradeController) Update() { if p.DownloadURL != nil { up["download_url"] = strings.TrimSpace(*p.DownloadURL) } + if p.DownloadURLs != nil { + clean := map[string]string{} + for k, v := range p.DownloadURLs { + platform := strings.ToLower(strings.TrimSpace(k)) + url := strings.TrimSpace(v) + if platform == "" || url == "" { + continue + } + clean[platform] = url + } + if len(clean) == 0 { + up["download_urls"] = nil + } else if b, err := json.Marshal(clean); err == nil { + up["download_urls"] = string(b) + } + } if p.ForceUpdate != nil { up["force_update"] = *p.ForceUpdate } diff --git a/go/controllers/platform_tenant.go b/go/controllers/platform_tenant.go index e0958c0..134c68b 100644 --- a/go/controllers/platform_tenant.go +++ b/go/controllers/platform_tenant.go @@ -1,343 +1,343 @@ -package controllers - -import ( - "encoding/json" - "io" - "strconv" - "strings" - "time" - - "server/models" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformTenantController 平台端租户管ç? -type PlatformTenantController struct { - beego.Controller -} - -type tenantDTO struct { - ID uint64 `json:"id"` - TenantCode string `json:"tenant_code"` - TenantName string `json:"tenant_name"` - ContactPerson string `json:"contact_person"` - ContactPhone string `json:"contact_phone"` - ContactEmail string `json:"contact_email"` - Address string `json:"address"` - Worktime string `json:"worktime"` - Status int8 `json:"status"` - Remark string `json:"remark"` - CreateTime *time.Time `json:"create_time,omitempty"` - UpdateTime *time.Time `json:"update_time,omitempty"` - DeleteTime *time.Time `json:"delete_time,omitempty"` -} - -func stringValue(s *string) string { - if s == nil { - return "" - } - return *s -} - -func stringPtr(s string) *string { - return &s -} - -func toTenantDTO(t models.SystemTenant) tenantDTO { - ct := t.CreateTime - ut := t.UpdateTime - return tenantDTO{ - ID: t.ID, - TenantCode: t.TenantCode, - TenantName: t.TenantName, - ContactPerson: stringValue(t.ContactPerson), - ContactPhone: stringValue(t.ContactPhone), - ContactEmail: stringValue(t.ContactEmail), - Address: stringValue(t.Address), - Worktime: stringValue(t.Worktime), - Status: t.Status, - Remark: stringValue(t.Remark), - CreateTime: &ct, - UpdateTime: &ut, - DeleteTime: t.DeleteTime, - } -} - -// GetTenant 获取租户列表 -// GET /platform/tenant/getTenant?page=1&pageSize=10&tenant_name=...&tenant_code=...&contact_person=...&contact_phone=... -func (c *PlatformTenantController) GetTenant() { - page, _ := c.GetInt("page", 1) - pageSize, _ := c.GetInt("pageSize", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 10 - } - - tenantName := strings.TrimSpace(c.GetString("tenant_name")) - tenantCode := strings.TrimSpace(c.GetString("tenant_code")) - contactPerson := strings.TrimSpace(c.GetString("contact_person")) - contactPhone := strings.TrimSpace(c.GetString("contact_phone")) - - qs := models.Orm.QueryTable(new(models.SystemTenant)) - if tenantName != "" { - qs = qs.Filter("tenant_name__icontains", tenantName) - } - if tenantCode != "" { - qs = qs.Filter("tenant_code__icontains", tenantCode) - } - if contactPerson != "" { - qs = qs.Filter("contact_person__icontains", contactPerson) - } - if contactPhone != "" { - qs = qs.Filter("contact_phone__icontains", contactPhone) - } - - total, err := qs.Count() - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取租户失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - var rows []models.SystemTenant - _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取租户失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - list := make([]tenantDTO, 0, len(rows)) - for _, t := range rows { - list = append(list, toTenantDTO(t)) - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": list, - "total": total, - }, - } - _ = c.ServeJSON() -} - -// GetTenantDetail čŽˇĺ–ç§ŸćˆˇčŻŚćƒ -// GET /platform/tenant/getTenantDetail/:id -func (c *PlatformTenantController) GetTenantDetail() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} - _ = c.ServeJSON() - return - } - - var t models.SystemTenant - err = models.Orm.QueryTable(new(models.SystemTenant)).Filter("id", id).One(&t) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "租户不存在"} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": toTenantDTO(t), - } - _ = c.ServeJSON() -} - -type tenantPayload struct { - TenantCode string `json:"tenant_code"` - TenantName string `json:"tenant_name"` - ContactPerson string `json:"contact_person"` - ContactPhone string `json:"contact_phone"` - ContactEmail string `json:"contact_email"` - Address string `json:"address"` - Worktime string `json:"worktime"` - Status *int8 `json:"status"` - Remark string `json:"remark"` -} - -func (c *PlatformTenantController) parseTenantPayload() (tenantPayload, error) { - // 优先从表单读取(createTenant 使用 multipart/form-dataďź? - p := tenantPayload{ - TenantCode: strings.TrimSpace(c.GetString("tenant_code")), - TenantName: strings.TrimSpace(c.GetString("tenant_name")), - ContactPerson: strings.TrimSpace(c.GetString("contact_person")), - ContactPhone: strings.TrimSpace(c.GetString("contact_phone")), - ContactEmail: strings.TrimSpace(c.GetString("contact_email")), - Address: strings.TrimSpace(c.GetString("address")), - Worktime: strings.TrimSpace(c.GetString("worktime")), - Remark: strings.TrimSpace(c.GetString("remark")), - } - if s := strings.TrimSpace(c.GetString("status")); s != "" { - if v, err := strconv.ParseInt(s, 10, 8); err == nil { - tmp := int8(v) - p.Status = &tmp - } - } - - // 如果关键字段为空,尝试从 JSON body 解析(editTenant 靘莤 JSONďź? - if p.TenantName == "" && p.TenantCode == "" { - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if len(raw) > 0 { - _ = json.Unmarshal(raw, &p) - } - } - return p, nil -} - -// CreateTenant 创建租户 -// POST /platform/tenant/createTenant -func (c *PlatformTenantController) CreateTenant() { - p, _ := c.parseTenantPayload() - if strings.TrimSpace(p.TenantName) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户名称不能为空"} - _ = c.ServeJSON() - return - } - if strings.TrimSpace(p.TenantCode) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户编码不能为空"} - _ = c.ServeJSON() - return - } - - // 校验编码唯一 - cnt, err := models.Orm.QueryTable(new(models.SystemTenant)).Filter("tenant_code", p.TenantCode).Count() - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} - _ = c.ServeJSON() - return - } - if cnt > 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户编码已存在"} - _ = c.ServeJSON() - return - } - - status := int8(1) - if p.Status != nil { - status = *p.Status - } - - t := models.SystemTenant{ - TenantCode: p.TenantCode, - TenantName: p.TenantName, - ContactPerson: stringPtr(p.ContactPerson), - ContactPhone: stringPtr(p.ContactPhone), - ContactEmail: stringPtr(p.ContactEmail), - Address: stringPtr(p.Address), - Worktime: stringPtr(p.Worktime), - Status: status, - Remark: stringPtr(p.Remark), - } - - id, err := models.Orm.Insert(&t) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"id": id}, - } - _ = c.ServeJSON() -} - -// EditTenant 编辑租户 -// POST /platform/tenant/editTenant/:id -func (c *PlatformTenantController) EditTenant() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} - _ = c.ServeJSON() - return - } - - p, _ := c.parseTenantPayload() - if strings.TrimSpace(p.TenantName) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户名称不能为空"} - _ = c.ServeJSON() - return - } - - update := map[string]interface{}{ - "tenant_name": p.TenantName, - "contact_person": p.ContactPerson, - "contact_phone": p.ContactPhone, - "contact_email": p.ContactEmail, - "address": p.Address, - "worktime": p.Worktime, - "remark": p.Remark, - } - if p.Status != nil { - update["status"] = *p.Status - } - - _, err = models.Orm.QueryTable(new(models.SystemTenant)).Filter("id", id).Update(update) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// DeleteTenant 删除租户 -// DELETE /platform/tenant/deleteTenant/:id -func (c *PlatformTenantController) DeleteTenant() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} - _ = c.ServeJSON() - return - } - - _, err = models.Orm.QueryTable(new(models.SystemTenant)).Filter("id", id).Delete() - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// FindTenantCode 校验租户编码是否重复 -// GET /platform/tenant/findTenantCode?tenant_code=xxxxxx -// 返回 code=200 表示可用;非200表示重复/不可用(前端会自动重新生成) -func (c *PlatformTenantController) FindTenantCode() { - code := strings.TrimSpace(c.GetString("tenant_code")) - if code == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tenant_code 不能为空"} - _ = c.ServeJSON() - return - } - - cnt, err := models.Orm.QueryTable(new(models.SystemTenant)).Filter("tenant_code", code).Count() - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "校验失败: " + err.Error()} - _ = c.ServeJSON() - return - } - if cnt > 0 { - c.Data["json"] = map[string]interface{}{"code": 409, "msg": "租户编码已存在"} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "ok"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "io" + "strconv" + "strings" + "time" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformTenantController 平台端租户管ç? +type PlatformTenantController struct { + beego.Controller +} + +type tenantDTO struct { + ID uint64 `json:"id"` + TenantCode string `json:"tenant_code"` + TenantName string `json:"tenant_name"` + ContactPerson string `json:"contact_person"` + ContactPhone string `json:"contact_phone"` + ContactEmail string `json:"contact_email"` + Address string `json:"address"` + Worktime string `json:"worktime"` + Status int8 `json:"status"` + Remark string `json:"remark"` + CreateTime *time.Time `json:"create_time,omitempty"` + UpdateTime *time.Time `json:"update_time,omitempty"` + DeleteTime *time.Time `json:"delete_time,omitempty"` +} + +func stringValue(s *string) string { + if s == nil { + return "" + } + return *s +} + +func stringPtr(s string) *string { + return &s +} + +func toTenantDTO(t models.SystemTenant) tenantDTO { + ct := t.CreateTime + ut := t.UpdateTime + return tenantDTO{ + ID: t.ID, + TenantCode: t.TenantCode, + TenantName: t.TenantName, + ContactPerson: stringValue(t.ContactPerson), + ContactPhone: stringValue(t.ContactPhone), + ContactEmail: stringValue(t.ContactEmail), + Address: stringValue(t.Address), + Worktime: stringValue(t.Worktime), + Status: t.Status, + Remark: stringValue(t.Remark), + CreateTime: &ct, + UpdateTime: &ut, + DeleteTime: t.DeleteTime, + } +} + +// GetTenant 获取租户列表 +// GET /platform/tenant/getTenant?page=1&pageSize=10&tenant_name=...&tenant_code=...&contact_person=...&contact_phone=... +func (c *PlatformTenantController) GetTenant() { + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + + tenantName := strings.TrimSpace(c.GetString("tenant_name")) + tenantCode := strings.TrimSpace(c.GetString("tenant_code")) + contactPerson := strings.TrimSpace(c.GetString("contact_person")) + contactPhone := strings.TrimSpace(c.GetString("contact_phone")) + + qs := models.Orm.QueryTable(new(models.SystemTenant)) + if tenantName != "" { + qs = qs.Filter("tenant_name__icontains", tenantName) + } + if tenantCode != "" { + qs = qs.Filter("tenant_code__icontains", tenantCode) + } + if contactPerson != "" { + qs = qs.Filter("contact_person__icontains", contactPerson) + } + if contactPhone != "" { + qs = qs.Filter("contact_phone__icontains", contactPhone) + } + + total, err := qs.Count() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取租户失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + var rows []models.SystemTenant + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取租户失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + list := make([]tenantDTO, 0, len(rows)) + for _, t := range rows { + list = append(list, toTenantDTO(t)) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": list, + "total": total, + }, + } + _ = c.ServeJSON() +} + +// GetTenantDetail čŽˇĺ–ç§ŸćˆˇčŻŚćƒ +// GET /platform/tenant/getTenantDetail/:id +func (c *PlatformTenantController) GetTenantDetail() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + var t models.SystemTenant + err = models.Orm.QueryTable(new(models.SystemTenant)).Filter("id", id).One(&t) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "租户不存在"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": toTenantDTO(t), + } + _ = c.ServeJSON() +} + +type tenantPayload struct { + TenantCode string `json:"tenant_code"` + TenantName string `json:"tenant_name"` + ContactPerson string `json:"contact_person"` + ContactPhone string `json:"contact_phone"` + ContactEmail string `json:"contact_email"` + Address string `json:"address"` + Worktime string `json:"worktime"` + Status *int8 `json:"status"` + Remark string `json:"remark"` +} + +func (c *PlatformTenantController) parseTenantPayload() (tenantPayload, error) { + // 优先从表单读取(createTenant 使用 multipart/form-dataďź? + p := tenantPayload{ + TenantCode: strings.TrimSpace(c.GetString("tenant_code")), + TenantName: strings.TrimSpace(c.GetString("tenant_name")), + ContactPerson: strings.TrimSpace(c.GetString("contact_person")), + ContactPhone: strings.TrimSpace(c.GetString("contact_phone")), + ContactEmail: strings.TrimSpace(c.GetString("contact_email")), + Address: strings.TrimSpace(c.GetString("address")), + Worktime: strings.TrimSpace(c.GetString("worktime")), + Remark: strings.TrimSpace(c.GetString("remark")), + } + if s := strings.TrimSpace(c.GetString("status")); s != "" { + if v, err := strconv.ParseInt(s, 10, 8); err == nil { + tmp := int8(v) + p.Status = &tmp + } + } + + // 如果关键字段为空,尝试从 JSON body 解析(editTenant 靘莤 JSONďź? + if p.TenantName == "" && p.TenantCode == "" { + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if len(raw) > 0 { + _ = json.Unmarshal(raw, &p) + } + } + return p, nil +} + +// CreateTenant 创建租户 +// POST /platform/tenant/createTenant +func (c *PlatformTenantController) CreateTenant() { + p, _ := c.parseTenantPayload() + if strings.TrimSpace(p.TenantName) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户名称不能为空"} + _ = c.ServeJSON() + return + } + if strings.TrimSpace(p.TenantCode) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户编码不能为空"} + _ = c.ServeJSON() + return + } + + // 校验编码唯一 + cnt, err := models.Orm.QueryTable(new(models.SystemTenant)).Filter("tenant_code", p.TenantCode).Count() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} + _ = c.ServeJSON() + return + } + if cnt > 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户编码已存在"} + _ = c.ServeJSON() + return + } + + status := int8(1) + if p.Status != nil { + status = *p.Status + } + + t := models.SystemTenant{ + TenantCode: p.TenantCode, + TenantName: p.TenantName, + ContactPerson: stringPtr(p.ContactPerson), + ContactPhone: stringPtr(p.ContactPhone), + ContactEmail: stringPtr(p.ContactEmail), + Address: stringPtr(p.Address), + Worktime: stringPtr(p.Worktime), + Status: status, + Remark: stringPtr(p.Remark), + } + + id, err := models.Orm.Insert(&t) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"id": id}, + } + _ = c.ServeJSON() +} + +// EditTenant 编辑租户 +// POST /platform/tenant/editTenant/:id +func (c *PlatformTenantController) EditTenant() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + p, _ := c.parseTenantPayload() + if strings.TrimSpace(p.TenantName) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "租户名称不能为空"} + _ = c.ServeJSON() + return + } + + update := map[string]interface{}{ + "tenant_name": p.TenantName, + "contact_person": p.ContactPerson, + "contact_phone": p.ContactPhone, + "contact_email": p.ContactEmail, + "address": p.Address, + "worktime": p.Worktime, + "remark": p.Remark, + } + if p.Status != nil { + update["status"] = *p.Status + } + + _, err = models.Orm.QueryTable(new(models.SystemTenant)).Filter("id", id).Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteTenant 删除租户 +// DELETE /platform/tenant/deleteTenant/:id +func (c *PlatformTenantController) DeleteTenant() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + _, err = models.Orm.QueryTable(new(models.SystemTenant)).Filter("id", id).Delete() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// FindTenantCode 校验租户编码是否重复 +// GET /platform/tenant/findTenantCode?tenant_code=xxxxxx +// 返回 code=200 表示可用;非200表示重复/不可用(前端会自动重新生成) +func (c *PlatformTenantController) FindTenantCode() { + code := strings.TrimSpace(c.GetString("tenant_code")) + if code == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tenant_code 不能为空"} + _ = c.ServeJSON() + return + } + + cnt, err := models.Orm.QueryTable(new(models.SystemTenant)).Filter("tenant_code", code).Count() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "校验失败: " + err.Error()} + _ = c.ServeJSON() + return + } + if cnt > 0 { + c.Data["json"] = map[string]interface{}{"code": 409, "msg": "租户编码已存在"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "ok"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_tenant_user.go b/go/controllers/platform_tenant_user.go index 2ea7ff5..6cc6054 100644 --- a/go/controllers/platform_tenant_user.go +++ b/go/controllers/platform_tenant_user.go @@ -1,332 +1,332 @@ -package controllers - -import ( - "encoding/json" - "errors" - "io" - "math/rand" - "strconv" - "strings" - "time" - - "server/models" - "server/pkg/passwordutil" - "server/services" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformTenantUserController 平台租户用户绑定管理 -type PlatformTenantUserController struct { - beego.Controller -} - -type tenantUserPayload struct { - Tid uint64 `json:"tid"` - Uid uint64 `json:"uid"` - Account *string `json:"account"` - Name *string `json:"name"` - Phone *string `json:"phone"` - Email *string `json:"email"` - Password *string `json:"password"` - IsDefault *int8 `json:"is_default"` - Status *int8 `json:"status"` - Remark *string `json:"remark"` -} - -// GetTenantUserList 获取绑定列表(支持按 tid / uid 过滤,keyword 对姓名/手机/邮箱/账号模糊匹配) -// GET /platform/tenantUser/list?tid=1&uid=2&keyword=xxx -func (c *PlatformTenantUserController) GetTenantUserList() { - tid, _ := c.GetUint64("tid") - uid, _ := c.GetUint64("uid") - keyword := strings.TrimSpace(c.GetString("keyword")) - - qs := models.Orm.QueryTable(new(models.SystemTenantUser)) - - var cond *orm.Condition - needCond := false - if tid > 0 { - if cond == nil { - cond = orm.NewCondition() - } - cond = cond.And("tid", tid) - needCond = true - } - if uid > 0 { - if cond == nil { - cond = orm.NewCondition() - } - cond = cond.And("uid", uid) - needCond = true - } - if keyword != "" { - kwCond := orm.NewCondition() - kwCond = kwCond.Or("name__icontains", keyword). - Or("phone__icontains", keyword). - Or("email__icontains", keyword). - Or("account__icontains", keyword) - if cond == nil { - cond = kwCond - } else { - cond = cond.AndCond(kwCond) - } - needCond = true - } - if needCond { - qs = qs.SetCond(cond) - } - - var rows []models.SystemTenantUser - _, err := qs.OrderBy("-is_default", "-id").All(&rows) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "list": rows, - "total": len(rows), - }, - } - _ = c.ServeJSON() -} - -// GetTenantUsersByTid 兼容旧路由,根据租户 ID 获取租户用户列表 -// GET /platform/getTenantUsers/:tid -func (c *PlatformTenantUserController) GetTenantUsersByTid() { - tidStr := c.Ctx.Input.Param(":tid") - tid, _ := strconv.ParseUint(tidStr, 10, 64) - if tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} - _ = c.ServeJSON() - return - } - var rows []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("tid", tid). - OrderBy("-is_default", "-id"). - All(&rows) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"list": rows, "total": len(rows)}, - } - _ = c.ServeJSON() -} - -// GetTenantUserDetail 获取绑定详情 -// GET /platform/tenantUser/detail/:id -func (c *PlatformTenantUserController) GetTenantUserDetail() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} - _ = c.ServeJSON() - return - } - - var row models.SystemTenantUser - err = models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).One(&row) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 404, "msg": "记录不存在"} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} - _ = c.ServeJSON() -} - -// CreateTenantUser 创建租户用户绑定(写入 yz_system_tenant_user;uid 为空时自动生成) -// POST /platform/tenantUser/create -func (c *PlatformTenantUserController) CreateTenantUser() { - p, ok := c.parsePayload() - if !ok { - return - } - if p.Tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} - _ = c.ServeJSON() - return - } - if p.Account == nil || strings.TrimSpace(*p.Account) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} - _ = c.ServeJSON() - return - } - if p.Password == nil || strings.TrimSpace(*p.Password) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} - _ = c.ServeJSON() - return - } - hashed, err := passwordutil.Hash(*p.Password) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} - _ = c.ServeJSON() - return - } - p.Password = &hashed - if p.Uid == 0 { - uid, err := generateTenantUID(p.Tid) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "生成租户用户ID失败"} - _ = c.ServeJSON() - return - } - p.Uid = uid - } - - isDefault := int8(0) - status := int8(1) - if p.IsDefault != nil { - isDefault = *p.IsDefault - } - if p.Status != nil { - status = *p.Status - } - - id, err := services.BindTenantUser(p.Tid, p.Uid, p.Account, p.Name, p.Phone, p.Email, nil, nil, p.Password, isDefault, status, p.Remark) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} - _ = c.ServeJSON() - return - } - if isDefault == 1 { - _ = services.SetDefaultTenant(p.Uid, p.Tid) - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}} - _ = c.ServeJSON() -} - -// EditTenantUser 编辑绑定 -// POST /platform/tenantUser/edit/:id -func (c *PlatformTenantUserController) EditTenantUser() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} - _ = c.ServeJSON() - return - } - - p, ok := c.parsePayload() - if !ok { - return - } - - update := map[string]interface{}{} - if p.Tid > 0 { - update["tid"] = p.Tid - } - if p.Uid > 0 { - update["uid"] = p.Uid - } - if p.Account != nil { - update["account"] = p.Account - } - if p.Name != nil { - update["name"] = p.Name - } - if p.Phone != nil { - update["phone"] = p.Phone - } - if p.Email != nil { - update["email"] = p.Email - } - if p.Password != nil { - hashed, err := passwordutil.Hash(*p.Password) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} - _ = c.ServeJSON() - return - } - update["password"] = hashed - } - if p.IsDefault != nil { - update["is_default"] = *p.IsDefault - } - if p.Status != nil { - update["status"] = *p.Status - } - if p.Remark != nil { - update["remark"] = p.Remark - } - - if len(update) == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"} - _ = c.ServeJSON() - return - } - - _, err = models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).Update(update) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - if p.IsDefault != nil && *p.IsDefault == 1 && p.Uid > 0 && p.Tid > 0 { - _ = services.SetDefaultTenant(p.Uid, p.Tid) - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -// DeleteTenantUser 删除绑定 -// DELETE /platform/tenantUser/delete/:id -func (c *PlatformTenantUserController) DeleteTenantUser() { - id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) - if err != nil || id == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} - _ = c.ServeJSON() - return - } - - if err := services.UnbindTenantUser(id); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} - _ = c.ServeJSON() -} - -func (c *PlatformTenantUserController) parsePayload() (tenantUserPayload, bool) { - var p tenantUserPayload - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return tenantUserPayload{}, false - } - return p, true -} - -func generateTenantUID(tid uint64) (uint64, error) { - rand.Seed(time.Now().UnixNano()) - for i := 0; i < 8; i++ { - uid := uint64(10000000 + rand.Intn(90000000)) - cnt, err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("tid", tid). - Filter("uid", uid). - Count() - if err != nil { - return 0, err - } - if cnt == 0 { - return uid, nil - } - } - return 0, errors.New("uid collision") -} +package controllers + +import ( + "encoding/json" + "errors" + "io" + "math/rand" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/passwordutil" + "server/services" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformTenantUserController 平台租户用户绑定管理 +type PlatformTenantUserController struct { + beego.Controller +} + +type tenantUserPayload struct { + Tid uint64 `json:"tid"` + Uid uint64 `json:"uid"` + Account *string `json:"account"` + Name *string `json:"name"` + Phone *string `json:"phone"` + Email *string `json:"email"` + Password *string `json:"password"` + IsDefault *int8 `json:"is_default"` + Status *int8 `json:"status"` + Remark *string `json:"remark"` +} + +// GetTenantUserList 获取绑定列表(支持按 tid / uid 过滤,keyword 对姓名/手机/邮箱/账号模糊匹配) +// GET /platform/tenantUser/list?tid=1&uid=2&keyword=xxx +func (c *PlatformTenantUserController) GetTenantUserList() { + tid, _ := c.GetUint64("tid") + uid, _ := c.GetUint64("uid") + keyword := strings.TrimSpace(c.GetString("keyword")) + + qs := models.Orm.QueryTable(new(models.SystemTenantUser)) + + var cond *orm.Condition + needCond := false + if tid > 0 { + if cond == nil { + cond = orm.NewCondition() + } + cond = cond.And("tid", tid) + needCond = true + } + if uid > 0 { + if cond == nil { + cond = orm.NewCondition() + } + cond = cond.And("uid", uid) + needCond = true + } + if keyword != "" { + kwCond := orm.NewCondition() + kwCond = kwCond.Or("name__icontains", keyword). + Or("phone__icontains", keyword). + Or("email__icontains", keyword). + Or("account__icontains", keyword) + if cond == nil { + cond = kwCond + } else { + cond = cond.AndCond(kwCond) + } + needCond = true + } + if needCond { + qs = qs.SetCond(cond) + } + + var rows []models.SystemTenantUser + _, err := qs.OrderBy("-is_default", "-id").All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "list": rows, + "total": len(rows), + }, + } + _ = c.ServeJSON() +} + +// GetTenantUsersByTid 兼容旧路由,根据租户 ID 获取租户用户列表 +// GET /platform/getTenantUsers/:tid +func (c *PlatformTenantUserController) GetTenantUsersByTid() { + tidStr := c.Ctx.Input.Param(":tid") + tid, _ := strconv.ParseUint(tidStr, 10, 64) + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} + _ = c.ServeJSON() + return + } + var rows []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", tid). + OrderBy("-is_default", "-id"). + All(&rows) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"list": rows, "total": len(rows)}, + } + _ = c.ServeJSON() +} + +// GetTenantUserDetail 获取绑定详情 +// GET /platform/tenantUser/detail/:id +func (c *PlatformTenantUserController) GetTenantUserDetail() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + var row models.SystemTenantUser + err = models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).One(&row) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "记录不存在"} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row} + _ = c.ServeJSON() +} + +// CreateTenantUser 创建租户用户绑定(写入 yz_system_tenant_user;uid 为空时自动生成) +// POST /platform/tenantUser/create +func (c *PlatformTenantUserController) CreateTenantUser() { + p, ok := c.parsePayload() + if !ok { + return + } + if p.Tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} + _ = c.ServeJSON() + return + } + if p.Account == nil || strings.TrimSpace(*p.Account) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} + _ = c.ServeJSON() + return + } + if p.Password == nil || strings.TrimSpace(*p.Password) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + hashed, err := passwordutil.Hash(*p.Password) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + p.Password = &hashed + if p.Uid == 0 { + uid, err := generateTenantUID(p.Tid) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "生成租户用户ID失败"} + _ = c.ServeJSON() + return + } + p.Uid = uid + } + + isDefault := int8(0) + status := int8(1) + if p.IsDefault != nil { + isDefault = *p.IsDefault + } + if p.Status != nil { + status = *p.Status + } + + id, err := services.BindTenantUser(p.Tid, p.Uid, p.Account, p.Name, p.Phone, p.Email, nil, nil, p.Password, isDefault, status, p.Remark) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()} + _ = c.ServeJSON() + return + } + if isDefault == 1 { + _ = services.SetDefaultTenant(p.Uid, p.Tid) + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}} + _ = c.ServeJSON() +} + +// EditTenantUser 编辑绑定 +// POST /platform/tenantUser/edit/:id +func (c *PlatformTenantUserController) EditTenantUser() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + p, ok := c.parsePayload() + if !ok { + return + } + + update := map[string]interface{}{} + if p.Tid > 0 { + update["tid"] = p.Tid + } + if p.Uid > 0 { + update["uid"] = p.Uid + } + if p.Account != nil { + update["account"] = p.Account + } + if p.Name != nil { + update["name"] = p.Name + } + if p.Phone != nil { + update["phone"] = p.Phone + } + if p.Email != nil { + update["email"] = p.Email + } + if p.Password != nil { + hashed, err := passwordutil.Hash(*p.Password) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + update["password"] = hashed + } + if p.IsDefault != nil { + update["is_default"] = *p.IsDefault + } + if p.Status != nil { + update["status"] = *p.Status + } + if p.Remark != nil { + update["remark"] = p.Remark + } + + if len(update) == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"} + _ = c.ServeJSON() + return + } + + _, err = models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + if p.IsDefault != nil && *p.IsDefault == 1 && p.Uid > 0 && p.Tid > 0 { + _ = services.SetDefaultTenant(p.Uid, p.Tid) + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +// DeleteTenantUser 删除绑定 +// DELETE /platform/tenantUser/delete/:id +func (c *PlatformTenantUserController) DeleteTenantUser() { + id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64) + if err != nil || id == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"} + _ = c.ServeJSON() + return + } + + if err := services.UnbindTenantUser(id); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()} + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"} + _ = c.ServeJSON() +} + +func (c *PlatformTenantUserController) parsePayload() (tenantUserPayload, bool) { + var p tenantUserPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return tenantUserPayload{}, false + } + return p, true +} + +func generateTenantUID(tid uint64) (uint64, error) { + rand.Seed(time.Now().UnixNano()) + for i := 0; i < 8; i++ { + uid := uint64(10000000 + rand.Intn(90000000)) + cnt, err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", tid). + Filter("uid", uid). + Count() + if err != nil { + return 0, err + } + if cnt == 0 { + return uid, nil + } + } + return 0, errors.New("uid collision") +} diff --git a/go/controllers/platform_user.go b/go/controllers/platform_user.go index 8c514e5..c080606 100644 --- a/go/controllers/platform_user.go +++ b/go/controllers/platform_user.go @@ -1,107 +1,107 @@ -package controllers - -import ( - "encoding/json" - "io" - "math/rand" - "strings" - "time" - - "server/pkg/passwordutil" - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -// PlatformUserController 平台端用户相关(简化:当前用户信息落在 yz_system_tenant_user) -type PlatformUserController struct { - beego.Controller -} - -type addUserPayload struct { - Tid uint64 `json:"tid"` - Account string `json:"account"` - Password string `json:"password"` - Name string `json:"name"` - Phone string `json:"phone"` - Email string `json:"email"` - Status *int8 `json:"status"` - Remark *string `json:"remark"` -} - -// AddUser 添加用户(绑定到租户) -// POST /platform/addUser -func (c *PlatformUserController) AddUser() { - var p addUserPayload - - // 兼容 JSON body - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - - p.Account = strings.TrimSpace(p.Account) - p.Password = strings.TrimSpace(p.Password) - p.Name = strings.TrimSpace(p.Name) - p.Phone = strings.TrimSpace(p.Phone) - p.Email = strings.TrimSpace(p.Email) - - if p.Tid == 0 { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} - _ = c.ServeJSON() - return - } - if p.Account == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} - _ = c.ServeJSON() - return - } - if p.Password == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} - _ = c.ServeJSON() - return - } - hashed, err := passwordutil.Hash(p.Password) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} - _ = c.ServeJSON() - return - } - - status := int8(1) - if p.Status != nil { - status = *p.Status - } - - // 生成 uid:8位数字即可(10000000~99999999) - rand.Seed(time.Now().UnixNano()) - var uid uint64 - for i := 0; i < 5; i++ { - uid = uint64(10000000 + rand.Intn(90000000)) - // 尝试写入(若冲突由唯一索引兜底,外层再重试) - account := &p.Account - name := &p.Name - phone := &p.Phone - email := &p.Email - hashedPwd := hashed - password := &hashedPwd - - _, err := services.BindTenantUser(p.Tid, uid, account, name, phone, email, nil, nil, password, 0, status, p.Remark) - if err == nil { - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{"tid": p.Tid, "uid": uid}, - } - _ = c.ServeJSON() - return - } - // 轻量重试 - time.Sleep(5 * time.Millisecond) - } - - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败,请重试"} - _ = c.ServeJSON() -} +package controllers + +import ( + "encoding/json" + "io" + "math/rand" + "strings" + "time" + + "server/pkg/passwordutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +// PlatformUserController 平台端用户相关(简化:当前用户信息落在 yz_system_tenant_user) +type PlatformUserController struct { + beego.Controller +} + +type addUserPayload struct { + Tid uint64 `json:"tid"` + Account string `json:"account"` + Password string `json:"password"` + Name string `json:"name"` + Phone string `json:"phone"` + Email string `json:"email"` + Status *int8 `json:"status"` + Remark *string `json:"remark"` +} + +// AddUser 添加用户(绑定到租户) +// POST /platform/addUser +func (c *PlatformUserController) AddUser() { + var p addUserPayload + + // 兼容 JSON body + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + p.Account = strings.TrimSpace(p.Account) + p.Password = strings.TrimSpace(p.Password) + p.Name = strings.TrimSpace(p.Name) + p.Phone = strings.TrimSpace(p.Phone) + p.Email = strings.TrimSpace(p.Email) + + if p.Tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"} + _ = c.ServeJSON() + return + } + if p.Account == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"} + _ = c.ServeJSON() + return + } + if p.Password == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"} + _ = c.ServeJSON() + return + } + hashed, err := passwordutil.Hash(p.Password) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + + status := int8(1) + if p.Status != nil { + status = *p.Status + } + + // 生成 uid:8位数字即可(10000000~99999999) + rand.Seed(time.Now().UnixNano()) + var uid uint64 + for i := 0; i < 5; i++ { + uid = uint64(10000000 + rand.Intn(90000000)) + // 尝试写入(若冲突由唯一索引兜底,外层再重试) + account := &p.Account + name := &p.Name + phone := &p.Phone + email := &p.Email + hashedPwd := hashed + password := &hashedPwd + + _, err := services.BindTenantUser(p.Tid, uid, account, name, phone, email, nil, nil, password, 0, status, p.Remark) + if err == nil { + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{"tid": p.Tid, "uid": uid}, + } + _ = c.ServeJSON() + return + } + // 轻量重试 + time.Sleep(5 * time.Millisecond) + } + + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败,请重试"} + _ = c.ServeJSON() +} diff --git a/go/controllers/pool_probe.go b/go/controllers/pool_probe.go index 7195224..8e79d12 100644 --- a/go/controllers/pool_probe.go +++ b/go/controllers/pool_probe.go @@ -1,66 +1,66 @@ -package controllers - -import ( - "strings" - "time" - - "server/models" - "server/pkg/tokenprobe" -) - -func poolTableName(module string) string { - switch module { - case "cursor": - return new(models.PlatformAccountPoolCursor).TableName() - case "windsurf": - return new(models.PlatformAccountPoolWindsurf).TableName() - case "krio": - return new(models.PlatformAccountPoolKiro).TableName() - case "codex": - return new(models.PlatformAccountPoolCodex).TableName() - default: - - return "" - } -} - -// poolNeedsTokenProbe account 类型无 Token,无需探测;tk / account_tk 需探测。 -func poolNeedsTokenProbe(dataType, token string) bool { - if strings.TrimSpace(token) == "" { - return false - } - return dataType != "account" -} - -func poolSaveCursorIsUsed(id uint64, isUsed int8) { - _, _ = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - Filter("id", id). - Update(map[string]interface{}{ - "is_used": isUsed, - "update_time": time.Now(), - }) -} - -// poolProbeToken 探测 Token;cursor 模块会回写 is_used。 -func poolProbeToken(module, dataType, token string, rowID uint64) bool { - if !poolNeedsTokenProbe(dataType, token) { - return true - } - r := tokenprobe.ProbeOfficial(module, token) - if module == "cursor" && rowID > 0 { - var isUsed int8 - if r.OK { - isUsed = 1 - } - poolSaveCursorIsUsed(rowID, isUsed) - } - return r.OK -} - -// poolIsUsedAvailable 已有探测结论时:1=可用,0=不可用,nil=未探测。 -func poolIsUsedAvailable(isUsed *int8) (known bool, available bool) { - if isUsed == nil { - return false, false - } - return true, *isUsed == 1 -} +package controllers + +import ( + "strings" + "time" + + "server/models" + "server/pkg/tokenprobe" +) + +func poolTableName(module string) string { + switch module { + case "cursor": + return new(models.PlatformAccountPoolCursor).TableName() + case "windsurf": + return new(models.PlatformAccountPoolWindsurf).TableName() + case "krio": + return new(models.PlatformAccountPoolKiro).TableName() + case "codex": + return new(models.PlatformAccountPoolCodex).TableName() + default: + + return "" + } +} + +// poolNeedsTokenProbe account 类型无 Token,无需探测;tk / account_tk 需探测。 +func poolNeedsTokenProbe(dataType, token string) bool { + if strings.TrimSpace(token) == "" { + return false + } + return dataType != "account" +} + +func poolSaveCursorIsUsed(id uint64, isUsed int8) { + _, _ = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("id", id). + Update(map[string]interface{}{ + "is_used": isUsed, + "update_time": time.Now(), + }) +} + +// poolProbeToken 探测 Token;cursor 模块会回写 is_used。 +func poolProbeToken(module, dataType, token string, rowID uint64) bool { + if !poolNeedsTokenProbe(dataType, token) { + return true + } + r := tokenprobe.ProbeOfficial(module, token) + if module == "cursor" && rowID > 0 { + var isUsed int8 + if r.OK { + isUsed = 1 + } + poolSaveCursorIsUsed(rowID, isUsed) + } + return r.OK +} + +// poolIsUsedAvailable 已有探测结论时:1=可用,0=不可用,nil=未探测。 +func poolIsUsedAvailable(isUsed *int8) (known bool, available bool) { + if isUsed == nil { + return false, false + } + return true, *isUsed == 1 +} diff --git a/go/controllers/qiniu_upload.go b/go/controllers/qiniu_upload.go index cc8a130..b82113d 100644 --- a/go/controllers/qiniu_upload.go +++ b/go/controllers/qiniu_upload.go @@ -1,327 +1,327 @@ -package controllers - -import ( - "crypto/md5" - "encoding/hex" - "encoding/json" - "fmt" - "strings" - "time" - - "server/models" - "server/pkg/jwtutil" - - beego "github.com/beego/beego/v2/server/web" - "github.com/qiniu/go-sdk/v7/auth/qbox" - "github.com/qiniu/go-sdk/v7/storage" -) - -// QiniuUploadController 七牛云上传控制器 -type QiniuUploadController struct { - beego.Controller -} - -// platformClaims 获取平台端 JWT claims -func (c *QiniuUploadController) platformClaims() (*jwtutil.Claims, error) { - auth := c.Ctx.Request.Header.Get("Authorization") - if auth == "" { - return nil, fmt.Errorf("未登录") - } - parts := strings.Split(auth, " ") - if len(parts) != 2 || parts[0] != "Bearer" { - return nil, fmt.Errorf("token 格式错误") - } - claims, err := jwtutil.ParseToken(parts[1]) - if err != nil { - return nil, fmt.Errorf("token 无效") - } - return claims, nil -} - -// effectiveTid 获取有效的租户 ID -func (c *QiniuUploadController) effectiveTid(claims *jwtutil.Claims) uint64 { - if claims.TenantId > 0 { - return uint64(claims.TenantId) - } - return 0 -} - -// jsonErr 返回错误响应 -func (c *QiniuUploadController) jsonErr(httpStatus, bizCode int, msg string) { - c.Ctx.Output.SetStatus(httpStatus) - c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} - _ = c.ServeJSON() -} - -// jsonOK 返回成功响应 -func (c *QiniuUploadController) jsonOK(data interface{}) { - c.Data["json"] = map[string]interface{}{"code": 200, "data": data} - _ = c.ServeJSON() -} - -// ParseJSON 解析 JSON 请求体 -func (c *QiniuUploadController) ParseJSON(v interface{}) error { - body := c.Ctx.Input.RequestBody - if len(body) == 0 { - return fmt.Errorf("请求体为空") - } - return json.Unmarshal(body, v) -} - -// GetUploadToken 获取上传凭证 -// GET /platform/qiniu/token -func (c *QiniuUploadController) GetUploadToken() { - _, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - // 获取存储配置 - cfg, err := models.GetStorageConfig() - if err != nil || cfg.StorageType != "qiniu" { - c.jsonErr(400, 400, "当前未配置七牛云存储") - return - } - - // 检查配置完整性 - if cfg.QiniuAccessKey == "" || cfg.QiniuSecretKey == "" || cfg.QiniuBucket == "" { - c.jsonErr(500, 500, "七牛云配置不完整") - return - } - - // 生成文件 key(前端可以覆盖) - datePath := time.Now().Format("2006/01/02") - timestamp := time.Now().UnixNano() - keyPrefix := fmt.Sprintf("%s/%d", datePath, timestamp) - - // 创建上传策略 - mac := qbox.NewMac(cfg.QiniuAccessKey, cfg.QiniuSecretKey) - putPolicy := storage.PutPolicy{ - Scope: cfg.QiniuBucket, - ReturnBody: `{"key":"$(key)","hash":"$(etag)","size":$(fsize),"mimeType":"$(mimeType)"}`, - Expires: 3600, // 1小时有效期 - } - upToken := putPolicy.UploadToken(mac) - - // 返回上传凭证和配置 - c.jsonOK(map[string]interface{}{ - "token": upToken, - "domain": cfg.QiniuDomain, - "bucket": cfg.QiniuBucket, - "region": cfg.QiniuRegion, - "keyPrefix": keyPrefix, - "expires": time.Now().Add(time.Hour).Unix(), - "uploadUrl": getQiniuUploadURL(cfg.QiniuRegion), - }) -} - -// SaveFileRecord 保存文件记录 -// POST /platform/qiniu/save -func (c *QiniuUploadController) SaveFileRecord() { - claims, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - tid := c.effectiveTid(claims) - - // 调试:打印请求体 - body := c.Ctx.Input.RequestBody - fmt.Println("SaveFileRecord 请求体长度:", len(body)) - fmt.Println("SaveFileRecord 请求体内容:", string(body)) - - // 解析请求参数 - type SaveRequest struct { - Key string `json:"key"` // 七牛云文件 key - Hash string `json:"hash"` // 文件 hash (etag) - Size int64 `json:"size"` // 文件大小 - Name string `json:"name"` // 原始文件名 - MimeType string `json:"mimeType"` // 文件类型 - Cate uint64 `json:"cate"` // 分类 ID - } - - var req SaveRequest - if err := c.ParseJSON(&req); err != nil { - c.jsonErr(400, 400, "参数解析失败: "+err.Error()) - return - } - - // 验证必填字段 - if req.Key == "" || req.Name == "" { - c.jsonErr(400, 400, "缺少必填参数") - return - } - - // 获取存储配置 - cfg, err := models.GetStorageConfig() - if err != nil || cfg.StorageType != "qiniu" { - c.jsonErr(400, 400, "当前未配置七牛云存储") - return - } - - // 构建完整 URL - domain := strings.TrimRight(cfg.QiniuDomain, "/") - fileURL := fmt.Sprintf("%s/%s", domain, req.Key) - - // 计算 MD5(使用 hash 作为 MD5,或者重新计算) - md5Sum := req.Hash - if md5Sum == "" { - // 如果没有 hash,使用 key 生成一个唯一标识 - h := md5.New() - h.Write([]byte(req.Key)) - md5Sum = hex.EncodeToString(h.Sum(nil)) - } - - // 检查文件是否已存在(通过 MD5) - var exist models.SystemFile - err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("md5", md5Sum). - Filter("tid", tid). - Filter("delete_time__isnull", true). - One(&exist) - if err == nil { - // 文件已存在,返回已有记录 - c.Data["json"] = map[string]interface{}{ - "code": 201, - "msg": "文件已存在", - "data": map[string]interface{}{ - "url": exist.Src, - "id": exist.ID, - "name": exist.Name, - }, - } - _ = c.ServeJSON() - return - } - - // 检测文件类型 - ext := getQiniuFileExt(req.Name) - fileType := detectQiniuFileType(ext) - - // 保存文件记录 - adminID := uint64(claims.UserID) - row := &models.SystemFile{ - Tid: tid, - Uid: &adminID, - Name: req.Name, - Type: fileType, - Cate: req.Cate, - Size: uint64(req.Size), - Src: fileURL, - Uploader: adminID, - Md5: md5Sum, - } - - id, err := models.Orm.Insert(row) - if err != nil { - c.jsonErr(500, 500, "保存文件记录失败: "+err.Error()) - return - } - - c.jsonOK(map[string]interface{}{ - "url": fileURL, - "id": uint64(id), - "name": req.Name, - "key": req.Key, - }) -} - -// GetStorageConfig 获取存储配置(前端用于判断上传方式) -// GET /platform/storage/config -func (c *QiniuUploadController) GetStorageConfig() { - _, err := c.platformClaims() - if err != nil { - c.jsonErr(401, 401, err.Error()) - return - } - - cfg, err := models.GetStorageConfig() - if err != nil { - c.jsonOK(map[string]interface{}{ - "storageType": "local", - }) - return - } - - // 只返回必要的配置信息,不返回密钥 - c.jsonOK(map[string]interface{}{ - "storageType": cfg.StorageType, - "qiniuDomain": cfg.QiniuDomain, - "qiniuRegion": cfg.QiniuRegion, - }) -} - -// getQiniuUploadURL 根据区域获取上传地址 -func getQiniuUploadURL(region string) string { - switch region { - case "z0": - return "https://up-z0.qiniup.com" - case "z1": - return "https://up-z1.qiniup.com" - case "z2": - return "https://up-z2.qiniup.com" - case "na0": - return "https://up-na0.qiniup.com" - case "as0": - return "https://up-as0.qiniup.com" - case "cn-east-2": - return "https://up-cn-east-2.qiniup.com" - default: - return "https://up-z0.qiniup.com" // 默认华东 - } -} - -// getQiniuFileExt 获取文件扩展名 -func getQiniuFileExt(filename string) string { - parts := strings.Split(filename, ".") - if len(parts) > 1 { - return strings.ToLower(parts[len(parts)-1]) - } - return "" -} - -// detectQiniuFileType 检测文件类型 -func detectQiniuFileType(ext string) uint8 { - imageExts := map[string]bool{ - "jpg": true, "jpeg": true, "png": true, "gif": true, "bmp": true, - "webp": true, "svg": true, "ico": true, - } - videoExts := map[string]bool{ - "mp4": true, "avi": true, "mov": true, "wmv": true, "flv": true, - "mkv": true, "webm": true, "m4v": true, - } - audioExts := map[string]bool{ - "mp3": true, "wav": true, "flac": true, "aac": true, "ogg": true, - "m4a": true, "wma": true, - } - docExts := map[string]bool{ - "doc": true, "docx": true, "xls": true, "xlsx": true, "ppt": true, - "pptx": true, "pdf": true, "txt": true, "md": true, - } - archiveExts := map[string]bool{ - "zip": true, "rar": true, "7z": true, "tar": true, "gz": true, - "bz2": true, "xz": true, - } - executableExts := map[string]bool{ - "exe": true, "msi": true, "dmg": true, "pkg": true, "deb": true, - "rpm": true, "apk": true, "msix": true, - } - - if imageExts[ext] { - return 1 // 图片 - } - if videoExts[ext] { - return 2 // 视频 - } - if audioExts[ext] { - return 3 // 音频 - } - if docExts[ext] { - return 4 // 文档 - } - if archiveExts[ext] || executableExts[ext] { - return 5 // 压缩包/安装包 - } - return 0 // 其他 -} +package controllers + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" + "github.com/qiniu/go-sdk/v7/auth/qbox" + "github.com/qiniu/go-sdk/v7/storage" +) + +// QiniuUploadController 七牛云上传控制器 +type QiniuUploadController struct { + beego.Controller +} + +// platformClaims 获取平台端 JWT claims +func (c *QiniuUploadController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.Split(auth, " ") + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("token 格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("token 无效") + } + return claims, nil +} + +// effectiveTid 获取有效的租户 ID +func (c *QiniuUploadController) effectiveTid(claims *jwtutil.Claims) uint64 { + if claims.TenantId > 0 { + return uint64(claims.TenantId) + } + return 0 +} + +// jsonErr 返回错误响应 +func (c *QiniuUploadController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// jsonOK 返回成功响应 +func (c *QiniuUploadController) jsonOK(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "data": data} + _ = c.ServeJSON() +} + +// ParseJSON 解析 JSON 请求体 +func (c *QiniuUploadController) ParseJSON(v interface{}) error { + body := c.Ctx.Input.RequestBody + if len(body) == 0 { + return fmt.Errorf("请求体为空") + } + return json.Unmarshal(body, v) +} + +// GetUploadToken 获取上传凭证 +// GET /platform/qiniu/token +func (c *QiniuUploadController) GetUploadToken() { + _, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + // 获取存储配置 + cfg, err := models.GetStorageConfig() + if err != nil || cfg.StorageType != "qiniu" { + c.jsonErr(400, 400, "当前未配置七牛云存储") + return + } + + // 检查配置完整性 + if cfg.QiniuAccessKey == "" || cfg.QiniuSecretKey == "" || cfg.QiniuBucket == "" { + c.jsonErr(500, 500, "七牛云配置不完整") + return + } + + // 生成文件 key(前端可以覆盖) + datePath := time.Now().Format("2006/01/02") + timestamp := time.Now().UnixNano() + keyPrefix := fmt.Sprintf("%s/%d", datePath, timestamp) + + // 创建上传策略 + mac := qbox.NewMac(cfg.QiniuAccessKey, cfg.QiniuSecretKey) + putPolicy := storage.PutPolicy{ + Scope: cfg.QiniuBucket, + ReturnBody: `{"key":"$(key)","hash":"$(etag)","size":$(fsize),"mimeType":"$(mimeType)"}`, + Expires: 3600, // 1小时有效期 + } + upToken := putPolicy.UploadToken(mac) + + // 返回上传凭证和配置 + c.jsonOK(map[string]interface{}{ + "token": upToken, + "domain": cfg.QiniuDomain, + "bucket": cfg.QiniuBucket, + "region": cfg.QiniuRegion, + "keyPrefix": keyPrefix, + "expires": time.Now().Add(time.Hour).Unix(), + "uploadUrl": getQiniuUploadURL(cfg.QiniuRegion), + }) +} + +// SaveFileRecord 保存文件记录 +// POST /platform/qiniu/save +func (c *QiniuUploadController) SaveFileRecord() { + claims, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + + // 调试:打印请求体 + body := c.Ctx.Input.RequestBody + fmt.Println("SaveFileRecord 请求体长度:", len(body)) + fmt.Println("SaveFileRecord 请求体内容:", string(body)) + + // 解析请求参数 + type SaveRequest struct { + Key string `json:"key"` // 七牛云文件 key + Hash string `json:"hash"` // 文件 hash (etag) + Size int64 `json:"size"` // 文件大小 + Name string `json:"name"` // 原始文件名 + MimeType string `json:"mimeType"` // 文件类型 + Cate uint64 `json:"cate"` // 分类 ID + } + + var req SaveRequest + if err := c.ParseJSON(&req); err != nil { + c.jsonErr(400, 400, "参数解析失败: "+err.Error()) + return + } + + // 验证必填字段 + if req.Key == "" || req.Name == "" { + c.jsonErr(400, 400, "缺少必填参数") + return + } + + // 获取存储配置 + cfg, err := models.GetStorageConfig() + if err != nil || cfg.StorageType != "qiniu" { + c.jsonErr(400, 400, "当前未配置七牛云存储") + return + } + + // 构建完整 URL + domain := strings.TrimRight(cfg.QiniuDomain, "/") + fileURL := fmt.Sprintf("%s/%s", domain, req.Key) + + // 计算 MD5(使用 hash 作为 MD5,或者重新计算) + md5Sum := req.Hash + if md5Sum == "" { + // 如果没有 hash,使用 key 生成一个唯一标识 + h := md5.New() + h.Write([]byte(req.Key)) + md5Sum = hex.EncodeToString(h.Sum(nil)) + } + + // 检查文件是否已存在(通过 MD5) + var exist models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("md5", md5Sum). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&exist) + if err == nil { + // 文件已存在,返回已有记录 + c.Data["json"] = map[string]interface{}{ + "code": 201, + "msg": "文件已存在", + "data": map[string]interface{}{ + "url": exist.Src, + "id": exist.ID, + "name": exist.Name, + }, + } + _ = c.ServeJSON() + return + } + + // 检测文件类型 + ext := getQiniuFileExt(req.Name) + fileType := detectQiniuFileType(ext) + + // 保存文件记录 + adminID := uint64(claims.UserID) + row := &models.SystemFile{ + Tid: tid, + Uid: &adminID, + Name: req.Name, + Type: fileType, + Cate: req.Cate, + Size: uint64(req.Size), + Src: fileURL, + Uploader: adminID, + Md5: md5Sum, + } + + id, err := models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "保存文件记录失败: "+err.Error()) + return + } + + c.jsonOK(map[string]interface{}{ + "url": fileURL, + "id": uint64(id), + "name": req.Name, + "key": req.Key, + }) +} + +// GetStorageConfig 获取存储配置(前端用于判断上传方式) +// GET /platform/storage/config +func (c *QiniuUploadController) GetStorageConfig() { + _, err := c.platformClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + cfg, err := models.GetStorageConfig() + if err != nil { + c.jsonOK(map[string]interface{}{ + "storageType": "local", + }) + return + } + + // 只返回必要的配置信息,不返回密钥 + c.jsonOK(map[string]interface{}{ + "storageType": cfg.StorageType, + "qiniuDomain": cfg.QiniuDomain, + "qiniuRegion": cfg.QiniuRegion, + }) +} + +// getQiniuUploadURL 根据区域获取上传地址 +func getQiniuUploadURL(region string) string { + switch region { + case "z0": + return "https://up-z0.qiniup.com" + case "z1": + return "https://up-z1.qiniup.com" + case "z2": + return "https://up-z2.qiniup.com" + case "na0": + return "https://up-na0.qiniup.com" + case "as0": + return "https://up-as0.qiniup.com" + case "cn-east-2": + return "https://up-cn-east-2.qiniup.com" + default: + return "https://up-z0.qiniup.com" // 默认华东 + } +} + +// getQiniuFileExt 获取文件扩展名 +func getQiniuFileExt(filename string) string { + parts := strings.Split(filename, ".") + if len(parts) > 1 { + return strings.ToLower(parts[len(parts)-1]) + } + return "" +} + +// detectQiniuFileType 检测文件类型 +func detectQiniuFileType(ext string) uint8 { + imageExts := map[string]bool{ + "jpg": true, "jpeg": true, "png": true, "gif": true, "bmp": true, + "webp": true, "svg": true, "ico": true, + } + videoExts := map[string]bool{ + "mp4": true, "avi": true, "mov": true, "wmv": true, "flv": true, + "mkv": true, "webm": true, "m4v": true, + } + audioExts := map[string]bool{ + "mp3": true, "wav": true, "flac": true, "aac": true, "ogg": true, + "m4a": true, "wma": true, + } + docExts := map[string]bool{ + "doc": true, "docx": true, "xls": true, "xlsx": true, "ppt": true, + "pptx": true, "pdf": true, "txt": true, "md": true, + } + archiveExts := map[string]bool{ + "zip": true, "rar": true, "7z": true, "tar": true, "gz": true, + "bz2": true, "xz": true, + } + executableExts := map[string]bool{ + "exe": true, "msi": true, "dmg": true, "pkg": true, "deb": true, + "rpm": true, "apk": true, "msix": true, + } + + if imageExts[ext] { + return 1 // 图片 + } + if videoExts[ext] { + return 2 // 视频 + } + if audioExts[ext] { + return 3 // 音频 + } + if docExts[ext] { + return 4 // 文档 + } + if archiveExts[ext] || executableExts[ext] { + return 5 // 压缩包/安装包 + } + return 0 // 其他 +} diff --git a/go/controllers/storage_config.go b/go/controllers/storage_config.go index 4ce38e7..7e7e69c 100644 --- a/go/controllers/storage_config.go +++ b/go/controllers/storage_config.go @@ -1,140 +1,140 @@ -package controllers - -import ( - "encoding/json" - "io" - "strings" - - "server/models" - - beego "github.com/beego/beego/v2/server/web" -) - -type StorageConfigController struct { - beego.Controller -} - -type storageConfigPayload struct { - StorageType string `json:"storage_type"` - QiniuAccessKey *string `json:"qiniu_access_key"` - QiniuSecretKey *string `json:"qiniu_secret_key"` - QiniuBucket *string `json:"qiniu_bucket"` - QiniuDomain *string `json:"qiniu_domain"` - QiniuRegion *string `json:"qiniu_region"` -} - -func normalizeStorageType(v string) string { - switch strings.TrimSpace(v) { - case "local", "qiniu": - return strings.TrimSpace(v) - default: - return "local" - } -} - -// GetStorageConfig 获取存储配置 -// GET /platform/storageConfig -func (c *StorageConfigController) GetStorageConfig() { - cfg, err := models.GetStorageConfig() - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取配置失败"} - _ = c.ServeJSON() - return - } - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "storage_type": cfg.StorageType, - "qiniu_access_key": cfg.QiniuAccessKey, - "qiniu_secret_key": cfg.QiniuSecretKey, - "qiniu_bucket": cfg.QiniuBucket, - "qiniu_domain": cfg.QiniuDomain, - "qiniu_region": cfg.QiniuRegion, - }, - } - _ = c.ServeJSON() -} - -// SaveStorageConfig 保存存储配置 -// POST /platform/saveStorageConfig -func (c *StorageConfigController) SaveStorageConfig() { - var p storageConfigPayload - raw, _ := io.ReadAll(c.Ctx.Request.Body) - if err := json.Unmarshal(raw, &p); err != nil { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} - _ = c.ServeJSON() - return - } - - storageType := normalizeStorageType(p.StorageType) - - // 如果选择七牛云,验证必填字段 - if storageType == "qiniu" { - if p.QiniuAccessKey == nil || strings.TrimSpace(*p.QiniuAccessKey) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 AccessKey 不能为空"} - _ = c.ServeJSON() - return - } - if p.QiniuSecretKey == nil || strings.TrimSpace(*p.QiniuSecretKey) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 SecretKey 不能为空"} - _ = c.ServeJSON() - return - } - if p.QiniuBucket == nil || strings.TrimSpace(*p.QiniuBucket) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 Bucket 不能为空"} - _ = c.ServeJSON() - return - } - if p.QiniuDomain == nil || strings.TrimSpace(*p.QiniuDomain) == "" { - c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云域名不能为空"} - _ = c.ServeJSON() - return - } - } - - var existed models.StorageConfig - err := models.Orm.QueryTable(new(models.StorageConfig)).OrderBy("-id").One(&existed) - if err == nil { - // 更新现有配置 - update := map[string]interface{}{ - "storage_type": storageType, - "qiniu_access_key": p.QiniuAccessKey, - "qiniu_secret_key": p.QiniuSecretKey, - "qiniu_bucket": p.QiniuBucket, - "qiniu_domain": p.QiniuDomain, - "qiniu_region": p.QiniuRegion, - } - _, err = models.Orm.QueryTable(new(models.StorageConfig)).Filter("id", existed.ID).Update(update) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} - _ = c.ServeJSON() - return - } - } else { - // 创建新配置 - row := &models.StorageConfig{ - StorageType: storageType, - QiniuAccessKey: getStringValue(p.QiniuAccessKey), - QiniuSecretKey: getStringValue(p.QiniuSecretKey), - QiniuBucket: getStringValue(p.QiniuBucket), - QiniuDomain: getStringValue(p.QiniuDomain), - QiniuRegion: getStringValue(p.QiniuRegion), - } - if _, err := models.Orm.Insert(row); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} - _ = c.ServeJSON() - return - } - } - - c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} - _ = c.ServeJSON() -} - -func getStringValue(s *string) string { - if s == nil { - return "" - } - return *s -} +package controllers + +import ( + "encoding/json" + "io" + "strings" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +type StorageConfigController struct { + beego.Controller +} + +type storageConfigPayload struct { + StorageType string `json:"storage_type"` + QiniuAccessKey *string `json:"qiniu_access_key"` + QiniuSecretKey *string `json:"qiniu_secret_key"` + QiniuBucket *string `json:"qiniu_bucket"` + QiniuDomain *string `json:"qiniu_domain"` + QiniuRegion *string `json:"qiniu_region"` +} + +func normalizeStorageType(v string) string { + switch strings.TrimSpace(v) { + case "local", "qiniu": + return strings.TrimSpace(v) + default: + return "local" + } +} + +// GetStorageConfig 获取存储配置 +// GET /platform/storageConfig +func (c *StorageConfigController) GetStorageConfig() { + cfg, err := models.GetStorageConfig() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取配置失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "storage_type": cfg.StorageType, + "qiniu_access_key": cfg.QiniuAccessKey, + "qiniu_secret_key": cfg.QiniuSecretKey, + "qiniu_bucket": cfg.QiniuBucket, + "qiniu_domain": cfg.QiniuDomain, + "qiniu_region": cfg.QiniuRegion, + }, + } + _ = c.ServeJSON() +} + +// SaveStorageConfig 保存存储配置 +// POST /platform/saveStorageConfig +func (c *StorageConfigController) SaveStorageConfig() { + var p storageConfigPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + storageType := normalizeStorageType(p.StorageType) + + // 如果选择七牛云,验证必填字段 + if storageType == "qiniu" { + if p.QiniuAccessKey == nil || strings.TrimSpace(*p.QiniuAccessKey) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 AccessKey 不能为空"} + _ = c.ServeJSON() + return + } + if p.QiniuSecretKey == nil || strings.TrimSpace(*p.QiniuSecretKey) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 SecretKey 不能为空"} + _ = c.ServeJSON() + return + } + if p.QiniuBucket == nil || strings.TrimSpace(*p.QiniuBucket) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云 Bucket 不能为空"} + _ = c.ServeJSON() + return + } + if p.QiniuDomain == nil || strings.TrimSpace(*p.QiniuDomain) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "七牛云域名不能为空"} + _ = c.ServeJSON() + return + } + } + + var existed models.StorageConfig + err := models.Orm.QueryTable(new(models.StorageConfig)).OrderBy("-id").One(&existed) + if err == nil { + // 更新现有配置 + update := map[string]interface{}{ + "storage_type": storageType, + "qiniu_access_key": p.QiniuAccessKey, + "qiniu_secret_key": p.QiniuSecretKey, + "qiniu_bucket": p.QiniuBucket, + "qiniu_domain": p.QiniuDomain, + "qiniu_region": p.QiniuRegion, + } + _, err = models.Orm.QueryTable(new(models.StorageConfig)).Filter("id", existed.ID).Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} + _ = c.ServeJSON() + return + } + } else { + // 创建新配置 + row := &models.StorageConfig{ + StorageType: storageType, + QiniuAccessKey: getStringValue(p.QiniuAccessKey), + QiniuSecretKey: getStringValue(p.QiniuSecretKey), + QiniuBucket: getStringValue(p.QiniuBucket), + QiniuDomain: getStringValue(p.QiniuDomain), + QiniuRegion: getStringValue(p.QiniuRegion), + } + if _, err := models.Orm.Insert(row); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} + _ = c.ServeJSON() + return + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +func getStringValue(s *string) string { + if s == nil { + return "" + } + return *s +} diff --git a/go/controllers/storage_migration.go b/go/controllers/storage_migration.go index afe3619..3d8cc8e 100644 --- a/go/controllers/storage_migration.go +++ b/go/controllers/storage_migration.go @@ -1,62 +1,62 @@ -package controllers - -import ( - "server/services" - - beego "github.com/beego/beego/v2/server/web" -) - -type StorageMigrationController struct { - beego.Controller -} - -// MigrateToQiniu 迁移文件到七牛云 -// POST /platform/storage/migrateToQiniu -func (c *StorageMigrationController) MigrateToQiniu() { - // 这里简化处理,实际应该使用异步任务 - // 可以使用 goroutine + 进度查询接口实现 - - // 获取租户ID(从token或参数) - tid := uint64(1) // 示例,实际应从认证信息获取 - - progress, err := services.MigrateLocalToQiniu(tid) - if err != nil { - c.Data["json"] = map[string]interface{}{ - "code": 500, - "msg": "迁移失败: " + err.Error(), - "data": progress, - } - _ = c.ServeJSON() - return - } - - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "迁移完成", - "data": map[string]interface{}{ - "total": progress.Total, - "success": progress.Success, - "failed": progress.Failed, - "errors": progress.Errors, - }, - } - _ = c.ServeJSON() -} - -// GetMigrationProgress 获取迁移进度 -// GET /platform/storage/migrationProgress -func (c *StorageMigrationController) GetMigrationProgress() { - // 这里需要实现进度查询逻辑 - // 可以使用全局变量或Redis存储进度信息 - c.Data["json"] = map[string]interface{}{ - "code": 200, - "msg": "success", - "data": map[string]interface{}{ - "total": 0, - "success": 0, - "failed": 0, - "current": "", - }, - } - _ = c.ServeJSON() -} +package controllers + +import ( + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +type StorageMigrationController struct { + beego.Controller +} + +// MigrateToQiniu 迁移文件到七牛云 +// POST /platform/storage/migrateToQiniu +func (c *StorageMigrationController) MigrateToQiniu() { + // 这里简化处理,实际应该使用异步任务 + // 可以使用 goroutine + 进度查询接口实现 + + // 获取租户ID(从token或参数) + tid := uint64(1) // 示例,实际应从认证信息获取 + + progress, err := services.MigrateLocalToQiniu(tid) + if err != nil { + c.Data["json"] = map[string]interface{}{ + "code": 500, + "msg": "迁移失败: " + err.Error(), + "data": progress, + } + _ = c.ServeJSON() + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "迁移完成", + "data": map[string]interface{}{ + "total": progress.Total, + "success": progress.Success, + "failed": progress.Failed, + "errors": progress.Errors, + }, + } + _ = c.ServeJSON() +} + +// GetMigrationProgress 获取迁移进度 +// GET /platform/storage/migrationProgress +func (c *StorageMigrationController) GetMigrationProgress() { + // 这里需要实现进度查询逻辑 + // 可以使用全局变量或Redis存储进度信息 + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "total": 0, + "success": 0, + "failed": 0, + "current": "", + }, + } + _ = c.ServeJSON() +} diff --git a/go/database/init_mysql.sql b/go/database/init_mysql.sql index 478a3fe..f013555 100644 --- a/go/database/init_mysql.sql +++ b/go/database/init_mysql.sql @@ -1,950 +1,950 @@ --- MySQL dump 10.13 Distrib 8.0.41, for Win64 (x86_64) --- --- Host: 212.64.112.158 Database: gotest --- ------------------------------------------------------ --- Server version 5.7.44-log - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!50503 SET NAMES utf8mb4 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Current Database: `gotest` --- - -CREATE DATABASE /*!32312 IF NOT EXISTS*/ `gotest` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; - -USE `gotest`; - --- --- Table structure for table `sys_access_log` --- - -DROP TABLE IF EXISTS `sys_access_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_access_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `tenant_id` int(11) NOT NULL DEFAULT '0', - `user_id` int(11) NOT NULL DEFAULT '0', - `username` varchar(255) NOT NULL DEFAULT '', - `module` varchar(255) NOT NULL DEFAULT '', - `resource_type` varchar(255) NOT NULL DEFAULT '', - `resource_id` int(11) DEFAULT NULL, - `request_url` varchar(255) DEFAULT NULL, - `query_string` longtext, - `ip_address` varchar(255) DEFAULT NULL, - `user_agent` varchar(255) DEFAULT NULL, - `request_method` varchar(255) DEFAULT NULL, - `duration` int(11) DEFAULT NULL, - `create_time` datetime NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `sys_dict_item` --- - -DROP TABLE IF EXISTS `sys_dict_item`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_dict_item` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `dict_type_id` bigint(20) NOT NULL COMMENT '字典类型ID', - `dict_label` varchar(100) NOT NULL COMMENT '字典标签(显示值,如 正常)', - `dict_value` varchar(100) NOT NULL COMMENT '字典值(存储值,如 1)', - `parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父级字典项ID(0表示一级项)', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0-禁用,1-启用)', - `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序号', - `color` varchar(20) DEFAULT NULL COMMENT '颜色标记(如 #1890ff)', - `icon` varchar(50) DEFAULT NULL COMMENT '图标(如 el-icon-success)', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` varchar(50) DEFAULT NULL COMMENT '更新人', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删,1-已删)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_dict_type_value` (`dict_type_id`,`dict_value`,`is_deleted`), - KEY `idx_dict_type_parent_status` (`dict_type_id`,`parent_id`,`status`,`is_deleted`), - KEY `idx_parent_id` (`parent_id`,`status`,`is_deleted`) -) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8mb4 COMMENT='字典项表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `sys_dict_type` --- - -DROP TABLE IF EXISTS `sys_dict_type`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_dict_type` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID(0表示平台字典,>0表示租户字典)', - `dict_code` varchar(50) NOT NULL COMMENT '字典编码(唯一,如 USER_STATUS)', - `dict_name` varchar(100) NOT NULL COMMENT '字典名称(如 用户状态)', - `parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父级字典ID(0表示一级字典)', - `is_global` tinyint(4) DEFAULT '0' COMMENT '是否全局0-否 1-是', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0-禁用,1-启用)', - `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序号', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` varchar(50) DEFAULT NULL COMMENT '更新人', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删,1-已删)', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_dict_code_tenant` (`dict_code`,`is_deleted`), - KEY `idx_parent_id` (`parent_id`,`is_deleted`), - KEY `idx_status` (`status`,`is_deleted`), - KEY `idx_tenant_id` (`tenant_id`,`is_deleted`) -) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COMMENT='字典类型表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `sys_feedback` --- - -DROP TABLE IF EXISTS `sys_feedback`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_feedback` ( - `id` varchar(36) NOT NULL COMMENT 'ID', - `tenant_id` varchar(64) NOT NULL COMMENT '租户ID', - `feedback_name` varchar(50) DEFAULT '' COMMENT '反馈人姓名', - `module` varchar(30) NOT NULL COMMENT '反馈对应模块', - `feedback_type` varchar(20) NOT NULL COMMENT '反馈类型', - `content` text NOT NULL COMMENT '反馈详细内容', - `attachment_url` varchar(255) DEFAULT '' COMMENT '附件URL', - `handle_status` varchar(20) NOT NULL DEFAULT '0' COMMENT '处理状态(0-待处理/1-处理中/2-已解决/3-已驳回/4-无需处理)', - `handle_remark` text COMMENT '处理备注(移除默认值,TEXT类型不支持)', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`), - KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引,优化租户级查询', - KEY `idx_module` (`module`) COMMENT '模块索引,优化模块级反馈统计', - KEY `idx_handle_status` (`handle_status`) COMMENT '处理状态索引,优化待处理反馈查询', - KEY `idx_create_time` (`create_time`) COMMENT '创建时间索引,优化时间范围查询' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通用反馈表(支持租户隔离、软删除)'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `sys_operation_log` --- - -DROP TABLE IF EXISTS `sys_operation_log`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `sys_operation_log` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID', - `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID(0表示平台操作,>0表示租户操作)', - `user_id` int(11) NOT NULL COMMENT '操作用户ID', - `username` varchar(50) NOT NULL COMMENT '操作用户名', - `module` varchar(100) NOT NULL COMMENT '操作模块(user/tenant/dict/role等)', - `resource_type` varchar(50) NOT NULL COMMENT '资源类型(如User/Tenant/Dict等)', - `resource_id` int(11) DEFAULT NULL COMMENT '资源ID(如被操作的用户ID、租户ID等)', - `operation` varchar(20) NOT NULL COMMENT '操作类型(CREATE/UPDATE/DELETE/LOGIN/LOGOUT/VIEW等)', - `description` varchar(500) DEFAULT NULL COMMENT '操作描述', - `old_value` longtext COMMENT '修改前的值(JSON格式,用于UPDATE操作)', - `new_value` longtext COMMENT '修改后的值(JSON格式,用于UPDATE操作)', - `ip_address` varchar(50) DEFAULT NULL COMMENT 'IP地址', - `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理信息', - `request_method` varchar(10) DEFAULT NULL COMMENT '请求方法(GET/POST/PUT/DELETE等)', - `request_url` varchar(500) DEFAULT NULL COMMENT '请求URL', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1-成功,0-失败', - `error_message` text COMMENT '错误信息', - `duration` int(11) DEFAULT NULL COMMENT '执行时长(毫秒)', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间', - PRIMARY KEY (`id`), - KEY `idx_tenant_id` (`tenant_id`), - KEY `idx_user_id` (`user_id`), - KEY `idx_module` (`module`), - KEY `idx_resource_type` (`resource_type`), - KEY `idx_resource_id` (`resource_id`), - KEY `idx_operation` (`operation`), - KEY `idx_create_time` (`create_time`), - KEY `idx_tenant_user_time` (`tenant_id`,`user_id`,`create_time`), - KEY `idx_tenant_module_time` (`tenant_id`,`module`,`create_time`) -) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_exam` --- - -DROP TABLE IF EXISTS `yz_exam`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_exam` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '考试唯一标识', - `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', - `category_id` int(11) NOT NULL COMMENT '关联考试分类表ID', - `exam_name` varchar(100) NOT NULL COMMENT '考试名称', - `exam_desc` varchar(500) DEFAULT '' COMMENT '考试描述', - `exam_time` datetime NOT NULL COMMENT '考试开始时间', - `exam_duration` int(11) NOT NULL COMMENT '考试时长(分钟)', - `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '考试状态(0-未开始,1-进行中,2-已结束,3-已取消)', - `create_by` int(11) NOT NULL COMMENT '创建人ID', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `idx_tenant_category_status` (`tenant_id`,`category_id`,`status`), - KEY `idx_tenant_create_by` (`tenant_id`,`create_by`), - KEY `fk_yz_exam_category` (`category_id`), - CONSTRAINT `fk_yz_exam_category` FOREIGN KEY (`category_id`) REFERENCES `yz_exam_category` (`id`) ON DELETE CASCADE, - CONSTRAINT `fk_yz_exam_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `yz_tenants` (`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试主表(关联分类+租户隔离)'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_exam_category` --- - -DROP TABLE IF EXISTS `yz_exam_category`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_exam_category` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类唯一标识', - `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', - `category_name` varchar(50) NOT NULL COMMENT '分类名称', - `parent_id` int(11) DEFAULT '0' COMMENT '父分类ID,0表示一级分类', - `sort_order` tinyint(4) DEFAULT '0' COMMENT '排序序号', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(1-启用,0-禁用)', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `idx_tenant_parent` (`tenant_id`,`parent_id`), - KEY `idx_tenant_status` (`tenant_id`,`status`), - KEY `idx_tenant_id` (`tenant_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试分类表(含多租户隔离)'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_exam_question_bank` --- - -DROP TABLE IF EXISTS `yz_exam_question_bank`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_exam_question_bank` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '题库ID', - `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', - `bank_name` varchar(100) NOT NULL COMMENT '题库名称', - `bank_desc` varchar(500) DEFAULT '' COMMENT '题库描述', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(1-启用,0-禁用)', - `sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序序号', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', - PRIMARY KEY (`id`), - KEY `idx_tenant_status` (`tenant_id`,`status`), - KEY `idx_tenant_name` (`tenant_id`,`bank_name`), - KEY `idx_delete_time` (`delete_time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试题库表(支持租户隔离、软删除)'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_exam_users` --- - -DROP TABLE IF EXISTS `yz_exam_users`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_exam_users` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户唯一标识', - `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', - `username` varchar(50) NOT NULL COMMENT '用户名', - `real_name` varchar(50) DEFAULT '' COMMENT '真实姓名', - `mobile` varchar(20) DEFAULT '' COMMENT '手机号', - `email` varchar(100) DEFAULT '' COMMENT '邮箱', - `user_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '用户类型(1-学生,2-教师,3-管理员)', - `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '账号状态(1-正常,0-禁用,2-冻结)', - `password` varchar(100) NOT NULL COMMENT '加密后密码(salt+明文密码哈希)', - `salt` varchar(50) NOT NULL COMMENT '密码盐值', - `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tenant_username` (`tenant_id`,`username`), - UNIQUE KEY `uk_salt` (`salt`) USING BTREE, - UNIQUE KEY `uk_tenant_mobile` (`tenant_id`,`mobile`) USING BTREE, - KEY `idx_tenant_username` (`tenant_id`,`username`), - KEY `idx_tenant_type_status` (`tenant_id`,`user_type`,`status`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试用户表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_files` --- - -DROP TABLE IF EXISTS `yz_files`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_files` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '文件ID', - `tenant_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户ID', - `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID', - `file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件名称', - `original_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名', - `file_path` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件存储路径', - `file_url` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件访问URL', - `file_size` bigint(20) NOT NULL DEFAULT '0' COMMENT '文件大小(字节)', - `file_type` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件类型', - `file_ext` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件扩展名', - `md5` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件MD5值,用于去重', - `category` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件分类', - `sub_category` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '子分类', - `status` tinyint(4) DEFAULT '1' COMMENT '状态(1:正常, 0:删除)', - `is_public` tinyint(4) DEFAULT '0' COMMENT '是否公开(1:是, 0:否)', - `upload_by` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '上传人', - `upload_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`), - KEY `idx_tenant` (`tenant_id`), - KEY `idx_user` (`user_id`), - KEY `idx_category` (`category`), - KEY `idx_upload_time` (`upload_time`), - KEY `idx_status` (`status`), - KEY `idx_md5` (`md5`), - KEY `idx_original_name` (`original_name`) -) ENGINE=InnoDB AUTO_INCREMENT=72 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_knowledge` --- - -DROP TABLE IF EXISTS `yz_knowledge`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_knowledge` ( - `knowledge_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '知识ID', - `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', - `title` varchar(200) NOT NULL COMMENT '标题', - `category_id` int(11) DEFAULT NULL COMMENT '分类ID', - `tags` text COMMENT '标签JSON数组,存储标签名称', - `author` varchar(50) NOT NULL COMMENT '作者', - `content` longtext COMMENT '正文内容(富文本)', - `summary` varchar(500) DEFAULT NULL COMMENT '摘要', - `cover_url` varchar(500) DEFAULT NULL COMMENT '封面图片URL', - `status` tinyint(4) DEFAULT '0' COMMENT '状态:0-草稿,1-已发布,2-已归档', - `share` int(10) DEFAULT '0' COMMENT '是否共享 0-不共享 1-共享', - `view_count` int(11) DEFAULT '0' COMMENT '查看次数', - `like_count` int(11) DEFAULT '0' COMMENT '点赞数', - `is_recommend` tinyint(4) DEFAULT '0' COMMENT '是否推荐:0-否,1-是', - `is_top` tinyint(4) DEFAULT '0' COMMENT '是否置顶:0-否,1-是', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', - `update_by` varchar(50) DEFAULT NULL COMMENT '更新人', - `delete_by` varchar(255) DEFAULT NULL COMMENT '删除人', - PRIMARY KEY (`knowledge_id`), - KEY `idx_category_id` (`category_id`), - KEY `idx_author` (`author`), - KEY `idx_status` (`status`), - KEY `idx_create_time` (`create_time`), - KEY `idx_view_count` (`view_count`), - KEY `idx_is_recommend` (`is_recommend`), - KEY `idx_is_top` (`is_top`) -) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COMMENT='知识库内容表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_knowledge_category` --- - -DROP TABLE IF EXISTS `yz_knowledge_category`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_knowledge_category` ( - `category_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类ID', - `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', - `category_name` varchar(100) NOT NULL COMMENT '分类名称', - `category_desc` varchar(500) DEFAULT NULL COMMENT '分类描述', - `parent_id` int(11) DEFAULT '0' COMMENT '父分类ID,0表示顶级分类', - `sort_order` int(11) DEFAULT '0' COMMENT '排序序号', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`category_id`), - KEY `idx_parent_id` (`parent_id`), - KEY `idx_sort_order` (`sort_order`) -) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COMMENT='知识库分类表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_knowledge_favorites` --- - -DROP TABLE IF EXISTS `yz_knowledge_favorites`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_knowledge_favorites` ( - `favorite_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '收藏ID', - `knowledge_id` int(11) NOT NULL COMMENT '知识ID', - `user_id` int(11) NOT NULL COMMENT '用户ID', - `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - PRIMARY KEY (`favorite_id`), - UNIQUE KEY `uk_knowledge_user` (`knowledge_id`,`user_id`), - KEY `idx_user_id` (`user_id`), - CONSTRAINT `yz_fk_fav_knowledge` FOREIGN KEY (`knowledge_id`) REFERENCES `yz_knowledge` (`knowledge_id`) ON DELETE CASCADE, - CONSTRAINT `yz_fk_fav_user` FOREIGN KEY (`user_id`) REFERENCES `yz_users` (`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库收藏表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_knowledge_tags` --- - -DROP TABLE IF EXISTS `yz_knowledge_tags`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_knowledge_tags` ( - `tag_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '标签ID', - `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', - `tag_name` varchar(50) NOT NULL COMMENT '标签名称', - `tag_background` varchar(20) DEFAULT NULL COMMENT '标签背景', - `tag_color` varchar(20) DEFAULT NULL COMMENT '标签颜色', - `usage_count` int(11) DEFAULT '0' COMMENT '使用次数', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`tag_id`), - UNIQUE KEY `uk_tag_name` (`tag_name`), - KEY `idx_usage_count` (`usage_count`) -) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT='知识库标签表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_menus` --- - -DROP TABLE IF EXISTS `yz_menus`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_menus` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', - `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单名称', - `path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单路径', - `parent_id` int(11) DEFAULT '0' COMMENT '父菜单ID,0表示顶级菜单', - `icon` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单图标', - `order` int(11) DEFAULT '0' COMMENT '排序序号', - `status` tinyint(4) DEFAULT '1' COMMENT '状态:0-禁用,1-启用', - `is_show` int(11) DEFAULT NULL COMMENT '是否显示 0-不显示 1-显示', - `component_path` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '组件路径', - `is_external` tinyint(4) DEFAULT '0' COMMENT '是否外部链接:0-内部路由,1-外部链接', - `external_url` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '外部链接地址', - `menu_type` tinyint(4) DEFAULT '1' COMMENT '菜单类型:1-页面菜单,2-目录菜单,3-权限按钮', - `permission` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '权限标识', - `description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单描述', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', - `create_by` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人', - `update_by` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人', - PRIMARY KEY (`id`), - KEY `idx_parent_id` (`parent_id`), - KEY `idx_status` (`status`), - KEY `idx_order` (`order`), - KEY `idx_menu_type` (`menu_type`), - KEY `idx_delete_time` (`delete_time`) -) ENGINE=InnoDB AUTO_INCREMENT=136 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单表(增强版)'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_program_category` --- - -DROP TABLE IF EXISTS `yz_program_category`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_program_category` ( - `category_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类ID', - `category_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称', - `category_desc` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '分类描述', - `parent_id` int(11) DEFAULT '0' COMMENT '父分类ID,0表示顶级分类', - `sort_order` int(11) DEFAULT '0' COMMENT '排序序号,用于展示顺序', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`category_id`), - KEY `idx_parent_id` (`parent_id`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序分类表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_program_info` --- - -DROP TABLE IF EXISTS `yz_program_info`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_program_info` ( - `program_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '程序ID', - `category_id` int(11) NOT NULL COMMENT '所属分类ID', - `program_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '程序名称', - `program_desc` text COLLATE utf8mb4_unicode_ci COMMENT '程序描述', - `jump_url` varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '跳转地址', - `icon_url` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '程序图标地址', - `version` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '程序版本', - `status` tinyint(4) DEFAULT '1' COMMENT '状态:0-禁用,1-启用', - `sort_order` int(11) DEFAULT '0' COMMENT '排序序号,用于展示顺序', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`program_id`), - KEY `idx_category_id` (`category_id`), - KEY `idx_status` (`status`), - CONSTRAINT `yz_fk_program_category` FOREIGN KEY (`category_id`) REFERENCES `yz_program_category` (`category_id`) ON DELETE CASCADE -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序信息表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_roles` --- - -DROP TABLE IF EXISTS `yz_roles`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_roles` ( - `role_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色ID', - `tenant_id` int(11) NOT NULL COMMENT '租户ID 0-全局角色 其他-各租户自设角色', - `role_code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色代码', - `role_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称', - `description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色描述', - `menu_ids` json DEFAULT NULL COMMENT '菜单权限ID数组,JSON格式存储', - `status` tinyint(4) DEFAULT '1' COMMENT '角色状态:0-禁用,1-启用', - `sort_order` int(11) DEFAULT '0' COMMENT '排序序号', - `default` tinyint(4) NOT NULL DEFAULT '0' COMMENT '角色显示范围:0-全局展示,1-平台用户展示(yz_users),2-租户用户展示(yz_tenant_employees)', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - `create_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人', - `update_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人', - PRIMARY KEY (`role_id`), - UNIQUE KEY `uk_role_code` (`role_code`), - KEY `idx_status` (`status`), - KEY `idx_sort_order` (`sort_order`), - KEY `idx_create_time` (`create_time`), - KEY `idx_tenant_id` (`tenant_id`), - KEY `idx_delete_time` (`delete_time`) -) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_tenant_crm_contact` --- - -DROP TABLE IF EXISTS `yz_tenant_crm_contact`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_tenant_crm_contact` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID', - `tenant_id` bigint(20) NOT NULL COMMENT '租户ID(关联租户表,隔离数据)', - `related_type` tinyint(4) NOT NULL COMMENT '关联类型:1=客户,2=供应商(区分联系人归属)', - `related_id` bigint(20) NOT NULL COMMENT '关联ID(关联yz_tenant_crm_customer.id或yz_tenant_crm_supplier.id)', - `contact_name` varchar(50) NOT NULL COMMENT '联系人姓名', - `gender` tinyint(4) DEFAULT '0' COMMENT '性别:0=未知,1=男,2=女', - `mobile` varchar(20) DEFAULT NULL COMMENT '手机号(唯一索引,避免重复)', - `phone` varchar(20) DEFAULT NULL COMMENT '固定电话', - `email` varchar(100) DEFAULT NULL COMMENT '邮箱', - `position` varchar(50) DEFAULT NULL COMMENT '职位(如:项目经理、采购负责人)', - `department` varchar(50) DEFAULT NULL COMMENT '所属部门', - `is_primary` tinyint(4) DEFAULT '0' COMMENT '是否主联系人:0=否,1=是(一个客户/供应商可设一个主联系人)', - `remark` varchar(500) DEFAULT NULL COMMENT '备注(如:关键决策人、对接优先级等)', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建人ID(关联用户表)', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新人ID(关联用户表)', - `is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除:0=正常,1=删除', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tenant_mobile` (`tenant_id`,`mobile`) COMMENT '同一租户内手机号唯一', - KEY `idx_tenant_related` (`tenant_id`,`related_type`,`related_id`) COMMENT '查询租户下某客户/供应商的所有联系人', - KEY `idx_contact_name` (`tenant_id`,`contact_name`) COMMENT '按姓名模糊查询联系人' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户CRM联系人表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_tenant_crm_customer` --- - -DROP TABLE IF EXISTS `yz_tenant_crm_customer`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_tenant_crm_customer` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID', - `tenant_id` varchar(64) NOT NULL COMMENT '租户ID', - `customer_name` varchar(100) NOT NULL COMMENT '客户名称(企业/个人名称)', - `customer_type` varchar(20) NOT NULL COMMENT '客户类型(1-企业/2-政府机构/3-国企/4-个人)', - `contact_person` varchar(50) NOT NULL COMMENT '联系人姓名', - `contact_phone` varchar(20) NOT NULL COMMENT '联系人电话', - `contact_email` varchar(100) DEFAULT '' COMMENT '联系人邮箱', - `customer_level` varchar(20) DEFAULT '3' COMMENT '客户等级(1-核心客户/2-重要客户/3-普通客户/4-潜在客户)', - `industry` varchar(50) DEFAULT '' COMMENT '所属行业(如:互联网、金融、制造业、教育等)', - `address` varchar(255) DEFAULT '' COMMENT '客户地址(详细地址)', - `register_time` date DEFAULT NULL COMMENT '客户注册/合作起始日期', - `expire_time` date DEFAULT NULL COMMENT '合作到期日期(无到期则为空)', - `status` varchar(20) NOT NULL DEFAULT '1' COMMENT '客户状态(0-禁用/1-正常/2-冻结/3-已注销)', - `remark` text COMMENT '客户备注', - `invoice_title` varchar(100) DEFAULT '' COMMENT '发票抬头', - `tax_number` varchar(50) DEFAULT '' COMMENT '纳税人识别号', - `bank_name` varchar(100) DEFAULT '' COMMENT '开户行名称', - `bank_account` varchar(50) DEFAULT '' COMMENT '开户行账号', - `registered_address` varchar(255) DEFAULT '' COMMENT '注册地址', - `registered_phone` varchar(50) DEFAULT '' COMMENT '注册电话', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`), - KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引,优化多租户隔离查询', - KEY `idx_customer_name` (`customer_name`) COMMENT '客户名称索引,优化按名称模糊查询', - KEY `idx_contact_phone` (`contact_phone`) COMMENT '联系人电话索引,优化按电话精准查询', - KEY `idx_status` (`status`) COMMENT '客户状态索引,优化按状态筛选(如:查询正常客户)', - KEY `idx_register_time` (`register_time`) COMMENT '注册时间索引,优化按合作时间范围查询' -) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COMMENT='客户管理表(支持租户隔离、软删除、客户全生命周期追踪)'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_tenant_crm_supplier` --- - -DROP TABLE IF EXISTS `yz_tenant_crm_supplier`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_tenant_crm_supplier` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID', - `tenant_id` varchar(64) NOT NULL COMMENT '租户ID', - `supplier_name` varchar(100) NOT NULL COMMENT '供应商名称(企业/个人名称)', - `supplier_type` varchar(20) NOT NULL COMMENT '供应商类型', - `contact_person` varchar(50) NOT NULL COMMENT '联系人姓名', - `contact_phone` varchar(20) NOT NULL COMMENT '联系人电话', - `contact_email` varchar(100) DEFAULT '' COMMENT '联系人邮箱', - `supplier_level` varchar(20) DEFAULT '3' COMMENT '供应商等级(1-核心供应商/2-重要供应商/3-普通供应商/4-潜在供应商)', - `industry` varchar(50) DEFAULT '' COMMENT '所属行业', - `address` varchar(255) DEFAULT '' COMMENT '供应商地址', - `register_time` date DEFAULT NULL COMMENT '供应商注册/合作起始日期', - `expire_time` date DEFAULT NULL COMMENT '合作到期日期', - `status` varchar(20) NOT NULL DEFAULT '1' COMMENT '供应商状态(0-禁用/1-正常/2-冻结/3-已注销)', - `remark` text COMMENT '供应商备注', - `invoice_title` varchar(100) DEFAULT '' COMMENT '发票抬头', - `tax_number` varchar(50) DEFAULT '' COMMENT '纳税人识别号', - `bank_name` varchar(100) DEFAULT '' COMMENT '开户行名称', - `bank_account` varchar(50) DEFAULT '' COMMENT '开户行账号', - `registered_address` varchar(255) DEFAULT '' COMMENT '注册地址', - `registered_phone` varchar(50) DEFAULT '' COMMENT '注册电话', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`), - KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引,优化多租户隔离查询', - KEY `idx_supplier_name` (`supplier_name`) COMMENT '供应商名称索引,优化按名称模糊查询', - KEY `idx_contact_phone` (`contact_phone`) COMMENT '联系人电话索引,优化按电话精准查询', - KEY `idx_status` (`status`) COMMENT '供应商状态索引,优化按状态筛选', - KEY `idx_register_time` (`register_time`) COMMENT '注册时间索引,优化按合作时间范围查询' -) ENGINE=InnoDB AUTO_INCREMENT=501 DEFAULT CHARSET=utf8mb4 COMMENT='供应商管理表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_tenant_departments` --- - -DROP TABLE IF EXISTS `yz_tenant_departments`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_tenant_departments` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '部门ID', - `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID', - `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '部门名称', - `code` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门编码', - `parent_id` int(11) DEFAULT '0' COMMENT '父部门ID,0表示顶级部门', - `description` text COLLATE utf8mb4_unicode_ci COMMENT '部门描述', - `manager_id` int(11) DEFAULT NULL COMMENT '部门经理ID', - `sort_order` int(11) DEFAULT '0' COMMENT '排序序号', - `status` tinyint(4) DEFAULT '1' COMMENT '状态:1-启用,0-禁用', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', - PRIMARY KEY (`id`), - KEY `idx_tenant_id` (`tenant_id`), - KEY `idx_code` (`code`), - KEY `idx_parent_id` (`parent_id`), - KEY `idx_status` (`status`), - KEY `idx_sort_order` (`sort_order`), - KEY `idx_delete_time` (`delete_time`), - KEY `idx_tenant_delete` (`tenant_id`,`delete_time`) -) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_tenant_employees` --- - -DROP TABLE IF EXISTS `yz_tenant_employees`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_tenant_employees` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '员工ID', - `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID', - `employee_no` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '工号', - `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名', - `phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号', - `email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱', - `department_id` int(11) DEFAULT NULL COMMENT '部门ID', - `position_id` int(11) DEFAULT NULL COMMENT '职位ID', - `role` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户角色', - `bank_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '工资卡开户行', - `bank_account` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '工资卡卡号', - `password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '登录密码(加密后)', - `salt` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码盐值', - `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', - `last_login_ip` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '最后登录IP', - `status` tinyint(4) DEFAULT '1' COMMENT '状态:1-在职,0-离职', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', - PRIMARY KEY (`id`), - KEY `idx_tenant_id` (`tenant_id`), - KEY `idx_employee_no` (`employee_no`), - KEY `idx_name` (`name`), - KEY `idx_department_id` (`department_id`), - KEY `idx_position_id` (`position_id`), - KEY `idx_status` (`status`), - KEY `idx_create_time` (`create_time`) -) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='员工表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_tenant_positions` --- - -DROP TABLE IF EXISTS `yz_tenant_positions`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_tenant_positions` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '职位ID', - `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID', - `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '职位名称', - `code` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '职位编码', - `department_id` int(11) DEFAULT NULL COMMENT '所属部门ID', - `level` int(11) DEFAULT '0' COMMENT '职位级别', - `description` text COLLATE utf8mb4_unicode_ci COMMENT '职位描述', - `sort_order` int(11) DEFAULT '0' COMMENT '排序序号', - `status` tinyint(4) DEFAULT '1' COMMENT '状态:1-启用,0-禁用', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', - PRIMARY KEY (`id`), - KEY `idx_tenant_id` (`tenant_id`), - KEY `idx_code` (`code`), - KEY `idx_department_id` (`department_id`), - KEY `idx_status` (`status`), - KEY `idx_sort_order` (`sort_order`), - KEY `idx_delete_time` (`delete_time`), - KEY `idx_dept_delete_status` (`department_id`,`delete_time`,`status`) -) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='职位表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_tenant_tasks` --- - -DROP TABLE IF EXISTS `yz_tenant_tasks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_tenant_tasks` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID(主键)', - `tenant_id` bigint(20) NOT NULL COMMENT '租户ID(多租户隔离,如无多租户需求可设为默认1)', - `task_no` varchar(64) NOT NULL COMMENT '任务编号(唯一标识,如TASK20251112001)', - `task_name` varchar(255) NOT NULL COMMENT '任务名称', - `task_desc` text COMMENT '任务描述(富文本内容,支持图片/表格)', - `task_type` varchar(32) DEFAULT 'common' COMMENT '任务类型(common=普通任务,project=项目任务,repeat=重复任务,可自定义)', - `business_tag` varchar(64) DEFAULT NULL COMMENT '业务标签(多个标签用逗号分隔,如"紧急,日常协作")', - `parent_task_id` bigint(20) DEFAULT NULL COMMENT '父任务ID(子任务关联用,无父任务则为NULL)', - `project_id` bigint(20) DEFAULT NULL COMMENT '关联项目ID(关联OA项目模块)', - `related_id` bigint(20) DEFAULT NULL COMMENT '关联其他模块ID(如审批单ID、客户ID)', - `related_type` varchar(32) DEFAULT NULL COMMENT '关联模块类型(approval=审批单,customer=客户,为空则无关联)', - `team_employee_ids` varchar(500) DEFAULT NULL COMMENT '团队成员', - `creator_id` bigint(20) NOT NULL COMMENT '创建人ID', - `creator_name` varchar(64) NOT NULL COMMENT '创建人姓名', - `principal_id` bigint(20) NOT NULL COMMENT '负责人ID', - `principal_name` varchar(64) NOT NULL COMMENT '负责人姓名', - `participant_ids` varchar(512) DEFAULT NULL COMMENT '参与人ID(多个用逗号分隔)', - `participant_names` varchar(512) DEFAULT NULL COMMENT '参与人姓名(多个用逗号分隔)', - `cc_ids` varchar(512) DEFAULT NULL COMMENT '抄送人ID(多个用逗号分隔)', - `cc_names` varchar(512) DEFAULT NULL COMMENT '抄送人姓名(多个用逗号分隔)', - `plan_start_time` datetime DEFAULT NULL COMMENT '计划开始时间', - `plan_end_time` datetime NOT NULL COMMENT '计划截止时间', - `actual_start_time` datetime DEFAULT NULL COMMENT '实际开始时间', - `actual_end_time` datetime DEFAULT NULL COMMENT '实际结束时间', - `estimated_hours` decimal(10,2) DEFAULT NULL COMMENT '预估工时(小时)', - `actual_hours` decimal(10,2) DEFAULT NULL COMMENT '实际工时(小时)', - `task_status` varchar(32) NOT NULL DEFAULT 'not_started' COMMENT '任务状态(0-未开始,1-进行中,2-暂停,3-已完成,4-已关闭)', - `priority` varchar(16) NOT NULL DEFAULT 'medium' COMMENT '优先级(0-高,1-中,2-低,3-紧急)', - `progress` tinyint(4) NOT NULL DEFAULT '0' COMMENT '任务进度(0-100,子任务存在时自动计算)', - `need_approval` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要完成审批(0=否,1=是)', - `approval_id` bigint(20) DEFAULT NULL COMMENT '关联审批单ID(完成审批时填写)', - `delay_approved` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否已延期审批(0=否,1=是)', - `old_plan_end_time` datetime DEFAULT NULL COMMENT '原计划截止时间(延期时记录)', - `repeat_type` varchar(16) DEFAULT NULL COMMENT '重复类型(daily=按日,weekly=按周,monthly=按月,为空则非重复任务)', - `repeat_cycle` int(11) DEFAULT NULL COMMENT '重复周期(如每周重复则为7,每月重复则为30)', - `repeat_end_time` datetime DEFAULT NULL COMMENT '重复截止时间(重复任务终止时间)', - `attachment_ids` varchar(1024) DEFAULT NULL COMMENT '附件ID(关联文件表,多个用逗号分隔)', - `remark` varchar(512) DEFAULT NULL COMMENT '备注(额外说明)', - `is_archived` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否归档(0=未归档,1=已归档)', - `archive_time` datetime DEFAULT NULL COMMENT '归档时间', - `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted_time` datetime DEFAULT NULL COMMENT '删除时间', - `operator_id` bigint(20) DEFAULT NULL COMMENT '最后操作人ID', - `operator_name` varchar(64) DEFAULT NULL COMMENT '最后操作人姓名', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tenant_task_no` (`tenant_id`,`task_no`) COMMENT '租户+任务编号唯一索引', - KEY `idx_tenant_principal` (`tenant_id`,`principal_id`) COMMENT '租户+负责人索引(查询个人任务)', - KEY `idx_tenant_status` (`tenant_id`,`task_status`) COMMENT '租户+状态索引(筛选任务状态)', - KEY `idx_tenant_project` (`tenant_id`,`project_id`) COMMENT '租户+项目索引(查询项目下任务)', - KEY `idx_tenant_plan_end_time` (`tenant_id`,`plan_end_time`) COMMENT '租户+截止时间索引(逾期提醒、日历视图)', - KEY `idx_parent_task_id` (`parent_task_id`) COMMENT '父任务ID索引(查询子任务)' -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='OA系统任务表(多租户适配)'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_tenants` --- - -DROP TABLE IF EXISTS `yz_tenants`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_tenants` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '租户ID', - `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户名称', - `code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户编码(唯一)', - `owner` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '负责人', - `phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', - `email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱地址', - `status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT '1' COMMENT '状态:1-启用,0-禁用', - `audit_status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'pending' COMMENT '审核状态:pending-待审核,approved-已通过,rejected-已拒绝', - `audit_comment` text COLLATE utf8mb4_unicode_ci COMMENT '审核意见', - `audit_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核人', - `audit_time` datetime DEFAULT NULL COMMENT '审核时间', - `capacity` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '分配空间容量(MB)', - `capacity_used` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '已用空间容量(MB)', - `attachment_url` longtext COLLATE utf8mb4_unicode_ci COMMENT '附件', - `remark` text COLLATE utf8mb4_unicode_ci COMMENT '备注', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - `create_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人', - `update_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_code` (`code`), - KEY `idx_name` (`name`), - KEY `idx_owner` (`owner`), - KEY `idx_status` (`status`), - KEY `idx_audit_status` (`audit_status`), - KEY `idx_create_time` (`create_time`) -) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表'; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Table structure for table `yz_users` --- - -DROP TABLE IF EXISTS `yz_users`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!50503 SET character_set_client = utf8mb4 */; -CREATE TABLE `yz_users` ( - `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID', - `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', - `username` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名', - `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '加密后的密码', - `salt` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码盐值', - `email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱地址', - `avatar` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像URL', - `nickname` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称', - `role` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'user' COMMENT '用户角色', - `department_id` int(11) DEFAULT NULL COMMENT '部门ID', - `position_id` int(11) DEFAULT NULL COMMENT '职位ID', - `status` tinyint(4) DEFAULT '1' COMMENT '用户状态:0-禁用,1-启用', - `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', - `last_login_ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '最后登录IP', - `login_count` int(11) DEFAULT '0' COMMENT '登录次数', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - `create_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人', - `update_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_username` (`username`), - KEY `idx_email` (`email`), - KEY `idx_role` (`role`), - KEY `idx_status` (`status`), - KEY `idx_create_time` (`create_time`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表'; -/*!40101 SET character_set_client = @saved_cs_client */; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - -CREATE TABLE `yz_exam_question` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '试题唯一标识', - `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', - `question_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '题型(1-单选,2-多选,3-判断,4-填空,5-简答)', - `question_title` varchar(1000) NOT NULL COMMENT '题干内容', - `question_analysis` varchar(2000) DEFAULT '' COMMENT '试题解析', - `score` decimal(5,2) NOT NULL DEFAULT 0.00 COMMENT '试题分值', - `sort_order` tinyint(4) DEFAULT 0 COMMENT '排序序号', - `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态(1-启用,0-禁用)', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', - PRIMARY KEY (`id`), - -- 核心联合索引:租户+状态+删除时间(查询可用试题) - KEY `idx_tenant_status_delete` (`tenant_id`, `status`, `delete_time`), - -- 联合索引:租户+题型+删除时间(筛选特定题型试题) - KEY `idx_tenant_type_delete` (`tenant_id`, `question_type`, `delete_time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='试题主表(通用型+多租户隔离+软删除)'; - -CREATE TABLE `yz_exam_question_option` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '选项唯一标识', - `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', - `question_id` bigint(20) NOT NULL COMMENT '关联试题主表ID', - `option_label` varchar(10) NOT NULL COMMENT '选项标签(A/B/C/D/对/错)', - `option_content` varchar(500) NOT NULL COMMENT '选项内容', - `sort_order` tinyint(4) DEFAULT 0 COMMENT '排序序号', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', - PRIMARY KEY (`id`), - -- 核心联合索引:租户+试题ID+删除时间(查询某试题所有选项) - KEY `idx_tenant_question_delete` (`tenant_id`, `question_id`, `delete_time`), - -- 联合索引:租户+试题ID+选项标签+删除时间(快速定位选项) - KEY `idx_tenant_question_label_delete` (`tenant_id`, `question_id`, `option_label`, `delete_time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='试题选项表(客观题专用+多租户隔离+软删除)'; - -CREATE TABLE `yz_exam_question_answer` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '答案唯一标识', - `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', - `question_id` bigint(20) NOT NULL COMMENT '关联试题主表ID', - `answer_content` varchar(1000) NOT NULL COMMENT '正确答案(客观题存标签,主观题存文字)', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', - PRIMARY KEY (`id`), - -- 核心联合索引:租户+试题ID+删除时间(查询某试题正确答案) - KEY `idx_tenant_question_delete` (`tenant_id`, `question_id`, `delete_time`), - -- 唯一索引:同一租户下同一试题只能有一个正确答案 - UNIQUE KEY `uk_tenant_question` (`tenant_id`, `question_id`, `delete_time`) COMMENT '软删除状态下试题答案唯一' +-- MySQL dump 10.13 Distrib 8.0.41, for Win64 (x86_64) +-- +-- Host: 212.64.112.158 Database: gotest +-- ------------------------------------------------------ +-- Server version 5.7.44-log + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Current Database: `gotest` +-- + +CREATE DATABASE /*!32312 IF NOT EXISTS*/ `gotest` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; + +USE `gotest`; + +-- +-- Table structure for table `sys_access_log` +-- + +DROP TABLE IF EXISTS `sys_access_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_access_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `tenant_id` int(11) NOT NULL DEFAULT '0', + `user_id` int(11) NOT NULL DEFAULT '0', + `username` varchar(255) NOT NULL DEFAULT '', + `module` varchar(255) NOT NULL DEFAULT '', + `resource_type` varchar(255) NOT NULL DEFAULT '', + `resource_id` int(11) DEFAULT NULL, + `request_url` varchar(255) DEFAULT NULL, + `query_string` longtext, + `ip_address` varchar(255) DEFAULT NULL, + `user_agent` varchar(255) DEFAULT NULL, + `request_method` varchar(255) DEFAULT NULL, + `duration` int(11) DEFAULT NULL, + `create_time` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `sys_dict_item` +-- + +DROP TABLE IF EXISTS `sys_dict_item`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_dict_item` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `dict_type_id` bigint(20) NOT NULL COMMENT '字典类型ID', + `dict_label` varchar(100) NOT NULL COMMENT '字典标签(显示值,如 正常)', + `dict_value` varchar(100) NOT NULL COMMENT '字典值(存储值,如 1)', + `parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父级字典项ID(0表示一级项)', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0-禁用,1-启用)', + `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序号', + `color` varchar(20) DEFAULT NULL COMMENT '颜色标记(如 #1890ff)', + `icon` varchar(50) DEFAULT NULL COMMENT '图标(如 el-icon-success)', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(50) DEFAULT NULL COMMENT '更新人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删,1-已删)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_dict_type_value` (`dict_type_id`,`dict_value`,`is_deleted`), + KEY `idx_dict_type_parent_status` (`dict_type_id`,`parent_id`,`status`,`is_deleted`), + KEY `idx_parent_id` (`parent_id`,`status`,`is_deleted`) +) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8mb4 COMMENT='字典项表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `sys_dict_type` +-- + +DROP TABLE IF EXISTS `sys_dict_type`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_dict_type` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID(0表示平台字典,>0表示租户字典)', + `dict_code` varchar(50) NOT NULL COMMENT '字典编码(唯一,如 USER_STATUS)', + `dict_name` varchar(100) NOT NULL COMMENT '字典名称(如 用户状态)', + `parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父级字典ID(0表示一级字典)', + `is_global` tinyint(4) DEFAULT '0' COMMENT '是否全局0-否 1-是', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(0-禁用,1-启用)', + `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序号', + `remark` varchar(500) DEFAULT NULL COMMENT '备注', + `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` varchar(50) DEFAULT NULL COMMENT '更新人', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删,1-已删)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_dict_code_tenant` (`dict_code`,`is_deleted`), + KEY `idx_parent_id` (`parent_id`,`is_deleted`), + KEY `idx_status` (`status`,`is_deleted`), + KEY `idx_tenant_id` (`tenant_id`,`is_deleted`) +) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COMMENT='字典类型表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `sys_feedback` +-- + +DROP TABLE IF EXISTS `sys_feedback`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_feedback` ( + `id` varchar(36) NOT NULL COMMENT 'ID', + `tenant_id` varchar(64) NOT NULL COMMENT '租户ID', + `feedback_name` varchar(50) DEFAULT '' COMMENT '反馈人姓名', + `module` varchar(30) NOT NULL COMMENT '反馈对应模块', + `feedback_type` varchar(20) NOT NULL COMMENT '反馈类型', + `content` text NOT NULL COMMENT '反馈详细内容', + `attachment_url` varchar(255) DEFAULT '' COMMENT '附件URL', + `handle_status` varchar(20) NOT NULL DEFAULT '0' COMMENT '处理状态(0-待处理/1-处理中/2-已解决/3-已驳回/4-无需处理)', + `handle_remark` text COMMENT '处理备注(移除默认值,TEXT类型不支持)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引,优化租户级查询', + KEY `idx_module` (`module`) COMMENT '模块索引,优化模块级反馈统计', + KEY `idx_handle_status` (`handle_status`) COMMENT '处理状态索引,优化待处理反馈查询', + KEY `idx_create_time` (`create_time`) COMMENT '创建时间索引,优化时间范围查询' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通用反馈表(支持租户隔离、软删除)'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `sys_operation_log` +-- + +DROP TABLE IF EXISTS `sys_operation_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sys_operation_log` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志ID', + `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID(0表示平台操作,>0表示租户操作)', + `user_id` int(11) NOT NULL COMMENT '操作用户ID', + `username` varchar(50) NOT NULL COMMENT '操作用户名', + `module` varchar(100) NOT NULL COMMENT '操作模块(user/tenant/dict/role等)', + `resource_type` varchar(50) NOT NULL COMMENT '资源类型(如User/Tenant/Dict等)', + `resource_id` int(11) DEFAULT NULL COMMENT '资源ID(如被操作的用户ID、租户ID等)', + `operation` varchar(20) NOT NULL COMMENT '操作类型(CREATE/UPDATE/DELETE/LOGIN/LOGOUT/VIEW等)', + `description` varchar(500) DEFAULT NULL COMMENT '操作描述', + `old_value` longtext COMMENT '修改前的值(JSON格式,用于UPDATE操作)', + `new_value` longtext COMMENT '修改后的值(JSON格式,用于UPDATE操作)', + `ip_address` varchar(50) DEFAULT NULL COMMENT 'IP地址', + `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理信息', + `request_method` varchar(10) DEFAULT NULL COMMENT '请求方法(GET/POST/PUT/DELETE等)', + `request_url` varchar(500) DEFAULT NULL COMMENT '请求URL', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1-成功,0-失败', + `error_message` text COMMENT '错误信息', + `duration` int(11) DEFAULT NULL COMMENT '执行时长(毫秒)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间', + PRIMARY KEY (`id`), + KEY `idx_tenant_id` (`tenant_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_module` (`module`), + KEY `idx_resource_type` (`resource_type`), + KEY `idx_resource_id` (`resource_id`), + KEY `idx_operation` (`operation`), + KEY `idx_create_time` (`create_time`), + KEY `idx_tenant_user_time` (`tenant_id`,`user_id`,`create_time`), + KEY `idx_tenant_module_time` (`tenant_id`,`module`,`create_time`) +) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8mb4 COMMENT='系统操作日志表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_exam` +-- + +DROP TABLE IF EXISTS `yz_exam`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_exam` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '考试唯一标识', + `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', + `category_id` int(11) NOT NULL COMMENT '关联考试分类表ID', + `exam_name` varchar(100) NOT NULL COMMENT '考试名称', + `exam_desc` varchar(500) DEFAULT '' COMMENT '考试描述', + `exam_time` datetime NOT NULL COMMENT '考试开始时间', + `exam_duration` int(11) NOT NULL COMMENT '考试时长(分钟)', + `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '考试状态(0-未开始,1-进行中,2-已结束,3-已取消)', + `create_by` int(11) NOT NULL COMMENT '创建人ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_tenant_category_status` (`tenant_id`,`category_id`,`status`), + KEY `idx_tenant_create_by` (`tenant_id`,`create_by`), + KEY `fk_yz_exam_category` (`category_id`), + CONSTRAINT `fk_yz_exam_category` FOREIGN KEY (`category_id`) REFERENCES `yz_exam_category` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_yz_exam_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `yz_tenants` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试主表(关联分类+租户隔离)'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_exam_category` +-- + +DROP TABLE IF EXISTS `yz_exam_category`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_exam_category` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类唯一标识', + `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', + `category_name` varchar(50) NOT NULL COMMENT '分类名称', + `parent_id` int(11) DEFAULT '0' COMMENT '父分类ID,0表示一级分类', + `sort_order` tinyint(4) DEFAULT '0' COMMENT '排序序号', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(1-启用,0-禁用)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_tenant_parent` (`tenant_id`,`parent_id`), + KEY `idx_tenant_status` (`tenant_id`,`status`), + KEY `idx_tenant_id` (`tenant_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试分类表(含多租户隔离)'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_exam_question_bank` +-- + +DROP TABLE IF EXISTS `yz_exam_question_bank`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_exam_question_bank` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '题库ID', + `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', + `bank_name` varchar(100) NOT NULL COMMENT '题库名称', + `bank_desc` varchar(500) DEFAULT '' COMMENT '题库描述', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态(1-启用,0-禁用)', + `sort_order` int(11) NOT NULL DEFAULT '0' COMMENT '排序序号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + KEY `idx_tenant_status` (`tenant_id`,`status`), + KEY `idx_tenant_name` (`tenant_id`,`bank_name`), + KEY `idx_delete_time` (`delete_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试题库表(支持租户隔离、软删除)'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_exam_users` +-- + +DROP TABLE IF EXISTS `yz_exam_users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_exam_users` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户唯一标识', + `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', + `username` varchar(50) NOT NULL COMMENT '用户名', + `real_name` varchar(50) DEFAULT '' COMMENT '真实姓名', + `mobile` varchar(20) DEFAULT '' COMMENT '手机号', + `email` varchar(100) DEFAULT '' COMMENT '邮箱', + `user_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '用户类型(1-学生,2-教师,3-管理员)', + `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '账号状态(1-正常,0-禁用,2-冻结)', + `password` varchar(100) NOT NULL COMMENT '加密后密码(salt+明文密码哈希)', + `salt` varchar(50) NOT NULL COMMENT '密码盐值', + `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tenant_username` (`tenant_id`,`username`), + UNIQUE KEY `uk_salt` (`salt`) USING BTREE, + UNIQUE KEY `uk_tenant_mobile` (`tenant_id`,`mobile`) USING BTREE, + KEY `idx_tenant_username` (`tenant_id`,`username`), + KEY `idx_tenant_type_status` (`tenant_id`,`user_type`,`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='考试用户表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_files` +-- + +DROP TABLE IF EXISTS `yz_files`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_files` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '文件ID', + `tenant_id` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户ID', + `user_id` int(11) NOT NULL DEFAULT '0' COMMENT '用户ID', + `file_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件名称', + `original_name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始文件名', + `file_path` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件存储路径', + `file_url` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件访问URL', + `file_size` bigint(20) NOT NULL DEFAULT '0' COMMENT '文件大小(字节)', + `file_type` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件类型', + `file_ext` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件扩展名', + `md5` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件MD5值,用于去重', + `category` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文件分类', + `sub_category` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '子分类', + `status` tinyint(4) DEFAULT '1' COMMENT '状态(1:正常, 0:删除)', + `is_public` tinyint(4) DEFAULT '0' COMMENT '是否公开(1:是, 0:否)', + `upload_by` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '上传人', + `upload_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + KEY `idx_tenant` (`tenant_id`), + KEY `idx_user` (`user_id`), + KEY `idx_category` (`category`), + KEY `idx_upload_time` (`upload_time`), + KEY `idx_status` (`status`), + KEY `idx_md5` (`md5`), + KEY `idx_original_name` (`original_name`) +) ENGINE=InnoDB AUTO_INCREMENT=72 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文件表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_knowledge` +-- + +DROP TABLE IF EXISTS `yz_knowledge`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_knowledge` ( + `knowledge_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '知识ID', + `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', + `title` varchar(200) NOT NULL COMMENT '标题', + `category_id` int(11) DEFAULT NULL COMMENT '分类ID', + `tags` text COMMENT '标签JSON数组,存储标签名称', + `author` varchar(50) NOT NULL COMMENT '作者', + `content` longtext COMMENT '正文内容(富文本)', + `summary` varchar(500) DEFAULT NULL COMMENT '摘要', + `cover_url` varchar(500) DEFAULT NULL COMMENT '封面图片URL', + `status` tinyint(4) DEFAULT '0' COMMENT '状态:0-草稿,1-已发布,2-已归档', + `share` int(10) DEFAULT '0' COMMENT '是否共享 0-不共享 1-共享', + `view_count` int(11) DEFAULT '0' COMMENT '查看次数', + `like_count` int(11) DEFAULT '0' COMMENT '点赞数', + `is_recommend` tinyint(4) DEFAULT '0' COMMENT '是否推荐:0-否,1-是', + `is_top` tinyint(4) DEFAULT '0' COMMENT '是否置顶:0-否,1-是', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `create_by` varchar(50) DEFAULT NULL COMMENT '创建人', + `update_by` varchar(50) DEFAULT NULL COMMENT '更新人', + `delete_by` varchar(255) DEFAULT NULL COMMENT '删除人', + PRIMARY KEY (`knowledge_id`), + KEY `idx_category_id` (`category_id`), + KEY `idx_author` (`author`), + KEY `idx_status` (`status`), + KEY `idx_create_time` (`create_time`), + KEY `idx_view_count` (`view_count`), + KEY `idx_is_recommend` (`is_recommend`), + KEY `idx_is_top` (`is_top`) +) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COMMENT='知识库内容表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_knowledge_category` +-- + +DROP TABLE IF EXISTS `yz_knowledge_category`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_knowledge_category` ( + `category_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类ID', + `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', + `category_name` varchar(100) NOT NULL COMMENT '分类名称', + `category_desc` varchar(500) DEFAULT NULL COMMENT '分类描述', + `parent_id` int(11) DEFAULT '0' COMMENT '父分类ID,0表示顶级分类', + `sort_order` int(11) DEFAULT '0' COMMENT '排序序号', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`category_id`), + KEY `idx_parent_id` (`parent_id`), + KEY `idx_sort_order` (`sort_order`) +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COMMENT='知识库分类表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_knowledge_favorites` +-- + +DROP TABLE IF EXISTS `yz_knowledge_favorites`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_knowledge_favorites` ( + `favorite_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '收藏ID', + `knowledge_id` int(11) NOT NULL COMMENT '知识ID', + `user_id` int(11) NOT NULL COMMENT '用户ID', + `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`favorite_id`), + UNIQUE KEY `uk_knowledge_user` (`knowledge_id`,`user_id`), + KEY `idx_user_id` (`user_id`), + CONSTRAINT `yz_fk_fav_knowledge` FOREIGN KEY (`knowledge_id`) REFERENCES `yz_knowledge` (`knowledge_id`) ON DELETE CASCADE, + CONSTRAINT `yz_fk_fav_user` FOREIGN KEY (`user_id`) REFERENCES `yz_users` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库收藏表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_knowledge_tags` +-- + +DROP TABLE IF EXISTS `yz_knowledge_tags`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_knowledge_tags` ( + `tag_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '标签ID', + `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', + `tag_name` varchar(50) NOT NULL COMMENT '标签名称', + `tag_background` varchar(20) DEFAULT NULL COMMENT '标签背景', + `tag_color` varchar(20) DEFAULT NULL COMMENT '标签颜色', + `usage_count` int(11) DEFAULT '0' COMMENT '使用次数', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`tag_id`), + UNIQUE KEY `uk_tag_name` (`tag_name`), + KEY `idx_usage_count` (`usage_count`) +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COMMENT='知识库标签表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_menus` +-- + +DROP TABLE IF EXISTS `yz_menus`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_menus` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '菜单ID', + `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单名称', + `path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '菜单路径', + `parent_id` int(11) DEFAULT '0' COMMENT '父菜单ID,0表示顶级菜单', + `icon` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单图标', + `order` int(11) DEFAULT '0' COMMENT '排序序号', + `status` tinyint(4) DEFAULT '1' COMMENT '状态:0-禁用,1-启用', + `is_show` int(11) DEFAULT NULL COMMENT '是否显示 0-不显示 1-显示', + `component_path` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '组件路径', + `is_external` tinyint(4) DEFAULT '0' COMMENT '是否外部链接:0-内部路由,1-外部链接', + `external_url` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '外部链接地址', + `menu_type` tinyint(4) DEFAULT '1' COMMENT '菜单类型:1-页面菜单,2-目录菜单,3-权限按钮', + `permission` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '权限标识', + `description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '菜单描述', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + `create_by` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人', + `update_by` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`), + KEY `idx_parent_id` (`parent_id`), + KEY `idx_status` (`status`), + KEY `idx_order` (`order`), + KEY `idx_menu_type` (`menu_type`), + KEY `idx_delete_time` (`delete_time`) +) ENGINE=InnoDB AUTO_INCREMENT=136 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单表(增强版)'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_program_category` +-- + +DROP TABLE IF EXISTS `yz_program_category`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_program_category` ( + `category_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '分类ID', + `category_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称', + `category_desc` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '分类描述', + `parent_id` int(11) DEFAULT '0' COMMENT '父分类ID,0表示顶级分类', + `sort_order` int(11) DEFAULT '0' COMMENT '排序序号,用于展示顺序', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`category_id`), + KEY `idx_parent_id` (`parent_id`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序分类表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_program_info` +-- + +DROP TABLE IF EXISTS `yz_program_info`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_program_info` ( + `program_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '程序ID', + `category_id` int(11) NOT NULL COMMENT '所属分类ID', + `program_name` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '程序名称', + `program_desc` text COLLATE utf8mb4_unicode_ci COMMENT '程序描述', + `jump_url` varchar(1000) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '跳转地址', + `icon_url` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '程序图标地址', + `version` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '程序版本', + `status` tinyint(4) DEFAULT '1' COMMENT '状态:0-禁用,1-启用', + `sort_order` int(11) DEFAULT '0' COMMENT '排序序号,用于展示顺序', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`program_id`), + KEY `idx_category_id` (`category_id`), + KEY `idx_status` (`status`), + CONSTRAINT `yz_fk_program_category` FOREIGN KEY (`category_id`) REFERENCES `yz_program_category` (`category_id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='程序信息表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_roles` +-- + +DROP TABLE IF EXISTS `yz_roles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_roles` ( + `role_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色ID', + `tenant_id` int(11) NOT NULL COMMENT '租户ID 0-全局角色 其他-各租户自设角色', + `role_code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色代码', + `role_name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称', + `description` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色描述', + `menu_ids` json DEFAULT NULL COMMENT '菜单权限ID数组,JSON格式存储', + `status` tinyint(4) DEFAULT '1' COMMENT '角色状态:0-禁用,1-启用', + `sort_order` int(11) DEFAULT '0' COMMENT '排序序号', + `default` tinyint(4) NOT NULL DEFAULT '0' COMMENT '角色显示范围:0-全局展示,1-平台用户展示(yz_users),2-租户用户展示(yz_tenant_employees)', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `create_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人', + `update_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`role_id`), + UNIQUE KEY `uk_role_code` (`role_code`), + KEY `idx_status` (`status`), + KEY `idx_sort_order` (`sort_order`), + KEY `idx_create_time` (`create_time`), + KEY `idx_tenant_id` (`tenant_id`), + KEY `idx_delete_time` (`delete_time`) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_tenant_crm_contact` +-- + +DROP TABLE IF EXISTS `yz_tenant_crm_contact`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_tenant_crm_contact` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID(关联租户表,隔离数据)', + `related_type` tinyint(4) NOT NULL COMMENT '关联类型:1=客户,2=供应商(区分联系人归属)', + `related_id` bigint(20) NOT NULL COMMENT '关联ID(关联yz_tenant_crm_customer.id或yz_tenant_crm_supplier.id)', + `contact_name` varchar(50) NOT NULL COMMENT '联系人姓名', + `gender` tinyint(4) DEFAULT '0' COMMENT '性别:0=未知,1=男,2=女', + `mobile` varchar(20) DEFAULT NULL COMMENT '手机号(唯一索引,避免重复)', + `phone` varchar(20) DEFAULT NULL COMMENT '固定电话', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱', + `position` varchar(50) DEFAULT NULL COMMENT '职位(如:项目经理、采购负责人)', + `department` varchar(50) DEFAULT NULL COMMENT '所属部门', + `is_primary` tinyint(4) DEFAULT '0' COMMENT '是否主联系人:0=否,1=是(一个客户/供应商可设一个主联系人)', + `remark` varchar(500) DEFAULT NULL COMMENT '备注(如:关键决策人、对接优先级等)', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `create_by` bigint(20) DEFAULT NULL COMMENT '创建人ID(关联用户表)', + `update_by` bigint(20) DEFAULT NULL COMMENT '更新人ID(关联用户表)', + `is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除:0=正常,1=删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tenant_mobile` (`tenant_id`,`mobile`) COMMENT '同一租户内手机号唯一', + KEY `idx_tenant_related` (`tenant_id`,`related_type`,`related_id`) COMMENT '查询租户下某客户/供应商的所有联系人', + KEY `idx_contact_name` (`tenant_id`,`contact_name`) COMMENT '按姓名模糊查询联系人' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户CRM联系人表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_tenant_crm_customer` +-- + +DROP TABLE IF EXISTS `yz_tenant_crm_customer`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_tenant_crm_customer` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID', + `tenant_id` varchar(64) NOT NULL COMMENT '租户ID', + `customer_name` varchar(100) NOT NULL COMMENT '客户名称(企业/个人名称)', + `customer_type` varchar(20) NOT NULL COMMENT '客户类型(1-企业/2-政府机构/3-国企/4-个人)', + `contact_person` varchar(50) NOT NULL COMMENT '联系人姓名', + `contact_phone` varchar(20) NOT NULL COMMENT '联系人电话', + `contact_email` varchar(100) DEFAULT '' COMMENT '联系人邮箱', + `customer_level` varchar(20) DEFAULT '3' COMMENT '客户等级(1-核心客户/2-重要客户/3-普通客户/4-潜在客户)', + `industry` varchar(50) DEFAULT '' COMMENT '所属行业(如:互联网、金融、制造业、教育等)', + `address` varchar(255) DEFAULT '' COMMENT '客户地址(详细地址)', + `register_time` date DEFAULT NULL COMMENT '客户注册/合作起始日期', + `expire_time` date DEFAULT NULL COMMENT '合作到期日期(无到期则为空)', + `status` varchar(20) NOT NULL DEFAULT '1' COMMENT '客户状态(0-禁用/1-正常/2-冻结/3-已注销)', + `remark` text COMMENT '客户备注', + `invoice_title` varchar(100) DEFAULT '' COMMENT '发票抬头', + `tax_number` varchar(50) DEFAULT '' COMMENT '纳税人识别号', + `bank_name` varchar(100) DEFAULT '' COMMENT '开户行名称', + `bank_account` varchar(50) DEFAULT '' COMMENT '开户行账号', + `registered_address` varchar(255) DEFAULT '' COMMENT '注册地址', + `registered_phone` varchar(50) DEFAULT '' COMMENT '注册电话', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引,优化多租户隔离查询', + KEY `idx_customer_name` (`customer_name`) COMMENT '客户名称索引,优化按名称模糊查询', + KEY `idx_contact_phone` (`contact_phone`) COMMENT '联系人电话索引,优化按电话精准查询', + KEY `idx_status` (`status`) COMMENT '客户状态索引,优化按状态筛选(如:查询正常客户)', + KEY `idx_register_time` (`register_time`) COMMENT '注册时间索引,优化按合作时间范围查询' +) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4 COMMENT='客户管理表(支持租户隔离、软删除、客户全生命周期追踪)'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_tenant_crm_supplier` +-- + +DROP TABLE IF EXISTS `yz_tenant_crm_supplier`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_tenant_crm_supplier` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键ID', + `tenant_id` varchar(64) NOT NULL COMMENT '租户ID', + `supplier_name` varchar(100) NOT NULL COMMENT '供应商名称(企业/个人名称)', + `supplier_type` varchar(20) NOT NULL COMMENT '供应商类型', + `contact_person` varchar(50) NOT NULL COMMENT '联系人姓名', + `contact_phone` varchar(20) NOT NULL COMMENT '联系人电话', + `contact_email` varchar(100) DEFAULT '' COMMENT '联系人邮箱', + `supplier_level` varchar(20) DEFAULT '3' COMMENT '供应商等级(1-核心供应商/2-重要供应商/3-普通供应商/4-潜在供应商)', + `industry` varchar(50) DEFAULT '' COMMENT '所属行业', + `address` varchar(255) DEFAULT '' COMMENT '供应商地址', + `register_time` date DEFAULT NULL COMMENT '供应商注册/合作起始日期', + `expire_time` date DEFAULT NULL COMMENT '合作到期日期', + `status` varchar(20) NOT NULL DEFAULT '1' COMMENT '供应商状态(0-禁用/1-正常/2-冻结/3-已注销)', + `remark` text COMMENT '供应商备注', + `invoice_title` varchar(100) DEFAULT '' COMMENT '发票抬头', + `tax_number` varchar(50) DEFAULT '' COMMENT '纳税人识别号', + `bank_name` varchar(100) DEFAULT '' COMMENT '开户行名称', + `bank_account` varchar(50) DEFAULT '' COMMENT '开户行账号', + `registered_address` varchar(255) DEFAULT '' COMMENT '注册地址', + `registered_phone` varchar(50) DEFAULT '' COMMENT '注册电话', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引,优化多租户隔离查询', + KEY `idx_supplier_name` (`supplier_name`) COMMENT '供应商名称索引,优化按名称模糊查询', + KEY `idx_contact_phone` (`contact_phone`) COMMENT '联系人电话索引,优化按电话精准查询', + KEY `idx_status` (`status`) COMMENT '供应商状态索引,优化按状态筛选', + KEY `idx_register_time` (`register_time`) COMMENT '注册时间索引,优化按合作时间范围查询' +) ENGINE=InnoDB AUTO_INCREMENT=501 DEFAULT CHARSET=utf8mb4 COMMENT='供应商管理表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_tenant_departments` +-- + +DROP TABLE IF EXISTS `yz_tenant_departments`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_tenant_departments` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '部门ID', + `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID', + `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '部门名称', + `code` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '部门编码', + `parent_id` int(11) DEFAULT '0' COMMENT '父部门ID,0表示顶级部门', + `description` text COLLATE utf8mb4_unicode_ci COMMENT '部门描述', + `manager_id` int(11) DEFAULT NULL COMMENT '部门经理ID', + `sort_order` int(11) DEFAULT '0' COMMENT '排序序号', + `status` tinyint(4) DEFAULT '1' COMMENT '状态:1-启用,0-禁用', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + KEY `idx_tenant_id` (`tenant_id`), + KEY `idx_code` (`code`), + KEY `idx_parent_id` (`parent_id`), + KEY `idx_status` (`status`), + KEY `idx_sort_order` (`sort_order`), + KEY `idx_delete_time` (`delete_time`), + KEY `idx_tenant_delete` (`tenant_id`,`delete_time`) +) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='部门表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_tenant_employees` +-- + +DROP TABLE IF EXISTS `yz_tenant_employees`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_tenant_employees` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '员工ID', + `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID', + `employee_no` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '工号', + `name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '姓名', + `phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号', + `email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱', + `department_id` int(11) DEFAULT NULL COMMENT '部门ID', + `position_id` int(11) DEFAULT NULL COMMENT '职位ID', + `role` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户角色', + `bank_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '工资卡开户行', + `bank_account` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '工资卡卡号', + `password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '登录密码(加密后)', + `salt` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码盐值', + `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', + `last_login_ip` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '最后登录IP', + `status` tinyint(4) DEFAULT '1' COMMENT '状态:1-在职,0-离职', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + KEY `idx_tenant_id` (`tenant_id`), + KEY `idx_employee_no` (`employee_no`), + KEY `idx_name` (`name`), + KEY `idx_department_id` (`department_id`), + KEY `idx_position_id` (`position_id`), + KEY `idx_status` (`status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='员工表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_tenant_positions` +-- + +DROP TABLE IF EXISTS `yz_tenant_positions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_tenant_positions` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '职位ID', + `tenant_id` int(11) NOT NULL DEFAULT '0' COMMENT '租户ID', + `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '职位名称', + `code` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '职位编码', + `department_id` int(11) DEFAULT NULL COMMENT '所属部门ID', + `level` int(11) DEFAULT '0' COMMENT '职位级别', + `description` text COLLATE utf8mb4_unicode_ci COMMENT '职位描述', + `sort_order` int(11) DEFAULT '0' COMMENT '排序序号', + `status` tinyint(4) DEFAULT '1' COMMENT '状态:1-启用,0-禁用', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + KEY `idx_tenant_id` (`tenant_id`), + KEY `idx_code` (`code`), + KEY `idx_department_id` (`department_id`), + KEY `idx_status` (`status`), + KEY `idx_sort_order` (`sort_order`), + KEY `idx_delete_time` (`delete_time`), + KEY `idx_dept_delete_status` (`department_id`,`delete_time`,`status`) +) ENGINE=InnoDB AUTO_INCREMENT=104 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='职位表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_tenant_tasks` +-- + +DROP TABLE IF EXISTS `yz_tenant_tasks`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_tenant_tasks` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '任务ID(主键)', + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID(多租户隔离,如无多租户需求可设为默认1)', + `task_no` varchar(64) NOT NULL COMMENT '任务编号(唯一标识,如TASK20251112001)', + `task_name` varchar(255) NOT NULL COMMENT '任务名称', + `task_desc` text COMMENT '任务描述(富文本内容,支持图片/表格)', + `task_type` varchar(32) DEFAULT 'common' COMMENT '任务类型(common=普通任务,project=项目任务,repeat=重复任务,可自定义)', + `business_tag` varchar(64) DEFAULT NULL COMMENT '业务标签(多个标签用逗号分隔,如"紧急,日常协作")', + `parent_task_id` bigint(20) DEFAULT NULL COMMENT '父任务ID(子任务关联用,无父任务则为NULL)', + `project_id` bigint(20) DEFAULT NULL COMMENT '关联项目ID(关联OA项目模块)', + `related_id` bigint(20) DEFAULT NULL COMMENT '关联其他模块ID(如审批单ID、客户ID)', + `related_type` varchar(32) DEFAULT NULL COMMENT '关联模块类型(approval=审批单,customer=客户,为空则无关联)', + `team_employee_ids` varchar(500) DEFAULT NULL COMMENT '团队成员', + `creator_id` bigint(20) NOT NULL COMMENT '创建人ID', + `creator_name` varchar(64) NOT NULL COMMENT '创建人姓名', + `principal_id` bigint(20) NOT NULL COMMENT '负责人ID', + `principal_name` varchar(64) NOT NULL COMMENT '负责人姓名', + `participant_ids` varchar(512) DEFAULT NULL COMMENT '参与人ID(多个用逗号分隔)', + `participant_names` varchar(512) DEFAULT NULL COMMENT '参与人姓名(多个用逗号分隔)', + `cc_ids` varchar(512) DEFAULT NULL COMMENT '抄送人ID(多个用逗号分隔)', + `cc_names` varchar(512) DEFAULT NULL COMMENT '抄送人姓名(多个用逗号分隔)', + `plan_start_time` datetime DEFAULT NULL COMMENT '计划开始时间', + `plan_end_time` datetime NOT NULL COMMENT '计划截止时间', + `actual_start_time` datetime DEFAULT NULL COMMENT '实际开始时间', + `actual_end_time` datetime DEFAULT NULL COMMENT '实际结束时间', + `estimated_hours` decimal(10,2) DEFAULT NULL COMMENT '预估工时(小时)', + `actual_hours` decimal(10,2) DEFAULT NULL COMMENT '实际工时(小时)', + `task_status` varchar(32) NOT NULL DEFAULT 'not_started' COMMENT '任务状态(0-未开始,1-进行中,2-暂停,3-已完成,4-已关闭)', + `priority` varchar(16) NOT NULL DEFAULT 'medium' COMMENT '优先级(0-高,1-中,2-低,3-紧急)', + `progress` tinyint(4) NOT NULL DEFAULT '0' COMMENT '任务进度(0-100,子任务存在时自动计算)', + `need_approval` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否需要完成审批(0=否,1=是)', + `approval_id` bigint(20) DEFAULT NULL COMMENT '关联审批单ID(完成审批时填写)', + `delay_approved` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否已延期审批(0=否,1=是)', + `old_plan_end_time` datetime DEFAULT NULL COMMENT '原计划截止时间(延期时记录)', + `repeat_type` varchar(16) DEFAULT NULL COMMENT '重复类型(daily=按日,weekly=按周,monthly=按月,为空则非重复任务)', + `repeat_cycle` int(11) DEFAULT NULL COMMENT '重复周期(如每周重复则为7,每月重复则为30)', + `repeat_end_time` datetime DEFAULT NULL COMMENT '重复截止时间(重复任务终止时间)', + `attachment_ids` varchar(1024) DEFAULT NULL COMMENT '附件ID(关联文件表,多个用逗号分隔)', + `remark` varchar(512) DEFAULT NULL COMMENT '备注(额外说明)', + `is_archived` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否归档(0=未归档,1=已归档)', + `archive_time` datetime DEFAULT NULL COMMENT '归档时间', + `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted_time` datetime DEFAULT NULL COMMENT '删除时间', + `operator_id` bigint(20) DEFAULT NULL COMMENT '最后操作人ID', + `operator_name` varchar(64) DEFAULT NULL COMMENT '最后操作人姓名', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tenant_task_no` (`tenant_id`,`task_no`) COMMENT '租户+任务编号唯一索引', + KEY `idx_tenant_principal` (`tenant_id`,`principal_id`) COMMENT '租户+负责人索引(查询个人任务)', + KEY `idx_tenant_status` (`tenant_id`,`task_status`) COMMENT '租户+状态索引(筛选任务状态)', + KEY `idx_tenant_project` (`tenant_id`,`project_id`) COMMENT '租户+项目索引(查询项目下任务)', + KEY `idx_tenant_plan_end_time` (`tenant_id`,`plan_end_time`) COMMENT '租户+截止时间索引(逾期提醒、日历视图)', + KEY `idx_parent_task_id` (`parent_task_id`) COMMENT '父任务ID索引(查询子任务)' +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='OA系统任务表(多租户适配)'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_tenants` +-- + +DROP TABLE IF EXISTS `yz_tenants`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_tenants` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '租户ID', + `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户名称', + `code` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '租户编码(唯一)', + `owner` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '负责人', + `phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话', + `email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱地址', + `status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT '1' COMMENT '状态:1-启用,0-禁用', + `audit_status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'pending' COMMENT '审核状态:pending-待审核,approved-已通过,rejected-已拒绝', + `audit_comment` text COLLATE utf8mb4_unicode_ci COMMENT '审核意见', + `audit_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核人', + `audit_time` datetime DEFAULT NULL COMMENT '审核时间', + `capacity` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '分配空间容量(MB)', + `capacity_used` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '0' COMMENT '已用空间容量(MB)', + `attachment_url` longtext COLLATE utf8mb4_unicode_ci COMMENT '附件', + `remark` text COLLATE utf8mb4_unicode_ci COMMENT '备注', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `create_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人', + `update_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`code`), + KEY `idx_name` (`name`), + KEY `idx_owner` (`owner`), + KEY `idx_status` (`status`), + KEY `idx_audit_status` (`audit_status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='租户表'; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `yz_users` +-- + +DROP TABLE IF EXISTS `yz_users`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `yz_users` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `tenant_id` int(11) DEFAULT NULL COMMENT '租户ID', + `username` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名', + `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '加密后的密码', + `salt` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码盐值', + `email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱地址', + `avatar` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像URL', + `nickname` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称', + `role` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'user' COMMENT '用户角色', + `department_id` int(11) DEFAULT NULL COMMENT '部门ID', + `position_id` int(11) DEFAULT NULL COMMENT '职位ID', + `status` tinyint(4) DEFAULT '1' COMMENT '用户状态:0-禁用,1-启用', + `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', + `last_login_ip` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '最后登录IP', + `login_count` int(11) DEFAULT '0' COMMENT '登录次数', + `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `create_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人', + `update_by` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`), + KEY `idx_email` (`email`), + KEY `idx_role` (`role`), + KEY `idx_status` (`status`), + KEY `idx_create_time` (`create_time`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表'; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +CREATE TABLE `yz_exam_question` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '试题唯一标识', + `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', + `question_type` tinyint(1) NOT NULL DEFAULT 1 COMMENT '题型(1-单选,2-多选,3-判断,4-填空,5-简答)', + `question_title` varchar(1000) NOT NULL COMMENT '题干内容', + `question_analysis` varchar(2000) DEFAULT '' COMMENT '试题解析', + `score` decimal(5,2) NOT NULL DEFAULT 0.00 COMMENT '试题分值', + `sort_order` tinyint(4) DEFAULT 0 COMMENT '排序序号', + `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '状态(1-启用,0-禁用)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + -- 核心联合索引:租户+状态+删除时间(查询可用试题) + KEY `idx_tenant_status_delete` (`tenant_id`, `status`, `delete_time`), + -- 联合索引:租户+题型+删除时间(筛选特定题型试题) + KEY `idx_tenant_type_delete` (`tenant_id`, `question_type`, `delete_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='试题主表(通用型+多租户隔离+软删除)'; + +CREATE TABLE `yz_exam_question_option` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '选项唯一标识', + `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', + `question_id` bigint(20) NOT NULL COMMENT '关联试题主表ID', + `option_label` varchar(10) NOT NULL COMMENT '选项标签(A/B/C/D/对/错)', + `option_content` varchar(500) NOT NULL COMMENT '选项内容', + `sort_order` tinyint(4) DEFAULT 0 COMMENT '排序序号', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + -- 核心联合索引:租户+试题ID+删除时间(查询某试题所有选项) + KEY `idx_tenant_question_delete` (`tenant_id`, `question_id`, `delete_time`), + -- 联合索引:租户+试题ID+选项标签+删除时间(快速定位选项) + KEY `idx_tenant_question_label_delete` (`tenant_id`, `question_id`, `option_label`, `delete_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='试题选项表(客观题专用+多租户隔离+软删除)'; + +CREATE TABLE `yz_exam_question_answer` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '答案唯一标识', + `tenant_id` int(11) NOT NULL COMMENT '租户ID(多租户隔离)', + `question_id` bigint(20) NOT NULL COMMENT '关联试题主表ID', + `answer_content` varchar(1000) NOT NULL COMMENT '正确答案(客观题存标签,主观题存文字)', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间(软删除)', + PRIMARY KEY (`id`), + -- 核心联合索引:租户+试题ID+删除时间(查询某试题正确答案) + KEY `idx_tenant_question_delete` (`tenant_id`, `question_id`, `delete_time`), + -- 唯一索引:同一租户下同一试题只能有一个正确答案 + UNIQUE KEY `uk_tenant_question` (`tenant_id`, `question_id`, `delete_time`) COMMENT '软删除状态下试题答案唯一' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='试题答案表(通用答案存储+多租户隔离+软删除)'; \ No newline at end of file diff --git a/go/docs/README.md b/go/docs/README.md index 4ae162a..1976249 100644 --- a/go/docs/README.md +++ b/go/docs/README.md @@ -1,70 +1,70 @@ -# Go后端项目文档 - -## 📚 文档目录 - -### 开发文档 -- [后端开发规则](./后端开发规则.md) -- [接口文件](./接口文件.md) -- [服务端启动命令](./服务端启动命令.md) -- [大文件上传配置](./大文件上传配置.md) - 文件上传限制和超时配置 - -### 存储配置功能文档 -- [📖 快速开始](./QUICK_START.md) - 5分钟快速上手 -- [📘 完整实现说明](./README_STORAGE.md) - 功能概述和使用指南 -- [📗 详细使用指南](./storage-config-guide.md) - 深入的配置和使用说明 -- [✅ 部署检查清单](./DEPLOYMENT_CHECKLIST.md) - 生产环境部署指南 -- [🎉 实现报告](./IMPLEMENTATION_COMPLETE.md) - 完整的实现细节 - -### 数据库文档 -- [SQL迁移脚本](./sql/) - 数据库迁移文件 - -## 🚀 快速导航 - -### 新手入门 -1. 阅读 [快速开始](./QUICK_START.md) -2. 查看 [服务端启动命令](./服务端启动命令.md) -3. 了解 [后端开发规则](./后端开发规则.md) - -### 存储功能使用 -1. [快速开始](./QUICK_START.md) - 快速配置存储 -2. [完整实现说明](./README_STORAGE.md) - 了解核心功能 -3. [详细使用指南](./storage-config-guide.md) - 深入学习 - -### 生产部署 -1. [部署检查清单](./DEPLOYMENT_CHECKLIST.md) - 按清单逐项检查 -2. [实现报告](./IMPLEMENTATION_COMPLETE.md) - 了解技术细节 - -## 📂 项目结构 - -``` -go/ -├── controllers/ # 控制器层 -├── models/ # 数据模型层 -├── services/ # 业务服务层 -├── routers/ # 路由配置 -├── pkg/ # 公共包 -├── conf/ # 配置文件 -├── migrations/ # 数据库迁移 -├── scripts/ # 脚本工具 -└── docs/ # 文档(本目录) -``` - -## 🔗 相关链接 - -- [Beego框架文档](https://beego.vip/) -- [七牛云开发文档](https://developer.qiniu.com/) -- [Go语言官方文档](https://golang.org/doc/) - -## 📝 更新日志 - -### 2026-04-09 -- ✅ 增加大文件上传支持(最大 2GB) -- ✅ 移除服务器超时限制 -- ✅ 优化 CORS 配置 -- ✅ 完善文件上传文档 - -### 2024-01-01 -- ✅ 完成存储配置功能 -- ✅ 支持本地存储和七牛云存储 -- ✅ 实现文件迁移功能 -- ✅ 完善文档体系 +# Go后端项目文档 + +## 📚 文档目录 + +### 开发文档 +- [后端开发规则](./后端开发规则.md) +- [接口文件](./接口文件.md) +- [服务端启动命令](./服务端启动命令.md) +- [大文件上传配置](./大文件上传配置.md) - 文件上传限制和超时配置 + +### 存储配置功能文档 +- [📖 快速开始](./QUICK_START.md) - 5分钟快速上手 +- [📘 完整实现说明](./README_STORAGE.md) - 功能概述和使用指南 +- [📗 详细使用指南](./storage-config-guide.md) - 深入的配置和使用说明 +- [✅ 部署检查清单](./DEPLOYMENT_CHECKLIST.md) - 生产环境部署指南 +- [🎉 实现报告](./IMPLEMENTATION_COMPLETE.md) - 完整的实现细节 + +### 数据库文档 +- [SQL迁移脚本](./sql/) - 数据库迁移文件 + +## 🚀 快速导航 + +### 新手入门 +1. 阅读 [快速开始](./QUICK_START.md) +2. 查看 [服务端启动命令](./服务端启动命令.md) +3. 了解 [后端开发规则](./后端开发规则.md) + +### 存储功能使用 +1. [快速开始](./QUICK_START.md) - 快速配置存储 +2. [完整实现说明](./README_STORAGE.md) - 了解核心功能 +3. [详细使用指南](./storage-config-guide.md) - 深入学习 + +### 生产部署 +1. [部署检查清单](./DEPLOYMENT_CHECKLIST.md) - 按清单逐项检查 +2. [实现报告](./IMPLEMENTATION_COMPLETE.md) - 了解技术细节 + +## 📂 项目结构 + +``` +go/ +├── controllers/ # 控制器层 +├── models/ # 数据模型层 +├── services/ # 业务服务层 +├── routers/ # 路由配置 +├── pkg/ # 公共包 +├── conf/ # 配置文件 +├── migrations/ # 数据库迁移 +├── scripts/ # 脚本工具 +└── docs/ # 文档(本目录) +``` + +## 🔗 相关链接 + +- [Beego框架文档](https://beego.vip/) +- [七牛云开发文档](https://developer.qiniu.com/) +- [Go语言官方文档](https://golang.org/doc/) + +## 📝 更新日志 + +### 2026-04-09 +- ✅ 增加大文件上传支持(最大 2GB) +- ✅ 移除服务器超时限制 +- ✅ 优化 CORS 配置 +- ✅ 完善文件上传文档 + +### 2024-01-01 +- ✅ 完成存储配置功能 +- ✅ 支持本地存储和七牛云存储 +- ✅ 实现文件迁移功能 +- ✅ 完善文档体系 diff --git a/go/docs/sql/add_storage_config_table.sql b/go/docs/sql/add_storage_config_table.sql index 9fcafb2..388ab82 100644 --- a/go/docs/sql/add_storage_config_table.sql +++ b/go/docs/sql/add_storage_config_table.sql @@ -1,18 +1,18 @@ --- 创建存储配置表 -CREATE TABLE IF NOT EXISTS `yz_system_storage_config` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `storage_type` varchar(20) NOT NULL DEFAULT 'local' COMMENT '存储类型: local-本地存储, qiniu-七牛云', - `qiniu_access_key` varchar(255) DEFAULT NULL COMMENT '七牛云AccessKey', - `qiniu_secret_key` varchar(255) DEFAULT NULL COMMENT '七牛云SecretKey', - `qiniu_bucket` varchar(128) DEFAULT NULL COMMENT '七牛云Bucket名称', - `qiniu_domain` varchar(255) DEFAULT NULL COMMENT '七牛云CDN域名', - `qiniu_region` varchar(50) DEFAULT NULL COMMENT '七牛云存储区域', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统存储配置表'; - --- 插入默认配置(本地存储) -INSERT INTO `yz_system_storage_config` (`storage_type`, `create_time`) -VALUES ('local', NOW()) -ON DUPLICATE KEY UPDATE `storage_type` = 'local'; +-- 创建存储配置表 +CREATE TABLE IF NOT EXISTS `yz_system_storage_config` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `storage_type` varchar(20) NOT NULL DEFAULT 'local' COMMENT '存储类型: local-本地存储, qiniu-七牛云', + `qiniu_access_key` varchar(255) DEFAULT NULL COMMENT '七牛云AccessKey', + `qiniu_secret_key` varchar(255) DEFAULT NULL COMMENT '七牛云SecretKey', + `qiniu_bucket` varchar(128) DEFAULT NULL COMMENT '七牛云Bucket名称', + `qiniu_domain` varchar(255) DEFAULT NULL COMMENT '七牛云CDN域名', + `qiniu_region` varchar(50) DEFAULT NULL COMMENT '七牛云存储区域', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统存储配置表'; + +-- 插入默认配置(本地存储) +INSERT INTO `yz_system_storage_config` (`storage_type`, `create_time`) +VALUES ('local', NOW()) +ON DUPLICATE KEY UPDATE `storage_type` = 'local'; diff --git a/go/docs/sql/yz_complaint.sql b/go/docs/sql/yz_complaint.sql index c899c85..c05f933 100644 --- a/go/docs/sql/yz_complaint.sql +++ b/go/docs/sql/yz_complaint.sql @@ -1,45 +1,45 @@ --- 投诉建议「产品分类」:区分用户针对哪类产品提建议 --- 请在目标库手动执行(utf8mb4) - -CREATE TABLE IF NOT EXISTS `yz_system_complaint_category` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(64) NOT NULL COMMENT '分类名称,如:官网、租户后台、小程序', - `code` varchar(32) DEFAULT NULL COMMENT '可选编码,便于程序识别', - `sort` int NOT NULL DEFAULT 0 COMMENT '排序,越小越靠前', - `status` tinyint NOT NULL DEFAULT 1 COMMENT '1启用 0禁用', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `delete_time` datetime DEFAULT NULL COMMENT '软删', - PRIMARY KEY (`id`), - KEY `idx_delete_time` (`delete_time`), - KEY `idx_status` (`status`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='投诉建议-产品分类'; - -CREATE TABLE IF NOT EXISTS `yz_system_platform_complaint` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT, - `category_id` bigint unsigned NOT NULL COMMENT '产品分类ID', - `title` varchar(200) NOT NULL COMMENT '标题', - `content` text NOT NULL COMMENT '建议/投诉内容', - `contact_name` varchar(64) DEFAULT NULL COMMENT '联系人', - `contact_phone` varchar(32) DEFAULT NULL COMMENT '联系电话', - `contact_email` varchar(128) DEFAULT NULL COMMENT '联系邮箱', - `status` tinyint NOT NULL DEFAULT 0 COMMENT '0待处理 1处理中 2已回复 3已关闭', - `reply_content` text COMMENT '平台回复内容', - `reply_time` datetime DEFAULT NULL COMMENT '回复时间', - `tid` bigint unsigned DEFAULT NULL COMMENT '可选:关联租户ID', - `remark` varchar(512) DEFAULT NULL COMMENT '管理员内部备注', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `delete_time` datetime DEFAULT NULL COMMENT '软删', - PRIMARY KEY (`id`), - KEY `idx_category_id` (`category_id`), - KEY `idx_status` (`status`), - KEY `idx_delete_time` (`delete_time`), - KEY `idx_tid` (`tid`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='平台端-投诉建议'; - --- 可选:示例分类(执行完建表后按需取消注释) --- INSERT INTO `yz_system_complaint_category` (`name`,`code`,`sort`,`status`) VALUES --- ('官网','site',0,1), --- ('租户后台','tenant_admin',10,1), --- ('小程序','miniapp',20,1); +-- 投诉建议「产品分类」:区分用户针对哪类产品提建议 +-- 请在目标库手动执行(utf8mb4) + +CREATE TABLE IF NOT EXISTS `yz_system_complaint_category` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(64) NOT NULL COMMENT '分类名称,如:官网、租户后台、小程序', + `code` varchar(32) DEFAULT NULL COMMENT '可选编码,便于程序识别', + `sort` int NOT NULL DEFAULT 0 COMMENT '排序,越小越靠前', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '1启用 0禁用', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '软删', + PRIMARY KEY (`id`), + KEY `idx_delete_time` (`delete_time`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='投诉建议-产品分类'; + +CREATE TABLE IF NOT EXISTS `yz_system_platform_complaint` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `category_id` bigint unsigned NOT NULL COMMENT '产品分类ID', + `title` varchar(200) NOT NULL COMMENT '标题', + `content` text NOT NULL COMMENT '建议/投诉内容', + `contact_name` varchar(64) DEFAULT NULL COMMENT '联系人', + `contact_phone` varchar(32) DEFAULT NULL COMMENT '联系电话', + `contact_email` varchar(128) DEFAULT NULL COMMENT '联系邮箱', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '0待处理 1处理中 2已回复 3已关闭', + `reply_content` text COMMENT '平台回复内容', + `reply_time` datetime DEFAULT NULL COMMENT '回复时间', + `tid` bigint unsigned DEFAULT NULL COMMENT '可选:关联租户ID', + `remark` varchar(512) DEFAULT NULL COMMENT '管理员内部备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL COMMENT '软删', + PRIMARY KEY (`id`), + KEY `idx_category_id` (`category_id`), + KEY `idx_status` (`status`), + KEY `idx_delete_time` (`delete_time`), + KEY `idx_tid` (`tid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='平台端-投诉建议'; + +-- 可选:示例分类(执行完建表后按需取消注释) +-- INSERT INTO `yz_system_complaint_category` (`name`,`code`,`sort`,`status`) VALUES +-- ('官网','site',0,1), +-- ('租户后台','tenant_admin',10,1), +-- ('小程序','miniapp',20,1); diff --git a/go/docs/sql/yz_platform_cursor_activation_code.sql b/go/docs/sql/yz_platform_cursor_activation_code.sql index d751da2..e411140 100644 --- a/go/docs/sql/yz_platform_cursor_activation_code.sql +++ b/go/docs/sql/yz_platform_cursor_activation_code.sql @@ -1,31 +1,31 @@ --- Cursor 激活码管理 --- status: 0 未使用 1 已使用 2 已过期 3 已禁用 --- type: 0 自定义 1 天卡 7 周卡 30 月卡 90 季卡 365 年卡 - -CREATE TABLE IF NOT EXISTS `yz_platform_cursor_activation_code` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `code` varchar(128) NOT NULL COMMENT '激活码', - `type` int NOT NULL DEFAULT 30 COMMENT '卡密类型:0自定义 1天卡 7周卡 30月卡 90季卡 365年卡', - `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0未使用 1已使用 2已过期 3已禁用', - `duration_days` int NOT NULL DEFAULT 30 COMMENT '有效天数', - `bind_account` varchar(128) DEFAULT NULL COMMENT '绑定账号', - `bind_device_id` bigint unsigned DEFAULT NULL COMMENT '绑定设备ID,关联 yz_platform_cursor_equipment.id', - `machine_code` varchar(128) DEFAULT NULL COMMENT '绑定设备机器码', - `device_info` varchar(1000) DEFAULT NULL COMMENT '绑定设备信息', - `owner_user_id` bigint unsigned DEFAULT NULL COMMENT '归属用户ID', - `owner_user_name` varchar(128) DEFAULT NULL COMMENT '归属用户名称', - `activated_at` datetime DEFAULT NULL COMMENT '激活时间', - `expired_at` datetime DEFAULT NULL COMMENT '过期时间', - `remark` varchar(1000) DEFAULT NULL COMMENT '备注', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_code` (`code`), - KEY `idx_status_delete` (`status`,`delete_time`), - KEY `idx_type_status` (`type`,`status`), - KEY `idx_bind_account` (`bind_account`), - KEY `idx_bind_device_id` (`bind_device_id`), - KEY `idx_owner_user_id` (`owner_user_id`), - KEY `idx_expired_at` (`expired_at`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Cursor续杯激活码'; +-- Cursor 激活码管理 +-- status: 0 未使用 1 已使用 2 已过期 3 已禁用 +-- type: 0 自定义 1 天卡 7 周卡 30 月卡 90 季卡 365 年卡 + +CREATE TABLE IF NOT EXISTS `yz_platform_cursor_activation_code` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `code` varchar(128) NOT NULL COMMENT '激活码', + `type` int NOT NULL DEFAULT 30 COMMENT '卡密类型:0自定义 1天卡 7周卡 30月卡 90季卡 365年卡', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0未使用 1已使用 2已过期 3已禁用', + `duration_days` int NOT NULL DEFAULT 30 COMMENT '有效天数', + `bind_account` varchar(128) DEFAULT NULL COMMENT '绑定账号', + `bind_device_id` bigint unsigned DEFAULT NULL COMMENT '绑定设备ID,关联 yz_platform_cursor_equipment.id', + `machine_code` varchar(128) DEFAULT NULL COMMENT '绑定设备机器码', + `device_info` varchar(1000) DEFAULT NULL COMMENT '绑定设备信息', + `owner_user_id` bigint unsigned DEFAULT NULL COMMENT '归属用户ID', + `owner_user_name` varchar(128) DEFAULT NULL COMMENT '归属用户名称', + `activated_at` datetime DEFAULT NULL COMMENT '激活时间', + `expired_at` datetime DEFAULT NULL COMMENT '过期时间', + `remark` varchar(1000) DEFAULT NULL COMMENT '备注', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`code`), + KEY `idx_status_delete` (`status`,`delete_time`), + KEY `idx_type_status` (`type`,`status`), + KEY `idx_bind_account` (`bind_account`), + KEY `idx_bind_device_id` (`bind_device_id`), + KEY `idx_owner_user_id` (`owner_user_id`), + KEY `idx_expired_at` (`expired_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Cursor续杯激活码'; diff --git a/go/docs/sql/yz_software_upgrade.sql b/go/docs/sql/yz_software_upgrade.sql index 2a754f7..17892a6 100644 --- a/go/docs/sql/yz_software_upgrade.sql +++ b/go/docs/sql/yz_software_upgrade.sql @@ -1,21 +1,26 @@ --- 软件升级产品(客户端拉取版本与下载地址) --- 安装包建议上传到文件管理,分类使用「appsupgrade」(或任意分类,记录 file_id 即可) - -CREATE TABLE IF NOT EXISTS `yz_system_software_upgrade` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(128) NOT NULL COMMENT '软件显示名称', - `code` varchar(64) NOT NULL COMMENT '客户端唯一标识,与 check 接口 code 一致', - `latest_version` varchar(32) NOT NULL DEFAULT '0.0.0' COMMENT '当前发布的最新版本号', - `file_id` bigint unsigned DEFAULT NULL COMMENT '关联 yz_system_files.id,安装包', - `download_url` varchar(512) DEFAULT NULL COMMENT '完整下载地址;为空则用 file_id 对应 src 拼公开 URL', - `force_update` tinyint NOT NULL DEFAULT 0 COMMENT '1 建议强制更新', - `release_notes` varchar(2000) DEFAULT NULL COMMENT '更新说明', - `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 启用 0 停用', - `sort` int NOT NULL DEFAULT 0, - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, - `delete_time` datetime DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `uk_code` (`code`), - KEY `idx_status_delete` (`status`,`delete_time`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='软件升级产品'; +-- 软件升级产品(客户端拉取版本与下载地址) +-- 安装包建议上传到文件管理,分类使用「appsupgrade」(或任意分类,记录 file_id 即可) + +CREATE TABLE IF NOT EXISTS `yz_system_software_upgrade` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(128) NOT NULL COMMENT '软件显示名称', + `code` varchar(64) NOT NULL COMMENT '客户端唯一标识,与 check 接口 code 一致', + `latest_version` varchar(32) NOT NULL DEFAULT '0.0.0' COMMENT '当前发布的最新版本号', + `file_id` bigint unsigned DEFAULT NULL COMMENT '关联 yz_system_files.id,安装包', + `download_url` varchar(512) DEFAULT NULL COMMENT '兼容旧客户端的单安装包地址;为空则用 file_id 对应 src 拼公开 URL', + `download_urls` text DEFAULT NULL COMMENT '多运行环境安装包地址 JSON,如 {"windows":"...","mac":"...","ubuntu":"...","linux":"..."}', + `force_update` tinyint NOT NULL DEFAULT 0 COMMENT '1 建议强制更新', + `release_notes` varchar(2000) DEFAULT NULL COMMENT '更新说明', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '1 启用 0 停用', + `sort` int NOT NULL DEFAULT 0, + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + `delete_time` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_code` (`code`), + KEY `idx_status_delete` (`status`,`delete_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='软件升级产品'; + +-- 已有表升级: +-- ALTER TABLE `yz_system_software_upgrade` +-- ADD COLUMN `download_urls` text DEFAULT NULL COMMENT '多运行环境安装包地址 JSON,如 {"windows":"...","mac":"...","ubuntu":"...","linux":"..."}' AFTER `download_url`; diff --git a/go/docs/后端开发规则.md b/go/docs/后端开发规则.md index 8c8bdfe..9f54ce1 100644 --- a/go/docs/后端开发规则.md +++ b/go/docs/后端开发规则.md @@ -1,43 +1,43 @@ -go/ -├── models/ # 仅负责数据模型相关 -│ ├── 结构体(struct)定义 -│ ├── 字段标签与表名(TableName) -│ └── 数据库初始化(注册模型、连接数据库) -│ -├── services/ # 核心业务逻辑层 -│ ├── 所有业务处理方法(含CRUD) -│ ├── 模型数据校验 -│ ├── 密码等安全相关加解密 -│ └── 与 models 层的数据库操作 -│ -└── controllers/ # 控制器层,专注 HTTP - ├── 请求参数解析 - ├── 参数有效性验证 - ├── 调用 services 处理业务 - └── 响应数据统一格式化与错误处理 - - -## 分层架构开发规范 - -### Models 层 -- 只负责定义数据库结构和初始化,包含结构体、字段标签与表名映射,数据库注册与连接。 -- 不允许包含任何业务逻辑、数据校验、密码处理或和 HTTP 相关的代码。 - -### Services 层 -- 实现所有业务流程、数据访问、校验和跨模型业务逻辑。 -- 通过 models 操作数据库,仅返回 struct 或错误。 -- 实现数据校验、密码加密等业务需求;不直接处理 HTTP 请求或响应。 - -### Controllers 层 -- 只负责接收和解析 HTTP 请求,进行参数校验。 -- 调用 services 执行业务逻辑。 -- 负责返回统一格式的响应结果,对业务错误进行捕获和转义为 HTTP 状态码和消息。 - -### 其它要求 -- 各层代码职责单一,禁止跨层调用(如 controllers 直接操作 models)。 -- 统一异常处理,业务错误只在 services 返回,controllers 负责转换为 HTTP 响应。 -- 保持 controller 轻量简洁,绝不包含业务处理逻辑。 -- services 层所有数据变更、校验等均可单元测试。 -- models 变动需清晰文档和数据库迁移脚本。 - +go/ +├── models/ # 仅负责数据模型相关 +│ ├── 结构体(struct)定义 +│ ├── 字段标签与表名(TableName) +│ └── 数据库初始化(注册模型、连接数据库) +│ +├── services/ # 核心业务逻辑层 +│ ├── 所有业务处理方法(含CRUD) +│ ├── 模型数据校验 +│ ├── 密码等安全相关加解密 +│ └── 与 models 层的数据库操作 +│ +└── controllers/ # 控制器层,专注 HTTP + ├── 请求参数解析 + ├── 参数有效性验证 + ├── 调用 services 处理业务 + └── 响应数据统一格式化与错误处理 + + +## 分层架构开发规范 + +### Models 层 +- 只负责定义数据库结构和初始化,包含结构体、字段标签与表名映射,数据库注册与连接。 +- 不允许包含任何业务逻辑、数据校验、密码处理或和 HTTP 相关的代码。 + +### Services 层 +- 实现所有业务流程、数据访问、校验和跨模型业务逻辑。 +- 通过 models 操作数据库,仅返回 struct 或错误。 +- 实现数据校验、密码加密等业务需求;不直接处理 HTTP 请求或响应。 + +### Controllers 层 +- 只负责接收和解析 HTTP 请求,进行参数校验。 +- 调用 services 执行业务逻辑。 +- 负责返回统一格式的响应结果,对业务错误进行捕获和转义为 HTTP 状态码和消息。 + +### 其它要求 +- 各层代码职责单一,禁止跨层调用(如 controllers 直接操作 models)。 +- 统一异常处理,业务错误只在 services 返回,controllers 负责转换为 HTTP 响应。 +- 保持 controller 轻量简洁,绝不包含业务处理逻辑。 +- services 层所有数据变更、校验等均可单元测试。 +- models 变动需清晰文档和数据库迁移脚本。 + 建议先设计 models 层,随后 services 层,最后实现 controllers,实现过程中注意分层原则。 \ No newline at end of file diff --git a/go/docs/大文件上传配置.md b/go/docs/大文件上传配置.md index 8330ae7..67abccb 100644 --- a/go/docs/大文件上传配置.md +++ b/go/docs/大文件上传配置.md @@ -1,243 +1,243 @@ -# 大文件上传配置说明 - -## 概述 - -为支持大型软件安装包(如桌面客户端安装程序)的上传,系统已调整文件上传限制和超时配置。 - -## 配置修改 - -### 1. 文件大小限制 - -**文件位置**: `go/controllers/platform_file.go` - -**修改内容**: -```go -// 修改前 -const fileUploadMaxMB = 200 // 200MB - -// 修改后 -const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 -``` - -### 2. 服务器超时配置 - -**文件位置**: `go/conf/app.conf` - -**新增配置**: -```ini -# 服务器超时配置(支持大文件上传) -# 0 表示不设置超时限制 -ServerTimeOut = 0 -# 最大请求体大小(字节),0 表示不限制 -MaxMemory = 0 -``` - -## CORS 配置 - -**文件位置**: `go/routers/router.go` - -当前 CORS 配置允许跨域请求: - -```go -beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) { - ctx.Output.Header("Access-Control-Allow-Origin", "*") - ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") - ctx.Output.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") - ctx.Output.Header("Access-Control-Max-Age", "86400") - - if ctx.Input.Method() == "OPTIONS" { - ctx.Output.Status = 200 - ctx.Output.Body([]byte("")) - return - } -}) -``` - -### 生产环境 CORS 配置建议 - -在生产环境中,建议将 `Access-Control-Allow-Origin` 设置为具体的前端域名: - -```go -// 开发环境 -ctx.Output.Header("Access-Control-Allow-Origin", "*") - -// 生产环境(推荐) -allowedOrigins := []string{ - "https://platform.yunzer.cn", - "https://www.yunzer.cn", -} -origin := ctx.Request.Header.Get("Origin") -for _, allowed := range allowedOrigins { - if origin == allowed { - ctx.Output.Header("Access-Control-Allow-Origin", origin) - break - } -} -``` - -## 上传流程 - -### 1. 文件上传接口 - -**路由**: `POST /platform/uploadfile` - -**控制器**: `PlatformFileController.UploadFile` - -**处理流程**: -1. 验证用户身份(JWT token) -2. 解析 multipart form(最大 2GB) -3. 检查文件大小(不超过 2GB) -4. 获取存储服务(本地或七牛云) -5. 上传文件到存储服务 -6. 检查文件 MD5 是否已存在 -7. 保存文件记录到数据库 -8. 返回文件信息(URL、ID、名称) - -### 2. 存储服务 - -系统支持两种存储方式: - -- **本地存储**: 文件保存在 `uploads/` 目录 -- **七牛云存储**: 文件上传到七牛云 OSS - -存储方式通过 `yz_system_storage_config` 表配置。 - -## 性能优化建议 - -### 1. Nginx 反向代理配置 - -如果使用 Nginx 作为反向代理,需要调整以下配置: - -```nginx -server { - listen 80; - server_name api.yunzer.cn; - - # 客户端请求体大小限制(0 表示不限制) - client_max_body_size 0; - - # 客户端请求体缓冲区大小 - client_body_buffer_size 128k; - - # 超时配置 - client_body_timeout 3600s; - send_timeout 3600s; - proxy_connect_timeout 3600s; - proxy_send_timeout 3600s; - proxy_read_timeout 3600s; - - location / { - proxy_pass http://localhost:8081; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # 禁用请求体缓冲(直接流式传输) - proxy_request_buffering off; - } -} -``` - -### 2. 磁盘空间监控 - -大文件上传需要足够的磁盘空间: - -```bash -# 检查磁盘空间 -df -h - -# 监控 uploads 目录大小 -du -sh uploads/ - -# 设置磁盘空间告警(推荐使用监控工具) -``` - -### 3. 数据库优化 - -对于频繁的文件查询,建议添加索引: - -```sql --- MD5 索引(用于去重) -CREATE INDEX idx_system_file_md5 ON yz_system_file(md5); - --- 租户 + 删除时间索引(用于文件列表查询) -CREATE INDEX idx_system_file_tid_delete ON yz_system_file(tid, delete_time); -``` - -## 故障排查 - -### 1. 上传失败:文件过大 - -**错误信息**: "文件大小不能超过 2048MB" - -**解决方案**: -- 检查 `fileUploadMaxMB` 常量设置 -- 确认 Nginx `client_max_body_size` 配置 -- 检查磁盘剩余空间 - -### 2. 上传超时 - -**错误信息**: "请求失败,请检查网络连接" - -**解决方案**: -- 检查 `app.conf` 中的 `ServerTimeOut` 配置 -- 检查 Nginx 超时配置 -- 检查网络带宽和稳定性 - -### 3. CORS 错误 - -**错误信息**: "已拦截跨源请求:同源策略禁止读取..." - -**解决方案**: -- 检查 `go/routers/router.go` 中的 CORS 配置 -- 确认 `Access-Control-Allow-Origin` 包含前端域名 -- 检查 `Access-Control-Allow-Headers` 包含 `Authorization` - -### 4. 文件不存在(404) - -**错误信息**: "请求的资源不存在" - -**可能原因**: -- 文件记录在数据库中不存在 -- 租户 ID (tid) 不匹配 -- 文件已被标记为删除 - -**解决方案**: -```sql --- 检查文件记录 -SELECT * FROM yz_system_file WHERE id = 320; - --- 检查是否被删除 -SELECT * FROM yz_system_file WHERE id = 320 AND delete_time IS NULL; -``` - -## 监控指标 - -建议监控以下指标: - -1. **上传成功率**: 成功上传数 / 总上传请求数 -2. **平均上传时间**: 按文件大小分段统计 -3. **磁盘使用率**: uploads 目录大小 / 总磁盘空间 -4. **错误率**: 按错误类型分类统计 - -## 相关文件 - -- `go/controllers/platform_file.go` - 文件上传控制器 -- `go/services/storage_service.go` - 存储服务接口 -- `go/conf/app.conf` - 服务器配置 -- `go/routers/router.go` - 路由和 CORS 配置 -- `go/models/system_file.go` - 文件数据模型 - -## 更新日志 - -- **2026-04-09**: - - 文件大小限制从 200MB 提升到 2GB - - 移除服务器超时限制 - - 更新文档 - -## 参考资料 - -- [Beego 文档 - 文件上传](https://beego.vip/docs/mvc/controller/file.md) -- [Nginx 文件上传配置](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size) -- [七牛云 Go SDK](https://developer.qiniu.com/kodo/1238/go) +# 大文件上传配置说明 + +## 概述 + +为支持大型软件安装包(如桌面客户端安装程序)的上传,系统已调整文件上传限制和超时配置。 + +## 配置修改 + +### 1. 文件大小限制 + +**文件位置**: `go/controllers/platform_file.go` + +**修改内容**: +```go +// 修改前 +const fileUploadMaxMB = 200 // 200MB + +// 修改后 +const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 +``` + +### 2. 服务器超时配置 + +**文件位置**: `go/conf/app.conf` + +**新增配置**: +```ini +# 服务器超时配置(支持大文件上传) +# 0 表示不设置超时限制 +ServerTimeOut = 0 +# 最大请求体大小(字节),0 表示不限制 +MaxMemory = 0 +``` + +## CORS 配置 + +**文件位置**: `go/routers/router.go` + +当前 CORS 配置允许跨域请求: + +```go +beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) { + ctx.Output.Header("Access-Control-Allow-Origin", "*") + ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") + ctx.Output.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") + ctx.Output.Header("Access-Control-Max-Age", "86400") + + if ctx.Input.Method() == "OPTIONS" { + ctx.Output.Status = 200 + ctx.Output.Body([]byte("")) + return + } +}) +``` + +### 生产环境 CORS 配置建议 + +在生产环境中,建议将 `Access-Control-Allow-Origin` 设置为具体的前端域名: + +```go +// 开发环境 +ctx.Output.Header("Access-Control-Allow-Origin", "*") + +// 生产环境(推荐) +allowedOrigins := []string{ + "https://platform.yunzer.cn", + "https://www.yunzer.cn", +} +origin := ctx.Request.Header.Get("Origin") +for _, allowed := range allowedOrigins { + if origin == allowed { + ctx.Output.Header("Access-Control-Allow-Origin", origin) + break + } +} +``` + +## 上传流程 + +### 1. 文件上传接口 + +**路由**: `POST /platform/uploadfile` + +**控制器**: `PlatformFileController.UploadFile` + +**处理流程**: +1. 验证用户身份(JWT token) +2. 解析 multipart form(最大 2GB) +3. 检查文件大小(不超过 2GB) +4. 获取存储服务(本地或七牛云) +5. 上传文件到存储服务 +6. 检查文件 MD5 是否已存在 +7. 保存文件记录到数据库 +8. 返回文件信息(URL、ID、名称) + +### 2. 存储服务 + +系统支持两种存储方式: + +- **本地存储**: 文件保存在 `uploads/` 目录 +- **七牛云存储**: 文件上传到七牛云 OSS + +存储方式通过 `yz_system_storage_config` 表配置。 + +## 性能优化建议 + +### 1. Nginx 反向代理配置 + +如果使用 Nginx 作为反向代理,需要调整以下配置: + +```nginx +server { + listen 80; + server_name api.yunzer.cn; + + # 客户端请求体大小限制(0 表示不限制) + client_max_body_size 0; + + # 客户端请求体缓冲区大小 + client_body_buffer_size 128k; + + # 超时配置 + client_body_timeout 3600s; + send_timeout 3600s; + proxy_connect_timeout 3600s; + proxy_send_timeout 3600s; + proxy_read_timeout 3600s; + + location / { + proxy_pass http://localhost:8081; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 禁用请求体缓冲(直接流式传输) + proxy_request_buffering off; + } +} +``` + +### 2. 磁盘空间监控 + +大文件上传需要足够的磁盘空间: + +```bash +# 检查磁盘空间 +df -h + +# 监控 uploads 目录大小 +du -sh uploads/ + +# 设置磁盘空间告警(推荐使用监控工具) +``` + +### 3. 数据库优化 + +对于频繁的文件查询,建议添加索引: + +```sql +-- MD5 索引(用于去重) +CREATE INDEX idx_system_file_md5 ON yz_system_file(md5); + +-- 租户 + 删除时间索引(用于文件列表查询) +CREATE INDEX idx_system_file_tid_delete ON yz_system_file(tid, delete_time); +``` + +## 故障排查 + +### 1. 上传失败:文件过大 + +**错误信息**: "文件大小不能超过 2048MB" + +**解决方案**: +- 检查 `fileUploadMaxMB` 常量设置 +- 确认 Nginx `client_max_body_size` 配置 +- 检查磁盘剩余空间 + +### 2. 上传超时 + +**错误信息**: "请求失败,请检查网络连接" + +**解决方案**: +- 检查 `app.conf` 中的 `ServerTimeOut` 配置 +- 检查 Nginx 超时配置 +- 检查网络带宽和稳定性 + +### 3. CORS 错误 + +**错误信息**: "已拦截跨源请求:同源策略禁止读取..." + +**解决方案**: +- 检查 `go/routers/router.go` 中的 CORS 配置 +- 确认 `Access-Control-Allow-Origin` 包含前端域名 +- 检查 `Access-Control-Allow-Headers` 包含 `Authorization` + +### 4. 文件不存在(404) + +**错误信息**: "请求的资源不存在" + +**可能原因**: +- 文件记录在数据库中不存在 +- 租户 ID (tid) 不匹配 +- 文件已被标记为删除 + +**解决方案**: +```sql +-- 检查文件记录 +SELECT * FROM yz_system_file WHERE id = 320; + +-- 检查是否被删除 +SELECT * FROM yz_system_file WHERE id = 320 AND delete_time IS NULL; +``` + +## 监控指标 + +建议监控以下指标: + +1. **上传成功率**: 成功上传数 / 总上传请求数 +2. **平均上传时间**: 按文件大小分段统计 +3. **磁盘使用率**: uploads 目录大小 / 总磁盘空间 +4. **错误率**: 按错误类型分类统计 + +## 相关文件 + +- `go/controllers/platform_file.go` - 文件上传控制器 +- `go/services/storage_service.go` - 存储服务接口 +- `go/conf/app.conf` - 服务器配置 +- `go/routers/router.go` - 路由和 CORS 配置 +- `go/models/system_file.go` - 文件数据模型 + +## 更新日志 + +- **2026-04-09**: + - 文件大小限制从 200MB 提升到 2GB + - 移除服务器超时限制 + - 更新文档 + +## 参考资料 + +- [Beego 文档 - 文件上传](https://beego.vip/docs/mvc/controller/file.md) +- [Nginx 文件上传配置](http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size) +- [七牛云 Go SDK](https://developer.qiniu.com/kodo/1238/go) diff --git a/go/docs/文档整理说明.md b/go/docs/文档整理说明.md index 67f6f2d..ecea6b1 100644 --- a/go/docs/文档整理说明.md +++ b/go/docs/文档整理说明.md @@ -1,183 +1,183 @@ -# 文档整理说明 - -## 📁 文档结构 - -所有文档已按照项目结构整理到对应的 `docs/` 目录中。 - -### 后端文档 (go/docs/) - -``` -go/docs/ -├── README.md # 文档索引(新增) -├── 后端开发规则.md # 开发规范 -├── 接口文件.md # 接口文档 -├── 服务端启动命令.md # 启动说明 -├── QUICK_START.md # 快速开始(新增) -├── README_STORAGE.md # 存储功能说明(新增) -├── storage-config-guide.md # 存储详细指南(新增) -├── DEPLOYMENT_CHECKLIST.md # 部署清单(新增) -├── IMPLEMENTATION_COMPLETE.md # 实现报告(新增) -├── 文档整理说明.md # 本文件(新增) -└── sql/ - └── add_storage_config_table.sql # 数据库迁移 -``` - -### 前端文档 (platform/docs/) - -``` -platform/docs/ -├── README.md # 文档索引(新增) -├── dictionary-usage.md # 字典使用 -├── pinia-dict-guide.md # Pinia字典指南 -├── 一键复制.md # 复制功能 -├── 拼接接口路径.md # 接口路径 -├── 接口调用.md # 接口调用 -├── 获取缓存数据.md # 缓存数据 -├── 调用图片上传组件.md # 图片上传 -└── 调用字典.md # 字典调用 -``` - -### 项目根目录 - -``` -项目根目录/ -└── README.md # 总导航(新增) -``` - -## 📝 文档分类 - -### 1. 开发文档 -- 后端开发规则.md -- 接口文件.md -- 服务端启动命令.md - -### 2. 功能文档 -- dictionary-usage.md -- pinia-dict-guide.md -- 调用字典.md -- 调用图片上传组件.md -- 等... - -### 3. 存储配置功能文档(新增) -- QUICK_START.md - 快速开始 -- README_STORAGE.md - 功能说明 -- storage-config-guide.md - 详细指南 -- DEPLOYMENT_CHECKLIST.md - 部署清单 -- IMPLEMENTATION_COMPLETE.md - 实现报告 - -### 4. 索引文档(新增) -- 项目根目录/README.md - 总导航 -- go/docs/README.md - 后端文档索引 -- platform/docs/README.md - 前端文档索引 - -## 🔍 文档查找 - -### 按功能查找 - -**存储配置功能**: -1. 快速开始 → `go/docs/QUICK_START.md` -2. 功能说明 → `go/docs/README_STORAGE.md` -3. 详细指南 → `go/docs/storage-config-guide.md` -4. 部署清单 → `go/docs/DEPLOYMENT_CHECKLIST.md` - -**字典功能**: -1. 使用说明 → `platform/docs/dictionary-usage.md` -2. Pinia指南 → `platform/docs/pinia-dict-guide.md` - -**图片上传**: -1. 组件调用 → `platform/docs/调用图片上传组件.md` - -### 按角色查找 - -**新手开发者**: -1. 项目总览 → `README.md` -2. 后端开发 → `go/docs/后端开发规则.md` -3. 快速开始 → `go/docs/QUICK_START.md` - -**运维人员**: -1. 启动命令 → `go/docs/服务端启动命令.md` -2. 部署清单 → `go/docs/DEPLOYMENT_CHECKLIST.md` - -**产品经理**: -1. 功能说明 → `go/docs/README_STORAGE.md` -2. 实现报告 → `go/docs/IMPLEMENTATION_COMPLETE.md` - -## 📋 文档规范 - -### 文件命名 -- 中文文档:使用中文名称(如:后端开发规则.md) -- 英文文档:使用大写+下划线(如:README_STORAGE.md) -- 索引文档:统一使用 README.md - -### 文档结构 -```markdown -# 标题 - -## 概述 -简要说明文档内容 - -## 目录 -- 章节1 -- 章节2 - -## 详细内容 -... - -## 相关链接 -- 链接1 -- 链接2 -``` - -### 文档位置 -- 后端相关文档 → `go/docs/` -- 前端相关文档 → `platform/docs/` -- 移动端相关文档 → `babyhealth/docs/` -- 项目总览 → 根目录 `README.md` - -## 🔄 文档更新 - -### 新增文档 -1. 确定文档类型(后端/前端/通用) -2. 放入对应的 `docs/` 目录 -3. 更新对应的 `README.md` 索引 -4. 如需要,更新根目录 `README.md` - -### 修改文档 -1. 直接修改对应文档 -2. 更新文档底部的"最后更新"时间 -3. 如有重大变更,更新索引文档 - -### 删除文档 -1. 删除文档文件 -2. 从索引中移除引用 -3. 检查其他文档中的链接 - -## ✅ 整理完成清单 - -- [x] 创建后端文档索引 (go/docs/README.md) -- [x] 创建前端文档索引 (platform/docs/README.md) -- [x] 创建项目总导航 (README.md) -- [x] 移动存储功能文档到 go/docs/ -- [x] 删除根目录的临时文档 -- [x] 创建文档整理说明(本文件) - -## 📌 注意事项 - -1. **文档位置**: 所有文档必须放在对应项目的 `docs/` 目录中 -2. **索引更新**: 新增文档后必须更新索引文件 -3. **链接检查**: 修改文档位置后检查所有引用链接 -4. **命名规范**: 遵循统一的文件命名规范 -5. **内容质量**: 保持文档的准确性和时效性 - -## 🎯 后续优化 - -- [ ] 添加文档搜索功能 -- [ ] 生成文档网站(如使用 VuePress) -- [ ] 添加文档版本管理 -- [ ] 自动化文档检查工具 -- [ ] 文档贡献指南 - ---- - -**整理完成时间**: 2024-01-01 -**整理人员**: AI Assistant +# 文档整理说明 + +## 📁 文档结构 + +所有文档已按照项目结构整理到对应的 `docs/` 目录中。 + +### 后端文档 (go/docs/) + +``` +go/docs/ +├── README.md # 文档索引(新增) +├── 后端开发规则.md # 开发规范 +├── 接口文件.md # 接口文档 +├── 服务端启动命令.md # 启动说明 +├── QUICK_START.md # 快速开始(新增) +├── README_STORAGE.md # 存储功能说明(新增) +├── storage-config-guide.md # 存储详细指南(新增) +├── DEPLOYMENT_CHECKLIST.md # 部署清单(新增) +├── IMPLEMENTATION_COMPLETE.md # 实现报告(新增) +├── 文档整理说明.md # 本文件(新增) +└── sql/ + └── add_storage_config_table.sql # 数据库迁移 +``` + +### 前端文档 (platform/docs/) + +``` +platform/docs/ +├── README.md # 文档索引(新增) +├── dictionary-usage.md # 字典使用 +├── pinia-dict-guide.md # Pinia字典指南 +├── 一键复制.md # 复制功能 +├── 拼接接口路径.md # 接口路径 +├── 接口调用.md # 接口调用 +├── 获取缓存数据.md # 缓存数据 +├── 调用图片上传组件.md # 图片上传 +└── 调用字典.md # 字典调用 +``` + +### 项目根目录 + +``` +项目根目录/ +└── README.md # 总导航(新增) +``` + +## 📝 文档分类 + +### 1. 开发文档 +- 后端开发规则.md +- 接口文件.md +- 服务端启动命令.md + +### 2. 功能文档 +- dictionary-usage.md +- pinia-dict-guide.md +- 调用字典.md +- 调用图片上传组件.md +- 等... + +### 3. 存储配置功能文档(新增) +- QUICK_START.md - 快速开始 +- README_STORAGE.md - 功能说明 +- storage-config-guide.md - 详细指南 +- DEPLOYMENT_CHECKLIST.md - 部署清单 +- IMPLEMENTATION_COMPLETE.md - 实现报告 + +### 4. 索引文档(新增) +- 项目根目录/README.md - 总导航 +- go/docs/README.md - 后端文档索引 +- platform/docs/README.md - 前端文档索引 + +## 🔍 文档查找 + +### 按功能查找 + +**存储配置功能**: +1. 快速开始 → `go/docs/QUICK_START.md` +2. 功能说明 → `go/docs/README_STORAGE.md` +3. 详细指南 → `go/docs/storage-config-guide.md` +4. 部署清单 → `go/docs/DEPLOYMENT_CHECKLIST.md` + +**字典功能**: +1. 使用说明 → `platform/docs/dictionary-usage.md` +2. Pinia指南 → `platform/docs/pinia-dict-guide.md` + +**图片上传**: +1. 组件调用 → `platform/docs/调用图片上传组件.md` + +### 按角色查找 + +**新手开发者**: +1. 项目总览 → `README.md` +2. 后端开发 → `go/docs/后端开发规则.md` +3. 快速开始 → `go/docs/QUICK_START.md` + +**运维人员**: +1. 启动命令 → `go/docs/服务端启动命令.md` +2. 部署清单 → `go/docs/DEPLOYMENT_CHECKLIST.md` + +**产品经理**: +1. 功能说明 → `go/docs/README_STORAGE.md` +2. 实现报告 → `go/docs/IMPLEMENTATION_COMPLETE.md` + +## 📋 文档规范 + +### 文件命名 +- 中文文档:使用中文名称(如:后端开发规则.md) +- 英文文档:使用大写+下划线(如:README_STORAGE.md) +- 索引文档:统一使用 README.md + +### 文档结构 +```markdown +# 标题 + +## 概述 +简要说明文档内容 + +## 目录 +- 章节1 +- 章节2 + +## 详细内容 +... + +## 相关链接 +- 链接1 +- 链接2 +``` + +### 文档位置 +- 后端相关文档 → `go/docs/` +- 前端相关文档 → `platform/docs/` +- 移动端相关文档 → `babyhealth/docs/` +- 项目总览 → 根目录 `README.md` + +## 🔄 文档更新 + +### 新增文档 +1. 确定文档类型(后端/前端/通用) +2. 放入对应的 `docs/` 目录 +3. 更新对应的 `README.md` 索引 +4. 如需要,更新根目录 `README.md` + +### 修改文档 +1. 直接修改对应文档 +2. 更新文档底部的"最后更新"时间 +3. 如有重大变更,更新索引文档 + +### 删除文档 +1. 删除文档文件 +2. 从索引中移除引用 +3. 检查其他文档中的链接 + +## ✅ 整理完成清单 + +- [x] 创建后端文档索引 (go/docs/README.md) +- [x] 创建前端文档索引 (platform/docs/README.md) +- [x] 创建项目总导航 (README.md) +- [x] 移动存储功能文档到 go/docs/ +- [x] 删除根目录的临时文档 +- [x] 创建文档整理说明(本文件) + +## 📌 注意事项 + +1. **文档位置**: 所有文档必须放在对应项目的 `docs/` 目录中 +2. **索引更新**: 新增文档后必须更新索引文件 +3. **链接检查**: 修改文档位置后检查所有引用链接 +4. **命名规范**: 遵循统一的文件命名规范 +5. **内容质量**: 保持文档的准确性和时效性 + +## 🎯 后续优化 + +- [ ] 添加文档搜索功能 +- [ ] 生成文档网站(如使用 VuePress) +- [ ] 添加文档版本管理 +- [ ] 自动化文档检查工具 +- [ ] 文档贡献指南 + +--- + +**整理完成时间**: 2024-01-01 +**整理人员**: AI Assistant diff --git a/go/docs/服务端启动命令.md b/go/docs/服务端启动命令.md index f9322a4..951c07f 100644 --- a/go/docs/服务端启动命令.md +++ b/go/docs/服务端启动命令.md @@ -1,300 +1,300 @@ -## 方式一:使用 systemd 服务(推荐) - -### 自动安装(推荐) - -使用安装脚本自动配置 systemd 服务: - -```bash -# 进入脚本目录 -cd /www/wwwroot/api.yunzer.cn/scripts - -# 添加执行权限 -chmod +x install-systemd-service.sh - -# 运行安装脚本 -sudo bash install-systemd-service.sh - -或者 - -sudo env PATH=$PATH:/usr/local/btgo/bin bash install-systemd-service.sh - - -``` - -脚本会自动: -- 停止现有服务和进程 -- 创建正确的 systemd 配置文件 -- 启动服务 -- 启用开机自启 -- 显示服务状态和日志 - -### 手动安装 - -如果需要手动配置: - -```bash -# 1. 停止现有服务 -systemctl stop go-api -pkill -f "go run main.go" - -# 2. 复制服务文件 -sudo cp /www/wwwroot/api.yunzer.cn/scripts/go-api.service /etc/systemd/system/ - -# 3. 重载 systemd -sudo systemctl daemon-reload - -# 4. 启动服务 -sudo systemctl start go-api - -# 5. 启用开机自启 -sudo systemctl enable go-api - -# 6. 查看状态 -sudo systemctl status go-api -``` - -### 启动服务 -```bash -systemctl start go-api -``` - -### 查看状态 -```bash -systemctl status go-api -``` - -### 常用命令 -```bash -# 启动 -systemctl start go-api - -# 停止 -systemctl stop go-api - -# 重启 -systemctl restart go-api - -# 查看状态 -systemctl status go-api - -# 查看日志(systemd 日志) -journalctl -u go-api -f - -# 查看日志(文件日志) -tail -f /www/wwwroot/api.yunzer.cn/go.log - -# 开机自启 -systemctl enable go-api - -# 禁用开机自启 -systemctl disable go-api -``` - -## 方式二:使用管理脚本(推荐) - -### 脚本位置 -```bash -/www/wwwroot/api.yunzer.cn/scripts/service.sh -``` - -### 添加执行权限 -```bash -chmod +x /www/wwwroot/api.yunzer.cn/scripts/service.sh -``` - -### 常用命令 -```bash -# 启动服务 -bash /www/wwwroot/api.yunzer.cn/scripts/service.sh start - -# 停止服务 -bash /www/wwwroot/api.yunzer.cn/scripts/service.sh stop - -# 重启服务 -bash /www/wwwroot/api.yunzer.cn/scripts/service.sh restart - -# 查看状态 -bash /www/wwwroot/api.yunzer.cn/scripts/service.sh status - -# 查看日志(最后 50 行) -bash /www/wwwroot/api.yunzer.cn/scripts/service.sh logs - -# 实时查看日志 -bash /www/wwwroot/api.yunzer.cn/scripts/service.sh logs -f - -# 查看最后 100 行日志 -bash /www/wwwroot/api.yunzer.cn/scripts/service.sh logs 100 -``` - -### 创建快捷命令(可选) -```bash -# 添加到 ~/.bashrc -echo 'alias go-service="bash /www/wwwroot/api.yunzer.cn/scripts/service.sh"' >> ~/.bashrc -source ~/.bashrc - -# 使用快捷命令 -go-service start -go-service restart -go-service status -go-service logs -f -``` - -## 方式三:后台直接启动 - -### 启动服务 -```bash -cd /www/wwwroot/api.yunzer.cn -nohup go run main.go > go.log 2>&1 & -``` - -### 查看是否运行成功 -```bash -tail -f go.log -``` - -### 查看进程 -```bash -ps aux | grep "go run main.go" | grep -v grep -``` - -### 重启服务 -```bash -pkill -f "go run main.go" && cd /www/wwwroot/api.yunzer.cn && nohup go run main.go > go.log 2>&1 & -``` - -### 停止服务 -```bash -pkill -f "go run main.go" -``` - -## 日志查看 - -### 查看实时日志 -```bash -# systemd 方式 -journalctl -u go-api -f - -# 直接启动方式 -tail -f /www/wwwroot/api.yunzer.cn/go.log -``` - -### 查看最近日志 -```bash -# systemd 方式 -journalctl -u go-api -n 100 - -# 直接启动方式 -tail -n 100 /www/wwwroot/api.yunzer.cn/go.log -``` - -### 查看错误日志 -```bash -# systemd 方式 -journalctl -u go-api -p err - -# 直接启动方式 -grep -i error /www/wwwroot/api.yunzer.cn/go.log -``` - -## 常见问题 - -### 1. 服务启动失败 - -**检查日志**: -```bash -# systemd -journalctl -u go-api -n 50 - -# 直接启动 -tail -n 50 /www/wwwroot/api.yunzer.cn/go.log -``` - -**常见原因**: -- 端口被占用(8081) -- 数据库连接失败 -- 配置文件错误 - -### 2. 端口被占用 - -**查看端口占用**: -```bash -netstat -tlnp | grep 8081 -# 或 -lsof -i :8081 -``` - -**停止占用进程**: -```bash -# 找到 PID -lsof -i :8081 - -# 停止进程 -kill -9 -``` - -### 3. 进程残留 - -**查找残留进程**: -```bash -ps aux | grep "go run main.go" | grep -v grep -``` - -**清理残留进程**: -```bash -pkill -9 -f "go run main.go" -``` - -### 4. 日志文件不存在 - -**原因**:启动命令没有重定向输出 - -**解决**:使用正确的启动命令 -```bash -nohup go run main.go > go.log 2>&1 & -``` - -## 性能监控 - -### 查看资源占用 -```bash -# CPU 和内存 -top -p $(pgrep -f "go run main.go") - -# 详细信息 -ps aux | grep "go run main.go" | grep -v grep -``` - -### 查看连接数 -```bash -netstat -an | grep 8081 | wc -l -``` - -### 查看文件描述符 -```bash -lsof -p $(pgrep -f "go run main.go") | wc -l -``` - -## 生产环境建议 - -1. **使用 systemd 服务**:更稳定,支持自动重启 -2. **配置日志轮转**:防止日志文件过大 -3. **监控服务状态**:使用监控工具(如 Prometheus) -4. **定期备份**:备份数据库和配置文件 -5. **使用编译后的二进制**:比 `go run` 更高效 - -### 编译并运行(推荐生产环境) -```bash -# 编译 -cd /www/wwwroot/api.yunzer.cn -go build -o server main.go - -# 运行 -nohup ./server > go.log 2>&1 & - -# 或使用 systemd(修改 ExecStart) -# ExecStart=/www/wwwroot/api.yunzer.cn/server -``` - -## 更新日期 - +## 方式一:使用 systemd 服务(推荐) + +### 自动安装(推荐) + +使用安装脚本自动配置 systemd 服务: + +```bash +# 进入脚本目录 +cd /www/wwwroot/api.yunzer.cn/scripts + +# 添加执行权限 +chmod +x install-systemd-service.sh + +# 运行安装脚本 +sudo bash install-systemd-service.sh + +或者 + +sudo env PATH=$PATH:/usr/local/btgo/bin bash install-systemd-service.sh + + +``` + +脚本会自动: +- 停止现有服务和进程 +- 创建正确的 systemd 配置文件 +- 启动服务 +- 启用开机自启 +- 显示服务状态和日志 + +### 手动安装 + +如果需要手动配置: + +```bash +# 1. 停止现有服务 +systemctl stop go-api +pkill -f "go run main.go" + +# 2. 复制服务文件 +sudo cp /www/wwwroot/api.yunzer.cn/scripts/go-api.service /etc/systemd/system/ + +# 3. 重载 systemd +sudo systemctl daemon-reload + +# 4. 启动服务 +sudo systemctl start go-api + +# 5. 启用开机自启 +sudo systemctl enable go-api + +# 6. 查看状态 +sudo systemctl status go-api +``` + +### 启动服务 +```bash +systemctl start go-api +``` + +### 查看状态 +```bash +systemctl status go-api +``` + +### 常用命令 +```bash +# 启动 +systemctl start go-api + +# 停止 +systemctl stop go-api + +# 重启 +systemctl restart go-api + +# 查看状态 +systemctl status go-api + +# 查看日志(systemd 日志) +journalctl -u go-api -f + +# 查看日志(文件日志) +tail -f /www/wwwroot/api.yunzer.cn/go.log + +# 开机自启 +systemctl enable go-api + +# 禁用开机自启 +systemctl disable go-api +``` + +## 方式二:使用管理脚本(推荐) + +### 脚本位置 +```bash +/www/wwwroot/api.yunzer.cn/scripts/service.sh +``` + +### 添加执行权限 +```bash +chmod +x /www/wwwroot/api.yunzer.cn/scripts/service.sh +``` + +### 常用命令 +```bash +# 启动服务 +bash /www/wwwroot/api.yunzer.cn/scripts/service.sh start + +# 停止服务 +bash /www/wwwroot/api.yunzer.cn/scripts/service.sh stop + +# 重启服务 +bash /www/wwwroot/api.yunzer.cn/scripts/service.sh restart + +# 查看状态 +bash /www/wwwroot/api.yunzer.cn/scripts/service.sh status + +# 查看日志(最后 50 行) +bash /www/wwwroot/api.yunzer.cn/scripts/service.sh logs + +# 实时查看日志 +bash /www/wwwroot/api.yunzer.cn/scripts/service.sh logs -f + +# 查看最后 100 行日志 +bash /www/wwwroot/api.yunzer.cn/scripts/service.sh logs 100 +``` + +### 创建快捷命令(可选) +```bash +# 添加到 ~/.bashrc +echo 'alias go-service="bash /www/wwwroot/api.yunzer.cn/scripts/service.sh"' >> ~/.bashrc +source ~/.bashrc + +# 使用快捷命令 +go-service start +go-service restart +go-service status +go-service logs -f +``` + +## 方式三:后台直接启动 + +### 启动服务 +```bash +cd /www/wwwroot/api.yunzer.cn +nohup go run main.go > go.log 2>&1 & +``` + +### 查看是否运行成功 +```bash +tail -f go.log +``` + +### 查看进程 +```bash +ps aux | grep "go run main.go" | grep -v grep +``` + +### 重启服务 +```bash +pkill -f "go run main.go" && cd /www/wwwroot/api.yunzer.cn && nohup go run main.go > go.log 2>&1 & +``` + +### 停止服务 +```bash +pkill -f "go run main.go" +``` + +## 日志查看 + +### 查看实时日志 +```bash +# systemd 方式 +journalctl -u go-api -f + +# 直接启动方式 +tail -f /www/wwwroot/api.yunzer.cn/go.log +``` + +### 查看最近日志 +```bash +# systemd 方式 +journalctl -u go-api -n 100 + +# 直接启动方式 +tail -n 100 /www/wwwroot/api.yunzer.cn/go.log +``` + +### 查看错误日志 +```bash +# systemd 方式 +journalctl -u go-api -p err + +# 直接启动方式 +grep -i error /www/wwwroot/api.yunzer.cn/go.log +``` + +## 常见问题 + +### 1. 服务启动失败 + +**检查日志**: +```bash +# systemd +journalctl -u go-api -n 50 + +# 直接启动 +tail -n 50 /www/wwwroot/api.yunzer.cn/go.log +``` + +**常见原因**: +- 端口被占用(8081) +- 数据库连接失败 +- 配置文件错误 + +### 2. 端口被占用 + +**查看端口占用**: +```bash +netstat -tlnp | grep 8081 +# 或 +lsof -i :8081 +``` + +**停止占用进程**: +```bash +# 找到 PID +lsof -i :8081 + +# 停止进程 +kill -9 +``` + +### 3. 进程残留 + +**查找残留进程**: +```bash +ps aux | grep "go run main.go" | grep -v grep +``` + +**清理残留进程**: +```bash +pkill -9 -f "go run main.go" +``` + +### 4. 日志文件不存在 + +**原因**:启动命令没有重定向输出 + +**解决**:使用正确的启动命令 +```bash +nohup go run main.go > go.log 2>&1 & +``` + +## 性能监控 + +### 查看资源占用 +```bash +# CPU 和内存 +top -p $(pgrep -f "go run main.go") + +# 详细信息 +ps aux | grep "go run main.go" | grep -v grep +``` + +### 查看连接数 +```bash +netstat -an | grep 8081 | wc -l +``` + +### 查看文件描述符 +```bash +lsof -p $(pgrep -f "go run main.go") | wc -l +``` + +## 生产环境建议 + +1. **使用 systemd 服务**:更稳定,支持自动重启 +2. **配置日志轮转**:防止日志文件过大 +3. **监控服务状态**:使用监控工具(如 Prometheus) +4. **定期备份**:备份数据库和配置文件 +5. **使用编译后的二进制**:比 `go run` 更高效 + +### 编译并运行(推荐生产环境) +```bash +# 编译 +cd /www/wwwroot/api.yunzer.cn +go build -o server main.go + +# 运行 +nohup ./server > go.log 2>&1 & + +# 或使用 systemd(修改 ExecStart) +# ExecStart=/www/wwwroot/api.yunzer.cn/server +``` + +## 更新日期 + 2026-04-09 \ No newline at end of file diff --git a/go/go.mod b/go/go.mod index bf818bb..591d3ea 100644 --- a/go/go.mod +++ b/go/go.mod @@ -1,38 +1,38 @@ -module server - -go 1.17 - -require ( - github.com/beego/beego/v2 v2.1.0 - github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/qiniu/go-sdk/v7 v7.18.2 - golang.org/x/crypto v0.1.0 // indirect -) - -require ( - github.com/go-sql-driver/mysql v1.7.0 - github.com/google/uuid v1.6.0 - golang.org/x/net v0.7.0 -) - -require ( - github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.15.1 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.7.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) - -exclude github.com/mattn/go-sqlite3 v1.14.31 +module server + +go 1.17 + +require ( + github.com/beego/beego/v2 v2.1.0 + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/qiniu/go-sdk/v7 v7.18.2 + golang.org/x/crypto v0.1.0 // indirect +) + +require ( + github.com/go-sql-driver/mysql v1.7.0 + github.com/google/uuid v1.6.0 + golang.org/x/net v0.7.0 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.15.1 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.7.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +exclude github.com/mattn/go-sqlite3 v1.14.31 diff --git a/go/go.sum b/go/go.sum index 326877b..8b390e8 100644 --- a/go/go.sum +++ b/go/go.sum @@ -1,911 +1,911 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/beego/beego/v2 v2.1.0 h1:Lk0FtQGvDQCx5V5yEu4XwDsIgt+QOlNjt5emUa3/ZmA= -github.com/beego/beego/v2 v2.1.0/go.mod h1:6h36ISpaxNrrpJ27siTpXBG8d/Icjzsc7pU1bWpp0EE= -github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.4.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bloom/v3 v3.3.1/go.mod h1:bhUUknWd5khVbTe4UgMCSiOOVJzr3tMoijSK3WwvW90= -github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= -github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog= -github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/couchbase/go-couchbase v0.1.0/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= -github.com/couchbase/gomemcached v0.1.3/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo= -github.com/couchbase/goutils v0.1.0/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elastic/go-elasticsearch/v6 v6.8.10/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= -github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= -github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.12.1-0.20220826005032-a7ba4fa4e289/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk= -github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ= -github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= -github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= -github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= -github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= -github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= -github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4= -github.com/nats-io/nats.go v1.15.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= -github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.2/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= -github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= -github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= -github.com/qiniu/go-sdk/v7 v7.18.2 h1:vk9eo5OO7aqgAOPF0Ytik/gt7CMKuNgzC/IPkhda6rk= -github.com/qiniu/go-sdk/v7 v7.18.2/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w= -github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= -github.com/rabbitmq/amqp091-go v1.2.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik= -github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= -github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= -github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s= -github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= -github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2/go.mod h1:bx//lU66dPzNT+Y0hHA12ciKoMOH9iixEwCqC1OeQWQ= -go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= -go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/kingpin/v2 v2.3.1/go.mod h1:oYL5vtsvEHZGHxU7DMp32Dvx+qL+ptGn6lWaot2vCNE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/beego/beego/v2 v2.1.0 h1:Lk0FtQGvDQCx5V5yEu4XwDsIgt+QOlNjt5emUa3/ZmA= +github.com/beego/beego/v2 v2.1.0/go.mod h1:6h36ISpaxNrrpJ27siTpXBG8d/Icjzsc7pU1bWpp0EE= +github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.4.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bloom/v3 v3.3.1/go.mod h1:bhUUknWd5khVbTe4UgMCSiOOVJzr3tMoijSK3WwvW90= +github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog= +github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/couchbase/go-couchbase v0.1.0/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= +github.com/couchbase/gomemcached v0.1.3/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo= +github.com/couchbase/goutils v0.1.0/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elastic/go-elasticsearch/v6 v6.8.10/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= +github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw= +github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.12.1-0.20220826005032-a7ba4fa4e289/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk= +github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ= +github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= +github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= +github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4= +github.com/nats-io/nats.go v1.15.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.2/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= +github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= +github.com/qiniu/go-sdk/v7 v7.18.2 h1:vk9eo5OO7aqgAOPF0Ytik/gt7CMKuNgzC/IPkhda6rk= +github.com/qiniu/go-sdk/v7 v7.18.2/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w= +github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= +github.com/rabbitmq/amqp091-go v1.2.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik= +github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= +github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= +github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s= +github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= +github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +github.com/xhit/go-str2duration v1.2.0/go.mod h1:3cPSlfZlUHVlneIVfePFWcJZsuwf+P1v2SRTV4cUmp4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.11.2/go.mod h1:bx//lU66dPzNT+Y0hHA12ciKoMOH9iixEwCqC1OeQWQ= +go.opentelemetry.io/otel/sdk v1.11.2/go.mod h1:wZ1WxImwpq+lVRo4vsmSOxdd+xwoUJ6rqyLc3SyX9aU= +go.opentelemetry.io/otel/trace v1.11.2/go.mod h1:4N+yC7QEz7TTsG9BSRLNAa63eg5E06ObSbKPmxQ/pKA= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/go/main.go b/go/main.go index 6ada88c..e03b7f3 100644 --- a/go/main.go +++ b/go/main.go @@ -1,29 +1,29 @@ -package main - -import ( - "server/models" - "server/services" - _ "server/routers" - "server/version" - - beego "github.com/beego/beego/v2/server/web" -) - -func main() { - // 初始化数据库 - models.Init(version.Version) - - // 启用请求体复制(允许多次读取请求体) - beego.BConfig.CopyRequestBody = true - - // 设置最大请求体大小(10MB,足够登录请求使用) - beego.BConfig.MaxMemory = 10 << 20 // 10MB - - // 静态资源:映射 /uploads 到本地 uploads 目录,供前端访问上传文件 - beego.SetStaticPath("/uploads", "uploads") - - // 启动日程提醒定时任务 - services.StartReminderScheduler(make(chan struct{})) - - beego.Run() -} +package main + +import ( + "server/models" + "server/services" + _ "server/routers" + "server/version" + + beego "github.com/beego/beego/v2/server/web" +) + +func main() { + // 初始化数据库 + models.Init(version.Version) + + // 启用请求体复制(允许多次读取请求体) + beego.BConfig.CopyRequestBody = true + + // 设置最大请求体大小(10MB,足够登录请求使用) + beego.BConfig.MaxMemory = 10 << 20 // 10MB + + // 静态资源:映射 /uploads 到本地 uploads 目录,供前端访问上传文件 + beego.SetStaticPath("/uploads", "uploads") + + // 启动日程提醒定时任务 + services.StartReminderScheduler(make(chan struct{})) + + beego.Run() +} diff --git a/go/middleware/jwt.go b/go/middleware/jwt.go index 4276f68..3ac905e 100644 --- a/go/middleware/jwt.go +++ b/go/middleware/jwt.go @@ -1,75 +1,75 @@ -package middleware - -import ( - "strings" - - "server/pkg/jwtutil" - - "github.com/beego/beego/v2/server/web" - "github.com/beego/beego/v2/server/web/context" -) - -// JWTAuthMiddleware JWT认证中间件 -func JWTAuthMiddleware() web.FilterFunc { - return func(ctx *context.Context) { - // 跳过登录相关的路由 - if strings.HasPrefix(ctx.Request.RequestURI, "/api/login") || - strings.HasPrefix(ctx.Request.RequestURI, "/api/reset-password") { - return - } - - // 从请求头中获取Authorization - authHeader := ctx.Request.Header.Get("Authorization") - if authHeader == "" { - ctx.Output.SetStatus(401) - ctx.Output.JSON(map[string]interface{}{ - "success": false, - "message": "未提供认证信息", - }, false, false) - return - } - - // 按空格分割 - authParts := strings.SplitN(authHeader, " ", 2) - if !(len(authParts) == 2 && authParts[0] == "Bearer") { - ctx.Output.SetStatus(401) - ctx.Output.JSON(map[string]interface{}{ - "success": false, - "message": "认证信息格式错误", - }, false, false) - return - } - - // 解析token - claims, err := jwtutil.ParseToken(authParts[1]) - if err != nil { - // 处理各种错误情况 - ctx.Output.SetStatus(401) - switch err.Error() { - case "token is expired": - ctx.Output.JSON(map[string]interface{}{ - "success": false, - "message": "token已过期", - }, false, false) - default: - ctx.Output.JSON(map[string]interface{}{ - "success": false, - "message": "无效的token", - }, false, false) - } - return - } - - // 将用户信息存储在上下文 - ctx.Input.SetData("userId", claims.UserID) - ctx.Input.SetData("username", claims.Username) - ctx.Input.SetData("tenantId", claims.TenantId) - - // 从token中获取用户类型(如果token中没有,则默认为"user") - userType := claims.UserType - if userType == "" { - userType = "user" - } - ctx.Input.SetData("userType", userType) - } -} +package middleware + +import ( + "strings" + + "server/pkg/jwtutil" + + "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/context" +) + +// JWTAuthMiddleware JWT认证中间件 +func JWTAuthMiddleware() web.FilterFunc { + return func(ctx *context.Context) { + // 跳过登录相关的路由 + if strings.HasPrefix(ctx.Request.RequestURI, "/api/login") || + strings.HasPrefix(ctx.Request.RequestURI, "/api/reset-password") { + return + } + + // 从请求头中获取Authorization + authHeader := ctx.Request.Header.Get("Authorization") + if authHeader == "" { + ctx.Output.SetStatus(401) + ctx.Output.JSON(map[string]interface{}{ + "success": false, + "message": "未提供认证信息", + }, false, false) + return + } + + // 按空格分割 + authParts := strings.SplitN(authHeader, " ", 2) + if !(len(authParts) == 2 && authParts[0] == "Bearer") { + ctx.Output.SetStatus(401) + ctx.Output.JSON(map[string]interface{}{ + "success": false, + "message": "认证信息格式错误", + }, false, false) + return + } + + // 解析token + claims, err := jwtutil.ParseToken(authParts[1]) + if err != nil { + // 处理各种错误情况 + ctx.Output.SetStatus(401) + switch err.Error() { + case "token is expired": + ctx.Output.JSON(map[string]interface{}{ + "success": false, + "message": "token已过期", + }, false, false) + default: + ctx.Output.JSON(map[string]interface{}{ + "success": false, + "message": "无效的token", + }, false, false) + } + return + } + + // 将用户信息存储在上下文 + ctx.Input.SetData("userId", claims.UserID) + ctx.Input.SetData("username", claims.Username) + ctx.Input.SetData("tenantId", claims.TenantId) + + // 从token中获取用户类型(如果token中没有,则默认为"user") + userType := claims.UserType + if userType == "" { + userType = "user" + } + ctx.Input.SetData("userType", userType) + } +} diff --git a/go/middleware/operationLog.go b/go/middleware/operationLog.go index ac8d99c..6cb3f61 100644 --- a/go/middleware/operationLog.go +++ b/go/middleware/operationLog.go @@ -1,251 +1,251 @@ -package middleware - -import ( - "encoding/json" - "strconv" - "strings" - "time" - - "server/models" - - "github.com/beego/beego/v2/server/web/context" -) - -const ( - oplogStartKey = "__oplog_start" - oplogReqBodyKey = "__oplog_req_body" -) - -// BeginOperationLog 在 BeforeRouter 采集请求信息 -func BeginOperationLog(ctx *context.Context) { - url := ctx.Input.URL() - method := ctx.Input.Method() - if shouldSkipLogging(method, url) { - return - } - ctx.Input.SetData(oplogStartKey, time.Now()) - - // 请求体由 main.go 的 CopyBody 保留在 Input.RequestBody - if rb := ctx.Input.RequestBody; len(rb) > 0 { - s := string(rb) - ctx.Input.SetData(oplogReqBodyKey, truncateString(maskSensitive(s), 5000)) - } -} - -// FinishOperationLog 在 FinishRouter 统一落库到 yz_system_operation_log -func FinishOperationLog(ctx *context.Context) { - url := ctx.Input.URL() - method := ctx.Input.Method() - if shouldSkipLogging(method, url) { - return - } - - start, _ := ctx.Input.GetData(oplogStartKey).(time.Time) - if start.IsZero() { - start = time.Now() - } - execSec := float64(time.Since(start).Milliseconds()) / 1000.0 - - uid := parseUint64FromCtx(ctx.Input.GetData("userId")) - tidVal := parseUint64FromCtx(ctx.Input.GetData("tenantId")) - var tid *uint64 - if tidVal > 0 { - tid = &tidVal - } - - module := parseModule(url) - action := parseAction(method, url) - ip := ctx.Input.IP() - userAgent := truncateString(ctx.Input.Header("User-Agent"), 500) - status := int8(1) - if code := ctx.ResponseWriter.Status; code >= 400 { - status = 0 - } - - var reqData *string - if v, ok := ctx.Input.GetData(oplogReqBodyKey).(string); ok && strings.TrimSpace(v) != "" { - reqData = &v - } else if q := strings.TrimSpace(ctx.Request.URL.RawQuery); q != "" { - q = truncateString(maskSensitive(q), 5000) - reqData = &q - } - - var respData *string - if code := ctx.ResponseWriter.Status; code >= 400 { - msg := "HTTP " + strconv.Itoa(code) - respData = &msg - } - - var errMsg *string - if status == 0 { - msg := "请求失败" - if respData != nil { - msg = *respData - } - errMsg = &msg - } - - logRow := &models.SystemOperationLog{ - Tid: tid, - UserID: uid, - Module: module, - Action: action, - Method: method, - URL: truncateString(url, 255), - IP: truncateString(ip, 50), - UserAgent: userAgent, - RequestData: reqData, - ResponseData: respData, - Status: status, - ErrorMessage: errMsg, - ExecutionTime: execSec, - } - _, _ = models.Orm.Insert(logRow) -} - -func parseAction(method, url string) string { - u := strings.ToLower(url) - if strings.Contains(u, "login") { - return "登录" - } - if strings.Contains(u, "logout") { - return "退出" - } - if strings.Contains(u, "upload") { - return "上传" - } - switch method { - case "POST": - if strings.Contains(u, "delete") { - return "删除" - } - if strings.Contains(u, "update") || strings.Contains(u, "edit") || strings.Contains(u, "rename") { - return "编辑" - } - if strings.Contains(u, "create") || strings.Contains(u, "add") { - return "新增" - } - return "提交" - case "PUT", "PATCH": - return "编辑" - case "DELETE": - return "删除" - default: - return "查询" - } -} - -func parseModule(url string) string { - path := strings.Trim(strings.ToLower(url), "/") - parts := strings.Split(path, "/") - if len(parts) >= 2 { - return truncateString(parts[1], 50) - } - if len(parts) == 1 && parts[0] != "" { - return truncateString(parts[0], 50) - } - return "unknown" -} - -func shouldSkipLogging(method, url string) bool { - skipPatterns := []string{ - "/static/", - "/uploads/", - "/favicon.ico", - "/health", - "/ping", - } - for _, pattern := range skipPatterns { - if strings.HasPrefix(url, pattern) { - return true - } - } - - // 高频噪声接口:默认跳过(可按需再扩充) - if method == "GET" { - noisyExact := map[string]bool{ - "/platform/currentUser": true, - "/platform/allmenu": true, - "/platform/getOpenVerify": true, // 若未来改名/迁移可再调整 - } - if noisyExact[url] { - return true - } - // 菜单详情/列表类:频率高且多为前端路由加载 - if strings.HasPrefix(url, "/platform/menu/") { - return true - } - // 登录页极验配置轮询/获取(不影响关键业务) - if strings.HasPrefix(url, "/platform/login/getGeetest") || strings.HasPrefix(url, "/platform/login/getOpenVerify") { - return true - } - // 客户端高频版本检查 - if strings.HasPrefix(url, "/api/softwareupgrade/check") { - return true - } - } - return false -} - -func parseUint64FromCtx(v interface{}) uint64 { - switch x := v.(type) { - case int: - if x > 0 { - return uint64(x) - } - case int64: - if x > 0 { - return uint64(x) - } - case uint64: - return x - case float64: - if x > 0 { - return uint64(x) - } - } - return 0 -} - -func truncateString(s string, maxLen int) string { - if len(s) <= maxLen { - return s - } - return s[:maxLen] + "..." -} - -func maskSensitive(s string) string { - // 尝试 JSON 脱敏(失败则返回原文) - var obj interface{} - if err := json.Unmarshal([]byte(s), &obj); err != nil { - return s - } - maskInObj(&obj) - bs, err := json.Marshal(obj) - if err != nil { - return s - } - return string(bs) -} - -func maskInObj(v *interface{}) { - switch t := (*v).(type) { - case map[string]interface{}: - for k, val := range t { - lk := strings.ToLower(k) - if lk == "password" || lk == "pwd" || lk == "token" || lk == "api_key" || lk == "api_secret" || lk == "authorization" { - t[k] = "***" - continue - } - tmp := val - maskInObj(&tmp) - t[k] = tmp - } - case []interface{}: - for i := range t { - tmp := t[i] - maskInObj(&tmp) - t[i] = tmp - } - } -} +package middleware + +import ( + "encoding/json" + "strconv" + "strings" + "time" + + "server/models" + + "github.com/beego/beego/v2/server/web/context" +) + +const ( + oplogStartKey = "__oplog_start" + oplogReqBodyKey = "__oplog_req_body" +) + +// BeginOperationLog 在 BeforeRouter 采集请求信息 +func BeginOperationLog(ctx *context.Context) { + url := ctx.Input.URL() + method := ctx.Input.Method() + if shouldSkipLogging(method, url) { + return + } + ctx.Input.SetData(oplogStartKey, time.Now()) + + // 请求体由 main.go 的 CopyBody 保留在 Input.RequestBody + if rb := ctx.Input.RequestBody; len(rb) > 0 { + s := string(rb) + ctx.Input.SetData(oplogReqBodyKey, truncateString(maskSensitive(s), 5000)) + } +} + +// FinishOperationLog 在 FinishRouter 统一落库到 yz_system_operation_log +func FinishOperationLog(ctx *context.Context) { + url := ctx.Input.URL() + method := ctx.Input.Method() + if shouldSkipLogging(method, url) { + return + } + + start, _ := ctx.Input.GetData(oplogStartKey).(time.Time) + if start.IsZero() { + start = time.Now() + } + execSec := float64(time.Since(start).Milliseconds()) / 1000.0 + + uid := parseUint64FromCtx(ctx.Input.GetData("userId")) + tidVal := parseUint64FromCtx(ctx.Input.GetData("tenantId")) + var tid *uint64 + if tidVal > 0 { + tid = &tidVal + } + + module := parseModule(url) + action := parseAction(method, url) + ip := ctx.Input.IP() + userAgent := truncateString(ctx.Input.Header("User-Agent"), 500) + status := int8(1) + if code := ctx.ResponseWriter.Status; code >= 400 { + status = 0 + } + + var reqData *string + if v, ok := ctx.Input.GetData(oplogReqBodyKey).(string); ok && strings.TrimSpace(v) != "" { + reqData = &v + } else if q := strings.TrimSpace(ctx.Request.URL.RawQuery); q != "" { + q = truncateString(maskSensitive(q), 5000) + reqData = &q + } + + var respData *string + if code := ctx.ResponseWriter.Status; code >= 400 { + msg := "HTTP " + strconv.Itoa(code) + respData = &msg + } + + var errMsg *string + if status == 0 { + msg := "请求失败" + if respData != nil { + msg = *respData + } + errMsg = &msg + } + + logRow := &models.SystemOperationLog{ + Tid: tid, + UserID: uid, + Module: module, + Action: action, + Method: method, + URL: truncateString(url, 255), + IP: truncateString(ip, 50), + UserAgent: userAgent, + RequestData: reqData, + ResponseData: respData, + Status: status, + ErrorMessage: errMsg, + ExecutionTime: execSec, + } + _, _ = models.Orm.Insert(logRow) +} + +func parseAction(method, url string) string { + u := strings.ToLower(url) + if strings.Contains(u, "login") { + return "登录" + } + if strings.Contains(u, "logout") { + return "退出" + } + if strings.Contains(u, "upload") { + return "上传" + } + switch method { + case "POST": + if strings.Contains(u, "delete") { + return "删除" + } + if strings.Contains(u, "update") || strings.Contains(u, "edit") || strings.Contains(u, "rename") { + return "编辑" + } + if strings.Contains(u, "create") || strings.Contains(u, "add") { + return "新增" + } + return "提交" + case "PUT", "PATCH": + return "编辑" + case "DELETE": + return "删除" + default: + return "查询" + } +} + +func parseModule(url string) string { + path := strings.Trim(strings.ToLower(url), "/") + parts := strings.Split(path, "/") + if len(parts) >= 2 { + return truncateString(parts[1], 50) + } + if len(parts) == 1 && parts[0] != "" { + return truncateString(parts[0], 50) + } + return "unknown" +} + +func shouldSkipLogging(method, url string) bool { + skipPatterns := []string{ + "/static/", + "/uploads/", + "/favicon.ico", + "/health", + "/ping", + } + for _, pattern := range skipPatterns { + if strings.HasPrefix(url, pattern) { + return true + } + } + + // 高频噪声接口:默认跳过(可按需再扩充) + if method == "GET" { + noisyExact := map[string]bool{ + "/platform/currentUser": true, + "/platform/allmenu": true, + "/platform/getOpenVerify": true, // 若未来改名/迁移可再调整 + } + if noisyExact[url] { + return true + } + // 菜单详情/列表类:频率高且多为前端路由加载 + if strings.HasPrefix(url, "/platform/menu/") { + return true + } + // 登录页极验配置轮询/获取(不影响关键业务) + if strings.HasPrefix(url, "/platform/login/getGeetest") || strings.HasPrefix(url, "/platform/login/getOpenVerify") { + return true + } + // 客户端高频版本检查 + if strings.HasPrefix(url, "/api/softwareupgrade/check") { + return true + } + } + return false +} + +func parseUint64FromCtx(v interface{}) uint64 { + switch x := v.(type) { + case int: + if x > 0 { + return uint64(x) + } + case int64: + if x > 0 { + return uint64(x) + } + case uint64: + return x + case float64: + if x > 0 { + return uint64(x) + } + } + return 0 +} + +func truncateString(s string, maxLen int) string { + if len(s) <= maxLen { + return s + } + return s[:maxLen] + "..." +} + +func maskSensitive(s string) string { + // 尝试 JSON 脱敏(失败则返回原文) + var obj interface{} + if err := json.Unmarshal([]byte(s), &obj); err != nil { + return s + } + maskInObj(&obj) + bs, err := json.Marshal(obj) + if err != nil { + return s + } + return string(bs) +} + +func maskInObj(v *interface{}) { + switch t := (*v).(type) { + case map[string]interface{}: + for k, val := range t { + lk := strings.ToLower(k) + if lk == "password" || lk == "pwd" || lk == "token" || lk == "api_key" || lk == "api_secret" || lk == "authorization" { + t[k] = "***" + continue + } + tmp := val + maskInObj(&tmp) + t[k] = tmp + } + case []interface{}: + for i := range t { + tmp := t[i] + maskInObj(&tmp) + t[i] = tmp + } + } +} diff --git a/go/middleware/permission.go b/go/middleware/permission.go index 6e85bbe..655aa95 100644 --- a/go/middleware/permission.go +++ b/go/middleware/permission.go @@ -1,188 +1,188 @@ -package middleware - -import ( - "server/services" - "strings" - - "github.com/beego/beego/v2/server/web/context" -) - -// PermissionMiddleware 权限验证中间件 -// 根据路由的权限标识检查用户是否有访问权限 -func PermissionMiddleware() func(ctx *context.Context) { - return func(ctx *context.Context) { - // 获取当前请求的路径 - path := ctx.Input.URL() - - // 不需要权限验证的路径列表 - publicPaths := []string{ - "/api/login", - "/api/logout", - "/api/reset-password", - "/api/program-categories/public", - "/api/program-infos/public", - "/api/files/public", - } - - // 检查是否为公开路径 - for _, p := range publicPaths { - if path == p { - return - } - } - - // 检查是否为公开预览接口 - if strings.HasPrefix(path, "/api/files/public-preview/") { - return - } - - // 获取用户ID - userIdData := ctx.Input.GetData("userId") - if userIdData == nil { - // 如果没有用户ID,说明未登录,这个应该在JWT中间件中处理 - // 这里直接返回,因为JWT中间件已经拦截了 - return - } - - userId, ok := userIdData.(int) - if !ok { - ctx.Output.JSON(map[string]interface{}{ - "success": false, - "message": "用户ID格式错误", - }, false, false) - return - } - - // 获取当前路由对应的权限标识 - permission := getPermissionByPath(path, ctx.Input.Method()) - - // 如果没有权限标识,说明该接口不需要权限控制 - if permission == "" { - return - } - - // 检查用户是否拥有该权限 - hasPermission, err := services.CheckUserPermission(userId, permission) - if err != nil { - ctx.Output.JSON(map[string]interface{}{ - "success": false, - "message": "权限验证失败", - "error": err.Error(), - }, false, false) - return - } - - if !hasPermission { - ctx.Output.JSON(map[string]interface{}{ - "success": false, - "message": "您没有权限访问此接口", - "code": 403, - }, false, false) - return - } - } -} - -// getPermissionByPath 根据路径和方法获取权限标识 -// 这是一个简化版本,实际应该从数据库中动态获取路由-权限映射关系 -func getPermissionByPath(path, method string) string { - // 权限映射表(路径模式 -> 权限标识) - // 这里只列举了部分示例,实际应该从数据库中加载 - permissionMap := map[string]string{ - // 用户管理 - "GET:/api/allUsers": "user:list", - "GET:/api/user/:id": "user:detail", - "POST:/api/addUser": "user:add", - "POST:/api/editUser/:id": "user:edit", - "DELETE:/api/deleteUser/:id": "user:delete", - "POST:/api/changePassword/:id":"user:changePassword", - - // 角色管理 - "GET:/api/roles": "role:list", - "POST:/api/roles": "role:create", - "GET:/api/roles/:id": "role:detail", - "POST:/api/roles/:id": "role:update", - "DELETE:/api/roles/:id": "role:delete", - - // 菜单管理 - "GET:/api/allmenu": "menu:list", - "POST:/api/menu": "menu:create", - "PUT:/api/menu/:id": "menu:update", - "DELETE:/api/menu/:id": "menu:delete", - - // 文件管理 - "GET:/api/files": "file:list", - "POST:/api/files": "file:upload", - "GET:/api/files/my": "file:my", - "GET:/api/files/download/:id": "file:download", - "GET:/api/files/preview/:id": "file:preview", - "GET:/api/files/:id": "file:detail", - "PUT:/api/files/:id": "file:update", - "DELETE:/api/files/:id": "file:delete", - "GET:/api/files/search": "file:search", - "GET:/api/files/statistics": "file:statistics", - - // 租户管理 - "GET:/api/tenant/list": "tenant:list", - "POST:/api/tenant": "tenant:create", - "PUT:/api/tenant/:id": "tenant:update", - "DELETE:/api/tenant/:id": "tenant:delete", - "POST:/api/tenant/:id/audit": "tenant:audit", - "GET:/api/tenant/:id": "tenant:detail", - - // 知识库 - "GET:/api/knowledge/list": "knowledge:list", - "GET:/api/knowledge/detail": "knowledge:detail", - "POST:/api/knowledge/create": "knowledge:create", - "POST:/api/knowledge/update": "knowledge:update", - "POST:/api/knowledge/delete": "knowledge:delete", - } - - // 匹配路径(简化版本,不支持动态参数匹配) - key := method + ":" + path - if perm, ok := permissionMap[key]; ok { - return perm - } - - // 尝试匹配动态路由(简单的ID参数替换) - // 例如:/api/user/123 -> /api/user/:id - pathParts := strings.Split(path, "/") - for pattern, perm := range permissionMap { - parts := strings.Split(pattern, ":") - if len(parts) != 2 { - continue - } - - methodPart := parts[0] - pathPattern := parts[1] - - if methodPart != method { - continue - } - - patternParts := strings.Split(pathPattern, "/") - if len(patternParts) != len(pathParts) { - continue - } - - match := true - for i, part := range patternParts { - if strings.HasPrefix(part, ":") { - // 动态参数,跳过 - continue - } - if part != pathParts[i] { - match = false - break - } - } - - if match { - return perm - } - } - - // 如果没有找到匹配的权限标识,返回空字符串(表示不需要权限控制) - return "" -} - +package middleware + +import ( + "server/services" + "strings" + + "github.com/beego/beego/v2/server/web/context" +) + +// PermissionMiddleware 权限验证中间件 +// 根据路由的权限标识检查用户是否有访问权限 +func PermissionMiddleware() func(ctx *context.Context) { + return func(ctx *context.Context) { + // 获取当前请求的路径 + path := ctx.Input.URL() + + // 不需要权限验证的路径列表 + publicPaths := []string{ + "/api/login", + "/api/logout", + "/api/reset-password", + "/api/program-categories/public", + "/api/program-infos/public", + "/api/files/public", + } + + // 检查是否为公开路径 + for _, p := range publicPaths { + if path == p { + return + } + } + + // 检查是否为公开预览接口 + if strings.HasPrefix(path, "/api/files/public-preview/") { + return + } + + // 获取用户ID + userIdData := ctx.Input.GetData("userId") + if userIdData == nil { + // 如果没有用户ID,说明未登录,这个应该在JWT中间件中处理 + // 这里直接返回,因为JWT中间件已经拦截了 + return + } + + userId, ok := userIdData.(int) + if !ok { + ctx.Output.JSON(map[string]interface{}{ + "success": false, + "message": "用户ID格式错误", + }, false, false) + return + } + + // 获取当前路由对应的权限标识 + permission := getPermissionByPath(path, ctx.Input.Method()) + + // 如果没有权限标识,说明该接口不需要权限控制 + if permission == "" { + return + } + + // 检查用户是否拥有该权限 + hasPermission, err := services.CheckUserPermission(userId, permission) + if err != nil { + ctx.Output.JSON(map[string]interface{}{ + "success": false, + "message": "权限验证失败", + "error": err.Error(), + }, false, false) + return + } + + if !hasPermission { + ctx.Output.JSON(map[string]interface{}{ + "success": false, + "message": "您没有权限访问此接口", + "code": 403, + }, false, false) + return + } + } +} + +// getPermissionByPath 根据路径和方法获取权限标识 +// 这是一个简化版本,实际应该从数据库中动态获取路由-权限映射关系 +func getPermissionByPath(path, method string) string { + // 权限映射表(路径模式 -> 权限标识) + // 这里只列举了部分示例,实际应该从数据库中加载 + permissionMap := map[string]string{ + // 用户管理 + "GET:/api/allUsers": "user:list", + "GET:/api/user/:id": "user:detail", + "POST:/api/addUser": "user:add", + "POST:/api/editUser/:id": "user:edit", + "DELETE:/api/deleteUser/:id": "user:delete", + "POST:/api/changePassword/:id":"user:changePassword", + + // 角色管理 + "GET:/api/roles": "role:list", + "POST:/api/roles": "role:create", + "GET:/api/roles/:id": "role:detail", + "POST:/api/roles/:id": "role:update", + "DELETE:/api/roles/:id": "role:delete", + + // 菜单管理 + "GET:/api/allmenu": "menu:list", + "POST:/api/menu": "menu:create", + "PUT:/api/menu/:id": "menu:update", + "DELETE:/api/menu/:id": "menu:delete", + + // 文件管理 + "GET:/api/files": "file:list", + "POST:/api/files": "file:upload", + "GET:/api/files/my": "file:my", + "GET:/api/files/download/:id": "file:download", + "GET:/api/files/preview/:id": "file:preview", + "GET:/api/files/:id": "file:detail", + "PUT:/api/files/:id": "file:update", + "DELETE:/api/files/:id": "file:delete", + "GET:/api/files/search": "file:search", + "GET:/api/files/statistics": "file:statistics", + + // 租户管理 + "GET:/api/tenant/list": "tenant:list", + "POST:/api/tenant": "tenant:create", + "PUT:/api/tenant/:id": "tenant:update", + "DELETE:/api/tenant/:id": "tenant:delete", + "POST:/api/tenant/:id/audit": "tenant:audit", + "GET:/api/tenant/:id": "tenant:detail", + + // 知识库 + "GET:/api/knowledge/list": "knowledge:list", + "GET:/api/knowledge/detail": "knowledge:detail", + "POST:/api/knowledge/create": "knowledge:create", + "POST:/api/knowledge/update": "knowledge:update", + "POST:/api/knowledge/delete": "knowledge:delete", + } + + // 匹配路径(简化版本,不支持动态参数匹配) + key := method + ":" + path + if perm, ok := permissionMap[key]; ok { + return perm + } + + // 尝试匹配动态路由(简单的ID参数替换) + // 例如:/api/user/123 -> /api/user/:id + pathParts := strings.Split(path, "/") + for pattern, perm := range permissionMap { + parts := strings.Split(pattern, ":") + if len(parts) != 2 { + continue + } + + methodPart := parts[0] + pathPattern := parts[1] + + if methodPart != method { + continue + } + + patternParts := strings.Split(pathPattern, "/") + if len(patternParts) != len(pathParts) { + continue + } + + match := true + for i, part := range patternParts { + if strings.HasPrefix(part, ":") { + // 动态参数,跳过 + continue + } + if part != pathParts[i] { + match = false + break + } + } + + if match { + return perm + } + } + + // 如果没有找到匹配的权限标识,返回空字符串(表示不需要权限控制) + return "" +} + diff --git a/go/models/admin_role.go b/go/models/admin_role.go index 8960346..a08cf5c 100644 --- a/go/models/admin_role.go +++ b/go/models/admin_role.go @@ -1,19 +1,19 @@ -package models - -import "time" - -// AdminRole 平台角色表 yz_system_admin_role -type AdminRole struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Cid uint8 `orm:"column(cid);default(1)" json:"cid"` // 1平台角色 2租户角色 - Name string `orm:"column(name);size(32)" json:"name"` - Status uint8 `orm:"column(status);default(1)" json:"status"` - Rights *string `orm:"column(rights);type(text);null" json:"rights"` - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *AdminRole) TableName() string { - return "yz_system_admin_role" -} +package models + +import "time" + +// AdminRole 平台角色表 yz_system_admin_role +type AdminRole struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Cid uint8 `orm:"column(cid);default(1)" json:"cid"` // 1平台角色 2租户角色 + Name string `orm:"column(name);size(32)" json:"name"` + Status uint8 `orm:"column(status);default(1)" json:"status"` + Rights *string `orm:"column(rights);type(text);null" json:"rights"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *AdminRole) TableName() string { + return "yz_system_admin_role" +} diff --git a/go/models/admin_user.go b/go/models/admin_user.go index fc10398..8581677 100644 --- a/go/models/admin_user.go +++ b/go/models/admin_user.go @@ -1,27 +1,27 @@ -package models - -import "time" - -// AdminUser 平台管理员信息表 yz_system_admin_user -type AdminUser struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Account string `orm:"column(account);size(64)" json:"account"` - Password string `orm:"column(password);size(255)" json:"-"` - Name *string `orm:"column(name);size(32);null" json:"name"` - Phone *string `orm:"column(phone);size(18);null" json:"phone"` - Email *string `orm:"column(email);size(255);null" json:"email"` - Qq *string `orm:"column(qq);size(16);null" json:"qq"` - Sex uint8 `orm:"column(sex);default(0)" json:"sex"` - Avatar *string `orm:"column(avatar);size(255);null" json:"avatar"` - RoleID uint64 `orm:"column(role_id)" json:"rid"` - LoginCount uint64 `orm:"column(login_count);default(0)" json:"login_count"` - LastLoginIP *string `orm:"column(last_login_ip);size(255);null" json:"last_login_ip"` - Status uint8 `orm:"column(status);default(1)" json:"status"` - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *AdminUser) TableName() string { - return "yz_system_admin_user" -} +package models + +import "time" + +// AdminUser 平台管理员信息表 yz_system_admin_user +type AdminUser struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Account string `orm:"column(account);size(64)" json:"account"` + Password string `orm:"column(password);size(255)" json:"-"` + Name *string `orm:"column(name);size(32);null" json:"name"` + Phone *string `orm:"column(phone);size(18);null" json:"phone"` + Email *string `orm:"column(email);size(255);null" json:"email"` + Qq *string `orm:"column(qq);size(16);null" json:"qq"` + Sex uint8 `orm:"column(sex);default(0)" json:"sex"` + Avatar *string `orm:"column(avatar);size(255);null" json:"avatar"` + RoleID uint64 `orm:"column(role_id)" json:"rid"` + LoginCount uint64 `orm:"column(login_count);default(0)" json:"login_count"` + LastLoginIP *string `orm:"column(last_login_ip);size(255);null" json:"last_login_ip"` + Status uint8 `orm:"column(status);default(1)" json:"status"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *AdminUser) TableName() string { + return "yz_system_admin_user" +} diff --git a/go/models/cms_article.go b/go/models/cms_article.go index 8ff270f..dec5c3b 100644 --- a/go/models/cms_article.go +++ b/go/models/cms_article.go @@ -1,159 +1,159 @@ -package models - -import ( - "sync" - "time" - - "github.com/beego/beego/v2/client/orm" -) - -// CmsArticleCategory CMS 文章分类 yz_cms_article_category -type CmsArticleCategory struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid uint64 `orm:"column(tid);default(0)" json:"tid"` - Cid uint64 `orm:"column(cid);default(0)" json:"cid"` - Name string `orm:"column(name);size(100)" json:"name"` - Image string `orm:"column(image);size(500);default()" json:"image"` - Desc string `orm:"column(desc);size(500);default()" json:"desc"` - Sort int `orm:"column(sort);default(0)" json:"sort"` - Status int8 `orm:"column(status);default(1)" json:"status"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *CmsArticleCategory) TableName() string { - return "yz_cms_article_category" -} - -// CmsArticle CMS 文章 yz_cms_article -type CmsArticle struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid uint64 `orm:"column(tid);default(0)" json:"tid"` - Title string `orm:"column(title);size(255)" json:"title"` - Author string `orm:"column(author);size(100);default()" json:"author"` - CateID uint64 `orm:"column(cate_id);default(0)" json:"cate_id"` - Content string `orm:"column(content);type(mediumtext);null" json:"content"` - Desc string `orm:"column(desc);size(500);default()" json:"desc"` - Image string `orm:"column(image);size(500);default()" json:"image"` - IsTrans int8 `orm:"column(is_trans);default(0)" json:"is_trans"` - TransURL *string `orm:"column(transurl);size(500);null" json:"transurl"` - Status int8 `orm:"column(status);default(0)" json:"status"` - Top int8 `orm:"column(top);default(0)" json:"top"` - Recommend int8 `orm:"column(recommend);default(0)" json:"recommend"` - Views int `orm:"column(views);default(0)" json:"views"` - Likes int `orm:"column(likes);default(0)" json:"likes"` - PublisherID *uint64 `orm:"column(publisher_id);null" json:"publisher_id"` - PublishTime *time.Time `orm:"column(publish_time);type(datetime);null" json:"publish_time"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *CmsArticle) TableName() string { - return "yz_cms_article" -} - -var cmsArticleTablesOnce sync.Once - -// EnsureCmsArticleTables 首次使用时自动建表(若不存在)。 -func EnsureCmsArticleTables() error { - var err error - cmsArticleTablesOnce.Do(func() { - _, err = Orm.Raw(` -CREATE TABLE IF NOT EXISTS yz_cms_article_category ( - id bigint unsigned NOT NULL AUTO_INCREMENT, - tid bigint unsigned NOT NULL DEFAULT 0, - cid bigint unsigned NOT NULL DEFAULT 0, - name varchar(100) NOT NULL DEFAULT '', - image varchar(500) NOT NULL DEFAULT '', - ` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '', - sort int NOT NULL DEFAULT 0, - status tinyint NOT NULL DEFAULT 1, - create_time datetime NOT NULL, - update_time datetime DEFAULT NULL, - delete_time datetime DEFAULT NULL, - PRIMARY KEY (id), - KEY idx_tid_cid (tid, cid) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec() - if err != nil { - return - } - _, err = Orm.Raw(` -CREATE TABLE IF NOT EXISTS yz_cms_article ( - id bigint unsigned NOT NULL AUTO_INCREMENT, - tid bigint unsigned NOT NULL DEFAULT 0, - title varchar(255) NOT NULL DEFAULT '', - author varchar(100) NOT NULL DEFAULT '', - cate_id bigint unsigned NOT NULL DEFAULT 0, - content mediumtext, - ` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '', - image varchar(500) NOT NULL DEFAULT '', - is_trans tinyint NOT NULL DEFAULT 0, - transurl varchar(500) DEFAULT NULL, - status tinyint NOT NULL DEFAULT 0, - top tinyint NOT NULL DEFAULT 0, - recommend tinyint NOT NULL DEFAULT 0, - views int NOT NULL DEFAULT 0, - likes int NOT NULL DEFAULT 0, - publisher_id bigint unsigned DEFAULT NULL, - publish_time datetime DEFAULT NULL, - create_time datetime NOT NULL, - update_time datetime DEFAULT NULL, - delete_time datetime DEFAULT NULL, - PRIMARY KEY (id), - KEY idx_tid_status (tid, status), - KEY idx_cate_id (cate_id) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec() - }) - return err -} - -func CmsCategoryNameMap(tid uint64, ids []uint64) map[uint64]string { - out := make(map[uint64]string) - if len(ids) == 0 { - return out - } - var rows []CmsArticleCategory - _, _ = Orm.QueryTable(new(CmsArticleCategory)). - Filter("tid", tid). - Filter("id__in", ids). - Filter("delete_time__isnull", true). - All(&rows, "ID", "Name") - for _, r := range rows { - out[r.ID] = r.Name - } - return out -} - -func CmsFormatTime(t *time.Time) string { - if t == nil { - return "" - } - return t.Format("2006-01-02 15:04:05") -} - -func CmsSimilarArticles(tid uint64, title string, limit int) ([]orm.Params, error) { - if limit <= 0 { - limit = 5 - } - var rows []CmsArticle - _, err := Orm.QueryTable(new(CmsArticle)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - Filter("title__icontains", title). - Limit(limit). - All(&rows, "ID", "Title") - if err != nil { - return nil, err - } - out := make([]orm.Params, 0, len(rows)) - for _, r := range rows { - out = append(out, orm.Params{ - "id": r.ID, - "title": r.Title, - "similarity": 80, - }) - } - return out, nil -} +package models + +import ( + "sync" + "time" + + "github.com/beego/beego/v2/client/orm" +) + +// CmsArticleCategory CMS 文章分类 yz_cms_article_category +type CmsArticleCategory struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid uint64 `orm:"column(tid);default(0)" json:"tid"` + Cid uint64 `orm:"column(cid);default(0)" json:"cid"` + Name string `orm:"column(name);size(100)" json:"name"` + Image string `orm:"column(image);size(500);default()" json:"image"` + Desc string `orm:"column(desc);size(500);default()" json:"desc"` + Sort int `orm:"column(sort);default(0)" json:"sort"` + Status int8 `orm:"column(status);default(1)" json:"status"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *CmsArticleCategory) TableName() string { + return "yz_cms_article_category" +} + +// CmsArticle CMS 文章 yz_cms_article +type CmsArticle struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid uint64 `orm:"column(tid);default(0)" json:"tid"` + Title string `orm:"column(title);size(255)" json:"title"` + Author string `orm:"column(author);size(100);default()" json:"author"` + CateID uint64 `orm:"column(cate_id);default(0)" json:"cate_id"` + Content string `orm:"column(content);type(mediumtext);null" json:"content"` + Desc string `orm:"column(desc);size(500);default()" json:"desc"` + Image string `orm:"column(image);size(500);default()" json:"image"` + IsTrans int8 `orm:"column(is_trans);default(0)" json:"is_trans"` + TransURL *string `orm:"column(transurl);size(500);null" json:"transurl"` + Status int8 `orm:"column(status);default(0)" json:"status"` + Top int8 `orm:"column(top);default(0)" json:"top"` + Recommend int8 `orm:"column(recommend);default(0)" json:"recommend"` + Views int `orm:"column(views);default(0)" json:"views"` + Likes int `orm:"column(likes);default(0)" json:"likes"` + PublisherID *uint64 `orm:"column(publisher_id);null" json:"publisher_id"` + PublishTime *time.Time `orm:"column(publish_time);type(datetime);null" json:"publish_time"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *CmsArticle) TableName() string { + return "yz_cms_article" +} + +var cmsArticleTablesOnce sync.Once + +// EnsureCmsArticleTables 首次使用时自动建表(若不存在)。 +func EnsureCmsArticleTables() error { + var err error + cmsArticleTablesOnce.Do(func() { + _, err = Orm.Raw(` +CREATE TABLE IF NOT EXISTS yz_cms_article_category ( + id bigint unsigned NOT NULL AUTO_INCREMENT, + tid bigint unsigned NOT NULL DEFAULT 0, + cid bigint unsigned NOT NULL DEFAULT 0, + name varchar(100) NOT NULL DEFAULT '', + image varchar(500) NOT NULL DEFAULT '', + ` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '', + sort int NOT NULL DEFAULT 0, + status tinyint NOT NULL DEFAULT 1, + create_time datetime NOT NULL, + update_time datetime DEFAULT NULL, + delete_time datetime DEFAULT NULL, + PRIMARY KEY (id), + KEY idx_tid_cid (tid, cid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec() + if err != nil { + return + } + _, err = Orm.Raw(` +CREATE TABLE IF NOT EXISTS yz_cms_article ( + id bigint unsigned NOT NULL AUTO_INCREMENT, + tid bigint unsigned NOT NULL DEFAULT 0, + title varchar(255) NOT NULL DEFAULT '', + author varchar(100) NOT NULL DEFAULT '', + cate_id bigint unsigned NOT NULL DEFAULT 0, + content mediumtext, + ` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '', + image varchar(500) NOT NULL DEFAULT '', + is_trans tinyint NOT NULL DEFAULT 0, + transurl varchar(500) DEFAULT NULL, + status tinyint NOT NULL DEFAULT 0, + top tinyint NOT NULL DEFAULT 0, + recommend tinyint NOT NULL DEFAULT 0, + views int NOT NULL DEFAULT 0, + likes int NOT NULL DEFAULT 0, + publisher_id bigint unsigned DEFAULT NULL, + publish_time datetime DEFAULT NULL, + create_time datetime NOT NULL, + update_time datetime DEFAULT NULL, + delete_time datetime DEFAULT NULL, + PRIMARY KEY (id), + KEY idx_tid_status (tid, status), + KEY idx_cate_id (cate_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec() + }) + return err +} + +func CmsCategoryNameMap(tid uint64, ids []uint64) map[uint64]string { + out := make(map[uint64]string) + if len(ids) == 0 { + return out + } + var rows []CmsArticleCategory + _, _ = Orm.QueryTable(new(CmsArticleCategory)). + Filter("tid", tid). + Filter("id__in", ids). + Filter("delete_time__isnull", true). + All(&rows, "ID", "Name") + for _, r := range rows { + out[r.ID] = r.Name + } + return out +} + +func CmsFormatTime(t *time.Time) string { + if t == nil { + return "" + } + return t.Format("2006-01-02 15:04:05") +} + +func CmsSimilarArticles(tid uint64, title string, limit int) ([]orm.Params, error) { + if limit <= 0 { + limit = 5 + } + var rows []CmsArticle + _, err := Orm.QueryTable(new(CmsArticle)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Filter("title__icontains", title). + Limit(limit). + All(&rows, "ID", "Title") + if err != nil { + return nil, err + } + out := make([]orm.Params, 0, len(rows)) + for _, r := range rows { + out = append(out, orm.Params{ + "id": r.ID, + "title": r.Title, + "similarity": 80, + }) + } + return out, nil +} diff --git a/go/models/complaint_category.go b/go/models/complaint_category.go index 7830da5..877e18a 100644 --- a/go/models/complaint_category.go +++ b/go/models/complaint_category.go @@ -1,19 +1,19 @@ -package models - -import "time" - -// ComplaintCategory 投诉建议产品分类 yz_system_complaint_category -type ComplaintCategory struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Name string `orm:"column(name);size(64)" json:"name"` - Code *string `orm:"column(code);size(32);null" json:"code"` - Sort int `orm:"column(sort);default(0)" json:"sort"` - Status int8 `orm:"column(status);default(1)" json:"status"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` - UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` -} - -func (c *ComplaintCategory) TableName() string { - return "yz_system_complaint_category" -} +package models + +import "time" + +// ComplaintCategory 投诉建议产品分类 yz_system_complaint_category +type ComplaintCategory struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Name string `orm:"column(name);size(64)" json:"name"` + Code *string `orm:"column(code);size(32);null" json:"code"` + Sort int `orm:"column(sort);default(0)" json:"sort"` + Status int8 `orm:"column(status);default(1)" json:"status"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` +} + +func (c *ComplaintCategory) TableName() string { + return "yz_system_complaint_category" +} diff --git a/go/models/erp.go b/go/models/erp.go index 79153e9..4e8cfe5 100644 --- a/go/models/erp.go +++ b/go/models/erp.go @@ -1,73 +1,73 @@ -package models - -import "time" - -// BackendErpOrganization 组织架构表 yz_backend_erp_organization -type BackendErpOrganization struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid uint64 `orm:"column(tid)" json:"tid"` - OrgName string `orm:"column(org_name);size(128)" json:"org_name"` - OrgCode string `orm:"column(org_code);size(64)" json:"org_code"` - ParentID uint64 `orm:"column(parent_id);default(0)" json:"parent_id"` - Sort uint `orm:"column(sort);default(0)" json:"sort"` - LeaderID *uint64 `orm:"column(leader_id);null" json:"leader_id"` - IsCompany int `orm:"column(is_company);default(0)" json:"is_company"` - Status int8 `orm:"column(status);default(1)" json:"status"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` - Remark *string `orm:"column(remark);size(512);null" json:"remark"` -} - -// TableName 自定义表名 -func (m *BackendErpOrganization) TableName() string { - return "yz_backend_erp_organization" -} - -// BackendErpEmployee 员工信息表 yz_backend_erp_employee -type BackendErpEmployee struct { - ID uint `orm:"column(id);pk;auto" json:"id"` - Tid *int `orm:"column(tid);null" json:"tid"` - Account string `orm:"column(account);size(50)" json:"account"` - Password string `orm:"column(password);size(64);default()" json:"-"` - Name string `orm:"column(name);size(30)" json:"name"` - Gender int8 `orm:"column(gender);default(0)" json:"gender"` - Birthday *time.Time `orm:"column(birthday);type(date);null" json:"birthday"` - AffiliateUnit *string `orm:"column(affiliate_unit);size(100);null" json:"affiliate_unit"` - Department *string `orm:"column(department);size(50);null" json:"department"` - Position *string `orm:"column(position);size(50);null" json:"position"` - Education *string `orm:"column(education);size(20);null" json:"education"` - Nation *string `orm:"column(nation);size(20);null" json:"nation"` - Phone *string `orm:"column(phone);size(20);null" json:"phone"` - Wechat *string `orm:"column(wechat);size(50);null" json:"wechat"` - Email *string `orm:"column(email);size(100);null" json:"email"` - HomeAddress *string `orm:"column(home_address);size(255);null" json:"home_address"` - AccountStatus int8 `orm:"column(account_status);default(1)" json:"account_status"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -// TableName 自定义表名 -func (m *BackendErpEmployee) TableName() string { - return "yz_backend_erp_employee" -} - -// BackendErpPosition 职位表 yz_backend_erp_position -type BackendErpPosition struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - TenantID uint64 `orm:"column(tenant_id)" json:"tenant_id"` - DepartmentID uint64 `orm:"column(department_id)" json:"department_id"` - PositionCode string `orm:"column(position_code);size(50)" json:"position_code"` - PositionName string `orm:"column(position_name);size(100)" json:"position_name"` - PositionType int8 `orm:"column(position_type);default(0)" json:"position_type"` - Status int8 `orm:"column(status);default(1)" json:"status"` - Sort uint `orm:"column(sort);default(0)" json:"sort"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` -} - -// TableName 自定义表名 -func (m *BackendErpPosition) TableName() string { - return "yz_backend_erp_position" -} +package models + +import "time" + +// BackendErpOrganization 组织架构表 yz_backend_erp_organization +type BackendErpOrganization struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid uint64 `orm:"column(tid)" json:"tid"` + OrgName string `orm:"column(org_name);size(128)" json:"org_name"` + OrgCode string `orm:"column(org_code);size(64)" json:"org_code"` + ParentID uint64 `orm:"column(parent_id);default(0)" json:"parent_id"` + Sort uint `orm:"column(sort);default(0)" json:"sort"` + LeaderID *uint64 `orm:"column(leader_id);null" json:"leader_id"` + IsCompany int `orm:"column(is_company);default(0)" json:"is_company"` + Status int8 `orm:"column(status);default(1)" json:"status"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` + Remark *string `orm:"column(remark);size(512);null" json:"remark"` +} + +// TableName 自定义表名 +func (m *BackendErpOrganization) TableName() string { + return "yz_backend_erp_organization" +} + +// BackendErpEmployee 员工信息表 yz_backend_erp_employee +type BackendErpEmployee struct { + ID uint `orm:"column(id);pk;auto" json:"id"` + Tid *int `orm:"column(tid);null" json:"tid"` + Account string `orm:"column(account);size(50)" json:"account"` + Password string `orm:"column(password);size(64);default()" json:"-"` + Name string `orm:"column(name);size(30)" json:"name"` + Gender int8 `orm:"column(gender);default(0)" json:"gender"` + Birthday *time.Time `orm:"column(birthday);type(date);null" json:"birthday"` + AffiliateUnit *string `orm:"column(affiliate_unit);size(100);null" json:"affiliate_unit"` + Department *string `orm:"column(department);size(50);null" json:"department"` + Position *string `orm:"column(position);size(50);null" json:"position"` + Education *string `orm:"column(education);size(20);null" json:"education"` + Nation *string `orm:"column(nation);size(20);null" json:"nation"` + Phone *string `orm:"column(phone);size(20);null" json:"phone"` + Wechat *string `orm:"column(wechat);size(50);null" json:"wechat"` + Email *string `orm:"column(email);size(100);null" json:"email"` + HomeAddress *string `orm:"column(home_address);size(255);null" json:"home_address"` + AccountStatus int8 `orm:"column(account_status);default(1)" json:"account_status"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +// TableName 自定义表名 +func (m *BackendErpEmployee) TableName() string { + return "yz_backend_erp_employee" +} + +// BackendErpPosition 职位表 yz_backend_erp_position +type BackendErpPosition struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + TenantID uint64 `orm:"column(tenant_id)" json:"tenant_id"` + DepartmentID uint64 `orm:"column(department_id)" json:"department_id"` + PositionCode string `orm:"column(position_code);size(50)" json:"position_code"` + PositionName string `orm:"column(position_name);size(100)" json:"position_name"` + PositionType int8 `orm:"column(position_type);default(0)" json:"position_type"` + Status int8 `orm:"column(status);default(1)" json:"status"` + Sort uint `orm:"column(sort);default(0)" json:"sort"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` +} + +// TableName 自定义表名 +func (m *BackendErpPosition) TableName() string { + return "yz_backend_erp_position" +} diff --git a/go/models/init.go b/go/models/init.go index 43771bf..1e6f163 100644 --- a/go/models/init.go +++ b/go/models/init.go @@ -1,81 +1,81 @@ -package models - -import ( - "fmt" - - "github.com/beego/beego/v2/client/orm" - beego "github.com/beego/beego/v2/server/web" - _ "github.com/go-sql-driver/mysql" -) - -// Orm 全局 ORM 对象,供业务层和控制器使用 -var Orm orm.Ormer - -// Init 初始化模型层资源(数据库连接、模型注册等)。 -func Init(_ string) { - // 从配置读取数据库连接信息 - user, _ := beego.AppConfig.String("mysqluser") - pass, _ := beego.AppConfig.String("mysqlpass") - urls, _ := beego.AppConfig.String("mysqlurls") - dbname, _ := beego.AppConfig.String("mysqldb") - - if user == "" || urls == "" || dbname == "" { - panic("数据库配置(mysqluser/mysqlurls/mysqldb) 未正确设置") - } - - // 组装 DSN:user:pass@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local - dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, pass, urls, dbname) - - // 注册默认数据库 - if err := orm.RegisterDataBase("default", "mysql", dsn); err != nil { - panic("注册数据库失败: " + err.Error()) - } - - // 注册模型 - orm.RegisterModel( - new(SystemTenant), - new(SystemTenantUser), - new(BackendErpOrganization), - new(BackendErpEmployee), - new(BackendErpPosition), - new(SystemMenu), - new(AdminUser), - new(AdminRole), - new(SystemFile), - new(SystemFilesCategory), - new(SystemSMSTask), - new(SystemOperationLog), - new(SystemDomainPool), - new(SystemTenantDomain), - new(SystemModules), - new(StorageConfig), - new(TenantSiteSetting), - new(ComplaintCategory), - new(PlatformComplaint), - new(SystemSoftwareUpgrade), - new(PlatformCursorEquipment), - new(PlatformCursorActivationCode), - new(PlatformCursorEquipmentIpLog), - new(PlatformAccountPoolKiro), - new(PlatformAccountPoolWindsurf), - new(PlatformAccountPoolCursor), - new(PlatformAccountPoolCodex), - new(PlatformNotebook), - - new(CmsArticleCategory), - new(CmsArticle), - - new(SystemReminderList), - - new(SystemNormalSetting), - new(PlatformNormalSetting), - new(BackendNormalSetting), - - new(PlatformSchedule), - new(PlatformScheduleReminder), - new(PlatformScheduleReminderSendLog), - ) - - // 创建全局 Ormer - Orm = orm.NewOrm() -} +package models + +import ( + "fmt" + + "github.com/beego/beego/v2/client/orm" + beego "github.com/beego/beego/v2/server/web" + _ "github.com/go-sql-driver/mysql" +) + +// Orm 全局 ORM 对象,供业务层和控制器使用 +var Orm orm.Ormer + +// Init 初始化模型层资源(数据库连接、模型注册等)。 +func Init(_ string) { + // 从配置读取数据库连接信息 + user, _ := beego.AppConfig.String("mysqluser") + pass, _ := beego.AppConfig.String("mysqlpass") + urls, _ := beego.AppConfig.String("mysqlurls") + dbname, _ := beego.AppConfig.String("mysqldb") + + if user == "" || urls == "" || dbname == "" { + panic("数据库配置(mysqluser/mysqlurls/mysqldb) 未正确设置") + } + + // 组装 DSN:user:pass@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", user, pass, urls, dbname) + + // 注册默认数据库 + if err := orm.RegisterDataBase("default", "mysql", dsn); err != nil { + panic("注册数据库失败: " + err.Error()) + } + + // 注册模型 + orm.RegisterModel( + new(SystemTenant), + new(SystemTenantUser), + new(BackendErpOrganization), + new(BackendErpEmployee), + new(BackendErpPosition), + new(SystemMenu), + new(AdminUser), + new(AdminRole), + new(SystemFile), + new(SystemFilesCategory), + new(SystemSMSTask), + new(SystemOperationLog), + new(SystemDomainPool), + new(SystemTenantDomain), + new(SystemModules), + new(StorageConfig), + new(TenantSiteSetting), + new(ComplaintCategory), + new(PlatformComplaint), + new(SystemSoftwareUpgrade), + new(PlatformCursorEquipment), + new(PlatformCursorActivationCode), + new(PlatformCursorEquipmentIpLog), + new(PlatformAccountPoolKiro), + new(PlatformAccountPoolWindsurf), + new(PlatformAccountPoolCursor), + new(PlatformAccountPoolCodex), + new(PlatformNotebook), + + new(CmsArticleCategory), + new(CmsArticle), + + new(SystemReminderList), + + new(SystemNormalSetting), + new(PlatformNormalSetting), + new(BackendNormalSetting), + + new(PlatformSchedule), + new(PlatformScheduleReminder), + new(PlatformScheduleReminderSendLog), + ) + + // 创建全局 Ormer + Orm = orm.NewOrm() +} diff --git a/go/models/platform_account_pool.go b/go/models/platform_account_pool.go index 7c6b759..56a8e5b 100644 --- a/go/models/platform_account_pool.go +++ b/go/models/platform_account_pool.go @@ -1,86 +1,86 @@ -package models - -import "time" - -// PlatformAccountPoolKiro 号池表: yz_platform_account_pool_krio -type PlatformAccountPoolKiro struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - DataType string `orm:"column(data_type);size(32)" json:"data_type"` // account | tk | account_tk - Account string `orm:"column(account);size(128);default()" json:"account"` - Password string `orm:"column(password);size(255);default()" json:"password"` - Token string `orm:"column(token);type(text);null" json:"token"` - Remark string `orm:"column(remark);size(255);default()" json:"remark"` - IsExtracted int8 `orm:"column(is_extracted);default(0)" json:"is_extracted"` - ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` - ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *PlatformAccountPoolKiro) TableName() string { - return "yz_platform_account_pool_krio" -} - -// PlatformAccountPoolCodex 号池表: yz_platform_account_pool_codex -type PlatformAccountPoolCodex struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - DataType string `orm:"column(data_type);size(32)" json:"data_type"` // account | tk | account_tk - Account string `orm:"column(account);size(128);default()" json:"account"` - Password string `orm:"column(password);size(255);default()" json:"password"` - Token string `orm:"column(token);type(text);null" json:"token"` - Remark string `orm:"column(remark);size(255);default()" json:"remark"` - IsExtracted int8 `orm:"column(is_extracted);default(0)" json:"is_extracted"` - ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` - ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *PlatformAccountPoolCodex) TableName() string { - return "yz_platform_account_pool_codex" -} - - -// PlatformAccountPoolWindsurf 号池表: yz_platform_account_pool_windsurf -type PlatformAccountPoolWindsurf struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - DataType string `orm:"column(data_type);size(32)" json:"data_type"` // account | tk | account_tk - Account string `orm:"column(account);size(128);default()" json:"account"` - Password string `orm:"column(password);size(255);default()" json:"password"` - Token string `orm:"column(token);type(text);null" json:"token"` - Remark string `orm:"column(remark);size(255);default()" json:"remark"` - IsExtracted int8 `orm:"column(is_extracted);default(0)" json:"is_extracted"` - ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` - ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *PlatformAccountPoolWindsurf) TableName() string { - return "yz_platform_account_pool_windsurf" -} - -// PlatformAccountPoolCursor 号池表: yz_platform_account_pool_cursor -type PlatformAccountPoolCursor struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - DataType string `orm:"column(data_type);size(32)" json:"data_type"` // account | tk | account_tk - Account string `orm:"column(account);size(128);default()" json:"account"` - Password string `orm:"column(password);size(255);default()" json:"password"` - Token string `orm:"column(token);type(text);null" json:"token"` - Remark string `orm:"column(remark);size(255);default()" json:"remark"` - IsExtracted int8 `orm:"column(is_extracted);default(0)" json:"is_extracted"` - IsUsed *int8 `orm:"column(is_used);null" json:"is_used"` // 0=用完/不可用 1=可用 NULL=未探测 - ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` - ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` - MachineCode string `orm:"column(machine_code);size(128);default('')" json:"machine_code"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *PlatformAccountPoolCursor) TableName() string { - return "yz_platform_account_pool_cursor" -} +package models + +import "time" + +// PlatformAccountPoolKiro 号池表: yz_platform_account_pool_krio +type PlatformAccountPoolKiro struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + DataType string `orm:"column(data_type);size(32)" json:"data_type"` // account | tk | account_tk + Account string `orm:"column(account);size(128);default()" json:"account"` + Password string `orm:"column(password);size(255);default()" json:"password"` + Token string `orm:"column(token);type(text);null" json:"token"` + Remark string `orm:"column(remark);size(255);default()" json:"remark"` + IsExtracted int8 `orm:"column(is_extracted);default(0)" json:"is_extracted"` + ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` + ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *PlatformAccountPoolKiro) TableName() string { + return "yz_platform_account_pool_krio" +} + +// PlatformAccountPoolCodex 号池表: yz_platform_account_pool_codex +type PlatformAccountPoolCodex struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + DataType string `orm:"column(data_type);size(32)" json:"data_type"` // account | tk | account_tk + Account string `orm:"column(account);size(128);default()" json:"account"` + Password string `orm:"column(password);size(255);default()" json:"password"` + Token string `orm:"column(token);type(text);null" json:"token"` + Remark string `orm:"column(remark);size(255);default()" json:"remark"` + IsExtracted int8 `orm:"column(is_extracted);default(0)" json:"is_extracted"` + ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` + ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *PlatformAccountPoolCodex) TableName() string { + return "yz_platform_account_pool_codex" +} + + +// PlatformAccountPoolWindsurf 号池表: yz_platform_account_pool_windsurf +type PlatformAccountPoolWindsurf struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + DataType string `orm:"column(data_type);size(32)" json:"data_type"` // account | tk | account_tk + Account string `orm:"column(account);size(128);default()" json:"account"` + Password string `orm:"column(password);size(255);default()" json:"password"` + Token string `orm:"column(token);type(text);null" json:"token"` + Remark string `orm:"column(remark);size(255);default()" json:"remark"` + IsExtracted int8 `orm:"column(is_extracted);default(0)" json:"is_extracted"` + ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` + ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *PlatformAccountPoolWindsurf) TableName() string { + return "yz_platform_account_pool_windsurf" +} + +// PlatformAccountPoolCursor 号池表: yz_platform_account_pool_cursor +type PlatformAccountPoolCursor struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + DataType string `orm:"column(data_type);size(32)" json:"data_type"` // account | tk | account_tk + Account string `orm:"column(account);size(128);default()" json:"account"` + Password string `orm:"column(password);size(255);default()" json:"password"` + Token string `orm:"column(token);type(text);null" json:"token"` + Remark string `orm:"column(remark);size(255);default()" json:"remark"` + IsExtracted int8 `orm:"column(is_extracted);default(0)" json:"is_extracted"` + IsUsed *int8 `orm:"column(is_used);null" json:"is_used"` // 0=用完/不可用 1=可用 NULL=未探测 + ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` + ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` + MachineCode string `orm:"column(machine_code);size(128);default('')" json:"machine_code"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *PlatformAccountPoolCursor) TableName() string { + return "yz_platform_account_pool_cursor" +} diff --git a/go/models/platform_complaint.go b/go/models/platform_complaint.go index 7bb61f1..dc3d0cb 100644 --- a/go/models/platform_complaint.go +++ b/go/models/platform_complaint.go @@ -1,26 +1,26 @@ -package models - -import "time" - -// PlatformComplaint 平台投诉建议 yz_system_platform_complaint -type PlatformComplaint struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - CategoryID uint64 `orm:"column(category_id)" json:"categoryId"` - Title string `orm:"column(title);size(200)" json:"title"` - Content string `orm:"column(content);type(text)" json:"content"` - ContactName *string `orm:"column(contact_name);size(64);null" json:"contactName"` - ContactPhone *string `orm:"column(contact_phone);size(32);null" json:"contactPhone"` - ContactEmail *string `orm:"column(contact_email);size(128);null" json:"contactEmail"` - Status int8 `orm:"column(status);default(0)" json:"status"` - ReplyContent *string `orm:"column(reply_content);type(text);null" json:"replyContent"` - ReplyTime *time.Time `orm:"column(reply_time);type(datetime);null" json:"replyTime"` - Tid *uint64 `orm:"column(tid);null" json:"tid"` - Remark *string `orm:"column(remark);size(512);null" json:"remark"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` - UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` -} - -func (p *PlatformComplaint) TableName() string { - return "yz_system_platform_complaint" -} +package models + +import "time" + +// PlatformComplaint 平台投诉建议 yz_system_platform_complaint +type PlatformComplaint struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + CategoryID uint64 `orm:"column(category_id)" json:"categoryId"` + Title string `orm:"column(title);size(200)" json:"title"` + Content string `orm:"column(content);type(text)" json:"content"` + ContactName *string `orm:"column(contact_name);size(64);null" json:"contactName"` + ContactPhone *string `orm:"column(contact_phone);size(32);null" json:"contactPhone"` + ContactEmail *string `orm:"column(contact_email);size(128);null" json:"contactEmail"` + Status int8 `orm:"column(status);default(0)" json:"status"` + ReplyContent *string `orm:"column(reply_content);type(text);null" json:"replyContent"` + ReplyTime *time.Time `orm:"column(reply_time);type(datetime);null" json:"replyTime"` + Tid *uint64 `orm:"column(tid);null" json:"tid"` + Remark *string `orm:"column(remark);size(512);null" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` +} + +func (p *PlatformComplaint) TableName() string { + return "yz_system_platform_complaint" +} diff --git a/go/models/platform_cursor_activation_code.go b/go/models/platform_cursor_activation_code.go index 26ed40f..177070a 100644 --- a/go/models/platform_cursor_activation_code.go +++ b/go/models/platform_cursor_activation_code.go @@ -1,28 +1,28 @@ -package models - -import "time" - -// PlatformCursorActivationCode Cursor 续杯激活码 yz_platform_cursor_activation_code -type PlatformCursorActivationCode struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Code string `orm:"column(code);size(128);unique" json:"code"` - Type int `orm:"column(type);default(30)" json:"type"` - Status int8 `orm:"column(status);default(0)" json:"status"` - DurationDays int `orm:"column(duration_days);default(30)" json:"durationDays"` - BindAccount *string `orm:"column(bind_account);size(128);null" json:"bindAccount"` - BindDeviceID *uint64 `orm:"column(bind_device_id);null" json:"bindDeviceId"` - MachineCode *string `orm:"column(machine_code);size(128);null" json:"machineCode"` - DeviceInfo *string `orm:"column(device_info);size(1000);null" json:"deviceInfo"` - OwnerUserID *uint64 `orm:"column(owner_user_id);null" json:"ownerUserId"` - OwnerUserName *string `orm:"column(owner_user_name);size(128);null" json:"ownerUserName"` - ActivatedAt *time.Time `orm:"column(activated_at);type(datetime);null" json:"activatedAt"` - ExpiredAt *time.Time `orm:"column(expired_at);type(datetime);null" json:"expiredAt"` - Remark *string `orm:"column(remark);size(1000);null" json:"remark"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` - UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` -} - -func (m *PlatformCursorActivationCode) TableName() string { - return "yz_platform_cursor_activation_code" -} +package models + +import "time" + +// PlatformCursorActivationCode Cursor 续杯激活码 yz_platform_cursor_activation_code +type PlatformCursorActivationCode struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Code string `orm:"column(code);size(128);unique" json:"code"` + Type int `orm:"column(type);default(30)" json:"type"` + Status int8 `orm:"column(status);default(0)" json:"status"` + DurationDays int `orm:"column(duration_days);default(30)" json:"durationDays"` + BindAccount *string `orm:"column(bind_account);size(128);null" json:"bindAccount"` + BindDeviceID *uint64 `orm:"column(bind_device_id);null" json:"bindDeviceId"` + MachineCode *string `orm:"column(machine_code);size(128);null" json:"machineCode"` + DeviceInfo *string `orm:"column(device_info);size(1000);null" json:"deviceInfo"` + OwnerUserID *uint64 `orm:"column(owner_user_id);null" json:"ownerUserId"` + OwnerUserName *string `orm:"column(owner_user_name);size(128);null" json:"ownerUserName"` + ActivatedAt *time.Time `orm:"column(activated_at);type(datetime);null" json:"activatedAt"` + ExpiredAt *time.Time `orm:"column(expired_at);type(datetime);null" json:"expiredAt"` + Remark *string `orm:"column(remark);size(1000);null" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` +} + +func (m *PlatformCursorActivationCode) TableName() string { + return "yz_platform_cursor_activation_code" +} diff --git a/go/models/platform_cursor_equipment.go b/go/models/platform_cursor_equipment.go index 9b9e425..e250d60 100644 --- a/go/models/platform_cursor_equipment.go +++ b/go/models/platform_cursor_equipment.go @@ -1,27 +1,27 @@ -package models - -import "time" - -// PlatformCursorEquipment Cursor 设备管理 yz_platform_cursor_equipment -type PlatformCursorEquipment struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - DeviceInfo *string `orm:"column(device_info);size(1000);null" json:"deviceInfo"` - MachineCode string `orm:"column(machine_code);size(128);unique" json:"machineCode"` - Status int8 `orm:"column(status);default(0)" json:"status"` - System *string `orm:"column(system);size(64);null" json:"system"` - Version *string `orm:"column(version);size(64);null" json:"version"` - BindAccount *string `orm:"column(bind_account);size(128);null" json:"bindAccount"` - OwnerUserID *uint64 `orm:"column(owner_user_id);null" json:"ownerUserId"` - OwnerUserName *string `orm:"column(owner_user_name);size(128);null" json:"ownerUserName"` - ActivationTime *time.Time `orm:"column(activation_time);type(datetime);null" json:"activationTime"` - ExpireTime *time.Time `orm:"column(expire_time);type(datetime);null" json:"expireTime"` - Remark *string `orm:"column(remark);size(1000);null" json:"remark"` - LastHeartbeatAt *time.Time `orm:"column(last_heartbeat_at);type(datetime);null" json:"lastHeartbeatAt"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` - UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` -} - -func (m *PlatformCursorEquipment) TableName() string { - return "yz_platform_cursor_equipment" -} +package models + +import "time" + +// PlatformCursorEquipment Cursor 设备管理 yz_platform_cursor_equipment +type PlatformCursorEquipment struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + DeviceInfo *string `orm:"column(device_info);size(1000);null" json:"deviceInfo"` + MachineCode string `orm:"column(machine_code);size(128);unique" json:"machineCode"` + Status int8 `orm:"column(status);default(0)" json:"status"` + System *string `orm:"column(system);size(64);null" json:"system"` + Version *string `orm:"column(version);size(64);null" json:"version"` + BindAccount *string `orm:"column(bind_account);size(128);null" json:"bindAccount"` + OwnerUserID *uint64 `orm:"column(owner_user_id);null" json:"ownerUserId"` + OwnerUserName *string `orm:"column(owner_user_name);size(128);null" json:"ownerUserName"` + ActivationTime *time.Time `orm:"column(activation_time);type(datetime);null" json:"activationTime"` + ExpireTime *time.Time `orm:"column(expire_time);type(datetime);null" json:"expireTime"` + Remark *string `orm:"column(remark);size(1000);null" json:"remark"` + LastHeartbeatAt *time.Time `orm:"column(last_heartbeat_at);type(datetime);null" json:"lastHeartbeatAt"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` +} + +func (m *PlatformCursorEquipment) TableName() string { + return "yz_platform_cursor_equipment" +} diff --git a/go/models/platform_login_verify.go b/go/models/platform_login_verify.go index 2ed40cb..f233d51 100644 --- a/go/models/platform_login_verify.go +++ b/go/models/platform_login_verify.go @@ -1,132 +1,132 @@ -package models - -import "time" - -// PlatformLoginVerify 平台登录验证配置(单行配置) -type PlatformLoginVerify struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - OpenVerifyEnabled int8 `orm:"column(open_verify_enabled);default(1)" json:"openVerify_enabled"` // 0关闭 1开启 - VerifyType string `orm:"column(verify_type);size(20);default(captcha)" json:"verify_type"` // captcha/sms/geetest/email - Geetest3ID *string `orm:"column(geetest3_id);size(128);null" json:"geetest3_id"` - Geetest3Key *string `orm:"column(geetest3_key);size(255);null" json:"geetest3_key"` - Geetest4ID *string `orm:"column(geetest4_id);size(128);null" json:"geetest4_id"` - Geetest4Key *string `orm:"column(geetest4_key);size(255);null" json:"geetest4_key"` - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` -} - -func GetPlatformLoginVerify() (*PlatformLoginVerify, error) { - // 从 yz_platform_normal_setting 表中按 code 获取各个配置 - enabledStr := GetPlatformSettingValue("login_verify_enabled", "1") - verifyType := GetPlatformSettingValue("login_verify_type", "captcha") - geetest3ID := GetPlatformSettingValue("login_verify_geetest3_id", "") - geetest3Key := GetPlatformSettingValue("login_verify_geetest3_key", "") - geetest4ID := GetPlatformSettingValue("login_verify_geetest4_id", "") - geetest4Key := GetPlatformSettingValue("login_verify_geetest4_key", "") - - openVerifyEnabled := int8(1) - if enabledStr == "0" { - openVerifyEnabled = 0 - } - - cfg := &PlatformLoginVerify{ - OpenVerifyEnabled: openVerifyEnabled, - VerifyType: verifyType, - } - if geetest3ID != "" { - cfg.Geetest3ID = &geetest3ID - } - if geetest3Key != "" { - cfg.Geetest3Key = &geetest3Key - } - if geetest4ID != "" { - cfg.Geetest4ID = &geetest4ID - } - if geetest4Key != "" { - cfg.Geetest4Key = &geetest4Key - } - - return cfg, nil -} - -func GetPlatformSettingValue(code string, defaultVal string) string { - var setting PlatformNormalSetting - err := Orm.QueryTable(new(PlatformNormalSetting)). - Filter("code", code). - Filter("delete_time__isnull", true). - One(&setting) - if err != nil { - return defaultVal - } - return setting.Value -} - -func SavePlatformLoginVerify(cfg *PlatformLoginVerify) error { - openVerifyEnabledStr := "1" - if cfg.OpenVerifyEnabled == 0 { - openVerifyEnabledStr = "0" - } - geetest3ID := "" - if cfg.Geetest3ID != nil { - geetest3ID = *cfg.Geetest3ID - } - geetest3Key := "" - if cfg.Geetest3Key != nil { - geetest3Key = *cfg.Geetest3Key - } - geetest4ID := "" - if cfg.Geetest4ID != nil { - geetest4ID = *cfg.Geetest4ID - } - geetest4Key := "" - if cfg.Geetest4Key != nil { - geetest4Key = *cfg.Geetest4Key - } - - settings := []struct { - code string - name string - value string - remark string - }{ - {"login_verify_enabled", "登录验证开启状态", openVerifyEnabledStr, "0为关闭,1为开启"}, - {"login_verify_type", "登录验证类型", cfg.VerifyType, "支持 captcha/sms/geetest/email"}, - {"login_verify_geetest3_id", "极验3 ID", geetest3ID, ""}, - {"login_verify_geetest3_key", "极验3 Key", geetest3Key, ""}, - {"login_verify_geetest4_id", "极验4 ID", geetest4ID, ""}, - {"login_verify_geetest4_key", "极验4 Key", geetest4Key, ""}, - } - - for _, item := range settings { - var setting PlatformNormalSetting - err := Orm.QueryTable(new(PlatformNormalSetting)). - Filter("code", item.code). - Filter("delete_time__isnull", true). - One(&setting) - if err == nil { - setting.Value = item.value - setting.Name = item.name - setting.Remark = item.remark - now := time.Now() - setting.UpdateTime = &now - _, err = Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") - if err != nil { - return err - } - } else { - newSetting := PlatformNormalSetting{ - Name: item.name, - Code: item.code, - Value: item.value, - Remark: item.remark, - CreateTime: time.Now(), - } - _, err = Orm.Insert(&newSetting) - if err != nil { - return err - } - } - } - return nil -} - +package models + +import "time" + +// PlatformLoginVerify 平台登录验证配置(单行配置) +type PlatformLoginVerify struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + OpenVerifyEnabled int8 `orm:"column(open_verify_enabled);default(1)" json:"openVerify_enabled"` // 0关闭 1开启 + VerifyType string `orm:"column(verify_type);size(20);default(captcha)" json:"verify_type"` // captcha/sms/geetest/email + Geetest3ID *string `orm:"column(geetest3_id);size(128);null" json:"geetest3_id"` + Geetest3Key *string `orm:"column(geetest3_key);size(255);null" json:"geetest3_key"` + Geetest4ID *string `orm:"column(geetest4_id);size(128);null" json:"geetest4_id"` + Geetest4Key *string `orm:"column(geetest4_key);size(255);null" json:"geetest4_key"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` +} + +func GetPlatformLoginVerify() (*PlatformLoginVerify, error) { + // 从 yz_platform_normal_setting 表中按 code 获取各个配置 + enabledStr := GetPlatformSettingValue("login_verify_enabled", "1") + verifyType := GetPlatformSettingValue("login_verify_type", "captcha") + geetest3ID := GetPlatformSettingValue("login_verify_geetest3_id", "") + geetest3Key := GetPlatformSettingValue("login_verify_geetest3_key", "") + geetest4ID := GetPlatformSettingValue("login_verify_geetest4_id", "") + geetest4Key := GetPlatformSettingValue("login_verify_geetest4_key", "") + + openVerifyEnabled := int8(1) + if enabledStr == "0" { + openVerifyEnabled = 0 + } + + cfg := &PlatformLoginVerify{ + OpenVerifyEnabled: openVerifyEnabled, + VerifyType: verifyType, + } + if geetest3ID != "" { + cfg.Geetest3ID = &geetest3ID + } + if geetest3Key != "" { + cfg.Geetest3Key = &geetest3Key + } + if geetest4ID != "" { + cfg.Geetest4ID = &geetest4ID + } + if geetest4Key != "" { + cfg.Geetest4Key = &geetest4Key + } + + return cfg, nil +} + +func GetPlatformSettingValue(code string, defaultVal string) string { + var setting PlatformNormalSetting + err := Orm.QueryTable(new(PlatformNormalSetting)). + Filter("code", code). + Filter("delete_time__isnull", true). + One(&setting) + if err != nil { + return defaultVal + } + return setting.Value +} + +func SavePlatformLoginVerify(cfg *PlatformLoginVerify) error { + openVerifyEnabledStr := "1" + if cfg.OpenVerifyEnabled == 0 { + openVerifyEnabledStr = "0" + } + geetest3ID := "" + if cfg.Geetest3ID != nil { + geetest3ID = *cfg.Geetest3ID + } + geetest3Key := "" + if cfg.Geetest3Key != nil { + geetest3Key = *cfg.Geetest3Key + } + geetest4ID := "" + if cfg.Geetest4ID != nil { + geetest4ID = *cfg.Geetest4ID + } + geetest4Key := "" + if cfg.Geetest4Key != nil { + geetest4Key = *cfg.Geetest4Key + } + + settings := []struct { + code string + name string + value string + remark string + }{ + {"login_verify_enabled", "登录验证开启状态", openVerifyEnabledStr, "0为关闭,1为开启"}, + {"login_verify_type", "登录验证类型", cfg.VerifyType, "支持 captcha/sms/geetest/email"}, + {"login_verify_geetest3_id", "极验3 ID", geetest3ID, ""}, + {"login_verify_geetest3_key", "极验3 Key", geetest3Key, ""}, + {"login_verify_geetest4_id", "极验4 ID", geetest4ID, ""}, + {"login_verify_geetest4_key", "极验4 Key", geetest4Key, ""}, + } + + for _, item := range settings { + var setting PlatformNormalSetting + err := Orm.QueryTable(new(PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + return err + } + } else { + newSetting := PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = Orm.Insert(&newSetting) + if err != nil { + return err + } + } + } + return nil +} + diff --git a/go/models/platform_notebook.go b/go/models/platform_notebook.go index e3ec585..6185fe8 100644 --- a/go/models/platform_notebook.go +++ b/go/models/platform_notebook.go @@ -1,20 +1,20 @@ -package models - -import "time" - -// PlatformNotebook 平台记事本表: yz_platform_notebook -type PlatformNotebook struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Title string `orm:"column(title);size(255)" json:"title"` - Content string `orm:"column(content);type(longtext);null" json:"content"` - UserID *uint64 `orm:"column(user_id);null" json:"user_id"` - UserName *string `orm:"column(user_name);size(100);null" json:"user_name"` - IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *PlatformNotebook) TableName() string { - return "yz_platform_notebook" -} +package models + +import "time" + +// PlatformNotebook 平台记事本表: yz_platform_notebook +type PlatformNotebook struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Title string `orm:"column(title);size(255)" json:"title"` + Content string `orm:"column(content);type(longtext);null" json:"content"` + UserID *uint64 `orm:"column(user_id);null" json:"user_id"` + UserName *string `orm:"column(user_name);size(100);null" json:"user_name"` + IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *PlatformNotebook) TableName() string { + return "yz_platform_notebook" +} diff --git a/go/models/platform_schedule_reminder.go b/go/models/platform_schedule_reminder.go index 8e96596..5332985 100644 --- a/go/models/platform_schedule_reminder.go +++ b/go/models/platform_schedule_reminder.go @@ -1,55 +1,55 @@ -package models - -import "time" - -// PlatformSchedule 日程主表: yz_platform_schedule -type PlatformSchedule struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Title string `orm:"column(title);size(255)" json:"title"` - Content string `orm:"column(content);type(text)" json:"content"` - ScheduleTime time.Time `orm:"column(schedule_time);type(datetime)" json:"schedule_time"` - UserID uint64 `orm:"column(user_id)" json:"user_id"` -} - -func (m *PlatformSchedule) TableName() string { - return "yz_platform_schedule" -} - -// PlatformScheduleReminder 日程提醒主表: yz_platform_schedule_reminder -type PlatformScheduleReminder struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - ScheduleID uint64 `orm:"column(schedule_id)" json:"schedule_id"` - RemindChannel string `orm:"column(remind_channel);size(20)" json:"remind_channel"` // SMS/EMAIL/BARK/SITE_MSG - AdvanceMinutes int `orm:"column(advance_minutes);default(0)" json:"advance_minutes"` - RepeatIntervalMinutes int `orm:"column(repeat_interval_minutes);default(0)" json:"repeat_interval_minutes"` - NextRemindTime time.Time `orm:"column(next_remind_time);type(datetime)" json:"next_remind_time"` - SendCount int `orm:"column(send_count);default(0)" json:"send_count"` - MaxSendCount int `orm:"column(max_send_count);default(1)" json:"max_send_count"` - AckToken *string `orm:"column(ack_token);size(64);null" json:"ack_token"` - AckStatus int8 `orm:"column(ack_status);default(0)" json:"ack_status"` // 0-未确认 1-已确认 - AckTime *time.Time `orm:"column(ack_time);type(datetime);null" json:"ack_time"` - ReceiverUserID uint64 `orm:"column(receiver_user_id)" json:"receiver_user_id"` - ReceiverTarget *string `orm:"column(receiver_target);size(255);null" json:"receiver_target"` - RemindStatus int8 `orm:"column(remind_status);default(0)" json:"remind_status"` // 0-待提醒 1-提醒中 2-已结束 - ScanLock string `orm:"column(scan_lock);size(64);default('')" json:"scan_lock"` - IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` -} - -func (m *PlatformScheduleReminder) TableName() string { - return "yz_platform_schedule_reminder" -} - -// PlatformScheduleReminderSendLog 提醒实际发送流水: yz_platform_schedule_reminder_send_log -type PlatformScheduleReminderSendLog struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - ReminderID uint64 `orm:"column(reminder_id)" json:"reminder_id"` - SendTime time.Time `orm:"column(send_time);type(datetime)" json:"send_time"` - SendResult int8 `orm:"column(send_result)" json:"send_result"` // 0-失败 1-成功 - FailReason *string `orm:"column(fail_reason);size(255);null" json:"fail_reason"` -} - -func (m *PlatformScheduleReminderSendLog) TableName() string { - return "yz_platform_schedule_reminder_send_log" -} +package models + +import "time" + +// PlatformSchedule 日程主表: yz_platform_schedule +type PlatformSchedule struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Title string `orm:"column(title);size(255)" json:"title"` + Content string `orm:"column(content);type(text)" json:"content"` + ScheduleTime time.Time `orm:"column(schedule_time);type(datetime)" json:"schedule_time"` + UserID uint64 `orm:"column(user_id)" json:"user_id"` +} + +func (m *PlatformSchedule) TableName() string { + return "yz_platform_schedule" +} + +// PlatformScheduleReminder 日程提醒主表: yz_platform_schedule_reminder +type PlatformScheduleReminder struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + ScheduleID uint64 `orm:"column(schedule_id)" json:"schedule_id"` + RemindChannel string `orm:"column(remind_channel);size(20)" json:"remind_channel"` // SMS/EMAIL/BARK/SITE_MSG + AdvanceMinutes int `orm:"column(advance_minutes);default(0)" json:"advance_minutes"` + RepeatIntervalMinutes int `orm:"column(repeat_interval_minutes);default(0)" json:"repeat_interval_minutes"` + NextRemindTime time.Time `orm:"column(next_remind_time);type(datetime)" json:"next_remind_time"` + SendCount int `orm:"column(send_count);default(0)" json:"send_count"` + MaxSendCount int `orm:"column(max_send_count);default(1)" json:"max_send_count"` + AckToken *string `orm:"column(ack_token);size(64);null" json:"ack_token"` + AckStatus int8 `orm:"column(ack_status);default(0)" json:"ack_status"` // 0-未确认 1-已确认 + AckTime *time.Time `orm:"column(ack_time);type(datetime);null" json:"ack_time"` + ReceiverUserID uint64 `orm:"column(receiver_user_id)" json:"receiver_user_id"` + ReceiverTarget *string `orm:"column(receiver_target);size(255);null" json:"receiver_target"` + RemindStatus int8 `orm:"column(remind_status);default(0)" json:"remind_status"` // 0-待提醒 1-提醒中 2-已结束 + ScanLock string `orm:"column(scan_lock);size(64);default('')" json:"scan_lock"` + IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` +} + +func (m *PlatformScheduleReminder) TableName() string { + return "yz_platform_schedule_reminder" +} + +// PlatformScheduleReminderSendLog 提醒实际发送流水: yz_platform_schedule_reminder_send_log +type PlatformScheduleReminderSendLog struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + ReminderID uint64 `orm:"column(reminder_id)" json:"reminder_id"` + SendTime time.Time `orm:"column(send_time);type(datetime)" json:"send_time"` + SendResult int8 `orm:"column(send_result)" json:"send_result"` // 0-失败 1-成功 + FailReason *string `orm:"column(fail_reason);size(255);null" json:"fail_reason"` +} + +func (m *PlatformScheduleReminderSendLog) TableName() string { + return "yz_platform_schedule_reminder_send_log" +} diff --git a/go/models/storage_config.go b/go/models/storage_config.go index c72f948..d3d6a55 100644 --- a/go/models/storage_config.go +++ b/go/models/storage_config.go @@ -1,35 +1,35 @@ -package models - -import "time" - -// StorageConfig 存储配置(单行配置) -type StorageConfig struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - StorageType string `orm:"column(storage_type);size(20);default(local)" json:"storage_type"` // local/qiniu - // 七牛云配置 - QiniuAccessKey string `orm:"column(qiniu_access_key);size(255);null" json:"qiniu_access_key"` - QiniuSecretKey string `orm:"column(qiniu_secret_key);size(255);null" json:"qiniu_secret_key"` - QiniuBucket string `orm:"column(qiniu_bucket);size(128);null" json:"qiniu_bucket"` - QiniuDomain string `orm:"column(qiniu_domain);size(255);null" json:"qiniu_domain"` // CDN域名 - QiniuRegion string `orm:"column(qiniu_region);size(50);null" json:"qiniu_region"` // 存储区域 - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` -} - -func (m *StorageConfig) TableName() string { - return "yz_system_storage_config" -} - -// GetStorageConfig 获取存储配置 -func GetStorageConfig() (*StorageConfig, error) { - var cfg StorageConfig - err := Orm.QueryTable(new(StorageConfig)).OrderBy("-id").One(&cfg) - if err != nil { - // 默认配置:本地存储 - return &StorageConfig{StorageType: "local"}, nil - } - if cfg.StorageType == "" { - cfg.StorageType = "local" - } - return &cfg, nil -} +package models + +import "time" + +// StorageConfig 存储配置(单行配置) +type StorageConfig struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + StorageType string `orm:"column(storage_type);size(20);default(local)" json:"storage_type"` // local/qiniu + // 七牛云配置 + QiniuAccessKey string `orm:"column(qiniu_access_key);size(255);null" json:"qiniu_access_key"` + QiniuSecretKey string `orm:"column(qiniu_secret_key);size(255);null" json:"qiniu_secret_key"` + QiniuBucket string `orm:"column(qiniu_bucket);size(128);null" json:"qiniu_bucket"` + QiniuDomain string `orm:"column(qiniu_domain);size(255);null" json:"qiniu_domain"` // CDN域名 + QiniuRegion string `orm:"column(qiniu_region);size(50);null" json:"qiniu_region"` // 存储区域 + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` +} + +func (m *StorageConfig) TableName() string { + return "yz_system_storage_config" +} + +// GetStorageConfig 获取存储配置 +func GetStorageConfig() (*StorageConfig, error) { + var cfg StorageConfig + err := Orm.QueryTable(new(StorageConfig)).OrderBy("-id").One(&cfg) + if err != nil { + // 默认配置:本地存储 + return &StorageConfig{StorageType: "local"}, nil + } + if cfg.StorageType == "" { + cfg.StorageType = "local" + } + return &cfg, nil +} diff --git a/go/models/system_domain_pool.go b/go/models/system_domain_pool.go index 6ddaf33..bc2963f 100644 --- a/go/models/system_domain_pool.go +++ b/go/models/system_domain_pool.go @@ -1,17 +1,17 @@ -package models - -import "time" - -// SystemDomainPool 主域名池表 yz_system_domain_pool -type SystemDomainPool struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - MainDomain string `orm:"column(main_domain);size(255)" json:"main_domain"` - Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用 0禁用 - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemDomainPool) TableName() string { - return "yz_system_domain_pool" -} +package models + +import "time" + +// SystemDomainPool 主域名池表 yz_system_domain_pool +type SystemDomainPool struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + MainDomain string `orm:"column(main_domain);size(255)" json:"main_domain"` + Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用 0禁用 + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemDomainPool) TableName() string { + return "yz_system_domain_pool" +} diff --git a/go/models/system_email.go b/go/models/system_email.go index 397a15c..da8b56d 100644 --- a/go/models/system_email.go +++ b/go/models/system_email.go @@ -1,21 +1,21 @@ -package models - -import "time" - -// SystemEmail 系统邮箱配置表 yz_system_email -type SystemEmail struct { - ID uint `orm:"column(id);pk;auto" json:"id"` - FromAddress string `orm:"column(from_address);size(191)" json:"from_address"` - FromName *string `orm:"column(from_name);size(191);null" json:"from_name"` - Host string `orm:"column(host);size(191)" json:"host"` - Port uint `orm:"column(port);default(465)" json:"port"` - Password string `orm:"column(password);size(255)" json:"password"` - Encryption string `orm:"column(encryption);size(8)" json:"encryption"` // ssl / tls / none - Timeout uint `orm:"column(timeout);default(30)" json:"timeout"` - Status int8 `orm:"column(status);default(1)" json:"status"` - Remark *string `orm:"column(remark);size(255);null" json:"remark"` - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add;null" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` -} - - +package models + +import "time" + +// SystemEmail 系统邮箱配置表 yz_system_email +type SystemEmail struct { + ID uint `orm:"column(id);pk;auto" json:"id"` + FromAddress string `orm:"column(from_address);size(191)" json:"from_address"` + FromName *string `orm:"column(from_name);size(191);null" json:"from_name"` + Host string `orm:"column(host);size(191)" json:"host"` + Port uint `orm:"column(port);default(465)" json:"port"` + Password string `orm:"column(password);size(255)" json:"password"` + Encryption string `orm:"column(encryption);size(8)" json:"encryption"` // ssl / tls / none + Timeout uint `orm:"column(timeout);default(30)" json:"timeout"` + Status int8 `orm:"column(status);default(1)" json:"status"` + Remark *string `orm:"column(remark);size(255);null" json:"remark"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add;null" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` +} + + diff --git a/go/models/system_file.go b/go/models/system_file.go index 396e74a..e7d0c40 100644 --- a/go/models/system_file.go +++ b/go/models/system_file.go @@ -1,25 +1,25 @@ -package models - -import "time" - -// SystemFile 附件表 yz_system_files -type SystemFile struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid uint64 `orm:"column(tid)" json:"tid"` - Uid *uint64 `orm:"column(uid);null" json:"uid"` - Tuid *uint64 `orm:"column(tuid);null" json:"tuid"` - Name string `orm:"column(name);size(255)" json:"name"` - Type uint8 `orm:"column(type);default(2)" json:"type"` - Cate uint64 `orm:"column(cate);default(0)" json:"cate"` - Size uint64 `orm:"column(size);default(0)" json:"size"` - Src string `orm:"column(src);size(512)" json:"src"` - Uploader uint64 `orm:"column(uploader);default(0)" json:"uploader"` - Md5 string `orm:"column(md5);size(32)" json:"md5"` - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null;auto_now" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemFile) TableName() string { - return "yz_system_files" -} +package models + +import "time" + +// SystemFile 附件表 yz_system_files +type SystemFile struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid uint64 `orm:"column(tid)" json:"tid"` + Uid *uint64 `orm:"column(uid);null" json:"uid"` + Tuid *uint64 `orm:"column(tuid);null" json:"tuid"` + Name string `orm:"column(name);size(255)" json:"name"` + Type uint8 `orm:"column(type);default(2)" json:"type"` + Cate uint64 `orm:"column(cate);default(0)" json:"cate"` + Size uint64 `orm:"column(size);default(0)" json:"size"` + Src string `orm:"column(src);size(512)" json:"src"` + Uploader uint64 `orm:"column(uploader);default(0)" json:"uploader"` + Md5 string `orm:"column(md5);size(32)" json:"md5"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null;auto_now" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemFile) TableName() string { + return "yz_system_files" +} diff --git a/go/models/system_files_category.go b/go/models/system_files_category.go index 23a499c..860c2fb 100644 --- a/go/models/system_files_category.go +++ b/go/models/system_files_category.go @@ -1,19 +1,19 @@ -package models - -import "time" - -// SystemFilesCategory 文件分类表 yz_system_files_category -type SystemFilesCategory struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid uint64 `orm:"column(tid)" json:"tid"` - Uid *uint64 `orm:"column(uid);null" json:"uid"` - Tuid *uint64 `orm:"column(tuid);null" json:"tuid"` - Name string `orm:"column(name);size(128)" json:"name"` - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null;auto_now" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemFilesCategory) TableName() string { - return "yz_system_files_category" -} +package models + +import "time" + +// SystemFilesCategory 文件分类表 yz_system_files_category +type SystemFilesCategory struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid uint64 `orm:"column(tid)" json:"tid"` + Uid *uint64 `orm:"column(uid);null" json:"uid"` + Tuid *uint64 `orm:"column(tuid);null" json:"tuid"` + Name string `orm:"column(name);size(128)" json:"name"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null;auto_now" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemFilesCategory) TableName() string { + return "yz_system_files_category" +} diff --git a/go/models/system_menu.go b/go/models/system_menu.go index 757cc8e..2902cda 100644 --- a/go/models/system_menu.go +++ b/go/models/system_menu.go @@ -1,30 +1,30 @@ -package models - -import "time" - -// SystemMenu 系统菜单表 yz_system_menu -type SystemMenu struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` // 菜单ID - Pid int64 `orm:"column(pid);default(0)" json:"pid"` // 上级菜单ID - Title string `orm:"column(title);size(50)" json:"title"` // 菜单名称 - Path *string `orm:"column(path);size(200);null" json:"path"` // 路由路径 - ComponentPath *string `orm:"column(component_path);size(255);null" json:"componentPath"` // 组件路径 - Icon *string `orm:"column(icon);size(100);null" json:"icon"` // 菜单图标 - Sort int64 `orm:"column(sort);default(0)" json:"sort"` // 排序号 - Status int8 `orm:"column(status);default(0)" json:"status"` // 状态:1-启用,0-禁用 - IsVisible *int8 `orm:"column(is_visible);null" json:"isVisible"` // 是否显示:1-显示 0-不显示 - Views *string `orm:"column(views);size(255);null" json:"views"` // 菜单显示端(JSON数组字符串):[1]=平台端 [2]=租户端 [1,2]=双端 - Type int8 `orm:"column(type)" json:"type"` // 菜单类型:1-目录,2-页面,3-接口 - Permission *string `orm:"column(permission);size(100);null" json:"permission"` // 权限标识(按钮类型时填写) - Creater *string `orm:"column(creater);size(50);null" json:"creater"` // 创建者 - Remark *string `orm:"column(remark);size(500);null" json:"remark"` // 备注 - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` // 创建时间 - UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` // 更新时间 - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` // 删除时间 -} - -// TableName 自定义表名 -func (m *SystemMenu) TableName() string { - return "yz_system_menu" -} - +package models + +import "time" + +// SystemMenu 系统菜单表 yz_system_menu +type SystemMenu struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` // 菜单ID + Pid int64 `orm:"column(pid);default(0)" json:"pid"` // 上级菜单ID + Title string `orm:"column(title);size(50)" json:"title"` // 菜单名称 + Path *string `orm:"column(path);size(200);null" json:"path"` // 路由路径 + ComponentPath *string `orm:"column(component_path);size(255);null" json:"componentPath"` // 组件路径 + Icon *string `orm:"column(icon);size(100);null" json:"icon"` // 菜单图标 + Sort int64 `orm:"column(sort);default(0)" json:"sort"` // 排序号 + Status int8 `orm:"column(status);default(0)" json:"status"` // 状态:1-启用,0-禁用 + IsVisible *int8 `orm:"column(is_visible);null" json:"isVisible"` // 是否显示:1-显示 0-不显示 + Views *string `orm:"column(views);size(255);null" json:"views"` // 菜单显示端(JSON数组字符串):[1]=平台端 [2]=租户端 [1,2]=双端 + Type int8 `orm:"column(type)" json:"type"` // 菜单类型:1-目录,2-页面,3-接口 + Permission *string `orm:"column(permission);size(100);null" json:"permission"` // 权限标识(按钮类型时填写) + Creater *string `orm:"column(creater);size(50);null" json:"creater"` // 创建者 + Remark *string `orm:"column(remark);size(500);null" json:"remark"` // 备注 + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` // 创建时间 + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` // 更新时间 + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` // 删除时间 +} + +// TableName 自定义表名 +func (m *SystemMenu) TableName() string { + return "yz_system_menu" +} + diff --git a/go/models/system_modules.go b/go/models/system_modules.go index a520b81..1f067c0 100644 --- a/go/models/system_modules.go +++ b/go/models/system_modules.go @@ -1,25 +1,25 @@ -package models - -import "time" - -// SystemModules 系统模块表 yz_system_modules -type SystemModules struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Mid *uint64 `orm:"column(mid);null" json:"mid"` - Name string `orm:"column(name);size(50)" json:"name"` - Code string `orm:"column(code);size(50)" json:"code"` - Path string `orm:"column(path);size(100)" json:"path"` - Icon string `orm:"column(icon);size(50)" json:"icon"` - Description string `orm:"column(description);size(255)" json:"description"` - Type int `orm:"column(type);default(0)" json:"type"` // 0未分类 1功能模块 2系统配置 - Sort int `orm:"column(sort);default(0)" json:"sort"` - Status int8 `orm:"column(status);default(1)" json:"status"` // 0禁用 1启用 - IsShow int8 `orm:"column(is_show);default(1)" json:"is_show"` // 0否 1是 - CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemModules) TableName() string { - return "yz_system_modules" -} +package models + +import "time" + +// SystemModules 系统模块表 yz_system_modules +type SystemModules struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Mid *uint64 `orm:"column(mid);null" json:"mid"` + Name string `orm:"column(name);size(50)" json:"name"` + Code string `orm:"column(code);size(50)" json:"code"` + Path string `orm:"column(path);size(100)" json:"path"` + Icon string `orm:"column(icon);size(50)" json:"icon"` + Description string `orm:"column(description);size(255)" json:"description"` + Type int `orm:"column(type);default(0)" json:"type"` // 0未分类 1功能模块 2系统配置 + Sort int `orm:"column(sort);default(0)" json:"sort"` + Status int8 `orm:"column(status);default(1)" json:"status"` // 0禁用 1启用 + IsShow int8 `orm:"column(is_show);default(1)" json:"is_show"` // 0否 1是 + CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemModules) TableName() string { + return "yz_system_modules" +} diff --git a/go/models/system_normal_setting.go b/go/models/system_normal_setting.go index 61037cc..2749066 100644 --- a/go/models/system_normal_setting.go +++ b/go/models/system_normal_setting.go @@ -1,51 +1,51 @@ -package models - -import "time" - -// SystemNormalSetting 系统通用配置表: yz_system_normal_setting -type SystemNormalSetting struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Name string `orm:"column(name);size(128);default('')" json:"name"` - Value string `orm:"column(value);type(text);null" json:"value"` - Code string `orm:"column(code);size(64);default('')" json:"code"` - Remark string `orm:"column(remark);size(255);default('')" json:"remark"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemNormalSetting) TableName() string { - return "yz_system_normal_setting" -} - -// PlatformNormalSetting 平台通用配置表: yz_platform_normal_setting -type PlatformNormalSetting struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Name string `orm:"column(name);size(128);default('')" json:"name"` - Value string `orm:"column(value);type(text);null" json:"value"` - Code string `orm:"column(code);size(64);default('')" json:"code"` - Remark string `orm:"column(remark);size(255);default('')" json:"remark"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *PlatformNormalSetting) TableName() string { - return "yz_platform_normal_setting" -} - -// BackendNormalSetting 管理端通用配置表: yz_backend_normal_setting -type BackendNormalSetting struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Name string `orm:"column(name);size(128);default('')" json:"name"` - Value string `orm:"column(value);type(text);null" json:"value"` - Code string `orm:"column(code);size(64);default('')" json:"code"` - Remark string `orm:"column(remark);size(255);default('')" json:"remark"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *BackendNormalSetting) TableName() string { - return "yz_backend_normal_setting" -} +package models + +import "time" + +// SystemNormalSetting 系统通用配置表: yz_system_normal_setting +type SystemNormalSetting struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Name string `orm:"column(name);size(128);default('')" json:"name"` + Value string `orm:"column(value);type(text);null" json:"value"` + Code string `orm:"column(code);size(64);default('')" json:"code"` + Remark string `orm:"column(remark);size(255);default('')" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemNormalSetting) TableName() string { + return "yz_system_normal_setting" +} + +// PlatformNormalSetting 平台通用配置表: yz_platform_normal_setting +type PlatformNormalSetting struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Name string `orm:"column(name);size(128);default('')" json:"name"` + Value string `orm:"column(value);type(text);null" json:"value"` + Code string `orm:"column(code);size(64);default('')" json:"code"` + Remark string `orm:"column(remark);size(255);default('')" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *PlatformNormalSetting) TableName() string { + return "yz_platform_normal_setting" +} + +// BackendNormalSetting 管理端通用配置表: yz_backend_normal_setting +type BackendNormalSetting struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Name string `orm:"column(name);size(128);default('')" json:"name"` + Value string `orm:"column(value);type(text);null" json:"value"` + Code string `orm:"column(code);size(64);default('')" json:"code"` + Remark string `orm:"column(remark);size(255);default('')" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *BackendNormalSetting) TableName() string { + return "yz_backend_normal_setting" +} diff --git a/go/models/system_operation_log.go b/go/models/system_operation_log.go index df2216d..352c2c4 100644 --- a/go/models/system_operation_log.go +++ b/go/models/system_operation_log.go @@ -1,28 +1,28 @@ -package models - -import "time" - -// SystemOperationLog 操作日志表 yz_system_operation_log -type SystemOperationLog struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid *uint64 `orm:"column(tid);null" json:"tid"` - UserID uint64 `orm:"column(user_id);default(0)" json:"user_id"` - Module string `orm:"column(module);size(50);default('')" json:"module"` - Action string `orm:"column(action);size(50);default('')" json:"action"` - Method string `orm:"column(method);size(10);default('')" json:"method"` - URL string `orm:"column(url);size(255);default('')" json:"url"` - IP string `orm:"column(ip);size(50);default('')" json:"ip"` - UserAgent string `orm:"column(user_agent);size(500);default('')" json:"user_agent"` - RequestData *string `orm:"column(request_data);type(text);null" json:"request_data"` - ResponseData *string `orm:"column(response_data);type(text);null" json:"response_data"` - Status int8 `orm:"column(status);default(1)" json:"status"` - ErrorMessage *string `orm:"column(error_message);type(text);null" json:"error_message"` - ExecutionTime float64 `orm:"column(execution_time);digits(10);decimals(3);default(0)" json:"execution_time"` - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemOperationLog) TableName() string { - return "yz_system_operation_log" -} +package models + +import "time" + +// SystemOperationLog 操作日志表 yz_system_operation_log +type SystemOperationLog struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid *uint64 `orm:"column(tid);null" json:"tid"` + UserID uint64 `orm:"column(user_id);default(0)" json:"user_id"` + Module string `orm:"column(module);size(50);default('')" json:"module"` + Action string `orm:"column(action);size(50);default('')" json:"action"` + Method string `orm:"column(method);size(10);default('')" json:"method"` + URL string `orm:"column(url);size(255);default('')" json:"url"` + IP string `orm:"column(ip);size(50);default('')" json:"ip"` + UserAgent string `orm:"column(user_agent);size(500);default('')" json:"user_agent"` + RequestData *string `orm:"column(request_data);type(text);null" json:"request_data"` + ResponseData *string `orm:"column(response_data);type(text);null" json:"response_data"` + Status int8 `orm:"column(status);default(1)" json:"status"` + ErrorMessage *string `orm:"column(error_message);type(text);null" json:"error_message"` + ExecutionTime float64 `orm:"column(execution_time);digits(10);decimals(3);default(0)" json:"execution_time"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemOperationLog) TableName() string { + return "yz_system_operation_log" +} diff --git a/go/models/system_reminderlist.go b/go/models/system_reminderlist.go index 622b87f..477ef35 100644 --- a/go/models/system_reminderlist.go +++ b/go/models/system_reminderlist.go @@ -1,26 +1,26 @@ -package models - -import "time" - -// SystemReminderList 站内信消息列表表 yz_system_reminderlist -type SystemReminderList struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Title string `orm:"column(title);size(255)" json:"title"` - Content string `orm:"column(content);type(text)" json:"content"` - SenderID uint64 `orm:"column(sender_id);default(0)" json:"sender_id"` - SenderType string `orm:"column(sender_type);size(32);default('system')" json:"sender_type"` // system, platform, tenant - ReceiverID uint64 `orm:"column(receiver_id)" json:"receiver_id"` - ReceiverType string `orm:"column(receiver_type);size(32)" json:"receiver_type"` // platform, tenant - IsRead int8 `orm:"column(is_read);default(0)" json:"is_read"` // 0-未读, 1-已读 - ReadTime *time.Time `orm:"column(read_time);type(datetime);null" json:"read_time"` - CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` - BatchID string `orm:"column(batch_id);size(64);default('')" json:"batch_id"` - TargetType string `orm:"column(target_type);size(32);default('')" json:"target_type"` - TargetRoleID uint64 `orm:"column(target_role_id);default(0)" json:"target_role_id"` - TargetTenantID uint64 `orm:"column(target_tenant_id);default(0)" json:"target_tenant_id"` -} - -func (m *SystemReminderList) TableName() string { - return "yz_system_reminderlist" -} +package models + +import "time" + +// SystemReminderList 站内信消息列表表 yz_system_reminderlist +type SystemReminderList struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Title string `orm:"column(title);size(255)" json:"title"` + Content string `orm:"column(content);type(text)" json:"content"` + SenderID uint64 `orm:"column(sender_id);default(0)" json:"sender_id"` + SenderType string `orm:"column(sender_type);size(32);default('system')" json:"sender_type"` // system, platform, tenant + ReceiverID uint64 `orm:"column(receiver_id)" json:"receiver_id"` + ReceiverType string `orm:"column(receiver_type);size(32)" json:"receiver_type"` // platform, tenant + IsRead int8 `orm:"column(is_read);default(0)" json:"is_read"` // 0-未读, 1-已读 + ReadTime *time.Time `orm:"column(read_time);type(datetime);null" json:"read_time"` + CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` + BatchID string `orm:"column(batch_id);size(64);default('')" json:"batch_id"` + TargetType string `orm:"column(target_type);size(32);default('')" json:"target_type"` + TargetRoleID uint64 `orm:"column(target_role_id);default(0)" json:"target_role_id"` + TargetTenantID uint64 `orm:"column(target_tenant_id);default(0)" json:"target_tenant_id"` +} + +func (m *SystemReminderList) TableName() string { + return "yz_system_reminderlist" +} diff --git a/go/models/system_sitereminder.go b/go/models/system_sitereminder.go index ba45555..2a4da5f 100644 --- a/go/models/system_sitereminder.go +++ b/go/models/system_sitereminder.go @@ -1,14 +1,14 @@ -package models - -import "time" - -// SystemSiteReminder 站内信配置表 yz_system_sitereminder -type SystemSiteReminder struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - RetentionDays int `orm:"column(retention_days);default(30)" json:"retention_days"` - AutoRead int8 `orm:"column(auto_read);default(0)" json:"auto_read"` - CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` -} - - +package models + +import "time" + +// SystemSiteReminder 站内信配置表 yz_system_sitereminder +type SystemSiteReminder struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + RetentionDays int `orm:"column(retention_days);default(30)" json:"retention_days"` + AutoRead int8 `orm:"column(auto_read);default(0)" json:"auto_read"` + CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` +} + + diff --git a/go/models/system_sms.go b/go/models/system_sms.go index 094dcb1..0620600 100644 --- a/go/models/system_sms.go +++ b/go/models/system_sms.go @@ -1,30 +1,30 @@ -package models - -import "time" - -// SystemSMS 短信配置表 yz_system_sms -type SystemSMS struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - ConfigCode string `orm:"column(config_code);size(64)" json:"config_code"` - ConfigName string `orm:"column(config_name);size(128)" json:"config_name"` - ChannelType int8 `orm:"column(channel_type);default(1)" json:"channel_type"` - - ApiURL string `orm:"column(api_url);size(512);default('')" json:"api_url"` - ApiKey string `orm:"column(api_key);size(256);default('')" json:"api_key"` - ApiSecret string `orm:"column(api_secret);size(256);default('')" json:"api_secret"` - - SignName string `orm:"column(sign_name);size(128);default('')" json:"sign_name"` - TemplateID string `orm:"column(template_id);size(128);default('')" json:"template_id"` - ExtraParams *string `orm:"column(extra_params);type(json);null" json:"extra_params"` - - TestPhone string `orm:"column(test_phone);size(64);default('')" json:"test_phone"` - - Weight int `orm:"column(weight);default(10)" json:"weight"` - IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"` - Status int8 `orm:"column(status);default(1)" json:"status"` - Remark string `orm:"column(remark);size(512);default('')" json:"remark"` - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"` -} - - +package models + +import "time" + +// SystemSMS 短信配置表 yz_system_sms +type SystemSMS struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + ConfigCode string `orm:"column(config_code);size(64)" json:"config_code"` + ConfigName string `orm:"column(config_name);size(128)" json:"config_name"` + ChannelType int8 `orm:"column(channel_type);default(1)" json:"channel_type"` + + ApiURL string `orm:"column(api_url);size(512);default('')" json:"api_url"` + ApiKey string `orm:"column(api_key);size(256);default('')" json:"api_key"` + ApiSecret string `orm:"column(api_secret);size(256);default('')" json:"api_secret"` + + SignName string `orm:"column(sign_name);size(128);default('')" json:"sign_name"` + TemplateID string `orm:"column(template_id);size(128);default('')" json:"template_id"` + ExtraParams *string `orm:"column(extra_params);type(json);null" json:"extra_params"` + + TestPhone string `orm:"column(test_phone);size(64);default('')" json:"test_phone"` + + Weight int `orm:"column(weight);default(10)" json:"weight"` + IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"` + Status int8 `orm:"column(status);default(1)" json:"status"` + Remark string `orm:"column(remark);size(512);default('')" json:"remark"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"` +} + + diff --git a/go/models/system_sms_task.go b/go/models/system_sms_task.go index f1b9a48..544c7e2 100644 --- a/go/models/system_sms_task.go +++ b/go/models/system_sms_task.go @@ -1,22 +1,22 @@ -package models - -import "time" - -// SystemSMSTask 短信任务表 yz_system_sms_tasks -type SystemSMSTask struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid *uint64 `orm:"column(tid);null" json:"tid"` // 测试允许为空 - ApiKey string `orm:"column(api_key);size(255)" json:"api_key"` - Phone string `orm:"column(phone);size(50)" json:"phone"` - Content *string `orm:"column(content);type(text);null" json:"content"` - Status int `orm:"column(status);default(0)" json:"status"` - Code string `orm:"column(code);size(20);default('')" json:"code"` - ReportRaw *string `orm:"column(report_raw);type(text);null" json:"report_raw"` - CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemSMSTask) TableName() string { - return "yz_system_sms_tasks" -} +package models + +import "time" + +// SystemSMSTask 短信任务表 yz_system_sms_tasks +type SystemSMSTask struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid *uint64 `orm:"column(tid);null" json:"tid"` // 测试允许为空 + ApiKey string `orm:"column(api_key);size(255)" json:"api_key"` + Phone string `orm:"column(phone);size(50)" json:"phone"` + Content *string `orm:"column(content);type(text);null" json:"content"` + Status int `orm:"column(status);default(0)" json:"status"` + Code string `orm:"column(code);size(20);default('')" json:"code"` + ReportRaw *string `orm:"column(report_raw);type(text);null" json:"report_raw"` + CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemSMSTask) TableName() string { + return "yz_system_sms_tasks" +} diff --git a/go/models/system_software_upgrade.go b/go/models/system_software_upgrade.go index 2eb9be8..a40bcee 100644 --- a/go/models/system_software_upgrade.go +++ b/go/models/system_software_upgrade.go @@ -4,19 +4,20 @@ import "time" // SystemSoftwareUpgrade 软件升级产品 yz_system_software_upgrade type SystemSoftwareUpgrade struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Name string `orm:"column(name);size(128)" json:"name"` - Code string `orm:"column(code);size(64);unique" json:"code"` - LatestVersion string `orm:"column(latest_version);size(32)" json:"latestVersion"` - FileID *uint64 `orm:"column(file_id);null" json:"fileId"` - DownloadURL *string `orm:"column(download_url);size(512);null" json:"downloadUrl"` - ForceUpdate int8 `orm:"column(force_update);default(0)" json:"forceUpdate"` - ReleaseNotes *string `orm:"column(release_notes);size(2000);null" json:"releaseNotes"` - Status int8 `orm:"column(status);default(1)" json:"status"` - Sort int `orm:"column(sort);default(0)" json:"sort"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` - UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Name string `orm:"column(name);size(128)" json:"name"` + Code string `orm:"column(code);size(64);unique" json:"code"` + LatestVersion string `orm:"column(latest_version);size(32)" json:"latestVersion"` + FileID *uint64 `orm:"column(file_id);null" json:"fileId"` + DownloadURL *string `orm:"column(download_url);size(512);null" json:"downloadUrl"` + DownloadURLs *string `orm:"column(download_urls);type(text);null" json:"downloadUrls"` + ForceUpdate int8 `orm:"column(force_update);default(0)" json:"forceUpdate"` + ReleaseNotes *string `orm:"column(release_notes);size(2000);null" json:"releaseNotes"` + Status int8 `orm:"column(status);default(1)" json:"status"` + Sort int `orm:"column(sort);default(0)" json:"sort"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"` + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"` } func (m *SystemSoftwareUpgrade) TableName() string { diff --git a/go/models/system_tenant.go b/go/models/system_tenant.go index 8a22f94..162e399 100644 --- a/go/models/system_tenant.go +++ b/go/models/system_tenant.go @@ -1,23 +1,23 @@ -package models - -import "time" - -type SystemTenant struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - TenantCode string `orm:"column(tenant_code);size(32)" json:"tenant_code"` - TenantName string `orm:"column(tenant_name);size(128)" json:"tenant_name"` - ContactPerson *string `orm:"column(contact_person);size(64);null" json:"contact_person"` - ContactPhone *string `orm:"column(contact_phone);size(20);null" json:"contact_phone"` - ContactEmail *string `orm:"column(contact_email);size(128);null" json:"contact_email"` - Address *string `orm:"column(address);size(255);null" json:"address"` - Worktime *string `orm:"column(worktime);size(255);null" json:"worktime"` - Status int8 `orm:"column(status);default(1)" json:"status"` - Remark *string `orm:"column(remark);size(512);null" json:"remark"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemTenant) TableName() string { - return "yz_system_tenant" -} +package models + +import "time" + +type SystemTenant struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + TenantCode string `orm:"column(tenant_code);size(32)" json:"tenant_code"` + TenantName string `orm:"column(tenant_name);size(128)" json:"tenant_name"` + ContactPerson *string `orm:"column(contact_person);size(64);null" json:"contact_person"` + ContactPhone *string `orm:"column(contact_phone);size(20);null" json:"contact_phone"` + ContactEmail *string `orm:"column(contact_email);size(128);null" json:"contact_email"` + Address *string `orm:"column(address);size(255);null" json:"address"` + Worktime *string `orm:"column(worktime);size(255);null" json:"worktime"` + Status int8 `orm:"column(status);default(1)" json:"status"` + Remark *string `orm:"column(remark);size(512);null" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemTenant) TableName() string { + return "yz_system_tenant" +} diff --git a/go/models/system_tenant_domain.go b/go/models/system_tenant_domain.go index fe44f64..e19b85c 100644 --- a/go/models/system_tenant_domain.go +++ b/go/models/system_tenant_domain.go @@ -1,20 +1,20 @@ -package models - -import "time" - -// SystemTenantDomain 租户域名表 yz_tenant_domain -type SystemTenantDomain struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid *uint64 `orm:"column(tid);null" json:"tid"` - SubDomain *string `orm:"column(sub_domain);size(50);null" json:"sub_domain"` - MainDomain *string `orm:"column(main_domain);size(255);null" json:"main_domain"` - FullDomain *string `orm:"column(full_domain);size(255);null" json:"full_domain"` - Status int `orm:"column(status);null" json:"status"` // 1已生效 / 0审核中 / 2禁用 - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemTenantDomain) TableName() string { - return "yz_system_tenant_domain" -} +package models + +import "time" + +// SystemTenantDomain 租户域名表 yz_tenant_domain +type SystemTenantDomain struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid *uint64 `orm:"column(tid);null" json:"tid"` + SubDomain *string `orm:"column(sub_domain);size(50);null" json:"sub_domain"` + MainDomain *string `orm:"column(main_domain);size(255);null" json:"main_domain"` + FullDomain *string `orm:"column(full_domain);size(255);null" json:"full_domain"` + Status int `orm:"column(status);null" json:"status"` // 1已生效 / 0审核中 / 2禁用 + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemTenantDomain) TableName() string { + return "yz_system_tenant_domain" +} diff --git a/go/models/system_tenant_site_setting.go b/go/models/system_tenant_site_setting.go index 6d539ac..04bac88 100644 --- a/go/models/system_tenant_site_setting.go +++ b/go/models/system_tenant_site_setting.go @@ -1,30 +1,30 @@ -package models - -import "time" - -// TenantSiteSetting 租户站点设置表 yz_tenant_site_setting -// 主要用于“站点基本信息”配置(站点名称/Logo/企业介绍等) -type TenantSiteSetting struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - - Tid uint64 `orm:"column(tid);null" json:"tid"` - - Sitename string `orm:"column(sitename);size(255);null" json:"sitename"` - Logo string `orm:"column(logo);size(255);null" json:"logo"` - Logow string `orm:"column(logow);size(255);null" json:"logow"` - Ico string `orm:"column(ico);size(255);null" json:"ico"` - - Companyintroduction string `orm:"column(companyintroduction);type(longtext);null" json:"companyintroduction"` - Description string `orm:"column(description);size(255);null" json:"description"` - Copyright string `orm:"column(copyright);size(255);null" json:"copyright"` - Companyname string `orm:"column(companyname);size(255);null" json:"companyname"` - Icp string `orm:"column(icp);size(255);null" json:"icp"` - - CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add;null" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *TenantSiteSetting) TableName() string { - return "yz_system_tenant_site_setting" -} +package models + +import "time" + +// TenantSiteSetting 租户站点设置表 yz_tenant_site_setting +// 主要用于“站点基本信息”配置(站点名称/Logo/企业介绍等) +type TenantSiteSetting struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + + Tid uint64 `orm:"column(tid);null" json:"tid"` + + Sitename string `orm:"column(sitename);size(255);null" json:"sitename"` + Logo string `orm:"column(logo);size(255);null" json:"logo"` + Logow string `orm:"column(logow);size(255);null" json:"logow"` + Ico string `orm:"column(ico);size(255);null" json:"ico"` + + Companyintroduction string `orm:"column(companyintroduction);type(longtext);null" json:"companyintroduction"` + Description string `orm:"column(description);size(255);null" json:"description"` + Copyright string `orm:"column(copyright);size(255);null" json:"copyright"` + Companyname string `orm:"column(companyname);size(255);null" json:"companyname"` + Icp string `orm:"column(icp);size(255);null" json:"icp"` + + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add;null" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *TenantSiteSetting) TableName() string { + return "yz_system_tenant_site_setting" +} diff --git a/go/models/system_tenant_user.go b/go/models/system_tenant_user.go index 1f64874..4d2cb31 100644 --- a/go/models/system_tenant_user.go +++ b/go/models/system_tenant_user.go @@ -1,26 +1,26 @@ -package models - -import "time" - -type SystemTenantUser struct { - ID uint64 `orm:"column(id);pk;auto" json:"id"` - Tid uint64 `orm:"column(tid)" json:"tid"` - Uid uint64 `orm:"column(uid)" json:"uid"` - Account *string `orm:"column(account);size(64);null" json:"account"` - Name *string `orm:"column(name);size(64);null" json:"name"` - Phone *string `orm:"column(phone);size(20);null" json:"phone"` - Email *string `orm:"column(email);size(128);null" json:"email"` - Sex uint8 `orm:"column(sex);default(0)" json:"sex"` - Birth *string `orm:"column(birth);size(20);null" json:"birth"` - Password *string `orm:"column(password);size(255);null" json:"password"` - IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"` - Status int8 `orm:"column(status);default(1)" json:"status"` - Remark *string `orm:"column(remark);size(255);null" json:"remark"` - CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` - UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"update_time"` - DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` -} - -func (m *SystemTenantUser) TableName() string { - return "yz_system_tenant_user" -} +package models + +import "time" + +type SystemTenantUser struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Tid uint64 `orm:"column(tid)" json:"tid"` + Uid uint64 `orm:"column(uid)" json:"uid"` + Account *string `orm:"column(account);size(64);null" json:"account"` + Name *string `orm:"column(name);size(64);null" json:"name"` + Phone *string `orm:"column(phone);size(20);null" json:"phone"` + Email *string `orm:"column(email);size(128);null" json:"email"` + Sex uint8 `orm:"column(sex);default(0)" json:"sex"` + Birth *string `orm:"column(birth);size(20);null" json:"birth"` + Password *string `orm:"column(password);size(255);null" json:"password"` + IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"` + Status int8 `orm:"column(status);default(1)" json:"status"` + Remark *string `orm:"column(remark);size(255);null" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemTenantUser) TableName() string { + return "yz_system_tenant_user" +} diff --git a/go/nohup.out b/go/nohup.out index b227ae4..296b202 100644 --- a/go/nohup.out +++ b/go/nohup.out @@ -1,26 +1,26 @@ -2026/04/09 17:41:49.470 [I] [server.go:281] http server Running on http://:8081 -2026/04/09 17:43:09.442 [I] [server.go:281] http server Running on http://:8081 -2026/04/09 17:43:09.442 [C] [server.go:298] ListenAndServe: listen tcp :8081: bind: address already in use -2026/04/09 17:43:15.715 [D] [router.go:1305] | 127.0.0.1| 200 | 265.592518ms| match| GET  /platform/usercate r:/platform/usercate -2026/04/09 17:43:15.925 [D] [router.go:1305] | 127.0.0.1| 200 | 468.017304ms| match| GET  /platform/currentUser r:/platform/currentUser -2026/04/09 17:43:16.057 [D] [router.go:1305] | 127.0.0.1| 200 | 274.492712ms| match| GET  /platform/catefiles/0 r:/platform/catefiles/:id -2026/04/09 17:43:22.620 [D] [router.go:1305] | 127.0.0.1| 200 | 277.387093ms| match| GET  /platform/usercate r:/platform/usercate -2026/04/09 17:43:22.622 [D] [router.go:1305] | 127.0.0.1| 200 | 271.734643ms| match| GET  /platform/currentUser r:/platform/currentUser -2026/04/09 17:43:23.037 [D] [router.go:1305] | 127.0.0.1| 200 | 353.76378ms| match| GET  /platform/catefiles/0 r:/platform/catefiles/:id -2026/04/09 17:43:24.492 [D] [router.go:1305] | 127.0.0.1| 200 | 351.839484ms| match| GET  /platform/catefiles/5 r:/platform/catefiles/:id -2026/04/09 17:43:25.518 [D] [router.go:1305] | 127.0.0.1| 200 | 325.277959ms| match| GET  /platform/catefiles/0 r:/platform/catefiles/:id -2026/04/09 17:43:29.495 [D] [router.go:1305] | 127.0.0.1| 200 | 10.772µs| nomatch| OPTIONS  /platform/logout -2026/04/09 17:43:29.537 [D] [router.go:1305] | 127.0.0.1| 200 | 42.423µs| match| POST  /platform/logout r:/platform/logout -2026/04/09 17:43:29.744 [D] [router.go:1305] | 127.0.0.1| 200 | 88.149485ms| match| GET  /platform/loginVerifyInfos r:/platform/loginVerifyInfos -2026/04/09 17:43:30.734 [D] [router.go:1305] | 127.0.0.1| 200 | 87.869914ms| match| GET  /platform/login/getOpenVerify r:/platform/login/getOpenVerify -2026/04/09 17:43:30.868 [D] [router.go:1305] | 127.0.0.1| 200 | 87.927568ms| match| GET  /platform/login/getGeetest4Infos r:/platform/login/getGeetest4Infos -2026/04/09 17:43:36.373 [D] [router.go:1305] | 127.0.0.1| 200 | 61.401µs| match| POST  /platform/login r:/platform/login -2026/04/09 17:43:50.709 [D] [router.go:1305] | 127.0.0.1| 200 | 89.443767ms| match| GET  /platform/loginVerifyInfos r:/platform/loginVerifyInfos -2026/04/09 17:43:52.056 [D] [router.go:1305] | 127.0.0.1| 200 | 70.997714ms| match| GET  /platform/login/getOpenVerify r:/platform/login/getOpenVerify -2026/04/09 17:43:52.175 [D] [router.go:1305] | 127.0.0.1| 200 | 74.287857ms| match| GET  /platform/login/getGeetest4Infos r:/platform/login/getGeetest4Infos -2026/04/09 17:43:57.811 [D] [router.go:1305] | 127.0.0.1| 200 | 11.75µs| nomatch| OPTIONS  /platform/login -2026/04/09 17:43:57.854 [D] [router.go:1305] | 127.0.0.1| 200 | 42.819µs| match| POST  /platform/login r:/platform/login -2026/04/09 17:43:59.739 [D] [router.go:1305] | 127.0.0.1| 200 | 70.336904ms| match| GET  /platform/login/getOpenVerify r:/platform/login/getOpenVerify -2026/04/09 17:43:59.862 [D] [router.go:1305] | 127.0.0.1| 200 | 70.920607ms| match| GET  /platform/login/getGeetest4Infos r:/platform/login/getGeetest4Infos -2026/04/09 17:44:05.289 [D] [router.go:1305] | 127.0.0.1| 200 | 11.177µs| nomatch| OPTIONS  /platform/login -2026/04/09 17:44:05.332 [D] [router.go:1305] | 127.0.0.1| 200 | 42.979µs| match| POST  /platform/login r:/platform/login +2026/04/09 17:41:49.470 [I] [server.go:281] http server Running on http://:8081 +2026/04/09 17:43:09.442 [I] [server.go:281] http server Running on http://:8081 +2026/04/09 17:43:09.442 [C] [server.go:298] ListenAndServe: listen tcp :8081: bind: address already in use +2026/04/09 17:43:15.715 [D] [router.go:1305] | 127.0.0.1| 200 | 265.592518ms| match| GET  /platform/usercate r:/platform/usercate +2026/04/09 17:43:15.925 [D] [router.go:1305] | 127.0.0.1| 200 | 468.017304ms| match| GET  /platform/currentUser r:/platform/currentUser +2026/04/09 17:43:16.057 [D] [router.go:1305] | 127.0.0.1| 200 | 274.492712ms| match| GET  /platform/catefiles/0 r:/platform/catefiles/:id +2026/04/09 17:43:22.620 [D] [router.go:1305] | 127.0.0.1| 200 | 277.387093ms| match| GET  /platform/usercate r:/platform/usercate +2026/04/09 17:43:22.622 [D] [router.go:1305] | 127.0.0.1| 200 | 271.734643ms| match| GET  /platform/currentUser r:/platform/currentUser +2026/04/09 17:43:23.037 [D] [router.go:1305] | 127.0.0.1| 200 | 353.76378ms| match| GET  /platform/catefiles/0 r:/platform/catefiles/:id +2026/04/09 17:43:24.492 [D] [router.go:1305] | 127.0.0.1| 200 | 351.839484ms| match| GET  /platform/catefiles/5 r:/platform/catefiles/:id +2026/04/09 17:43:25.518 [D] [router.go:1305] | 127.0.0.1| 200 | 325.277959ms| match| GET  /platform/catefiles/0 r:/platform/catefiles/:id +2026/04/09 17:43:29.495 [D] [router.go:1305] | 127.0.0.1| 200 | 10.772µs| nomatch| OPTIONS  /platform/logout +2026/04/09 17:43:29.537 [D] [router.go:1305] | 127.0.0.1| 200 | 42.423µs| match| POST  /platform/logout r:/platform/logout +2026/04/09 17:43:29.744 [D] [router.go:1305] | 127.0.0.1| 200 | 88.149485ms| match| GET  /platform/loginVerifyInfos r:/platform/loginVerifyInfos +2026/04/09 17:43:30.734 [D] [router.go:1305] | 127.0.0.1| 200 | 87.869914ms| match| GET  /platform/login/getOpenVerify r:/platform/login/getOpenVerify +2026/04/09 17:43:30.868 [D] [router.go:1305] | 127.0.0.1| 200 | 87.927568ms| match| GET  /platform/login/getGeetest4Infos r:/platform/login/getGeetest4Infos +2026/04/09 17:43:36.373 [D] [router.go:1305] | 127.0.0.1| 200 | 61.401µs| match| POST  /platform/login r:/platform/login +2026/04/09 17:43:50.709 [D] [router.go:1305] | 127.0.0.1| 200 | 89.443767ms| match| GET  /platform/loginVerifyInfos r:/platform/loginVerifyInfos +2026/04/09 17:43:52.056 [D] [router.go:1305] | 127.0.0.1| 200 | 70.997714ms| match| GET  /platform/login/getOpenVerify r:/platform/login/getOpenVerify +2026/04/09 17:43:52.175 [D] [router.go:1305] | 127.0.0.1| 200 | 74.287857ms| match| GET  /platform/login/getGeetest4Infos r:/platform/login/getGeetest4Infos +2026/04/09 17:43:57.811 [D] [router.go:1305] | 127.0.0.1| 200 | 11.75µs| nomatch| OPTIONS  /platform/login +2026/04/09 17:43:57.854 [D] [router.go:1305] | 127.0.0.1| 200 | 42.819µs| match| POST  /platform/login r:/platform/login +2026/04/09 17:43:59.739 [D] [router.go:1305] | 127.0.0.1| 200 | 70.336904ms| match| GET  /platform/login/getOpenVerify r:/platform/login/getOpenVerify +2026/04/09 17:43:59.862 [D] [router.go:1305] | 127.0.0.1| 200 | 70.920607ms| match| GET  /platform/login/getGeetest4Infos r:/platform/login/getGeetest4Infos +2026/04/09 17:44:05.289 [D] [router.go:1305] | 127.0.0.1| 200 | 11.177µs| nomatch| OPTIONS  /platform/login +2026/04/09 17:44:05.332 [D] [router.go:1305] | 127.0.0.1| 200 | 42.979µs| match| POST  /platform/login r:/platform/login diff --git a/go/package-lock.json b/go/package-lock.json index 99ef3d8..a14116b 100644 --- a/go/package-lock.json +++ b/go/package-lock.json @@ -1,1640 +1,1640 @@ -{ - "name": "server", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "vue-office": "^0.0.5" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmmirror.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "2.7.16", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", - "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", - "dependencies": { - "@babel/parser": "^7.23.5", - "postcss": "^8.4.14", - "source-map": "^0.6.1" - }, - "optionalDependencies": { - "prettier": "^1.18.2 || ^2.0.0" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.11", - "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz", - "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC", - "optional": true - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "license": "ISC", - "optional": true - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "optional": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/canvas": { - "version": "2.11.2", - "resolved": "https://registry.npmmirror.com/canvas/-/canvas-2.11.2.tgz", - "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.0", - "nan": "^2.17.0", - "simple-get": "^3.0.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "license": "ISC", - "optional": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT", - "optional": true - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "license": "ISC", - "optional": true - }, - "node_modules/core-js": { - "version": "3.46.0", - "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.46.0.tgz", - "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "license": "MIT", - "optional": true, - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT", - "optional": true - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dingbat-to-unicode": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", - "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==", - "license": "BSD-2-Clause" - }, - "node_modules/docx-preview": { - "version": "0.1.20", - "resolved": "https://registry.npmmirror.com/docx-preview/-/docx-preview-0.1.20.tgz", - "integrity": "sha512-YfmRI6wdq5n2uh7Oi6Gk7FszDV+OysA6Gs5ZoLmSZPJrTOrgUmiEVZ87iJDAUxNIJENua/Tj7H7IYmpNEbFzlw==", - "license": "Apache-2.0", - "dependencies": { - "jszip": ">=3.0.0" - } - }, - "node_modules/duck": { - "version": "0.1.12", - "resolved": "https://registry.npmmirror.com/duck/-/duck-0.1.12.tgz", - "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==", - "license": "BSD", - "dependencies": { - "underscore": "^1.13.1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT", - "optional": true - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC", - "optional": true - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "license": "ISC", - "optional": true - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", - "optional": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lop": { - "version": "0.4.2", - "resolved": "https://registry.npmmirror.com/lop/-/lop-0.4.2.tgz", - "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==", - "license": "BSD-2-Clause", - "dependencies": { - "duck": "^0.1.12", - "option": "~0.2.1", - "underscore": "^1.13.1" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "license": "MIT", - "optional": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/mammoth": { - "version": "1.11.0", - "resolved": "https://registry.npmmirror.com/mammoth/-/mammoth-1.11.0.tgz", - "integrity": "sha512-BcEqqY/BOwIcI1iR5tqyVlqc3KIaMRa4egSoK83YAVrBf6+yqdAAbtUcFDCWX8Zef8/fgNZ6rl4VUv+vVX8ddQ==", - "license": "BSD-2-Clause", - "dependencies": { - "@xmldom/xmldom": "^0.8.6", - "argparse": "~1.0.3", - "base64-js": "^1.5.1", - "bluebird": "~3.4.0", - "dingbat-to-unicode": "^1.0.1", - "jszip": "^3.7.1", - "lop": "^0.4.2", - "path-is-absolute": "^1.0.0", - "underscore": "^1.13.1", - "xmlbuilder": "^10.0.0" - }, - "bin": { - "mammoth": "bin/mammoth" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "optional": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "optional": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "optional": true - }, - "node_modules/nan": { - "version": "2.23.0", - "resolved": "https://registry.npmmirror.com/nan/-/nan-2.23.0.tgz", - "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", - "license": "MIT", - "optional": true - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmmirror.com/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "optional": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/option": { - "version": "0.2.4", - "resolved": "https://registry.npmmirror.com/option/-/option-0.2.4.tgz", - "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==", - "license": "BSD-2-Clause" - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, - "node_modules/path": { - "version": "0.12.7", - "resolved": "https://registry.npmmirror.com/path/-/path-0.12.7.tgz", - "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", - "license": "MIT", - "dependencies": { - "process": "^0.11.1", - "util": "^0.10.3" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" - }, - "node_modules/path/node_modules/util": { - "version": "0.10.4", - "resolved": "https://registry.npmmirror.com/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "license": "MIT", - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/path2d-polyfill": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", - "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pdfjs-dist": { - "version": "3.11.174", - "resolved": "https://registry.npmmirror.com/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", - "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", - "license": "Apache-2.0", - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "canvas": "^2.11.2", - "path2d-polyfill": "^2.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "license": "MIT", - "optional": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC", - "optional": true - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC", - "optional": true - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true - }, - "node_modules/simple-get": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-3.1.1.tgz", - "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", - "license": "MIT", - "optional": true, - "dependencies": { - "decompress-response": "^4.2.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" - }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "license": "MIT", - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "node_modules/stream-browserify/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "optional": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "optional": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "license": "ISC", - "optional": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "optional": true - }, - "node_modules/underscore": { - "version": "1.13.7", - "resolved": "https://registry.npmmirror.com/underscore/-/underscore-1.13.7.tgz", - "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", - "license": "MIT" - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmmirror.com/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/vue": { - "version": "2.7.16", - "resolved": "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz", - "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", - "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", - "license": "MIT", - "dependencies": { - "@vue/compiler-sfc": "2.7.16", - "csstype": "^3.1.0" - } - }, - "node_modules/vue-office": { - "version": "0.0.5", - "resolved": "https://registry.npmmirror.com/vue-office/-/vue-office-0.0.5.tgz", - "integrity": "sha512-KVqmPwinTwzkDKYiyJ/iNw/IiFRDDC+DZr2ysXhuN2322d0hBp4LUUDrhtU2B0hyDSqxFsHsI/6pwMlvq5KFsg==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT", - "dependencies": { - "core-js": "^3.8.3", - "docx-preview": "^0.1.14", - "lodash": "^4.17.21", - "mammoth": "^1.5.1", - "path": "^0.12.7", - "pdfjs-dist": "^3.0.279", - "stream-browserify": "^3.0.0", - "util": "^0.12.5", - "vue": "^2.6.14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "optional": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "license": "ISC", - "optional": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC", - "optional": true - }, - "node_modules/xmlbuilder": { - "version": "10.1.1", - "resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz", - "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - } - } -} +{ + "name": "server", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "vue-office": "^0.0.5" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", + "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", + "dependencies": { + "@babel/parser": "^7.23.5", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + }, + "optionalDependencies": { + "prettier": "^1.18.2 || ^2.0.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmmirror.com/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/core-js": { + "version": "3.46.0", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.46.0.tgz", + "integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmmirror.com/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dingbat-to-unicode": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", + "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==", + "license": "BSD-2-Clause" + }, + "node_modules/docx-preview": { + "version": "0.1.20", + "resolved": "https://registry.npmmirror.com/docx-preview/-/docx-preview-0.1.20.tgz", + "integrity": "sha512-YfmRI6wdq5n2uh7Oi6Gk7FszDV+OysA6Gs5ZoLmSZPJrTOrgUmiEVZ87iJDAUxNIJENua/Tj7H7IYmpNEbFzlw==", + "license": "Apache-2.0", + "dependencies": { + "jszip": ">=3.0.0" + } + }, + "node_modules/duck": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/duck/-/duck-0.1.12.tgz", + "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==", + "license": "BSD", + "dependencies": { + "underscore": "^1.13.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lop": { + "version": "0.4.2", + "resolved": "https://registry.npmmirror.com/lop/-/lop-0.4.2.tgz", + "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==", + "license": "BSD-2-Clause", + "dependencies": { + "duck": "^0.1.12", + "option": "~0.2.1", + "underscore": "^1.13.1" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/mammoth": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/mammoth/-/mammoth-1.11.0.tgz", + "integrity": "sha512-BcEqqY/BOwIcI1iR5tqyVlqc3KIaMRa4egSoK83YAVrBf6+yqdAAbtUcFDCWX8Zef8/fgNZ6rl4VUv+vVX8ddQ==", + "license": "BSD-2-Clause", + "dependencies": { + "@xmldom/xmldom": "^0.8.6", + "argparse": "~1.0.3", + "base64-js": "^1.5.1", + "bluebird": "~3.4.0", + "dingbat-to-unicode": "^1.0.1", + "jszip": "^3.7.1", + "lop": "^0.4.2", + "path-is-absolute": "^1.0.0", + "underscore": "^1.13.1", + "xmlbuilder": "^10.0.0" + }, + "bin": { + "mammoth": "bin/mammoth" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT", + "optional": true + }, + "node_modules/nan": { + "version": "2.23.0", + "resolved": "https://registry.npmmirror.com/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "license": "MIT", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/option": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/option/-/option-0.2.4.tgz", + "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==", + "license": "BSD-2-Clause" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmmirror.com/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/path/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmmirror.com/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pdfjs-dist": { + "version": "3.11.174", + "resolved": "https://registry.npmmirror.com/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "license": "MIT", + "optional": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT", + "optional": true + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmmirror.com/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "license": "MIT" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmmirror.com/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vue": { + "version": "2.7.16", + "resolved": "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz", + "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", + "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "2.7.16", + "csstype": "^3.1.0" + } + }, + "node_modules/vue-office": { + "version": "0.0.5", + "resolved": "https://registry.npmmirror.com/vue-office/-/vue-office-0.0.5.tgz", + "integrity": "sha512-KVqmPwinTwzkDKYiyJ/iNw/IiFRDDC+DZr2ysXhuN2322d0hBp4LUUDrhtU2B0hyDSqxFsHsI/6pwMlvq5KFsg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "core-js": "^3.8.3", + "docx-preview": "^0.1.14", + "lodash": "^4.17.21", + "mammoth": "^1.5.1", + "path": "^0.12.7", + "pdfjs-dist": "^3.0.279", + "stream-browserify": "^3.0.0", + "util": "^0.12.5", + "vue": "^2.6.14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, + "node_modules/xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + } + } +} diff --git a/go/package.json b/go/package.json index 3e8823a..6c16f20 100644 --- a/go/package.json +++ b/go/package.json @@ -1,5 +1,5 @@ -{ - "dependencies": { - "vue-office": "^0.0.5" - } -} +{ + "dependencies": { + "vue-office": "^0.0.5" + } +} diff --git a/go/pkg/jwtutil/jwt.go b/go/pkg/jwtutil/jwt.go index 4cb5bed..42a4103 100644 --- a/go/pkg/jwtutil/jwt.go +++ b/go/pkg/jwtutil/jwt.go @@ -1,63 +1,63 @@ -package jwtutil - -import ( - "errors" - "time" - - "github.com/golang-jwt/jwt/v5" -) - -// 密钥(后续可从配置中读取) -var secret = []byte("yunzer_jwt_secret_key") - -// Claims 定义JWT的claims结构 -type Claims struct { - UserID int `json:"user_id"` - Username string `json:"username"` - TenantId int `json:"tenant_id"` // 租户ID - UserType string `json:"user_type"` // 用户类型:"user" / "employee" / "platform" 等 - jwt.RegisteredClaims -} - -// GenerateToken 生成JWT token -func GenerateToken(userID int, username string, tenantId int, userType string) (string, error) { - expirationTime := time.Now().Add(24 * time.Hour) - - claims := &Claims{ - UserID: userID, - Username: username, - TenantId: tenantId, - UserType: userType, - RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(expirationTime), - IssuedAt: jwt.NewNumericDate(time.Now()), - NotBefore: jwt.NewNumericDate(time.Now()), - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - tokenString, err := token.SignedString(secret) - return tokenString, err -} - -// ParseToken 解析JWT token -func ParseToken(tokenString string) (*Claims, error) { - claims := &Claims{} - token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, errors.New("unexpected signing method") - } - return secret, nil - }) - - if err != nil { - return nil, err - } - - if !token.Valid { - return nil, errors.New("invalid token") - } - - return claims, nil -} - +package jwtutil + +import ( + "errors" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +// 密钥(后续可从配置中读取) +var secret = []byte("yunzer_jwt_secret_key") + +// Claims 定义JWT的claims结构 +type Claims struct { + UserID int `json:"user_id"` + Username string `json:"username"` + TenantId int `json:"tenant_id"` // 租户ID + UserType string `json:"user_type"` // 用户类型:"user" / "employee" / "platform" 等 + jwt.RegisteredClaims +} + +// GenerateToken 生成JWT token +func GenerateToken(userID int, username string, tenantId int, userType string) (string, error) { + expirationTime := time.Now().Add(24 * time.Hour) + + claims := &Claims{ + UserID: userID, + Username: username, + TenantId: tenantId, + UserType: userType, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationTime), + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString(secret) + return tokenString, err +} + +// ParseToken 解析JWT token +func ParseToken(tokenString string) (*Claims, error) { + claims := &Claims{} + token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.New("unexpected signing method") + } + return secret, nil + }) + + if err != nil { + return nil, err + } + + if !token.Valid { + return nil, errors.New("invalid token") + } + + return claims, nil +} + diff --git a/go/pkg/passwordutil/password.go b/go/pkg/passwordutil/password.go index 2d8a829..d17ff6f 100644 --- a/go/pkg/passwordutil/password.go +++ b/go/pkg/passwordutil/password.go @@ -1,55 +1,55 @@ -package passwordutil - -import ( - "crypto/rand" - "crypto/sha256" - "encoding/hex" - "errors" - "strings" -) - -const ( - saltBytes = 16 - separator = "$" - hashLength = 64 // sha256 hex length -) - -// Hash 生成 salt+hash 的存储串,格式:salt$hash(均为 hex) -func Hash(plain string) (string, error) { - plain = strings.TrimSpace(plain) - if plain == "" { - return "", errors.New("password 不能为空") - } - salt := make([]byte, saltBytes) - if _, err := rand.Read(salt); err != nil { - return "", err - } - saltHex := hex.EncodeToString(salt) - hashHex := hashHex(saltHex, plain) - return saltHex + separator + hashHex, nil -} - -// Verify 校验存储串(salt$hash)是否匹配输入明文密码。 -func Verify(stored, plain string) bool { - stored = strings.TrimSpace(stored) - plain = strings.TrimSpace(plain) - if stored == "" || plain == "" { - return false - } - parts := strings.Split(stored, separator) - if len(parts) != 2 { - return false - } - saltHex := strings.TrimSpace(parts[0]) - hashHexStored := strings.TrimSpace(parts[1]) - if saltHex == "" || len(hashHexStored) != hashLength { - return false - } - return hashHex(saltHex, plain) == strings.ToLower(hashHexStored) -} - -func hashHex(saltHex, plain string) string { - sum := sha256.Sum256([]byte(saltHex + plain)) - return hex.EncodeToString(sum[:]) -} - +package passwordutil + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "errors" + "strings" +) + +const ( + saltBytes = 16 + separator = "$" + hashLength = 64 // sha256 hex length +) + +// Hash 生成 salt+hash 的存储串,格式:salt$hash(均为 hex) +func Hash(plain string) (string, error) { + plain = strings.TrimSpace(plain) + if plain == "" { + return "", errors.New("password 不能为空") + } + salt := make([]byte, saltBytes) + if _, err := rand.Read(salt); err != nil { + return "", err + } + saltHex := hex.EncodeToString(salt) + hashHex := hashHex(saltHex, plain) + return saltHex + separator + hashHex, nil +} + +// Verify 校验存储串(salt$hash)是否匹配输入明文密码。 +func Verify(stored, plain string) bool { + stored = strings.TrimSpace(stored) + plain = strings.TrimSpace(plain) + if stored == "" || plain == "" { + return false + } + parts := strings.Split(stored, separator) + if len(parts) != 2 { + return false + } + saltHex := strings.TrimSpace(parts[0]) + hashHexStored := strings.TrimSpace(parts[1]) + if saltHex == "" || len(hashHexStored) != hashLength { + return false + } + return hashHex(saltHex, plain) == strings.ToLower(hashHexStored) +} + +func hashHex(saltHex, plain string) string { + sum := sha256.Sum256([]byte(saltHex + plain)) + return hex.EncodeToString(sum[:]) +} + diff --git a/go/pkg/tokenprobe/cursor_hi.go b/go/pkg/tokenprobe/cursor_hi.go index 2a9ccf6..0eaf283 100644 --- a/go/pkg/tokenprobe/cursor_hi.go +++ b/go/pkg/tokenprobe/cursor_hi.go @@ -1,560 +1,560 @@ -package tokenprobe - -import ( - "bytes" - "compress/gzip" - "crypto/sha256" - "crypto/tls" - "encoding/hex" - "fmt" - "io" - "net/http" - "os" - "runtime" - "strings" - "time" - "unicode/utf8" - - "github.com/google/uuid" - "golang.org/x/net/http2" -) - -const ( - cursorBackendURL = "https://api2.cursor.sh" - cursorAgentPath = "/aiserver.v1.ChatService/StreamUnifiedChatWithTools" - cursorClientVersion = "2.6.22" - cursorHiMaxRead = 512 * 1024 - // probeHiText 发往官方 Agent 的探测内容(与前端展示 probeMessage 一致) - probeHiText = "hi" -) - -var cursorProbeHTTPClient = newCursorHTTP2Client() - -func newCursorHTTP2Client() *http.Client { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12}, - } - // 与 Cursor 官方一致走 HTTP/2 - if err := http2.ConfigureTransport(tr); err != nil { - return &http.Client{Timeout: 40 * time.Second} - } - return &http.Client{Transport: tr, Timeout: 40 * time.Second} -} - -func cursorClientOS() string { - switch runtime.GOOS { - case "windows": - return "win32" - case "darwin": - return "darwin" - default: - return "linux" - } -} - -func cursorClientArch() string { - switch runtime.GOARCH { - case "amd64": - return "x64" - case "arm64": - return "arm64" - default: - return runtime.GOARCH - } -} - -func cursorEnvVersion() string { - if v := strings.TrimSpace(os.Getenv("CURSOR_CLIENT_VERSION")); v != "" { - return v - } - return cursorClientVersion -} - -// --- protobuf wire (与 cursor_api_demo 对齐) --- - -func pbVarint(v uint64) []byte { - var out []byte - for v >= 0x80 { - out = append(out, byte(v&0x7f|0x80)) - v >>= 7 - } - out = append(out, byte(v&0x7f)) - return out -} - -func pbField(fieldNum int, wireType int, value interface{}) []byte { - tag := uint64(fieldNum<<3 | wireType) - out := pbVarint(tag) - switch wireType { - case 0: - var n uint64 - switch x := value.(type) { - case int: - n = uint64(x) - case int32: - n = uint64(x) - case uint32: - n = uint64(x) - case uint64: - n = x - default: - n = uint64(0) - } - out = append(out, pbVarint(n)...) - case 2: - var b []byte - switch x := value.(type) { - case string: - b = []byte(x) - case []byte: - b = x - default: - b = []byte(fmt.Sprint(x)) - } - out = append(out, pbVarint(uint64(len(b)))...) - out = append(out, b...) - } - return out -} - -func encodeCursorMessage(content string, role int, messageID string, chatModeEnum *int) []byte { - msg := pbField(1, 2, content) - msg = append(msg, pbField(2, 0, role)...) - msg = append(msg, pbField(13, 2, messageID)...) - if chatModeEnum != nil { - msg = append(msg, pbField(47, 0, *chatModeEnum)...) - } - return msg -} - -func encodeCursorModel(modelName string) []byte { - msg := pbField(1, 2, modelName) - msg = append(msg, pbField(4, 2, []byte{})...) - return msg -} - -func encodeCursorSetting() []byte { - inner := pbField(1, 2, []byte{}) - inner = append(inner, pbField(2, 2, []byte{})...) - msg := pbField(1, 2, `cursor\aisettings`) - msg = append(msg, pbField(3, 2, []byte{})...) - msg = append(msg, pbField(6, 2, inner)...) - msg = append(msg, pbField(8, 0, 1)...) - msg = append(msg, pbField(9, 0, 1)...) - return msg -} - -func encodeCursorMetadata() []byte { - msg := pbField(1, 2, cursorClientOS()) - msg = append(msg, pbField(2, 2, cursorClientArch())...) - msg = append(msg, pbField(3, 2, "unknown")...) - msg = append(msg, pbField(4, 2, "go-platform/tokenprobe")...) - msg = append(msg, pbField(5, 2, time.Now().Format(time.RFC3339))...) - return msg -} - -func encodeCursorMessageID(messageID string, role int) []byte { - msg := pbField(1, 2, messageID) - msg = append(msg, pbField(3, 0, role)...) - return msg -} - -// defaultAgentTools 与 cursor_agent_client.DEFAULT_TOOLS 一致 -var defaultAgentTools = []int{5, 6, 3, 15, 7, 8, 42} - -func encodeCursorAgentRequest(userContent, modelName string) []byte { - msgID := uuid.NewString() - cm := 2 // Agent - userMsg := encodeCursorMessage(userContent, 1, msgID, &cm) - - var msg []byte - msg = append(msg, pbField(1, 2, userMsg)...) - msg = append(msg, pbField(2, 0, 1)...) - msg = append(msg, pbField(3, 2, []byte{})...) - msg = append(msg, pbField(4, 0, 1)...) - msg = append(msg, pbField(5, 2, encodeCursorModel(modelName))...) - msg = append(msg, pbField(8, 2, "")...) - msg = append(msg, pbField(13, 0, 1)...) - msg = append(msg, pbField(15, 2, encodeCursorSetting())...) - msg = append(msg, pbField(19, 0, 1)...) - msg = append(msg, pbField(23, 2, uuid.NewString())...) - msg = append(msg, pbField(26, 2, encodeCursorMetadata())...) - msg = append(msg, pbField(27, 0, 1)...) - for _, t := range defaultAgentTools { - msg = append(msg, pbField(29, 0, t)...) - } - msg = append(msg, pbField(30, 2, encodeCursorMessageID(msgID, 1))...) - msg = append(msg, pbField(35, 0, 0)...) - msg = append(msg, pbField(38, 0, 0)...) - msg = append(msg, pbField(46, 0, 2)...) - msg = append(msg, pbField(47, 2, "")...) - msg = append(msg, pbField(48, 0, 0)...) - msg = append(msg, pbField(49, 0, 0)...) - msg = append(msg, pbField(51, 0, 0)...) - msg = append(msg, pbField(53, 0, 1)...) - msg = append(msg, pbField(54, 2, "agent")...) - return msg -} - -func encodeStreamUnifiedChatWithToolsRequest(inner []byte) []byte { - return pbField(1, 2, inner) -} - -func generateCursorAgentFramedBody(userText, model string) []byte { - inner := encodeCursorAgentRequest(userText, model) - buf := encodeStreamUnifiedChatWithToolsRequest(inner) - magic := byte(0x00) - hexLen := fmt.Sprintf("%08x", len(buf)) - lenBytes, err := hex.DecodeString(hexLen) - if err != nil || len(lenBytes) != 4 { - lenB := []byte{byte(len(buf) >> 24), byte(len(buf) >> 16), byte(len(buf) >> 8), byte(len(buf))} - return append([]byte{magic}, append(lenB, buf...)...) - } - return append([]byte{magic}, append(lenBytes, buf...)...) -} - -func hashed64Hex(input, salt string) string { - h := sha256.Sum256([]byte(input + salt)) - return hex.EncodeToString(h[:]) -} - -func generateCursorChecksum(authToken string) string { - machineID := hashed64Hex(authToken, "machineId") - ts := int(time.Now().UnixMilli() / 1_000_000) - barr := []byte{ - byte(ts >> 40), byte(ts >> 32), byte(ts >> 24), byte(ts >> 16), byte(ts >> 8), byte(ts), - } - t := byte(165) - for i := range barr { - barr[i] = ((barr[i] ^ t) + byte(i%256)) & 255 - t = barr[i] - } - const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" - var enc strings.Builder - for i := 0; i < len(barr); i += 3 { - a := barr[i] - var b, c byte - if i+1 < len(barr) { - b = barr[i+1] - } - if i+2 < len(barr) { - c = barr[i+2] - } - enc.WriteByte(alphabet[a>>2]) - enc.WriteByte(alphabet[((a&3)<<4)|(b>>4)]) - if i+1 < len(barr) { - enc.WriteByte(alphabet[((b&15)<<2)|(c>>6)]) - } - if i+2 < len(barr) { - enc.WriteByte(alphabet[c&63]) - } - } - return enc.String() + machineID -} - -func asciiLowerInPlace(b []byte) { - for i := range b { - c := b[i] - if c >= 'A' && c <= 'Z' { - b[i] = c + ('a' - 'A') - } - } -} - -// 社区脚本中的「额度用尽」ASCII 前缀(与明文一致,便于在二进制流中 bytes.Contains,无需整句) -// 对应明文前缀:Get Cursor Pro for more Agent usage -var cursorQuotaExhaustedSigCommunity = []byte{ - 0x47, 0x65, 0x74, 0x20, 0x43, 0x75, 0x72, 0x73, - 0x6f, 0x72, 0x20, 0x50, 0x72, 0x6f, 0x20, 0x66, - 0x6f, 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, - 0x41, 0x67, 0x65, 0x6e, 0x74, 0x20, 0x75, 0x73, - 0x61, 0x67, 0x65, -} - -// cursorQuotaTipSig 与常见示例一致:raw 全字节里 bytes.Contains(raw, tipSig) → 额度用尽 -var cursorQuotaTipSig = []byte("Get Cursor Pro for more Agent usage, unlimited Tab, and more.") - -const cursorLimitTipPrefix = "Get Cursor Pro for more Agent usage, unlimited Tab" - -// classifyCursorRawStream 在官方流式二进制/文本中匹配用量与升级提示(ASCII 区不区分大小写 + UTF-8 短语) -func classifyCursorRawStream(raw []byte) (blocked bool, reason string) { - if len(raw) == 0 { - return false, "" - } - for _, sig := range cursorQuotaExhaustedSigsFromEnv() { - if bytes.Contains(raw, sig) { - return true, fmt.Sprintf("流中匹配:CURSOR_QUOTA_EXHAUSTED_SIG_HEX 配置的二进制特征(%d 字节)", len(sig)) - } - } - if bytes.Contains(raw, cursorQuotaTipSig) { - return true, "流中匹配:" + string(cursorQuotaTipSig) - } - // 社区脚本:仅到「…Agent usage」的 ASCII 前缀(流里可能只有前半段) - if bytes.Contains(raw, cursorQuotaExhaustedSigCommunity) { - return true, "流中匹配:Get Cursor Pro for more Agent usage…(社区 QuotaExhaustedSignature 前缀)" - } - if bytes.Contains(raw, []byte(cursorLimitTipPrefix)) { - return true, "流中匹配:" + cursorLimitTipPrefix + "…" - } - low := append([]byte(nil), raw...) - asciiLowerInPlace(low) - if bytes.Contains(low, []byte("you've hit your usage limit")) || - bytes.Contains(low, []byte("youve hit your usage limit")) || - bytes.Contains(low, []byte("hit your usage limit")) { - return true, "流中匹配:hit your usage limit / you've hit your usage limit" - } - if bytes.Contains(low, []byte("get cursor pro for more agent usage")) { - return true, "流中匹配:get cursor pro for more agent usage" - } - if bytes.Contains(low, []byte("upgrade to pro")) { - return true, "流中匹配:upgrade to pro" - } - if bytes.Contains(low, []byte("get cursor pro")) && bytes.Contains(low, []byte("agent")) { - return true, "流中匹配:get cursor pro + agent" - } - if bytes.Contains(low, []byte("usage limit")) { - return true, "流中匹配:usage limit" - } - if bytes.Contains(low, []byte("unlimited tab")) && bytes.Contains(low, []byte("cursor pro")) { - return true, "流中匹配:unlimited tab + cursor pro" - } - - flat := strings.ToLower(strings.ToValidUTF8(string(raw), "\uFFFD")) - flat = strings.ReplaceAll(flat, "\u2019", "'") // 右单引号 - flat = strings.ReplaceAll(flat, "`", "'") - if strings.Contains(flat, "you've hit your usage limit") { - return true, "流中匹配:you've hit your usage limit(UTF-8)" - } - return false, "" -} - -func truncateUTF8Preview(raw []byte, maxBytes int) string { - s := strings.ToValidUTF8(string(raw), "\uFFFD") - if maxBytes <= 0 || len(s) <= maxBytes { - return s - } - // 按字节截断并保证合法 UTF-8 - s = s[:maxBytes] - for len(s) > 0 && !utf8.ValidString(s) { - s = s[:len(s)-1] - } - return s + "…(已截断)" -} - -func prefixHexBody(b []byte, max int) string { - if len(b) > max { - b = b[:max] - } - return hex.EncodeToString(b) -} - -func looksLikeGzip(raw []byte) bool { - return len(raw) >= 3 && raw[0] == 0x1f && raw[1] == 0x8b && raw[2] == 0x08 -} - -func gunzipBytes(raw []byte) ([]byte, error) { - zr, err := gzip.NewReader(bytes.NewReader(raw)) - if err != nil { - return nil, err - } - defer zr.Close() - return io.ReadAll(io.LimitReader(zr, cursorHiMaxRead)) -} - -func decodeConnectFramedBody(raw []byte) ([]byte, string, bool) { - if len(raw) < 5 { - return nil, "", false - } - - var out bytes.Buffer - offset := 0 - frameCount := 0 - compressedFrames := 0 - - for offset+5 <= len(raw) { - flags := raw[offset] - n := int(raw[offset+1])<<24 | int(raw[offset+2])<<16 | int(raw[offset+3])<<8 | int(raw[offset+4]) - offset += 5 - if n < 0 || offset+n > len(raw) { - return nil, "", false - } - payload := raw[offset : offset+n] - offset += n - frameCount++ - - isCompressed := flags&0x01 == 0x01 - if isCompressed || looksLikeGzip(payload) { - decoded, err := gunzipBytes(payload) - if err != nil { - out.Write(payload) - } else { - out.Write(decoded) - compressedFrames++ - } - } else { - out.Write(payload) - } - } - - if frameCount == 0 || offset != len(raw) { - return nil, "", false - } - - note := fmt.Sprintf("响应体已按 Connect 分帧解析(%d 帧", frameCount) - if compressedFrames > 0 { - note += fmt.Sprintf(",其中 %d 帧已做 gzip 解压", compressedFrames) - } - note += ")后分析" - return out.Bytes(), note, true -} - -func decodeCursorResponseBody(raw []byte, contentEncoding string) ([]byte, string) { - if decoded, note, ok := decodeConnectFramedBody(raw); ok { - return decoded, note - } - - enc := strings.ToLower(strings.TrimSpace(contentEncoding)) - if strings.Contains(enc, "gzip") || looksLikeGzip(raw) { - decoded, err := gunzipBytes(raw) - if err != nil { - if strings.Contains(enc, "gzip") { - return raw, "响应头声明 gzip,但解压失败,已回退为原始字节预览" - } - return raw, "检测到 gzip 魔数,但解压失败,已回退为原始字节预览" - } - if strings.Contains(enc, "gzip") { - return decoded, "响应体已按 gzip 解压后分析" - } - return decoded, "响应体虽未显式声明 Content-Encoding,但按 gzip 魔数解压后分析" - } - if enc != "" { - return raw, "响应头 Content-Encoding=" + enc + ",当前未额外解码,按原始字节分析" - } - return raw, "响应体未压缩或未声明压缩,且未识别为 Connect 分帧,按原始字节分析" -} - -// cursorStreamProtocol 与官方客户端一致:Connect-RPC + protobuf 体,HTTP/2 流式 -const cursorStreamProtocol = "Connect-Protocol-Version:1 + application/connect+proto,HTTP/2 二进制流(gRPC 兼容形态,非 JSON REST)" - -// cursorStreamNote 说明 rawPreview / ok 的含义边界(与「仅通 200」结论一致) -const cursorStreamNote = `【协议】本 URL 为 Cursor 官方 Agent 流式接口,请求体为 protobuf(requestBodyPrefixHex 可见非表单/JSON)。` + - `【响应】正文为分包二进制流,rawPreview 是按 UTF-8 有损解码的片段,绝大多数情况下会像乱码,属正常现象,不能当普通 UTF-8 接口正文解析。` + - `【HTTP 200】仅表示 TLS/代理/网络到 api2.cursor.sh 通畅,不代表 protobuf 业务层、鉴权、设备指纹、风控配额或「能持续对话」已全部通过。` + - `【ok 字段】当前仅在解码片段上做英文关键词启发式匹配;未命中不代表账户可用,命中也不覆盖「须在 IDE 内完整走流式协议」的场景。` + - `【若要等价客户端】需完整实现 Connect 帧解析、会话与校验头、可能的 gzip/分包及双向流,本探测只做粗连通与可观测性辅助。` + - `【二进制特征】若提示语被包在 protobuf 字段内、ASCII 子串匹配不到,可在运行环境设置 CURSOR_QUOTA_EXHAUSTED_SIG_HEX=hex1,hex2(逗号分隔十六进制,可选 0x 前缀),在原始响应字节上做 bytes.Contains,无需解析整条 proto;特征需自行对比「额度正常」与「用尽」两次抓包提取。` - -// cursorQuotaExhaustedSigsFromEnv 从环境变量解析额度用尽时的二进制特征(不转 UTF-8) -func cursorQuotaExhaustedSigsFromEnv() [][]byte { - s := strings.TrimSpace(os.Getenv("CURSOR_QUOTA_EXHAUSTED_SIG_HEX")) - if s == "" { - return nil - } - var out [][]byte - for _, part := range strings.Split(s, ",") { - part = strings.TrimSpace(part) - if part == "" { - continue - } - part = strings.TrimPrefix(strings.TrimPrefix(part, "0x"), "0X") - b, err := hex.DecodeString(part) - if err != nil || len(b) == 0 { - continue - } - out = append(out, b) - } - return out -} - -func cursorProbeResult(ok bool, detail string, httpStatus int, reqBody, raw, preview []byte) Result { - if preview == nil { - preview = raw - } - return Result{ - OK: ok, - Detail: detail, - HTTPStatus: httpStatus, - ProbeMessage: probeHiText, - Endpoint: cursorBackendURL + cursorAgentPath, - BytesRead: len(raw), - RawPreview: truncateUTF8Preview(preview, 24000), - RequestBodyPrefixHex: prefixHexBody(reqBody, 128), - StreamProtocol: cursorStreamProtocol, - StreamNote: cursorStreamNote, - } -} - -func probeCursorHiAgent(authToken string) Result { - if strings.Contains(authToken, "::") { - if i := strings.LastIndex(authToken, "::"); i >= 0 { - authToken = strings.TrimSpace(authToken[i+2:]) - } - } - if authToken == "" { - return Result{OK: false, Detail: "Token 为空"} - } - - sessionID := uuid.NewSHA1(uuid.NameSpaceDNS, []byte(authToken)).String() - clientKey := hashed64Hex(authToken, "") - checksum := generateCursorChecksum(authToken) - conversationID := uuid.NewString() - reqID := uuid.NewString() - - body := generateCursorAgentFramedBody(probeHiText, "default") - fullURL := cursorBackendURL + cursorAgentPath - req, err := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(body)) - if err != nil { - r := cursorProbeResult(false, err.Error(), 0, body, nil, nil) - return r - } - req.Header.Set("Authorization", "Bearer "+authToken) - req.Header.Set("Connect-Accept-Encoding", "gzip") - req.Header.Set("Connect-Protocol-Version", "1") - req.Header.Set("Content-Type", "application/connect+proto") - req.Header.Set("User-Agent", "connect-es/1.6.1") - req.Header.Set("X-Amzn-Trace-Id", "Root="+reqID) - req.Header.Set("X-Client-Key", clientKey) - req.Header.Set("X-Cursor-Checksum", checksum) - req.Header.Set("X-Cursor-Client-Version", cursorEnvVersion()) - req.Header.Set("X-Cursor-Client-Type", "ide") - req.Header.Set("X-Cursor-Client-Os", cursorClientOS()) - req.Header.Set("X-Cursor-Client-Arch", cursorClientArch()) - req.Header.Set("X-Cursor-Client-Os-Version", "unknown") - req.Header.Set("X-Cursor-Client-Device-Type", "desktop") - req.Header.Set("X-Cursor-Config-Version", uuid.NewString()) - req.Header.Set("X-Cursor-Timezone", "UTC") - req.Header.Set("X-Ghost-Mode", "false") - req.Header.Set("X-New-Onboarding-Completed", "true") - req.Header.Set("X-Request-Id", reqID) - req.Header.Set("X-Session-Id", sessionID) - req.Header.Set("X-Conversation-Id", conversationID) - req.Host = "api2.cursor.sh" - - resp, err := cursorProbeHTTPClient.Do(req) - if err != nil { - return cursorProbeResult(false, "请求 Cursor Agent 失败: "+err.Error(), 0, body, nil, nil) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - raw, _ := io.ReadAll(io.LimitReader(resp.Body, cursorHiMaxRead)) - decoded, decodeNote := decodeCursorResponseBody(raw, resp.Header.Get("Content-Encoding")) - blocked, reason := classifyCursorRawStream(decoded) - if blocked { - return cursorProbeResult(false, reason+";"+decodeNote, resp.StatusCode, body, raw, decoded) - } - detail := fmt.Sprintf("HTTP %d(非 200);%s;说明与协议边界见 streamNote", resp.StatusCode, decodeNote) - return cursorProbeResult(false, detail, resp.StatusCode, body, raw, decoded) - } - - var buf bytes.Buffer - _, _ = io.Copy(&buf, io.LimitReader(resp.Body, cursorHiMaxRead)) - raw := buf.Bytes() - decoded, decodeNote := decodeCursorResponseBody(raw, resp.Header.Get("Content-Encoding")) - blocked, reason := classifyCursorRawStream(decoded) - if blocked { - return cursorProbeResult(false, reason+";"+decodeNote, resp.StatusCode, body, raw, decoded) - } - detail := "HTTP 200;未命中内置英文关键词;" + decodeNote + ";二进制流含义与 ok 边界见 streamNote" - return cursorProbeResult(true, detail, resp.StatusCode, body, raw, decoded) -} +package tokenprobe + +import ( + "bytes" + "compress/gzip" + "crypto/sha256" + "crypto/tls" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + "runtime" + "strings" + "time" + "unicode/utf8" + + "github.com/google/uuid" + "golang.org/x/net/http2" +) + +const ( + cursorBackendURL = "https://api2.cursor.sh" + cursorAgentPath = "/aiserver.v1.ChatService/StreamUnifiedChatWithTools" + cursorClientVersion = "2.6.22" + cursorHiMaxRead = 512 * 1024 + // probeHiText 发往官方 Agent 的探测内容(与前端展示 probeMessage 一致) + probeHiText = "hi" +) + +var cursorProbeHTTPClient = newCursorHTTP2Client() + +func newCursorHTTP2Client() *http.Client { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12}, + } + // 与 Cursor 官方一致走 HTTP/2 + if err := http2.ConfigureTransport(tr); err != nil { + return &http.Client{Timeout: 40 * time.Second} + } + return &http.Client{Transport: tr, Timeout: 40 * time.Second} +} + +func cursorClientOS() string { + switch runtime.GOOS { + case "windows": + return "win32" + case "darwin": + return "darwin" + default: + return "linux" + } +} + +func cursorClientArch() string { + switch runtime.GOARCH { + case "amd64": + return "x64" + case "arm64": + return "arm64" + default: + return runtime.GOARCH + } +} + +func cursorEnvVersion() string { + if v := strings.TrimSpace(os.Getenv("CURSOR_CLIENT_VERSION")); v != "" { + return v + } + return cursorClientVersion +} + +// --- protobuf wire (与 cursor_api_demo 对齐) --- + +func pbVarint(v uint64) []byte { + var out []byte + for v >= 0x80 { + out = append(out, byte(v&0x7f|0x80)) + v >>= 7 + } + out = append(out, byte(v&0x7f)) + return out +} + +func pbField(fieldNum int, wireType int, value interface{}) []byte { + tag := uint64(fieldNum<<3 | wireType) + out := pbVarint(tag) + switch wireType { + case 0: + var n uint64 + switch x := value.(type) { + case int: + n = uint64(x) + case int32: + n = uint64(x) + case uint32: + n = uint64(x) + case uint64: + n = x + default: + n = uint64(0) + } + out = append(out, pbVarint(n)...) + case 2: + var b []byte + switch x := value.(type) { + case string: + b = []byte(x) + case []byte: + b = x + default: + b = []byte(fmt.Sprint(x)) + } + out = append(out, pbVarint(uint64(len(b)))...) + out = append(out, b...) + } + return out +} + +func encodeCursorMessage(content string, role int, messageID string, chatModeEnum *int) []byte { + msg := pbField(1, 2, content) + msg = append(msg, pbField(2, 0, role)...) + msg = append(msg, pbField(13, 2, messageID)...) + if chatModeEnum != nil { + msg = append(msg, pbField(47, 0, *chatModeEnum)...) + } + return msg +} + +func encodeCursorModel(modelName string) []byte { + msg := pbField(1, 2, modelName) + msg = append(msg, pbField(4, 2, []byte{})...) + return msg +} + +func encodeCursorSetting() []byte { + inner := pbField(1, 2, []byte{}) + inner = append(inner, pbField(2, 2, []byte{})...) + msg := pbField(1, 2, `cursor\aisettings`) + msg = append(msg, pbField(3, 2, []byte{})...) + msg = append(msg, pbField(6, 2, inner)...) + msg = append(msg, pbField(8, 0, 1)...) + msg = append(msg, pbField(9, 0, 1)...) + return msg +} + +func encodeCursorMetadata() []byte { + msg := pbField(1, 2, cursorClientOS()) + msg = append(msg, pbField(2, 2, cursorClientArch())...) + msg = append(msg, pbField(3, 2, "unknown")...) + msg = append(msg, pbField(4, 2, "go-platform/tokenprobe")...) + msg = append(msg, pbField(5, 2, time.Now().Format(time.RFC3339))...) + return msg +} + +func encodeCursorMessageID(messageID string, role int) []byte { + msg := pbField(1, 2, messageID) + msg = append(msg, pbField(3, 0, role)...) + return msg +} + +// defaultAgentTools 与 cursor_agent_client.DEFAULT_TOOLS 一致 +var defaultAgentTools = []int{5, 6, 3, 15, 7, 8, 42} + +func encodeCursorAgentRequest(userContent, modelName string) []byte { + msgID := uuid.NewString() + cm := 2 // Agent + userMsg := encodeCursorMessage(userContent, 1, msgID, &cm) + + var msg []byte + msg = append(msg, pbField(1, 2, userMsg)...) + msg = append(msg, pbField(2, 0, 1)...) + msg = append(msg, pbField(3, 2, []byte{})...) + msg = append(msg, pbField(4, 0, 1)...) + msg = append(msg, pbField(5, 2, encodeCursorModel(modelName))...) + msg = append(msg, pbField(8, 2, "")...) + msg = append(msg, pbField(13, 0, 1)...) + msg = append(msg, pbField(15, 2, encodeCursorSetting())...) + msg = append(msg, pbField(19, 0, 1)...) + msg = append(msg, pbField(23, 2, uuid.NewString())...) + msg = append(msg, pbField(26, 2, encodeCursorMetadata())...) + msg = append(msg, pbField(27, 0, 1)...) + for _, t := range defaultAgentTools { + msg = append(msg, pbField(29, 0, t)...) + } + msg = append(msg, pbField(30, 2, encodeCursorMessageID(msgID, 1))...) + msg = append(msg, pbField(35, 0, 0)...) + msg = append(msg, pbField(38, 0, 0)...) + msg = append(msg, pbField(46, 0, 2)...) + msg = append(msg, pbField(47, 2, "")...) + msg = append(msg, pbField(48, 0, 0)...) + msg = append(msg, pbField(49, 0, 0)...) + msg = append(msg, pbField(51, 0, 0)...) + msg = append(msg, pbField(53, 0, 1)...) + msg = append(msg, pbField(54, 2, "agent")...) + return msg +} + +func encodeStreamUnifiedChatWithToolsRequest(inner []byte) []byte { + return pbField(1, 2, inner) +} + +func generateCursorAgentFramedBody(userText, model string) []byte { + inner := encodeCursorAgentRequest(userText, model) + buf := encodeStreamUnifiedChatWithToolsRequest(inner) + magic := byte(0x00) + hexLen := fmt.Sprintf("%08x", len(buf)) + lenBytes, err := hex.DecodeString(hexLen) + if err != nil || len(lenBytes) != 4 { + lenB := []byte{byte(len(buf) >> 24), byte(len(buf) >> 16), byte(len(buf) >> 8), byte(len(buf))} + return append([]byte{magic}, append(lenB, buf...)...) + } + return append([]byte{magic}, append(lenBytes, buf...)...) +} + +func hashed64Hex(input, salt string) string { + h := sha256.Sum256([]byte(input + salt)) + return hex.EncodeToString(h[:]) +} + +func generateCursorChecksum(authToken string) string { + machineID := hashed64Hex(authToken, "machineId") + ts := int(time.Now().UnixMilli() / 1_000_000) + barr := []byte{ + byte(ts >> 40), byte(ts >> 32), byte(ts >> 24), byte(ts >> 16), byte(ts >> 8), byte(ts), + } + t := byte(165) + for i := range barr { + barr[i] = ((barr[i] ^ t) + byte(i%256)) & 255 + t = barr[i] + } + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + var enc strings.Builder + for i := 0; i < len(barr); i += 3 { + a := barr[i] + var b, c byte + if i+1 < len(barr) { + b = barr[i+1] + } + if i+2 < len(barr) { + c = barr[i+2] + } + enc.WriteByte(alphabet[a>>2]) + enc.WriteByte(alphabet[((a&3)<<4)|(b>>4)]) + if i+1 < len(barr) { + enc.WriteByte(alphabet[((b&15)<<2)|(c>>6)]) + } + if i+2 < len(barr) { + enc.WriteByte(alphabet[c&63]) + } + } + return enc.String() + machineID +} + +func asciiLowerInPlace(b []byte) { + for i := range b { + c := b[i] + if c >= 'A' && c <= 'Z' { + b[i] = c + ('a' - 'A') + } + } +} + +// 社区脚本中的「额度用尽」ASCII 前缀(与明文一致,便于在二进制流中 bytes.Contains,无需整句) +// 对应明文前缀:Get Cursor Pro for more Agent usage +var cursorQuotaExhaustedSigCommunity = []byte{ + 0x47, 0x65, 0x74, 0x20, 0x43, 0x75, 0x72, 0x73, + 0x6f, 0x72, 0x20, 0x50, 0x72, 0x6f, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x20, 0x75, 0x73, + 0x61, 0x67, 0x65, +} + +// cursorQuotaTipSig 与常见示例一致:raw 全字节里 bytes.Contains(raw, tipSig) → 额度用尽 +var cursorQuotaTipSig = []byte("Get Cursor Pro for more Agent usage, unlimited Tab, and more.") + +const cursorLimitTipPrefix = "Get Cursor Pro for more Agent usage, unlimited Tab" + +// classifyCursorRawStream 在官方流式二进制/文本中匹配用量与升级提示(ASCII 区不区分大小写 + UTF-8 短语) +func classifyCursorRawStream(raw []byte) (blocked bool, reason string) { + if len(raw) == 0 { + return false, "" + } + for _, sig := range cursorQuotaExhaustedSigsFromEnv() { + if bytes.Contains(raw, sig) { + return true, fmt.Sprintf("流中匹配:CURSOR_QUOTA_EXHAUSTED_SIG_HEX 配置的二进制特征(%d 字节)", len(sig)) + } + } + if bytes.Contains(raw, cursorQuotaTipSig) { + return true, "流中匹配:" + string(cursorQuotaTipSig) + } + // 社区脚本:仅到「…Agent usage」的 ASCII 前缀(流里可能只有前半段) + if bytes.Contains(raw, cursorQuotaExhaustedSigCommunity) { + return true, "流中匹配:Get Cursor Pro for more Agent usage…(社区 QuotaExhaustedSignature 前缀)" + } + if bytes.Contains(raw, []byte(cursorLimitTipPrefix)) { + return true, "流中匹配:" + cursorLimitTipPrefix + "…" + } + low := append([]byte(nil), raw...) + asciiLowerInPlace(low) + if bytes.Contains(low, []byte("you've hit your usage limit")) || + bytes.Contains(low, []byte("youve hit your usage limit")) || + bytes.Contains(low, []byte("hit your usage limit")) { + return true, "流中匹配:hit your usage limit / you've hit your usage limit" + } + if bytes.Contains(low, []byte("get cursor pro for more agent usage")) { + return true, "流中匹配:get cursor pro for more agent usage" + } + if bytes.Contains(low, []byte("upgrade to pro")) { + return true, "流中匹配:upgrade to pro" + } + if bytes.Contains(low, []byte("get cursor pro")) && bytes.Contains(low, []byte("agent")) { + return true, "流中匹配:get cursor pro + agent" + } + if bytes.Contains(low, []byte("usage limit")) { + return true, "流中匹配:usage limit" + } + if bytes.Contains(low, []byte("unlimited tab")) && bytes.Contains(low, []byte("cursor pro")) { + return true, "流中匹配:unlimited tab + cursor pro" + } + + flat := strings.ToLower(strings.ToValidUTF8(string(raw), "\uFFFD")) + flat = strings.ReplaceAll(flat, "\u2019", "'") // 右单引号 + flat = strings.ReplaceAll(flat, "`", "'") + if strings.Contains(flat, "you've hit your usage limit") { + return true, "流中匹配:you've hit your usage limit(UTF-8)" + } + return false, "" +} + +func truncateUTF8Preview(raw []byte, maxBytes int) string { + s := strings.ToValidUTF8(string(raw), "\uFFFD") + if maxBytes <= 0 || len(s) <= maxBytes { + return s + } + // 按字节截断并保证合法 UTF-8 + s = s[:maxBytes] + for len(s) > 0 && !utf8.ValidString(s) { + s = s[:len(s)-1] + } + return s + "…(已截断)" +} + +func prefixHexBody(b []byte, max int) string { + if len(b) > max { + b = b[:max] + } + return hex.EncodeToString(b) +} + +func looksLikeGzip(raw []byte) bool { + return len(raw) >= 3 && raw[0] == 0x1f && raw[1] == 0x8b && raw[2] == 0x08 +} + +func gunzipBytes(raw []byte) ([]byte, error) { + zr, err := gzip.NewReader(bytes.NewReader(raw)) + if err != nil { + return nil, err + } + defer zr.Close() + return io.ReadAll(io.LimitReader(zr, cursorHiMaxRead)) +} + +func decodeConnectFramedBody(raw []byte) ([]byte, string, bool) { + if len(raw) < 5 { + return nil, "", false + } + + var out bytes.Buffer + offset := 0 + frameCount := 0 + compressedFrames := 0 + + for offset+5 <= len(raw) { + flags := raw[offset] + n := int(raw[offset+1])<<24 | int(raw[offset+2])<<16 | int(raw[offset+3])<<8 | int(raw[offset+4]) + offset += 5 + if n < 0 || offset+n > len(raw) { + return nil, "", false + } + payload := raw[offset : offset+n] + offset += n + frameCount++ + + isCompressed := flags&0x01 == 0x01 + if isCompressed || looksLikeGzip(payload) { + decoded, err := gunzipBytes(payload) + if err != nil { + out.Write(payload) + } else { + out.Write(decoded) + compressedFrames++ + } + } else { + out.Write(payload) + } + } + + if frameCount == 0 || offset != len(raw) { + return nil, "", false + } + + note := fmt.Sprintf("响应体已按 Connect 分帧解析(%d 帧", frameCount) + if compressedFrames > 0 { + note += fmt.Sprintf(",其中 %d 帧已做 gzip 解压", compressedFrames) + } + note += ")后分析" + return out.Bytes(), note, true +} + +func decodeCursorResponseBody(raw []byte, contentEncoding string) ([]byte, string) { + if decoded, note, ok := decodeConnectFramedBody(raw); ok { + return decoded, note + } + + enc := strings.ToLower(strings.TrimSpace(contentEncoding)) + if strings.Contains(enc, "gzip") || looksLikeGzip(raw) { + decoded, err := gunzipBytes(raw) + if err != nil { + if strings.Contains(enc, "gzip") { + return raw, "响应头声明 gzip,但解压失败,已回退为原始字节预览" + } + return raw, "检测到 gzip 魔数,但解压失败,已回退为原始字节预览" + } + if strings.Contains(enc, "gzip") { + return decoded, "响应体已按 gzip 解压后分析" + } + return decoded, "响应体虽未显式声明 Content-Encoding,但按 gzip 魔数解压后分析" + } + if enc != "" { + return raw, "响应头 Content-Encoding=" + enc + ",当前未额外解码,按原始字节分析" + } + return raw, "响应体未压缩或未声明压缩,且未识别为 Connect 分帧,按原始字节分析" +} + +// cursorStreamProtocol 与官方客户端一致:Connect-RPC + protobuf 体,HTTP/2 流式 +const cursorStreamProtocol = "Connect-Protocol-Version:1 + application/connect+proto,HTTP/2 二进制流(gRPC 兼容形态,非 JSON REST)" + +// cursorStreamNote 说明 rawPreview / ok 的含义边界(与「仅通 200」结论一致) +const cursorStreamNote = `【协议】本 URL 为 Cursor 官方 Agent 流式接口,请求体为 protobuf(requestBodyPrefixHex 可见非表单/JSON)。` + + `【响应】正文为分包二进制流,rawPreview 是按 UTF-8 有损解码的片段,绝大多数情况下会像乱码,属正常现象,不能当普通 UTF-8 接口正文解析。` + + `【HTTP 200】仅表示 TLS/代理/网络到 api2.cursor.sh 通畅,不代表 protobuf 业务层、鉴权、设备指纹、风控配额或「能持续对话」已全部通过。` + + `【ok 字段】当前仅在解码片段上做英文关键词启发式匹配;未命中不代表账户可用,命中也不覆盖「须在 IDE 内完整走流式协议」的场景。` + + `【若要等价客户端】需完整实现 Connect 帧解析、会话与校验头、可能的 gzip/分包及双向流,本探测只做粗连通与可观测性辅助。` + + `【二进制特征】若提示语被包在 protobuf 字段内、ASCII 子串匹配不到,可在运行环境设置 CURSOR_QUOTA_EXHAUSTED_SIG_HEX=hex1,hex2(逗号分隔十六进制,可选 0x 前缀),在原始响应字节上做 bytes.Contains,无需解析整条 proto;特征需自行对比「额度正常」与「用尽」两次抓包提取。` + +// cursorQuotaExhaustedSigsFromEnv 从环境变量解析额度用尽时的二进制特征(不转 UTF-8) +func cursorQuotaExhaustedSigsFromEnv() [][]byte { + s := strings.TrimSpace(os.Getenv("CURSOR_QUOTA_EXHAUSTED_SIG_HEX")) + if s == "" { + return nil + } + var out [][]byte + for _, part := range strings.Split(s, ",") { + part = strings.TrimSpace(part) + if part == "" { + continue + } + part = strings.TrimPrefix(strings.TrimPrefix(part, "0x"), "0X") + b, err := hex.DecodeString(part) + if err != nil || len(b) == 0 { + continue + } + out = append(out, b) + } + return out +} + +func cursorProbeResult(ok bool, detail string, httpStatus int, reqBody, raw, preview []byte) Result { + if preview == nil { + preview = raw + } + return Result{ + OK: ok, + Detail: detail, + HTTPStatus: httpStatus, + ProbeMessage: probeHiText, + Endpoint: cursorBackendURL + cursorAgentPath, + BytesRead: len(raw), + RawPreview: truncateUTF8Preview(preview, 24000), + RequestBodyPrefixHex: prefixHexBody(reqBody, 128), + StreamProtocol: cursorStreamProtocol, + StreamNote: cursorStreamNote, + } +} + +func probeCursorHiAgent(authToken string) Result { + if strings.Contains(authToken, "::") { + if i := strings.LastIndex(authToken, "::"); i >= 0 { + authToken = strings.TrimSpace(authToken[i+2:]) + } + } + if authToken == "" { + return Result{OK: false, Detail: "Token 为空"} + } + + sessionID := uuid.NewSHA1(uuid.NameSpaceDNS, []byte(authToken)).String() + clientKey := hashed64Hex(authToken, "") + checksum := generateCursorChecksum(authToken) + conversationID := uuid.NewString() + reqID := uuid.NewString() + + body := generateCursorAgentFramedBody(probeHiText, "default") + fullURL := cursorBackendURL + cursorAgentPath + req, err := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(body)) + if err != nil { + r := cursorProbeResult(false, err.Error(), 0, body, nil, nil) + return r + } + req.Header.Set("Authorization", "Bearer "+authToken) + req.Header.Set("Connect-Accept-Encoding", "gzip") + req.Header.Set("Connect-Protocol-Version", "1") + req.Header.Set("Content-Type", "application/connect+proto") + req.Header.Set("User-Agent", "connect-es/1.6.1") + req.Header.Set("X-Amzn-Trace-Id", "Root="+reqID) + req.Header.Set("X-Client-Key", clientKey) + req.Header.Set("X-Cursor-Checksum", checksum) + req.Header.Set("X-Cursor-Client-Version", cursorEnvVersion()) + req.Header.Set("X-Cursor-Client-Type", "ide") + req.Header.Set("X-Cursor-Client-Os", cursorClientOS()) + req.Header.Set("X-Cursor-Client-Arch", cursorClientArch()) + req.Header.Set("X-Cursor-Client-Os-Version", "unknown") + req.Header.Set("X-Cursor-Client-Device-Type", "desktop") + req.Header.Set("X-Cursor-Config-Version", uuid.NewString()) + req.Header.Set("X-Cursor-Timezone", "UTC") + req.Header.Set("X-Ghost-Mode", "false") + req.Header.Set("X-New-Onboarding-Completed", "true") + req.Header.Set("X-Request-Id", reqID) + req.Header.Set("X-Session-Id", sessionID) + req.Header.Set("X-Conversation-Id", conversationID) + req.Host = "api2.cursor.sh" + + resp, err := cursorProbeHTTPClient.Do(req) + if err != nil { + return cursorProbeResult(false, "请求 Cursor Agent 失败: "+err.Error(), 0, body, nil, nil) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + raw, _ := io.ReadAll(io.LimitReader(resp.Body, cursorHiMaxRead)) + decoded, decodeNote := decodeCursorResponseBody(raw, resp.Header.Get("Content-Encoding")) + blocked, reason := classifyCursorRawStream(decoded) + if blocked { + return cursorProbeResult(false, reason+";"+decodeNote, resp.StatusCode, body, raw, decoded) + } + detail := fmt.Sprintf("HTTP %d(非 200);%s;说明与协议边界见 streamNote", resp.StatusCode, decodeNote) + return cursorProbeResult(false, detail, resp.StatusCode, body, raw, decoded) + } + + var buf bytes.Buffer + _, _ = io.Copy(&buf, io.LimitReader(resp.Body, cursorHiMaxRead)) + raw := buf.Bytes() + decoded, decodeNote := decodeCursorResponseBody(raw, resp.Header.Get("Content-Encoding")) + blocked, reason := classifyCursorRawStream(decoded) + if blocked { + return cursorProbeResult(false, reason+";"+decodeNote, resp.StatusCode, body, raw, decoded) + } + detail := "HTTP 200;未命中内置英文关键词;" + decodeNote + ";二进制流含义与 ok 边界见 streamNote" + return cursorProbeResult(true, detail, resp.StatusCode, body, raw, decoded) +} diff --git a/go/pkg/tokenprobe/probe.go b/go/pkg/tokenprobe/probe.go index 0be8602..f07fe3a 100644 --- a/go/pkg/tokenprobe/probe.go +++ b/go/pkg/tokenprobe/probe.go @@ -1,217 +1,217 @@ -package tokenprobe - -import ( - "bytes" - "crypto/tls" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "time" -) - -var httpClient = &http.Client{ - Timeout: 12 * time.Second, - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, -} - -type Result struct { - OK bool `json:"ok"` - Detail string `json:"detail"` - HTTPStatus int `json:"httpStatus"` - ProbeMessage string `json:"probeMessage,omitempty"` - Endpoint string `json:"endpoint,omitempty"` - BytesRead int `json:"bytesRead,omitempty"` - RawPreview string `json:"rawPreview,omitempty"` - RequestBodyPrefixHex string `json:"requestBodyPrefixHex,omitempty"` - StreamProtocol string `json:"streamProtocol,omitempty"` - StreamNote string `json:"streamNote,omitempty"` -} - -func ProbeOfficial(module, rawToken string) Result { - tok := normalizeBearerToken(strings.TrimSpace(rawToken)) - if tok == "" { - return Result{OK: false, Detail: "Token 为空"} - } - switch module { - case "cursor": - return probeCursor(tok) - case "windsurf": - return probeWindsurf(tok) - case "krio": - return probeKiro(tok) - default: - return Result{OK: false, Detail: "未知模块"} - } -} - -func normalizeBearerToken(s string) string { - s = strings.TrimSpace(s) - if i := strings.LastIndex(s, "::"); i >= 0 { - return strings.TrimSpace(s[i+2:]) - } - return s -} - -func probeCursor(token string) Result { - return probeCursorHiAgent(token) -} - -func probeWindsurf(apiKey string) Result { - payload := map[string]interface{}{ - "metadata": map[string]string{ - "apiKey": apiKey, - "ideName": "windsurf", - "ideVersion": "0.0.0", - "extensionName": "windsurf", - "extensionVersion": "0.0.0", - "locale": "zh", - }, - } - raw, err := json.Marshal(payload) - if err != nil { - return Result{OK: false, Detail: err.Error()} - } - req, err := http.NewRequest( - http.MethodPost, - "https://server.codeium.com/exa.seat_management_pb.SeatManagementService/GetUserStatus", - bytes.NewReader(raw), - ) - if err != nil { - return Result{OK: false, Detail: err.Error()} - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Connect-Protocol-Version", "1") - - resp, err := httpClient.Do(req) - if err != nil { - return Result{OK: false, Detail: "请求失败: " + err.Error()} - } - defer resp.Body.Close() - body, _ := io.ReadAll(io.LimitReader(resp.Body, 8192)) - - switch resp.StatusCode { - case http.StatusOK: - var wrap map[string]interface{} - if json.Unmarshal(body, &wrap) == nil { - if _, ok := wrap["userStatus"]; ok { - return Result{OK: true, Detail: "Codeium 云端接口响应正常", HTTPStatus: resp.StatusCode} - } - } - if bytes.Contains(body, []byte(`"planStatus"`)) || bytes.Contains(body, []byte(`"userStatus"`)) { - return Result{OK: true, Detail: "Codeium 云端接口响应正常", HTTPStatus: resp.StatusCode} - } - return Result{OK: true, Detail: fmt.Sprintf("HTTP %d,已收到响应", resp.StatusCode), HTTPStatus: resp.StatusCode} - case http.StatusUnauthorized, http.StatusForbidden: - return Result{OK: false, Detail: fmt.Sprintf("API Key 无效或已失效(HTTP %d)", resp.StatusCode), HTTPStatus: resp.StatusCode} - default: - snip := strings.TrimSpace(string(body)) - if len(snip) > 220 { - snip = snip[:220] + "…" - } - return Result{OK: false, Detail: fmt.Sprintf("HTTP %d %s", resp.StatusCode, snip), HTTPStatus: resp.StatusCode} - } -} - -func probeKiro(accessToken string) Result { - arn := findProfileArnInJWT(accessToken) - if arn == "" { - return Result{ - OK: false, - Detail: "无法从 Token 中解析 profileArn,Kiro 暂无法自动探测", - } - } - - q := url.Values{} - q.Set("origin", "AI_EDITOR") - q.Set("profileArn", arn) - q.Set("resourceType", "AGENTIC_REQUEST") - u := "https://q.us-east-1.amazonaws.com/getUsageLimits?" + q.Encode() - - req, err := http.NewRequest(http.MethodGet, u, nil) - if err != nil { - return Result{OK: false, Detail: err.Error()} - } - req.Header.Set("Authorization", "Bearer "+normalizeBearerToken(accessToken)) - req.Header.Set("Accept", "application/json") - - resp, err := httpClient.Do(req) - if err != nil { - return Result{OK: false, Detail: "请求失败: " + err.Error()} - } - defer resp.Body.Close() - body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) - - switch resp.StatusCode { - case http.StatusOK: - return Result{OK: true, Detail: "Kiro(AWS Q)用量接口响应正常", HTTPStatus: resp.StatusCode} - case http.StatusUnauthorized, http.StatusForbidden: - return Result{OK: false, Detail: fmt.Sprintf("Token 无效或已过期(HTTP %d)", resp.StatusCode), HTTPStatus: resp.StatusCode} - default: - snip := strings.TrimSpace(string(body)) - if len(snip) > 220 { - snip = snip[:220] + "…" - } - return Result{OK: false, Detail: fmt.Sprintf("HTTP %d %s", resp.StatusCode, snip), HTTPStatus: resp.StatusCode} - } -} - -func decodeJWTPayloadMap(raw string) (map[string]interface{}, error) { - tok := normalizeBearerToken(strings.TrimSpace(raw)) - parts := strings.Split(tok, ".") - if len(parts) < 2 { - return nil, fmt.Errorf("not a JWT") - } - b, err := base64.RawURLEncoding.DecodeString(parts[1]) - if err != nil { - return nil, err - } - var m map[string]interface{} - if err := json.Unmarshal(b, &m); err != nil { - return nil, err - } - return m, nil -} - -func findProfileArnInJWT(raw string) string { - m, err := decodeJWTPayloadMap(raw) - if err != nil { - return "" - } - return findProfileArnValue(m) -} - -func findProfileArnValue(v interface{}) string { - switch x := v.(type) { - case map[string]interface{}: - for k, val := range x { - lk := strings.ToLower(k) - if lk == "profilearn" || lk == "profile_arn" { - if s, ok := val.(string); ok && strings.Contains(s, "arn:") { - return s - } - } - } - for _, val := range x { - if s := findProfileArnValue(val); s != "" { - return s - } - } - case []interface{}: - for _, el := range x { - if s := findProfileArnValue(el); s != "" { - return s - } - } - case string: - if strings.Contains(x, "arn:aws:codewhisperer") && strings.Contains(x, ":profile/") { - return x - } - } - return "" -} +package tokenprobe + +import ( + "bytes" + "crypto/tls" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" +) + +var httpClient = &http.Client{ + Timeout: 12 * time.Second, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, +} + +type Result struct { + OK bool `json:"ok"` + Detail string `json:"detail"` + HTTPStatus int `json:"httpStatus"` + ProbeMessage string `json:"probeMessage,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + BytesRead int `json:"bytesRead,omitempty"` + RawPreview string `json:"rawPreview,omitempty"` + RequestBodyPrefixHex string `json:"requestBodyPrefixHex,omitempty"` + StreamProtocol string `json:"streamProtocol,omitempty"` + StreamNote string `json:"streamNote,omitempty"` +} + +func ProbeOfficial(module, rawToken string) Result { + tok := normalizeBearerToken(strings.TrimSpace(rawToken)) + if tok == "" { + return Result{OK: false, Detail: "Token 为空"} + } + switch module { + case "cursor": + return probeCursor(tok) + case "windsurf": + return probeWindsurf(tok) + case "krio": + return probeKiro(tok) + default: + return Result{OK: false, Detail: "未知模块"} + } +} + +func normalizeBearerToken(s string) string { + s = strings.TrimSpace(s) + if i := strings.LastIndex(s, "::"); i >= 0 { + return strings.TrimSpace(s[i+2:]) + } + return s +} + +func probeCursor(token string) Result { + return probeCursorHiAgent(token) +} + +func probeWindsurf(apiKey string) Result { + payload := map[string]interface{}{ + "metadata": map[string]string{ + "apiKey": apiKey, + "ideName": "windsurf", + "ideVersion": "0.0.0", + "extensionName": "windsurf", + "extensionVersion": "0.0.0", + "locale": "zh", + }, + } + raw, err := json.Marshal(payload) + if err != nil { + return Result{OK: false, Detail: err.Error()} + } + req, err := http.NewRequest( + http.MethodPost, + "https://server.codeium.com/exa.seat_management_pb.SeatManagementService/GetUserStatus", + bytes.NewReader(raw), + ) + if err != nil { + return Result{OK: false, Detail: err.Error()} + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Connect-Protocol-Version", "1") + + resp, err := httpClient.Do(req) + if err != nil { + return Result{OK: false, Detail: "请求失败: " + err.Error()} + } + defer resp.Body.Close() + body, _ := io.ReadAll(io.LimitReader(resp.Body, 8192)) + + switch resp.StatusCode { + case http.StatusOK: + var wrap map[string]interface{} + if json.Unmarshal(body, &wrap) == nil { + if _, ok := wrap["userStatus"]; ok { + return Result{OK: true, Detail: "Codeium 云端接口响应正常", HTTPStatus: resp.StatusCode} + } + } + if bytes.Contains(body, []byte(`"planStatus"`)) || bytes.Contains(body, []byte(`"userStatus"`)) { + return Result{OK: true, Detail: "Codeium 云端接口响应正常", HTTPStatus: resp.StatusCode} + } + return Result{OK: true, Detail: fmt.Sprintf("HTTP %d,已收到响应", resp.StatusCode), HTTPStatus: resp.StatusCode} + case http.StatusUnauthorized, http.StatusForbidden: + return Result{OK: false, Detail: fmt.Sprintf("API Key 无效或已失效(HTTP %d)", resp.StatusCode), HTTPStatus: resp.StatusCode} + default: + snip := strings.TrimSpace(string(body)) + if len(snip) > 220 { + snip = snip[:220] + "…" + } + return Result{OK: false, Detail: fmt.Sprintf("HTTP %d %s", resp.StatusCode, snip), HTTPStatus: resp.StatusCode} + } +} + +func probeKiro(accessToken string) Result { + arn := findProfileArnInJWT(accessToken) + if arn == "" { + return Result{ + OK: false, + Detail: "无法从 Token 中解析 profileArn,Kiro 暂无法自动探测", + } + } + + q := url.Values{} + q.Set("origin", "AI_EDITOR") + q.Set("profileArn", arn) + q.Set("resourceType", "AGENTIC_REQUEST") + u := "https://q.us-east-1.amazonaws.com/getUsageLimits?" + q.Encode() + + req, err := http.NewRequest(http.MethodGet, u, nil) + if err != nil { + return Result{OK: false, Detail: err.Error()} + } + req.Header.Set("Authorization", "Bearer "+normalizeBearerToken(accessToken)) + req.Header.Set("Accept", "application/json") + + resp, err := httpClient.Do(req) + if err != nil { + return Result{OK: false, Detail: "请求失败: " + err.Error()} + } + defer resp.Body.Close() + body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) + + switch resp.StatusCode { + case http.StatusOK: + return Result{OK: true, Detail: "Kiro(AWS Q)用量接口响应正常", HTTPStatus: resp.StatusCode} + case http.StatusUnauthorized, http.StatusForbidden: + return Result{OK: false, Detail: fmt.Sprintf("Token 无效或已过期(HTTP %d)", resp.StatusCode), HTTPStatus: resp.StatusCode} + default: + snip := strings.TrimSpace(string(body)) + if len(snip) > 220 { + snip = snip[:220] + "…" + } + return Result{OK: false, Detail: fmt.Sprintf("HTTP %d %s", resp.StatusCode, snip), HTTPStatus: resp.StatusCode} + } +} + +func decodeJWTPayloadMap(raw string) (map[string]interface{}, error) { + tok := normalizeBearerToken(strings.TrimSpace(raw)) + parts := strings.Split(tok, ".") + if len(parts) < 2 { + return nil, fmt.Errorf("not a JWT") + } + b, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return nil, err + } + var m map[string]interface{} + if err := json.Unmarshal(b, &m); err != nil { + return nil, err + } + return m, nil +} + +func findProfileArnInJWT(raw string) string { + m, err := decodeJWTPayloadMap(raw) + if err != nil { + return "" + } + return findProfileArnValue(m) +} + +func findProfileArnValue(v interface{}) string { + switch x := v.(type) { + case map[string]interface{}: + for k, val := range x { + lk := strings.ToLower(k) + if lk == "profilearn" || lk == "profile_arn" { + if s, ok := val.(string); ok && strings.Contains(s, "arn:") { + return s + } + } + } + for _, val := range x { + if s := findProfileArnValue(val); s != "" { + return s + } + } + case []interface{}: + for _, el := range x { + if s := findProfileArnValue(el); s != "" { + return s + } + } + case string: + if strings.Contains(x, "arn:aws:codewhisperer") && strings.Contains(x, ":profile/") { + return x + } + } + return "" +} diff --git a/go/pkg/versionutil/compare.go b/go/pkg/versionutil/compare.go index 07eb548..7e2e9e6 100644 --- a/go/pkg/versionutil/compare.go +++ b/go/pkg/versionutil/compare.go @@ -1,57 +1,57 @@ -package versionutil - -import ( - "strconv" - "strings" -) - -// Compare 比较语义化版本号(按段数字比较,如 1.10.0 > 1.9.0)。不支持复杂 pre-release 规则。 -// 返回 -1 表示 a < b,0 表示相等,1 表示 a > b。 -func Compare(a, b string) int { - pa := parseParts(a) - pb := parseParts(b) - maxLen := len(pa) - if len(pb) > maxLen { - maxLen = len(pb) - } - for i := 0; i < maxLen; i++ { - var xa, xb int64 - if i < len(pa) { - xa = pa[i] - } - if i < len(pb) { - xb = pb[i] - } - if xa < xb { - return -1 - } - if xa > xb { - return 1 - } - } - return 0 -} - -func parseParts(s string) []int64 { - s = strings.TrimSpace(s) - if s == "" { - return []int64{0} - } - if i := strings.IndexByte(s, '-'); i >= 0 { - s = s[:i] - } - parts := strings.Split(s, ".") - out := make([]int64, 0, len(parts)) - for _, p := range parts { - p = strings.TrimSpace(p) - n, err := strconv.ParseInt(p, 10, 64) - if err != nil { - n = 0 - } - out = append(out, n) - } - if len(out) == 0 { - return []int64{0} - } - return out -} +package versionutil + +import ( + "strconv" + "strings" +) + +// Compare 比较语义化版本号(按段数字比较,如 1.10.0 > 1.9.0)。不支持复杂 pre-release 规则。 +// 返回 -1 表示 a < b,0 表示相等,1 表示 a > b。 +func Compare(a, b string) int { + pa := parseParts(a) + pb := parseParts(b) + maxLen := len(pa) + if len(pb) > maxLen { + maxLen = len(pb) + } + for i := 0; i < maxLen; i++ { + var xa, xb int64 + if i < len(pa) { + xa = pa[i] + } + if i < len(pb) { + xb = pb[i] + } + if xa < xb { + return -1 + } + if xa > xb { + return 1 + } + } + return 0 +} + +func parseParts(s string) []int64 { + s = strings.TrimSpace(s) + if s == "" { + return []int64{0} + } + if i := strings.IndexByte(s, '-'); i >= 0 { + s = s[:i] + } + parts := strings.Split(s, ".") + out := make([]int64, 0, len(parts)) + for _, p := range parts { + p = strings.TrimSpace(p) + n, err := strconv.ParseInt(p, 10, 64) + if err != nil { + n = 0 + } + out = append(out, n) + } + if len(out) == 0 { + return []int64{0} + } + return out +} diff --git a/go/routers/api/api.go b/go/routers/api/api.go index b1ae4a0..6572128 100644 --- a/go/routers/api/api.go +++ b/go/routers/api/api.go @@ -1,39 +1,39 @@ -package api - -import ( - "server/controllers" - - beego "github.com/beego/beego/v2/server/web" -) - -// Register 注册移动端 / 开放 API(api)路由。 -func Register() { - // 客户端检查更新(无需登录) - beego.Router("/api/softwareupgrade/check", &controllers.ApiSoftwareUpgradeController{}, "get:Check") - - // 登录器上报 Cursor 设备信息(无需登录) - beego.Router("/api/cursor/equipment/report", &controllers.ApiCursorEquipmentController{}, "post:Report") - - // 登录器使用激活码激活/续期 Cursor 设备(无需登录) - beego.Router("/api/cursor/equipment/activateByCode", &controllers.ApiCursorEquipmentController{}, "post:ActivateByCode") - - // 登录器心跳接口,用于更新在线状态(无需登录) - beego.Router("/api/cursor/equipment/heartbeat", &controllers.ApiCursorEquipmentController{}, "post:Heartbeat") - - // Cursor Token 顺序读取/检测接口(无需登录,peek 不改变号池状态) - // GET /api/cursor/token/peek?id=11&data_type=tk - beego.Router("/api/cursor/token/peek", &controllers.ApiCursorDetectController{}, "get:PeekToken") - - // Cursor Token 可用状态标记接口(无需登录) - // POST /api/cursor/token/available?id=11 - // POST /api/cursor/token/unavailable?id=11 - beego.Router("/api/cursor/token/available", &controllers.ApiCursorDetectController{}, "post:MarkTokenAvailable") - beego.Router("/api/cursor/token/unavailable", &controllers.ApiCursorDetectController{}, "post:MarkTokenUnavailable") - - // 对外提卡接口(无需登录) - // GET /api/getcard?type=xianyu&module=cursor&data_type=tk - beego.Router("/api/getcard", &controllers.ApiGetCardController{}, "get:GetCard") - - // 日程提醒确认接口(无需登录) - beego.Router("/api/schedule/reminder/ack", &controllers.ApiReminderController{}, "get:AckReminder") -} +package api + +import ( + "server/controllers" + + beego "github.com/beego/beego/v2/server/web" +) + +// Register 注册移动端 / 开放 API(api)路由。 +func Register() { + // 客户端检查更新(无需登录) + beego.Router("/api/softwareupgrade/check", &controllers.ApiSoftwareUpgradeController{}, "get:Check") + + // 登录器上报 Cursor 设备信息(无需登录) + beego.Router("/api/cursor/equipment/report", &controllers.ApiCursorEquipmentController{}, "post:Report") + + // 登录器使用激活码激活/续期 Cursor 设备(无需登录) + beego.Router("/api/cursor/equipment/activateByCode", &controllers.ApiCursorEquipmentController{}, "post:ActivateByCode") + + // 登录器心跳接口,用于更新在线状态(无需登录) + beego.Router("/api/cursor/equipment/heartbeat", &controllers.ApiCursorEquipmentController{}, "post:Heartbeat") + + // Cursor Token 顺序读取/检测接口(无需登录,peek 不改变号池状态) + // GET /api/cursor/token/peek?id=11&data_type=tk + beego.Router("/api/cursor/token/peek", &controllers.ApiCursorDetectController{}, "get:PeekToken") + + // Cursor Token 可用状态标记接口(无需登录) + // POST /api/cursor/token/available?id=11 + // POST /api/cursor/token/unavailable?id=11 + beego.Router("/api/cursor/token/available", &controllers.ApiCursorDetectController{}, "post:MarkTokenAvailable") + beego.Router("/api/cursor/token/unavailable", &controllers.ApiCursorDetectController{}, "post:MarkTokenUnavailable") + + // 对外提卡接口(无需登录) + // GET /api/getcard?type=xianyu&module=cursor&data_type=tk + beego.Router("/api/getcard", &controllers.ApiGetCardController{}, "get:GetCard") + + // 日程提醒确认接口(无需登录) + beego.Router("/api/schedule/reminder/ack", &controllers.ApiReminderController{}, "get:AckReminder") +} diff --git a/go/routers/backend/backend.go b/go/routers/backend/backend.go index 1c5eecc..d3378f2 100644 --- a/go/routers/backend/backend.go +++ b/go/routers/backend/backend.go @@ -1,147 +1,147 @@ -package backend - -import ( - "server/controllers" - - beego "github.com/beego/beego/v2/server/web" -) - -// Register 注册租户端(backend)路由。 -// 该端不包含平台菜单配置接口。 -func Register() { - RegisterAuthRoutes() -} - -// RegisterAuthRoutes 注册 backend 认证相关路由。 -func RegisterAuthRoutes() { - // 登录、注册与找回密码相关 - beego.Router("/backend/login", &controllers.BackendAuthController{}, "post:LoginBackend") - beego.Router("/backend/sendLoginCode", &controllers.BackendAuthController{}, "post:SendLoginCode") - beego.Router("/backend/loginBySms", &controllers.BackendAuthController{}, "post:LoginBySms") - beego.Router("/backend/logout", &controllers.BackendAuthController{}, "post:Logout") - beego.Router("/backend/register", &controllers.BackendAuthController{}, "post:Register") - beego.Router("/backend/sendRegisterCode", &controllers.BackendAuthController{}, "post:SendRegisterCode") - beego.Router("/backend/resetPassword", &controllers.BackendAuthController{}, "post:ResetPassword") - beego.Router("/backend/sendResetCode", &controllers.BackendAuthController{}, "post:SendResetCode") - - // 极验与登录验证配置 - beego.Router("/backend/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos") - beego.Router("/backend/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos") - beego.Router("/backend/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify") - - // 菜单接口 - beego.Router("/backend/menu/:id", &controllers.BackendMenuController{}, "get:GetBackendMenu") - beego.Router("/backend/allmenu", &controllers.BackendMenuController{}, "get:GetAllBackendMenus") - - // 操作日志(yz_system_operation_log) - beego.Router("/backend/operationLogs", &controllers.BackendOperationLogController{}, "get:List") - beego.Router("/backend/operationLogs/statistics", &controllers.BackendOperationLogController{}, "get:Statistics") - beego.Router("/backend/operationLogs/:id", &controllers.BackendOperationLogController{}, "get:Detail;delete:Delete") - beego.Router("/backend/operationLogs/batchDelete", &controllers.BackendOperationLogController{}, "post:BatchDelete") - - // 租户站点设置 - beego.Router("/backend/normalInfos", &controllers.BackendSiteSettingsController{}, "get:GetNormalInfos") - beego.Router("/backend/saveNormalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveNormalInfos") - beego.Router("/backend/legalInfos", &controllers.BackendSiteSettingsController{}, "get:GetLegalInfos") - beego.Router("/backend/saveLegalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveLegalInfos") - beego.Router("/backend/companyInfos", &controllers.BackendSiteSettingsController{}, "get:GetCompanyInfos") - beego.Router("/backend/saveCompanyInfos", &controllers.BackendSiteSettingsController{}, "post:SaveCompanyInfos") - beego.Router("/backend/companySeo", &controllers.BackendSiteSettingsController{}, "get:GetCompanySeo") - beego.Router("/backend/saveCompanySeo", &controllers.BackendSiteSettingsController{}, "post:SaveCompanySeo") - beego.Router("/backend/loginVerifyInfos", &controllers.BackendLoginVerifyController{}, "get:GetLoginVerifyInfos") - beego.Router("/backend/saveloginVerifyInfos", &controllers.BackendLoginVerifyController{}, "post:SaveLoginVerifyInfos") - - // 站内信(yz_system_reminderlist) - beego.Router("/backend/sitereminder/myList", &controllers.BackendSiteReminderController{}, "get:GetMyList") - beego.Router("/backend/sitereminder/read", &controllers.BackendSiteReminderController{}, "post:MarkRead") - beego.Router("/backend/sitereminder/readall", &controllers.BackendSiteReminderController{}, "post:MarkAllRead") - beego.Router("/backend/sitereminder/delete", &controllers.BackendSiteReminderController{}, "post:Delete") - - // 文件管理(yz_system_files / yz_system_files_category) - beego.Router("/backend/usercate", &controllers.BackendFileController{}, "get:GetUserCate") - beego.Router("/backend/allfiles", &controllers.BackendFileController{}, "get:GetAllFiles") - beego.Router("/backend/catefiles/:id", &controllers.BackendFileController{}, "get:GetCateFiles") - beego.Router("/backend/file/:id", &controllers.BackendFileController{}, "get:GetFileByID") - beego.Router("/backend/deletefilepermanently/:id", &controllers.BackendFileController{}, "delete:DeleteFilePermanently") - beego.Router("/backend/uploadfile", &controllers.BackendFileController{}, "post:UploadFile") - beego.Router("/backend/uploadfiles", &controllers.BackendFileController{}, "post:UploadFile") - beego.Router("/backend/updatefile/:id", &controllers.BackendFileController{}, "post:UpdateFile") - beego.Router("/backend/deletefile/:id", &controllers.BackendFileController{}, "delete:DeleteFile") - beego.Router("/backend/movefile/:id", &controllers.BackendFileController{}, "get:MoveFile") - beego.Router("/backend/createfilecate", &controllers.BackendFileController{}, "post:CreateFileCate") - beego.Router("/backend/renamefilecate/:id", &controllers.BackendFileController{}, "post:RenameFileCate") - beego.Router("/backend/deletefilecate/:id", &controllers.BackendFileController{}, "delete:DeleteFileCate") - beego.Router("/backend/uploadavatar", &controllers.BackendFileController{}, "post:UploadAvatar") - beego.Router("/backend/uploadavatar/:id", &controllers.BackendFileController{}, "post:UpdateAvatar") - beego.Router("/backend/batchdeletefiles", &controllers.BackendFileController{}, "post:BatchDeleteFiles") - beego.Router("/backend/batchDeleteFilesPermanently", &controllers.BackendFileController{}, "post:BatchDeleteFilesPermanently") - beego.Router("/backend/batchMoveFiles", &controllers.BackendFileController{}, "post:BatchMoveFiles") - - // 模块接口 - beego.Router("/backend/modules/getTenantList", &controllers.BackendModulesController{}, "get:GetTenantList") - - // 用户接口 - beego.Router("/backend/getTenantUsers/:tid", &controllers.BackendAdminUserController{}, "get:GetTenantUsers") - beego.Router("/backend/getAllUsers", &controllers.BackendAdminUserController{}, "get:GetAllUsers") - beego.Router("/backend/getUserInfo/:id", &controllers.BackendAdminUserController{}, "get:GetUserInfo") - beego.Router("/backend/addUser", &controllers.BackendAdminUserController{}, "post:AddUser") - beego.Router("/backend/editUser/:id", &controllers.BackendAdminUserController{}, "post:EditUser") - beego.Router("/backend/deleteUser/:id", &controllers.BackendAdminUserController{}, "delete:DeleteUser") - beego.Router("/backend/changePassword", &controllers.BackendAdminUserController{}, "post:ChangePassword") - - // ERP 接口 - beego.Router("/backend/erp/getOrganization", &controllers.BackendErpController{}, "get:GetOrganization") - beego.Router("/backend/erp/getOrganizationDetail/:id", &controllers.BackendErpController{}, "get:GetOrganizationDetail") - beego.Router("/backend/erp/createOrganization", &controllers.BackendErpController{}, "post:CreateOrganization") - beego.Router("/backend/erp/editOrganization/:id", &controllers.BackendErpController{}, "post:EditOrganization") - beego.Router("/backend/erp/deleteOrganization/:id", &controllers.BackendErpController{}, "delete:DeleteOrganization") - beego.Router("/backend/erp/getCompanys", &controllers.BackendErpController{}, "get:GetCompanys") - beego.Router("/backend/erp/getDepartments", &controllers.BackendErpController{}, "get:GetDepartments") - beego.Router("/backend/erp/getEmployee", &controllers.BackendErpController{}, "get:GetEmployee") - beego.Router("/backend/erp/getEmployeeDetail/:id", &controllers.BackendErpController{}, "get:GetEmployeeDetail") - beego.Router("/backend/erp/createEmployee", &controllers.BackendErpController{}, "post:CreateEmployee") - beego.Router("/backend/erp/editEmployee/:id", &controllers.BackendErpController{}, "post:EditEmployee") - beego.Router("/backend/erp/deleteEmployee/:id", &controllers.BackendErpController{}, "delete:DeleteEmployee") - beego.Router("/backend/erp/getPosition", &controllers.BackendErpController{}, "get:GetPosition") - beego.Router("/backend/erp/getPositionDetail/:id", &controllers.BackendErpController{}, "get:GetPositionDetail") - beego.Router("/backend/erp/createPosition", &controllers.BackendErpController{}, "post:CreatePosition") - beego.Router("/backend/erp/editPosition/:id", &controllers.BackendErpController{}, "post:EditPosition") - beego.Router("/backend/erp/deletePosition/:id", &controllers.BackendErpController{}, "delete:DeletePosition") - - // 文章管理 - beego.Router("/backend/articlesList", &controllers.BackendArticleController{}, "get:List") - beego.Router("/backend/allarticles", &controllers.BackendArticleController{}, "get:ListAll") - beego.Router("/backend/articles/:id", &controllers.BackendArticleController{}, "get:Detail") - beego.Router("/backend/createarticle", &controllers.BackendArticleController{}, "post:Create") - beego.Router("/backend/editarticle/:id", &controllers.BackendArticleController{}, "post:Update") - beego.Router("/backend/deletearticle/:id", &controllers.BackendArticleController{}, "delete:Delete") - beego.Router("/backend/publisharticle/:id", &controllers.BackendArticleController{}, "post:Publish") - beego.Router("/backend/unPublisharticle/:id", &controllers.BackendArticleController{}, "post:Unpublish") - beego.Router("/backend/articleRecommend/:id", &controllers.BackendArticleController{}, "post:Recommend") - beego.Router("/backend/unArticleRecommend/:id", &controllers.BackendArticleController{}, "post:Unrecommend") - beego.Router("/backend/articleTop/:id", &controllers.BackendArticleController{}, "post:Top") - beego.Router("/backend/unArticleTop/:id", &controllers.BackendArticleController{}, "post:Untop") - - beego.Router("/backend/categories", &controllers.BackendArticleCategoryController{}, "get:List") - beego.Router("/backend/allcategories", &controllers.BackendArticleCategoryController{}, "get:ListAll") - beego.Router("/backend/categories/:id", &controllers.BackendArticleCategoryController{}, "get:Detail;delete:Delete") - beego.Router("/backend/createCategory", &controllers.BackendArticleCategoryController{}, "post:Create") - beego.Router("/backend/editCategory/:id", &controllers.BackendArticleCategoryController{}, "post:Update") - beego.Router("/backend/categories/:id/status", &controllers.BackendArticleCategoryController{}, "patch:UpdateStatus") - - // 域名管理(主域名池 / 租户域名) - beego.Router("/backend/domain/pool/index", &controllers.BackendDomainPoolController{}, "get:Index") - beego.Router("/backend/domain/pool/getEnabledDomains", &controllers.BackendDomainPoolController{}, "get:GetEnabledDomains") - beego.Router("/backend/domain/pool/create", &controllers.BackendDomainPoolController{}, "post:Create") - beego.Router("/backend/domain/pool/update", &controllers.BackendDomainPoolController{}, "post:Update") - beego.Router("/backend/domain/pool/delete/:id", &controllers.BackendDomainPoolController{}, "delete:Delete") - beego.Router("/backend/domain/pool/toggleStatus", &controllers.BackendDomainPoolController{}, "post:ToggleStatus") - - beego.Router("/backend/domain/tenant/index", &controllers.BackendTenantDomainController{}, "get:Index") - beego.Router("/backend/domain/tenant/myDomains", &controllers.BackendTenantDomainController{}, "get:MyDomains") - beego.Router("/backend/domain/tenant/apply", &controllers.BackendTenantDomainController{}, "post:Apply") - beego.Router("/backend/domain/tenant/audit", &controllers.BackendTenantDomainController{}, "post:Audit") - beego.Router("/backend/domain/tenant/toggleStatus", &controllers.BackendTenantDomainController{}, "post:ToggleStatus") - beego.Router("/backend/domain/tenant/delete/:id", &controllers.BackendTenantDomainController{}, "delete:Delete") - -} +package backend + +import ( + "server/controllers" + + beego "github.com/beego/beego/v2/server/web" +) + +// Register 注册租户端(backend)路由。 +// 该端不包含平台菜单配置接口。 +func Register() { + RegisterAuthRoutes() +} + +// RegisterAuthRoutes 注册 backend 认证相关路由。 +func RegisterAuthRoutes() { + // 登录、注册与找回密码相关 + beego.Router("/backend/login", &controllers.BackendAuthController{}, "post:LoginBackend") + beego.Router("/backend/sendLoginCode", &controllers.BackendAuthController{}, "post:SendLoginCode") + beego.Router("/backend/loginBySms", &controllers.BackendAuthController{}, "post:LoginBySms") + beego.Router("/backend/logout", &controllers.BackendAuthController{}, "post:Logout") + beego.Router("/backend/register", &controllers.BackendAuthController{}, "post:Register") + beego.Router("/backend/sendRegisterCode", &controllers.BackendAuthController{}, "post:SendRegisterCode") + beego.Router("/backend/resetPassword", &controllers.BackendAuthController{}, "post:ResetPassword") + beego.Router("/backend/sendResetCode", &controllers.BackendAuthController{}, "post:SendResetCode") + + // 极验与登录验证配置 + beego.Router("/backend/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos") + beego.Router("/backend/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos") + beego.Router("/backend/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify") + + // 菜单接口 + beego.Router("/backend/menu/:id", &controllers.BackendMenuController{}, "get:GetBackendMenu") + beego.Router("/backend/allmenu", &controllers.BackendMenuController{}, "get:GetAllBackendMenus") + + // 操作日志(yz_system_operation_log) + beego.Router("/backend/operationLogs", &controllers.BackendOperationLogController{}, "get:List") + beego.Router("/backend/operationLogs/statistics", &controllers.BackendOperationLogController{}, "get:Statistics") + beego.Router("/backend/operationLogs/:id", &controllers.BackendOperationLogController{}, "get:Detail;delete:Delete") + beego.Router("/backend/operationLogs/batchDelete", &controllers.BackendOperationLogController{}, "post:BatchDelete") + + // 租户站点设置 + beego.Router("/backend/normalInfos", &controllers.BackendSiteSettingsController{}, "get:GetNormalInfos") + beego.Router("/backend/saveNormalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveNormalInfos") + beego.Router("/backend/legalInfos", &controllers.BackendSiteSettingsController{}, "get:GetLegalInfos") + beego.Router("/backend/saveLegalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveLegalInfos") + beego.Router("/backend/companyInfos", &controllers.BackendSiteSettingsController{}, "get:GetCompanyInfos") + beego.Router("/backend/saveCompanyInfos", &controllers.BackendSiteSettingsController{}, "post:SaveCompanyInfos") + beego.Router("/backend/companySeo", &controllers.BackendSiteSettingsController{}, "get:GetCompanySeo") + beego.Router("/backend/saveCompanySeo", &controllers.BackendSiteSettingsController{}, "post:SaveCompanySeo") + beego.Router("/backend/loginVerifyInfos", &controllers.BackendLoginVerifyController{}, "get:GetLoginVerifyInfos") + beego.Router("/backend/saveloginVerifyInfos", &controllers.BackendLoginVerifyController{}, "post:SaveLoginVerifyInfos") + + // 站内信(yz_system_reminderlist) + beego.Router("/backend/sitereminder/myList", &controllers.BackendSiteReminderController{}, "get:GetMyList") + beego.Router("/backend/sitereminder/read", &controllers.BackendSiteReminderController{}, "post:MarkRead") + beego.Router("/backend/sitereminder/readall", &controllers.BackendSiteReminderController{}, "post:MarkAllRead") + beego.Router("/backend/sitereminder/delete", &controllers.BackendSiteReminderController{}, "post:Delete") + + // 文件管理(yz_system_files / yz_system_files_category) + beego.Router("/backend/usercate", &controllers.BackendFileController{}, "get:GetUserCate") + beego.Router("/backend/allfiles", &controllers.BackendFileController{}, "get:GetAllFiles") + beego.Router("/backend/catefiles/:id", &controllers.BackendFileController{}, "get:GetCateFiles") + beego.Router("/backend/file/:id", &controllers.BackendFileController{}, "get:GetFileByID") + beego.Router("/backend/deletefilepermanently/:id", &controllers.BackendFileController{}, "delete:DeleteFilePermanently") + beego.Router("/backend/uploadfile", &controllers.BackendFileController{}, "post:UploadFile") + beego.Router("/backend/uploadfiles", &controllers.BackendFileController{}, "post:UploadFile") + beego.Router("/backend/updatefile/:id", &controllers.BackendFileController{}, "post:UpdateFile") + beego.Router("/backend/deletefile/:id", &controllers.BackendFileController{}, "delete:DeleteFile") + beego.Router("/backend/movefile/:id", &controllers.BackendFileController{}, "get:MoveFile") + beego.Router("/backend/createfilecate", &controllers.BackendFileController{}, "post:CreateFileCate") + beego.Router("/backend/renamefilecate/:id", &controllers.BackendFileController{}, "post:RenameFileCate") + beego.Router("/backend/deletefilecate/:id", &controllers.BackendFileController{}, "delete:DeleteFileCate") + beego.Router("/backend/uploadavatar", &controllers.BackendFileController{}, "post:UploadAvatar") + beego.Router("/backend/uploadavatar/:id", &controllers.BackendFileController{}, "post:UpdateAvatar") + beego.Router("/backend/batchdeletefiles", &controllers.BackendFileController{}, "post:BatchDeleteFiles") + beego.Router("/backend/batchDeleteFilesPermanently", &controllers.BackendFileController{}, "post:BatchDeleteFilesPermanently") + beego.Router("/backend/batchMoveFiles", &controllers.BackendFileController{}, "post:BatchMoveFiles") + + // 模块接口 + beego.Router("/backend/modules/getTenantList", &controllers.BackendModulesController{}, "get:GetTenantList") + + // 用户接口 + beego.Router("/backend/getTenantUsers/:tid", &controllers.BackendAdminUserController{}, "get:GetTenantUsers") + beego.Router("/backend/getAllUsers", &controllers.BackendAdminUserController{}, "get:GetAllUsers") + beego.Router("/backend/getUserInfo/:id", &controllers.BackendAdminUserController{}, "get:GetUserInfo") + beego.Router("/backend/addUser", &controllers.BackendAdminUserController{}, "post:AddUser") + beego.Router("/backend/editUser/:id", &controllers.BackendAdminUserController{}, "post:EditUser") + beego.Router("/backend/deleteUser/:id", &controllers.BackendAdminUserController{}, "delete:DeleteUser") + beego.Router("/backend/changePassword", &controllers.BackendAdminUserController{}, "post:ChangePassword") + + // ERP 接口 + beego.Router("/backend/erp/getOrganization", &controllers.BackendErpController{}, "get:GetOrganization") + beego.Router("/backend/erp/getOrganizationDetail/:id", &controllers.BackendErpController{}, "get:GetOrganizationDetail") + beego.Router("/backend/erp/createOrganization", &controllers.BackendErpController{}, "post:CreateOrganization") + beego.Router("/backend/erp/editOrganization/:id", &controllers.BackendErpController{}, "post:EditOrganization") + beego.Router("/backend/erp/deleteOrganization/:id", &controllers.BackendErpController{}, "delete:DeleteOrganization") + beego.Router("/backend/erp/getCompanys", &controllers.BackendErpController{}, "get:GetCompanys") + beego.Router("/backend/erp/getDepartments", &controllers.BackendErpController{}, "get:GetDepartments") + beego.Router("/backend/erp/getEmployee", &controllers.BackendErpController{}, "get:GetEmployee") + beego.Router("/backend/erp/getEmployeeDetail/:id", &controllers.BackendErpController{}, "get:GetEmployeeDetail") + beego.Router("/backend/erp/createEmployee", &controllers.BackendErpController{}, "post:CreateEmployee") + beego.Router("/backend/erp/editEmployee/:id", &controllers.BackendErpController{}, "post:EditEmployee") + beego.Router("/backend/erp/deleteEmployee/:id", &controllers.BackendErpController{}, "delete:DeleteEmployee") + beego.Router("/backend/erp/getPosition", &controllers.BackendErpController{}, "get:GetPosition") + beego.Router("/backend/erp/getPositionDetail/:id", &controllers.BackendErpController{}, "get:GetPositionDetail") + beego.Router("/backend/erp/createPosition", &controllers.BackendErpController{}, "post:CreatePosition") + beego.Router("/backend/erp/editPosition/:id", &controllers.BackendErpController{}, "post:EditPosition") + beego.Router("/backend/erp/deletePosition/:id", &controllers.BackendErpController{}, "delete:DeletePosition") + + // 文章管理 + beego.Router("/backend/articlesList", &controllers.BackendArticleController{}, "get:List") + beego.Router("/backend/allarticles", &controllers.BackendArticleController{}, "get:ListAll") + beego.Router("/backend/articles/:id", &controllers.BackendArticleController{}, "get:Detail") + beego.Router("/backend/createarticle", &controllers.BackendArticleController{}, "post:Create") + beego.Router("/backend/editarticle/:id", &controllers.BackendArticleController{}, "post:Update") + beego.Router("/backend/deletearticle/:id", &controllers.BackendArticleController{}, "delete:Delete") + beego.Router("/backend/publisharticle/:id", &controllers.BackendArticleController{}, "post:Publish") + beego.Router("/backend/unPublisharticle/:id", &controllers.BackendArticleController{}, "post:Unpublish") + beego.Router("/backend/articleRecommend/:id", &controllers.BackendArticleController{}, "post:Recommend") + beego.Router("/backend/unArticleRecommend/:id", &controllers.BackendArticleController{}, "post:Unrecommend") + beego.Router("/backend/articleTop/:id", &controllers.BackendArticleController{}, "post:Top") + beego.Router("/backend/unArticleTop/:id", &controllers.BackendArticleController{}, "post:Untop") + + beego.Router("/backend/categories", &controllers.BackendArticleCategoryController{}, "get:List") + beego.Router("/backend/allcategories", &controllers.BackendArticleCategoryController{}, "get:ListAll") + beego.Router("/backend/categories/:id", &controllers.BackendArticleCategoryController{}, "get:Detail;delete:Delete") + beego.Router("/backend/createCategory", &controllers.BackendArticleCategoryController{}, "post:Create") + beego.Router("/backend/editCategory/:id", &controllers.BackendArticleCategoryController{}, "post:Update") + beego.Router("/backend/categories/:id/status", &controllers.BackendArticleCategoryController{}, "patch:UpdateStatus") + + // 域名管理(主域名池 / 租户域名) + beego.Router("/backend/domain/pool/index", &controllers.BackendDomainPoolController{}, "get:Index") + beego.Router("/backend/domain/pool/getEnabledDomains", &controllers.BackendDomainPoolController{}, "get:GetEnabledDomains") + beego.Router("/backend/domain/pool/create", &controllers.BackendDomainPoolController{}, "post:Create") + beego.Router("/backend/domain/pool/update", &controllers.BackendDomainPoolController{}, "post:Update") + beego.Router("/backend/domain/pool/delete/:id", &controllers.BackendDomainPoolController{}, "delete:Delete") + beego.Router("/backend/domain/pool/toggleStatus", &controllers.BackendDomainPoolController{}, "post:ToggleStatus") + + beego.Router("/backend/domain/tenant/index", &controllers.BackendTenantDomainController{}, "get:Index") + beego.Router("/backend/domain/tenant/myDomains", &controllers.BackendTenantDomainController{}, "get:MyDomains") + beego.Router("/backend/domain/tenant/apply", &controllers.BackendTenantDomainController{}, "post:Apply") + beego.Router("/backend/domain/tenant/audit", &controllers.BackendTenantDomainController{}, "post:Audit") + beego.Router("/backend/domain/tenant/toggleStatus", &controllers.BackendTenantDomainController{}, "post:ToggleStatus") + beego.Router("/backend/domain/tenant/delete/:id", &controllers.BackendTenantDomainController{}, "delete:Delete") + +} diff --git a/go/routers/index/index.go b/go/routers/index/index.go index d13070b..c2cd5d3 100644 --- a/go/routers/index/index.go +++ b/go/routers/index/index.go @@ -1,7 +1,7 @@ -package index - -// Register 注册前端站点(index)路由。 -// 建议统一使用 /index/* 或根路径,根据后续设计补充。 -func Register() { -} - +package index + +// Register 注册前端站点(index)路由。 +// 建议统一使用 /index/* 或根路径,根据后续设计补充。 +func Register() { +} + diff --git a/go/routers/platform/platform.go b/go/routers/platform/platform.go index 9aac00d..79953af 100644 --- a/go/routers/platform/platform.go +++ b/go/routers/platform/platform.go @@ -1,266 +1,266 @@ -package platform - -import ( - "server/controllers" - - beego "github.com/beego/beego/v2/server/web" -) - -// Register 注册平台端路由 -func Register() { - // 平台登录相关 - beego.Router("/platform/login", &controllers.PlatformAuthController{}, "post:LoginPlatform") - beego.Router("/platform/currentUser", &controllers.PlatformAuthController{}, "get:GetCurrentUser") - beego.Router("/platform/sendLoginCode", &controllers.PlatformAuthController{}, "post:SendLoginCode") - beego.Router("/platform/loginBySms", &controllers.PlatformAuthController{}, "post:LoginBySms") - beego.Router("/platform/logout", &controllers.PlatformAuthController{}, "post:Logout") - - // 极验与登录验证配置 - beego.Router("/platform/login/getGeetest3Infos", &controllers.PlatformAuthController{}, "get:GetGeetest3Infos") - beego.Router("/platform/login/getGeetest4Infos", &controllers.PlatformAuthController{}, "get:GetGeetest4Infos") - beego.Router("/platform/login/getOpenVerify", &controllers.PlatformAuthController{}, "get:GetOpenVerify") - beego.Router("/platform/loginVerifyInfos", &controllers.PlatformLoginVerifyController{}, "get:GetLoginVerifyInfos") - beego.Router("/platform/saveloginVerifyInfos", &controllers.PlatformLoginVerifyController{}, "post:SaveLoginVerifyInfos") - - // 存储配置 - beego.Router("/platform/storageConfig", &controllers.StorageConfigController{}, "get:GetStorageConfig") - beego.Router("/platform/saveStorageConfig", &controllers.StorageConfigController{}, "post:SaveStorageConfig") - - // 存储迁移 - beego.Router("/platform/storage/migrateToQiniu", &controllers.StorageMigrationController{}, "post:MigrateToQiniu") - beego.Router("/platform/storage/migrationProgress", &controllers.StorageMigrationController{}, "get:GetMigrationProgress") - - // 找回密码相关 - beego.Router("/platform/resetPassword", &controllers.PlatformAuthController{}, "post:ResetPassword") - beego.Router("/platform/sendResetCode", &controllers.PlatformAuthController{}, "post:SendResetCode") - - // 平台菜单配置相关 - beego.Router("/platform/menu/:id", &controllers.AdminMenuController{}, "get:GetMenu") - beego.Router("/platform/allmenu", &controllers.AdminMenuController{}, "get:GetAllMenus") - beego.Router("/platform/menu/status/:id", &controllers.AdminMenuController{}, "patch:UpdateMenuStatus") - beego.Router("/platform/createmenu", &controllers.AdminMenuController{}, "post:CreateMenu") - beego.Router("/platform/updatemenu/:id", &controllers.AdminMenuController{}, "put:UpdateMenu") - beego.Router("/platform/deletemenu/:id", &controllers.AdminMenuController{}, "delete:DeleteMenu") - - // 平台租户管理相关 - beego.Router("/platform/tenant/getTenant", &controllers.PlatformTenantController{}, "get:GetTenant") - beego.Router("/platform/tenant/getTenantDetail/:id", &controllers.PlatformTenantController{}, "get:GetTenantDetail") - beego.Router("/platform/tenant/createTenant", &controllers.PlatformTenantController{}, "post:CreateTenant") - beego.Router("/platform/tenant/editTenant/:id", &controllers.PlatformTenantController{}, "post:EditTenant") - beego.Router("/platform/tenant/deleteTenant/:id", &controllers.PlatformTenantController{}, "delete:DeleteTenant") - beego.Router("/platform/tenant/findTenantCode", &controllers.PlatformTenantController{}, "get:FindTenantCode") - - // 平台租户用户绑定相关 - beego.Router("/platform/getTenantUsers/:tid", &controllers.PlatformTenantUserController{}, "get:GetTenantUsersByTid") - beego.Router("/platform/tenantUser/list", &controllers.PlatformTenantUserController{}, "get:GetTenantUserList") - beego.Router("/platform/tenantUser/detail/:id", &controllers.PlatformTenantUserController{}, "get:GetTenantUserDetail") - beego.Router("/platform/tenantUser/create", &controllers.PlatformTenantUserController{}, "post:CreateTenantUser") - beego.Router("/platform/tenantUser/edit/:id", &controllers.PlatformTenantUserController{}, "post:EditTenantUser") - beego.Router("/platform/tenantUser/delete/:id", &controllers.PlatformTenantUserController{}, "delete:DeleteTenantUser") - - // 平台管理员用户管理(yz_system_admin_user) - beego.Router("/platform/getAllUsers", &controllers.PlatformAdminUserController{}, "get:GetAllUsers") - beego.Router("/platform/getUserInfo/:id", &controllers.PlatformAdminUserController{}, "get:GetUserInfo") - beego.Router("/platform/addUser", &controllers.PlatformAdminUserController{}, "post:AddUser") - beego.Router("/platform/editUser/:id", &controllers.PlatformAdminUserController{}, "post:EditUser") - beego.Router("/platform/deleteUser/:id", &controllers.PlatformAdminUserController{}, "delete:DeleteUser") - beego.Router("/platform/changePassword", &controllers.PlatformAdminUserController{}, "post:ChangePassword") - - // 平台角色管理(yz_system_admin_role) - beego.Router("/platform/allRoles", &controllers.PlatformRoleController{}, "get:GetAllRoles") - beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "get:GetRoleByID") - beego.Router("/platform/roles", &controllers.PlatformRoleController{}, "post:CreateRole") - beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "put:UpdateRole") - beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "delete:DeleteRole") - - // 操作日志(yz_system_operation_log) - beego.Router("/platform/operationLogs", &controllers.PlatformOperationLogController{}, "get:List") - beego.Router("/platform/operationLogs/statistics", &controllers.PlatformOperationLogController{}, "get:Statistics") - beego.Router("/platform/operationLogs/:id", &controllers.PlatformOperationLogController{}, "get:Detail;delete:Delete") - beego.Router("/platform/operationLogs/batchDelete", &controllers.PlatformOperationLogController{}, "post:BatchDelete") - - // 域名管理(主域名池 / 租户域名) - beego.Router("/platform/domain/pool/index", &controllers.PlatformDomainPoolController{}, "get:Index") - beego.Router("/platform/domain/pool/getEnabledDomains", &controllers.PlatformDomainPoolController{}, "get:GetEnabledDomains") - beego.Router("/platform/domain/pool/create", &controllers.PlatformDomainPoolController{}, "post:Create") - beego.Router("/platform/domain/pool/update", &controllers.PlatformDomainPoolController{}, "post:Update") - beego.Router("/platform/domain/pool/delete/:id", &controllers.PlatformDomainPoolController{}, "delete:Delete") - beego.Router("/platform/domain/pool/toggleStatus", &controllers.PlatformDomainPoolController{}, "post:ToggleStatus") - - beego.Router("/platform/domain/tenant/index", &controllers.PlatformTenantDomainController{}, "get:Index") - beego.Router("/platform/domain/tenant/myDomains", &controllers.PlatformTenantDomainController{}, "get:MyDomains") - beego.Router("/platform/domain/tenant/apply", &controllers.PlatformTenantDomainController{}, "post:Apply") - beego.Router("/platform/domain/tenant/audit", &controllers.PlatformTenantDomainController{}, "post:Audit") - beego.Router("/platform/domain/tenant/toggleStatus", &controllers.PlatformTenantDomainController{}, "post:ToggleStatus") - beego.Router("/platform/domain/tenant/delete/:id", &controllers.PlatformTenantDomainController{}, "delete:Delete") - - // 模块管理(yz_system_modules) - beego.Router("/platform/modules/list", &controllers.PlatformModulesController{}, "get:GetList") - beego.Router("/platform/modules/getTenantList", &controllers.PlatformModulesController{}, "get:GetTenantList") - beego.Router("/platform/modules/select/list", &controllers.PlatformModulesController{}, "get:GetSelectList") - beego.Router("/platform/modules/status", &controllers.PlatformModulesController{}, "post:ChangeStatus") - beego.Router("/platform/modules/batchDelete", &controllers.PlatformModulesController{}, "post:BatchDelete") - beego.Router("/platform/modules", &controllers.PlatformModulesController{}, "post:Add") - beego.Router("/platform/modules/:id", &controllers.PlatformModulesController{}, "get:GetDetail;put:Edit;delete:Delete") - - // 投诉建议(yz_system_complaint_category / yz_system_platform_complaint) - beego.Router("/platform/complaintCategory/list", &controllers.PlatformComplaintCategoryController{}, "get:List") - beego.Router("/platform/complaintCategory/select", &controllers.PlatformComplaintCategoryController{}, "get:SelectList") - beego.Router("/platform/complaintCategory", &controllers.PlatformComplaintCategoryController{}, "post:Create") - beego.Router("/platform/complaintCategory/:id", &controllers.PlatformComplaintCategoryController{}, "post:Update;delete:Delete") - beego.Router("/platform/complaint/list", &controllers.PlatformComplaintController{}, "get:List") - beego.Router("/platform/complaint", &controllers.PlatformComplaintController{}, "post:Create") - beego.Router("/platform/complaint/:id", &controllers.PlatformComplaintController{}, "get:Detail;post:Update;delete:Delete") - - // 软件升级产品(yz_system_software_upgrade) - beego.Router("/platform/softwareupgrade/list", &controllers.PlatformSoftwareUpgradeController{}, "get:List") - beego.Router("/platform/softwareupgrade", &controllers.PlatformSoftwareUpgradeController{}, "post:Create") - beego.Router("/platform/softwareupgrade/:id", &controllers.PlatformSoftwareUpgradeController{}, "get:Detail;post:Update;delete:Delete") - - // 租户站点设置(yz_tenant_site_setting) - beego.Router("/platform/normalInfos", &controllers.PlatformSiteSettingsController{}, "get:GetNormalInfos") - beego.Router("/platform/saveNormalInfos", &controllers.PlatformSiteSettingsController{}, "post:SaveNormalInfos") - - // 系统邮箱配置(yz_system_email) - beego.Router("/platform/email/info", &controllers.PlatformEmailController{}, "get:GetInfo") - beego.Router("/platform/email/editinfo", &controllers.PlatformEmailController{}, "post:EditInfo") - beego.Router("/platform/email/sendtestemail", &controllers.PlatformEmailController{}, "post:SendTestEmail") - - // 站内信配置与发送(yz_system_sitereminder / yz_system_reminderlist) - beego.Router("/platform/sitereminder/config", &controllers.PlatformSiteReminderController{}, "get:GetConfig;post:SaveConfig") - beego.Router("/platform/sitereminder/send", &controllers.PlatformSiteReminderController{}, "post:Send") - beego.Router("/platform/sitereminder/myList", &controllers.PlatformSiteReminderController{}, "get:GetMyList") - beego.Router("/platform/sitereminder/read", &controllers.PlatformSiteReminderController{}, "post:MarkRead") - beego.Router("/platform/sitereminder/readall", &controllers.PlatformSiteReminderController{}, "post:MarkAllRead") - beego.Router("/platform/sitereminder/delete", &controllers.PlatformSiteReminderController{}, "post:Delete") - beego.Router("/platform/sitereminder/sentList", &controllers.PlatformSiteReminderController{}, "get:GetSentList") - beego.Router("/platform/sitereminder/updateSent", &controllers.PlatformSiteReminderController{}, "post:UpdateSent") - beego.Router("/platform/sitereminder/deleteSent", &controllers.PlatformSiteReminderController{}, "post:DeleteSentBatch") - - // 短信配置(yz_system_sms) - beego.Router("/platform/sms/info", &controllers.PlatformSMSController{}, "get:GetSmsInfo") - beego.Router("/platform/sms/editinfo", &controllers.PlatformSMSController{}, "post:EditSmsInfo") - beego.Router("/platform/sms/sendtest", &controllers.PlatformSMSController{}, "post:SendTestSms") - beego.Router("/platform/sms/taskList", &controllers.PlatformSMSController{}, "get:GetSmsTaskList") - beego.Router("/platform/sms/taskEdit/:id", &controllers.PlatformSMSController{}, "post:EditSmsTask") - - // Bark 推送配置 - beego.Router("/platform/bark/info", &controllers.PlatformBarkController{}, "get:GetBarkInfo") - beego.Router("/platform/bark/editinfo", &controllers.PlatformBarkController{}, "post:EditBarkInfo") - beego.Router("/platform/bark/sendtest", &controllers.PlatformBarkController{}, "post:SendTestBark") - - // 文件管理(yz_system_files / yz_system_files_category) - beego.Router("/platform/usercate", &controllers.PlatformFileController{}, "get:GetUserCate") - beego.Router("/platform/allfiles", &controllers.PlatformFileController{}, "get:GetAllFiles") - beego.Router("/platform/catefiles/:id", &controllers.PlatformFileController{}, "get:GetCateFiles") - beego.Router("/platform/file/:id", &controllers.PlatformFileController{}, "get:GetFileByID") - beego.Router("/platform/deletefilepermanently/:id", &controllers.PlatformFileController{}, "delete:DeleteFilePermanently") - beego.Router("/platform/uploadfile", &controllers.PlatformFileController{}, "post:UploadFile") - beego.Router("/platform/uploadfiles", &controllers.PlatformFileController{}, "post:UploadFile") - beego.Router("/platform/updatefile/:id", &controllers.PlatformFileController{}, "post:UpdateFile") - beego.Router("/platform/deletefile/:id", &controllers.PlatformFileController{}, "delete:DeleteFile") - beego.Router("/platform/movefile/:id", &controllers.PlatformFileController{}, "get:MoveFile") - beego.Router("/platform/createfilecate", &controllers.PlatformFileController{}, "post:CreateFileCate") - beego.Router("/platform/renamefilecate/:id", &controllers.PlatformFileController{}, "post:RenameFileCate") - beego.Router("/platform/deletefilecate/:id", &controllers.PlatformFileController{}, "delete:DeleteFileCate") - beego.Router("/platform/uploadavatar", &controllers.PlatformFileController{}, "post:UploadAvatar") - beego.Router("/platform/uploadavatar/:id", &controllers.PlatformFileController{}, "post:UpdateAvatar") - beego.Router("/platform/batchdeletefiles", &controllers.PlatformFileController{}, "post:BatchDeleteFiles") - beego.Router("/platform/batchDeleteFilesPermanently", &controllers.PlatformFileController{}, "post:BatchDeleteFilesPermanently") - beego.Router("/platform/batchMoveFiles", &controllers.PlatformFileController{}, "post:BatchMoveFiles") - - // 七牛云直传相关 - beego.Router("/platform/storage/config", &controllers.QiniuUploadController{}, "get:GetStorageConfig") - beego.Router("/platform/qiniu/token", &controllers.QiniuUploadController{}, "get:GetUploadToken") - beego.Router("/platform/qiniu/save", &controllers.QiniuUploadController{}, "post:SaveFileRecord") - - // 首页统计 - beego.Router("/platform/home/accountPoolDailyExtract", &controllers.PlatformHomeController{}, "get:AccountPoolDailyExtract") - beego.Router("/platform/home/accountPoolInventoryTotals", &controllers.PlatformHomeController{}, "get:AccountPoolInventoryTotals") - - // Cursor 设备管理(yz_platform_cursor_equipment) - beego.Router("/platform/cursor/equipment/list", &controllers.PlatformCursorEquipmentController{}, "get:List") - beego.Router("/platform/cursor/equipment/detail/:id", &controllers.PlatformCursorEquipmentController{}, "get:Detail") - beego.Router("/platform/cursor/equipment/add", &controllers.PlatformCursorEquipmentController{}, "post:Add") - beego.Router("/platform/cursor/equipment/update", &controllers.PlatformCursorEquipmentController{}, "post:Update") - beego.Router("/platform/cursor/equipment/delete/:id", &controllers.PlatformCursorEquipmentController{}, "post:Delete") - beego.Router("/platform/cursor/equipment/activate", &controllers.PlatformCursorEquipmentController{}, "post:Activate") - beego.Router("/platform/cursor/equipment/activationRecords", &controllers.PlatformCursorEquipmentController{}, "get:ActivationRecords") - beego.Router("/platform/cursor/equipment/extractRecords", &controllers.PlatformCursorEquipmentController{}, "get:ExtractRecords") - beego.Router("/platform/cursor/equipment/ipLogs", &controllers.PlatformCursorEquipmentController{}, "get:IpLogs") - - // Cursor 激活码管理(yz_platform_cursor_activation_code) - beego.Router("/platform/cursor/activationcode/list", &controllers.PlatformCursorActivationCodeController{}, "get:List") - beego.Router("/platform/cursor/activationcode/detail/:id", &controllers.PlatformCursorActivationCodeController{}, "get:Detail") - beego.Router("/platform/cursor/activationcode/add", &controllers.PlatformCursorActivationCodeController{}, "post:Add") - beego.Router("/platform/cursor/activationcode/update", &controllers.PlatformCursorActivationCodeController{}, "post:Update") - beego.Router("/platform/cursor/activationcode/delete/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Delete") - beego.Router("/platform/cursor/activationcode/generate", &controllers.PlatformCursorActivationCodeController{}, "post:Generate") - beego.Router("/platform/cursor/activationcode/enable/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Enable") - beego.Router("/platform/cursor/activationcode/disable/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Disable") - beego.Router("/platform/cursor/activationcode/export", &controllers.PlatformCursorActivationCodeController{}, "get:Export") - - // 账号池管理(cursor/windsurf/krio/codex) - beego.Router("/platform/accountPool/cursor/list", &controllers.PlatformAccountPoolCursorController{}, "get:List") - beego.Router("/platform/accountPool/cursor/add", &controllers.PlatformAccountPoolCursorController{}, "post:Add") - beego.Router("/platform/accountPool/cursor/batchAdd", &controllers.PlatformAccountPoolCursorController{}, "post:BatchAdd") - beego.Router("/platform/accountPool/cursor/detail/:id", &controllers.PlatformAccountPoolCursorController{}, "get:Detail") - beego.Router("/platform/accountPool/cursor/extract", &controllers.PlatformAccountPoolCursorController{}, "post:Extract") - beego.Router("/platform/accountPool/cursor/updateRemark", &controllers.PlatformAccountPoolCursorController{}, "post:UpdateRemark") - beego.Router("/platform/accountPool/cursor/setUnavailable", &controllers.PlatformAccountPoolCursorController{}, "post:SetUnavailable") - beego.Router("/platform/accountPool/cursor/updateUsable", &controllers.PlatformAccountPoolCursorController{}, "post:UpdateUsable") - beego.Router("/platform/accountPool/cursor/updatePlatform", &controllers.PlatformAccountPoolCursorController{}, "post:UpdatePlatform") - beego.Router("/platform/accountPool/cursor/unextract", &controllers.PlatformAccountPoolCursorController{}, "post:Unextract") - beego.Router("/platform/accountPool/cursor/replenish", &controllers.PlatformAccountPoolCursorController{}, "post:Replenish") - beego.Router("/platform/accountPool/cursor/probeToken", &controllers.PlatformAccountPoolCursorController{}, "post:ProbeToken") - - beego.Router("/platform/accountPool/windsurf/list", &controllers.PlatformAccountPoolWindsurfController{}, "get:List") - beego.Router("/platform/accountPool/windsurf/add", &controllers.PlatformAccountPoolWindsurfController{}, "post:Add") - beego.Router("/platform/accountPool/windsurf/batchAdd", &controllers.PlatformAccountPoolWindsurfController{}, "post:BatchAdd") - beego.Router("/platform/accountPool/windsurf/detail/:id", &controllers.PlatformAccountPoolWindsurfController{}, "get:Detail") - beego.Router("/platform/accountPool/windsurf/extract", &controllers.PlatformAccountPoolWindsurfController{}, "post:Extract") - beego.Router("/platform/accountPool/windsurf/updateRemark", &controllers.PlatformAccountPoolWindsurfController{}, "post:UpdateRemark") - beego.Router("/platform/accountPool/windsurf/setUnavailable", &controllers.PlatformAccountPoolWindsurfController{}, "post:SetUnavailable") - beego.Router("/platform/accountPool/windsurf/updatePlatform", &controllers.PlatformAccountPoolWindsurfController{}, "post:UpdatePlatform") - beego.Router("/platform/accountPool/windsurf/unextract", &controllers.PlatformAccountPoolWindsurfController{}, "post:Unextract") - beego.Router("/platform/accountPool/windsurf/replenish", &controllers.PlatformAccountPoolWindsurfController{}, "post:Replenish") - beego.Router("/platform/accountPool/windsurf/probeToken", &controllers.PlatformAccountPoolWindsurfController{}, "post:ProbeToken") - - beego.Router("/platform/accountPool/krio/list", &controllers.PlatformAccountPoolKrioController{}, "get:List") - beego.Router("/platform/accountPool/krio/add", &controllers.PlatformAccountPoolKrioController{}, "post:Add") - beego.Router("/platform/accountPool/krio/batchAdd", &controllers.PlatformAccountPoolKrioController{}, "post:BatchAdd") - beego.Router("/platform/accountPool/krio/detail/:id", &controllers.PlatformAccountPoolKrioController{}, "get:Detail") - beego.Router("/platform/accountPool/krio/extract", &controllers.PlatformAccountPoolKrioController{}, "post:Extract") - beego.Router("/platform/accountPool/krio/updateRemark", &controllers.PlatformAccountPoolKrioController{}, "post:UpdateRemark") - beego.Router("/platform/accountPool/krio/setUnavailable", &controllers.PlatformAccountPoolKrioController{}, "post:SetUnavailable") - beego.Router("/platform/accountPool/krio/updatePlatform", &controllers.PlatformAccountPoolKrioController{}, "post:UpdatePlatform") - beego.Router("/platform/accountPool/krio/unextract", &controllers.PlatformAccountPoolKrioController{}, "post:Unextract") - beego.Router("/platform/accountPool/krio/replenish", &controllers.PlatformAccountPoolKrioController{}, "post:Replenish") - beego.Router("/platform/accountPool/krio/probeToken", &controllers.PlatformAccountPoolKrioController{}, "post:ProbeToken") - - beego.Router("/platform/accountPool/codex/list", &controllers.PlatformAccountPoolCodexController{}, "get:List") - beego.Router("/platform/accountPool/codex/add", &controllers.PlatformAccountPoolCodexController{}, "post:Add") - beego.Router("/platform/accountPool/codex/batchAdd", &controllers.PlatformAccountPoolCodexController{}, "post:BatchAdd") - beego.Router("/platform/accountPool/codex/detail/:id", &controllers.PlatformAccountPoolCodexController{}, "get:Detail") - beego.Router("/platform/accountPool/codex/extract", &controllers.PlatformAccountPoolCodexController{}, "post:Extract") - beego.Router("/platform/accountPool/codex/updateRemark", &controllers.PlatformAccountPoolCodexController{}, "post:UpdateRemark") - beego.Router("/platform/accountPool/codex/setUnavailable", &controllers.PlatformAccountPoolCodexController{}, "post:SetUnavailable") - beego.Router("/platform/accountPool/codex/updatePlatform", &controllers.PlatformAccountPoolCodexController{}, "post:UpdatePlatform") - beego.Router("/platform/accountPool/codex/unextract", &controllers.PlatformAccountPoolCodexController{}, "post:Unextract") - beego.Router("/platform/accountPool/codex/replenish", &controllers.PlatformAccountPoolCodexController{}, "post:Replenish") - beego.Router("/platform/accountPool/codex/probeToken", &controllers.PlatformAccountPoolCodexController{}, "post:ProbeToken") - - // 记事本管理 - beego.Router("/platform/notebook/list", &controllers.PlatformNotebookController{}, "get:List") - beego.Router("/platform/notebook/detail/:id", &controllers.PlatformNotebookController{}, "get:Detail") - beego.Router("/platform/notebook/create", &controllers.PlatformNotebookController{}, "post:Create") - beego.Router("/platform/notebook/update/:id", &controllers.PlatformNotebookController{}, "post:Update") - beego.Router("/platform/notebook/delete/:id", &controllers.PlatformNotebookController{}, "delete:Delete") - - // 日程提醒管理 - beego.Router("/platform/reminder/list", &controllers.PlatformReminderController{}, "get:GetReminderList") - beego.Router("/platform/reminder/test", &controllers.PlatformReminderController{}, "post:TestReminder") - beego.Router("/platform/reminder/:id", &controllers.PlatformReminderController{}, "get:GetReminderDetail;put:UpdateReminder;delete:DeleteReminder") - beego.Router("/platform/reminder", &controllers.PlatformReminderController{}, "post:CreateReminder") - beego.Router("/platform/reminder/batchDelete", &controllers.PlatformReminderController{}, "post:BatchDeleteReminder") -} +package platform + +import ( + "server/controllers" + + beego "github.com/beego/beego/v2/server/web" +) + +// Register 注册平台端路由 +func Register() { + // 平台登录相关 + beego.Router("/platform/login", &controllers.PlatformAuthController{}, "post:LoginPlatform") + beego.Router("/platform/currentUser", &controllers.PlatformAuthController{}, "get:GetCurrentUser") + beego.Router("/platform/sendLoginCode", &controllers.PlatformAuthController{}, "post:SendLoginCode") + beego.Router("/platform/loginBySms", &controllers.PlatformAuthController{}, "post:LoginBySms") + beego.Router("/platform/logout", &controllers.PlatformAuthController{}, "post:Logout") + + // 极验与登录验证配置 + beego.Router("/platform/login/getGeetest3Infos", &controllers.PlatformAuthController{}, "get:GetGeetest3Infos") + beego.Router("/platform/login/getGeetest4Infos", &controllers.PlatformAuthController{}, "get:GetGeetest4Infos") + beego.Router("/platform/login/getOpenVerify", &controllers.PlatformAuthController{}, "get:GetOpenVerify") + beego.Router("/platform/loginVerifyInfos", &controllers.PlatformLoginVerifyController{}, "get:GetLoginVerifyInfos") + beego.Router("/platform/saveloginVerifyInfos", &controllers.PlatformLoginVerifyController{}, "post:SaveLoginVerifyInfos") + + // 存储配置 + beego.Router("/platform/storageConfig", &controllers.StorageConfigController{}, "get:GetStorageConfig") + beego.Router("/platform/saveStorageConfig", &controllers.StorageConfigController{}, "post:SaveStorageConfig") + + // 存储迁移 + beego.Router("/platform/storage/migrateToQiniu", &controllers.StorageMigrationController{}, "post:MigrateToQiniu") + beego.Router("/platform/storage/migrationProgress", &controllers.StorageMigrationController{}, "get:GetMigrationProgress") + + // 找回密码相关 + beego.Router("/platform/resetPassword", &controllers.PlatformAuthController{}, "post:ResetPassword") + beego.Router("/platform/sendResetCode", &controllers.PlatformAuthController{}, "post:SendResetCode") + + // 平台菜单配置相关 + beego.Router("/platform/menu/:id", &controllers.AdminMenuController{}, "get:GetMenu") + beego.Router("/platform/allmenu", &controllers.AdminMenuController{}, "get:GetAllMenus") + beego.Router("/platform/menu/status/:id", &controllers.AdminMenuController{}, "patch:UpdateMenuStatus") + beego.Router("/platform/createmenu", &controllers.AdminMenuController{}, "post:CreateMenu") + beego.Router("/platform/updatemenu/:id", &controllers.AdminMenuController{}, "put:UpdateMenu") + beego.Router("/platform/deletemenu/:id", &controllers.AdminMenuController{}, "delete:DeleteMenu") + + // 平台租户管理相关 + beego.Router("/platform/tenant/getTenant", &controllers.PlatformTenantController{}, "get:GetTenant") + beego.Router("/platform/tenant/getTenantDetail/:id", &controllers.PlatformTenantController{}, "get:GetTenantDetail") + beego.Router("/platform/tenant/createTenant", &controllers.PlatformTenantController{}, "post:CreateTenant") + beego.Router("/platform/tenant/editTenant/:id", &controllers.PlatformTenantController{}, "post:EditTenant") + beego.Router("/platform/tenant/deleteTenant/:id", &controllers.PlatformTenantController{}, "delete:DeleteTenant") + beego.Router("/platform/tenant/findTenantCode", &controllers.PlatformTenantController{}, "get:FindTenantCode") + + // 平台租户用户绑定相关 + beego.Router("/platform/getTenantUsers/:tid", &controllers.PlatformTenantUserController{}, "get:GetTenantUsersByTid") + beego.Router("/platform/tenantUser/list", &controllers.PlatformTenantUserController{}, "get:GetTenantUserList") + beego.Router("/platform/tenantUser/detail/:id", &controllers.PlatformTenantUserController{}, "get:GetTenantUserDetail") + beego.Router("/platform/tenantUser/create", &controllers.PlatformTenantUserController{}, "post:CreateTenantUser") + beego.Router("/platform/tenantUser/edit/:id", &controllers.PlatformTenantUserController{}, "post:EditTenantUser") + beego.Router("/platform/tenantUser/delete/:id", &controllers.PlatformTenantUserController{}, "delete:DeleteTenantUser") + + // 平台管理员用户管理(yz_system_admin_user) + beego.Router("/platform/getAllUsers", &controllers.PlatformAdminUserController{}, "get:GetAllUsers") + beego.Router("/platform/getUserInfo/:id", &controllers.PlatformAdminUserController{}, "get:GetUserInfo") + beego.Router("/platform/addUser", &controllers.PlatformAdminUserController{}, "post:AddUser") + beego.Router("/platform/editUser/:id", &controllers.PlatformAdminUserController{}, "post:EditUser") + beego.Router("/platform/deleteUser/:id", &controllers.PlatformAdminUserController{}, "delete:DeleteUser") + beego.Router("/platform/changePassword", &controllers.PlatformAdminUserController{}, "post:ChangePassword") + + // 平台角色管理(yz_system_admin_role) + beego.Router("/platform/allRoles", &controllers.PlatformRoleController{}, "get:GetAllRoles") + beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "get:GetRoleByID") + beego.Router("/platform/roles", &controllers.PlatformRoleController{}, "post:CreateRole") + beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "put:UpdateRole") + beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "delete:DeleteRole") + + // 操作日志(yz_system_operation_log) + beego.Router("/platform/operationLogs", &controllers.PlatformOperationLogController{}, "get:List") + beego.Router("/platform/operationLogs/statistics", &controllers.PlatformOperationLogController{}, "get:Statistics") + beego.Router("/platform/operationLogs/:id", &controllers.PlatformOperationLogController{}, "get:Detail;delete:Delete") + beego.Router("/platform/operationLogs/batchDelete", &controllers.PlatformOperationLogController{}, "post:BatchDelete") + + // 域名管理(主域名池 / 租户域名) + beego.Router("/platform/domain/pool/index", &controllers.PlatformDomainPoolController{}, "get:Index") + beego.Router("/platform/domain/pool/getEnabledDomains", &controllers.PlatformDomainPoolController{}, "get:GetEnabledDomains") + beego.Router("/platform/domain/pool/create", &controllers.PlatformDomainPoolController{}, "post:Create") + beego.Router("/platform/domain/pool/update", &controllers.PlatformDomainPoolController{}, "post:Update") + beego.Router("/platform/domain/pool/delete/:id", &controllers.PlatformDomainPoolController{}, "delete:Delete") + beego.Router("/platform/domain/pool/toggleStatus", &controllers.PlatformDomainPoolController{}, "post:ToggleStatus") + + beego.Router("/platform/domain/tenant/index", &controllers.PlatformTenantDomainController{}, "get:Index") + beego.Router("/platform/domain/tenant/myDomains", &controllers.PlatformTenantDomainController{}, "get:MyDomains") + beego.Router("/platform/domain/tenant/apply", &controllers.PlatformTenantDomainController{}, "post:Apply") + beego.Router("/platform/domain/tenant/audit", &controllers.PlatformTenantDomainController{}, "post:Audit") + beego.Router("/platform/domain/tenant/toggleStatus", &controllers.PlatformTenantDomainController{}, "post:ToggleStatus") + beego.Router("/platform/domain/tenant/delete/:id", &controllers.PlatformTenantDomainController{}, "delete:Delete") + + // 模块管理(yz_system_modules) + beego.Router("/platform/modules/list", &controllers.PlatformModulesController{}, "get:GetList") + beego.Router("/platform/modules/getTenantList", &controllers.PlatformModulesController{}, "get:GetTenantList") + beego.Router("/platform/modules/select/list", &controllers.PlatformModulesController{}, "get:GetSelectList") + beego.Router("/platform/modules/status", &controllers.PlatformModulesController{}, "post:ChangeStatus") + beego.Router("/platform/modules/batchDelete", &controllers.PlatformModulesController{}, "post:BatchDelete") + beego.Router("/platform/modules", &controllers.PlatformModulesController{}, "post:Add") + beego.Router("/platform/modules/:id", &controllers.PlatformModulesController{}, "get:GetDetail;put:Edit;delete:Delete") + + // 投诉建议(yz_system_complaint_category / yz_system_platform_complaint) + beego.Router("/platform/complaintCategory/list", &controllers.PlatformComplaintCategoryController{}, "get:List") + beego.Router("/platform/complaintCategory/select", &controllers.PlatformComplaintCategoryController{}, "get:SelectList") + beego.Router("/platform/complaintCategory", &controllers.PlatformComplaintCategoryController{}, "post:Create") + beego.Router("/platform/complaintCategory/:id", &controllers.PlatformComplaintCategoryController{}, "post:Update;delete:Delete") + beego.Router("/platform/complaint/list", &controllers.PlatformComplaintController{}, "get:List") + beego.Router("/platform/complaint", &controllers.PlatformComplaintController{}, "post:Create") + beego.Router("/platform/complaint/:id", &controllers.PlatformComplaintController{}, "get:Detail;post:Update;delete:Delete") + + // 软件升级产品(yz_system_software_upgrade) + beego.Router("/platform/softwareupgrade/list", &controllers.PlatformSoftwareUpgradeController{}, "get:List") + beego.Router("/platform/softwareupgrade", &controllers.PlatformSoftwareUpgradeController{}, "post:Create") + beego.Router("/platform/softwareupgrade/:id", &controllers.PlatformSoftwareUpgradeController{}, "get:Detail;post:Update;delete:Delete") + + // 租户站点设置(yz_tenant_site_setting) + beego.Router("/platform/normalInfos", &controllers.PlatformSiteSettingsController{}, "get:GetNormalInfos") + beego.Router("/platform/saveNormalInfos", &controllers.PlatformSiteSettingsController{}, "post:SaveNormalInfos") + + // 系统邮箱配置(yz_system_email) + beego.Router("/platform/email/info", &controllers.PlatformEmailController{}, "get:GetInfo") + beego.Router("/platform/email/editinfo", &controllers.PlatformEmailController{}, "post:EditInfo") + beego.Router("/platform/email/sendtestemail", &controllers.PlatformEmailController{}, "post:SendTestEmail") + + // 站内信配置与发送(yz_system_sitereminder / yz_system_reminderlist) + beego.Router("/platform/sitereminder/config", &controllers.PlatformSiteReminderController{}, "get:GetConfig;post:SaveConfig") + beego.Router("/platform/sitereminder/send", &controllers.PlatformSiteReminderController{}, "post:Send") + beego.Router("/platform/sitereminder/myList", &controllers.PlatformSiteReminderController{}, "get:GetMyList") + beego.Router("/platform/sitereminder/read", &controllers.PlatformSiteReminderController{}, "post:MarkRead") + beego.Router("/platform/sitereminder/readall", &controllers.PlatformSiteReminderController{}, "post:MarkAllRead") + beego.Router("/platform/sitereminder/delete", &controllers.PlatformSiteReminderController{}, "post:Delete") + beego.Router("/platform/sitereminder/sentList", &controllers.PlatformSiteReminderController{}, "get:GetSentList") + beego.Router("/platform/sitereminder/updateSent", &controllers.PlatformSiteReminderController{}, "post:UpdateSent") + beego.Router("/platform/sitereminder/deleteSent", &controllers.PlatformSiteReminderController{}, "post:DeleteSentBatch") + + // 短信配置(yz_system_sms) + beego.Router("/platform/sms/info", &controllers.PlatformSMSController{}, "get:GetSmsInfo") + beego.Router("/platform/sms/editinfo", &controllers.PlatformSMSController{}, "post:EditSmsInfo") + beego.Router("/platform/sms/sendtest", &controllers.PlatformSMSController{}, "post:SendTestSms") + beego.Router("/platform/sms/taskList", &controllers.PlatformSMSController{}, "get:GetSmsTaskList") + beego.Router("/platform/sms/taskEdit/:id", &controllers.PlatformSMSController{}, "post:EditSmsTask") + + // Bark 推送配置 + beego.Router("/platform/bark/info", &controllers.PlatformBarkController{}, "get:GetBarkInfo") + beego.Router("/platform/bark/editinfo", &controllers.PlatformBarkController{}, "post:EditBarkInfo") + beego.Router("/platform/bark/sendtest", &controllers.PlatformBarkController{}, "post:SendTestBark") + + // 文件管理(yz_system_files / yz_system_files_category) + beego.Router("/platform/usercate", &controllers.PlatformFileController{}, "get:GetUserCate") + beego.Router("/platform/allfiles", &controllers.PlatformFileController{}, "get:GetAllFiles") + beego.Router("/platform/catefiles/:id", &controllers.PlatformFileController{}, "get:GetCateFiles") + beego.Router("/platform/file/:id", &controllers.PlatformFileController{}, "get:GetFileByID") + beego.Router("/platform/deletefilepermanently/:id", &controllers.PlatformFileController{}, "delete:DeleteFilePermanently") + beego.Router("/platform/uploadfile", &controllers.PlatformFileController{}, "post:UploadFile") + beego.Router("/platform/uploadfiles", &controllers.PlatformFileController{}, "post:UploadFile") + beego.Router("/platform/updatefile/:id", &controllers.PlatformFileController{}, "post:UpdateFile") + beego.Router("/platform/deletefile/:id", &controllers.PlatformFileController{}, "delete:DeleteFile") + beego.Router("/platform/movefile/:id", &controllers.PlatformFileController{}, "get:MoveFile") + beego.Router("/platform/createfilecate", &controllers.PlatformFileController{}, "post:CreateFileCate") + beego.Router("/platform/renamefilecate/:id", &controllers.PlatformFileController{}, "post:RenameFileCate") + beego.Router("/platform/deletefilecate/:id", &controllers.PlatformFileController{}, "delete:DeleteFileCate") + beego.Router("/platform/uploadavatar", &controllers.PlatformFileController{}, "post:UploadAvatar") + beego.Router("/platform/uploadavatar/:id", &controllers.PlatformFileController{}, "post:UpdateAvatar") + beego.Router("/platform/batchdeletefiles", &controllers.PlatformFileController{}, "post:BatchDeleteFiles") + beego.Router("/platform/batchDeleteFilesPermanently", &controllers.PlatformFileController{}, "post:BatchDeleteFilesPermanently") + beego.Router("/platform/batchMoveFiles", &controllers.PlatformFileController{}, "post:BatchMoveFiles") + + // 七牛云直传相关 + beego.Router("/platform/storage/config", &controllers.QiniuUploadController{}, "get:GetStorageConfig") + beego.Router("/platform/qiniu/token", &controllers.QiniuUploadController{}, "get:GetUploadToken") + beego.Router("/platform/qiniu/save", &controllers.QiniuUploadController{}, "post:SaveFileRecord") + + // 首页统计 + beego.Router("/platform/home/accountPoolDailyExtract", &controllers.PlatformHomeController{}, "get:AccountPoolDailyExtract") + beego.Router("/platform/home/accountPoolInventoryTotals", &controllers.PlatformHomeController{}, "get:AccountPoolInventoryTotals") + + // Cursor 设备管理(yz_platform_cursor_equipment) + beego.Router("/platform/cursor/equipment/list", &controllers.PlatformCursorEquipmentController{}, "get:List") + beego.Router("/platform/cursor/equipment/detail/:id", &controllers.PlatformCursorEquipmentController{}, "get:Detail") + beego.Router("/platform/cursor/equipment/add", &controllers.PlatformCursorEquipmentController{}, "post:Add") + beego.Router("/platform/cursor/equipment/update", &controllers.PlatformCursorEquipmentController{}, "post:Update") + beego.Router("/platform/cursor/equipment/delete/:id", &controllers.PlatformCursorEquipmentController{}, "post:Delete") + beego.Router("/platform/cursor/equipment/activate", &controllers.PlatformCursorEquipmentController{}, "post:Activate") + beego.Router("/platform/cursor/equipment/activationRecords", &controllers.PlatformCursorEquipmentController{}, "get:ActivationRecords") + beego.Router("/platform/cursor/equipment/extractRecords", &controllers.PlatformCursorEquipmentController{}, "get:ExtractRecords") + beego.Router("/platform/cursor/equipment/ipLogs", &controllers.PlatformCursorEquipmentController{}, "get:IpLogs") + + // Cursor 激活码管理(yz_platform_cursor_activation_code) + beego.Router("/platform/cursor/activationcode/list", &controllers.PlatformCursorActivationCodeController{}, "get:List") + beego.Router("/platform/cursor/activationcode/detail/:id", &controllers.PlatformCursorActivationCodeController{}, "get:Detail") + beego.Router("/platform/cursor/activationcode/add", &controllers.PlatformCursorActivationCodeController{}, "post:Add") + beego.Router("/platform/cursor/activationcode/update", &controllers.PlatformCursorActivationCodeController{}, "post:Update") + beego.Router("/platform/cursor/activationcode/delete/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Delete") + beego.Router("/platform/cursor/activationcode/generate", &controllers.PlatformCursorActivationCodeController{}, "post:Generate") + beego.Router("/platform/cursor/activationcode/enable/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Enable") + beego.Router("/platform/cursor/activationcode/disable/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Disable") + beego.Router("/platform/cursor/activationcode/export", &controllers.PlatformCursorActivationCodeController{}, "get:Export") + + // 账号池管理(cursor/windsurf/krio/codex) + beego.Router("/platform/accountPool/cursor/list", &controllers.PlatformAccountPoolCursorController{}, "get:List") + beego.Router("/platform/accountPool/cursor/add", &controllers.PlatformAccountPoolCursorController{}, "post:Add") + beego.Router("/platform/accountPool/cursor/batchAdd", &controllers.PlatformAccountPoolCursorController{}, "post:BatchAdd") + beego.Router("/platform/accountPool/cursor/detail/:id", &controllers.PlatformAccountPoolCursorController{}, "get:Detail") + beego.Router("/platform/accountPool/cursor/extract", &controllers.PlatformAccountPoolCursorController{}, "post:Extract") + beego.Router("/platform/accountPool/cursor/updateRemark", &controllers.PlatformAccountPoolCursorController{}, "post:UpdateRemark") + beego.Router("/platform/accountPool/cursor/setUnavailable", &controllers.PlatformAccountPoolCursorController{}, "post:SetUnavailable") + beego.Router("/platform/accountPool/cursor/updateUsable", &controllers.PlatformAccountPoolCursorController{}, "post:UpdateUsable") + beego.Router("/platform/accountPool/cursor/updatePlatform", &controllers.PlatformAccountPoolCursorController{}, "post:UpdatePlatform") + beego.Router("/platform/accountPool/cursor/unextract", &controllers.PlatformAccountPoolCursorController{}, "post:Unextract") + beego.Router("/platform/accountPool/cursor/replenish", &controllers.PlatformAccountPoolCursorController{}, "post:Replenish") + beego.Router("/platform/accountPool/cursor/probeToken", &controllers.PlatformAccountPoolCursorController{}, "post:ProbeToken") + + beego.Router("/platform/accountPool/windsurf/list", &controllers.PlatformAccountPoolWindsurfController{}, "get:List") + beego.Router("/platform/accountPool/windsurf/add", &controllers.PlatformAccountPoolWindsurfController{}, "post:Add") + beego.Router("/platform/accountPool/windsurf/batchAdd", &controllers.PlatformAccountPoolWindsurfController{}, "post:BatchAdd") + beego.Router("/platform/accountPool/windsurf/detail/:id", &controllers.PlatformAccountPoolWindsurfController{}, "get:Detail") + beego.Router("/platform/accountPool/windsurf/extract", &controllers.PlatformAccountPoolWindsurfController{}, "post:Extract") + beego.Router("/platform/accountPool/windsurf/updateRemark", &controllers.PlatformAccountPoolWindsurfController{}, "post:UpdateRemark") + beego.Router("/platform/accountPool/windsurf/setUnavailable", &controllers.PlatformAccountPoolWindsurfController{}, "post:SetUnavailable") + beego.Router("/platform/accountPool/windsurf/updatePlatform", &controllers.PlatformAccountPoolWindsurfController{}, "post:UpdatePlatform") + beego.Router("/platform/accountPool/windsurf/unextract", &controllers.PlatformAccountPoolWindsurfController{}, "post:Unextract") + beego.Router("/platform/accountPool/windsurf/replenish", &controllers.PlatformAccountPoolWindsurfController{}, "post:Replenish") + beego.Router("/platform/accountPool/windsurf/probeToken", &controllers.PlatformAccountPoolWindsurfController{}, "post:ProbeToken") + + beego.Router("/platform/accountPool/krio/list", &controllers.PlatformAccountPoolKrioController{}, "get:List") + beego.Router("/platform/accountPool/krio/add", &controllers.PlatformAccountPoolKrioController{}, "post:Add") + beego.Router("/platform/accountPool/krio/batchAdd", &controllers.PlatformAccountPoolKrioController{}, "post:BatchAdd") + beego.Router("/platform/accountPool/krio/detail/:id", &controllers.PlatformAccountPoolKrioController{}, "get:Detail") + beego.Router("/platform/accountPool/krio/extract", &controllers.PlatformAccountPoolKrioController{}, "post:Extract") + beego.Router("/platform/accountPool/krio/updateRemark", &controllers.PlatformAccountPoolKrioController{}, "post:UpdateRemark") + beego.Router("/platform/accountPool/krio/setUnavailable", &controllers.PlatformAccountPoolKrioController{}, "post:SetUnavailable") + beego.Router("/platform/accountPool/krio/updatePlatform", &controllers.PlatformAccountPoolKrioController{}, "post:UpdatePlatform") + beego.Router("/platform/accountPool/krio/unextract", &controllers.PlatformAccountPoolKrioController{}, "post:Unextract") + beego.Router("/platform/accountPool/krio/replenish", &controllers.PlatformAccountPoolKrioController{}, "post:Replenish") + beego.Router("/platform/accountPool/krio/probeToken", &controllers.PlatformAccountPoolKrioController{}, "post:ProbeToken") + + beego.Router("/platform/accountPool/codex/list", &controllers.PlatformAccountPoolCodexController{}, "get:List") + beego.Router("/platform/accountPool/codex/add", &controllers.PlatformAccountPoolCodexController{}, "post:Add") + beego.Router("/platform/accountPool/codex/batchAdd", &controllers.PlatformAccountPoolCodexController{}, "post:BatchAdd") + beego.Router("/platform/accountPool/codex/detail/:id", &controllers.PlatformAccountPoolCodexController{}, "get:Detail") + beego.Router("/platform/accountPool/codex/extract", &controllers.PlatformAccountPoolCodexController{}, "post:Extract") + beego.Router("/platform/accountPool/codex/updateRemark", &controllers.PlatformAccountPoolCodexController{}, "post:UpdateRemark") + beego.Router("/platform/accountPool/codex/setUnavailable", &controllers.PlatformAccountPoolCodexController{}, "post:SetUnavailable") + beego.Router("/platform/accountPool/codex/updatePlatform", &controllers.PlatformAccountPoolCodexController{}, "post:UpdatePlatform") + beego.Router("/platform/accountPool/codex/unextract", &controllers.PlatformAccountPoolCodexController{}, "post:Unextract") + beego.Router("/platform/accountPool/codex/replenish", &controllers.PlatformAccountPoolCodexController{}, "post:Replenish") + beego.Router("/platform/accountPool/codex/probeToken", &controllers.PlatformAccountPoolCodexController{}, "post:ProbeToken") + + // 记事本管理 + beego.Router("/platform/notebook/list", &controllers.PlatformNotebookController{}, "get:List") + beego.Router("/platform/notebook/detail/:id", &controllers.PlatformNotebookController{}, "get:Detail") + beego.Router("/platform/notebook/create", &controllers.PlatformNotebookController{}, "post:Create") + beego.Router("/platform/notebook/update/:id", &controllers.PlatformNotebookController{}, "post:Update") + beego.Router("/platform/notebook/delete/:id", &controllers.PlatformNotebookController{}, "delete:Delete") + + // 日程提醒管理 + beego.Router("/platform/reminder/list", &controllers.PlatformReminderController{}, "get:GetReminderList") + beego.Router("/platform/reminder/test", &controllers.PlatformReminderController{}, "post:TestReminder") + beego.Router("/platform/reminder/:id", &controllers.PlatformReminderController{}, "get:GetReminderDetail;put:UpdateReminder;delete:DeleteReminder") + beego.Router("/platform/reminder", &controllers.PlatformReminderController{}, "post:CreateReminder") + beego.Router("/platform/reminder/batchDelete", &controllers.PlatformReminderController{}, "post:BatchDeleteReminder") +} diff --git a/go/routers/router.go b/go/routers/router.go index cbd6501..256b63b 100644 --- a/go/routers/router.go +++ b/go/routers/router.go @@ -1,69 +1,69 @@ -package routers - -import ( - "os" - - "server/middleware" - "server/routers/api" - "server/routers/backend" - "server/routers/index" - "server/routers/platform" - - beego "github.com/beego/beego/v2/server/web" - "github.com/beego/beego/v2/server/web/context" -) - -// 初始化路由(精简版) -func init() { - // 全局 CORS 处理 + 预检请求 - // 注意:Allow-Origin 为 * 时不能同时设置 Allow-Credentials: true,否则浏览器会拒绝带 Authorization 的预检(上传/接口跨域常见现象)。 - // 当前 JWT 走 Header、前端 axios withCredentials=false,无需携带 Cookie,故不返回 Allow-Credentials。 - beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) { - ctx.Output.Header("Access-Control-Allow-Origin", "*") - ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") - ctx.Output.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") - ctx.Output.Header("Access-Control-Max-Age", "86400") - - if ctx.Input.Method() == "OPTIONS" { - ctx.Output.Status = 200 - ctx.Output.Body([]byte("")) - return - } - }) - - // 全局操作日志:请求开始采集 - beego.InsertFilter("*", beego.BeforeRouter, middleware.BeginOperationLog) - // 全局操作日志:请求结束统一落库 - beego.InsertFilter("*", beego.FinishRouter, middleware.FinishOperationLog) - - // 根据运行模式选择要注册的路由组 - // 优先读取环境变量 APP_MODE,其次读取配置 app_mode,默认 all - mode := os.Getenv("APP_MODE") - if mode == "" { - mode, _ = beego.AppConfig.String("app_mode") - } - if mode == "" { - mode = "all" - } - - switch mode { - case "platform": - platform.Register() - // 在 platform 模式下,仍保留 backend 登录相关路由,避免后台登录 404 - backend.RegisterAuthRoutes() - case "backend": - backend.Register() - case "index": - index.Register() - case "api": - api.Register() - case "all": - platform.Register() - backend.Register() - index.Register() - api.Register() - default: - // 未知模式时,退回到只启用平台端,避免启动失败 - platform.Register() - } -} +package routers + +import ( + "os" + + "server/middleware" + "server/routers/api" + "server/routers/backend" + "server/routers/index" + "server/routers/platform" + + beego "github.com/beego/beego/v2/server/web" + "github.com/beego/beego/v2/server/web/context" +) + +// 初始化路由(精简版) +func init() { + // 全局 CORS 处理 + 预检请求 + // 注意:Allow-Origin 为 * 时不能同时设置 Allow-Credentials: true,否则浏览器会拒绝带 Authorization 的预检(上传/接口跨域常见现象)。 + // 当前 JWT 走 Header、前端 axios withCredentials=false,无需携带 Cookie,故不返回 Allow-Credentials。 + beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) { + ctx.Output.Header("Access-Control-Allow-Origin", "*") + ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") + ctx.Output.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") + ctx.Output.Header("Access-Control-Max-Age", "86400") + + if ctx.Input.Method() == "OPTIONS" { + ctx.Output.Status = 200 + ctx.Output.Body([]byte("")) + return + } + }) + + // 全局操作日志:请求开始采集 + beego.InsertFilter("*", beego.BeforeRouter, middleware.BeginOperationLog) + // 全局操作日志:请求结束统一落库 + beego.InsertFilter("*", beego.FinishRouter, middleware.FinishOperationLog) + + // 根据运行模式选择要注册的路由组 + // 优先读取环境变量 APP_MODE,其次读取配置 app_mode,默认 all + mode := os.Getenv("APP_MODE") + if mode == "" { + mode, _ = beego.AppConfig.String("app_mode") + } + if mode == "" { + mode = "all" + } + + switch mode { + case "platform": + platform.Register() + // 在 platform 模式下,仍保留 backend 登录相关路由,避免后台登录 404 + backend.RegisterAuthRoutes() + case "backend": + backend.Register() + case "index": + index.Register() + case "api": + api.Register() + case "all": + platform.Register() + backend.Register() + index.Register() + api.Register() + default: + // 未知模式时,退回到只启用平台端,避免启动失败 + platform.Register() + } +} diff --git a/go/scripts/go-api.service b/go/scripts/go-api.service index f87efb5..2ef0071 100644 --- a/go/scripts/go-api.service +++ b/go/scripts/go-api.service @@ -1,29 +1,29 @@ -[Unit] -Description=Go API Server -After=network.target mysql.service -Wants=mysql.service - -[Service] -Type=simple -User=root -Group=root -WorkingDirectory=/www/wwwroot/api.yunzer.cn -ExecStart=/usr/local/go/bin/go run main.go -Restart=always -RestartSec=5 -StandardOutput=append:/www/wwwroot/api.yunzer.cn/go.log -StandardError=append:/www/wwwroot/api.yunzer.cn/go.log - -# 环境变量 -Environment="PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -# 资源限制 -LimitNOFILE=65535 -LimitNPROC=65535 - -# 安全设置 -PrivateTmp=true -NoNewPrivileges=false - -[Install] -WantedBy=multi-user.target +[Unit] +Description=Go API Server +After=network.target mysql.service +Wants=mysql.service + +[Service] +Type=simple +User=root +Group=root +WorkingDirectory=/www/wwwroot/api.yunzer.cn +ExecStart=/usr/local/go/bin/go run main.go +Restart=always +RestartSec=5 +StandardOutput=append:/www/wwwroot/api.yunzer.cn/go.log +StandardError=append:/www/wwwroot/api.yunzer.cn/go.log + +# 环境变量 +Environment="PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +# 资源限制 +LimitNOFILE=65535 +LimitNPROC=65535 + +# 安全设置 +PrivateTmp=true +NoNewPrivileges=false + +[Install] +WantedBy=multi-user.target diff --git a/go/scripts/install-systemd-service.sh b/go/scripts/install-systemd-service.sh index 5c3e392..3add692 100644 --- a/go/scripts/install-systemd-service.sh +++ b/go/scripts/install-systemd-service.sh @@ -1,168 +1,168 @@ -#!/bin/bash - -# ======================================== -# 安装 systemd 服务脚本 -# 用途:配置 Go API 为 systemd 服务 -# ======================================== - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# 配置 -SERVICE_NAME="go-api" -SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SERVICE_TEMPLATE="${SCRIPT_DIR}/${SERVICE_NAME}.service" -WORK_DIR="/www/wwwroot/api.yunzer.cn" -MAIN_FILE="${WORK_DIR}/main.go" - -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}安装 Go API systemd 服务${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" - -# 检查是否为 root -if [ "$EUID" -ne 0 ]; then - echo -e "${RED}错误:请使用 root 用户运行此脚本${NC}" - echo "使用方法: sudo bash $0" - exit 1 -fi - -# 检查工作目录 -if [ ! -d "$WORK_DIR" ]; then - echo -e "${RED}错误:工作目录不存在: $WORK_DIR${NC}" - exit 1 -fi - -# 检查 main.go -if [ ! -f "$MAIN_FILE" ]; then - echo -e "${RED}错误:找不到 main.go: $MAIN_FILE${NC}" - exit 1 -fi - -# 检查 Go 是否安装 -if ! command -v go &> /dev/null; then - echo -e "${RED}错误:Go 未安装或不在 PATH 中${NC}" - exit 1 -fi - -GO_PATH=$(which go) -echo -e "${GREEN}✓ Go 路径: $GO_PATH${NC}" - -# 停止现有服务 -echo -e "${YELLOW}1. 停止现有服务...${NC}" -if systemctl is-active --quiet "$SERVICE_NAME"; then - systemctl stop "$SERVICE_NAME" - echo -e "${GREEN} ✓ 已停止现有服务${NC}" -else - echo -e "${YELLOW} - 服务未运行${NC}" -fi - -# 停止可能的手动启动进程 -echo -e "${YELLOW}2. 清理手动启动的进程...${NC}" -if pgrep -f "go run main.go" > /dev/null; then - pkill -f "go run main.go" - echo -e "${GREEN} ✓ 已清理手动启动的进程${NC}" -else - echo -e "${YELLOW} - 无手动启动的进程${NC}" -fi - -# 创建服务文件 -echo -e "${YELLOW}3. 创建 systemd 服务文件...${NC}" - -cat > "$SERVICE_FILE" << EOF -[Unit] -Description=Go API Server -After=network.target mysql.service -Wants=mysql.service - -[Service] -Type=simple -User=root -Group=root -WorkingDirectory=$WORK_DIR -ExecStart=$GO_PATH run main.go -Restart=always -RestartSec=5 -StandardOutput=append:$WORK_DIR/go.log -StandardError=append:$WORK_DIR/go.log - -# 环境变量 -Environment="PATH=$GO_PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -# 资源限制 -LimitNOFILE=65535 -LimitNPROC=65535 - -# 安全设置 -PrivateTmp=true -NoNewPrivileges=false - -[Install] -WantedBy=multi-user.target -EOF - -echo -e "${GREEN} ✓ 服务文件已创建: $SERVICE_FILE${NC}" - -# 重载 systemd -echo -e "${YELLOW}4. 重载 systemd 配置...${NC}" -systemctl daemon-reload -echo -e "${GREEN} ✓ systemd 配置已重载${NC}" - -# 启动服务 -echo -e "${YELLOW}5. 启动服务...${NC}" -systemctl start "$SERVICE_NAME" -sleep 2 - -# 检查服务状态 -if systemctl is-active --quiet "$SERVICE_NAME"; then - echo -e "${GREEN} ✓ 服务启动成功${NC}" -else - echo -e "${RED} ✗ 服务启动失败${NC}" - echo "" - echo -e "${YELLOW}查看错误日志:${NC}" - journalctl -u "$SERVICE_NAME" -n 20 --no-pager - exit 1 -fi - -# 启用开机自启 -echo -e "${YELLOW}6. 启用开机自启...${NC}" -systemctl enable "$SERVICE_NAME" -echo -e "${GREEN} ✓ 已启用开机自启${NC}" - -# 显示服务状态 -echo "" -echo -e "${BLUE}========================================${NC}" -echo -e "${BLUE}安装完成!${NC}" -echo -e "${BLUE}========================================${NC}" -echo "" -echo -e "${GREEN}服务信息:${NC}" -echo -e " 服务名称: $SERVICE_NAME" -echo -e " 工作目录: $WORK_DIR" -echo -e " 日志文件: $WORK_DIR/go.log" -echo -e " 配置文件: $SERVICE_FILE" -echo "" -echo -e "${GREEN}常用命令:${NC}" -echo -e " 启动服务: systemctl start $SERVICE_NAME" -echo -e " 停止服务: systemctl stop $SERVICE_NAME" -echo -e " 重启服务: systemctl restart $SERVICE_NAME" -echo -e " 查看状态: systemctl status $SERVICE_NAME" -echo -e " 查看日志: journalctl -u $SERVICE_NAME -f" -echo -e " 查看文件日志: tail -f $WORK_DIR/go.log" -echo "" -echo -e "${YELLOW}当前服务状态:${NC}" -systemctl status "$SERVICE_NAME" --no-pager -l -echo "" -echo -e "${YELLOW}最近日志(最后 10 行):${NC}" -if [ -f "$WORK_DIR/go.log" ]; then - tail -n 10 "$WORK_DIR/go.log" -else - echo " 日志文件尚未创建" -fi -echo "" +#!/bin/bash + +# ======================================== +# 安装 systemd 服务脚本 +# 用途:配置 Go API 为 systemd 服务 +# ======================================== + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 配置 +SERVICE_NAME="go-api" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SERVICE_TEMPLATE="${SCRIPT_DIR}/${SERVICE_NAME}.service" +WORK_DIR="/www/wwwroot/api.yunzer.cn" +MAIN_FILE="${WORK_DIR}/main.go" + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE}安装 Go API systemd 服务${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# 检查是否为 root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}错误:请使用 root 用户运行此脚本${NC}" + echo "使用方法: sudo bash $0" + exit 1 +fi + +# 检查工作目录 +if [ ! -d "$WORK_DIR" ]; then + echo -e "${RED}错误:工作目录不存在: $WORK_DIR${NC}" + exit 1 +fi + +# 检查 main.go +if [ ! -f "$MAIN_FILE" ]; then + echo -e "${RED}错误:找不到 main.go: $MAIN_FILE${NC}" + exit 1 +fi + +# 检查 Go 是否安装 +if ! command -v go &> /dev/null; then + echo -e "${RED}错误:Go 未安装或不在 PATH 中${NC}" + exit 1 +fi + +GO_PATH=$(which go) +echo -e "${GREEN}✓ Go 路径: $GO_PATH${NC}" + +# 停止现有服务 +echo -e "${YELLOW}1. 停止现有服务...${NC}" +if systemctl is-active --quiet "$SERVICE_NAME"; then + systemctl stop "$SERVICE_NAME" + echo -e "${GREEN} ✓ 已停止现有服务${NC}" +else + echo -e "${YELLOW} - 服务未运行${NC}" +fi + +# 停止可能的手动启动进程 +echo -e "${YELLOW}2. 清理手动启动的进程...${NC}" +if pgrep -f "go run main.go" > /dev/null; then + pkill -f "go run main.go" + echo -e "${GREEN} ✓ 已清理手动启动的进程${NC}" +else + echo -e "${YELLOW} - 无手动启动的进程${NC}" +fi + +# 创建服务文件 +echo -e "${YELLOW}3. 创建 systemd 服务文件...${NC}" + +cat > "$SERVICE_FILE" << EOF +[Unit] +Description=Go API Server +After=network.target mysql.service +Wants=mysql.service + +[Service] +Type=simple +User=root +Group=root +WorkingDirectory=$WORK_DIR +ExecStart=$GO_PATH run main.go +Restart=always +RestartSec=5 +StandardOutput=append:$WORK_DIR/go.log +StandardError=append:$WORK_DIR/go.log + +# 环境变量 +Environment="PATH=$GO_PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +# 资源限制 +LimitNOFILE=65535 +LimitNPROC=65535 + +# 安全设置 +PrivateTmp=true +NoNewPrivileges=false + +[Install] +WantedBy=multi-user.target +EOF + +echo -e "${GREEN} ✓ 服务文件已创建: $SERVICE_FILE${NC}" + +# 重载 systemd +echo -e "${YELLOW}4. 重载 systemd 配置...${NC}" +systemctl daemon-reload +echo -e "${GREEN} ✓ systemd 配置已重载${NC}" + +# 启动服务 +echo -e "${YELLOW}5. 启动服务...${NC}" +systemctl start "$SERVICE_NAME" +sleep 2 + +# 检查服务状态 +if systemctl is-active --quiet "$SERVICE_NAME"; then + echo -e "${GREEN} ✓ 服务启动成功${NC}" +else + echo -e "${RED} ✗ 服务启动失败${NC}" + echo "" + echo -e "${YELLOW}查看错误日志:${NC}" + journalctl -u "$SERVICE_NAME" -n 20 --no-pager + exit 1 +fi + +# 启用开机自启 +echo -e "${YELLOW}6. 启用开机自启...${NC}" +systemctl enable "$SERVICE_NAME" +echo -e "${GREEN} ✓ 已启用开机自启${NC}" + +# 显示服务状态 +echo "" +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE}安装完成!${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" +echo -e "${GREEN}服务信息:${NC}" +echo -e " 服务名称: $SERVICE_NAME" +echo -e " 工作目录: $WORK_DIR" +echo -e " 日志文件: $WORK_DIR/go.log" +echo -e " 配置文件: $SERVICE_FILE" +echo "" +echo -e "${GREEN}常用命令:${NC}" +echo -e " 启动服务: systemctl start $SERVICE_NAME" +echo -e " 停止服务: systemctl stop $SERVICE_NAME" +echo -e " 重启服务: systemctl restart $SERVICE_NAME" +echo -e " 查看状态: systemctl status $SERVICE_NAME" +echo -e " 查看日志: journalctl -u $SERVICE_NAME -f" +echo -e " 查看文件日志: tail -f $WORK_DIR/go.log" +echo "" +echo -e "${YELLOW}当前服务状态:${NC}" +systemctl status "$SERVICE_NAME" --no-pager -l +echo "" +echo -e "${YELLOW}最近日志(最后 10 行):${NC}" +if [ -f "$WORK_DIR/go.log" ]; then + tail -n 10 "$WORK_DIR/go.log" +else + echo " 日志文件尚未创建" +fi +echo "" diff --git a/go/scripts/install_dependencies.bat b/go/scripts/install_dependencies.bat index e068e02..3f57e79 100644 --- a/go/scripts/install_dependencies.bat +++ b/go/scripts/install_dependencies.bat @@ -1,36 +1,36 @@ -@echo off -REM 安装Go依赖脚本 (Windows) - -echo 开始安装Go依赖... -echo. - -REM 进入go目录 -cd /d "%~dp0\.." - -REM 下载依赖 -echo 下载依赖包... -go mod download - -REM 整理依赖 -echo 整理依赖... -go mod tidy - -REM 验证依赖 -echo 验证依赖... -go mod verify - -echo. -echo 依赖安装完成! -echo. -echo 已安装的主要依赖: -echo - github.com/beego/beego/v2 -echo - github.com/qiniu/go-sdk/v7 (七牛云SDK) -echo - github.com/golang-jwt/jwt/v5 -echo - github.com/go-sql-driver/mysql -echo. -echo 下一步: -echo 1. 执行数据库迁移: mysql -u root -p your_database ^< migrations/add_storage_config_table.sql -echo 2. 配置存储设置: 访问平台管理后台 -^> 系统设置 -^> 平台设置 -^> 存储配置 -echo 3. 重启服务: bee run 或 go run main.go -echo. -pause +@echo off +REM 安装Go依赖脚本 (Windows) + +echo 开始安装Go依赖... +echo. + +REM 进入go目录 +cd /d "%~dp0\.." + +REM 下载依赖 +echo 下载依赖包... +go mod download + +REM 整理依赖 +echo 整理依赖... +go mod tidy + +REM 验证依赖 +echo 验证依赖... +go mod verify + +echo. +echo 依赖安装完成! +echo. +echo 已安装的主要依赖: +echo - github.com/beego/beego/v2 +echo - github.com/qiniu/go-sdk/v7 (七牛云SDK) +echo - github.com/golang-jwt/jwt/v5 +echo - github.com/go-sql-driver/mysql +echo. +echo 下一步: +echo 1. 执行数据库迁移: mysql -u root -p your_database ^< migrations/add_storage_config_table.sql +echo 2. 配置存储设置: 访问平台管理后台 -^> 系统设置 -^> 平台设置 -^> 存储配置 +echo 3. 重启服务: bee run 或 go run main.go +echo. +pause diff --git a/go/scripts/install_dependencies.sh b/go/scripts/install_dependencies.sh index f5ef24a..b6238bd 100644 --- a/go/scripts/install_dependencies.sh +++ b/go/scripts/install_dependencies.sh @@ -1,33 +1,33 @@ -#!/bin/bash - -# 安装Go依赖脚本 - -echo "开始安装Go依赖..." - -# 进入go目录 -cd "$(dirname "$0")/.." || exit - -# 下载依赖 -echo "下载依赖包..." -go mod download - -# 整理依赖 -echo "整理依赖..." -go mod tidy - -# 验证依赖 -echo "验证依赖..." -go mod verify - -echo "依赖安装完成!" -echo "" -echo "已安装的主要依赖:" -echo "- github.com/beego/beego/v2" -echo "- github.com/qiniu/go-sdk/v7 (七牛云SDK)" -echo "- github.com/golang-jwt/jwt/v5" -echo "- github.com/go-sql-driver/mysql" -echo "" -echo "下一步:" -echo "1. 执行数据库迁移: mysql -u root -p your_database < migrations/add_storage_config_table.sql" -echo "2. 配置存储设置: 访问平台管理后台 -> 系统设置 -> 平台设置 -> 存储配置" -echo "3. 重启服务: bee run 或 go run main.go" +#!/bin/bash + +# 安装Go依赖脚本 + +echo "开始安装Go依赖..." + +# 进入go目录 +cd "$(dirname "$0")/.." || exit + +# 下载依赖 +echo "下载依赖包..." +go mod download + +# 整理依赖 +echo "整理依赖..." +go mod tidy + +# 验证依赖 +echo "验证依赖..." +go mod verify + +echo "依赖安装完成!" +echo "" +echo "已安装的主要依赖:" +echo "- github.com/beego/beego/v2" +echo "- github.com/qiniu/go-sdk/v7 (七牛云SDK)" +echo "- github.com/golang-jwt/jwt/v5" +echo "- github.com/go-sql-driver/mysql" +echo "" +echo "下一步:" +echo "1. 执行数据库迁移: mysql -u root -p your_database < migrations/add_storage_config_table.sql" +echo "2. 配置存储设置: 访问平台管理后台 -> 系统设置 -> 平台设置 -> 存储配置" +echo "3. 重启服务: bee run 或 go run main.go" diff --git a/go/scripts/quick-restart.sh b/go/scripts/quick-restart.sh index 769aeac..e073e53 100644 --- a/go/scripts/quick-restart.sh +++ b/go/scripts/quick-restart.sh @@ -1,19 +1,19 @@ -#!/bin/bash - -# 快速重启脚本 -echo "停止服务..." -systemctl stop go-api -pkill -f "go run main.go" - -echo "启动服务..." -systemctl start go-api - -echo "等待服务启动..." -sleep 3 - -echo "查看服务状态..." -systemctl status go-api --no-pager - -echo "" -echo "查看最近日志..." -tail -n 20 /www/wwwroot/api.yunzer.cn/go.log +#!/bin/bash + +# 快速重启脚本 +echo "停止服务..." +systemctl stop go-api +pkill -f "go run main.go" + +echo "启动服务..." +systemctl start go-api + +echo "等待服务启动..." +sleep 3 + +echo "查看服务状态..." +systemctl status go-api --no-pager + +echo "" +echo "查看最近日志..." +tail -n 20 /www/wwwroot/api.yunzer.cn/go.log diff --git a/go/scripts/service.sh b/go/scripts/service.sh index c050654..477649e 100644 --- a/go/scripts/service.sh +++ b/go/scripts/service.sh @@ -1,285 +1,285 @@ -#!/bin/bash - -# ======================================== -# Go 服务管理脚本 -# 用途:启动、停止、重启、查看状态 -# ======================================== - -set -e - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# 配置 -SERVICE_DIR="/www/wwwroot/api.yunzer.cn" -LOG_FILE="$SERVICE_DIR/go.log" -PID_FILE="$SERVICE_DIR/go.pid" -MAIN_FILE="$SERVICE_DIR/main.go" - -# 检查服务目录 -if [ ! -d "$SERVICE_DIR" ]; then - echo -e "${RED}错误:服务目录不存在: $SERVICE_DIR${NC}" - exit 1 -fi - -# 检查 main.go -if [ ! -f "$MAIN_FILE" ]; then - echo -e "${RED}错误:找不到 main.go: $MAIN_FILE${NC}" - exit 1 -fi - -# 获取进程 ID -get_pid() { - if [ -f "$PID_FILE" ]; then - cat "$PID_FILE" - else - # 通过进程名查找 - pgrep -f "go run main.go" | head -n 1 - fi -} - -# 检查服务是否运行 -is_running() { - local pid=$(get_pid) - if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then - return 0 - else - return 1 - fi -} - -# 启动服务 -start() { - echo -e "${BLUE}========================================${NC}" - echo -e "${BLUE}启动 Go 服务${NC}" - echo -e "${BLUE}========================================${NC}" - echo "" - - if is_running; then - local pid=$(get_pid) - echo -e "${YELLOW}服务已在运行中 (PID: $pid)${NC}" - return 0 - fi - - echo -e "${YELLOW}切换到服务目录...${NC}" - cd "$SERVICE_DIR" - - echo -e "${YELLOW}启动服务...${NC}" - nohup go run main.go > "$LOG_FILE" 2>&1 & - local pid=$! - echo $pid > "$PID_FILE" - - # 等待服务启动 - sleep 2 - - if is_running; then - echo -e "${GREEN}✓ 服务启动成功 (PID: $pid)${NC}" - echo -e "${GREEN}✓ 日志文件: $LOG_FILE${NC}" - echo "" - echo -e "${YELLOW}查看日志:${NC}" - echo -e " tail -f $LOG_FILE" - echo "" - echo -e "${YELLOW}查看最近日志:${NC}" - tail -n 20 "$LOG_FILE" - else - echo -e "${RED}✗ 服务启动失败${NC}" - if [ -f "$LOG_FILE" ]; then - echo -e "${YELLOW}最近的错误日志:${NC}" - tail -n 20 "$LOG_FILE" - fi - exit 1 - fi -} - -# 停止服务 -stop() { - echo -e "${BLUE}========================================${NC}" - echo -e "${BLUE}停止 Go 服务${NC}" - echo -e "${BLUE}========================================${NC}" - echo "" - - if ! is_running; then - echo -e "${YELLOW}服务未运行${NC}" - rm -f "$PID_FILE" - return 0 - fi - - local pid=$(get_pid) - echo -e "${YELLOW}停止服务 (PID: $pid)...${NC}" - - # 尝试优雅停止 - kill "$pid" 2>/dev/null || true - - # 等待最多 10 秒 - local count=0 - while is_running && [ $count -lt 10 ]; do - sleep 1 - count=$((count + 1)) - echo -n "." - done - echo "" - - # 如果还在运行,强制停止 - if is_running; then - echo -e "${YELLOW}强制停止服务...${NC}" - kill -9 "$pid" 2>/dev/null || true - sleep 1 - fi - - # 清理所有相关进程 - pkill -f "go run main.go" 2>/dev/null || true - - rm -f "$PID_FILE" - - if is_running; then - echo -e "${RED}✗ 服务停止失败${NC}" - exit 1 - else - echo -e "${GREEN}✓ 服务已停止${NC}" - fi -} - -# 重启服务 -restart() { - echo -e "${BLUE}========================================${NC}" - echo -e "${BLUE}重启 Go 服务${NC}" - echo -e "${BLUE}========================================${NC}" - echo "" - - stop - echo "" - sleep 2 - start -} - -# 查看状态 -status() { - echo -e "${BLUE}========================================${NC}" - echo -e "${BLUE}Go 服务状态${NC}" - echo -e "${BLUE}========================================${NC}" - echo "" - - if is_running; then - local pid=$(get_pid) - echo -e "${GREEN}✓ 服务运行中${NC}" - echo -e " PID: $pid" - echo -e " 目录: $SERVICE_DIR" - echo -e " 日志: $LOG_FILE" - echo "" - - # 显示进程信息 - echo -e "${YELLOW}进程信息:${NC}" - ps aux | grep "$pid" | grep -v grep - echo "" - - # 显示端口监听 - echo -e "${YELLOW}端口监听:${NC}" - netstat -tlnp 2>/dev/null | grep "$pid" || lsof -i -P -n | grep "$pid" || echo " 无法获取端口信息" - echo "" - - # 显示最近日志 - if [ -f "$LOG_FILE" ]; then - echo -e "${YELLOW}最近日志(最后 10 行):${NC}" - tail -n 10 "$LOG_FILE" - fi - else - echo -e "${RED}✗ 服务未运行${NC}" - - # 检查是否有残留进程 - local pids=$(pgrep -f "go run main.go" || true) - if [ -n "$pids" ]; then - echo -e "${YELLOW}发现残留进程:${NC}" - ps aux | grep "go run main.go" | grep -v grep - echo "" - echo -e "${YELLOW}清理残留进程:${NC}" - echo " bash $0 stop" - fi - - # 显示最近日志 - if [ -f "$LOG_FILE" ]; then - echo "" - echo -e "${YELLOW}最近日志(最后 20 行):${NC}" - tail -n 20 "$LOG_FILE" - fi - fi -} - -# 查看日志 -logs() { - if [ ! -f "$LOG_FILE" ]; then - echo -e "${RED}日志文件不存在: $LOG_FILE${NC}" - exit 1 - fi - - if [ "$1" = "-f" ] || [ "$1" = "--follow" ]; then - echo -e "${YELLOW}实时查看日志(Ctrl+C 退出):${NC}" - tail -f "$LOG_FILE" - else - local lines=${1:-50} - echo -e "${YELLOW}最近 $lines 行日志:${NC}" - tail -n "$lines" "$LOG_FILE" - fi -} - -# 显示帮助 -help() { - echo -e "${BLUE}========================================${NC}" - echo -e "${BLUE}Go 服务管理脚本${NC}" - echo -e "${BLUE}========================================${NC}" - echo "" - echo "用法: $0 {start|stop|restart|status|logs}" - echo "" - echo "命令:" - echo " start - 启动服务" - echo " stop - 停止服务" - echo " restart - 重启服务" - echo " status - 查看服务状态" - echo " logs - 查看日志(默认最后 50 行)" - echo " logs -f - 实时查看日志" - echo " logs 100 - 查看最后 100 行日志" - echo "" - echo "示例:" - echo " $0 start # 启动服务" - echo " $0 restart # 重启服务" - echo " $0 status # 查看状态" - echo " $0 logs -f # 实时查看日志" - echo " $0 logs 100 # 查看最后 100 行" - echo "" -} - -# 主函数 -main() { - case "${1:-}" in - start) - start - ;; - stop) - stop - ;; - restart) - restart - ;; - status) - status - ;; - logs) - logs "${2:-}" - ;; - help|--help|-h) - help - ;; - *) - echo -e "${RED}错误:未知命令 '$1'${NC}" - echo "" - help - exit 1 - ;; - esac -} - -# 运行主函数 -main "$@" +#!/bin/bash + +# ======================================== +# Go 服务管理脚本 +# 用途:启动、停止、重启、查看状态 +# ======================================== + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 配置 +SERVICE_DIR="/www/wwwroot/api.yunzer.cn" +LOG_FILE="$SERVICE_DIR/go.log" +PID_FILE="$SERVICE_DIR/go.pid" +MAIN_FILE="$SERVICE_DIR/main.go" + +# 检查服务目录 +if [ ! -d "$SERVICE_DIR" ]; then + echo -e "${RED}错误:服务目录不存在: $SERVICE_DIR${NC}" + exit 1 +fi + +# 检查 main.go +if [ ! -f "$MAIN_FILE" ]; then + echo -e "${RED}错误:找不到 main.go: $MAIN_FILE${NC}" + exit 1 +fi + +# 获取进程 ID +get_pid() { + if [ -f "$PID_FILE" ]; then + cat "$PID_FILE" + else + # 通过进程名查找 + pgrep -f "go run main.go" | head -n 1 + fi +} + +# 检查服务是否运行 +is_running() { + local pid=$(get_pid) + if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then + return 0 + else + return 1 + fi +} + +# 启动服务 +start() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}启动 Go 服务${NC}" + echo -e "${BLUE}========================================${NC}" + echo "" + + if is_running; then + local pid=$(get_pid) + echo -e "${YELLOW}服务已在运行中 (PID: $pid)${NC}" + return 0 + fi + + echo -e "${YELLOW}切换到服务目录...${NC}" + cd "$SERVICE_DIR" + + echo -e "${YELLOW}启动服务...${NC}" + nohup go run main.go > "$LOG_FILE" 2>&1 & + local pid=$! + echo $pid > "$PID_FILE" + + # 等待服务启动 + sleep 2 + + if is_running; then + echo -e "${GREEN}✓ 服务启动成功 (PID: $pid)${NC}" + echo -e "${GREEN}✓ 日志文件: $LOG_FILE${NC}" + echo "" + echo -e "${YELLOW}查看日志:${NC}" + echo -e " tail -f $LOG_FILE" + echo "" + echo -e "${YELLOW}查看最近日志:${NC}" + tail -n 20 "$LOG_FILE" + else + echo -e "${RED}✗ 服务启动失败${NC}" + if [ -f "$LOG_FILE" ]; then + echo -e "${YELLOW}最近的错误日志:${NC}" + tail -n 20 "$LOG_FILE" + fi + exit 1 + fi +} + +# 停止服务 +stop() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}停止 Go 服务${NC}" + echo -e "${BLUE}========================================${NC}" + echo "" + + if ! is_running; then + echo -e "${YELLOW}服务未运行${NC}" + rm -f "$PID_FILE" + return 0 + fi + + local pid=$(get_pid) + echo -e "${YELLOW}停止服务 (PID: $pid)...${NC}" + + # 尝试优雅停止 + kill "$pid" 2>/dev/null || true + + # 等待最多 10 秒 + local count=0 + while is_running && [ $count -lt 10 ]; do + sleep 1 + count=$((count + 1)) + echo -n "." + done + echo "" + + # 如果还在运行,强制停止 + if is_running; then + echo -e "${YELLOW}强制停止服务...${NC}" + kill -9 "$pid" 2>/dev/null || true + sleep 1 + fi + + # 清理所有相关进程 + pkill -f "go run main.go" 2>/dev/null || true + + rm -f "$PID_FILE" + + if is_running; then + echo -e "${RED}✗ 服务停止失败${NC}" + exit 1 + else + echo -e "${GREEN}✓ 服务已停止${NC}" + fi +} + +# 重启服务 +restart() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}重启 Go 服务${NC}" + echo -e "${BLUE}========================================${NC}" + echo "" + + stop + echo "" + sleep 2 + start +} + +# 查看状态 +status() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}Go 服务状态${NC}" + echo -e "${BLUE}========================================${NC}" + echo "" + + if is_running; then + local pid=$(get_pid) + echo -e "${GREEN}✓ 服务运行中${NC}" + echo -e " PID: $pid" + echo -e " 目录: $SERVICE_DIR" + echo -e " 日志: $LOG_FILE" + echo "" + + # 显示进程信息 + echo -e "${YELLOW}进程信息:${NC}" + ps aux | grep "$pid" | grep -v grep + echo "" + + # 显示端口监听 + echo -e "${YELLOW}端口监听:${NC}" + netstat -tlnp 2>/dev/null | grep "$pid" || lsof -i -P -n | grep "$pid" || echo " 无法获取端口信息" + echo "" + + # 显示最近日志 + if [ -f "$LOG_FILE" ]; then + echo -e "${YELLOW}最近日志(最后 10 行):${NC}" + tail -n 10 "$LOG_FILE" + fi + else + echo -e "${RED}✗ 服务未运行${NC}" + + # 检查是否有残留进程 + local pids=$(pgrep -f "go run main.go" || true) + if [ -n "$pids" ]; then + echo -e "${YELLOW}发现残留进程:${NC}" + ps aux | grep "go run main.go" | grep -v grep + echo "" + echo -e "${YELLOW}清理残留进程:${NC}" + echo " bash $0 stop" + fi + + # 显示最近日志 + if [ -f "$LOG_FILE" ]; then + echo "" + echo -e "${YELLOW}最近日志(最后 20 行):${NC}" + tail -n 20 "$LOG_FILE" + fi + fi +} + +# 查看日志 +logs() { + if [ ! -f "$LOG_FILE" ]; then + echo -e "${RED}日志文件不存在: $LOG_FILE${NC}" + exit 1 + fi + + if [ "$1" = "-f" ] || [ "$1" = "--follow" ]; then + echo -e "${YELLOW}实时查看日志(Ctrl+C 退出):${NC}" + tail -f "$LOG_FILE" + else + local lines=${1:-50} + echo -e "${YELLOW}最近 $lines 行日志:${NC}" + tail -n "$lines" "$LOG_FILE" + fi +} + +# 显示帮助 +help() { + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE}Go 服务管理脚本${NC}" + echo -e "${BLUE}========================================${NC}" + echo "" + echo "用法: $0 {start|stop|restart|status|logs}" + echo "" + echo "命令:" + echo " start - 启动服务" + echo " stop - 停止服务" + echo " restart - 重启服务" + echo " status - 查看服务状态" + echo " logs - 查看日志(默认最后 50 行)" + echo " logs -f - 实时查看日志" + echo " logs 100 - 查看最后 100 行日志" + echo "" + echo "示例:" + echo " $0 start # 启动服务" + echo " $0 restart # 重启服务" + echo " $0 status # 查看状态" + echo " $0 logs -f # 实时查看日志" + echo " $0 logs 100 # 查看最后 100 行" + echo "" +} + +# 主函数 +main() { + case "${1:-}" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + status) + status + ;; + logs) + logs "${2:-}" + ;; + help|--help|-h) + help + ;; + *) + echo -e "${RED}错误:未知命令 '$1'${NC}" + echo "" + help + exit 1 + ;; + esac +} + +# 运行主函数 +main "$@" diff --git a/go/scripts/test_storage.sh b/go/scripts/test_storage.sh index 58ac5a2..380b558 100644 --- a/go/scripts/test_storage.sh +++ b/go/scripts/test_storage.sh @@ -1,93 +1,93 @@ -#!/bin/bash - -# 存储功能测试脚本 - -echo "================================" -echo "存储功能测试" -echo "================================" -echo "" - -# 颜色定义 -GREEN='\033[0;32m' -RED='\033[0;31m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -# 测试结果 -PASS=0 -FAIL=0 - -# 测试函数 -test_api() { - local name=$1 - local method=$2 - local url=$3 - local data=$4 - - echo -n "测试 $name ... " - - if [ "$method" = "GET" ]; then - response=$(curl -s -w "\n%{http_code}" "$url") - else - response=$(curl -s -w "\n%{http_code}" -X "$method" -H "Content-Type: application/json" -d "$data" "$url") - fi - - http_code=$(echo "$response" | tail -n1) - body=$(echo "$response" | head -n-1) - - if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then - echo -e "${GREEN}✓ PASS${NC}" - PASS=$((PASS + 1)) - else - echo -e "${RED}✗ FAIL${NC} (HTTP $http_code)" - echo " 响应: $body" - FAIL=$((FAIL + 1)) - fi -} - -# 基础URL -BASE_URL="http://localhost:8080" - -echo "1. 测试存储配置API" -echo "-------------------" - -# 测试获取存储配置 -test_api "获取存储配置" "GET" "$BASE_URL/platform/storageConfig" - -# 测试保存本地存储配置 -test_api "保存本地存储配置" "POST" "$BASE_URL/platform/saveStorageConfig" \ -'{"storage_type":"local"}' - -echo "" -echo "2. 测试文件上传" -echo "-------------------" - -# 创建测试文件 -TEST_FILE="/tmp/test_upload.txt" -echo "This is a test file" > "$TEST_FILE" - -# 测试文件上传(需要认证token,这里简化) -echo -e "${YELLOW}注意: 文件上传需要认证token,请手动测试${NC}" - -echo "" -echo "3. 检查数据库表" -echo "-------------------" - -# 检查数据库表是否存在(需要MySQL连接信息) -echo -e "${YELLOW}请手动检查数据库表: yz_system_storage_config${NC}" - -echo "" -echo "================================" -echo "测试结果" -echo "================================" -echo -e "通过: ${GREEN}$PASS${NC}" -echo -e "失败: ${RED}$FAIL${NC}" -echo "" - -if [ $FAIL -eq 0 ]; then - echo -e "${GREEN}所有测试通过!${NC}" - exit 0 -else - echo -e "${RED}部分测试失败,请检查日志${NC}" - exit 1 -fi +#!/bin/bash + +# 存储功能测试脚本 + +echo "================================" +echo "存储功能测试" +echo "================================" +echo "" + +# 颜色定义 +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# 测试结果 +PASS=0 +FAIL=0 + +# 测试函数 +test_api() { + local name=$1 + local method=$2 + local url=$3 + local data=$4 + + echo -n "测试 $name ... " + + if [ "$method" = "GET" ]; then + response=$(curl -s -w "\n%{http_code}" "$url") + else + response=$(curl -s -w "\n%{http_code}" -X "$method" -H "Content-Type: application/json" -d "$data" "$url") + fi + + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | head -n-1) + + if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then + echo -e "${GREEN}✓ PASS${NC}" + PASS=$((PASS + 1)) + else + echo -e "${RED}✗ FAIL${NC} (HTTP $http_code)" + echo " 响应: $body" + FAIL=$((FAIL + 1)) + fi +} + +# 基础URL +BASE_URL="http://localhost:8080" + +echo "1. 测试存储配置API" +echo "-------------------" + +# 测试获取存储配置 +test_api "获取存储配置" "GET" "$BASE_URL/platform/storageConfig" + +# 测试保存本地存储配置 +test_api "保存本地存储配置" "POST" "$BASE_URL/platform/saveStorageConfig" \ +'{"storage_type":"local"}' + +echo "" +echo "2. 测试文件上传" +echo "-------------------" + +# 创建测试文件 +TEST_FILE="/tmp/test_upload.txt" +echo "This is a test file" > "$TEST_FILE" + +# 测试文件上传(需要认证token,这里简化) +echo -e "${YELLOW}注意: 文件上传需要认证token,请手动测试${NC}" + +echo "" +echo "3. 检查数据库表" +echo "-------------------" + +# 检查数据库表是否存在(需要MySQL连接信息) +echo -e "${YELLOW}请手动检查数据库表: yz_system_storage_config${NC}" + +echo "" +echo "================================" +echo "测试结果" +echo "================================" +echo -e "通过: ${GREEN}$PASS${NC}" +echo -e "失败: ${RED}$FAIL${NC}" +echo "" + +if [ $FAIL -eq 0 ]; then + echo -e "${GREEN}所有测试通过!${NC}" + exit 0 +else + echo -e "${RED}部分测试失败,请检查日志${NC}" + exit 1 +fi diff --git a/go/services/admin_user.go b/go/services/admin_user.go index b1852d5..4c8470c 100644 --- a/go/services/admin_user.go +++ b/go/services/admin_user.go @@ -1,73 +1,73 @@ -package services - -import ( - "strings" - - "server/models" - "server/pkg/passwordutil" -) - -func NormalizeAccount(s string) string { - return strings.TrimSpace(s) -} - -func CreateAdminUser(account, password string, name, phone, email, qq, avatar *string, sex uint8, roleID uint64, status uint8) (uint64, error) { - hashed, err := passwordutil.Hash(password) - if err != nil { - return 0, err - } - u := &models.AdminUser{ - Account: NormalizeAccount(account), - Password: hashed, - Name: name, - Phone: phone, - Email: email, - Qq: qq, - Avatar: avatar, - Sex: sex, - RoleID: roleID, - Status: status, - } - id, err := models.Orm.Insert(u) - return uint64(id), err -} - -func GetAdminUserByID(id uint64) (*models.AdminUser, error) { - u := &models.AdminUser{ID: id} - if err := models.Orm.Read(u); err != nil { - return nil, err - } - return u, nil -} - -func UpdateAdminUser(id uint64, fields map[string]interface{}) error { - _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", id).Update(fields) - return err -} - -func DeleteAdminUser(id uint64) error { - _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", id).Delete() - return err -} - -func ChangeAdminUserPassword(id uint64, newPassword string) error { - hashed, err := passwordutil.Hash(newPassword) - if err != nil { - return err - } - _, err = models.Orm.QueryTable(new(models.AdminUser)).Filter("id", id).Update(map[string]interface{}{ - "password": hashed, - }) - return err -} - -func ListAdminUsers() ([]models.AdminUser, int64, error) { - var rows []models.AdminUser - total, err := models.Orm.QueryTable(new(models.AdminUser)).Count() - if err != nil { - return nil, 0, err - } - _, err = models.Orm.QueryTable(new(models.AdminUser)).OrderBy("-id").All(&rows) - return rows, total, err -} - +package services + +import ( + "strings" + + "server/models" + "server/pkg/passwordutil" +) + +func NormalizeAccount(s string) string { + return strings.TrimSpace(s) +} + +func CreateAdminUser(account, password string, name, phone, email, qq, avatar *string, sex uint8, roleID uint64, status uint8) (uint64, error) { + hashed, err := passwordutil.Hash(password) + if err != nil { + return 0, err + } + u := &models.AdminUser{ + Account: NormalizeAccount(account), + Password: hashed, + Name: name, + Phone: phone, + Email: email, + Qq: qq, + Avatar: avatar, + Sex: sex, + RoleID: roleID, + Status: status, + } + id, err := models.Orm.Insert(u) + return uint64(id), err +} + +func GetAdminUserByID(id uint64) (*models.AdminUser, error) { + u := &models.AdminUser{ID: id} + if err := models.Orm.Read(u); err != nil { + return nil, err + } + return u, nil +} + +func UpdateAdminUser(id uint64, fields map[string]interface{}) error { + _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", id).Update(fields) + return err +} + +func DeleteAdminUser(id uint64) error { + _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", id).Delete() + return err +} + +func ChangeAdminUserPassword(id uint64, newPassword string) error { + hashed, err := passwordutil.Hash(newPassword) + if err != nil { + return err + } + _, err = models.Orm.QueryTable(new(models.AdminUser)).Filter("id", id).Update(map[string]interface{}{ + "password": hashed, + }) + return err +} + +func ListAdminUsers() ([]models.AdminUser, int64, error) { + var rows []models.AdminUser + total, err := models.Orm.QueryTable(new(models.AdminUser)).Count() + if err != nil { + return nil, 0, err + } + _, err = models.Orm.QueryTable(new(models.AdminUser)).OrderBy("-id").All(&rows) + return rows, total, err +} + diff --git a/go/services/login_verify_code.go b/go/services/login_verify_code.go index 963027f..ebbb235 100644 --- a/go/services/login_verify_code.go +++ b/go/services/login_verify_code.go @@ -1,232 +1,232 @@ -package services - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "math/rand" - "net/http" - "strings" - "sync" - "time" - - "server/models" -) - -type loginCodeItem struct { - Code string - Channel string - ExpiredAt time.Time -} - -var loginCodeStore sync.Map - -func codeKey(account, channel string) string { - return strings.ToLower(strings.TrimSpace(account)) + "|" + strings.TrimSpace(channel) -} - -func SendPlatformLoginCode(account, channel string) error { - account = strings.TrimSpace(account) - channel = strings.TrimSpace(channel) - if account == "" { - return errors.New("账号不能为空") - } - if channel != "sms" && channel != "email" { - return errors.New("仅支持短信或邮箱验证码") - } - - var u models.AdminUser - if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("account", account).One(&u); err != nil { - return errors.New("用户不存在") - } - if u.Status == 0 { - return errors.New("账号已禁用") - } - if channel == "sms" && (u.Phone == nil || strings.TrimSpace(*u.Phone) == "") { - return errors.New("该账号未绑定手机号") - } - if channel == "email" && (u.Email == nil || strings.TrimSpace(*u.Email) == "") { - return errors.New("该账号未绑定邮箱") - } - - rand.Seed(time.Now().UnixNano()) - code := fmt.Sprintf("%06d", rand.Intn(1000000)) - loginCodeStore.Store(codeKey(account, channel), loginCodeItem{ - Code: code, - Channel: channel, - ExpiredAt: time.Now().Add(5 * time.Minute), - }) - return nil -} - -func VerifyPlatformLoginCode(account, channel, code string) error { - account = strings.TrimSpace(account) - channel = strings.TrimSpace(channel) - code = strings.TrimSpace(code) - if account == "" || code == "" { - return errors.New("验证码不能为空") - } - val, ok := loginCodeStore.Load(codeKey(account, channel)) - if !ok { - return errors.New("验证码不存在或已失效") - } - item, ok := val.(loginCodeItem) - if !ok { - return errors.New("验证码状态异常") - } - if time.Now().After(item.ExpiredAt) { - loginCodeStore.Delete(codeKey(account, channel)) - return errors.New("验证码已过期") - } - if item.Code != code { - return errors.New("验证码错误") - } - loginCodeStore.Delete(codeKey(account, channel)) - return nil -} - -func SendBackendLoginCode(tenantName, account, channel string) error { - tenantName = strings.TrimSpace(tenantName) - account = strings.TrimSpace(account) - channel = strings.TrimSpace(channel) - if tenantName == "" || account == "" { - return errors.New("租户名称和账号不能为空") - } - if channel != "sms" && channel != "email" { - return errors.New("仅支持短信或邮箱验证码") - } - - var tenant models.SystemTenant - if err := models.Orm.QueryTable(new(models.SystemTenant)).Filter("tenant_name", tenantName).One(&tenant); err != nil { - return errors.New("租户不存在") - } - - rand.Seed(time.Now().UnixNano()) - code := fmt.Sprintf("%06d", rand.Intn(1000000)) - - switch channel { - case "sms": - phone := account - var user models.SystemTenantUser - if err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("tid", tenant.ID). - Filter("phone", phone). - One(&user); err != nil { - return errors.New("该手机号非当前企业绑定号码,请重试") - } - if user.Status == 0 { - return errors.New("账号已禁用") - } - if user.Phone == nil || strings.TrimSpace(*user.Phone) == "" { - return errors.New("该手机号非当前企业绑定号码,请重试") - } - - content := "短信验证码:" + code - if err := enqueueSMSTaskForLogin(tenant.ID, phone, content, code); err != nil { - return errors.New("短信发送失败,请重试") - } - case "email": - email := account - var user models.SystemTenantUser - if err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("tid", tenant.ID). - Filter("email", email). - One(&user); err != nil { - return errors.New("该账号未绑定邮箱") - } - if user.Status == 0 { - return errors.New("账号已禁用") - } - if user.Email == nil || strings.TrimSpace(*user.Email) == "" { - return errors.New("该账号未绑定邮箱") - } - } - - loginCodeStore.Store(codeKey(tenantName+"#"+account, channel), loginCodeItem{ - Code: code, - Channel: channel, - ExpiredAt: time.Now().Add(5 * time.Minute), - }) - return nil -} - -func VerifyBackendLoginCode(tenantName, account, channel, code string) error { - return VerifyPlatformLoginCode(tenantName+"#"+account, channel, code) -} - -func getDefaultSystemSMSConfig() (backendURL string, apiKey string, err error) { - backendURL = models.GetPlatformSettingValue("sms_custom_url", "") - apiKey = models.GetPlatformSettingValue("sms_custom_key", "") - if backendURL == "" || apiKey == "" { - return "", "", fmt.Errorf("短信网关未配置") - } - return backendURL, apiKey, nil -} - -// enqueueSMSTaskForLogin 入队短信任务到网关,并写入 yz_system_sms_tasks -func enqueueSMSTaskForLogin(tid uint64, phone, content, code string) error { - backendURL, apiKey, err := getDefaultSystemSMSConfig() - if err != nil { - return err - } - if backendURL == "" || apiKey == "" { - return errors.New("短信网关未配置") - } - - enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks" - payload := map[string]interface{}{ - "phone": phone, - "content": content, - } - bs, _ := json.Marshal(payload) - - client := &http.Client{Timeout: 10 * time.Second} - req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs)) - if err != nil { - return err - } - req.Header.Set("X-Api-Key", apiKey) - req.Header.Set("Content-Type", "application/json; charset=utf-8") - req.Header.Set("Accept", "application/json") - - resp, err := client.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - bodyBytes, _ := io.ReadAll(resp.Body) - bodyStr := strings.TrimSpace(string(bodyBytes)) - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return fmt.Errorf("gateway http status: %d, body: %s", resp.StatusCode, bodyStr) - } - - now := time.Now() - tidCopy := tid - contentPtr := content - var reportPtr *string - if bodyStr != "" { - reportPtr = &bodyStr - } - - task := &models.SystemSMSTask{ - Tid: &tidCopy, - ApiKey: apiKey, - Phone: phone, - Content: &contentPtr, - Status: 3, - Code: code, - ReportRaw: reportPtr, - CreateTime: &now, - UpdateTime: &now, - } - - _, insertErr := models.Orm.Insert(task) - if insertErr != nil { - return nil - } - return nil -} +package services + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "math/rand" + "net/http" + "strings" + "sync" + "time" + + "server/models" +) + +type loginCodeItem struct { + Code string + Channel string + ExpiredAt time.Time +} + +var loginCodeStore sync.Map + +func codeKey(account, channel string) string { + return strings.ToLower(strings.TrimSpace(account)) + "|" + strings.TrimSpace(channel) +} + +func SendPlatformLoginCode(account, channel string) error { + account = strings.TrimSpace(account) + channel = strings.TrimSpace(channel) + if account == "" { + return errors.New("账号不能为空") + } + if channel != "sms" && channel != "email" { + return errors.New("仅支持短信或邮箱验证码") + } + + var u models.AdminUser + if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("account", account).One(&u); err != nil { + return errors.New("用户不存在") + } + if u.Status == 0 { + return errors.New("账号已禁用") + } + if channel == "sms" && (u.Phone == nil || strings.TrimSpace(*u.Phone) == "") { + return errors.New("该账号未绑定手机号") + } + if channel == "email" && (u.Email == nil || strings.TrimSpace(*u.Email) == "") { + return errors.New("该账号未绑定邮箱") + } + + rand.Seed(time.Now().UnixNano()) + code := fmt.Sprintf("%06d", rand.Intn(1000000)) + loginCodeStore.Store(codeKey(account, channel), loginCodeItem{ + Code: code, + Channel: channel, + ExpiredAt: time.Now().Add(5 * time.Minute), + }) + return nil +} + +func VerifyPlatformLoginCode(account, channel, code string) error { + account = strings.TrimSpace(account) + channel = strings.TrimSpace(channel) + code = strings.TrimSpace(code) + if account == "" || code == "" { + return errors.New("验证码不能为空") + } + val, ok := loginCodeStore.Load(codeKey(account, channel)) + if !ok { + return errors.New("验证码不存在或已失效") + } + item, ok := val.(loginCodeItem) + if !ok { + return errors.New("验证码状态异常") + } + if time.Now().After(item.ExpiredAt) { + loginCodeStore.Delete(codeKey(account, channel)) + return errors.New("验证码已过期") + } + if item.Code != code { + return errors.New("验证码错误") + } + loginCodeStore.Delete(codeKey(account, channel)) + return nil +} + +func SendBackendLoginCode(tenantName, account, channel string) error { + tenantName = strings.TrimSpace(tenantName) + account = strings.TrimSpace(account) + channel = strings.TrimSpace(channel) + if tenantName == "" || account == "" { + return errors.New("租户名称和账号不能为空") + } + if channel != "sms" && channel != "email" { + return errors.New("仅支持短信或邮箱验证码") + } + + var tenant models.SystemTenant + if err := models.Orm.QueryTable(new(models.SystemTenant)).Filter("tenant_name", tenantName).One(&tenant); err != nil { + return errors.New("租户不存在") + } + + rand.Seed(time.Now().UnixNano()) + code := fmt.Sprintf("%06d", rand.Intn(1000000)) + + switch channel { + case "sms": + phone := account + var user models.SystemTenantUser + if err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", tenant.ID). + Filter("phone", phone). + One(&user); err != nil { + return errors.New("该手机号非当前企业绑定号码,请重试") + } + if user.Status == 0 { + return errors.New("账号已禁用") + } + if user.Phone == nil || strings.TrimSpace(*user.Phone) == "" { + return errors.New("该手机号非当前企业绑定号码,请重试") + } + + content := "短信验证码:" + code + if err := enqueueSMSTaskForLogin(tenant.ID, phone, content, code); err != nil { + return errors.New("短信发送失败,请重试") + } + case "email": + email := account + var user models.SystemTenantUser + if err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", tenant.ID). + Filter("email", email). + One(&user); err != nil { + return errors.New("该账号未绑定邮箱") + } + if user.Status == 0 { + return errors.New("账号已禁用") + } + if user.Email == nil || strings.TrimSpace(*user.Email) == "" { + return errors.New("该账号未绑定邮箱") + } + } + + loginCodeStore.Store(codeKey(tenantName+"#"+account, channel), loginCodeItem{ + Code: code, + Channel: channel, + ExpiredAt: time.Now().Add(5 * time.Minute), + }) + return nil +} + +func VerifyBackendLoginCode(tenantName, account, channel, code string) error { + return VerifyPlatformLoginCode(tenantName+"#"+account, channel, code) +} + +func getDefaultSystemSMSConfig() (backendURL string, apiKey string, err error) { + backendURL = models.GetPlatformSettingValue("sms_custom_url", "") + apiKey = models.GetPlatformSettingValue("sms_custom_key", "") + if backendURL == "" || apiKey == "" { + return "", "", fmt.Errorf("短信网关未配置") + } + return backendURL, apiKey, nil +} + +// enqueueSMSTaskForLogin 入队短信任务到网关,并写入 yz_system_sms_tasks +func enqueueSMSTaskForLogin(tid uint64, phone, content, code string) error { + backendURL, apiKey, err := getDefaultSystemSMSConfig() + if err != nil { + return err + } + if backendURL == "" || apiKey == "" { + return errors.New("短信网关未配置") + } + + enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks" + payload := map[string]interface{}{ + "phone": phone, + "content": content, + } + bs, _ := json.Marshal(payload) + + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs)) + if err != nil { + return err + } + req.Header.Set("X-Api-Key", apiKey) + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Accept", "application/json") + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + bodyBytes, _ := io.ReadAll(resp.Body) + bodyStr := strings.TrimSpace(string(bodyBytes)) + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("gateway http status: %d, body: %s", resp.StatusCode, bodyStr) + } + + now := time.Now() + tidCopy := tid + contentPtr := content + var reportPtr *string + if bodyStr != "" { + reportPtr = &bodyStr + } + + task := &models.SystemSMSTask{ + Tid: &tidCopy, + ApiKey: apiKey, + Phone: phone, + Content: &contentPtr, + Status: 3, + Code: code, + ReportRaw: reportPtr, + CreateTime: &now, + UpdateTime: &now, + } + + _, insertErr := models.Orm.Insert(task) + if insertErr != nil { + return nil + } + return nil +} diff --git a/go/services/permission.go b/go/services/permission.go index 116efe1..cbdf86b 100644 --- a/go/services/permission.go +++ b/go/services/permission.go @@ -1,51 +1,51 @@ -package services - -import ( - "encoding/json" - "strings" - - "server/models" -) - -// CheckUserPermission 校验用户是否拥有指定权限标识。 -// 兼容 rights 为 JSON 数组 / 逗号分隔字符串;解析失败时默认放行,避免历史数据阻断请求。 -func CheckUserPermission(userID int, permission string) (bool, error) { - if permission == "" || userID <= 0 { - return true, nil - } - - var user models.AdminUser - if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", userID).One(&user); err != nil { - return false, err - } - - var role models.AdminRole - if err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", user.RoleID).One(&role); err != nil { - return false, err - } - if role.Rights == nil || strings.TrimSpace(*role.Rights) == "" { - return true, nil - } - rightsRaw := strings.TrimSpace(*role.Rights) - - // 1) JSON 数组格式 - var arr []string - if err := json.Unmarshal([]byte(rightsRaw), &arr); err == nil { - for _, p := range arr { - if strings.TrimSpace(p) == permission { - return true, nil - } - } - return false, nil - } - - // 2) 逗号分隔字符串 - for _, p := range strings.Split(rightsRaw, ",") { - if strings.TrimSpace(p) == permission { - return true, nil - } - } - - return false, nil -} - +package services + +import ( + "encoding/json" + "strings" + + "server/models" +) + +// CheckUserPermission 校验用户是否拥有指定权限标识。 +// 兼容 rights 为 JSON 数组 / 逗号分隔字符串;解析失败时默认放行,避免历史数据阻断请求。 +func CheckUserPermission(userID int, permission string) (bool, error) { + if permission == "" || userID <= 0 { + return true, nil + } + + var user models.AdminUser + if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", userID).One(&user); err != nil { + return false, err + } + + var role models.AdminRole + if err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", user.RoleID).One(&role); err != nil { + return false, err + } + if role.Rights == nil || strings.TrimSpace(*role.Rights) == "" { + return true, nil + } + rightsRaw := strings.TrimSpace(*role.Rights) + + // 1) JSON 数组格式 + var arr []string + if err := json.Unmarshal([]byte(rightsRaw), &arr); err == nil { + for _, p := range arr { + if strings.TrimSpace(p) == permission { + return true, nil + } + } + return false, nil + } + + // 2) 逗号分隔字符串 + for _, p := range strings.Split(rightsRaw, ",") { + if strings.TrimSpace(p) == permission { + return true, nil + } + } + + return false, nil +} + diff --git a/go/services/platform_auth.go b/go/services/platform_auth.go index 770a9b8..0f10c1e 100644 --- a/go/services/platform_auth.go +++ b/go/services/platform_auth.go @@ -1,158 +1,158 @@ -package services - -import ( - "errors" - "strings" - - "server/models" - "server/pkg/jwtutil" - "server/pkg/passwordutil" -) - -type PlatformLoginUser struct { - ID uint64 - Account string - Name string - Tid uint64 - Rid uint64 - Avatar string - RoleName string -} - -func adminRoleNameByID(roleID uint64) string { - if roleID == 0 { - return "" - } - var role models.AdminRole - err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", roleID).One(&role) - if err != nil { - return "" - } - return role.Name -} - -func toPlatformLoginUser(user *models.AdminUser) *PlatformLoginUser { - name := "" - if user.Name != nil { - name = *user.Name - } - avatar := "" - if user.Avatar != nil { - avatar = *user.Avatar - } - return &PlatformLoginUser{ - ID: user.ID, - Account: user.Account, - Name: name, - Tid: 0, - Rid: user.RoleID, - Avatar: avatar, - RoleName: adminRoleNameByID(user.RoleID), - } -} - -// PlatformAdminLogin 平台端登录:仅校验 yz_system_admin_user,不需要租户。 -func PlatformAdminLogin(account, password string) (string, *PlatformLoginUser, error) { - account = strings.TrimSpace(account) - password = strings.TrimSpace(password) - if account == "" || password == "" { - return "", nil, errors.New("用户名或密码不能为空") - } - - var user models.AdminUser - err := models.Orm.QueryTable(new(models.AdminUser)). - Filter("account", account). - One(&user) - if err != nil { - return "", nil, errors.New("用户名或密码错误") - } - if user.Status == 0 { - return "", nil, errors.New("账号已禁用") - } - if !passwordutil.Verify(user.Password, password) { - return "", nil, errors.New("用户名或密码错误") - } - - const tenantID = 0 - const userType = "platform" - token, err := jwtutil.GenerateToken(int(user.ID), user.Account, tenantID, userType) - if err != nil { - return "", nil, err - } - - loginUser := toPlatformLoginUser(&user) - return token, loginUser, nil -} - -// BackendLogin backend 登录:先校验租户,再校验租户下用户账号和密码。 -func BackendLogin(tenantName, account, password string) (string, *PlatformLoginUser, error) { - tenantName = strings.TrimSpace(tenantName) - account = strings.TrimSpace(account) - password = strings.TrimSpace(password) - if tenantName == "" || account == "" || password == "" { - return "", nil, errors.New("租户名称、用户名或密码不能为空") - } - - var tenant models.SystemTenant - err := models.Orm.QueryTable(new(models.SystemTenant)). - Filter("tenant_name", tenantName). - One(&tenant) - if err != nil { - return "", nil, errors.New("租户不存在") - } - if tenant.Status != 1 { - return "", nil, errors.New("租户已停用") - } - - var tenantUser models.SystemTenantUser - err = models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("tid", tenant.ID). - Filter("account", account). - One(&tenantUser) - if err != nil { - return "", nil, errors.New("用户名或密码错误") - } - if tenantUser.Status == 0 { - return "", nil, errors.New("账号已禁用") - } - if tenantUser.Password == nil || !passwordutil.Verify(*tenantUser.Password, password) { - return "", nil, errors.New("用户名或密码错误") - } - - tenantID := int(tenant.ID) - const userType = "backend" - token, err := jwtutil.GenerateToken(int(tenantUser.Uid), account, tenantID, userType) - if err != nil { - return "", nil, err - } - - loginUser := &PlatformLoginUser{ - ID: tenantUser.Uid, - Account: account, - Name: "", - Tid: tenant.ID, - Rid: 0, - Avatar: "", - RoleName: "", - } - if tenantUser.Account != nil && strings.TrimSpace(*tenantUser.Account) != "" { - loginUser.Account = strings.TrimSpace(*tenantUser.Account) - } - if tenantUser.Name != nil { - loginUser.Name = strings.TrimSpace(*tenantUser.Name) - } - - return token, loginUser, nil -} - -// PlatformGetCurrentUser 根据平台管理员用户 ID 返回登录用户信息(含角色名称)。 -func PlatformGetCurrentUser(uid uint64) (*PlatformLoginUser, error) { - u, err := GetAdminUserByID(uid) - if err != nil { - return nil, errors.New("用户不存在") - } - if u.Status == 0 { - return nil, errors.New("账号已禁用") - } - return toPlatformLoginUser(u), nil -} +package services + +import ( + "errors" + "strings" + + "server/models" + "server/pkg/jwtutil" + "server/pkg/passwordutil" +) + +type PlatformLoginUser struct { + ID uint64 + Account string + Name string + Tid uint64 + Rid uint64 + Avatar string + RoleName string +} + +func adminRoleNameByID(roleID uint64) string { + if roleID == 0 { + return "" + } + var role models.AdminRole + err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", roleID).One(&role) + if err != nil { + return "" + } + return role.Name +} + +func toPlatformLoginUser(user *models.AdminUser) *PlatformLoginUser { + name := "" + if user.Name != nil { + name = *user.Name + } + avatar := "" + if user.Avatar != nil { + avatar = *user.Avatar + } + return &PlatformLoginUser{ + ID: user.ID, + Account: user.Account, + Name: name, + Tid: 0, + Rid: user.RoleID, + Avatar: avatar, + RoleName: adminRoleNameByID(user.RoleID), + } +} + +// PlatformAdminLogin 平台端登录:仅校验 yz_system_admin_user,不需要租户。 +func PlatformAdminLogin(account, password string) (string, *PlatformLoginUser, error) { + account = strings.TrimSpace(account) + password = strings.TrimSpace(password) + if account == "" || password == "" { + return "", nil, errors.New("用户名或密码不能为空") + } + + var user models.AdminUser + err := models.Orm.QueryTable(new(models.AdminUser)). + Filter("account", account). + One(&user) + if err != nil { + return "", nil, errors.New("用户名或密码错误") + } + if user.Status == 0 { + return "", nil, errors.New("账号已禁用") + } + if !passwordutil.Verify(user.Password, password) { + return "", nil, errors.New("用户名或密码错误") + } + + const tenantID = 0 + const userType = "platform" + token, err := jwtutil.GenerateToken(int(user.ID), user.Account, tenantID, userType) + if err != nil { + return "", nil, err + } + + loginUser := toPlatformLoginUser(&user) + return token, loginUser, nil +} + +// BackendLogin backend 登录:先校验租户,再校验租户下用户账号和密码。 +func BackendLogin(tenantName, account, password string) (string, *PlatformLoginUser, error) { + tenantName = strings.TrimSpace(tenantName) + account = strings.TrimSpace(account) + password = strings.TrimSpace(password) + if tenantName == "" || account == "" || password == "" { + return "", nil, errors.New("租户名称、用户名或密码不能为空") + } + + var tenant models.SystemTenant + err := models.Orm.QueryTable(new(models.SystemTenant)). + Filter("tenant_name", tenantName). + One(&tenant) + if err != nil { + return "", nil, errors.New("租户不存在") + } + if tenant.Status != 1 { + return "", nil, errors.New("租户已停用") + } + + var tenantUser models.SystemTenantUser + err = models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", tenant.ID). + Filter("account", account). + One(&tenantUser) + if err != nil { + return "", nil, errors.New("用户名或密码错误") + } + if tenantUser.Status == 0 { + return "", nil, errors.New("账号已禁用") + } + if tenantUser.Password == nil || !passwordutil.Verify(*tenantUser.Password, password) { + return "", nil, errors.New("用户名或密码错误") + } + + tenantID := int(tenant.ID) + const userType = "backend" + token, err := jwtutil.GenerateToken(int(tenantUser.Uid), account, tenantID, userType) + if err != nil { + return "", nil, err + } + + loginUser := &PlatformLoginUser{ + ID: tenantUser.Uid, + Account: account, + Name: "", + Tid: tenant.ID, + Rid: 0, + Avatar: "", + RoleName: "", + } + if tenantUser.Account != nil && strings.TrimSpace(*tenantUser.Account) != "" { + loginUser.Account = strings.TrimSpace(*tenantUser.Account) + } + if tenantUser.Name != nil { + loginUser.Name = strings.TrimSpace(*tenantUser.Name) + } + + return token, loginUser, nil +} + +// PlatformGetCurrentUser 根据平台管理员用户 ID 返回登录用户信息(含角色名称)。 +func PlatformGetCurrentUser(uid uint64) (*PlatformLoginUser, error) { + u, err := GetAdminUserByID(uid) + if err != nil { + return nil, errors.New("用户不存在") + } + if u.Status == 0 { + return nil, errors.New("账号已禁用") + } + return toPlatformLoginUser(u), nil +} diff --git a/go/services/reminder_scheduler.go b/go/services/reminder_scheduler.go index 059ddf6..39ea447 100644 --- a/go/services/reminder_scheduler.go +++ b/go/services/reminder_scheduler.go @@ -1,371 +1,371 @@ -package services - -import ( - "bytes" - "context" - "crypto/rand" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" - "time" - - "server/models" -) - -// ReminderSender 提醒发送接口 -type ReminderSender interface { - Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (success bool, err error) -} - -// SMSSender 短信发送实现 -type SMSSender struct{} - -func (s *SMSSender) Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (bool, error) { - backendURL, apiKey, err := getDefaultSystemSMSConfig() - if err != nil { - return false, err - } - phone := "" - if reminder.ReceiverTarget != nil && *reminder.ReceiverTarget != "" { - phone = *reminder.ReceiverTarget - } else { - var user models.AdminUser - if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", reminder.ReceiverUserID).One(&user); err == nil && user.Phone != nil { - phone = *user.Phone - } - } - if phone == "" { - return false, fmt.Errorf("未配置手机号") - } - - enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks" - payload := map[string]interface{}{ - "phone": phone, - "content": title + ": " + content, - } - bs, _ := json.Marshal(payload) - - client := &http.Client{Timeout: 10 * time.Second} - req, err := http.NewRequestWithContext(ctx, "POST", enqueueURL, bytes.NewReader(bs)) - if err != nil { - return false, err - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-Api-Key", apiKey) - - resp, err := client.Do(req) - if err != nil { - return false, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := io.ReadAll(resp.Body) - return false, fmt.Errorf("网关返回HTTP状态码: %d, 返回内容: %s", resp.StatusCode, string(bodyBytes)) - } - - return true, nil -} - -// EmailSender 邮件发送实现 -type EmailSender struct{} - -func (s *EmailSender) Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (bool, error) { - emails, err := ListSystemEmails() - if err != nil || len(emails) == 0 { - return false, fmt.Errorf("未配置系统邮箱") - } - emailCfg := emails[0] - if emailCfg.FromAddress == "" || emailCfg.Host == "" { - return false, fmt.Errorf("未配置系统邮箱") - } - - toEmail := "" - if reminder.ReceiverTarget != nil && *reminder.ReceiverTarget != "" { - toEmail = *reminder.ReceiverTarget - } else { - var user models.AdminUser - if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", reminder.ReceiverUserID).One(&user); err == nil && user.Email != nil { - toEmail = *user.Email - } - } - if toEmail == "" { - return false, fmt.Errorf("未配置收件邮箱") - } - - sysDomain := models.GetPlatformSettingValue("system_domain", "https://api.yunzer.cn") - ackToken := "" - if reminder.AckToken != nil { - ackToken = *reminder.AckToken - } - - // 构造 HTML 邮件 - htmlBody := fmt.Sprintf(` -
-

日程提醒:%s

-

%s

-
- `, title, content) - - if ackToken != "" { - ackURL := fmt.Sprintf("%s/api/schedule/reminder/ack?token=%s", strings.TrimRight(sysDomain, "/"), ackToken) - htmlBody += fmt.Sprintf(` - -

确认收到后,系统将不再向您发送该日程的重复提醒。

- `, ackURL) - } - - htmlBody += "
" - - cfg := SMTPConfig{ - FromAddress: emailCfg.FromAddress, - Host: emailCfg.Host, - Port: emailCfg.Port, - Password: emailCfg.Password, - Encryption: emailCfg.Encryption, - Timeout: emailCfg.Timeout, - } - if emailCfg.FromName != nil { - cfg.FromName = *emailCfg.FromName - } - - err = SendHTMLEmailSMTP(cfg, toEmail, title, htmlBody) - if err != nil { - return false, err - } - - return true, nil -} - -// BarkSender Bark 推送实现 -type BarkSender struct{} - -func (s *BarkSender) Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (bool, error) { - deviceKey := "" - if reminder.ReceiverTarget != nil && *reminder.ReceiverTarget != "" { - deviceKey = *reminder.ReceiverTarget - } else { - deviceKey = models.GetPlatformSettingValue("bark_device_key", "") - } - if deviceKey == "" { - return false, fmt.Errorf("Bark 设备 Key 未配置") - } - - serverURL := models.GetPlatformSettingValue("bark_server_url", "https://api.day.app") - sysDomain := models.GetPlatformSettingValue("system_domain", "https://api.yunzer.cn") - ackToken := "" - if reminder.AckToken != nil { - ackToken = *reminder.AckToken - } - - baseURL := strings.TrimRight(serverURL, "/") - escapedTitle := url.PathEscape(title) - pushContent := content - if ackToken != "" { - pushContent += "\n确认收到请点击→" - } - escapedContent := url.PathEscape(pushContent) - - barkURL := fmt.Sprintf("%s/%s/%s/%s", baseURL, deviceKey, escapedTitle, escapedContent) - - if ackToken != "" { - ackURL := fmt.Sprintf("%s/api/schedule/reminder/ack?token=%s", strings.TrimRight(sysDomain, "/"), ackToken) - // Bark 官方推送支持 url 参数 - barkURL += "?url=" + url.QueryEscape(ackURL) - } - - client := &http.Client{Timeout: 10 * time.Second} - req, err := http.NewRequestWithContext(ctx, "GET", barkURL, nil) - if err != nil { - return false, err - } - - resp, err := client.Do(req) - if err != nil { - return false, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - bodyBytes, _ := io.ReadAll(resp.Body) - return false, fmt.Errorf("Bark返回HTTP状态码: %d, 返回内容: %s", resp.StatusCode, string(bodyBytes)) - } - - return true, nil -} - -// SiteMsgSender 站内信发送实现 -type SiteMsgSender struct{} - -func (s *SiteMsgSender) Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (bool, error) { - now := time.Now() - msg := &models.SystemReminderList{ - Title: title, - Content: content, - SenderID: 0, - SenderType: "system", - ReceiverID: reminder.ReceiverUserID, - ReceiverType: "platform", // 平台端用户 - IsRead: 0, - CreateTime: &now, - } - _, err := models.Orm.Insert(msg) - if err != nil { - return false, err - } - return true, nil -} - -// generateUUID 生成一个安全的随机 UUID 字符 -func generateUUID() string { - b := make([]byte, 16) - _, _ = rand.Read(b) - b[6] = (b[6] & 0x0f) | 0x40 - b[8] = (b[8] & 0x3f) | 0x80 - return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) -} - -// StartReminderScheduler 启动定时提醒调度器 (1分钟一次的 Ticker) -func StartReminderScheduler(stopChan chan struct{}) { - ticker := time.NewTicker(1 * time.Minute) - go func() { - for { - select { - case <-ticker.C: - scanAndSendReminders() - case <-stopChan: - ticker.Stop() - return - } - } - }() -} - -func scanAndSendReminders() { - // 1. 生成唯一扫描批次号用于抢占锁定 - scanBatch := generateUUID() - now := time.Now() - - // 2. 抢占待处理的数据(乐观锁防并发重复发送) - _, err := models.Orm.Raw(` - UPDATE yz_platform_schedule_reminder - SET scan_lock = ?, update_time = NOW() - WHERE next_remind_time <= ? - AND remind_status IN (0, 1) - AND is_deleted = 0 - AND (scan_lock = '' OR scan_lock IS NULL) - `, scanBatch, now).Exec() - if err != nil { - return - } - - // 3. 查询自己锁定成功的数据 - var list []models.PlatformScheduleReminder - _, err = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("scan_lock", scanBatch). - Filter("remind_status__in", 0, 1). - Filter("is_deleted", 0). - All(&list) - if err != nil || len(list) == 0 { - return - } - - // 实例分发发送 - senders := map[string]ReminderSender{ - "SMS": &SMSSender{}, - "EMAIL": &EmailSender{}, - "BARK": &BarkSender{}, - "SITE_MSG": &SiteMsgSender{}, - } - - for i := range list { - reminder := &list[i] - - // 3.1 获取日程信息(主要拿 Content,Title 统一为 "日程提醒") - var schedule models.PlatformSchedule - err := models.Orm.QueryTable(new(models.PlatformSchedule)). - Filter("id", reminder.ScheduleID). - One(&schedule) - title := "日程提醒" - content := "您有一个待处理的日程时间已到,请注意查收。" - if err == nil { - content = schedule.Content - } - - sender, ok := senders[reminder.RemindChannel] - if !ok { - // 未知渠道,直接强制置为结束 - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("id", reminder.ID). - Update(map[string]interface{}{ - "remind_status": 2, - "scan_lock": "", - "update_time": time.Now(), - }) - continue - } - - // 执行发送 - ctx := context.Background() - success, sendErr := sender.Send(ctx, reminder, title, content) - - // 3.2 记录发送流水日志 - sendResult := int8(0) - var failReason *string - if success { - sendResult = 1 - } else if sendErr != nil { - errStr := sendErr.Error() - if len(errStr) > 255 { - errStr = errStr[:255] - } - failReason = &errStr - } - - logRow := &models.PlatformScheduleReminderSendLog{ - ReminderID: reminder.ID, - SendTime: time.Now(), - SendResult: sendResult, - FailReason: failReason, - } - _, _ = models.Orm.Insert(logRow) - - // 3.3 根据发送渠道分类更新提醒状态和下一次发送时间 - newSendCount := reminder.SendCount + 1 - newStatus := reminder.RemindStatus - - if reminder.RemindChannel == "SMS" || reminder.RemindChannel == "SITE_MSG" { - // 一次性发送:发送后直接置为结束 - newStatus = 2 - } else { - // 重复发送渠道 EMAIL / BARK - // 如果还没被 Ack,且没有达到 max_send_count,继续提醒 - if reminder.AckStatus == 0 && newSendCount < reminder.MaxSendCount { - newStatus = 1 // 提醒中 - // 更新下次发送时间 - reminder.NextRemindTime = time.Now().Add(time.Duration(reminder.RepeatIntervalMinutes) * time.Minute) - } else { - // 达到最大上限或者已 Ack - newStatus = 2 - } - } - - // 3.4 回写主表记录 - _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). - Filter("id", reminder.ID). - Update(map[string]interface{}{ - "SendCount": newSendCount, - "NextRemindTime": reminder.NextRemindTime, - "RemindStatus": newStatus, - "ScanLock": "", // 释放扫描锁 - "UpdateTime": time.Now(), - }) - } -} +package services + +import ( + "bytes" + "context" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "server/models" +) + +// ReminderSender 提醒发送接口 +type ReminderSender interface { + Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (success bool, err error) +} + +// SMSSender 短信发送实现 +type SMSSender struct{} + +func (s *SMSSender) Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (bool, error) { + backendURL, apiKey, err := getDefaultSystemSMSConfig() + if err != nil { + return false, err + } + phone := "" + if reminder.ReceiverTarget != nil && *reminder.ReceiverTarget != "" { + phone = *reminder.ReceiverTarget + } else { + var user models.AdminUser + if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", reminder.ReceiverUserID).One(&user); err == nil && user.Phone != nil { + phone = *user.Phone + } + } + if phone == "" { + return false, fmt.Errorf("未配置手机号") + } + + enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks" + payload := map[string]interface{}{ + "phone": phone, + "content": title + ": " + content, + } + bs, _ := json.Marshal(payload) + + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequestWithContext(ctx, "POST", enqueueURL, bytes.NewReader(bs)) + if err != nil { + return false, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Api-Key", apiKey) + + resp, err := client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return false, fmt.Errorf("网关返回HTTP状态码: %d, 返回内容: %s", resp.StatusCode, string(bodyBytes)) + } + + return true, nil +} + +// EmailSender 邮件发送实现 +type EmailSender struct{} + +func (s *EmailSender) Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (bool, error) { + emails, err := ListSystemEmails() + if err != nil || len(emails) == 0 { + return false, fmt.Errorf("未配置系统邮箱") + } + emailCfg := emails[0] + if emailCfg.FromAddress == "" || emailCfg.Host == "" { + return false, fmt.Errorf("未配置系统邮箱") + } + + toEmail := "" + if reminder.ReceiverTarget != nil && *reminder.ReceiverTarget != "" { + toEmail = *reminder.ReceiverTarget + } else { + var user models.AdminUser + if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("id", reminder.ReceiverUserID).One(&user); err == nil && user.Email != nil { + toEmail = *user.Email + } + } + if toEmail == "" { + return false, fmt.Errorf("未配置收件邮箱") + } + + sysDomain := models.GetPlatformSettingValue("system_domain", "https://api.yunzer.cn") + ackToken := "" + if reminder.AckToken != nil { + ackToken = *reminder.AckToken + } + + // 构造 HTML 邮件 + htmlBody := fmt.Sprintf(` +
+

日程提醒:%s

+

%s

+
+ `, title, content) + + if ackToken != "" { + ackURL := fmt.Sprintf("%s/api/schedule/reminder/ack?token=%s", strings.TrimRight(sysDomain, "/"), ackToken) + htmlBody += fmt.Sprintf(` + +

确认收到后,系统将不再向您发送该日程的重复提醒。

+ `, ackURL) + } + + htmlBody += "
" + + cfg := SMTPConfig{ + FromAddress: emailCfg.FromAddress, + Host: emailCfg.Host, + Port: emailCfg.Port, + Password: emailCfg.Password, + Encryption: emailCfg.Encryption, + Timeout: emailCfg.Timeout, + } + if emailCfg.FromName != nil { + cfg.FromName = *emailCfg.FromName + } + + err = SendHTMLEmailSMTP(cfg, toEmail, title, htmlBody) + if err != nil { + return false, err + } + + return true, nil +} + +// BarkSender Bark 推送实现 +type BarkSender struct{} + +func (s *BarkSender) Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (bool, error) { + deviceKey := "" + if reminder.ReceiverTarget != nil && *reminder.ReceiverTarget != "" { + deviceKey = *reminder.ReceiverTarget + } else { + deviceKey = models.GetPlatformSettingValue("bark_device_key", "") + } + if deviceKey == "" { + return false, fmt.Errorf("Bark 设备 Key 未配置") + } + + serverURL := models.GetPlatformSettingValue("bark_server_url", "https://api.day.app") + sysDomain := models.GetPlatformSettingValue("system_domain", "https://api.yunzer.cn") + ackToken := "" + if reminder.AckToken != nil { + ackToken = *reminder.AckToken + } + + baseURL := strings.TrimRight(serverURL, "/") + escapedTitle := url.PathEscape(title) + pushContent := content + if ackToken != "" { + pushContent += "\n确认收到请点击→" + } + escapedContent := url.PathEscape(pushContent) + + barkURL := fmt.Sprintf("%s/%s/%s/%s", baseURL, deviceKey, escapedTitle, escapedContent) + + if ackToken != "" { + ackURL := fmt.Sprintf("%s/api/schedule/reminder/ack?token=%s", strings.TrimRight(sysDomain, "/"), ackToken) + // Bark 官方推送支持 url 参数 + barkURL += "?url=" + url.QueryEscape(ackURL) + } + + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequestWithContext(ctx, "GET", barkURL, nil) + if err != nil { + return false, err + } + + resp, err := client.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + return false, fmt.Errorf("Bark返回HTTP状态码: %d, 返回内容: %s", resp.StatusCode, string(bodyBytes)) + } + + return true, nil +} + +// SiteMsgSender 站内信发送实现 +type SiteMsgSender struct{} + +func (s *SiteMsgSender) Send(ctx context.Context, reminder *models.PlatformScheduleReminder, title, content string) (bool, error) { + now := time.Now() + msg := &models.SystemReminderList{ + Title: title, + Content: content, + SenderID: 0, + SenderType: "system", + ReceiverID: reminder.ReceiverUserID, + ReceiverType: "platform", // 平台端用户 + IsRead: 0, + CreateTime: &now, + } + _, err := models.Orm.Insert(msg) + if err != nil { + return false, err + } + return true, nil +} + +// generateUUID 生成一个安全的随机 UUID 字符 +func generateUUID() string { + b := make([]byte, 16) + _, _ = rand.Read(b) + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) +} + +// StartReminderScheduler 启动定时提醒调度器 (1分钟一次的 Ticker) +func StartReminderScheduler(stopChan chan struct{}) { + ticker := time.NewTicker(1 * time.Minute) + go func() { + for { + select { + case <-ticker.C: + scanAndSendReminders() + case <-stopChan: + ticker.Stop() + return + } + } + }() +} + +func scanAndSendReminders() { + // 1. 生成唯一扫描批次号用于抢占锁定 + scanBatch := generateUUID() + now := time.Now() + + // 2. 抢占待处理的数据(乐观锁防并发重复发送) + _, err := models.Orm.Raw(` + UPDATE yz_platform_schedule_reminder + SET scan_lock = ?, update_time = NOW() + WHERE next_remind_time <= ? + AND remind_status IN (0, 1) + AND is_deleted = 0 + AND (scan_lock = '' OR scan_lock IS NULL) + `, scanBatch, now).Exec() + if err != nil { + return + } + + // 3. 查询自己锁定成功的数据 + var list []models.PlatformScheduleReminder + _, err = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("scan_lock", scanBatch). + Filter("remind_status__in", 0, 1). + Filter("is_deleted", 0). + All(&list) + if err != nil || len(list) == 0 { + return + } + + // 实例分发发送 + senders := map[string]ReminderSender{ + "SMS": &SMSSender{}, + "EMAIL": &EmailSender{}, + "BARK": &BarkSender{}, + "SITE_MSG": &SiteMsgSender{}, + } + + for i := range list { + reminder := &list[i] + + // 3.1 获取日程信息(主要拿 Content,Title 统一为 "日程提醒") + var schedule models.PlatformSchedule + err := models.Orm.QueryTable(new(models.PlatformSchedule)). + Filter("id", reminder.ScheduleID). + One(&schedule) + title := "日程提醒" + content := "您有一个待处理的日程时间已到,请注意查收。" + if err == nil { + content = schedule.Content + } + + sender, ok := senders[reminder.RemindChannel] + if !ok { + // 未知渠道,直接强制置为结束 + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("id", reminder.ID). + Update(map[string]interface{}{ + "remind_status": 2, + "scan_lock": "", + "update_time": time.Now(), + }) + continue + } + + // 执行发送 + ctx := context.Background() + success, sendErr := sender.Send(ctx, reminder, title, content) + + // 3.2 记录发送流水日志 + sendResult := int8(0) + var failReason *string + if success { + sendResult = 1 + } else if sendErr != nil { + errStr := sendErr.Error() + if len(errStr) > 255 { + errStr = errStr[:255] + } + failReason = &errStr + } + + logRow := &models.PlatformScheduleReminderSendLog{ + ReminderID: reminder.ID, + SendTime: time.Now(), + SendResult: sendResult, + FailReason: failReason, + } + _, _ = models.Orm.Insert(logRow) + + // 3.3 根据发送渠道分类更新提醒状态和下一次发送时间 + newSendCount := reminder.SendCount + 1 + newStatus := reminder.RemindStatus + + if reminder.RemindChannel == "SMS" || reminder.RemindChannel == "SITE_MSG" { + // 一次性发送:发送后直接置为结束 + newStatus = 2 + } else { + // 重复发送渠道 EMAIL / BARK + // 如果还没被 Ack,且没有达到 max_send_count,继续提醒 + if reminder.AckStatus == 0 && newSendCount < reminder.MaxSendCount { + newStatus = 1 // 提醒中 + // 更新下次发送时间 + reminder.NextRemindTime = time.Now().Add(time.Duration(reminder.RepeatIntervalMinutes) * time.Minute) + } else { + // 达到最大上限或者已 Ack + newStatus = 2 + } + } + + // 3.4 回写主表记录 + _, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)). + Filter("id", reminder.ID). + Update(map[string]interface{}{ + "SendCount": newSendCount, + "NextRemindTime": reminder.NextRemindTime, + "RemindStatus": newStatus, + "ScanLock": "", // 释放扫描锁 + "UpdateTime": time.Now(), + }) + } +} diff --git a/go/services/software_upgrade_url.go b/go/services/software_upgrade_url.go index 27e5cd3..79800e4 100644 --- a/go/services/software_upgrade_url.go +++ b/go/services/software_upgrade_url.go @@ -1,6 +1,7 @@ package services import ( + "encoding/json" "strings" "server/models" @@ -21,6 +22,49 @@ func PublicRequestBaseURL(c *beego.Controller) (scheme, host string) { return scheme, host } +// ResolveSoftwareDownloadURL 优先使用自定义 download_url;否则根据 file_id 读附件 src 拼完整 URL +func ResolveSoftwareDownloadURLs(scheme, host string, downloadURLs *string) map[string]string { + result := map[string]string{} + if downloadURLs == nil || strings.TrimSpace(*downloadURLs) == "" { + return result + } + raw := strings.TrimSpace(*downloadURLs) + var values map[string]string + if err := json.Unmarshal([]byte(raw), &values); err != nil { + return result + } + for k, v := range values { + platform := strings.ToLower(strings.TrimSpace(k)) + url := strings.TrimSpace(v) + if platform == "" || url == "" { + continue + } + result[platform] = ResolvePublicURL(scheme, host, url) + } + return result +} + +// ResolvePublicURL 把相对路径拼成公开 URL;http(s) 地址原样返回。 +func ResolvePublicURL(scheme, host, src string) string { + src = strings.TrimSpace(src) + if src == "" { + return "" + } + if strings.HasPrefix(strings.ToLower(src), "http://") || strings.HasPrefix(strings.ToLower(src), "https://") { + return src + } + if host == "" { + return src + } + if !strings.HasPrefix(src, "/") { + src = "/" + src + } + if scheme == "" { + scheme = "http" + } + return scheme + "://" + host + src +} + // ResolveSoftwareDownloadURL 优先使用自定义 download_url;否则根据 file_id 读附件 src 拼完整 URL func ResolveSoftwareDownloadURL(scheme, host string, downloadURL *string, fileID *uint64) string { if downloadURL != nil { @@ -40,21 +84,5 @@ func ResolveSoftwareDownloadURL(scheme, host string, downloadURL *string, fileID if err != nil { return "" } - src := strings.TrimSpace(f.Src) - if src == "" { - return "" - } - if strings.HasPrefix(strings.ToLower(src), "http://") || strings.HasPrefix(strings.ToLower(src), "https://") { - return src - } - if host == "" { - return src - } - if !strings.HasPrefix(src, "/") { - src = "/" + src - } - if scheme == "" { - scheme = "http" - } - return scheme + "://" + host + src + return ResolvePublicURL(scheme, host, f.Src) } diff --git a/go/services/storage_migration.go b/go/services/storage_migration.go index 7e929cd..f0155fe 100644 --- a/go/services/storage_migration.go +++ b/go/services/storage_migration.go @@ -1,191 +1,191 @@ -package services - -import ( - "fmt" - "mime/multipart" - "os" - "path/filepath" - "strings" - "sync" - - "server/models" -) - -// MigrationProgress 迁移进度 -type MigrationProgress struct { - Total int - Success int - Failed int - Current string - Errors []string - mu sync.Mutex -} - -// AddSuccess 增加成功计数 -func (p *MigrationProgress) AddSuccess() { - p.mu.Lock() - defer p.mu.Unlock() - p.Success++ -} - -// AddFailed 增加失败计数 -func (p *MigrationProgress) AddFailed(err string) { - p.mu.Lock() - defer p.mu.Unlock() - p.Failed++ - p.Errors = append(p.Errors, err) -} - -// SetCurrent 设置当前处理的文件 -func (p *MigrationProgress) SetCurrent(filename string) { - p.mu.Lock() - defer p.mu.Unlock() - p.Current = filename -} - -// GetProgress 获取进度信息 -func (p *MigrationProgress) GetProgress() (int, int, int, string) { - p.mu.Lock() - defer p.mu.Unlock() - return p.Total, p.Success, p.Failed, p.Current -} - -// StorageMigration 存储迁移服务 -type StorageMigration struct { - fromService StorageService - toService StorageService - progress *MigrationProgress -} - -// NewStorageMigration 创建存储迁移服务 -func NewStorageMigration(from, to StorageService) *StorageMigration { - return &StorageMigration{ - fromService: from, - toService: to, - progress: &MigrationProgress{ - Errors: make([]string, 0), - }, - } -} - -// MigrateFile 迁移单个文件 -func (m *StorageMigration) MigrateFile(file *models.SystemFile) error { - m.progress.SetCurrent(file.Name) - - // 如果是本地存储,从本地读取文件 - if localFrom, ok := m.fromService.(*LocalStorage); ok { - // 从本地文件系统读取 - localPath := strings.TrimPrefix(file.Src, "/") - filePath := filepath.Join(localFrom.BaseDir, localPath) - - f, err := os.Open(filePath) - if err != nil { - return fmt.Errorf("打开本地文件失败: %w", err) - } - defer f.Close() - - // 获取文件信息 - stat, err := f.Stat() - if err != nil { - return fmt.Errorf("获取文件信息失败: %w", err) - } - - // 创建 multipart.FileHeader - header := &multipart.FileHeader{ - Filename: file.Name, - Size: stat.Size(), - } - - // 上传到目标存储 - result, err := m.toService.Upload(f, header) - if err != nil { - return fmt.Errorf("上传到目标存储失败: %w", err) - } - - // 更新数据库记录 - _, err = models.Orm.QueryTable(new(models.SystemFile)). - Filter("id", file.ID). - Update(map[string]interface{}{ - "src": result.URL, - }) - if err != nil { - // 上传成功但更新数据库失败,尝试删除已上传的文件 - _ = m.toService.Delete(result.Key) - return fmt.Errorf("更新数据库失败: %w", err) - } - - m.progress.AddSuccess() - return nil - } - - // 如果是七牛云存储,需要先下载再上传(这里简化处理) - return fmt.Errorf("暂不支持从七牛云迁移到本地") -} - -// MigrateAll 迁移所有文件 -func (m *StorageMigration) MigrateAll(tid uint64) error { - // 获取所有文件 - var files []models.SystemFile - _, err := models.Orm.QueryTable(new(models.SystemFile)). - Filter("tid", tid). - Filter("delete_time__isnull", true). - All(&files) - if err != nil { - return fmt.Errorf("获取文件列表失败: %w", err) - } - - m.progress.Total = len(files) - - // 并发迁移(限制并发数) - concurrency := 5 - sem := make(chan struct{}, concurrency) - var wg sync.WaitGroup - - for i := range files { - wg.Add(1) - go func(file *models.SystemFile) { - defer wg.Done() - sem <- struct{}{} // 获取信号量 - defer func() { <-sem }() // 释放信号量 - - if err := m.MigrateFile(file); err != nil { - m.progress.AddFailed(fmt.Sprintf("%s: %v", file.Name, err)) - } - }(&files[i]) - } - - wg.Wait() - return nil -} - -// GetProgress 获取迁移进度 -func (m *StorageMigration) GetProgress() *MigrationProgress { - return m.progress -} - -// MigrateLocalToQiniu 从本地存储迁移到七牛云 -func MigrateLocalToQiniu(tid uint64) (*MigrationProgress, error) { - // 获取存储配置 - cfg, err := models.GetStorageConfig() - if err != nil { - return nil, fmt.Errorf("获取存储配置失败: %w", err) - } - - if cfg.StorageType != "qiniu" { - return nil, fmt.Errorf("当前存储类型不是七牛云") - } - - // 创建存储服务 - localStorage := NewLocalStorage() - qiniuStorage := NewQiniuStorage(cfg) - - // 创建迁移服务 - migration := NewStorageMigration(localStorage, qiniuStorage) - - // 执行迁移 - if err := migration.MigrateAll(tid); err != nil { - return migration.GetProgress(), err - } - - return migration.GetProgress(), nil -} +package services + +import ( + "fmt" + "mime/multipart" + "os" + "path/filepath" + "strings" + "sync" + + "server/models" +) + +// MigrationProgress 迁移进度 +type MigrationProgress struct { + Total int + Success int + Failed int + Current string + Errors []string + mu sync.Mutex +} + +// AddSuccess 增加成功计数 +func (p *MigrationProgress) AddSuccess() { + p.mu.Lock() + defer p.mu.Unlock() + p.Success++ +} + +// AddFailed 增加失败计数 +func (p *MigrationProgress) AddFailed(err string) { + p.mu.Lock() + defer p.mu.Unlock() + p.Failed++ + p.Errors = append(p.Errors, err) +} + +// SetCurrent 设置当前处理的文件 +func (p *MigrationProgress) SetCurrent(filename string) { + p.mu.Lock() + defer p.mu.Unlock() + p.Current = filename +} + +// GetProgress 获取进度信息 +func (p *MigrationProgress) GetProgress() (int, int, int, string) { + p.mu.Lock() + defer p.mu.Unlock() + return p.Total, p.Success, p.Failed, p.Current +} + +// StorageMigration 存储迁移服务 +type StorageMigration struct { + fromService StorageService + toService StorageService + progress *MigrationProgress +} + +// NewStorageMigration 创建存储迁移服务 +func NewStorageMigration(from, to StorageService) *StorageMigration { + return &StorageMigration{ + fromService: from, + toService: to, + progress: &MigrationProgress{ + Errors: make([]string, 0), + }, + } +} + +// MigrateFile 迁移单个文件 +func (m *StorageMigration) MigrateFile(file *models.SystemFile) error { + m.progress.SetCurrent(file.Name) + + // 如果是本地存储,从本地读取文件 + if localFrom, ok := m.fromService.(*LocalStorage); ok { + // 从本地文件系统读取 + localPath := strings.TrimPrefix(file.Src, "/") + filePath := filepath.Join(localFrom.BaseDir, localPath) + + f, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("打开本地文件失败: %w", err) + } + defer f.Close() + + // 获取文件信息 + stat, err := f.Stat() + if err != nil { + return fmt.Errorf("获取文件信息失败: %w", err) + } + + // 创建 multipart.FileHeader + header := &multipart.FileHeader{ + Filename: file.Name, + Size: stat.Size(), + } + + // 上传到目标存储 + result, err := m.toService.Upload(f, header) + if err != nil { + return fmt.Errorf("上传到目标存储失败: %w", err) + } + + // 更新数据库记录 + _, err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", file.ID). + Update(map[string]interface{}{ + "src": result.URL, + }) + if err != nil { + // 上传成功但更新数据库失败,尝试删除已上传的文件 + _ = m.toService.Delete(result.Key) + return fmt.Errorf("更新数据库失败: %w", err) + } + + m.progress.AddSuccess() + return nil + } + + // 如果是七牛云存储,需要先下载再上传(这里简化处理) + return fmt.Errorf("暂不支持从七牛云迁移到本地") +} + +// MigrateAll 迁移所有文件 +func (m *StorageMigration) MigrateAll(tid uint64) error { + // 获取所有文件 + var files []models.SystemFile + _, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + All(&files) + if err != nil { + return fmt.Errorf("获取文件列表失败: %w", err) + } + + m.progress.Total = len(files) + + // 并发迁移(限制并发数) + concurrency := 5 + sem := make(chan struct{}, concurrency) + var wg sync.WaitGroup + + for i := range files { + wg.Add(1) + go func(file *models.SystemFile) { + defer wg.Done() + sem <- struct{}{} // 获取信号量 + defer func() { <-sem }() // 释放信号量 + + if err := m.MigrateFile(file); err != nil { + m.progress.AddFailed(fmt.Sprintf("%s: %v", file.Name, err)) + } + }(&files[i]) + } + + wg.Wait() + return nil +} + +// GetProgress 获取迁移进度 +func (m *StorageMigration) GetProgress() *MigrationProgress { + return m.progress +} + +// MigrateLocalToQiniu 从本地存储迁移到七牛云 +func MigrateLocalToQiniu(tid uint64) (*MigrationProgress, error) { + // 获取存储配置 + cfg, err := models.GetStorageConfig() + if err != nil { + return nil, fmt.Errorf("获取存储配置失败: %w", err) + } + + if cfg.StorageType != "qiniu" { + return nil, fmt.Errorf("当前存储类型不是七牛云") + } + + // 创建存储服务 + localStorage := NewLocalStorage() + qiniuStorage := NewQiniuStorage(cfg) + + // 创建迁移服务 + migration := NewStorageMigration(localStorage, qiniuStorage) + + // 执行迁移 + if err := migration.MigrateAll(tid); err != nil { + return migration.GetProgress(), err + } + + return migration.GetProgress(), nil +} diff --git a/go/services/storage_service.go b/go/services/storage_service.go index 0e33e46..7e439dd 100644 --- a/go/services/storage_service.go +++ b/go/services/storage_service.go @@ -1,252 +1,252 @@ -package services - -import ( - "context" - "crypto/md5" - "encoding/hex" - "fmt" - "io" - "mime/multipart" - "os" - "path/filepath" - "strings" - "time" - - "server/models" - - "github.com/qiniu/go-sdk/v7/auth/qbox" - "github.com/qiniu/go-sdk/v7/storage" -) - -// StorageService 存储服务接口 -type StorageService interface { - Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) - GetPublicURL(key string) string - Delete(key string) error -} - -// UploadResult 上传结果 -type UploadResult struct { - URL string // 完整访问URL - Key string // 存储key/路径 - Size int64 // 文件大小 - MD5 string // 文件MD5 - MimeType string // 文件类型 -} - -// LocalStorage 本地存储实现 -type LocalStorage struct { - BaseDir string // 基础目录,默认 "uploads" - BaseURL string // 基础URL,默认 "/" -} - -// NewLocalStorage 创建本地存储服务 -func NewLocalStorage() *LocalStorage { - return &LocalStorage{ - BaseDir: "uploads", - BaseURL: "/", - } -} - -// Upload 上传文件到本地 -func (s *LocalStorage) Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) { - // 生成存储路径 - ext := filepath.Ext(header.Filename) - datePath := time.Now().Format("2006/01/02") - fileName := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext) - savePath := filepath.Join(datePath, fileName) - - // 创建目录 - destDir := filepath.Join(s.BaseDir, filepath.FromSlash(datePath)) - if err := os.MkdirAll(destDir, 0755); err != nil { - return nil, fmt.Errorf("创建目录失败: %w", err) - } - - // 保存文件 - destPath := filepath.Join(s.BaseDir, filepath.FromSlash(savePath)) - dst, err := os.Create(destPath) - if err != nil { - return nil, fmt.Errorf("创建文件失败: %w", err) - } - defer dst.Close() - - // 计算MD5并复制文件 - hash := md5.New() - size, err := io.Copy(io.MultiWriter(dst, hash), file) - if err != nil { - _ = os.Remove(destPath) - return nil, fmt.Errorf("保存文件失败: %w", err) - } - - md5Sum := hex.EncodeToString(hash.Sum(nil)) - webURL := s.BaseURL + strings.ReplaceAll(filepath.ToSlash(destPath), "\\", "/") - - return &UploadResult{ - URL: webURL, - Key: savePath, - Size: size, - MD5: md5Sum, - MimeType: header.Header.Get("Content-Type"), - }, nil -} - -// GetPublicURL 获取公开访问URL -func (s *LocalStorage) GetPublicURL(key string) string { - return s.BaseURL + filepath.ToSlash(filepath.Join(s.BaseDir, key)) -} - -// Delete 删除本地文件 -func (s *LocalStorage) Delete(key string) error { - filePath := filepath.Join(s.BaseDir, filepath.FromSlash(key)) - return os.Remove(filePath) -} - -// QiniuStorage 七牛云存储实现 -type QiniuStorage struct { - AccessKey string - SecretKey string - Bucket string - Domain string - Region string -} - -// NewQiniuStorage 创建七牛云存储服务 -func NewQiniuStorage(cfg *models.StorageConfig) *QiniuStorage { - return &QiniuStorage{ - AccessKey: cfg.QiniuAccessKey, - SecretKey: cfg.QiniuSecretKey, - Bucket: cfg.QiniuBucket, - Domain: cfg.QiniuDomain, - Region: cfg.QiniuRegion, - } -} - -// getZone 根据区域代码获取存储区域 -func (s *QiniuStorage) getZone() *storage.Region { - switch s.Region { - case "z0": - return &storage.ZoneHuadong - case "z1": - return &storage.ZoneHuabei - case "z2": - return &storage.ZoneHuanan - case "na0": - return &storage.ZoneBeimei - case "as0": - return &storage.ZoneXinjiapo - case "cn-east-2": - return &storage.ZoneHuadongZheJiang2 - default: - return &storage.ZoneHuadong // 默认华东 - } -} - -// Upload 上传文件到七牛云 -func (s *QiniuStorage) Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) { - // 生成存储key - ext := filepath.Ext(header.Filename) - datePath := time.Now().Format("2006/01/02") - fileName := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext) - key := filepath.ToSlash(filepath.Join(datePath, fileName)) - - // 创建上传凭证 - mac := qbox.NewMac(s.AccessKey, s.SecretKey) - putPolicy := storage.PutPolicy{ - Scope: s.Bucket, - } - upToken := putPolicy.UploadToken(mac) - - // 配置上传参数 - cfg := storage.Config{ - Region: s.getZone(), - UseHTTPS: true, - UseCdnDomains: false, - } - - // 创建表单上传器 - formUploader := storage.NewFormUploader(&cfg) - ret := storage.PutRet{} - putExtra := storage.PutExtra{} - - // 计算文件大小和MD5 - tmpFile, err := os.CreateTemp("", "qiniu_upload_*") - if err != nil { - return nil, fmt.Errorf("创建临时文件失败: %w", err) - } - defer os.Remove(tmpFile.Name()) - defer tmpFile.Close() - - hash := md5.New() - size, err := io.Copy(io.MultiWriter(tmpFile, hash), file) - if err != nil { - return nil, fmt.Errorf("读取文件失败: %w", err) - } - md5Sum := hex.EncodeToString(hash.Sum(nil)) - - // 重置文件指针 - if _, err := tmpFile.Seek(0, 0); err != nil { - return nil, fmt.Errorf("重置文件指针失败: %w", err) - } - - // 执行上传 - err = formUploader.Put(context.Background(), &ret, upToken, key, tmpFile, size, &putExtra) - if err != nil { - return nil, fmt.Errorf("上传到七牛云失败: %w", err) - } - - // 构建完整URL - domain := strings.TrimRight(s.Domain, "/") - url := fmt.Sprintf("%s/%s", domain, ret.Key) - - return &UploadResult{ - URL: url, - Key: ret.Key, - Size: size, - MD5: md5Sum, - MimeType: header.Header.Get("Content-Type"), - }, nil -} - -// GetPublicURL 获取七牛云公开访问URL -func (s *QiniuStorage) GetPublicURL(key string) string { - domain := strings.TrimRight(s.Domain, "/") - return fmt.Sprintf("%s/%s", domain, key) -} - -// Delete 删除七牛云文件 -func (s *QiniuStorage) Delete(key string) error { - mac := qbox.NewMac(s.AccessKey, s.SecretKey) - cfg := storage.Config{ - Region: s.getZone(), - UseHTTPS: true, - } - - bucketManager := storage.NewBucketManager(mac, &cfg) - err := bucketManager.Delete(s.Bucket, key) - if err != nil { - return fmt.Errorf("删除七牛云文件失败: %w", err) - } - return nil -} - -// GetStorageService 根据配置获取存储服务 -func GetStorageService() (StorageService, error) { - cfg, err := models.GetStorageConfig() - if err != nil { - // 默认使用本地存储 - return NewLocalStorage(), nil - } - - switch cfg.StorageType { - case "qiniu": - if cfg.QiniuAccessKey == "" || cfg.QiniuSecretKey == "" || - cfg.QiniuBucket == "" || cfg.QiniuDomain == "" { - return nil, fmt.Errorf("七牛云配置不完整") - } - return NewQiniuStorage(cfg), nil - case "local": - return NewLocalStorage(), nil - default: - return NewLocalStorage(), nil - } -} +package services + +import ( + "context" + "crypto/md5" + "encoding/hex" + "fmt" + "io" + "mime/multipart" + "os" + "path/filepath" + "strings" + "time" + + "server/models" + + "github.com/qiniu/go-sdk/v7/auth/qbox" + "github.com/qiniu/go-sdk/v7/storage" +) + +// StorageService 存储服务接口 +type StorageService interface { + Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) + GetPublicURL(key string) string + Delete(key string) error +} + +// UploadResult 上传结果 +type UploadResult struct { + URL string // 完整访问URL + Key string // 存储key/路径 + Size int64 // 文件大小 + MD5 string // 文件MD5 + MimeType string // 文件类型 +} + +// LocalStorage 本地存储实现 +type LocalStorage struct { + BaseDir string // 基础目录,默认 "uploads" + BaseURL string // 基础URL,默认 "/" +} + +// NewLocalStorage 创建本地存储服务 +func NewLocalStorage() *LocalStorage { + return &LocalStorage{ + BaseDir: "uploads", + BaseURL: "/", + } +} + +// Upload 上传文件到本地 +func (s *LocalStorage) Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) { + // 生成存储路径 + ext := filepath.Ext(header.Filename) + datePath := time.Now().Format("2006/01/02") + fileName := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext) + savePath := filepath.Join(datePath, fileName) + + // 创建目录 + destDir := filepath.Join(s.BaseDir, filepath.FromSlash(datePath)) + if err := os.MkdirAll(destDir, 0755); err != nil { + return nil, fmt.Errorf("创建目录失败: %w", err) + } + + // 保存文件 + destPath := filepath.Join(s.BaseDir, filepath.FromSlash(savePath)) + dst, err := os.Create(destPath) + if err != nil { + return nil, fmt.Errorf("创建文件失败: %w", err) + } + defer dst.Close() + + // 计算MD5并复制文件 + hash := md5.New() + size, err := io.Copy(io.MultiWriter(dst, hash), file) + if err != nil { + _ = os.Remove(destPath) + return nil, fmt.Errorf("保存文件失败: %w", err) + } + + md5Sum := hex.EncodeToString(hash.Sum(nil)) + webURL := s.BaseURL + strings.ReplaceAll(filepath.ToSlash(destPath), "\\", "/") + + return &UploadResult{ + URL: webURL, + Key: savePath, + Size: size, + MD5: md5Sum, + MimeType: header.Header.Get("Content-Type"), + }, nil +} + +// GetPublicURL 获取公开访问URL +func (s *LocalStorage) GetPublicURL(key string) string { + return s.BaseURL + filepath.ToSlash(filepath.Join(s.BaseDir, key)) +} + +// Delete 删除本地文件 +func (s *LocalStorage) Delete(key string) error { + filePath := filepath.Join(s.BaseDir, filepath.FromSlash(key)) + return os.Remove(filePath) +} + +// QiniuStorage 七牛云存储实现 +type QiniuStorage struct { + AccessKey string + SecretKey string + Bucket string + Domain string + Region string +} + +// NewQiniuStorage 创建七牛云存储服务 +func NewQiniuStorage(cfg *models.StorageConfig) *QiniuStorage { + return &QiniuStorage{ + AccessKey: cfg.QiniuAccessKey, + SecretKey: cfg.QiniuSecretKey, + Bucket: cfg.QiniuBucket, + Domain: cfg.QiniuDomain, + Region: cfg.QiniuRegion, + } +} + +// getZone 根据区域代码获取存储区域 +func (s *QiniuStorage) getZone() *storage.Region { + switch s.Region { + case "z0": + return &storage.ZoneHuadong + case "z1": + return &storage.ZoneHuabei + case "z2": + return &storage.ZoneHuanan + case "na0": + return &storage.ZoneBeimei + case "as0": + return &storage.ZoneXinjiapo + case "cn-east-2": + return &storage.ZoneHuadongZheJiang2 + default: + return &storage.ZoneHuadong // 默认华东 + } +} + +// Upload 上传文件到七牛云 +func (s *QiniuStorage) Upload(file multipart.File, header *multipart.FileHeader) (*UploadResult, error) { + // 生成存储key + ext := filepath.Ext(header.Filename) + datePath := time.Now().Format("2006/01/02") + fileName := fmt.Sprintf("%d%s", time.Now().UnixNano(), ext) + key := filepath.ToSlash(filepath.Join(datePath, fileName)) + + // 创建上传凭证 + mac := qbox.NewMac(s.AccessKey, s.SecretKey) + putPolicy := storage.PutPolicy{ + Scope: s.Bucket, + } + upToken := putPolicy.UploadToken(mac) + + // 配置上传参数 + cfg := storage.Config{ + Region: s.getZone(), + UseHTTPS: true, + UseCdnDomains: false, + } + + // 创建表单上传器 + formUploader := storage.NewFormUploader(&cfg) + ret := storage.PutRet{} + putExtra := storage.PutExtra{} + + // 计算文件大小和MD5 + tmpFile, err := os.CreateTemp("", "qiniu_upload_*") + if err != nil { + return nil, fmt.Errorf("创建临时文件失败: %w", err) + } + defer os.Remove(tmpFile.Name()) + defer tmpFile.Close() + + hash := md5.New() + size, err := io.Copy(io.MultiWriter(tmpFile, hash), file) + if err != nil { + return nil, fmt.Errorf("读取文件失败: %w", err) + } + md5Sum := hex.EncodeToString(hash.Sum(nil)) + + // 重置文件指针 + if _, err := tmpFile.Seek(0, 0); err != nil { + return nil, fmt.Errorf("重置文件指针失败: %w", err) + } + + // 执行上传 + err = formUploader.Put(context.Background(), &ret, upToken, key, tmpFile, size, &putExtra) + if err != nil { + return nil, fmt.Errorf("上传到七牛云失败: %w", err) + } + + // 构建完整URL + domain := strings.TrimRight(s.Domain, "/") + url := fmt.Sprintf("%s/%s", domain, ret.Key) + + return &UploadResult{ + URL: url, + Key: ret.Key, + Size: size, + MD5: md5Sum, + MimeType: header.Header.Get("Content-Type"), + }, nil +} + +// GetPublicURL 获取七牛云公开访问URL +func (s *QiniuStorage) GetPublicURL(key string) string { + domain := strings.TrimRight(s.Domain, "/") + return fmt.Sprintf("%s/%s", domain, key) +} + +// Delete 删除七牛云文件 +func (s *QiniuStorage) Delete(key string) error { + mac := qbox.NewMac(s.AccessKey, s.SecretKey) + cfg := storage.Config{ + Region: s.getZone(), + UseHTTPS: true, + } + + bucketManager := storage.NewBucketManager(mac, &cfg) + err := bucketManager.Delete(s.Bucket, key) + if err != nil { + return fmt.Errorf("删除七牛云文件失败: %w", err) + } + return nil +} + +// GetStorageService 根据配置获取存储服务 +func GetStorageService() (StorageService, error) { + cfg, err := models.GetStorageConfig() + if err != nil { + // 默认使用本地存储 + return NewLocalStorage(), nil + } + + switch cfg.StorageType { + case "qiniu": + if cfg.QiniuAccessKey == "" || cfg.QiniuSecretKey == "" || + cfg.QiniuBucket == "" || cfg.QiniuDomain == "" { + return nil, fmt.Errorf("七牛云配置不完整") + } + return NewQiniuStorage(cfg), nil + case "local": + return NewLocalStorage(), nil + default: + return NewLocalStorage(), nil + } +} diff --git a/go/services/system_email_smtp.go b/go/services/system_email_smtp.go index 4eb3abc..c618f81 100644 --- a/go/services/system_email_smtp.go +++ b/go/services/system_email_smtp.go @@ -1,220 +1,220 @@ -package services - -import ( - "crypto/tls" - "fmt" - "net" - "net/smtp" - "strconv" - "strings" - "time" -) - -// SMTPConfig 发送邮件所需参数(与 yz_system_email 字段对应) -type SMTPConfig struct { - FromAddress string - FromName string - Host string - Port uint - Password string - Encryption string // ssl / tls / none - Timeout uint // 秒 -} - -// SendTestEmailSMTP 发送一封简单测试邮件(纯文本 UTF-8) -func SendTestEmailSMTP(cfg SMTPConfig, to string) error { - to = strings.TrimSpace(to) - if to == "" { - return fmt.Errorf("收件人不能为空") - } - if cfg.Host == "" || cfg.FromAddress == "" { - return fmt.Errorf("SMTP 主机或发件人不能为空") - } - if cfg.Port == 0 { - cfg.Port = 465 - } - timeout := cfg.Timeout - if timeout == 0 { - timeout = 30 - } - d := net.Dialer{Timeout: time.Duration(timeout) * time.Second} - addr := net.JoinHostPort(cfg.Host, strconv.FormatUint(uint64(cfg.Port), 10)) - enc := strings.ToLower(strings.TrimSpace(cfg.Encryption)) - if enc == "" { - enc = "ssl" - } - - var client *smtp.Client - var err error - - switch enc { - case "ssl": - conn, derr := tls.DialWithDialer(&d, "tcp", addr, &tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}) - if derr != nil { - return fmt.Errorf("连接 SMTP 失败: %w", derr) - } - defer conn.Close() - client, err = smtp.NewClient(conn, cfg.Host) - if err != nil { - return fmt.Errorf("SMTP 握手失败: %w", err) - } - case "tls": - conn, derr := d.Dial("tcp", addr) - if derr != nil { - return fmt.Errorf("连接 SMTP 失败: %w", derr) - } - defer conn.Close() - client, err = smtp.NewClient(conn, cfg.Host) - if err != nil { - return fmt.Errorf("SMTP 握手失败: %w", err) - } - if ok, _ := client.Extension("STARTTLS"); ok { - if err = client.StartTLS(&tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}); err != nil { - _ = client.Close() - return fmt.Errorf("STARTTLS 失败: %w", err) - } - } - case "none": - conn, derr := d.Dial("tcp", addr) - if derr != nil { - return fmt.Errorf("连接 SMTP 失败: %w", derr) - } - defer conn.Close() - client, err = smtp.NewClient(conn, cfg.Host) - if err != nil { - return fmt.Errorf("SMTP 握手失败: %w", err) - } - default: - return fmt.Errorf("不支持的加密方式: %s", cfg.Encryption) - } - defer func() { _ = client.Close() }() - - auth := smtp.PlainAuth("", cfg.FromAddress, cfg.Password, cfg.Host) - if err = client.Auth(auth); err != nil { - return fmt.Errorf("SMTP 认证失败: %w", err) - } - if err = client.Mail(cfg.FromAddress); err != nil { - return fmt.Errorf("MAIL FROM 失败: %w", err) - } - if err = client.Rcpt(to); err != nil { - return fmt.Errorf("RCPT TO 失败: %w", err) - } - wc, err := client.Data() - if err != nil { - return fmt.Errorf("DATA 失败: %w", err) - } - fromName := strings.TrimSpace(cfg.FromName) - subject := "平台邮箱测试" - body := "这是一封来自管理后台「邮箱管理」的测试邮件。\r\nThis is a test email from the platform email settings.\r\n" - headers := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n", - formatFromHeader(fromName, cfg.FromAddress), to, subject) - if _, err = wc.Write([]byte(headers + body)); err != nil { - return fmt.Errorf("写入邮件内容失败: %w", err) - } - if err = wc.Close(); err != nil { - return fmt.Errorf("结束 DATA 失败: %w", err) - } - return client.Quit() -} - -// SendHTMLEmailSMTP 发送一封 HTML 格式邮件 -func SendHTMLEmailSMTP(cfg SMTPConfig, to string, subject string, htmlBody string) error { - to = strings.TrimSpace(to) - if to == "" { - return fmt.Errorf("收件人不能为空") - } - if cfg.Host == "" || cfg.FromAddress == "" { - return fmt.Errorf("SMTP 主机或发件人不能为空") - } - if cfg.Port == 0 { - cfg.Port = 465 - } - timeout := cfg.Timeout - if timeout == 0 { - timeout = 30 - } - d := net.Dialer{Timeout: time.Duration(timeout) * time.Second} - addr := net.JoinHostPort(cfg.Host, strconv.FormatUint(uint64(cfg.Port), 10)) - enc := strings.ToLower(strings.TrimSpace(cfg.Encryption)) - if enc == "" { - enc = "ssl" - } - - var client *smtp.Client - var err error - - switch enc { - case "ssl": - conn, derr := tls.DialWithDialer(&d, "tcp", addr, &tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}) - if derr != nil { - return fmt.Errorf("连接 SMTP 失败: %w", derr) - } - defer conn.Close() - client, err = smtp.NewClient(conn, cfg.Host) - if err != nil { - return fmt.Errorf("SMTP 握手失败: %w", err) - } - case "tls": - conn, derr := d.Dial("tcp", addr) - if derr != nil { - return fmt.Errorf("连接 SMTP 失败: %w", derr) - } - defer conn.Close() - client, err = smtp.NewClient(conn, cfg.Host) - if err != nil { - return fmt.Errorf("SMTP 握手失败: %w", err) - } - if ok, _ := client.Extension("STARTTLS"); ok { - if err = client.StartTLS(&tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}); err != nil { - _ = client.Close() - return fmt.Errorf("STARTTLS 失败: %w", err) - } - } - case "none": - conn, derr := d.Dial("tcp", addr) - if derr != nil { - return fmt.Errorf("连接 SMTP 失败: %w", derr) - } - defer conn.Close() - client, err = smtp.NewClient(conn, cfg.Host) - if err != nil { - return fmt.Errorf("SMTP 握手失败: %w", err) - } - default: - return fmt.Errorf("不支持的加密方式: %s", cfg.Encryption) - } - defer func() { _ = client.Close() }() - - auth := smtp.PlainAuth("", cfg.FromAddress, cfg.Password, cfg.Host) - if err = client.Auth(auth); err != nil { - return fmt.Errorf("SMTP 认证失败: %w", err) - } - if err = client.Mail(cfg.FromAddress); err != nil { - return fmt.Errorf("MAIL FROM 失败: %w", err) - } - if err = client.Rcpt(to); err != nil { - return fmt.Errorf("RCPT TO 失败: %w", err) - } - wc, err := client.Data() - if err != nil { - return fmt.Errorf("DATA 失败: %w", err) - } - fromName := strings.TrimSpace(cfg.FromName) - headers := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n", - formatFromHeader(fromName, cfg.FromAddress), to, subject) - if _, err = wc.Write([]byte(headers + htmlBody)); err != nil { - return fmt.Errorf("写入邮件内容失败: %w", err) - } - if err = wc.Close(); err != nil { - return fmt.Errorf("结束 DATA 失败: %w", err) - } - return client.Quit() -} - -func formatFromHeader(name, addr string) string { - name = strings.TrimSpace(name) - if name == "" { - return addr - } - return fmt.Sprintf("%s <%s>", name, addr) -} +package services + +import ( + "crypto/tls" + "fmt" + "net" + "net/smtp" + "strconv" + "strings" + "time" +) + +// SMTPConfig 发送邮件所需参数(与 yz_system_email 字段对应) +type SMTPConfig struct { + FromAddress string + FromName string + Host string + Port uint + Password string + Encryption string // ssl / tls / none + Timeout uint // 秒 +} + +// SendTestEmailSMTP 发送一封简单测试邮件(纯文本 UTF-8) +func SendTestEmailSMTP(cfg SMTPConfig, to string) error { + to = strings.TrimSpace(to) + if to == "" { + return fmt.Errorf("收件人不能为空") + } + if cfg.Host == "" || cfg.FromAddress == "" { + return fmt.Errorf("SMTP 主机或发件人不能为空") + } + if cfg.Port == 0 { + cfg.Port = 465 + } + timeout := cfg.Timeout + if timeout == 0 { + timeout = 30 + } + d := net.Dialer{Timeout: time.Duration(timeout) * time.Second} + addr := net.JoinHostPort(cfg.Host, strconv.FormatUint(uint64(cfg.Port), 10)) + enc := strings.ToLower(strings.TrimSpace(cfg.Encryption)) + if enc == "" { + enc = "ssl" + } + + var client *smtp.Client + var err error + + switch enc { + case "ssl": + conn, derr := tls.DialWithDialer(&d, "tcp", addr, &tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}) + if derr != nil { + return fmt.Errorf("连接 SMTP 失败: %w", derr) + } + defer conn.Close() + client, err = smtp.NewClient(conn, cfg.Host) + if err != nil { + return fmt.Errorf("SMTP 握手失败: %w", err) + } + case "tls": + conn, derr := d.Dial("tcp", addr) + if derr != nil { + return fmt.Errorf("连接 SMTP 失败: %w", derr) + } + defer conn.Close() + client, err = smtp.NewClient(conn, cfg.Host) + if err != nil { + return fmt.Errorf("SMTP 握手失败: %w", err) + } + if ok, _ := client.Extension("STARTTLS"); ok { + if err = client.StartTLS(&tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}); err != nil { + _ = client.Close() + return fmt.Errorf("STARTTLS 失败: %w", err) + } + } + case "none": + conn, derr := d.Dial("tcp", addr) + if derr != nil { + return fmt.Errorf("连接 SMTP 失败: %w", derr) + } + defer conn.Close() + client, err = smtp.NewClient(conn, cfg.Host) + if err != nil { + return fmt.Errorf("SMTP 握手失败: %w", err) + } + default: + return fmt.Errorf("不支持的加密方式: %s", cfg.Encryption) + } + defer func() { _ = client.Close() }() + + auth := smtp.PlainAuth("", cfg.FromAddress, cfg.Password, cfg.Host) + if err = client.Auth(auth); err != nil { + return fmt.Errorf("SMTP 认证失败: %w", err) + } + if err = client.Mail(cfg.FromAddress); err != nil { + return fmt.Errorf("MAIL FROM 失败: %w", err) + } + if err = client.Rcpt(to); err != nil { + return fmt.Errorf("RCPT TO 失败: %w", err) + } + wc, err := client.Data() + if err != nil { + return fmt.Errorf("DATA 失败: %w", err) + } + fromName := strings.TrimSpace(cfg.FromName) + subject := "平台邮箱测试" + body := "这是一封来自管理后台「邮箱管理」的测试邮件。\r\nThis is a test email from the platform email settings.\r\n" + headers := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n", + formatFromHeader(fromName, cfg.FromAddress), to, subject) + if _, err = wc.Write([]byte(headers + body)); err != nil { + return fmt.Errorf("写入邮件内容失败: %w", err) + } + if err = wc.Close(); err != nil { + return fmt.Errorf("结束 DATA 失败: %w", err) + } + return client.Quit() +} + +// SendHTMLEmailSMTP 发送一封 HTML 格式邮件 +func SendHTMLEmailSMTP(cfg SMTPConfig, to string, subject string, htmlBody string) error { + to = strings.TrimSpace(to) + if to == "" { + return fmt.Errorf("收件人不能为空") + } + if cfg.Host == "" || cfg.FromAddress == "" { + return fmt.Errorf("SMTP 主机或发件人不能为空") + } + if cfg.Port == 0 { + cfg.Port = 465 + } + timeout := cfg.Timeout + if timeout == 0 { + timeout = 30 + } + d := net.Dialer{Timeout: time.Duration(timeout) * time.Second} + addr := net.JoinHostPort(cfg.Host, strconv.FormatUint(uint64(cfg.Port), 10)) + enc := strings.ToLower(strings.TrimSpace(cfg.Encryption)) + if enc == "" { + enc = "ssl" + } + + var client *smtp.Client + var err error + + switch enc { + case "ssl": + conn, derr := tls.DialWithDialer(&d, "tcp", addr, &tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}) + if derr != nil { + return fmt.Errorf("连接 SMTP 失败: %w", derr) + } + defer conn.Close() + client, err = smtp.NewClient(conn, cfg.Host) + if err != nil { + return fmt.Errorf("SMTP 握手失败: %w", err) + } + case "tls": + conn, derr := d.Dial("tcp", addr) + if derr != nil { + return fmt.Errorf("连接 SMTP 失败: %w", derr) + } + defer conn.Close() + client, err = smtp.NewClient(conn, cfg.Host) + if err != nil { + return fmt.Errorf("SMTP 握手失败: %w", err) + } + if ok, _ := client.Extension("STARTTLS"); ok { + if err = client.StartTLS(&tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}); err != nil { + _ = client.Close() + return fmt.Errorf("STARTTLS 失败: %w", err) + } + } + case "none": + conn, derr := d.Dial("tcp", addr) + if derr != nil { + return fmt.Errorf("连接 SMTP 失败: %w", derr) + } + defer conn.Close() + client, err = smtp.NewClient(conn, cfg.Host) + if err != nil { + return fmt.Errorf("SMTP 握手失败: %w", err) + } + default: + return fmt.Errorf("不支持的加密方式: %s", cfg.Encryption) + } + defer func() { _ = client.Close() }() + + auth := smtp.PlainAuth("", cfg.FromAddress, cfg.Password, cfg.Host) + if err = client.Auth(auth); err != nil { + return fmt.Errorf("SMTP 认证失败: %w", err) + } + if err = client.Mail(cfg.FromAddress); err != nil { + return fmt.Errorf("MAIL FROM 失败: %w", err) + } + if err = client.Rcpt(to); err != nil { + return fmt.Errorf("RCPT TO 失败: %w", err) + } + wc, err := client.Data() + if err != nil { + return fmt.Errorf("DATA 失败: %w", err) + } + fromName := strings.TrimSpace(cfg.FromName) + headers := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n", + formatFromHeader(fromName, cfg.FromAddress), to, subject) + if _, err = wc.Write([]byte(headers + htmlBody)); err != nil { + return fmt.Errorf("写入邮件内容失败: %w", err) + } + if err = wc.Close(); err != nil { + return fmt.Errorf("结束 DATA 失败: %w", err) + } + return client.Quit() +} + +func formatFromHeader(name, addr string) string { + name = strings.TrimSpace(name) + if name == "" { + return addr + } + return fmt.Sprintf("%s <%s>", name, addr) +} diff --git a/go/services/system_email_store.go b/go/services/system_email_store.go index 24f80f4..2aa8883 100644 --- a/go/services/system_email_store.go +++ b/go/services/system_email_store.go @@ -1,135 +1,135 @@ -package services - -import ( - "fmt" - "strconv" - "strings" - "time" - - "server/models" -) - -// ListSystemEmails 返回从 yz_platform_normal_setting 组装的邮箱配置(切片,通常仅一条) -func ListSystemEmails() ([]models.SystemEmail, error) { - enabledStr := models.GetPlatformSettingValue("email_enabled", "0") - fromAddress := models.GetPlatformSettingValue("email_from_address", "") - fromName := models.GetPlatformSettingValue("email_from_name", "") - host := models.GetPlatformSettingValue("email_host", "") - portStr := models.GetPlatformSettingValue("email_port", "465") - password := models.GetPlatformSettingValue("email_password", "") - encryption := models.GetPlatformSettingValue("email_encryption", "ssl") - timeoutStr := models.GetPlatformSettingValue("email_timeout", "30") - - status := int8(0) - if enabledStr == "1" { - status = 1 - } - portVal, _ := strconv.ParseUint(portStr, 10, 32) - timeoutVal, _ := strconv.ParseUint(timeoutStr, 10, 32) - - row := models.SystemEmail{ - ID: 1, - FromAddress: fromAddress, - Host: host, - Port: uint(portVal), - Password: password, - Encryption: encryption, - Timeout: uint(timeoutVal), - Status: status, - CreateTime: time.Now(), - UpdateTime: time.Now(), - } - if fromName != "" { - row.FromName = &fromName - } - - return []models.SystemEmail{row}, nil -} - -// UpsertFirstSystemEmail 将邮箱配置保存到 yz_platform_normal_setting 表中 -func UpsertFirstSystemEmail(fromAddress string, fromName *string, host string, port uint, password string, encryption string, timeout uint, status int8, remark *string) error { - if encryption == "" { - encryption = "ssl" - } - if port == 0 { - port = 465 - } - if timeout == 0 { - timeout = 30 - } - fromAddress = strings.TrimSpace(fromAddress) - host = strings.TrimSpace(host) - - fn := "" - if fromName != nil { - fn = *fromName - } - - statusStr := "0" - if status == 1 { - statusStr = "1" - } - - settings := []struct { - code string - name string - value string - remark string - }{ - {"email_enabled", "邮件服务启用状态", statusStr, "0为关闭,1为开启"}, - {"email_from_address", "发件人邮箱", fromAddress, ""}, - {"email_from_name", "发件人名称", fn, ""}, - {"email_host", "SMTP 服务器地址", host, ""}, - {"email_port", "SMTP 端口", strconv.FormatUint(uint64(port), 10), ""}, - {"email_encryption", "邮件加密方式", encryption, "支持 ssl/tls/none"}, - {"email_timeout", "邮件发送超时时间", strconv.FormatUint(uint64(timeout), 10), ""}, - } - - // 如果传入了新密码,或者目前还没有保存过密码,才更新密码 - if strings.TrimSpace(password) != "" { - settings = append(settings, struct { - code string - name string - value string - remark string - }{"email_password", "邮件授权码/密码", strings.TrimSpace(password), ""}) - } else { - // 校验:如果完全没有配置过密码,必须填写密码 - existingPass := models.GetPlatformSettingValue("email_password", "") - if existingPass == "" { - return fmt.Errorf("首次保存必须填写授权码/密码") - } - } - - for _, item := range settings { - var setting models.PlatformNormalSetting - err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). - Filter("code", item.code). - Filter("delete_time__isnull", true). - One(&setting) - if err == nil { - setting.Value = item.value - setting.Name = item.name - setting.Remark = item.remark - now := time.Now() - setting.UpdateTime = &now - _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") - if err != nil { - return err - } - } else { - newSetting := models.PlatformNormalSetting{ - Name: item.name, - Code: item.code, - Value: item.value, - Remark: item.remark, - CreateTime: time.Now(), - } - _, err = models.Orm.Insert(&newSetting) - if err != nil { - return err - } - } - } - return nil -} +package services + +import ( + "fmt" + "strconv" + "strings" + "time" + + "server/models" +) + +// ListSystemEmails 返回从 yz_platform_normal_setting 组装的邮箱配置(切片,通常仅一条) +func ListSystemEmails() ([]models.SystemEmail, error) { + enabledStr := models.GetPlatformSettingValue("email_enabled", "0") + fromAddress := models.GetPlatformSettingValue("email_from_address", "") + fromName := models.GetPlatformSettingValue("email_from_name", "") + host := models.GetPlatformSettingValue("email_host", "") + portStr := models.GetPlatformSettingValue("email_port", "465") + password := models.GetPlatformSettingValue("email_password", "") + encryption := models.GetPlatformSettingValue("email_encryption", "ssl") + timeoutStr := models.GetPlatformSettingValue("email_timeout", "30") + + status := int8(0) + if enabledStr == "1" { + status = 1 + } + portVal, _ := strconv.ParseUint(portStr, 10, 32) + timeoutVal, _ := strconv.ParseUint(timeoutStr, 10, 32) + + row := models.SystemEmail{ + ID: 1, + FromAddress: fromAddress, + Host: host, + Port: uint(portVal), + Password: password, + Encryption: encryption, + Timeout: uint(timeoutVal), + Status: status, + CreateTime: time.Now(), + UpdateTime: time.Now(), + } + if fromName != "" { + row.FromName = &fromName + } + + return []models.SystemEmail{row}, nil +} + +// UpsertFirstSystemEmail 将邮箱配置保存到 yz_platform_normal_setting 表中 +func UpsertFirstSystemEmail(fromAddress string, fromName *string, host string, port uint, password string, encryption string, timeout uint, status int8, remark *string) error { + if encryption == "" { + encryption = "ssl" + } + if port == 0 { + port = 465 + } + if timeout == 0 { + timeout = 30 + } + fromAddress = strings.TrimSpace(fromAddress) + host = strings.TrimSpace(host) + + fn := "" + if fromName != nil { + fn = *fromName + } + + statusStr := "0" + if status == 1 { + statusStr = "1" + } + + settings := []struct { + code string + name string + value string + remark string + }{ + {"email_enabled", "邮件服务启用状态", statusStr, "0为关闭,1为开启"}, + {"email_from_address", "发件人邮箱", fromAddress, ""}, + {"email_from_name", "发件人名称", fn, ""}, + {"email_host", "SMTP 服务器地址", host, ""}, + {"email_port", "SMTP 端口", strconv.FormatUint(uint64(port), 10), ""}, + {"email_encryption", "邮件加密方式", encryption, "支持 ssl/tls/none"}, + {"email_timeout", "邮件发送超时时间", strconv.FormatUint(uint64(timeout), 10), ""}, + } + + // 如果传入了新密码,或者目前还没有保存过密码,才更新密码 + if strings.TrimSpace(password) != "" { + settings = append(settings, struct { + code string + name string + value string + remark string + }{"email_password", "邮件授权码/密码", strings.TrimSpace(password), ""}) + } else { + // 校验:如果完全没有配置过密码,必须填写密码 + existingPass := models.GetPlatformSettingValue("email_password", "") + if existingPass == "" { + return fmt.Errorf("首次保存必须填写授权码/密码") + } + } + + for _, item := range settings { + var setting models.PlatformNormalSetting + err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + return err + } + } else { + newSetting := models.PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = models.Orm.Insert(&newSetting) + if err != nil { + return err + } + } + } + return nil +} diff --git a/go/services/system_sitereminder.go b/go/services/system_sitereminder.go index 4705f6f..e1a0227 100644 --- a/go/services/system_sitereminder.go +++ b/go/services/system_sitereminder.go @@ -1,439 +1,439 @@ -package services - -import ( - "context" - "fmt" - "strconv" - "time" - - "github.com/beego/beego/v2/client/orm" - "server/models" -) - -// GetSiteReminderConfig 获取站内信配置(从 yz_platform_normal_setting 读取) -func GetSiteReminderConfig() (models.SystemSiteReminder, error) { - retentionDaysStr := models.GetPlatformSettingValue("sitemsg_retention_days", "30") - autoReadStr := models.GetPlatformSettingValue("sitemsg_auto_read", "0") - - retentionDays, _ := strconv.Atoi(retentionDaysStr) - if retentionDays <= 0 { - retentionDays = 30 - } - autoRead := int8(0) - if autoReadStr == "1" { - autoRead = 1 - } - - now := time.Now() - row := models.SystemSiteReminder{ - ID: 1, - RetentionDays: retentionDays, - AutoRead: autoRead, - CreateTime: &now, - UpdateTime: &now, - } - return row, nil -} - -// SaveSiteReminderConfig 保存/更新配置 -func SaveSiteReminderConfig(retentionDays int, autoRead int8) error { - if retentionDays <= 0 { - retentionDays = 30 - } - - autoReadStr := "0" - if autoRead == 1 { - autoReadStr = "1" - } - - settings := []struct { - code string - name string - value string - remark string - }{ - {"sitemsg_retention_days", "站内信消息保留天数", strconv.Itoa(retentionDays), ""}, - {"sitemsg_auto_read", "自动标记已读状态", autoReadStr, "0为关闭,1为开启"}, - } - - for _, item := range settings { - var setting models.PlatformNormalSetting - err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). - Filter("code", item.code). - Filter("delete_time__isnull", true). - One(&setting) - if err == nil { - setting.Value = item.value - setting.Name = item.name - setting.Remark = item.remark - now := time.Now() - setting.UpdateTime = &now - _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") - if err != nil { - return err - } - } else { - newSetting := models.PlatformNormalSetting{ - Name: item.name, - Code: item.code, - Value: item.value, - Remark: item.remark, - CreateTime: time.Now(), - } - _, err = models.Orm.Insert(&newSetting) - if err != nil { - return err - } - } - } - return nil -} - -// SendSiteReminder 发送站内信 -// targetType: platform (平台端), tenant_all (管理端所有用户), role (平台角色), tenant (特定租户) -func SendSiteReminder(title, content string, senderID uint64, senderType string, targetType string, targetRoleID uint64, targetTenantID uint64) error { - var receiverIDs []uint64 - var receiverType string - - switch targetType { - case "platform": - receiverType = "platform" - var list []models.AdminUser - _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id") - if err != nil { - return fmt.Errorf("查询平台用户失败: %w", err) - } - for _, u := range list { - receiverIDs = append(receiverIDs, u.ID) - } - case "tenant_all": - receiverType = "tenant" - var list []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id") - if err != nil { - return fmt.Errorf("查询租户用户失败: %w", err) - } - for _, u := range list { - receiverIDs = append(receiverIDs, u.ID) - } - case "role": - receiverType = "platform" - var list []models.AdminUser - _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("role_id", targetRoleID).Filter("delete_time__isnull", true).All(&list, "id") - if err != nil { - return fmt.Errorf("根据角色查询用户失败: %w", err) - } - for _, u := range list { - receiverIDs = append(receiverIDs, u.ID) - } - case "tenant": - receiverType = "tenant" - var list []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("tid", targetTenantID).Filter("delete_time__isnull", true).All(&list, "id") - if err != nil { - return fmt.Errorf("根据租户查询用户失败: %w", err) - } - for _, u := range list { - receiverIDs = append(receiverIDs, u.ID) - } - default: - return fmt.Errorf("未知的发送目标类型: %s", targetType) - } - - if len(receiverIDs) == 0 { - return nil - } - - now := time.Now() - batchID := fmt.Sprintf("%d_%d", now.UnixNano(), senderID) - var reminders []models.SystemReminderList - for _, rid := range receiverIDs { - reminders = append(reminders, models.SystemReminderList{ - Title: title, - Content: content, - SenderID: senderID, - SenderType: senderType, - ReceiverID: rid, - ReceiverType: receiverType, - IsRead: 0, - CreateTime: &now, - BatchID: batchID, - TargetType: targetType, - TargetRoleID: targetRoleID, - TargetTenantID: targetTenantID, - }) - } - - // 批量插入 - _, err := models.Orm.InsertMulti(100, reminders) - return err -} - -// ListReminders 列表查询 -func ListReminders(receiverID uint64, receiverType string, page, pageSize int, isRead *int8) ([]models.SystemReminderList, int64, error) { - if page <= 0 { - page = 1 - } - if pageSize <= 0 { - pageSize = 10 - } - var list []models.SystemReminderList - qs := models.Orm.QueryTable(new(models.SystemReminderList)). - Filter("receiver_id", receiverID). - Filter("receiver_type", receiverType). - Filter("delete_time__isnull", true) - - if isRead != nil { - qs = qs.Filter("is_read", *isRead) - } - - total, err := qs.Count() - if err != nil { - return nil, 0, err - } - - offset := (page - 1) * pageSize - _, err = qs.OrderBy("-create_time", "-id").Limit(pageSize, offset).All(&list) - return list, total, err -} - -// MarkReminderRead 标记单条已读 -func MarkReminderRead(id uint64, receiverID uint64, receiverType string) error { - now := time.Now() - _, err := models.Orm.QueryTable(new(models.SystemReminderList)). - Filter("id", id). - Filter("receiver_id", receiverID). - Filter("receiver_type", receiverType). - Update(map[string]interface{}{ - "is_read": 1, - "read_time": &now, - }) - return err -} - -// MarkAllRemindersRead 一键全部已读 -func MarkAllRemindersRead(receiverID uint64, receiverType string) error { - now := time.Now() - _, err := models.Orm.QueryTable(new(models.SystemReminderList)). - Filter("receiver_id", receiverID). - Filter("receiver_type", receiverType). - Filter("is_read", 0). - Update(map[string]interface{}{ - "is_read": 1, - "read_time": &now, - }) - return err -} - -// DeleteReminder 删除消息 -func DeleteReminder(id uint64, receiverID uint64, receiverType string) error { - now := time.Now() - _, err := models.Orm.QueryTable(new(models.SystemReminderList)). - Filter("id", id). - Filter("receiver_id", receiverID). - Filter("receiver_type", receiverType). - Update(map[string]interface{}{ - "delete_time": &now, - }) - return err -} - -// AutoCleanExpiredReminders 自动清理过期站内信 -func AutoCleanExpiredReminders() error { - cfg, err := GetSiteReminderConfig() - if err != nil { - return err - } - if cfg.RetentionDays <= 0 { - return nil - } - expireTime := time.Now().AddDate(0, 0, -cfg.RetentionDays) - _, err = models.Orm.QueryTable(new(models.SystemReminderList)). - Filter("create_time__lt", expireTime). - Delete() - return err -} - -// ListSentReminders 获取已发送的消息列表(按 batch_id 分组) -func ListSentReminders(senderID uint64, page, pageSize int) ([]models.SystemReminderList, int64, error) { - if page <= 0 { - page = 1 - } - if pageSize <= 0 { - pageSize = 10 - } - offset := (page - 1) * pageSize - - var total int64 - err := models.Orm.Raw("SELECT COUNT(DISTINCT batch_id) FROM yz_system_reminderlist WHERE sender_id = ? AND delete_time IS NULL", senderID).QueryRow(&total) - if err != nil { - return nil, 0, err - } - - var list []models.SystemReminderList - _, err = models.Orm.Raw("SELECT * FROM yz_system_reminderlist WHERE id IN (SELECT MIN(id) FROM yz_system_reminderlist WHERE sender_id = ? AND delete_time IS NULL GROUP BY batch_id) ORDER BY id DESC LIMIT ? OFFSET ?", senderID, pageSize, offset).QueryRows(&list) - if err != nil { - return nil, 0, err - } - - return list, total, nil -} - -// UpdateSentReminder 更新已发出的消息(更新该批次下所有接收者的消息,支持修改目标接收群体) -func UpdateSentReminder(batchID string, title, content, targetType string, targetRoleID, targetTenantID uint64) error { - // 1. 获取当前发送者ID (从该批次中任意一条记录中获取) - var firstRecord models.SystemReminderList - err := models.Orm.QueryTable(new(models.SystemReminderList)).Filter("batch_id", batchID).Limit(1).One(&firstRecord) - if err != nil { - return fmt.Errorf("找不到该批次的站内信记录: %w", err) - } - senderID := firstRecord.SenderID - senderType := firstRecord.SenderType - - // 2. 根据新的目标接收群体获取接收人列表 - var receiverIDs []uint64 - var receiverType string - - switch targetType { - case "platform": - receiverType = "platform" - var list []models.AdminUser - _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id") - if err != nil { - return fmt.Errorf("查询平台用户失败: %w", err) - } - for _, u := range list { - receiverIDs = append(receiverIDs, u.ID) - } - case "tenant_all": - receiverType = "tenant" - var list []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id") - if err != nil { - return fmt.Errorf("查询租户用户失败: %w", err) - } - for _, u := range list { - receiverIDs = append(receiverIDs, u.ID) - } - case "role": - receiverType = "platform" - var list []models.AdminUser - _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("role_id", targetRoleID).Filter("delete_time__isnull", true).All(&list, "id") - if err != nil { - return fmt.Errorf("根据角色查询用户失败: %w", err) - } - for _, u := range list { - receiverIDs = append(receiverIDs, u.ID) - } - case "tenant": - receiverType = "tenant" - var list []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("tid", targetTenantID).Filter("delete_time__isnull", true).All(&list, "id") - if err != nil { - return fmt.Errorf("根据租户查询用户失败: %w", err) - } - for _, u := range list { - receiverIDs = append(receiverIDs, u.ID) - } - default: - return fmt.Errorf("未知的发送目标类型: %s", targetType) - } - - // 3. 获取该批次中现有的所有记录 (包括已删除的) - var existingRecords []models.SystemReminderList - _, err = models.Orm.QueryTable(new(models.SystemReminderList)).Filter("batch_id", batchID).All(&existingRecords) - if err != nil { - return fmt.Errorf("获取现有记录失败: %w", err) - } - - // 建立 map 快速查找,Key 为 "receiverType_receiverID" - existingMap := make(map[string]*models.SystemReminderList) - for i := range existingRecords { - key := fmt.Sprintf("%s_%d", existingRecords[i].ReceiverType, existingRecords[i].ReceiverID) - existingMap[key] = &existingRecords[i] - } - - newReceiverMap := make(map[string]bool) - for _, rid := range receiverIDs { - key := fmt.Sprintf("%s_%d", receiverType, rid) - newReceiverMap[key] = true - } - - now := time.Now() - - // 事务处理 - err = models.Orm.DoTx(func(c context.Context, txOrm orm.TxOrmer) error { - // A. 对于已经不在新接收者列表中的用户,软删除 - for key, rec := range existingMap { - if !newReceiverMap[key] { - if rec.DeleteTime == nil { - rec.DeleteTime = &now - if _, e := txOrm.Update(rec, "DeleteTime"); e != nil { - return e - } - } - } - } - - // B. 对于仍然在新接收者列表中的用户,更新标题、内容、以及 target 信息;如果原来被删除了,清除 delete_time - var newInserts []models.SystemReminderList - for _, rid := range receiverIDs { - key := fmt.Sprintf("%s_%d", receiverType, rid) - if rec, exists := existingMap[key]; exists { - rec.Title = title - rec.Content = content - rec.TargetType = targetType - rec.TargetRoleID = targetRoleID - rec.TargetTenantID = targetTenantID - - cols := []string{"Title", "Content", "TargetType", "TargetRoleID", "TargetTenantID"} - if rec.DeleteTime != nil { - rec.DeleteTime = nil - rec.IsRead = 0 - rec.ReadTime = nil - cols = append(cols, "DeleteTime", "IsRead", "ReadTime") - } - if _, e := txOrm.Update(rec, cols...); e != nil { - return e - } - } else { - // C. 对于新增加的接收者,插入新记录 - newInserts = append(newInserts, models.SystemReminderList{ - Title: title, - Content: content, - SenderID: senderID, - SenderType: senderType, - ReceiverID: rid, - ReceiverType: receiverType, - IsRead: 0, - CreateTime: &now, - BatchID: batchID, - TargetType: targetType, - TargetRoleID: targetRoleID, - TargetTenantID: targetTenantID, - }) - } - } - - if len(newInserts) > 0 { - if _, e := txOrm.InsertMulti(100, newInserts); e != nil { - return e - } - } - - return nil - }) - - return err -} - -// DeleteSentReminderBatch 删除已发送消息(删除该批次下所有记录) -func DeleteSentReminderBatch(batchID string) error { - now := time.Now() - _, err := models.Orm.QueryTable(new(models.SystemReminderList)). - Filter("batch_id", batchID). - Update(map[string]interface{}{ - "delete_time": &now, - }) - return err -} +package services + +import ( + "context" + "fmt" + "strconv" + "time" + + "github.com/beego/beego/v2/client/orm" + "server/models" +) + +// GetSiteReminderConfig 获取站内信配置(从 yz_platform_normal_setting 读取) +func GetSiteReminderConfig() (models.SystemSiteReminder, error) { + retentionDaysStr := models.GetPlatformSettingValue("sitemsg_retention_days", "30") + autoReadStr := models.GetPlatformSettingValue("sitemsg_auto_read", "0") + + retentionDays, _ := strconv.Atoi(retentionDaysStr) + if retentionDays <= 0 { + retentionDays = 30 + } + autoRead := int8(0) + if autoReadStr == "1" { + autoRead = 1 + } + + now := time.Now() + row := models.SystemSiteReminder{ + ID: 1, + RetentionDays: retentionDays, + AutoRead: autoRead, + CreateTime: &now, + UpdateTime: &now, + } + return row, nil +} + +// SaveSiteReminderConfig 保存/更新配置 +func SaveSiteReminderConfig(retentionDays int, autoRead int8) error { + if retentionDays <= 0 { + retentionDays = 30 + } + + autoReadStr := "0" + if autoRead == 1 { + autoReadStr = "1" + } + + settings := []struct { + code string + name string + value string + remark string + }{ + {"sitemsg_retention_days", "站内信消息保留天数", strconv.Itoa(retentionDays), ""}, + {"sitemsg_auto_read", "自动标记已读状态", autoReadStr, "0为关闭,1为开启"}, + } + + for _, item := range settings { + var setting models.PlatformNormalSetting + err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + return err + } + } else { + newSetting := models.PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = models.Orm.Insert(&newSetting) + if err != nil { + return err + } + } + } + return nil +} + +// SendSiteReminder 发送站内信 +// targetType: platform (平台端), tenant_all (管理端所有用户), role (平台角色), tenant (特定租户) +func SendSiteReminder(title, content string, senderID uint64, senderType string, targetType string, targetRoleID uint64, targetTenantID uint64) error { + var receiverIDs []uint64 + var receiverType string + + switch targetType { + case "platform": + receiverType = "platform" + var list []models.AdminUser + _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id") + if err != nil { + return fmt.Errorf("查询平台用户失败: %w", err) + } + for _, u := range list { + receiverIDs = append(receiverIDs, u.ID) + } + case "tenant_all": + receiverType = "tenant" + var list []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id") + if err != nil { + return fmt.Errorf("查询租户用户失败: %w", err) + } + for _, u := range list { + receiverIDs = append(receiverIDs, u.ID) + } + case "role": + receiverType = "platform" + var list []models.AdminUser + _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("role_id", targetRoleID).Filter("delete_time__isnull", true).All(&list, "id") + if err != nil { + return fmt.Errorf("根据角色查询用户失败: %w", err) + } + for _, u := range list { + receiverIDs = append(receiverIDs, u.ID) + } + case "tenant": + receiverType = "tenant" + var list []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("tid", targetTenantID).Filter("delete_time__isnull", true).All(&list, "id") + if err != nil { + return fmt.Errorf("根据租户查询用户失败: %w", err) + } + for _, u := range list { + receiverIDs = append(receiverIDs, u.ID) + } + default: + return fmt.Errorf("未知的发送目标类型: %s", targetType) + } + + if len(receiverIDs) == 0 { + return nil + } + + now := time.Now() + batchID := fmt.Sprintf("%d_%d", now.UnixNano(), senderID) + var reminders []models.SystemReminderList + for _, rid := range receiverIDs { + reminders = append(reminders, models.SystemReminderList{ + Title: title, + Content: content, + SenderID: senderID, + SenderType: senderType, + ReceiverID: rid, + ReceiverType: receiverType, + IsRead: 0, + CreateTime: &now, + BatchID: batchID, + TargetType: targetType, + TargetRoleID: targetRoleID, + TargetTenantID: targetTenantID, + }) + } + + // 批量插入 + _, err := models.Orm.InsertMulti(100, reminders) + return err +} + +// ListReminders 列表查询 +func ListReminders(receiverID uint64, receiverType string, page, pageSize int, isRead *int8) ([]models.SystemReminderList, int64, error) { + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 10 + } + var list []models.SystemReminderList + qs := models.Orm.QueryTable(new(models.SystemReminderList)). + Filter("receiver_id", receiverID). + Filter("receiver_type", receiverType). + Filter("delete_time__isnull", true) + + if isRead != nil { + qs = qs.Filter("is_read", *isRead) + } + + total, err := qs.Count() + if err != nil { + return nil, 0, err + } + + offset := (page - 1) * pageSize + _, err = qs.OrderBy("-create_time", "-id").Limit(pageSize, offset).All(&list) + return list, total, err +} + +// MarkReminderRead 标记单条已读 +func MarkReminderRead(id uint64, receiverID uint64, receiverType string) error { + now := time.Now() + _, err := models.Orm.QueryTable(new(models.SystemReminderList)). + Filter("id", id). + Filter("receiver_id", receiverID). + Filter("receiver_type", receiverType). + Update(map[string]interface{}{ + "is_read": 1, + "read_time": &now, + }) + return err +} + +// MarkAllRemindersRead 一键全部已读 +func MarkAllRemindersRead(receiverID uint64, receiverType string) error { + now := time.Now() + _, err := models.Orm.QueryTable(new(models.SystemReminderList)). + Filter("receiver_id", receiverID). + Filter("receiver_type", receiverType). + Filter("is_read", 0). + Update(map[string]interface{}{ + "is_read": 1, + "read_time": &now, + }) + return err +} + +// DeleteReminder 删除消息 +func DeleteReminder(id uint64, receiverID uint64, receiverType string) error { + now := time.Now() + _, err := models.Orm.QueryTable(new(models.SystemReminderList)). + Filter("id", id). + Filter("receiver_id", receiverID). + Filter("receiver_type", receiverType). + Update(map[string]interface{}{ + "delete_time": &now, + }) + return err +} + +// AutoCleanExpiredReminders 自动清理过期站内信 +func AutoCleanExpiredReminders() error { + cfg, err := GetSiteReminderConfig() + if err != nil { + return err + } + if cfg.RetentionDays <= 0 { + return nil + } + expireTime := time.Now().AddDate(0, 0, -cfg.RetentionDays) + _, err = models.Orm.QueryTable(new(models.SystemReminderList)). + Filter("create_time__lt", expireTime). + Delete() + return err +} + +// ListSentReminders 获取已发送的消息列表(按 batch_id 分组) +func ListSentReminders(senderID uint64, page, pageSize int) ([]models.SystemReminderList, int64, error) { + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = 10 + } + offset := (page - 1) * pageSize + + var total int64 + err := models.Orm.Raw("SELECT COUNT(DISTINCT batch_id) FROM yz_system_reminderlist WHERE sender_id = ? AND delete_time IS NULL", senderID).QueryRow(&total) + if err != nil { + return nil, 0, err + } + + var list []models.SystemReminderList + _, err = models.Orm.Raw("SELECT * FROM yz_system_reminderlist WHERE id IN (SELECT MIN(id) FROM yz_system_reminderlist WHERE sender_id = ? AND delete_time IS NULL GROUP BY batch_id) ORDER BY id DESC LIMIT ? OFFSET ?", senderID, pageSize, offset).QueryRows(&list) + if err != nil { + return nil, 0, err + } + + return list, total, nil +} + +// UpdateSentReminder 更新已发出的消息(更新该批次下所有接收者的消息,支持修改目标接收群体) +func UpdateSentReminder(batchID string, title, content, targetType string, targetRoleID, targetTenantID uint64) error { + // 1. 获取当前发送者ID (从该批次中任意一条记录中获取) + var firstRecord models.SystemReminderList + err := models.Orm.QueryTable(new(models.SystemReminderList)).Filter("batch_id", batchID).Limit(1).One(&firstRecord) + if err != nil { + return fmt.Errorf("找不到该批次的站内信记录: %w", err) + } + senderID := firstRecord.SenderID + senderType := firstRecord.SenderType + + // 2. 根据新的目标接收群体获取接收人列表 + var receiverIDs []uint64 + var receiverType string + + switch targetType { + case "platform": + receiverType = "platform" + var list []models.AdminUser + _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id") + if err != nil { + return fmt.Errorf("查询平台用户失败: %w", err) + } + for _, u := range list { + receiverIDs = append(receiverIDs, u.ID) + } + case "tenant_all": + receiverType = "tenant" + var list []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id") + if err != nil { + return fmt.Errorf("查询租户用户失败: %w", err) + } + for _, u := range list { + receiverIDs = append(receiverIDs, u.ID) + } + case "role": + receiverType = "platform" + var list []models.AdminUser + _, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("role_id", targetRoleID).Filter("delete_time__isnull", true).All(&list, "id") + if err != nil { + return fmt.Errorf("根据角色查询用户失败: %w", err) + } + for _, u := range list { + receiverIDs = append(receiverIDs, u.ID) + } + case "tenant": + receiverType = "tenant" + var list []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("tid", targetTenantID).Filter("delete_time__isnull", true).All(&list, "id") + if err != nil { + return fmt.Errorf("根据租户查询用户失败: %w", err) + } + for _, u := range list { + receiverIDs = append(receiverIDs, u.ID) + } + default: + return fmt.Errorf("未知的发送目标类型: %s", targetType) + } + + // 3. 获取该批次中现有的所有记录 (包括已删除的) + var existingRecords []models.SystemReminderList + _, err = models.Orm.QueryTable(new(models.SystemReminderList)).Filter("batch_id", batchID).All(&existingRecords) + if err != nil { + return fmt.Errorf("获取现有记录失败: %w", err) + } + + // 建立 map 快速查找,Key 为 "receiverType_receiverID" + existingMap := make(map[string]*models.SystemReminderList) + for i := range existingRecords { + key := fmt.Sprintf("%s_%d", existingRecords[i].ReceiverType, existingRecords[i].ReceiverID) + existingMap[key] = &existingRecords[i] + } + + newReceiverMap := make(map[string]bool) + for _, rid := range receiverIDs { + key := fmt.Sprintf("%s_%d", receiverType, rid) + newReceiverMap[key] = true + } + + now := time.Now() + + // 事务处理 + err = models.Orm.DoTx(func(c context.Context, txOrm orm.TxOrmer) error { + // A. 对于已经不在新接收者列表中的用户,软删除 + for key, rec := range existingMap { + if !newReceiverMap[key] { + if rec.DeleteTime == nil { + rec.DeleteTime = &now + if _, e := txOrm.Update(rec, "DeleteTime"); e != nil { + return e + } + } + } + } + + // B. 对于仍然在新接收者列表中的用户,更新标题、内容、以及 target 信息;如果原来被删除了,清除 delete_time + var newInserts []models.SystemReminderList + for _, rid := range receiverIDs { + key := fmt.Sprintf("%s_%d", receiverType, rid) + if rec, exists := existingMap[key]; exists { + rec.Title = title + rec.Content = content + rec.TargetType = targetType + rec.TargetRoleID = targetRoleID + rec.TargetTenantID = targetTenantID + + cols := []string{"Title", "Content", "TargetType", "TargetRoleID", "TargetTenantID"} + if rec.DeleteTime != nil { + rec.DeleteTime = nil + rec.IsRead = 0 + rec.ReadTime = nil + cols = append(cols, "DeleteTime", "IsRead", "ReadTime") + } + if _, e := txOrm.Update(rec, cols...); e != nil { + return e + } + } else { + // C. 对于新增加的接收者,插入新记录 + newInserts = append(newInserts, models.SystemReminderList{ + Title: title, + Content: content, + SenderID: senderID, + SenderType: senderType, + ReceiverID: rid, + ReceiverType: receiverType, + IsRead: 0, + CreateTime: &now, + BatchID: batchID, + TargetType: targetType, + TargetRoleID: targetRoleID, + TargetTenantID: targetTenantID, + }) + } + } + + if len(newInserts) > 0 { + if _, e := txOrm.InsertMulti(100, newInserts); e != nil { + return e + } + } + + return nil + }) + + return err +} + +// DeleteSentReminderBatch 删除已发送消息(删除该批次下所有记录) +func DeleteSentReminderBatch(batchID string) error { + now := time.Now() + _, err := models.Orm.QueryTable(new(models.SystemReminderList)). + Filter("batch_id", batchID). + Update(map[string]interface{}{ + "delete_time": &now, + }) + return err +} diff --git a/go/services/tenant_user.go b/go/services/tenant_user.go index 937f857..2c8ab8e 100644 --- a/go/services/tenant_user.go +++ b/go/services/tenant_user.go @@ -1,144 +1,144 @@ -package services - -import ( - "strings" - - "server/models" -) - -// BindTenantUser 绑定用户到租户(若已存在则更新状态/默认值) -func BindTenantUser(tid, uid uint64, account, name, phone, email *string, sex *uint8, birth *string, password *string, isDefault, status int8, remark *string) (uint64, error) { - var existed models.SystemTenantUser - err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("tid", tid). - Filter("uid", uid). - One(&existed) - if err == nil { - update := map[string]interface{}{ - "account": account, - "name": name, - "phone": phone, - "email": email, - "password": password, - "status": status, - "is_default": isDefault, - "remark": remark, - } - if sex != nil { - update["sex"] = *sex - } - if birth != nil { - trimmedBirth := strings.TrimSpace(*birth) - if trimmedBirth == "" { - update["birth"] = nil - } else { - update["birth"] = trimmedBirth - } - } - _, uErr := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", existed.ID).Update(update) - return existed.ID, uErr - } - - m := &models.SystemTenantUser{ - Tid: tid, - Uid: uid, - Account: account, - Name: name, - Phone: phone, - Email: email, - Password: password, - IsDefault: isDefault, - Status: status, - Remark: remark, - } - if sex != nil { - m.Sex = *sex - } - if birth != nil { - trimmedBirth := strings.TrimSpace(*birth) - if trimmedBirth != "" { - m.Birth = &trimmedBirth - } - } - id, iErr := models.Orm.Insert(m) - return uint64(id), iErr -} - -// UnbindTenantUser 删除绑定关系 -func UnbindTenantUser(id uint64) error { - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).Delete() - return err -} - -// ListTenantUsersByTid 根据租户ID查询绑定关系 -func ListTenantUsersByTid(tid uint64) ([]models.SystemTenantUser, error) { - var rows []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("tid", tid). - OrderBy("-is_default", "-id"). - All(&rows) - return rows, err -} - -// ListTenantBindingsByUid 根据用户ID查询绑定关系 -func ListTenantBindingsByUid(uid uint64) ([]models.SystemTenantUser, error) { - var rows []models.SystemTenantUser - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("uid", uid). - OrderBy("-is_default", "-id"). - All(&rows) - return rows, err -} - -// GetTenantUserByUidAndTid 根据用户ID和租户ID查询租户用户绑定关系 -func GetTenantUserByUidAndTid(uid, tid uint64) (*models.SystemTenantUser, error) { - var row models.SystemTenantUser - err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("uid", uid). - Filter("tid", tid). - One(&row) - if err != nil { - return nil, err - } - return &row, nil -} - -// GetTenantUserByUid 根据用户ID查询默认/最新租户用户绑定关系 -func GetTenantUserByUid(uid uint64) (*models.SystemTenantUser, error) { - var row models.SystemTenantUser - err := models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("uid", uid). - OrderBy("-is_default", "-id"). - One(&row) - if err != nil { - return nil, err - } - return &row, nil -} - -// GetTenantByID 根据租户ID查询租户信息 -func GetTenantByID(id uint64) (*models.SystemTenant, error) { - var row models.SystemTenant - err := models.Orm.QueryTable(new(models.SystemTenant)). - Filter("id", id). - One(&row) - if err != nil { - return nil, err - } - return &row, nil -} - -// SetDefaultTenant 设置用户默认租户(同一用户仅一个默认) -func SetDefaultTenant(uid, tid uint64) error { - _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("uid", uid).Update(map[string]interface{}{ - "is_default": 0, - }) - if err != nil { - return err - } - _, err = models.Orm.QueryTable(new(models.SystemTenantUser)). - Filter("uid", uid). - Filter("tid", tid). - Update(map[string]interface{}{"is_default": 1}) - return err -} +package services + +import ( + "strings" + + "server/models" +) + +// BindTenantUser 绑定用户到租户(若已存在则更新状态/默认值) +func BindTenantUser(tid, uid uint64, account, name, phone, email *string, sex *uint8, birth *string, password *string, isDefault, status int8, remark *string) (uint64, error) { + var existed models.SystemTenantUser + err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", tid). + Filter("uid", uid). + One(&existed) + if err == nil { + update := map[string]interface{}{ + "account": account, + "name": name, + "phone": phone, + "email": email, + "password": password, + "status": status, + "is_default": isDefault, + "remark": remark, + } + if sex != nil { + update["sex"] = *sex + } + if birth != nil { + trimmedBirth := strings.TrimSpace(*birth) + if trimmedBirth == "" { + update["birth"] = nil + } else { + update["birth"] = trimmedBirth + } + } + _, uErr := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", existed.ID).Update(update) + return existed.ID, uErr + } + + m := &models.SystemTenantUser{ + Tid: tid, + Uid: uid, + Account: account, + Name: name, + Phone: phone, + Email: email, + Password: password, + IsDefault: isDefault, + Status: status, + Remark: remark, + } + if sex != nil { + m.Sex = *sex + } + if birth != nil { + trimmedBirth := strings.TrimSpace(*birth) + if trimmedBirth != "" { + m.Birth = &trimmedBirth + } + } + id, iErr := models.Orm.Insert(m) + return uint64(id), iErr +} + +// UnbindTenantUser 删除绑定关系 +func UnbindTenantUser(id uint64) error { + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).Delete() + return err +} + +// ListTenantUsersByTid 根据租户ID查询绑定关系 +func ListTenantUsersByTid(tid uint64) ([]models.SystemTenantUser, error) { + var rows []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("tid", tid). + OrderBy("-is_default", "-id"). + All(&rows) + return rows, err +} + +// ListTenantBindingsByUid 根据用户ID查询绑定关系 +func ListTenantBindingsByUid(uid uint64) ([]models.SystemTenantUser, error) { + var rows []models.SystemTenantUser + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("uid", uid). + OrderBy("-is_default", "-id"). + All(&rows) + return rows, err +} + +// GetTenantUserByUidAndTid 根据用户ID和租户ID查询租户用户绑定关系 +func GetTenantUserByUidAndTid(uid, tid uint64) (*models.SystemTenantUser, error) { + var row models.SystemTenantUser + err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("uid", uid). + Filter("tid", tid). + One(&row) + if err != nil { + return nil, err + } + return &row, nil +} + +// GetTenantUserByUid 根据用户ID查询默认/最新租户用户绑定关系 +func GetTenantUserByUid(uid uint64) (*models.SystemTenantUser, error) { + var row models.SystemTenantUser + err := models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("uid", uid). + OrderBy("-is_default", "-id"). + One(&row) + if err != nil { + return nil, err + } + return &row, nil +} + +// GetTenantByID 根据租户ID查询租户信息 +func GetTenantByID(id uint64) (*models.SystemTenant, error) { + var row models.SystemTenant + err := models.Orm.QueryTable(new(models.SystemTenant)). + Filter("id", id). + One(&row) + if err != nil { + return nil, err + } + return &row, nil +} + +// SetDefaultTenant 设置用户默认租户(同一用户仅一个默认) +func SetDefaultTenant(uid, tid uint64) error { + _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("uid", uid).Update(map[string]interface{}{ + "is_default": 0, + }) + if err != nil { + return err + } + _, err = models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("uid", uid). + Filter("tid", tid). + Update(map[string]interface{}{"is_default": 1}) + return err +} diff --git a/go/static/js/reload.min.js b/go/static/js/reload.min.js index e780033..b2e17fc 100644 --- a/go/static/js/reload.min.js +++ b/go/static/js/reload.min.js @@ -1 +1 @@ -function b(a){var c=new WebSocket(a);c.onclose=function(){setTimeout(function(){b(a)},2E3)};c.onmessage=function(){location.reload()}}try{if(window.WebSocket)try{b("ws://localhost:12450/reload")}catch(a){console.error(a)}else console.log("Your browser does not support WebSockets.")}catch(a){console.error("Exception during connecting to Reload:",a)}; +function b(a){var c=new WebSocket(a);c.onclose=function(){setTimeout(function(){b(a)},2E3)};c.onmessage=function(){location.reload()}}try{if(window.WebSocket)try{b("ws://localhost:12450/reload")}catch(a){console.error(a)}else console.log("Your browser does not support WebSockets.")}catch(a){console.error("Exception during connecting to Reload:",a)}; diff --git a/go/version/version.go b/go/version/version.go index 7e95602..ed8f029 100644 --- a/go/version/version.go +++ b/go/version/version.go @@ -1,9 +1,9 @@ -package version - -// Version 项目版本号 -const Version = "1.0.1" - -// GetVersion 获取版本号 -func GetVersion() string { - return Version -} +package version + +// Version 项目版本号 +const Version = "1.0.1" + +// GetVersion 获取版本号 +func GetVersion() string { + return Version +} diff --git a/go/views/admin/index.tpl b/go/views/admin/index.tpl index 7375843..bf75ed2 100644 --- a/go/views/admin/index.tpl +++ b/go/views/admin/index.tpl @@ -1,3 +1,3 @@ -{{ .Title }} - +{{ .Title }} +

Hello, admin!

\ No newline at end of file diff --git a/go/views/index.tpl b/go/views/index.tpl index 8d6fbec..6e3e70b 100644 --- a/go/views/index.tpl +++ b/go/views/index.tpl @@ -1,95 +1,95 @@ - - - - - Beego - - - - - - - -
-

Welcome to Beego

-
- Beego is a simple & powerful Go web framework which is inspired by tornado and sinatra. -
-
- -
- - - - + + + + + Beego + + + + + + + +
+

Welcome to Beego

+
+ Beego is a simple & powerful Go web framework which is inspired by tornado and sinatra. +
+
+ +
+ + + + diff --git a/platform/.gitignore b/platform/.gitignore index 16308d4..d8ee227 100644 --- a/platform/.gitignore +++ b/platform/.gitignore @@ -1,33 +1,33 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -output -dist-ssr -*.local -output.zip -dist.zip -dist.7z -output.7z - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -.env -.env.* +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +output +dist-ssr +*.local +output.zip +dist.zip +dist.7z +output.7z + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env +.env.* !.example.env \ No newline at end of file diff --git a/platform/README.md b/platform/README.md index 1511959..658be6d 100644 --- a/platform/README.md +++ b/platform/README.md @@ -1,5 +1,5 @@ -# Vue 3 + Vite - -This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` - - -``` - ---- - -### 场景2:在编辑对话框使用字典 - -**文件**: `src/views/system/users/components/UserEdit.vue` - -```vue - - - -``` - ---- - -### 场景3:快速使用 Composable Hook - -最简单的方式,自动处理加载: - -```vue - - - -``` - ---- - -### 场景4:预加载应用启动时需要的字典 - -**文件**: `src/main.js` - -```javascript -import { createApp } from 'vue' -import { createPinia } from 'pinia' -import { useDictStore } from '@/stores/dict' -import { DICT_CODES } from '@/constants/dictCodes' - -const app = createApp(App) -const pinia = createPinia() - -app.use(pinia) - -// 在应用启动后预加载常用字典 -const dictStore = useDictStore() -await dictStore.preloadDicts([ - DICT_CODES.USER_STATUS, - DICT_CODES.COMMON_STATUS, - DICT_CODES.YES_NO, -]) - -app.mount('#app') -``` - ---- - -## 字典数据结构 - -后端返回的字典数据结构: - -```json -[ - { - "dict_id": 1, - "dict_value": "1", - "dict_label": "启用", - "dict_type": "user_status", - "remarks": "用户启用状态", - "remark": "用户启用状态" - }, - { - "dict_id": 2, - "dict_value": "0", - "dict_label": "禁用", - "dict_type": "user_status", - "remarks": "用户禁用状态", - "remark": "用户禁用状态" - } -] -``` - -**关键字段**: -- `dict_value`: 字典值(存储在数据库中) -- `dict_label`: 字典标签(显示给用户) -- `dict_type`: 字典类型编码(如 'user_status') - ---- - -## 最佳实践 - -### ✅ DO - -1. **使用常量而不是硬编码字符串** - ```javascript - // ✅ 好 - dictStore.getDictItems(DICT_CODES.USER_STATUS) - - // ❌ 差 - dictStore.getDictItems('user_status') - ``` - -2. **在父组件加载,通过 props 传给子组件** - ```javascript - // ✅ 父组件负责数据,子组件负责展示 - // index.vue - const statusDict = await dictStore.getDictItems(DICT_CODES.USER_STATUS) - - // UserEdit.vue - const props = defineProps({ statusDict: Array }) - ``` - -3. **用 Composable 简化组件逻辑** - ```javascript - // ✅ 一行代码搞定 - const { user_statusDict, loading } = useUserStatusDict() - ``` - -4. **预加载常用字典** - ```javascript - // ✅ 应用启动时预加载,避免页面初始化时加载 - await dictStore.preloadDicts([...]) - ``` - -### ❌ DON'T - -1. **不要在多个地方重复加载同一个字典** - ```javascript - // ❌ 糟糕:重复加载 - // 页面A - const dict1 = await dictStore.getDictItems('user_status') - - // 页面B(Store 会自动缓存,但代码看起来重复) - const dict2 = await dictStore.getDictItems('user_status') - ``` - -2. **不要忘记处理加载状态** - ```javascript - // ❌ 可能展示空白 - const { statusDict } = useUserStatusDict() - - // ✅ 处理加载状态 - const { statusDict, loading } = useUserStatusDict() - if (loading) { /* 显示加载中 */ } - ``` - -3. **不要混用不同的字典访问方式** - ```javascript - // ❌ 混乱 - const dict1 = await dictStore.getDictItems('user_status') - const dict2 = dictStore.getDictItemsSync('user_role') - - // ✅ 统一使用 - const dict1 = await dictStore.getDictItems('user_status') - const dict2 = await dictStore.getDictItems('user_role') - ``` - ---- - -## 性能优化建议 - -| 优化项 | 说明 | -|------|------| -| **缓存** | Store 自动缓存,同一个字典只请求一次 | -| **预加载** | 在路由切换前预加载需要的字典 | -| **同步访问** | 已加载的字典可用 `getDictItemsSync` 同步获取 | -| **避免重复** | 不要在多个组件重复请求同一个字典 | - ---- - -## 故障排查 - -### 问题1:状态选项为空 - -**原因**:字典未加载 -**解决**: -```javascript -// ❌ 错误:字典还未加载 -const statusDict = dictStore.getDictItemsSync('user_status') // 返回 [] - -// ✅ 正确:等待异步加载完成 -const statusDict = await dictStore.getDictItems('user_status') -``` - -### 问题2:重复加载字典 - -**原因**:没有使用 Store 的缓存 -**解决**: -```javascript -// 所有调用都会自动使用缓存,只请求一次 -await dictStore.getDictItems('user_status') // 首次:发送请求 -await dictStore.getDictItems('user_status') // 第二次:返回缓存 -``` - -### 问题3:字典显示不对 - -**原因**:value 类型不匹配(如 1 vs "1") -**解决**: -```javascript -// Store 会自动处理类型匹配 -const item = items.find(i => - String(i.dict_value) === String(value) || i.dict_value === value -) -``` - ---- - -## 集成检清表 - -- [ ] 创建 `src/stores/dict.js` - Store -- [ ] 创建 `src/constants/dictCodes.js` - 常量 -- [ ] 创建 `src/composables/useDict.js` - Composable -- [ ] 在 `index.vue` 中导入 `useDictStore` -- [ ] 在 `UserEdit.vue` 中接收 `statusDict` props -- [ ] 测试字典加载和显示 -- [ ] 验证缓存功能(打开浏览器 DevTools 检查 Network) -- [ ] 预加载常用字典(可选) - ---- - -## 相关文件修改 - -已修改的文件: -- ✅ `src/stores/dict.js` - 新建 -- ✅ `src/constants/dictCodes.js` - 新建 -- ✅ `src/composables/useDict.js` - 新建 -- ✅ `src/views/system/users/index.vue` - 使用 `useDictStore` -- ✅ `src/views/system/users/components/UserEdit.vue` - 导入字典库 - +# Pinia 字典管理系统使用指南 + +## 系统架构 + +``` +┌─────────────────────────────────────┐ +│ API 接口 (getDictItemsByCode) │ +│ /api/dict/items/code/{code} │ +└──────────────┬──────────────────────┘ + │ + ↓ +┌─────────────────────────────────────┐ +│ Pinia Store (useDictStore) │ +│ ✅ 自动缓存字典数据 │ +│ ✅ 避免重复请求 │ +│ ✅ 支持同步/异步访问 │ +└──────────────┬──────────────────────┘ + │ + ┌──────┴──────┐ + ↓ ↓ + ┌────────┐ ┌──────────────┐ + │组件 │ │Composable │ + │直接用 │ │useDict Hook │ + └────────┘ └──────────────┘ +``` + +--- + +## 核心文件说明 + +### 1. **Store**: `src/stores/dict.js` + +字典数据的全局管理器 + +**主要方法**: +```javascript +import { useDictStore } from '@/stores/dict' + +const dictStore = useDictStore() + +// ✅ 异步获取字典(推荐) +const items = await dictStore.getDictItems('user_status') + +// ✅ 同步获取字典(已缓存时) +const items = dictStore.getDictItemsSync('user_status') + +// ✅ 获取字典值对应的标签 +const label = dictStore.getDictLabel('user_status', 1) + +// ✅ 预加载多个字典 +await dictStore.preloadDicts(['user_status', 'user_role']) + +// ✅ 清空缓存 +dictStore.clearCache('user_status') +``` + +--- + +### 2. **常量**: `src/constants/dictCodes.js` + +集中管理所有字典编码 + +**使用示例**: +```javascript +import { DICT_CODES } from '@/constants/dictCodes' + +// 好处:避免硬编码,IDE 有自动完成 +const items = await dictStore.getDictItems(DICT_CODES.USER_STATUS) + +// 所有可用的编码: +DICT_CODES.USER_STATUS // 用户状态 +DICT_CODES.USER_GENDER // 用户性别 +DICT_CODES.USER_ROLE // 用户角色 +DICT_CODES.DEPT_STATUS // 部门状态 +DICT_CODES.POSITION_STATUS // 职位状态 +// ... 更多编码 +``` + +--- + +### 3. **Composable**: `src/composables/useDict.js` + +简化在组件中使用字典的 Hook + +**基础用法**: +```javascript +import { useDictionary, useUserStatusDict } from '@/composables/useDict' +import { DICT_CODES } from '@/constants/dictCodes' + +// 方式1:使用常量 +const { statusDict, loading } = useDictionary(DICT_CODES.USER_STATUS) + +// 方式2:使用字符串 +const { dicts, loading } = useDictionary('user_status') + +// 方式3:使用特化 Hook(推荐) +const { user_statusDict, loading } = useUserStatusDict() +``` + +--- + +## 使用场景 + +### 场景1:在列表页加载字典 + +**文件**: `src/views/system/users/index.vue` + +```vue + + + +``` + +--- + +### 场景2:在编辑对话框使用字典 + +**文件**: `src/views/system/users/components/UserEdit.vue` + +```vue + + + +``` + +--- + +### 场景3:快速使用 Composable Hook + +最简单的方式,自动处理加载: + +```vue + + + +``` + +--- + +### 场景4:预加载应用启动时需要的字典 + +**文件**: `src/main.js` + +```javascript +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import { useDictStore } from '@/stores/dict' +import { DICT_CODES } from '@/constants/dictCodes' + +const app = createApp(App) +const pinia = createPinia() + +app.use(pinia) + +// 在应用启动后预加载常用字典 +const dictStore = useDictStore() +await dictStore.preloadDicts([ + DICT_CODES.USER_STATUS, + DICT_CODES.COMMON_STATUS, + DICT_CODES.YES_NO, +]) + +app.mount('#app') +``` + +--- + +## 字典数据结构 + +后端返回的字典数据结构: + +```json +[ + { + "dict_id": 1, + "dict_value": "1", + "dict_label": "启用", + "dict_type": "user_status", + "remarks": "用户启用状态", + "remark": "用户启用状态" + }, + { + "dict_id": 2, + "dict_value": "0", + "dict_label": "禁用", + "dict_type": "user_status", + "remarks": "用户禁用状态", + "remark": "用户禁用状态" + } +] +``` + +**关键字段**: +- `dict_value`: 字典值(存储在数据库中) +- `dict_label`: 字典标签(显示给用户) +- `dict_type`: 字典类型编码(如 'user_status') + +--- + +## 最佳实践 + +### ✅ DO + +1. **使用常量而不是硬编码字符串** + ```javascript + // ✅ 好 + dictStore.getDictItems(DICT_CODES.USER_STATUS) + + // ❌ 差 + dictStore.getDictItems('user_status') + ``` + +2. **在父组件加载,通过 props 传给子组件** + ```javascript + // ✅ 父组件负责数据,子组件负责展示 + // index.vue + const statusDict = await dictStore.getDictItems(DICT_CODES.USER_STATUS) + + // UserEdit.vue + const props = defineProps({ statusDict: Array }) + ``` + +3. **用 Composable 简化组件逻辑** + ```javascript + // ✅ 一行代码搞定 + const { user_statusDict, loading } = useUserStatusDict() + ``` + +4. **预加载常用字典** + ```javascript + // ✅ 应用启动时预加载,避免页面初始化时加载 + await dictStore.preloadDicts([...]) + ``` + +### ❌ DON'T + +1. **不要在多个地方重复加载同一个字典** + ```javascript + // ❌ 糟糕:重复加载 + // 页面A + const dict1 = await dictStore.getDictItems('user_status') + + // 页面B(Store 会自动缓存,但代码看起来重复) + const dict2 = await dictStore.getDictItems('user_status') + ``` + +2. **不要忘记处理加载状态** + ```javascript + // ❌ 可能展示空白 + const { statusDict } = useUserStatusDict() + + // ✅ 处理加载状态 + const { statusDict, loading } = useUserStatusDict() + if (loading) { /* 显示加载中 */ } + ``` + +3. **不要混用不同的字典访问方式** + ```javascript + // ❌ 混乱 + const dict1 = await dictStore.getDictItems('user_status') + const dict2 = dictStore.getDictItemsSync('user_role') + + // ✅ 统一使用 + const dict1 = await dictStore.getDictItems('user_status') + const dict2 = await dictStore.getDictItems('user_role') + ``` + +--- + +## 性能优化建议 + +| 优化项 | 说明 | +|------|------| +| **缓存** | Store 自动缓存,同一个字典只请求一次 | +| **预加载** | 在路由切换前预加载需要的字典 | +| **同步访问** | 已加载的字典可用 `getDictItemsSync` 同步获取 | +| **避免重复** | 不要在多个组件重复请求同一个字典 | + +--- + +## 故障排查 + +### 问题1:状态选项为空 + +**原因**:字典未加载 +**解决**: +```javascript +// ❌ 错误:字典还未加载 +const statusDict = dictStore.getDictItemsSync('user_status') // 返回 [] + +// ✅ 正确:等待异步加载完成 +const statusDict = await dictStore.getDictItems('user_status') +``` + +### 问题2:重复加载字典 + +**原因**:没有使用 Store 的缓存 +**解决**: +```javascript +// 所有调用都会自动使用缓存,只请求一次 +await dictStore.getDictItems('user_status') // 首次:发送请求 +await dictStore.getDictItems('user_status') // 第二次:返回缓存 +``` + +### 问题3:字典显示不对 + +**原因**:value 类型不匹配(如 1 vs "1") +**解决**: +```javascript +// Store 会自动处理类型匹配 +const item = items.find(i => + String(i.dict_value) === String(value) || i.dict_value === value +) +``` + +--- + +## 集成检清表 + +- [ ] 创建 `src/stores/dict.js` - Store +- [ ] 创建 `src/constants/dictCodes.js` - 常量 +- [ ] 创建 `src/composables/useDict.js` - Composable +- [ ] 在 `index.vue` 中导入 `useDictStore` +- [ ] 在 `UserEdit.vue` 中接收 `statusDict` props +- [ ] 测试字典加载和显示 +- [ ] 验证缓存功能(打开浏览器 DevTools 检查 Network) +- [ ] 预加载常用字典(可选) + +--- + +## 相关文件修改 + +已修改的文件: +- ✅ `src/stores/dict.js` - 新建 +- ✅ `src/constants/dictCodes.js` - 新建 +- ✅ `src/composables/useDict.js` - 新建 +- ✅ `src/views/system/users/index.vue` - 使用 `useDictStore` +- ✅ `src/views/system/users/components/UserEdit.vue` - 导入字典库 + diff --git a/platform/docs/一键复制.md b/platform/docs/一键复制.md index 52c48c5..9bd39b3 100644 --- a/platform/docs/一键复制.md +++ b/platform/docs/一键复制.md @@ -1,31 +1,31 @@ - - - \ No newline at end of file diff --git a/platform/docs/七牛云上传测试步骤.md b/platform/docs/七牛云上传测试步骤.md index 4e3bafd..d471eba 100644 --- a/platform/docs/七牛云上传测试步骤.md +++ b/platform/docs/七牛云上传测试步骤.md @@ -1,201 +1,201 @@ -# 七牛云上传测试步骤 - -## 前置条件 - -1. 数据库配置已正确设置: - - `storage_type = 'qiniu'` - - `qiniu_region = 'z2'` (华南) - - `qiniu_bucket = 'yunzerwebsite'` - - `qiniu_domain = 'http://7colud.yunzer.cn'` - - `qiniu_access_key` 和 `qiniu_secret_key` 已配置 - -2. 前端依赖已安装: - - `qiniu-js@^3.4.4` ✓ (已在 package.json 中) - -3. 后端服务已重启: - ```bash - systemctl restart go-api - ``` - -## 测试步骤 - -### 1. 登录系统 - -访问前端页面并登录: -- URL: https://platform.yunzer.cn -- 账号: hero920103 -- 密码: 920103 - -### 2. 进入软件升级页面 - -导航到:平台管理 → 软件升级 - -### 3. 上传文件测试 - -点击"新增"或"编辑"按钮,在弹出的对话框中: - -1. 点击"上传软件包"按钮 -2. 选择一个文件(建议先用小文件测试,如 1-10MB) -3. 观察上传进度条 -4. 等待上传完成 - -### 4. 验证上传结果 - -#### 前端验证 -- 检查是否显示上传成功消息 -- 检查文件 URL 是否正确(应该是 `http://7colud.yunzer.cn/...`) -- 检查文件是否可以预览/下载 - -#### 后端日志验证 -```bash -tail -f /www/wwwroot/api.yunzer.cn/go.log -``` - -查看日志中是否有: -- `GET /platform/storage/config` - 获取存储配置 -- `GET /platform/qiniu/token` - 获取上传凭证 -- `POST /platform/qiniu/save` - 保存文件记录 - -#### 七牛云控制台验证 -1. 登录七牛云控制台 -2. 进入 `yunzerwebsite` 存储空间 -3. 检查文件是否已上传 -4. 文件路径格式应为:`2026/04/09/时间戳.扩展名` - -#### 数据库验证 -```sql -SELECT id, name, src, size, type, cate, md5, create_time -FROM system_file -ORDER BY id DESC -LIMIT 5; -``` - -检查: -- `src` 字段应该是完整的七牛云 URL -- `md5` 字段应该有值 -- `size` 字段应该正确 - -## 预期结果 - -### 成功标志 -1. 前端显示上传成功 -2. 文件 URL 格式正确:`http://7colud.yunzer.cn/2026/04/09/xxxxx.ext` -3. 七牛云控制台能看到文件 -4. 数据库有对应记录 -5. 文件可以正常访问和下载 - -### 上传流程 -``` -前端 → 获取存储配置 (storageType: 'qiniu') - → 获取上传凭证 (token, region: 'z2') - → 直接上传到七牛云 (up-z2.qiniup.com) - → 保存文件记录到数据库 - → 返回文件 URL -``` - -## 常见问题排查 - -### 问题 1: "incorrect region" 错误 - -**错误信息**: -``` -xhr request failed, code: 400 -response: {"error":"incorrect region, please use up-z2.qiniup.com, bucket is: yunzerwebsite"} -``` - -**原因**: 前端使用的区域与七牛云 bucket 实际区域不匹配 - -**解决**: -- 已修复:添加了 `getQiniuRegion()` 函数,根据后端返回的 region 动态设置 -- 后端返回 `region: 'z2'`,前端会自动使用 `qiniu.region.z2` - -### 问题 2: "获取上传凭证失败" - -**可能原因**: -- 未登录或 token 过期 -- 存储配置未设置为七牛云 -- 七牛云配置不完整 - -**解决**: -1. 检查登录状态 -2. 检查数据库 `system_storage_config` 表 -3. 确认 AccessKey 和 SecretKey 正确 - -### 问题 3: 上传后文件无法访问 - -**可能原因**: -- 七牛云域名配置错误 -- 域名未绑定或未备案 -- 文件权限设置问题 - -**解决**: -1. 检查 `qiniu_domain` 配置 -2. 登录七牛云控制台检查域名绑定 -3. 检查存储空间访问权限(应为公开) - -### 问题 4: 大文件上传失败 - -**可能原因**: -- 网络超时 -- 浏览器限制 - -**解决**: -1. 七牛云 SDK 支持断点续传,会自动重试 -2. 检查网络连接 -3. 尝试分片上传(SDK 自动处理) - -## 性能测试 - -### 小文件测试 (< 10MB) -- 预期时间:几秒内完成 -- 不会分片上传 - -### 中等文件测试 (10MB - 100MB) -- 预期时间:根据网速,通常 1-2 分钟 -- 可能会分片上传 - -### 大文件测试 (> 100MB) -- 预期时间:根据网速 -- 会自动分片上传 -- 支持断点续传 - -## 对比测试 - -### 旧方案(服务器中转) -``` -100MB 文件上传时间: -- 上传到服务器:2 分钟 -- 服务器上传到七牛云:2 分钟 -- 总计:4 分钟 -``` - -### 新方案(直传) -``` -100MB 文件上传时间: -- 直接上传到七牛云:2 分钟 -- 总计:2 分钟 -``` - -**效率提升**: 50% - -## 回滚方案 - -如果七牛云直传有问题,可以临时切换回本地存储: - -```sql -UPDATE system_storage_config -SET storage_type = 'local' -WHERE id = 1; -``` - -前端会自动检测并使用本地上传方式(通过服务器中转)。 - -## 相关文件 - -- `platform/src/utils/qiniuUpload.js` - 上传工具(已添加 getQiniuRegion 函数) -- `go/controllers/qiniu_upload.go` - 后端控制器 -- `platform/docs/七牛云直传配置.md` - 详细配置文档 - -## 更新日期 - -2026-04-09 +# 七牛云上传测试步骤 + +## 前置条件 + +1. 数据库配置已正确设置: + - `storage_type = 'qiniu'` + - `qiniu_region = 'z2'` (华南) + - `qiniu_bucket = 'yunzerwebsite'` + - `qiniu_domain = 'http://7colud.yunzer.cn'` + - `qiniu_access_key` 和 `qiniu_secret_key` 已配置 + +2. 前端依赖已安装: + - `qiniu-js@^3.4.4` ✓ (已在 package.json 中) + +3. 后端服务已重启: + ```bash + systemctl restart go-api + ``` + +## 测试步骤 + +### 1. 登录系统 + +访问前端页面并登录: +- URL: https://platform.yunzer.cn +- 账号: hero920103 +- 密码: 920103 + +### 2. 进入软件升级页面 + +导航到:平台管理 → 软件升级 + +### 3. 上传文件测试 + +点击"新增"或"编辑"按钮,在弹出的对话框中: + +1. 点击"上传软件包"按钮 +2. 选择一个文件(建议先用小文件测试,如 1-10MB) +3. 观察上传进度条 +4. 等待上传完成 + +### 4. 验证上传结果 + +#### 前端验证 +- 检查是否显示上传成功消息 +- 检查文件 URL 是否正确(应该是 `http://7colud.yunzer.cn/...`) +- 检查文件是否可以预览/下载 + +#### 后端日志验证 +```bash +tail -f /www/wwwroot/api.yunzer.cn/go.log +``` + +查看日志中是否有: +- `GET /platform/storage/config` - 获取存储配置 +- `GET /platform/qiniu/token` - 获取上传凭证 +- `POST /platform/qiniu/save` - 保存文件记录 + +#### 七牛云控制台验证 +1. 登录七牛云控制台 +2. 进入 `yunzerwebsite` 存储空间 +3. 检查文件是否已上传 +4. 文件路径格式应为:`2026/04/09/时间戳.扩展名` + +#### 数据库验证 +```sql +SELECT id, name, src, size, type, cate, md5, create_time +FROM system_file +ORDER BY id DESC +LIMIT 5; +``` + +检查: +- `src` 字段应该是完整的七牛云 URL +- `md5` 字段应该有值 +- `size` 字段应该正确 + +## 预期结果 + +### 成功标志 +1. 前端显示上传成功 +2. 文件 URL 格式正确:`http://7colud.yunzer.cn/2026/04/09/xxxxx.ext` +3. 七牛云控制台能看到文件 +4. 数据库有对应记录 +5. 文件可以正常访问和下载 + +### 上传流程 +``` +前端 → 获取存储配置 (storageType: 'qiniu') + → 获取上传凭证 (token, region: 'z2') + → 直接上传到七牛云 (up-z2.qiniup.com) + → 保存文件记录到数据库 + → 返回文件 URL +``` + +## 常见问题排查 + +### 问题 1: "incorrect region" 错误 + +**错误信息**: +``` +xhr request failed, code: 400 +response: {"error":"incorrect region, please use up-z2.qiniup.com, bucket is: yunzerwebsite"} +``` + +**原因**: 前端使用的区域与七牛云 bucket 实际区域不匹配 + +**解决**: +- 已修复:添加了 `getQiniuRegion()` 函数,根据后端返回的 region 动态设置 +- 后端返回 `region: 'z2'`,前端会自动使用 `qiniu.region.z2` + +### 问题 2: "获取上传凭证失败" + +**可能原因**: +- 未登录或 token 过期 +- 存储配置未设置为七牛云 +- 七牛云配置不完整 + +**解决**: +1. 检查登录状态 +2. 检查数据库 `system_storage_config` 表 +3. 确认 AccessKey 和 SecretKey 正确 + +### 问题 3: 上传后文件无法访问 + +**可能原因**: +- 七牛云域名配置错误 +- 域名未绑定或未备案 +- 文件权限设置问题 + +**解决**: +1. 检查 `qiniu_domain` 配置 +2. 登录七牛云控制台检查域名绑定 +3. 检查存储空间访问权限(应为公开) + +### 问题 4: 大文件上传失败 + +**可能原因**: +- 网络超时 +- 浏览器限制 + +**解决**: +1. 七牛云 SDK 支持断点续传,会自动重试 +2. 检查网络连接 +3. 尝试分片上传(SDK 自动处理) + +## 性能测试 + +### 小文件测试 (< 10MB) +- 预期时间:几秒内完成 +- 不会分片上传 + +### 中等文件测试 (10MB - 100MB) +- 预期时间:根据网速,通常 1-2 分钟 +- 可能会分片上传 + +### 大文件测试 (> 100MB) +- 预期时间:根据网速 +- 会自动分片上传 +- 支持断点续传 + +## 对比测试 + +### 旧方案(服务器中转) +``` +100MB 文件上传时间: +- 上传到服务器:2 分钟 +- 服务器上传到七牛云:2 分钟 +- 总计:4 分钟 +``` + +### 新方案(直传) +``` +100MB 文件上传时间: +- 直接上传到七牛云:2 分钟 +- 总计:2 分钟 +``` + +**效率提升**: 50% + +## 回滚方案 + +如果七牛云直传有问题,可以临时切换回本地存储: + +```sql +UPDATE system_storage_config +SET storage_type = 'local' +WHERE id = 1; +``` + +前端会自动检测并使用本地上传方式(通过服务器中转)。 + +## 相关文件 + +- `platform/src/utils/qiniuUpload.js` - 上传工具(已添加 getQiniuRegion 函数) +- `go/controllers/qiniu_upload.go` - 后端控制器 +- `platform/docs/七牛云直传配置.md` - 详细配置文档 + +## 更新日期 + +2026-04-09 diff --git a/platform/docs/七牛云区域配置修复说明.md b/platform/docs/七牛云区域配置修复说明.md index 186449c..1250e3c 100644 --- a/platform/docs/七牛云区域配置修复说明.md +++ b/platform/docs/七牛云区域配置修复说明.md @@ -1,248 +1,248 @@ -# 七牛云区域配置修复说明 - -## 问题描述 - -上传文件到七牛云时出现区域错误: - -``` -xhr request failed, code: 400 -response: {"error":"incorrect region, please use up-z2.qiniup.com, bucket is: yunzerwebsite"} -``` - -## 问题原因 - -前端代码中硬编码了七牛云区域为 `z0`(华东),但实际数据库配置的是 `z2`(华南): - -```javascript -// 错误的硬编码方式 -const config = { - useCdnDomain: true, - region: qiniu.region.z0, // ❌ 硬编码为华东 -}; -``` - -数据库实际配置: -- `qiniu_region = 'z2'` (华南) -- `qiniu_bucket = 'yunzerwebsite'` - -## 解决方案 - -### 1. 添加区域映射函数 - -在 `platform/src/utils/qiniuUpload.js` 文件末尾添加了 `getQiniuRegion()` 函数: - -```javascript -/** - * 根据区域代码获取七牛云区域对象 - * @param {string} regionCode 区域代码 (z0, z1, z2, na0, as0, cn-east-2) - * @returns {Object} 七牛云区域对象 - */ -function getQiniuRegion(regionCode) { - switch (regionCode) { - case 'z0': - return qiniu.region.z0; // 华东 - case 'z1': - return qiniu.region.z1; // 华北 - case 'z2': - return qiniu.region.z2; // 华南 - case 'na0': - return qiniu.region.na0; // 北美 - case 'as0': - return qiniu.region.as0; // 新加坡 - case 'cn-east-2': - return qiniu.region.cnEast2; // 华东-浙江2 - default: - return qiniu.region.z0; // 默认华东 - } -} -``` - -### 2. 动态获取区域配置 - -修改 `uploadToQiniu()` 函数,从后端返回的数据中读取区域配置: - -```javascript -export async function uploadToQiniu(file, options = {}) { - // 1. 获取上传凭证(包含区域信息) - const tokenRes = await getQiniuToken(); - const { token, keyPrefix, domain, region, uploadUrl } = tokenRes.data; - - // 2. 根据后端返回的区域代码动态获取区域对象 - const qiniuRegion = getQiniuRegion(region); // ✓ 动态获取 - - const config = { - useCdnDomain: true, - region: qiniuRegion, // ✓ 使用正确的区域 - }; - - // 3. 上传文件 - const observable = qiniu.upload(file, key, token, putExtra, config); - // ... -} -``` - -## 工作流程 - -``` -1. 前端调用 getQiniuToken() - ↓ -2. 后端从数据库读取配置 - - storage_type: 'qiniu' - - qiniu_region: 'z2' - - qiniu_bucket: 'yunzerwebsite' - ↓ -3. 后端返回配置信息 - { - token: "...", - region: "z2", ← 区域代码 - bucket: "yunzerwebsite", - uploadUrl: "https://up-z2.qiniup.com" - } - ↓ -4. 前端调用 getQiniuRegion('z2') - 返回: qiniu.region.z2 - ↓ -5. 使用正确的区域上传文件 - 上传地址: https://up-z2.qiniup.com -``` - -## 支持的区域 - -| 区域代码 | 区域名称 | qiniu-js 对象 | 上传地址 | -|---------|---------|--------------|---------| -| z0 | 华东 | qiniu.region.z0 | https://up-z0.qiniup.com | -| z1 | 华北 | qiniu.region.z1 | https://up-z1.qiniup.com | -| z2 | 华南 | qiniu.region.z2 | https://up-z2.qiniup.com | -| na0 | 北美 | qiniu.region.na0 | https://up-na0.qiniup.com | -| as0 | 新加坡 | qiniu.region.as0 | https://up-as0.qiniup.com | -| cn-east-2 | 华东-浙江2 | qiniu.region.cnEast2 | https://up-cn-east-2.qiniup.com | - -## 验证步骤 - -### 1. 确认依赖已安装 - -```bash -cd platform -npm list qiniu-js -``` - -预期输出: -``` -qiniu-js@3.4.4 -``` - -✓ 已确认:`qiniu-js@^3.4.4` 已在 `package.json` 中 - -### 2. 重启后端服务 - -```bash -systemctl restart go-api -systemctl status go-api -``` - -### 3. 测试上传 - -1. 登录系统 -2. 进入软件升级页面 -3. 上传一个文件 -4. 观察是否成功 - -### 4. 检查日志 - -```bash -# 后端日志 -tail -f /www/wwwroot/api.yunzer.cn/go.log - -# 前端控制台 -# 打开浏览器开发者工具,查看 Network 标签 -``` - -预期请求: -``` -GET /platform/storage/config -→ { storageType: 'qiniu', qiniuRegion: 'z2' } - -GET /platform/qiniu/token -→ { token: '...', region: 'z2', uploadUrl: 'https://up-z2.qiniup.com' } - -POST https://up-z2.qiniup.com ← 直接上传到七牛云 -→ { key: '...', hash: '...', size: ... } - -POST /platform/qiniu/save -→ { url: 'http://7colud.yunzer.cn/...', id: 123 } -``` - -## 修复的文件 - -### 前端 -- `platform/src/utils/qiniuUpload.js` - - 添加了 `getQiniuRegion()` 函数 - - 修改了 `uploadToQiniu()` 函数,使用动态区域配置 - -### 后端 -- `go/controllers/qiniu_upload.go` - - `GetUploadToken()` 方法返回 `region` 字段 - - `getQiniuUploadURL()` 函数根据区域返回正确的上传地址 - -## 优势 - -### 1. 灵活性 -- 支持所有七牛云区域 -- 无需修改代码即可切换区域 -- 只需在数据库中修改配置 - -### 2. 可维护性 -- 区域配置集中管理 -- 代码更清晰易懂 -- 便于扩展新区域 - -### 3. 正确性 -- 自动使用正确的上传地址 -- 避免区域不匹配错误 -- 提高上传成功率 - -## 切换区域方法 - -如果需要切换到其他区域,只需修改数据库配置: - -```sql --- 切换到华东 (z0) -UPDATE system_storage_config -SET qiniu_region = 'z0' -WHERE id = 1; - --- 切换到华北 (z1) -UPDATE system_storage_config -SET qiniu_region = 'z1' -WHERE id = 1; - --- 切换到华南 (z2) - 当前配置 -UPDATE system_storage_config -SET qiniu_region = 'z2' -WHERE id = 1; -``` - -前端会自动使用新的区域配置,无需重启或修改代码。 - -## 相关文档 - -- [七牛云直传配置说明](./七牛云直传配置.md) -- [七牛云上传测试步骤](./七牛云上传测试步骤.md) -- [七牛云官方文档 - 存储区域](https://developer.qiniu.com/kodo/1671/region-endpoint-fq) - -## 更新日期 - -2026-04-09 - -## 状态 - -✅ 已完成 -- 添加 `getQiniuRegion()` 函数 -- 修改 `uploadToQiniu()` 使用动态区域 -- 验证 `qiniu-js` 依赖已安装 -- 创建测试文档 - -⏭️ 下一步 -- 重启后端服务 -- 测试文件上传功能 -- 验证区域配置正确 +# 七牛云区域配置修复说明 + +## 问题描述 + +上传文件到七牛云时出现区域错误: + +``` +xhr request failed, code: 400 +response: {"error":"incorrect region, please use up-z2.qiniup.com, bucket is: yunzerwebsite"} +``` + +## 问题原因 + +前端代码中硬编码了七牛云区域为 `z0`(华东),但实际数据库配置的是 `z2`(华南): + +```javascript +// 错误的硬编码方式 +const config = { + useCdnDomain: true, + region: qiniu.region.z0, // ❌ 硬编码为华东 +}; +``` + +数据库实际配置: +- `qiniu_region = 'z2'` (华南) +- `qiniu_bucket = 'yunzerwebsite'` + +## 解决方案 + +### 1. 添加区域映射函数 + +在 `platform/src/utils/qiniuUpload.js` 文件末尾添加了 `getQiniuRegion()` 函数: + +```javascript +/** + * 根据区域代码获取七牛云区域对象 + * @param {string} regionCode 区域代码 (z0, z1, z2, na0, as0, cn-east-2) + * @returns {Object} 七牛云区域对象 + */ +function getQiniuRegion(regionCode) { + switch (regionCode) { + case 'z0': + return qiniu.region.z0; // 华东 + case 'z1': + return qiniu.region.z1; // 华北 + case 'z2': + return qiniu.region.z2; // 华南 + case 'na0': + return qiniu.region.na0; // 北美 + case 'as0': + return qiniu.region.as0; // 新加坡 + case 'cn-east-2': + return qiniu.region.cnEast2; // 华东-浙江2 + default: + return qiniu.region.z0; // 默认华东 + } +} +``` + +### 2. 动态获取区域配置 + +修改 `uploadToQiniu()` 函数,从后端返回的数据中读取区域配置: + +```javascript +export async function uploadToQiniu(file, options = {}) { + // 1. 获取上传凭证(包含区域信息) + const tokenRes = await getQiniuToken(); + const { token, keyPrefix, domain, region, uploadUrl } = tokenRes.data; + + // 2. 根据后端返回的区域代码动态获取区域对象 + const qiniuRegion = getQiniuRegion(region); // ✓ 动态获取 + + const config = { + useCdnDomain: true, + region: qiniuRegion, // ✓ 使用正确的区域 + }; + + // 3. 上传文件 + const observable = qiniu.upload(file, key, token, putExtra, config); + // ... +} +``` + +## 工作流程 + +``` +1. 前端调用 getQiniuToken() + ↓ +2. 后端从数据库读取配置 + - storage_type: 'qiniu' + - qiniu_region: 'z2' + - qiniu_bucket: 'yunzerwebsite' + ↓ +3. 后端返回配置信息 + { + token: "...", + region: "z2", ← 区域代码 + bucket: "yunzerwebsite", + uploadUrl: "https://up-z2.qiniup.com" + } + ↓ +4. 前端调用 getQiniuRegion('z2') + 返回: qiniu.region.z2 + ↓ +5. 使用正确的区域上传文件 + 上传地址: https://up-z2.qiniup.com +``` + +## 支持的区域 + +| 区域代码 | 区域名称 | qiniu-js 对象 | 上传地址 | +|---------|---------|--------------|---------| +| z0 | 华东 | qiniu.region.z0 | https://up-z0.qiniup.com | +| z1 | 华北 | qiniu.region.z1 | https://up-z1.qiniup.com | +| z2 | 华南 | qiniu.region.z2 | https://up-z2.qiniup.com | +| na0 | 北美 | qiniu.region.na0 | https://up-na0.qiniup.com | +| as0 | 新加坡 | qiniu.region.as0 | https://up-as0.qiniup.com | +| cn-east-2 | 华东-浙江2 | qiniu.region.cnEast2 | https://up-cn-east-2.qiniup.com | + +## 验证步骤 + +### 1. 确认依赖已安装 + +```bash +cd platform +npm list qiniu-js +``` + +预期输出: +``` +qiniu-js@3.4.4 +``` + +✓ 已确认:`qiniu-js@^3.4.4` 已在 `package.json` 中 + +### 2. 重启后端服务 + +```bash +systemctl restart go-api +systemctl status go-api +``` + +### 3. 测试上传 + +1. 登录系统 +2. 进入软件升级页面 +3. 上传一个文件 +4. 观察是否成功 + +### 4. 检查日志 + +```bash +# 后端日志 +tail -f /www/wwwroot/api.yunzer.cn/go.log + +# 前端控制台 +# 打开浏览器开发者工具,查看 Network 标签 +``` + +预期请求: +``` +GET /platform/storage/config +→ { storageType: 'qiniu', qiniuRegion: 'z2' } + +GET /platform/qiniu/token +→ { token: '...', region: 'z2', uploadUrl: 'https://up-z2.qiniup.com' } + +POST https://up-z2.qiniup.com ← 直接上传到七牛云 +→ { key: '...', hash: '...', size: ... } + +POST /platform/qiniu/save +→ { url: 'http://7colud.yunzer.cn/...', id: 123 } +``` + +## 修复的文件 + +### 前端 +- `platform/src/utils/qiniuUpload.js` + - 添加了 `getQiniuRegion()` 函数 + - 修改了 `uploadToQiniu()` 函数,使用动态区域配置 + +### 后端 +- `go/controllers/qiniu_upload.go` + - `GetUploadToken()` 方法返回 `region` 字段 + - `getQiniuUploadURL()` 函数根据区域返回正确的上传地址 + +## 优势 + +### 1. 灵活性 +- 支持所有七牛云区域 +- 无需修改代码即可切换区域 +- 只需在数据库中修改配置 + +### 2. 可维护性 +- 区域配置集中管理 +- 代码更清晰易懂 +- 便于扩展新区域 + +### 3. 正确性 +- 自动使用正确的上传地址 +- 避免区域不匹配错误 +- 提高上传成功率 + +## 切换区域方法 + +如果需要切换到其他区域,只需修改数据库配置: + +```sql +-- 切换到华东 (z0) +UPDATE system_storage_config +SET qiniu_region = 'z0' +WHERE id = 1; + +-- 切换到华北 (z1) +UPDATE system_storage_config +SET qiniu_region = 'z1' +WHERE id = 1; + +-- 切换到华南 (z2) - 当前配置 +UPDATE system_storage_config +SET qiniu_region = 'z2' +WHERE id = 1; +``` + +前端会自动使用新的区域配置,无需重启或修改代码。 + +## 相关文档 + +- [七牛云直传配置说明](./七牛云直传配置.md) +- [七牛云上传测试步骤](./七牛云上传测试步骤.md) +- [七牛云官方文档 - 存储区域](https://developer.qiniu.com/kodo/1671/region-endpoint-fq) + +## 更新日期 + +2026-04-09 + +## 状态 + +✅ 已完成 +- 添加 `getQiniuRegion()` 函数 +- 修改 `uploadToQiniu()` 使用动态区域 +- 验证 `qiniu-js` 依赖已安装 +- 创建测试文档 + +⏭️ 下一步 +- 重启后端服务 +- 测试文件上传功能 +- 验证区域配置正确 diff --git a/platform/docs/七牛云区域配置流程图.md b/platform/docs/七牛云区域配置流程图.md index 5da218f..f94da60 100644 --- a/platform/docs/七牛云区域配置流程图.md +++ b/platform/docs/七牛云区域配置流程图.md @@ -1,248 +1,248 @@ -# 七牛云区域配置流程图 - -## 问题:区域不匹配 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 修复前(错误) │ -└─────────────────────────────────────────────────────────────┘ - -前端代码 后端数据库 七牛云服务器 -┌──────────┐ ┌──────────┐ ┌──────────┐ -│ │ │ │ │ │ -│ 硬编码: │ │ 配置: │ │ 实际: │ -│ region: │ │ qiniu_ │ │ bucket │ -│ z0 (华东) │ ────X────> │ region: │ ────X────> │ 在 z2 │ -│ │ 不匹配 │ z2 (华南) │ 不匹配 │ (华南) │ -│ │ │ │ │ │ -└──────────┘ └──────────┘ └──────────┘ - │ - ▼ - ┌──────────┐ - │ 错误: │ - │ incorrect│ - │ region │ - └──────────┘ -``` - -## 解决方案:动态获取区域 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 修复后(正确) │ -└─────────────────────────────────────────────────────────────┘ - -前端 后端 七牛云 -┌──────────┐ ┌──────────┐ ┌──────────┐ -│ │ │ │ │ │ -│ 1. 请求 │ │ 2. 查询 │ │ │ -│ 获取配置 │ ───────> │ 数据库 │ │ │ -│ │ │ │ │ │ -└──────────┘ └──────────┘ └──────────┘ - │ │ - │ ▼ - │ ┌──────────┐ - │ │ 返回: │ - │ <─────────── │ region: │ - │ │ 'z2' │ - │ └──────────┘ - ▼ -┌──────────┐ -│ 3. 调用 │ -│ getQiniu │ -│ Region() │ -└──────────┘ - │ - ▼ -┌──────────┐ ┌──────────┐ ┌──────────┐ -│ 4. 使用 │ │ │ │ │ -│ qiniu. │ │ │ │ 5. 上传 │ -│ region. │ ───────────────────────────────> │ 成功 ✓ │ -│ z2 │ 上传到 up-z2.qiniup.com │ │ -│ │ │ │ -└──────────┘ └──────────┘ -``` - -## 详细流程 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 完整上传流程 │ -└─────────────────────────────────────────────────────────────┘ - -用户操作 前端 后端 七牛云 - │ │ │ │ - │ 1. 点击上传 │ │ │ - ├──────────────────> │ │ │ - │ │ │ │ - │ │ 2. GET /platform/ │ │ - │ │ storage/config │ │ - │ ├─────────────────────>│ │ - │ │ │ │ - │ │ │ 3. 查询数据库 │ - │ │ │ storage_type: │ - │ │ │ 'qiniu' │ - │ │ │ qiniu_region: │ - │ │ │ 'z2' │ - │ │ │ │ - │ │ 4. 返回配置 │ │ - │ │ { storageType: │ │ - │ │ 'qiniu', │ │ - │ │ qiniuRegion: 'z2' }│ │ - │ │<─────────────────────│ │ - │ │ │ │ - │ │ 5. GET /platform/ │ │ - │ │ qiniu/token │ │ - │ ├─────────────────────>│ │ - │ │ │ │ - │ │ │ 6. 生成上传凭证 │ - │ │ │ (使用 AccessKey │ - │ │ │ 和 SecretKey) │ - │ │ │ │ - │ │ 7. 返回 token 和配置 │ │ - │ │ { token: '...', │ │ - │ │ region: 'z2', │ │ - │ │ bucket: '...', │ │ - │ │ uploadUrl: │ │ - │ │ 'up-z2.qiniup.com' } │ - │ │<─────────────────────│ │ - │ │ │ │ - │ │ 8. 调用 │ │ - │ │ getQiniuRegion('z2') │ - │ │ 返回: qiniu.region.z2 │ - │ │ │ │ - │ │ 9. 直接上传文件 │ │ - │ │ (使用 qiniu-js SDK) │ - │ ├──────────────────────────────────────────> │ - │ │ │ │ - │ │ │ │ 10. 接收文件 - │ │ │ │ 存储到 z2 - │ │ │ │ (华南) - │ │ │ │ - │ │ 11. 返回上传结果 │ │ - │ │ { key: '...', │ │ - │ │ hash: '...', │ │ - │ │ size: ... } │ │ - │ │<──────────────────────────────────────────│ - │ │ │ │ - │ │ 12. POST /platform/ │ │ - │ │ qiniu/save │ │ - │ │ { key, hash, size, │ │ - │ │ name, mimeType } │ │ - │ ├─────────────────────>│ │ - │ │ │ │ - │ │ │ 13. 保存到数据库 │ - │ │ │ system_file 表 │ - │ │ │ │ - │ │ 14. 返回文件信息 │ │ - │ │ { url: '...', │ │ - │ │ id: 123, │ │ - │ │ name: '...' } │ │ - │ │<─────────────────────│ │ - │ │ │ │ - │ 15. 显示上传成功 │ │ │ - │<──────────────────│ │ │ - │ │ │ │ -``` - -## 区域映射关系 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ getQiniuRegion() 函数 │ -└─────────────────────────────────────────────────────────────┘ - -数据库配置 函数输入 函数输出 上传地址 -┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ -│ qiniu_ │ │ │ │ │ │ │ -│ region: │ ───> │ 'z0' │ ───> │ qiniu. │ ───> │ up-z0.qiniup.com │ -│ 'z0' │ │ │ │ region.z0│ │ (华东) │ -└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ - -┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ -│ qiniu_ │ │ │ │ │ │ │ -│ region: │ ───> │ 'z1' │ ───> │ qiniu. │ ───> │ up-z1.qiniup.com │ -│ 'z1' │ │ │ │ region.z1│ │ (华北) │ -└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ - -┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ -│ qiniu_ │ │ │ │ │ │ │ -│ region: │ ───> │ 'z2' │ ───> │ qiniu. │ ───> │ up-z2.qiniup.com │ -│ 'z2' │ │ │ │ region.z2│ │ (华南) ✓ 当前 │ -└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ - -┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ -│ qiniu_ │ │ │ │ │ │ │ -│ region: │ ───> │ 'na0' │ ───> │ qiniu. │ ───> │ up-na0.qiniup.com│ -│ 'na0' │ │ │ │ region. │ │ (北美) │ -│ │ │ │ │ na0 │ │ │ -└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ - -┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ -│ qiniu_ │ │ │ │ │ │ │ -│ region: │ ───> │ 'as0' │ ───> │ qiniu. │ ───> │ up-as0.qiniup.com│ -│ 'as0' │ │ │ │ region. │ │ (新加坡) │ -│ │ │ │ │ as0 │ │ │ -└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ - -┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ -│ qiniu_ │ │ │ │ │ │ │ -│ region: │ ───> │'cn-east-2'──────>│ qiniu. │ ───> │up-cn-east-2.qiniup. │ -│'cn-east-2' │ │ │ region. │ │com (华东-浙江2) │ -│ │ │ │ │ cnEast2 │ │ │ -└──────────┘ └──────────┘ └──────────┘ └──────────────────────┘ -``` - -## 代码对比 - -### 修复前(硬编码) - -```javascript -// ❌ 错误:硬编码区域 -const config = { - useCdnDomain: true, - region: qiniu.region.z0, // 总是使用华东 -}; -``` - -### 修复后(动态配置) - -```javascript -// ✓ 正确:动态获取区域 -const { region } = tokenRes.data; // 从后端获取: 'z2' -const qiniuRegion = getQiniuRegion(region); // 转换为: qiniu.region.z2 - -const config = { - useCdnDomain: true, - region: qiniuRegion, // 使用正确的区域 -}; -``` - -## 优势对比 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ 硬编码方式 │ -└─────────────────────────────────────────────────────────────┘ - -缺点: -❌ 区域固定,无法灵活切换 -❌ 修改区域需要改代码、重新部署 -❌ 容易出现区域不匹配错误 -❌ 不同环境需要不同的代码版本 - -┌─────────────────────────────────────────────────────────────┐ -│ 动态配置方式 │ -└─────────────────────────────────────────────────────────────┘ - -优点: -✓ 区域灵活,可随时切换 -✓ 修改区域只需改数据库配置 -✓ 自动使用正确的区域 -✓ 所有环境使用同一套代码 -✓ 支持多租户不同区域配置 -``` - -## 更新日期 - -2026-04-09 +# 七牛云区域配置流程图 + +## 问题:区域不匹配 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 修复前(错误) │ +└─────────────────────────────────────────────────────────────┘ + +前端代码 后端数据库 七牛云服务器 +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ │ │ │ │ │ +│ 硬编码: │ │ 配置: │ │ 实际: │ +│ region: │ │ qiniu_ │ │ bucket │ +│ z0 (华东) │ ────X────> │ region: │ ────X────> │ 在 z2 │ +│ │ 不匹配 │ z2 (华南) │ 不匹配 │ (华南) │ +│ │ │ │ │ │ +└──────────┘ └──────────┘ └──────────┘ + │ + ▼ + ┌──────────┐ + │ 错误: │ + │ incorrect│ + │ region │ + └──────────┘ +``` + +## 解决方案:动态获取区域 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 修复后(正确) │ +└─────────────────────────────────────────────────────────────┘ + +前端 后端 七牛云 +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ │ │ │ │ │ +│ 1. 请求 │ │ 2. 查询 │ │ │ +│ 获取配置 │ ───────> │ 数据库 │ │ │ +│ │ │ │ │ │ +└──────────┘ └──────────┘ └──────────┘ + │ │ + │ ▼ + │ ┌──────────┐ + │ │ 返回: │ + │ <─────────── │ region: │ + │ │ 'z2' │ + │ └──────────┘ + ▼ +┌──────────┐ +│ 3. 调用 │ +│ getQiniu │ +│ Region() │ +└──────────┘ + │ + ▼ +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ 4. 使用 │ │ │ │ │ +│ qiniu. │ │ │ │ 5. 上传 │ +│ region. │ ───────────────────────────────> │ 成功 ✓ │ +│ z2 │ 上传到 up-z2.qiniup.com │ │ +│ │ │ │ +└──────────┘ └──────────┘ +``` + +## 详细流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 完整上传流程 │ +└─────────────────────────────────────────────────────────────┘ + +用户操作 前端 后端 七牛云 + │ │ │ │ + │ 1. 点击上传 │ │ │ + ├──────────────────> │ │ │ + │ │ │ │ + │ │ 2. GET /platform/ │ │ + │ │ storage/config │ │ + │ ├─────────────────────>│ │ + │ │ │ │ + │ │ │ 3. 查询数据库 │ + │ │ │ storage_type: │ + │ │ │ 'qiniu' │ + │ │ │ qiniu_region: │ + │ │ │ 'z2' │ + │ │ │ │ + │ │ 4. 返回配置 │ │ + │ │ { storageType: │ │ + │ │ 'qiniu', │ │ + │ │ qiniuRegion: 'z2' }│ │ + │ │<─────────────────────│ │ + │ │ │ │ + │ │ 5. GET /platform/ │ │ + │ │ qiniu/token │ │ + │ ├─────────────────────>│ │ + │ │ │ │ + │ │ │ 6. 生成上传凭证 │ + │ │ │ (使用 AccessKey │ + │ │ │ 和 SecretKey) │ + │ │ │ │ + │ │ 7. 返回 token 和配置 │ │ + │ │ { token: '...', │ │ + │ │ region: 'z2', │ │ + │ │ bucket: '...', │ │ + │ │ uploadUrl: │ │ + │ │ 'up-z2.qiniup.com' } │ + │ │<─────────────────────│ │ + │ │ │ │ + │ │ 8. 调用 │ │ + │ │ getQiniuRegion('z2') │ + │ │ 返回: qiniu.region.z2 │ + │ │ │ │ + │ │ 9. 直接上传文件 │ │ + │ │ (使用 qiniu-js SDK) │ + │ ├──────────────────────────────────────────> │ + │ │ │ │ + │ │ │ │ 10. 接收文件 + │ │ │ │ 存储到 z2 + │ │ │ │ (华南) + │ │ │ │ + │ │ 11. 返回上传结果 │ │ + │ │ { key: '...', │ │ + │ │ hash: '...', │ │ + │ │ size: ... } │ │ + │ │<──────────────────────────────────────────│ + │ │ │ │ + │ │ 12. POST /platform/ │ │ + │ │ qiniu/save │ │ + │ │ { key, hash, size, │ │ + │ │ name, mimeType } │ │ + │ ├─────────────────────>│ │ + │ │ │ │ + │ │ │ 13. 保存到数据库 │ + │ │ │ system_file 表 │ + │ │ │ │ + │ │ 14. 返回文件信息 │ │ + │ │ { url: '...', │ │ + │ │ id: 123, │ │ + │ │ name: '...' } │ │ + │ │<─────────────────────│ │ + │ │ │ │ + │ 15. 显示上传成功 │ │ │ + │<──────────────────│ │ │ + │ │ │ │ +``` + +## 区域映射关系 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ getQiniuRegion() 函数 │ +└─────────────────────────────────────────────────────────────┘ + +数据库配置 函数输入 函数输出 上传地址 +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ +│ qiniu_ │ │ │ │ │ │ │ +│ region: │ ───> │ 'z0' │ ───> │ qiniu. │ ───> │ up-z0.qiniup.com │ +│ 'z0' │ │ │ │ region.z0│ │ (华东) │ +└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ + +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ +│ qiniu_ │ │ │ │ │ │ │ +│ region: │ ───> │ 'z1' │ ───> │ qiniu. │ ───> │ up-z1.qiniup.com │ +│ 'z1' │ │ │ │ region.z1│ │ (华北) │ +└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ + +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ +│ qiniu_ │ │ │ │ │ │ │ +│ region: │ ───> │ 'z2' │ ───> │ qiniu. │ ───> │ up-z2.qiniup.com │ +│ 'z2' │ │ │ │ region.z2│ │ (华南) ✓ 当前 │ +└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ + +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ +│ qiniu_ │ │ │ │ │ │ │ +│ region: │ ───> │ 'na0' │ ───> │ qiniu. │ ───> │ up-na0.qiniup.com│ +│ 'na0' │ │ │ │ region. │ │ (北美) │ +│ │ │ │ │ na0 │ │ │ +└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ + +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ +│ qiniu_ │ │ │ │ │ │ │ +│ region: │ ───> │ 'as0' │ ───> │ qiniu. │ ───> │ up-as0.qiniup.com│ +│ 'as0' │ │ │ │ region. │ │ (新加坡) │ +│ │ │ │ │ as0 │ │ │ +└──────────┘ └──────────┘ └──────────┘ └──────────────────┘ + +┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ +│ qiniu_ │ │ │ │ │ │ │ +│ region: │ ───> │'cn-east-2'──────>│ qiniu. │ ───> │up-cn-east-2.qiniup. │ +│'cn-east-2' │ │ │ region. │ │com (华东-浙江2) │ +│ │ │ │ │ cnEast2 │ │ │ +└──────────┘ └──────────┘ └──────────┘ └──────────────────────┘ +``` + +## 代码对比 + +### 修复前(硬编码) + +```javascript +// ❌ 错误:硬编码区域 +const config = { + useCdnDomain: true, + region: qiniu.region.z0, // 总是使用华东 +}; +``` + +### 修复后(动态配置) + +```javascript +// ✓ 正确:动态获取区域 +const { region } = tokenRes.data; // 从后端获取: 'z2' +const qiniuRegion = getQiniuRegion(region); // 转换为: qiniu.region.z2 + +const config = { + useCdnDomain: true, + region: qiniuRegion, // 使用正确的区域 +}; +``` + +## 优势对比 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 硬编码方式 │ +└─────────────────────────────────────────────────────────────┘ + +缺点: +❌ 区域固定,无法灵活切换 +❌ 修改区域需要改代码、重新部署 +❌ 容易出现区域不匹配错误 +❌ 不同环境需要不同的代码版本 + +┌─────────────────────────────────────────────────────────────┐ +│ 动态配置方式 │ +└─────────────────────────────────────────────────────────────┘ + +优点: +✓ 区域灵活,可随时切换 +✓ 修改区域只需改数据库配置 +✓ 自动使用正确的区域 +✓ 所有环境使用同一套代码 +✓ 支持多租户不同区域配置 +``` + +## 更新日期 + +2026-04-09 diff --git a/platform/docs/七牛云直传配置.md b/platform/docs/七牛云直传配置.md index a0a631d..015c74a 100644 --- a/platform/docs/七牛云直传配置.md +++ b/platform/docs/七牛云直传配置.md @@ -1,443 +1,443 @@ -# 七牛云直传配置说明 - -## 概述 - -新的上传机制实现了前端直接上传到七牛云,不再通过后端中转,大幅提升大文件上传效率。 - -## 上传流程对比 - -### 旧流程(低效) -``` -前端 → 后端服务器 → 七牛云 - (中转暂存) -``` - -问题: -- 大文件需要先上传到服务器,再由服务器上传到七牛云 -- 占用服务器带宽和磁盘空间 -- 上传时间翻倍 -- 服务器压力大 - -### 新流程(高效) -``` -前端 → 七牛云(直传) -后端 → 数据库(仅保存记录) -``` - -优势: -- 前端直接上传到七牛云,不经过服务器 -- 节省服务器资源 -- 上传速度快 -- 支持断点续传 - -## 安装依赖 - -### 前端安装七牛云 SDK - -```bash -cd platform -npm install qiniu-js -``` - -或使用 yarn: - -```bash -yarn add qiniu-js -``` - -## 后端 API - -### 1. 获取存储配置 - -**接口**: `GET /platform/storage/config` - -**响应**: -```json -{ - "code": 200, - "data": { - "storageType": "qiniu", // 或 "local" - "qiniuDomain": "http://7colud.yunzer.cn", - "qiniuRegion": "z0" - } -} -``` - -### 2. 获取上传凭证 - -**接口**: `GET /platform/qiniu/token` - -**响应**: -```json -{ - "code": 200, - "data": { - "token": "七牛云上传token", - "domain": "http://7colud.yunzer.cn", - "bucket": "your-bucket", - "region": "z0", - "keyPrefix": "2026/04/09/1775722615052606500", - "expires": 1712654400, - "uploadUrl": "https://up-z0.qiniup.com" - } -} -``` - -### 3. 保存文件记录 - -**接口**: `POST /platform/qiniu/save` - -**请求**: -```json -{ - "key": "2026/04/09/1775722615052606500.png", - "hash": "FhGxwBzoLwO_RGws...", - "size": 1024000, - "name": "screenshot.png", - "mimeType": "image/png", - "cate": 0 -} -``` - -**响应**: -```json -{ - "code": 200, - "data": { - "url": "http://7colud.yunzer.cn/2026/04/09/1775722615052606500.png", - "id": 123, - "name": "screenshot.png", - "key": "2026/04/09/1775722615052606500.png" - } -} -``` - -## 前端使用 - -### 基础用法 - -```javascript -import { smartUpload } from '@/utils/qiniuUpload'; - -// 自动选择上传方式(本地或七牛云) -const result = await smartUpload(file, { - cate: 0, // 文件分类 - onProgress: (progress) => { - console.log('上传进度:', progress.percent + '%'); - console.log('已上传:', progress.loaded); - console.log('总大小:', progress.total); - }, -}); - -console.log('上传成功:', result); -// { url: '...', id: 123, name: '...', key: '...' } -``` - -### 在组件中使用 - -```vue - - - -``` - -### 批量上传 - -```javascript -import { batchUpload } from '@/utils/qiniuUpload'; - -const files = [file1, file2, file3]; - -const results = await batchUpload(files, { - cate: 0, - onFileProgress: (file, progress) => { - console.log(`${file.name}: ${progress.percent}%`); - }, - onFileComplete: (file, result) => { - console.log(`${file.name} 上传成功:`, result); - }, - onFileError: (file, error) => { - console.error(`${file.name} 上传失败:`, error); - }, -}); - -console.log('所有文件上传完成:', results); -``` - -## 工作原理 - -### 1. 智能选择上传方式 - -`smartUpload` 函数会自动检测后端配置: - -```javascript -// 1. 获取存储配置 -const config = await getStorageConfig(); - -// 2. 根据配置选择上传方式 -if (config.storageType === 'qiniu') { - // 七牛云直传 - return uploadToQiniu(file, options); -} else { - // 本地上传(通过后端) - return uploadToLocal(file, options); -} -``` - -### 2. 七牛云直传流程 - -```javascript -// 1. 获取上传凭证 -const tokenRes = await getQiniuToken(); -const { token, keyPrefix } = tokenRes.data; - -// 2. 生成文件 key -const key = `${keyPrefix}.${ext}`; - -// 3. 使用七牛云 SDK 直接上传 -const observable = qiniu.upload(file, key, token); - -// 4. 监听上传进度 -observable.subscribe({ - next(res) { - // 进度回调 - onProgress(res.total.percent); - }, - complete(res) { - // 上传完成,保存记录到数据库 - await saveFileRecord({ - key: res.key, - hash: res.hash, - size: file.size, - name: file.name, - }); - }, -}); -``` - -### 3. 本地上传流程 - -```javascript -// 通过后端中转(兼容本地存储) -const formData = new FormData(); -formData.append('file', file); - -const res = await request({ - url: '/platform/uploadfile', - method: 'post', - data: formData, - onUploadProgress: (e) => { - onProgress(e.loaded / e.total * 100); - }, -}); -``` - -## 配置说明 - -### 七牛云区域配置 - -| 区域代码 | 区域名称 | 上传地址 | -|---------|---------|---------| -| z0 | 华东 | https://up-z0.qiniup.com | -| z1 | 华北 | https://up-z1.qiniup.com | -| z2 | 华南 | https://up-z2.qiniup.com | -| na0 | 北美 | https://up-na0.qiniup.com | -| as0 | 新加坡 | https://up-as0.qiniup.com | -| cn-east-2 | 华东-浙江2 | https://up-cn-east-2.qiniup.com | - -### 上传策略配置 - -后端生成 token 时的策略: - -```go -putPolicy := storage.PutPolicy{ - Scope: cfg.QiniuBucket, - ReturnBody: `{"key":"$(key)","hash":"$(etag)","size":$(fsize),"mimeType":"$(mimeType)"}`, - Expires: 3600, // 1小时有效期 -} -``` - -## 安全性 - -### 1. Token 有效期 - -上传 token 有效期为 1 小时,过期后需要重新获取。 - -### 2. 权限验证 - -- 获取 token 需要登录认证 -- 保存文件记录需要登录认证 -- 文件记录关联到当前用户和租户 - -### 3. 文件去重 - -通过 MD5 检查文件是否已存在,避免重复上传。 - -## 性能优化 - -### 1. 断点续传 - -七牛云 SDK 支持断点续传(大文件自动分片): - -```javascript -const config = { - useCdnDomain: true, - region: qiniu.region.z0, - chunkSize: 4, // 分片大小(MB) - concurrentRequestLimit: 3, // 并发上传数 -}; - -const observable = qiniu.upload(file, key, token, putExtra, config); -``` - -### 2. CDN 加速 - -启用 CDN 域名加速上传: - -```javascript -const config = { - useCdnDomain: true, // 使用 CDN 加速 -}; -``` - -### 3. 并发上传 - -批量上传时可以控制并发数: - -```javascript -// 限制同时上传 3 个文件 -const concurrency = 3; -const results = []; - -for (let i = 0; i < files.length; i += concurrency) { - const batch = files.slice(i, i + concurrency); - const batchResults = await Promise.all( - batch.map(file => smartUpload(file, options)) - ); - results.push(...batchResults); -} -``` - -## 故障排查 - -### 1. 上传失败:获取 token 失败 - -**错误**: "当前未配置七牛云存储" - -**解决**: -- 检查数据库存储配置 -- 确认 `storage_type` 为 `'qiniu'` -- 确认七牛云配置完整 - -### 2. 上传失败:token 无效 - -**错误**: "401 Unauthorized" - -**解决**: -- 检查 AccessKey 和 SecretKey 是否正确 -- 检查 token 是否过期(1小时有效期) -- 重新获取 token - -### 3. 上传失败:bucket 不存在 - -**错误**: "no such bucket" - -**解决**: -- 检查 bucket 名称是否正确 -- 检查 bucket 是否在对应区域 -- 登录七牛云控制台确认 - -### 4. 保存记录失败 - -**错误**: "保存文件记录失败" - -**解决**: -- 检查数据库连接 -- 检查文件信息是否完整 -- 查看后端日志 - -## 迁移指南 - -### 从旧版本迁移 - -1. **安装依赖**: -```bash -npm install qiniu-js -``` - -2. **更新导入**: -```javascript -// 旧版本 -import { uploadFile } from '@/api/file'; - -// 新版本 -import { smartUpload } from '@/utils/qiniuUpload'; -``` - -3. **更新上传代码**: -```javascript -// 旧版本 -const formData = new FormData(); -formData.append('file', file); -const res = await uploadFile(formData, { cate: 0 }); - -// 新版本 -const result = await smartUpload(file, { cate: 0 }); -``` - -4. **兼容性**: -- `smartUpload` 会自动检测存储配置 -- 如果配置为本地存储,会自动使用旧的上传方式 -- 无需修改其他代码 - -## 相关文件 - -### 后端 -- `go/controllers/qiniu_upload.go` - 七牛云上传控制器 -- `go/routers/platform/platform.go` - 路由配置 -- `go/services/storage_service.go` - 存储服务 - -### 前端 -- `platform/src/utils/qiniuUpload.js` - 七牛云上传工具 -- `platform/src/views/platform/softwareupgrade/components/edit.vue` - 软件升级组件(示例) - -## 更新日期 - -2026-04-09 +# 七牛云直传配置说明 + +## 概述 + +新的上传机制实现了前端直接上传到七牛云,不再通过后端中转,大幅提升大文件上传效率。 + +## 上传流程对比 + +### 旧流程(低效) +``` +前端 → 后端服务器 → 七牛云 + (中转暂存) +``` + +问题: +- 大文件需要先上传到服务器,再由服务器上传到七牛云 +- 占用服务器带宽和磁盘空间 +- 上传时间翻倍 +- 服务器压力大 + +### 新流程(高效) +``` +前端 → 七牛云(直传) +后端 → 数据库(仅保存记录) +``` + +优势: +- 前端直接上传到七牛云,不经过服务器 +- 节省服务器资源 +- 上传速度快 +- 支持断点续传 + +## 安装依赖 + +### 前端安装七牛云 SDK + +```bash +cd platform +npm install qiniu-js +``` + +或使用 yarn: + +```bash +yarn add qiniu-js +``` + +## 后端 API + +### 1. 获取存储配置 + +**接口**: `GET /platform/storage/config` + +**响应**: +```json +{ + "code": 200, + "data": { + "storageType": "qiniu", // 或 "local" + "qiniuDomain": "http://7colud.yunzer.cn", + "qiniuRegion": "z0" + } +} +``` + +### 2. 获取上传凭证 + +**接口**: `GET /platform/qiniu/token` + +**响应**: +```json +{ + "code": 200, + "data": { + "token": "七牛云上传token", + "domain": "http://7colud.yunzer.cn", + "bucket": "your-bucket", + "region": "z0", + "keyPrefix": "2026/04/09/1775722615052606500", + "expires": 1712654400, + "uploadUrl": "https://up-z0.qiniup.com" + } +} +``` + +### 3. 保存文件记录 + +**接口**: `POST /platform/qiniu/save` + +**请求**: +```json +{ + "key": "2026/04/09/1775722615052606500.png", + "hash": "FhGxwBzoLwO_RGws...", + "size": 1024000, + "name": "screenshot.png", + "mimeType": "image/png", + "cate": 0 +} +``` + +**响应**: +```json +{ + "code": 200, + "data": { + "url": "http://7colud.yunzer.cn/2026/04/09/1775722615052606500.png", + "id": 123, + "name": "screenshot.png", + "key": "2026/04/09/1775722615052606500.png" + } +} +``` + +## 前端使用 + +### 基础用法 + +```javascript +import { smartUpload } from '@/utils/qiniuUpload'; + +// 自动选择上传方式(本地或七牛云) +const result = await smartUpload(file, { + cate: 0, // 文件分类 + onProgress: (progress) => { + console.log('上传进度:', progress.percent + '%'); + console.log('已上传:', progress.loaded); + console.log('总大小:', progress.total); + }, +}); + +console.log('上传成功:', result); +// { url: '...', id: 123, name: '...', key: '...' } +``` + +### 在组件中使用 + +```vue + + + +``` + +### 批量上传 + +```javascript +import { batchUpload } from '@/utils/qiniuUpload'; + +const files = [file1, file2, file3]; + +const results = await batchUpload(files, { + cate: 0, + onFileProgress: (file, progress) => { + console.log(`${file.name}: ${progress.percent}%`); + }, + onFileComplete: (file, result) => { + console.log(`${file.name} 上传成功:`, result); + }, + onFileError: (file, error) => { + console.error(`${file.name} 上传失败:`, error); + }, +}); + +console.log('所有文件上传完成:', results); +``` + +## 工作原理 + +### 1. 智能选择上传方式 + +`smartUpload` 函数会自动检测后端配置: + +```javascript +// 1. 获取存储配置 +const config = await getStorageConfig(); + +// 2. 根据配置选择上传方式 +if (config.storageType === 'qiniu') { + // 七牛云直传 + return uploadToQiniu(file, options); +} else { + // 本地上传(通过后端) + return uploadToLocal(file, options); +} +``` + +### 2. 七牛云直传流程 + +```javascript +// 1. 获取上传凭证 +const tokenRes = await getQiniuToken(); +const { token, keyPrefix } = tokenRes.data; + +// 2. 生成文件 key +const key = `${keyPrefix}.${ext}`; + +// 3. 使用七牛云 SDK 直接上传 +const observable = qiniu.upload(file, key, token); + +// 4. 监听上传进度 +observable.subscribe({ + next(res) { + // 进度回调 + onProgress(res.total.percent); + }, + complete(res) { + // 上传完成,保存记录到数据库 + await saveFileRecord({ + key: res.key, + hash: res.hash, + size: file.size, + name: file.name, + }); + }, +}); +``` + +### 3. 本地上传流程 + +```javascript +// 通过后端中转(兼容本地存储) +const formData = new FormData(); +formData.append('file', file); + +const res = await request({ + url: '/platform/uploadfile', + method: 'post', + data: formData, + onUploadProgress: (e) => { + onProgress(e.loaded / e.total * 100); + }, +}); +``` + +## 配置说明 + +### 七牛云区域配置 + +| 区域代码 | 区域名称 | 上传地址 | +|---------|---------|---------| +| z0 | 华东 | https://up-z0.qiniup.com | +| z1 | 华北 | https://up-z1.qiniup.com | +| z2 | 华南 | https://up-z2.qiniup.com | +| na0 | 北美 | https://up-na0.qiniup.com | +| as0 | 新加坡 | https://up-as0.qiniup.com | +| cn-east-2 | 华东-浙江2 | https://up-cn-east-2.qiniup.com | + +### 上传策略配置 + +后端生成 token 时的策略: + +```go +putPolicy := storage.PutPolicy{ + Scope: cfg.QiniuBucket, + ReturnBody: `{"key":"$(key)","hash":"$(etag)","size":$(fsize),"mimeType":"$(mimeType)"}`, + Expires: 3600, // 1小时有效期 +} +``` + +## 安全性 + +### 1. Token 有效期 + +上传 token 有效期为 1 小时,过期后需要重新获取。 + +### 2. 权限验证 + +- 获取 token 需要登录认证 +- 保存文件记录需要登录认证 +- 文件记录关联到当前用户和租户 + +### 3. 文件去重 + +通过 MD5 检查文件是否已存在,避免重复上传。 + +## 性能优化 + +### 1. 断点续传 + +七牛云 SDK 支持断点续传(大文件自动分片): + +```javascript +const config = { + useCdnDomain: true, + region: qiniu.region.z0, + chunkSize: 4, // 分片大小(MB) + concurrentRequestLimit: 3, // 并发上传数 +}; + +const observable = qiniu.upload(file, key, token, putExtra, config); +``` + +### 2. CDN 加速 + +启用 CDN 域名加速上传: + +```javascript +const config = { + useCdnDomain: true, // 使用 CDN 加速 +}; +``` + +### 3. 并发上传 + +批量上传时可以控制并发数: + +```javascript +// 限制同时上传 3 个文件 +const concurrency = 3; +const results = []; + +for (let i = 0; i < files.length; i += concurrency) { + const batch = files.slice(i, i + concurrency); + const batchResults = await Promise.all( + batch.map(file => smartUpload(file, options)) + ); + results.push(...batchResults); +} +``` + +## 故障排查 + +### 1. 上传失败:获取 token 失败 + +**错误**: "当前未配置七牛云存储" + +**解决**: +- 检查数据库存储配置 +- 确认 `storage_type` 为 `'qiniu'` +- 确认七牛云配置完整 + +### 2. 上传失败:token 无效 + +**错误**: "401 Unauthorized" + +**解决**: +- 检查 AccessKey 和 SecretKey 是否正确 +- 检查 token 是否过期(1小时有效期) +- 重新获取 token + +### 3. 上传失败:bucket 不存在 + +**错误**: "no such bucket" + +**解决**: +- 检查 bucket 名称是否正确 +- 检查 bucket 是否在对应区域 +- 登录七牛云控制台确认 + +### 4. 保存记录失败 + +**错误**: "保存文件记录失败" + +**解决**: +- 检查数据库连接 +- 检查文件信息是否完整 +- 查看后端日志 + +## 迁移指南 + +### 从旧版本迁移 + +1. **安装依赖**: +```bash +npm install qiniu-js +``` + +2. **更新导入**: +```javascript +// 旧版本 +import { uploadFile } from '@/api/file'; + +// 新版本 +import { smartUpload } from '@/utils/qiniuUpload'; +``` + +3. **更新上传代码**: +```javascript +// 旧版本 +const formData = new FormData(); +formData.append('file', file); +const res = await uploadFile(formData, { cate: 0 }); + +// 新版本 +const result = await smartUpload(file, { cate: 0 }); +``` + +4. **兼容性**: +- `smartUpload` 会自动检测存储配置 +- 如果配置为本地存储,会自动使用旧的上传方式 +- 无需修改其他代码 + +## 相关文件 + +### 后端 +- `go/controllers/qiniu_upload.go` - 七牛云上传控制器 +- `go/routers/platform/platform.go` - 路由配置 +- `go/services/storage_service.go` - 存储服务 + +### 前端 +- `platform/src/utils/qiniuUpload.js` - 七牛云上传工具 +- `platform/src/views/platform/softwareupgrade/components/edit.vue` - 软件升级组件(示例) + +## 更新日期 + +2026-04-09 diff --git a/platform/docs/图片URL处理说明.md b/platform/docs/图片URL处理说明.md index 7a9ffd5..0e996f3 100644 --- a/platform/docs/图片URL处理说明.md +++ b/platform/docs/图片URL处理说明.md @@ -1,101 +1,101 @@ -# 图片URL处理说明 - -## 问题描述 - -在使用七牛云存储时,上传的图片URL会出现重复拼接的问题: - -``` -错误: http://localhost:8081http://7cloud.yunzer.cn/2026/04/09/xxx.png -正确: http://7cloud.yunzer.cn/2026/04/09/xxx.png -``` - -## 原因分析 - -1. **本地存储**返回的URL是相对路径:`/uploads/2026/04/09/xxx.png` -2. **七牛云存储**返回的URL是完整URL:`http://7cloud.yunzer.cn/2026/04/09/xxx.png` -3. 前端的 `getFileUrl` 方法会自动拼接 `VITE_API_BASE_URL` -4. 导致七牛云URL被重复拼接 - -## 解决方案 - -### 1. 创建通用工具函数 - -文件:`platform/src/utils/url.js` - -```javascript -export function getFileUrl(url) { - if (!url) return ''; - - // 如果URL已经是完整的URL,直接返回 - if (url.startsWith('http://') || url.startsWith('https://')) { - return url; - } - - // 否则拼接API基础URL - const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; - return `${API_BASE_URL}${url}`; -} -``` - -### 2. 在组件中使用 - -```vue - -``` - -## 使用示例 - -### 本地存储 - -```javascript -const url = '/uploads/2026/04/09/xxx.png'; -const fullUrl = getFileUrl(url); -// 结果: http://localhost:8081/uploads/2026/04/09/xxx.png -``` - -### 七牛云存储 - -```javascript -const url = 'http://7cloud.yunzer.cn/2026/04/09/xxx.png'; -const fullUrl = getFileUrl(url); -// 结果: http://7cloud.yunzer.cn/2026/04/09/xxx.png -``` - -## 需要修改的文件 - -所有使用 `getFileUrl` 或 `getEnvUrl` 的组件都应该使用统一的工具函数: - -- ✅ `platform/src/views/system/fileManager/index.vue` -- ⏳ `platform/src/views/moduleshop/center/index.vue` -- ⏳ `platform/src/views/apps/babyhealth/babys/index.vue` -- ⏳ `platform/src/views/apps/babyhealth/babys/components/edit.vue` -- ⏳ 其他使用图片URL的组件 - -## 最佳实践 - -1. **统一使用工具函数**:不要在组件中重复定义 `getFileUrl` -2. **判断完整URL**:始终检查URL是否已经是完整URL -3. **兼容两种存储**:确保本地存储和七牛云存储都能正常工作 - -## 测试清单 - -- [x] 本地存储图片显示正常 -- [x] 七牛云存储图片显示正常 -- [x] 图片预览功能正常 -- [ ] 视频文件显示正常 -- [ ] 文档下载功能正常 - -## 相关文档 - -- [存储配置功能](../go/docs/README_STORAGE.md) -- [七牛云配置指南](../go/docs/storage-config-guide.md) - ---- - -**修复时间**: 2024-01-01 -**修复人员**: AI Assistant +# 图片URL处理说明 + +## 问题描述 + +在使用七牛云存储时,上传的图片URL会出现重复拼接的问题: + +``` +错误: http://localhost:8081http://7cloud.yunzer.cn/2026/04/09/xxx.png +正确: http://7cloud.yunzer.cn/2026/04/09/xxx.png +``` + +## 原因分析 + +1. **本地存储**返回的URL是相对路径:`/uploads/2026/04/09/xxx.png` +2. **七牛云存储**返回的URL是完整URL:`http://7cloud.yunzer.cn/2026/04/09/xxx.png` +3. 前端的 `getFileUrl` 方法会自动拼接 `VITE_API_BASE_URL` +4. 导致七牛云URL被重复拼接 + +## 解决方案 + +### 1. 创建通用工具函数 + +文件:`platform/src/utils/url.js` + +```javascript +export function getFileUrl(url) { + if (!url) return ''; + + // 如果URL已经是完整的URL,直接返回 + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + + // 否则拼接API基础URL + const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; + return `${API_BASE_URL}${url}`; +} +``` + +### 2. 在组件中使用 + +```vue + +``` + +## 使用示例 + +### 本地存储 + +```javascript +const url = '/uploads/2026/04/09/xxx.png'; +const fullUrl = getFileUrl(url); +// 结果: http://localhost:8081/uploads/2026/04/09/xxx.png +``` + +### 七牛云存储 + +```javascript +const url = 'http://7cloud.yunzer.cn/2026/04/09/xxx.png'; +const fullUrl = getFileUrl(url); +// 结果: http://7cloud.yunzer.cn/2026/04/09/xxx.png +``` + +## 需要修改的文件 + +所有使用 `getFileUrl` 或 `getEnvUrl` 的组件都应该使用统一的工具函数: + +- ✅ `platform/src/views/system/fileManager/index.vue` +- ⏳ `platform/src/views/moduleshop/center/index.vue` +- ⏳ `platform/src/views/apps/babyhealth/babys/index.vue` +- ⏳ `platform/src/views/apps/babyhealth/babys/components/edit.vue` +- ⏳ 其他使用图片URL的组件 + +## 最佳实践 + +1. **统一使用工具函数**:不要在组件中重复定义 `getFileUrl` +2. **判断完整URL**:始终检查URL是否已经是完整URL +3. **兼容两种存储**:确保本地存储和七牛云存储都能正常工作 + +## 测试清单 + +- [x] 本地存储图片显示正常 +- [x] 七牛云存储图片显示正常 +- [x] 图片预览功能正常 +- [ ] 视频文件显示正常 +- [ ] 文档下载功能正常 + +## 相关文档 + +- [存储配置功能](../go/docs/README_STORAGE.md) +- [七牛云配置指南](../go/docs/storage-config-guide.md) + +--- + +**修复时间**: 2024-01-01 +**修复人员**: AI Assistant diff --git a/platform/docs/拼接接口路径.md b/platform/docs/拼接接口路径.md index 8b29f2a..1f31b17 100644 --- a/platform/docs/拼接接口路径.md +++ b/platform/docs/拼接接口路径.md @@ -1,11 +1,11 @@ -//拼接接口路径 -const getEnvUrl = (path: string) => { - const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; - return `${API_BASE_URL}${path}`; -}; - -用例: - - - -const url = getEnvUrl('/platform/moduleCenter/modules'); +//拼接接口路径 +const getEnvUrl = (path: string) => { + const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; + return `${API_BASE_URL}${path}`; +}; + +用例: + + + +const url = getEnvUrl('/platform/moduleCenter/modules'); diff --git a/platform/docs/接口调用.md b/platform/docs/接口调用.md index 0138236..e96bf34 100644 --- a/platform/docs/接口调用.md +++ b/platform/docs/接口调用.md @@ -1,12 +1,12 @@ -import { onMounted } from "vue"; -import { getMenus } from "@/api/menu"; - -onMounted(async () => { - try{ - const response = await getMenus(); - } catch (error) { - console.error('获取菜单数据失败:', error); - } -}); - - +import { onMounted } from "vue"; +import { getMenus } from "@/api/menu"; + +onMounted(async () => { + try{ + const response = await getMenus(); + } catch (error) { + console.error('获取菜单数据失败:', error); + } +}); + + diff --git a/platform/docs/文件上传超时配置.md b/platform/docs/文件上传超时配置.md index 83f90b9..91e4fb2 100644 --- a/platform/docs/文件上传超时配置.md +++ b/platform/docs/文件上传超时配置.md @@ -1,126 +1,126 @@ -# 文件上传超时配置说明 - -## 修改内容 - -移除了文件上传的超时限制,并增加了文件大小限制,允许大文件(如软件安装包)上传完成而不会因超时或大小限制中断。 - -## 前端修改 - -### 修改位置 -`platform/src/api/file.js` 中的 `uploadFile` 函数 - -### 修改详情 - -#### 修改前 -```javascript -const config = { - url: "/platform/uploadfile", - method: "post", - data: formData, - timeout: 2 * 60 * 60 * 1000, // 2小时超时 -}; -``` - -#### 修改后 -```javascript -const config = { - url: "/platform/uploadfile", - method: "post", - data: formData, - timeout: 0, // 不设置超时时间,等待文件上传完毕 -}; -``` - -## 后端修改 - -### 1. 文件大小限制调整 - -#### 修改位置 -`go/controllers/platform_file.go` - -#### 修改详情 - -##### 修改前 -```go -const fileUploadMaxMB = 200 // 200MB -const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 -``` - -##### 修改后 -```go -const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 -const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 -``` - -### 2. 服务器超时配置 - -#### 修改位置 -`go/conf/app.conf` - -#### 新增配置 -```ini -# 服务器超时配置(支持大文件上传) -# 0 表示不设置超时限制 -ServerTimeOut = 0 -# 最大请求体大小(字节),0 表示不限制 -MaxMemory = 0 -``` - -## 影响范围 - -此修改影响所有使用 `uploadFile` 函数的文件上传功能,包括但不限于: - -- 软件升级安装包上传 (`platform/src/views/platform/softwareupgrade/components/edit.vue`) -- 文件管理器上传 (`platform/src/views/system/fileManager/index.vue`) -- 其他调用 `uploadFile` API 的组件 - -## 技术说明 - -### 前端 -- `timeout: 0` 在 axios 中表示不设置超时限制 -- 上传进度仍然会正常显示(通过 `onUploadProgress` 回调) -- 用户可以随时取消上传操作 - -### 后端 -- 文件大小限制从 200MB 提升到 2GB -- `ServerTimeOut = 0` 表示服务器不设置请求超时 -- `MaxMemory = 0` 表示不限制请求体大小 -- 适用于大文件(如软件安装包)的上传场景 - -## CORS 问题说明 - -如果遇到 CORS 错误,请确认: - -1. 后端 `go/routers/router.go` 中已配置 CORS 中间件 -2. 允许的请求头包含 `Authorization` -3. 生产环境中,需要将 `Access-Control-Allow-Origin` 设置为具体的前端域名 - -当前配置: -```go -ctx.Output.Header("Access-Control-Allow-Origin", "*") -ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") -ctx.Output.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") -``` - -## 注意事项 - -1. 修改后需要重启 Go 后端服务才能生效 -2. 对于超大文件(>2GB),建议考虑使用分片上传或断点续传机制 -3. 生产环境建议配置 Nginx 等反向代理的超时和大小限制 -4. 监控服务器磁盘空间,确保有足够空间存储大文件 - -## 相关文件 - -### 前端 -- `platform/src/api/file.js` - 文件上传 API -- `platform/src/utils/request.js` - axios 请求配置 -- `platform/src/views/platform/softwareupgrade/components/edit.vue` - 软件升级上传组件 - -### 后端 -- `go/controllers/platform_file.go` - 文件上传控制器 -- `go/conf/app.conf` - 服务器配置 -- `go/routers/router.go` - CORS 配置 - -## 修改日期 - -2026-04-09 +# 文件上传超时配置说明 + +## 修改内容 + +移除了文件上传的超时限制,并增加了文件大小限制,允许大文件(如软件安装包)上传完成而不会因超时或大小限制中断。 + +## 前端修改 + +### 修改位置 +`platform/src/api/file.js` 中的 `uploadFile` 函数 + +### 修改详情 + +#### 修改前 +```javascript +const config = { + url: "/platform/uploadfile", + method: "post", + data: formData, + timeout: 2 * 60 * 60 * 1000, // 2小时超时 +}; +``` + +#### 修改后 +```javascript +const config = { + url: "/platform/uploadfile", + method: "post", + data: formData, + timeout: 0, // 不设置超时时间,等待文件上传完毕 +}; +``` + +## 后端修改 + +### 1. 文件大小限制调整 + +#### 修改位置 +`go/controllers/platform_file.go` + +#### 修改详情 + +##### 修改前 +```go +const fileUploadMaxMB = 200 // 200MB +const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 +``` + +##### 修改后 +```go +const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 +const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 +``` + +### 2. 服务器超时配置 + +#### 修改位置 +`go/conf/app.conf` + +#### 新增配置 +```ini +# 服务器超时配置(支持大文件上传) +# 0 表示不设置超时限制 +ServerTimeOut = 0 +# 最大请求体大小(字节),0 表示不限制 +MaxMemory = 0 +``` + +## 影响范围 + +此修改影响所有使用 `uploadFile` 函数的文件上传功能,包括但不限于: + +- 软件升级安装包上传 (`platform/src/views/platform/softwareupgrade/components/edit.vue`) +- 文件管理器上传 (`platform/src/views/system/fileManager/index.vue`) +- 其他调用 `uploadFile` API 的组件 + +## 技术说明 + +### 前端 +- `timeout: 0` 在 axios 中表示不设置超时限制 +- 上传进度仍然会正常显示(通过 `onUploadProgress` 回调) +- 用户可以随时取消上传操作 + +### 后端 +- 文件大小限制从 200MB 提升到 2GB +- `ServerTimeOut = 0` 表示服务器不设置请求超时 +- `MaxMemory = 0` 表示不限制请求体大小 +- 适用于大文件(如软件安装包)的上传场景 + +## CORS 问题说明 + +如果遇到 CORS 错误,请确认: + +1. 后端 `go/routers/router.go` 中已配置 CORS 中间件 +2. 允许的请求头包含 `Authorization` +3. 生产环境中,需要将 `Access-Control-Allow-Origin` 设置为具体的前端域名 + +当前配置: +```go +ctx.Output.Header("Access-Control-Allow-Origin", "*") +ctx.Output.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") +ctx.Output.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") +``` + +## 注意事项 + +1. 修改后需要重启 Go 后端服务才能生效 +2. 对于超大文件(>2GB),建议考虑使用分片上传或断点续传机制 +3. 生产环境建议配置 Nginx 等反向代理的超时和大小限制 +4. 监控服务器磁盘空间,确保有足够空间存储大文件 + +## 相关文件 + +### 前端 +- `platform/src/api/file.js` - 文件上传 API +- `platform/src/utils/request.js` - axios 请求配置 +- `platform/src/views/platform/softwareupgrade/components/edit.vue` - 软件升级上传组件 + +### 后端 +- `go/controllers/platform_file.go` - 文件上传控制器 +- `go/conf/app.conf` - 服务器配置 +- `go/routers/router.go` - CORS 配置 + +## 修改日期 + +2026-04-09 diff --git a/platform/docs/获取缓存数据.md b/platform/docs/获取缓存数据.md index ef0e2fb..176e741 100644 --- a/platform/docs/获取缓存数据.md +++ b/platform/docs/获取缓存数据.md @@ -1,23 +1,23 @@ - -import { useAuthStore } from '@/stores/auth'; -import { onMounted } from 'vue'; - -// 使用 auth store 获取用户信息 -const authStore = useAuthStore(); - -// 获取租户ID -const tenantId = (authStore.user as any)?.tid; - -// 获取用户信息 -const userInfo = authStore.user; -if (userInfo && userInfo.id) { - console.log('用户名:', userInfo.username || userInfo.nickname); - console.log('用户ID:', userInfo.id); - console.log('角色:', userInfo.role); -} else { - console.log('未找到用户信息或用户未登录'); -} - -onMounted(() => { - getUserInfo(); + +import { useAuthStore } from '@/stores/auth'; +import { onMounted } from 'vue'; + +// 使用 auth store 获取用户信息 +const authStore = useAuthStore(); + +// 获取租户ID +const tenantId = (authStore.user as any)?.tid; + +// 获取用户信息 +const userInfo = authStore.user; +if (userInfo && userInfo.id) { + console.log('用户名:', userInfo.username || userInfo.nickname); + console.log('用户ID:', userInfo.id); + console.log('角色:', userInfo.role); +} else { + console.log('未找到用户信息或用户未登录'); +} + +onMounted(() => { + getUserInfo(); }); \ No newline at end of file diff --git a/platform/docs/调用图片上传组件.md b/platform/docs/调用图片上传组件.md index 0db1a98..7215b75 100644 --- a/platform/docs/调用图片上传组件.md +++ b/platform/docs/调用图片上传组件.md @@ -1,90 +1,90 @@ - -
- - - - - - - - - Preview Image - - -
- 建议尺寸:250px × 140px -
-
-
- -import { uploadFile } from '@/api/file.js'; -import { ElMessage, ElUpload } from 'element-plus' - -// 上传相关 -const fileList = ref([]) -const dialogVisible = ref(false) -const dialogImageUrl = ref('') - -function beforeImgUpload(file: File) { - const isImage = file.type.startsWith('image/') - const isLt10M = file.size / 1024 / 1024 < 10 - if (!isImage) ElMessage.error('仅支持图片格式') - if (!isLt10M) ElMessage.error('图片大小不能超过10MB') - return isImage && isLt10M -} - -function handleImgUpload(file: File) { - const formData = new FormData() - formData.append('file', file) - formData.append('cate', 'article') - - uploadFile(formData).then((res: any) => { - if (res?.url) { - formData.image = res.url - fileList.value = [{ - name: file.name, - url: res.url - }] - } - }).catch((error: any) => { - ElMessage.error('上传失败:' + (error.msg || '未知错误')) - }) -} - -function handlePictureCardPreview(file: any) { - dialogImageUrl.value = file.url - dialogVisible.value = true -} - -function handleRemove(file: any) { - fileList.value = [] - formData.image = '' -} - - -.uploads{ - display: flex; - flex-direction: column; -} -.upload-tip { - font-size: 12px; - color: #999; + +
+ + + + + + + + + Preview Image + + +
+ 建议尺寸:250px × 140px +
+
+
+ +import { uploadFile } from '@/api/file.js'; +import { ElMessage, ElUpload } from 'element-plus' + +// 上传相关 +const fileList = ref([]) +const dialogVisible = ref(false) +const dialogImageUrl = ref('') + +function beforeImgUpload(file: File) { + const isImage = file.type.startsWith('image/') + const isLt10M = file.size / 1024 / 1024 < 10 + if (!isImage) ElMessage.error('仅支持图片格式') + if (!isLt10M) ElMessage.error('图片大小不能超过10MB') + return isImage && isLt10M +} + +function handleImgUpload(file: File) { + const formData = new FormData() + formData.append('file', file) + formData.append('cate', 'article') + + uploadFile(formData).then((res: any) => { + if (res?.url) { + formData.image = res.url + fileList.value = [{ + name: file.name, + url: res.url + }] + } + }).catch((error: any) => { + ElMessage.error('上传失败:' + (error.msg || '未知错误')) + }) +} + +function handlePictureCardPreview(file: any) { + dialogImageUrl.value = file.url + dialogVisible.value = true +} + +function handleRemove(file: any) { + fileList.value = [] + formData.image = '' +} + + +.uploads{ + display: flex; + flex-direction: column; +} +.upload-tip { + font-size: 12px; + color: #999; } \ No newline at end of file diff --git a/platform/docs/调用字典.md b/platform/docs/调用字典.md index adf787f..1abea5e 100644 --- a/platform/docs/调用字典.md +++ b/platform/docs/调用字典.md @@ -1,27 +1,27 @@ -```` - - +```` + + ```` \ No newline at end of file diff --git a/platform/index.html b/platform/index.html index 78222d2..f4f1421 100644 --- a/platform/index.html +++ b/platform/index.html @@ -1,16 +1,16 @@ - - - - - - - - - - 后台管理系统 - - -
- - - + + + + + + + + + + 后台管理系统 + + +
+ + + diff --git a/platform/package-lock.json b/platform/package-lock.json index 5a2f7cb..ff916a0 100644 --- a/platform/package-lock.json +++ b/platform/package-lock.json @@ -1,4964 +1,4992 @@ -{ - "name": "pc", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "pc", - "version": "0.0.0", - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "@wangeditor/editor": "^5.1.23", - "axios": "^1.13.1", - "chart": "^0.1.2", - "chart.js": "^4.5.1", - "docx-preview": "^0.3.7", - "echarts": "^6.0.0", - "element-plus": "^2.11.7", - "less": "^4.4.2", - "marked": "^16.4.1", - "os": "^0.1.2", - "pinia": "^3.0.3", - "qiniu-js": "^3.4.4", - "vue": "^3.5.22", - "vue-img-cutter": "^3.0.7", - "vue-router": "^4.6.3", - "vue3-pdf-app": "^1.0.3", - "xlsx": "^0.18.5" - }, - "devDependencies": { - "@types/node": "^24.10.7", - "@vitejs/plugin-vue": "^6.0.1", - "typescript": "^5.9.3", - "unplugin-auto-import": "^20.2.0", - "unplugin-vue-components": "^30.0.0", - "vite": "^7.1.7" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs2": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/runtime-corejs2/-/runtime-corejs2-7.29.2.tgz", - "integrity": "sha512-+FqVkbqWaDleqS9fgzFypApKoPvmGFgk5X2lGXbL9wgz6tf88qt2HEUuEn9E3yBeLt7p8pIgODbJ5icVRALKhQ==", - "license": "MIT", - "dependencies": { - "core-js": "^2.6.12" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs2/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true, - "license": "MIT" - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@ctrl/tinycolor": { - "version": "3.6.1", - "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", - "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/@element-plus/icons-vue": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", - "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.4", - "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.4.tgz", - "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.5", - "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.5.tgz", - "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.4", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@intlify/core-base": { - "version": "9.14.4", - "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.4.tgz", - "integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==", - "license": "MIT", - "dependencies": { - "@intlify/message-compiler": "9.14.4", - "@intlify/shared": "9.14.4" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@intlify/message-compiler": { - "version": "9.14.4", - "resolved": "https://registry.npmmirror.com/@intlify/message-compiler/-/message-compiler-9.14.4.tgz", - "integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==", - "license": "MIT", - "dependencies": { - "@intlify/shared": "9.14.4", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@intlify/shared": { - "version": "9.14.4", - "resolved": "https://registry.npmmirror.com/@intlify/shared/-/shared-9.14.4.tgz", - "integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@kurkle/color": { - "version": "0.3.4", - "resolved": "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.4.tgz", - "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", - "license": "MIT" - }, - "node_modules/@popperjs/core": { - "name": "@sxzz/popperjs-es", - "version": "2.11.8", - "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", - "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@transloadit/prettier-bytes": { - "version": "0.0.7", - "resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", - "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==", - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, - "node_modules/@types/node": { - "version": "24.10.14", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.14.tgz", - "integrity": "sha512-OowOUbD1lBCOFIPOZ8xnMIhgqA4sCutMiYOmPHL1PTLt5+y1XA+g2+yC9OOyz8p+deMZqPZLxfMjYIfrKsPeFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/@uppy/companion-client": { - "version": "2.2.2", - "resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz", - "integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==", - "license": "MIT", - "dependencies": { - "@uppy/utils": "^4.1.2", - "namespace-emitter": "^2.0.1" - } - }, - "node_modules/@uppy/core": { - "version": "2.3.4", - "resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz", - "integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==", - "license": "MIT", - "dependencies": { - "@transloadit/prettier-bytes": "0.0.7", - "@uppy/store-default": "^2.1.1", - "@uppy/utils": "^4.1.3", - "lodash.throttle": "^4.1.1", - "mime-match": "^1.0.2", - "namespace-emitter": "^2.0.1", - "nanoid": "^3.1.25", - "preact": "^10.5.13" - } - }, - "node_modules/@uppy/store-default": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz", - "integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==", - "license": "MIT" - }, - "node_modules/@uppy/utils": { - "version": "4.1.3", - "resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz", - "integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==", - "license": "MIT", - "dependencies": { - "lodash.throttle": "^4.1.1" - } - }, - "node_modules/@uppy/xhr-upload": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz", - "integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==", - "license": "MIT", - "dependencies": { - "@uppy/companion-client": "^2.2.2", - "@uppy/utils": "^4.1.2", - "nanoid": "^3.1.25" - }, - "peerDependencies": { - "@uppy/core": "^2.3.3" - } - }, - "node_modules/@vitejs/plugin-vue": { - "version": "6.0.4", - "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", - "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.2" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.29.tgz", - "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/shared": "3.5.29", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", - "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.29", - "@vue/shared": "3.5.29" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", - "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/compiler-core": "3.5.29", - "@vue/compiler-dom": "3.5.29", - "@vue/compiler-ssr": "3.5.29", - "@vue/shared": "3.5.29", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.6", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", - "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.29", - "@vue/shared": "3.5.29" - } - }, - "node_modules/@vue/devtools-api": { - "version": "7.7.9", - "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.9" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.9", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.29.tgz", - "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.29" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.29.tgz", - "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.29", - "@vue/shared": "3.5.29" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", - "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.29", - "@vue/runtime-core": "3.5.29", - "@vue/shared": "3.5.29", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.29.tgz", - "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.29", - "@vue/shared": "3.5.29" - }, - "peerDependencies": { - "vue": "3.5.29" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.29.tgz", - "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", - "license": "MIT" - }, - "node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", - "license": "MIT", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@wangeditor/basic-modules": { - "version": "1.1.7", - "resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz", - "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==", - "license": "MIT", - "dependencies": { - "is-url": "^1.2.4" - }, - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.throttle": "^4.1.1", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/code-highlight": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz", - "integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==", - "license": "MIT", - "dependencies": { - "prismjs": "^1.23.0" - }, - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/core": { - "version": "1.1.19", - "resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz", - "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==", - "license": "MIT", - "dependencies": { - "@types/event-emitter": "^0.3.3", - "event-emitter": "^0.3.5", - "html-void-elements": "^2.0.0", - "i18next": "^20.4.0", - "scroll-into-view-if-needed": "^2.2.28", - "slate-history": "^0.66.0" - }, - "peerDependencies": { - "@uppy/core": "^2.1.1", - "@uppy/xhr-upload": "^2.0.3", - "dom7": "^3.0.0", - "is-hotkey": "^0.2.0", - "lodash.camelcase": "^4.3.0", - "lodash.clonedeep": "^4.5.0", - "lodash.debounce": "^4.0.8", - "lodash.foreach": "^4.5.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "lodash.toarray": "^4.4.0", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/editor": { - "version": "5.1.23", - "resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz", - "integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==", - "license": "MIT", - "dependencies": { - "@uppy/core": "^2.1.1", - "@uppy/xhr-upload": "^2.0.3", - "@wangeditor/basic-modules": "^1.1.7", - "@wangeditor/code-highlight": "^1.0.3", - "@wangeditor/core": "^1.1.19", - "@wangeditor/list-module": "^1.0.5", - "@wangeditor/table-module": "^1.1.4", - "@wangeditor/upload-image-module": "^1.0.2", - "@wangeditor/video-module": "^1.1.4", - "dom7": "^3.0.0", - "is-hotkey": "^0.2.0", - "lodash.camelcase": "^4.3.0", - "lodash.clonedeep": "^4.5.0", - "lodash.debounce": "^4.0.8", - "lodash.foreach": "^4.5.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "lodash.toarray": "^4.4.0", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/list-module": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz", - "integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==", - "license": "MIT", - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/table-module": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz", - "integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==", - "license": "MIT", - "peerDependencies": { - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.isequal": "^4.5.0", - "lodash.throttle": "^4.1.1", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/upload-image-module": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz", - "integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==", - "license": "MIT", - "peerDependencies": { - "@uppy/core": "^2.0.3", - "@uppy/xhr-upload": "^2.0.3", - "@wangeditor/basic-modules": "1.x", - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "lodash.foreach": "^4.5.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/@wangeditor/video-module": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz", - "integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==", - "license": "MIT", - "peerDependencies": { - "@uppy/core": "^2.1.4", - "@uppy/xhr-upload": "^2.0.7", - "@wangeditor/core": "1.x", - "dom7": "^3.0.0", - "nanoid": "^3.2.0", - "slate": "^0.72.0", - "snabbdom": "^3.1.0" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adler-32": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz", - "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/async-validator": { - "version": "4.2.5", - "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", - "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/cfb": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz", - "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/chart": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/chart/-/chart-0.1.2.tgz", - "integrity": "sha512-MSiVzAd3qUEXv54k9KGe1oIoC7WG32W9wtjpovlTGlzo2ue/fRiHf7kJAK1zmD736jH/0fVWNCQLh41btfAEZQ==", - "dependencies": { - "hashish": "", - "hat": "", - "mrcolor": "https://github.com/rook2pawn/mrcolor/archive/master.tar.gz" - } - }, - "node_modules/chart.js": { - "version": "4.5.1", - "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.5.1.tgz", - "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", - "license": "MIT", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/codepage": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz", - "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/color-convert": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-0.2.1.tgz", - "integrity": "sha512-FWbwpCgyRV41Vml0iKU9UmL0dVTKORnm7ZC8h8cdfvutk2bU7ZcMLtSleggScK/IpUVXILg9Pw86LhPUQyTaVg==", - "engines": { - "node": "*" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compute-scroll-into-view": { - "version": "1.0.20", - "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", - "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "license": "MIT", - "dependencies": { - "is-what": "^3.14.1" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/core-js": { - "version": "3.48.0", - "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.48.0.tgz", - "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", - "license": "ISC", - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/docx-preview": { - "version": "0.3.7", - "resolved": "https://registry.npmmirror.com/docx-preview/-/docx-preview-0.3.7.tgz", - "integrity": "sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==", - "license": "Apache-2.0", - "dependencies": { - "jszip": ">=3.0.0" - } - }, - "node_modules/dom7": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz", - "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", - "license": "MIT", - "dependencies": { - "ssr-window": "^3.0.0-alpha.1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/echarts": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", - "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "2.3.0", - "zrender": "6.0.0" - } - }, - "node_modules/element-plus": { - "version": "2.13.2", - "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.2.tgz", - "integrity": "sha512-Zjzm1NnFXGhV4LYZ6Ze9skPlYi2B4KAmN18FL63A3PZcjhDfroHwhtM6RE8BonlOPHXUnPQynH0BgaoEfvhrGw==", - "license": "MIT", - "dependencies": { - "@ctrl/tinycolor": "^3.4.1", - "@element-plus/icons-vue": "^2.3.2", - "@floating-ui/dom": "^1.0.1", - "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", - "@types/lodash": "^4.17.20", - "@types/lodash-es": "^4.17.12", - "@vueuse/core": "^10.11.0", - "async-validator": "^4.2.5", - "dayjs": "^1.11.19", - "lodash": "^4.17.23", - "lodash-es": "^4.17.23", - "lodash-unified": "^1.0.3", - "memoize-one": "^6.0.0", - "normalize-wheel-es": "^1.2.0" - }, - "peerDependencies": { - "vue": "^3.3.0" - } - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "license": "MIT", - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", - "hasInstallScript": true, - "license": "ISC", - "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "license": "ISC", - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", - "license": "MIT", - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ext": { - "version": "1.7.0", - "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", - "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", - "license": "ISC", - "dependencies": { - "type": "^2.7.2" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/frac": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC", - "optional": true - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hashish": { - "version": "0.0.4", - "resolved": "https://registry.npmmirror.com/hashish/-/hashish-0.0.4.tgz", - "integrity": "sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==", - "license": "MIT/X11", - "dependencies": { - "traverse": ">=0.2.4" - }, - "engines": { - "node": "*" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hat": { - "version": "0.0.3", - "resolved": "https://registry.npmmirror.com/hat/-/hat-0.0.3.tgz", - "integrity": "sha512-zpImx2GoKXy42fVDSEad2BPKuSQdLcqsCYa48K3zHSzM/ugWuYjLDr8IXxpVuL7uCLHw56eaiLxCRthhOzf5ug==", - "license": "MIT/X11", - "engines": { - "node": "*" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz", - "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/i18next": { - "version": "20.6.1", - "resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz", - "integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "license": "MIT", - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-hotkey": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz", - "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", - "license": "MIT" - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "license": "MIT" - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "license": "MIT" - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jszip": { - "version": "3.10.1", - "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", - "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "license": "(MIT OR GPL-3.0-or-later)", - "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "setimmediate": "^1.0.5" - } - }, - "node_modules/less": { - "version": "4.5.1", - "resolved": "https://registry.npmmirror.com/less/-/less-4.5.1.tgz", - "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "needle": "^3.1.0", - "source-map": "~0.6.0" - } - }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "license": "MIT" - }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/lodash-unified": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", - "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", - "license": "MIT", - "peerDependencies": { - "@types/lodash-es": "*", - "lodash": "*", - "lodash-es": "*" - } - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "license": "MIT" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", - "license": "MIT" - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", - "license": "MIT" - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT" - }, - "node_modules/lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==", - "license": "MIT" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "license": "MIT", - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", - "license": "MIT" - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-match": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz", - "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", - "license": "ISC", - "dependencies": { - "wildcard": "^1.1.0" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, - "node_modules/mrcolor": { - "version": "0.0.1", - "resolved": "https://github.com/rook2pawn/mrcolor/archive/master.tar.gz", - "integrity": "sha512-feteSepg0FRp0fW3RafigAjU7gXiiaa4OlMW39FEmcvQPbD7Zlpc2PSu4hVBPSBR4XNee8n6EjCTfK0O37DL5A==", - "license": "MIT/X11", - "dependencies": { - "color-convert": "0.2.x" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/namespace-emitter": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz", - "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/needle": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", - "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" - }, - "engines": { - "node": ">= 4.4.x" - } - }, - "node_modules/next-tick": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz", - "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "license": "ISC" - }, - "node_modules/normalize-wheel-es": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", - "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", - "license": "BSD-3-Clause" - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/os": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/os/-/os-0.1.2.tgz", - "integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ==", - "license": "MIT" - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "license": "(MIT AND Zlib)" - }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pinia": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", - "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^7.7.7" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "typescript": ">=4.5.0", - "vue": "^3.5.11" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/preact": { - "version": "10.28.4", - "resolved": "https://registry.npmmirror.com/preact/-/preact-10.28.4.tgz", - "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/prismjs": { - "version": "1.30.0", - "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", - "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "license": "MIT", - "optional": true - }, - "node_modules/qiniu-js": { - "version": "3.4.4", - "resolved": "https://registry.npmmirror.com/qiniu-js/-/qiniu-js-3.4.4.tgz", - "integrity": "sha512-S/ooashZjyFQIIbxrte+OfwRxZQz5/MfVv55xWewc9GoxogP/xMmWixCaBEbdqmyjPAmJ2+VtZiWUuP8teB/BA==", - "license": "MIT", - "dependencies": { - "@babel/runtime-corejs2": "^7.10.2", - "querystring": "^0.2.1", - "spark-md5": "^3.0.0" - } - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, - "node_modules/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmmirror.com/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "license": "MIT", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "optional": true - }, - "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", - "license": "BlueOak-1.0.0", - "optional": true, - "engines": { - "node": ">=11.0.0" - } - }, - "node_modules/scroll-into-view-if-needed": { - "version": "2.2.31", - "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", - "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", - "license": "MIT", - "dependencies": { - "compute-scroll-into-view": "^1.0.20" - } - }, - "node_modules/scule": { - "version": "1.3.0", - "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", - "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "license": "MIT" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slate": { - "version": "0.72.8", - "resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz", - "integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==", - "license": "MIT", - "dependencies": { - "immer": "^9.0.6", - "is-plain-object": "^5.0.0", - "tiny-warning": "^1.0.3" - } - }, - "node_modules/slate-history": { - "version": "0.66.0", - "resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz", - "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^5.0.0" - }, - "peerDependencies": { - "slate": ">=0.65.3" - } - }, - "node_modules/snabbdom": { - "version": "3.6.3", - "resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.3.tgz", - "integrity": "sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==", - "license": "MIT", - "engines": { - "node": ">=12.17.0" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/spark-md5": { - "version": "3.0.2", - "resolved": "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz", - "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", - "license": "(WTFPL OR MIT)" - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssf": { - "version": "0.11.2", - "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz", - "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", - "license": "Apache-2.0", - "dependencies": { - "frac": "~1.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/ssr-window": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz", - "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==", - "license": "MIT" - }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "license": "MIT", - "dependencies": { - "copy-anything": "^4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/superjson/node_modules/copy-anything": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "license": "MIT", - "dependencies": { - "is-what": "^5.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/superjson/node_modules/is-what": { - "version": "5.5.0", - "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/traverse": { - "version": "0.6.11", - "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.6.11.tgz", - "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", - "license": "MIT", - "dependencies": { - "gopd": "^1.2.0", - "typedarray.prototype.slice": "^1.0.5", - "which-typed-array": "^1.1.18" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" - }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray.prototype.slice": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", - "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "math-intrinsics": "^1.1.0", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-offset": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unimport": { - "version": "5.6.0", - "resolved": "https://registry.npmmirror.com/unimport/-/unimport-5.6.0.tgz", - "integrity": "sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "escape-string-regexp": "^5.0.0", - "estree-walker": "^3.0.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.0", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "pkg-types": "^2.3.0", - "scule": "^1.3.0", - "strip-literal": "^3.1.0", - "tinyglobby": "^0.2.15", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin-auto-import": { - "version": "20.3.0", - "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-20.3.0.tgz", - "integrity": "sha512-RcSEQiVv7g0mLMMXibYVKk8mpteKxvyffGuDKqZZiFr7Oq3PB1HwgHdK5O7H4AzbhzHoVKG0NnMnsk/1HIVYzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "picomatch": "^4.0.3", - "unimport": "^5.5.0", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@nuxt/kit": "^4.0.0", - "@vueuse/core": "*" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - }, - "@vueuse/core": { - "optional": true - } - } - }, - "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", - "dev": true, - "license": "MIT", - "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - } - }, - "node_modules/unplugin-vue-components": { - "version": "30.0.0", - "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-30.0.0.tgz", - "integrity": "sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.3", - "debug": "^4.4.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.19", - "mlly": "^1.8.0", - "tinyglobby": "^0.2.15", - "unplugin": "^2.3.10", - "unplugin-utils": "^0.3.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@babel/parser": "^7.15.8", - "@nuxt/kit": "^3.2.2 || ^4.0.0", - "vue": "2 || 3" - }, - "peerDependenciesMeta": { - "@babel/parser": { - "optional": true - }, - "@nuxt/kit": { - "optional": true - } - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vue": { - "version": "3.5.29", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.29.tgz", - "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.29", - "@vue/compiler-sfc": "3.5.29", - "@vue/runtime-dom": "3.5.29", - "@vue/server-renderer": "3.5.29", - "@vue/shared": "3.5.29" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-i18n": { - "version": "9.14.4", - "resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-9.14.4.tgz", - "integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==", - "license": "MIT", - "dependencies": { - "@intlify/core-base": "9.14.4", - "@intlify/shared": "9.14.4", - "@vue/devtools-api": "^6.5.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/vue-i18n/node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" - }, - "node_modules/vue-img-cutter": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/vue-img-cutter/-/vue-img-cutter-3.0.7.tgz", - "integrity": "sha512-fNw3kimawg9XVXDZCw2bI74NI+Jq+H42wjymatZVVSY46wuBty6LbQsu4GeVfo/yzpS9AHY0tzckpYzX3D2fmA==", - "license": "MIT", - "dependencies": { - "core-js": "^3.20.3", - "vue": "^3.2.29", - "vue-i18n": "^9.1.10" - } - }, - "node_modules/vue-router": { - "version": "4.6.4", - "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", - "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", - "license": "MIT", - "dependencies": { - "@vue/devtools-api": "^6.6.4" - }, - "funding": { - "url": "https://github.com/sponsors/posva" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/vue-router/node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" - }, - "node_modules/vue3-pdf-app": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/vue3-pdf-app/-/vue3-pdf-app-1.0.3.tgz", - "integrity": "sha512-qegWTIF4wYKiocZ3KreB70wRXhqSdXWbdERDyyKzT7d5PbjKbS9tD6vaKkCqh3PzTM84NyKPYrQ3iuwJb60YPQ==", - "license": "MIT", - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wildcard": { - "version": "1.1.2", - "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz", - "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==", - "license": "MIT" - }, - "node_modules/wmf": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz", - "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz", - "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/xlsx": { - "version": "0.18.5", - "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", - "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "cfb": "~1.2.1", - "codepage": "~1.15.0", - "crc-32": "~1.2.1", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.3.0" - }, - "bin": { - "xlsx": "bin/xlsx.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/zrender": { - "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", - "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", - "license": "BSD-3-Clause", - "dependencies": { - "tslib": "2.3.0" - } - } - } -} +{ + "name": "pc", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pc", + "version": "0.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@wangeditor/editor": "^5.1.23", + "axios": "^1.13.1", + "chart": "^0.1.2", + "chart.js": "^4.5.1", + "docx-preview": "^0.3.7", + "echarts": "^6.0.0", + "element-plus": "^2.11.7", + "less": "^4.4.2", + "marked": "^16.4.1", + "os": "^0.1.2", + "pinia": "^3.0.3", + "qiniu-js": "^3.4.4", + "vue": "^3.5.22", + "vue-img-cutter": "^3.0.7", + "vue-router": "^4.6.3", + "vue3-pdf-app": "^1.0.3", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/node": "^24.10.7", + "@vitejs/plugin-vue": "^6.0.1", + "typescript": "^5.9.3", + "unplugin-auto-import": "^20.2.0", + "unplugin-vue-components": "^30.0.0", + "vite": "^7.1.7" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs2": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/runtime-corejs2/-/runtime-corejs2-7.29.2.tgz", + "integrity": "sha512-+FqVkbqWaDleqS9fgzFypApKoPvmGFgk5X2lGXbL9wgz6tf88qt2HEUuEn9E3yBeLt7p8pIgODbJ5icVRALKhQ==", + "license": "MIT", + "dependencies": { + "core-js": "^2.6.12" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs2/node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@intlify/core-base": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz", + "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "9.14.5", + "@intlify/shared": "9.14.5" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz", + "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "9.14.5", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", + "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@transloadit/prettier-bytes": { + "version": "0.0.7", + "resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", + "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==", + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.14", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.14.tgz", + "integrity": "sha512-OowOUbD1lBCOFIPOZ8xnMIhgqA4sCutMiYOmPHL1PTLt5+y1XA+g2+yC9OOyz8p+deMZqPZLxfMjYIfrKsPeFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@uppy/companion-client": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz", + "integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==", + "license": "MIT", + "dependencies": { + "@uppy/utils": "^4.1.2", + "namespace-emitter": "^2.0.1" + } + }, + "node_modules/@uppy/core": { + "version": "2.3.4", + "resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz", + "integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==", + "license": "MIT", + "dependencies": { + "@transloadit/prettier-bytes": "0.0.7", + "@uppy/store-default": "^2.1.1", + "@uppy/utils": "^4.1.3", + "lodash.throttle": "^4.1.1", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "nanoid": "^3.1.25", + "preact": "^10.5.13" + } + }, + "node_modules/@uppy/store-default": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz", + "integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ==", + "license": "MIT" + }, + "node_modules/@uppy/utils": { + "version": "4.1.3", + "resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz", + "integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==", + "license": "MIT", + "dependencies": { + "lodash.throttle": "^4.1.1" + } + }, + "node_modules/@uppy/xhr-upload": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz", + "integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==", + "license": "MIT", + "dependencies": { + "@uppy/companion-client": "^2.2.2", + "@uppy/utils": "^4.1.2", + "nanoid": "^3.1.25" + }, + "peerDependencies": { + "@uppy/core": "^2.3.3" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", + "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.29.tgz", + "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.29", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", + "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", + "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.29", + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", + "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.29.tgz", + "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.29.tgz", + "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", + "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.29", + "@vue/runtime-core": "3.5.29", + "@vue/shared": "3.5.29", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.29.tgz", + "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29" + }, + "peerDependencies": { + "vue": "3.5.29" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.29.tgz", + "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@wangeditor/basic-modules": { + "version": "1.1.7", + "resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz", + "integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==", + "license": "MIT", + "dependencies": { + "is-url": "^1.2.4" + }, + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.throttle": "^4.1.1", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/code-highlight": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz", + "integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.23.0" + }, + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/core": { + "version": "1.1.19", + "resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz", + "integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==", + "license": "MIT", + "dependencies": { + "@types/event-emitter": "^0.3.3", + "event-emitter": "^0.3.5", + "html-void-elements": "^2.0.0", + "i18next": "^20.4.0", + "scroll-into-view-if-needed": "^2.2.28", + "slate-history": "^0.66.0" + }, + "peerDependencies": { + "@uppy/core": "^2.1.1", + "@uppy/xhr-upload": "^2.0.3", + "dom7": "^3.0.0", + "is-hotkey": "^0.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.foreach": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "lodash.toarray": "^4.4.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/editor": { + "version": "5.1.23", + "resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz", + "integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==", + "license": "MIT", + "dependencies": { + "@uppy/core": "^2.1.1", + "@uppy/xhr-upload": "^2.0.3", + "@wangeditor/basic-modules": "^1.1.7", + "@wangeditor/code-highlight": "^1.0.3", + "@wangeditor/core": "^1.1.19", + "@wangeditor/list-module": "^1.0.5", + "@wangeditor/table-module": "^1.1.4", + "@wangeditor/upload-image-module": "^1.0.2", + "@wangeditor/video-module": "^1.1.4", + "dom7": "^3.0.0", + "is-hotkey": "^0.2.0", + "lodash.camelcase": "^4.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.debounce": "^4.0.8", + "lodash.foreach": "^4.5.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "lodash.toarray": "^4.4.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/list-module": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz", + "integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==", + "license": "MIT", + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/table-module": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz", + "integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==", + "license": "MIT", + "peerDependencies": { + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.isequal": "^4.5.0", + "lodash.throttle": "^4.1.1", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/upload-image-module": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz", + "integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==", + "license": "MIT", + "peerDependencies": { + "@uppy/core": "^2.0.3", + "@uppy/xhr-upload": "^2.0.3", + "@wangeditor/basic-modules": "1.x", + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "lodash.foreach": "^4.5.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/@wangeditor/video-module": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz", + "integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==", + "license": "MIT", + "peerDependencies": { + "@uppy/core": "^2.1.4", + "@uppy/xhr-upload": "^2.0.7", + "@wangeditor/core": "1.x", + "dom7": "^3.0.0", + "nanoid": "^3.2.0", + "slate": "^0.72.0", + "snabbdom": "^3.1.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chart": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/chart/-/chart-0.1.2.tgz", + "integrity": "sha512-MSiVzAd3qUEXv54k9KGe1oIoC7WG32W9wtjpovlTGlzo2ue/fRiHf7kJAK1zmD736jH/0fVWNCQLh41btfAEZQ==", + "dependencies": { + "hashish": "", + "hat": "", + "mrcolor": "https://github.com/rook2pawn/mrcolor/archive/master.tar.gz" + } + }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-0.2.1.tgz", + "integrity": "sha512-FWbwpCgyRV41Vml0iKU9UmL0dVTKORnm7ZC8h8cdfvutk2bU7ZcMLtSleggScK/IpUVXILg9Pw86LhPUQyTaVg==", + "engines": { + "node": "*" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "license": "MIT", + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/docx-preview": { + "version": "0.3.7", + "resolved": "https://registry.npmmirror.com/docx-preview/-/docx-preview-0.3.7.tgz", + "integrity": "sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==", + "license": "Apache-2.0", + "dependencies": { + "jszip": ">=3.0.0" + } + }, + "node_modules/dom7": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz", + "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", + "license": "MIT", + "dependencies": { + "ssr-window": "^3.0.0-alpha.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/element-plus": { + "version": "2.13.2", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.2.tgz", + "integrity": "sha512-Zjzm1NnFXGhV4LYZ6Ze9skPlYi2B4KAmN18FL63A3PZcjhDfroHwhtM6RE8BonlOPHXUnPQynH0BgaoEfvhrGw==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "^10.11.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hashish": { + "version": "0.0.4", + "resolved": "https://registry.npmmirror.com/hashish/-/hashish-0.0.4.tgz", + "integrity": "sha512-xyD4XgslstNAs72ENaoFvgMwtv8xhiDtC2AtzCG+8yF7W/Knxxm9BX+e2s25mm+HxMKh0rBmXVOEGF3zNImXvA==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.2.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hat": { + "version": "0.0.3", + "resolved": "https://registry.npmmirror.com/hat/-/hat-0.0.3.tgz", + "integrity": "sha512-zpImx2GoKXy42fVDSEad2BPKuSQdLcqsCYa48K3zHSzM/ugWuYjLDr8IXxpVuL7uCLHw56eaiLxCRthhOzf5ug==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/i18next": { + "version": "20.6.1", + "resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz", + "integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "license": "MIT", + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-hotkey": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz", + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", + "license": "MIT" + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmmirror.com/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/less": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/less/-/less-4.5.1.tgz", + "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "license": "MIT", + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==", + "license": "ISC", + "dependencies": { + "wildcard": "^1.1.0" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mrcolor": { + "version": "0.0.1", + "resolved": "https://github.com/rook2pawn/mrcolor/archive/master.tar.gz", + "integrity": "sha512-feteSepg0FRp0fW3RafigAjU7gXiiaa4OlMW39FEmcvQPbD7Zlpc2PSu4hVBPSBR4XNee8n6EjCTfK0O37DL5A==", + "license": "MIT/X11", + "dependencies": { + "color-convert": "0.2.x" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/os": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/os/-/os-0.1.2.tgz", + "integrity": "sha512-ZoXJkvAnljwvc56MbvhtKVWmSkzV712k42Is2mA0+0KTSRakq5XXuXpjZjgAt9ctzl51ojhQWakQQpmOvXWfjQ==", + "license": "MIT" + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.28.4", + "resolved": "https://registry.npmmirror.com/preact/-/preact-10.28.4.tgz", + "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "license": "MIT", + "optional": true + }, + "node_modules/qiniu-js": { + "version": "3.4.4", + "resolved": "https://registry.npmmirror.com/qiniu-js/-/qiniu-js-3.4.4.tgz", + "integrity": "sha512-S/ooashZjyFQIIbxrte+OfwRxZQz5/MfVv55xWewc9GoxogP/xMmWixCaBEbdqmyjPAmJ2+VtZiWUuP8teB/BA==", + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs2": "^7.10.2", + "querystring": "^0.2.1", + "spark-md5": "^3.0.0" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmmirror.com/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "license": "MIT", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmmirror.com/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/slate": { + "version": "0.72.8", + "resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz", + "integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==", + "license": "MIT", + "dependencies": { + "immer": "^9.0.6", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" + } + }, + "node_modules/slate-history": { + "version": "0.66.0", + "resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz", + "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^5.0.0" + }, + "peerDependencies": { + "slate": ">=0.65.3" + } + }, + "node_modules/snabbdom": { + "version": "3.6.3", + "resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.6.3.tgz", + "integrity": "sha512-W2lHLLw2qR2Vv0DcMmcxXqcfdBaIcoN+y/86SmHv8fn4DazEQSH6KN3TjZcWvwujW56OHiiirsbHWZb4vx/0fg==", + "license": "MIT", + "engines": { + "node": ">=12.17.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ssr-window": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz", + "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==", + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/superjson/node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/superjson/node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/traverse": { + "version": "0.6.11", + "resolved": "https://registry.npmmirror.com/traverse/-/traverse-0.6.11.tgz", + "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", + "license": "MIT", + "dependencies": { + "gopd": "^1.2.0", + "typedarray.prototype.slice": "^1.0.5", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmmirror.com/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray.prototype.slice": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", + "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "math-intrinsics": "^1.1.0", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-offset": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unimport": { + "version": "5.6.0", + "resolved": "https://registry.npmmirror.com/unimport/-/unimport-5.6.0.tgz", + "integrity": "sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "mlly": "^1.8.0", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "pkg-types": "^2.3.0", + "scule": "^1.3.0", + "strip-literal": "^3.1.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.11", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "20.3.0", + "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-20.3.0.tgz", + "integrity": "sha512-RcSEQiVv7g0mLMMXibYVKk8mpteKxvyffGuDKqZZiFr7Oq3PB1HwgHdK5O7H4AzbhzHoVKG0NnMnsk/1HIVYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "local-pkg": "^1.1.2", + "magic-string": "^0.30.21", + "picomatch": "^4.0.3", + "unimport": "^5.5.0", + "unplugin": "^2.3.11", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^4.0.0", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-vue-components": { + "version": "30.0.0", + "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-30.0.0.tgz", + "integrity": "sha512-4qVE/lwCgmdPTp6h0qsRN2u642tt4boBQtcpn4wQcWZAsr8TQwq+SPT3NDu/6kBFxzo/sSEK4ioXhOOBrXc3iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.3", + "debug": "^4.4.3", + "local-pkg": "^1.1.2", + "magic-string": "^0.30.19", + "mlly": "^1.8.0", + "tinyglobby": "^0.2.15", + "unplugin": "^2.3.10", + "unplugin-utils": "^0.3.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2 || ^4.0.0", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", + "integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.29", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.29.tgz", + "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-sfc": "3.5.29", + "@vue/runtime-dom": "3.5.29", + "@vue/server-renderer": "3.5.29", + "@vue/shared": "3.5.29" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-i18n": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz", + "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==", + "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "9.14.5", + "@intlify/shared": "9.14.5", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-i18n/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-img-cutter": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/vue-img-cutter/-/vue-img-cutter-3.0.7.tgz", + "integrity": "sha512-fNw3kimawg9XVXDZCw2bI74NI+Jq+H42wjymatZVVSY46wuBty6LbQsu4GeVfo/yzpS9AHY0tzckpYzX3D2fmA==", + "license": "MIT", + "dependencies": { + "core-js": "^3.20.3", + "vue": "^3.2.29", + "vue-i18n": "^9.1.10" + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue3-pdf-app": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/vue3-pdf-app/-/vue3-pdf-app-1.0.3.tgz", + "integrity": "sha512-qegWTIF4wYKiocZ3KreB70wRXhqSdXWbdERDyyKzT7d5PbjKbS9tD6vaKkCqh3PzTM84NyKPYrQ3iuwJb60YPQ==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==", + "license": "MIT" + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/platform/package.json b/platform/package.json index 5a25383..5603932 100644 --- a/platform/package.json +++ b/platform/package.json @@ -1,40 +1,40 @@ -{ - "name": "pc", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite --open", - "clean": "node scripts/clean-dist.mjs", - "build": "node --max-old-space-size=4096 ./node_modules/vite/bin/vite.js build", - "preview": "vite preview" - }, - "dependencies": { - "@element-plus/icons-vue": "^2.3.2", - "@wangeditor/editor": "^5.1.23", - "axios": "^1.13.1", - "chart": "^0.1.2", - "chart.js": "^4.5.1", - "docx-preview": "^0.3.7", - "echarts": "^6.0.0", - "element-plus": "^2.11.7", - "less": "^4.4.2", - "marked": "^16.4.1", - "os": "^0.1.2", - "pinia": "^3.0.3", - "qiniu-js": "^3.4.4", - "vue": "^3.5.22", - "vue-img-cutter": "^3.0.7", - "vue-router": "^4.6.3", - "vue3-pdf-app": "^1.0.3", - "xlsx": "^0.18.5" - }, - "devDependencies": { - "@types/node": "^24.10.7", - "@vitejs/plugin-vue": "^6.0.1", - "typescript": "^5.9.3", - "unplugin-auto-import": "^20.2.0", - "unplugin-vue-components": "^30.0.0", - "vite": "^7.1.7" - } -} +{ + "name": "pc", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --open", + "clean": "node scripts/clean-dist.mjs", + "build": "node --max-old-space-size=4096 ./node_modules/vite/bin/vite.js build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@wangeditor/editor": "^5.1.23", + "axios": "^1.13.1", + "chart": "^0.1.2", + "chart.js": "^4.5.1", + "docx-preview": "^0.3.7", + "echarts": "^6.0.0", + "element-plus": "^2.11.7", + "less": "^4.4.2", + "marked": "^16.4.1", + "os": "^0.1.2", + "pinia": "^3.0.3", + "qiniu-js": "^3.4.4", + "vue": "^3.5.22", + "vue-img-cutter": "^3.0.7", + "vue-router": "^4.6.3", + "vue3-pdf-app": "^1.0.3", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/node": "^24.10.7", + "@vitejs/plugin-vue": "^6.0.1", + "typescript": "^5.9.3", + "unplugin-auto-import": "^20.2.0", + "unplugin-vue-components": "^30.0.0", + "vite": "^7.1.7" + } +} diff --git a/platform/scripts/clean-dist.mjs b/platform/scripts/clean-dist.mjs index 6a20079..36c171d 100644 --- a/platform/scripts/clean-dist.mjs +++ b/platform/scripts/clean-dist.mjs @@ -1,49 +1,49 @@ -/** - * 构建前删除 dist,带重试。缓解 Windows 上 Vite emptyDir 的 EPERM(文件被资源管理器预览、杀毒、vite preview 等占用)。 - */ -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const root = path.resolve(__dirname, ".."); -const dirs = [path.join(root, "dist"), path.join(root, "output")]; - -const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - -async function rmDirWithRetry(dir) { - for (let i = 0; i < 10; i++) { - if (!fs.existsSync(dir)) { - return true; - } - try { - fs.rmSync(dir, { recursive: true, force: true }); - return true; - } catch (e) { - const code = e && typeof e === "object" && "code" in e ? e.code : ""; - const retryable = code === "EPERM" || code === "EBUSY" || code === "ENOTEMPTY"; - if (!retryable || i === 9) { - console.error(`[clean-dist] 无法删除 ${path.relative(root, dir) || dir}:`, e instanceof Error ? e.message : e); - return false; - } - await sleep(350 * (i + 1)); - } - } - return false; -} - -async function main() { - let ok = true; - for (const dir of dirs) { - const r = await rmDirWithRetry(dir); - if (!r) ok = false; - } - if (!ok) { - console.error( - "请关闭占用上述目录的程序:资源管理器预览、vite preview、IDE、杀毒实时扫描等后执行 npm run clean。" - ); - process.exit(1); - } -} - -await main(); +/** + * 构建前删除 dist,带重试。缓解 Windows 上 Vite emptyDir 的 EPERM(文件被资源管理器预览、杀毒、vite preview 等占用)。 + */ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, ".."); +const dirs = [path.join(root, "dist"), path.join(root, "output")]; + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +async function rmDirWithRetry(dir) { + for (let i = 0; i < 10; i++) { + if (!fs.existsSync(dir)) { + return true; + } + try { + fs.rmSync(dir, { recursive: true, force: true }); + return true; + } catch (e) { + const code = e && typeof e === "object" && "code" in e ? e.code : ""; + const retryable = code === "EPERM" || code === "EBUSY" || code === "ENOTEMPTY"; + if (!retryable || i === 9) { + console.error(`[clean-dist] 无法删除 ${path.relative(root, dir) || dir}:`, e instanceof Error ? e.message : e); + return false; + } + await sleep(350 * (i + 1)); + } + } + return false; +} + +async function main() { + let ok = true; + for (const dir of dirs) { + const r = await rmDirWithRetry(dir); + if (!r) ok = false; + } + if (!ok) { + console.error( + "请关闭占用上述目录的程序:资源管理器预览、vite preview、IDE、杀毒实时扫描等后执行 npm run clean。" + ); + process.exit(1); + } +} + +await main(); diff --git a/platform/src/App.vue b/platform/src/App.vue index 8e6b25f..6c5c720 100644 --- a/platform/src/App.vue +++ b/platform/src/App.vue @@ -1,17 +1,17 @@ - - - - - + + + + + diff --git a/platform/src/api/accountPool.js b/platform/src/api/accountPool.js index bf874a3..7015e00 100644 --- a/platform/src/api/accountPool.js +++ b/platform/src/api/accountPool.js @@ -1,101 +1,101 @@ -import request from '@/utils/request'; - -function base(module) { - return `/platform/accountPool/${module}`; -} - -export function getAccountPoolList(module, params) { - return request({ - url: `${base(module)}/list`, - method: 'get', - params, - }); -} - -export function addAccountPool(module, data) { - return request({ - url: `${base(module)}/add`, - method: 'post', - data, - }); -} - -export function batchAddAccountPool(module, rows) { - return request({ - url: `${base(module)}/batchAdd`, - method: 'post', - data: { rows }, - }); -} - -export function getAccountPoolDetail(module, id) { - return request({ - url: `${base(module)}/detail/${id}`, - method: 'get', - }); -} - -export function extractAccountPool(module, data) { - return request({ - url: `${base(module)}/extract`, - method: 'post', - data, - }); -} - -export function updateAccountPoolRemark(module, data) { - return request({ - url: `${base(module)}/updateRemark`, - method: 'post', - data, - }); -} - -export function setAccountPoolUnavailable(module, data) { - return request({ - url: `${base(module)}/setUnavailable`, - method: 'post', - data, - }); -} - -export function updateAccountPoolUsable(module, data) { - return request({ - url: `${base(module)}/updateUsable`, - method: 'post', - data, - }); -} - -export function updateAccountPoolPlatform(module, data) { - return request({ - url: `${base(module)}/updatePlatform`, - method: 'post', - data, - }); -} - -export function unextractAccountPool(module, data) { - return request({ - url: `${base(module)}/unextract`, - method: 'post', - data, - }); -} - -export function replenishAccountPool(module, data) { - return request({ - url: `${base(module)}/replenish`, - method: 'post', - data, - }); -} - -/** 使用厂商 Token 探测是否可用(服务端转发)。Cursor 传 { id, accessToken }(会话 JWT)以便回写 is_used;仅传 accessToken 也可探测但不更新库;Windsurf/Kiro 传 { id } */ -export function probeAccountPoolToken(module, data) { - return request({ - url: `${base(module)}/probeToken`, - method: 'post', - data, - }); -} +import request from '@/utils/request'; + +function base(module) { + return `/platform/accountPool/${module}`; +} + +export function getAccountPoolList(module, params) { + return request({ + url: `${base(module)}/list`, + method: 'get', + params, + }); +} + +export function addAccountPool(module, data) { + return request({ + url: `${base(module)}/add`, + method: 'post', + data, + }); +} + +export function batchAddAccountPool(module, rows) { + return request({ + url: `${base(module)}/batchAdd`, + method: 'post', + data: { rows }, + }); +} + +export function getAccountPoolDetail(module, id) { + return request({ + url: `${base(module)}/detail/${id}`, + method: 'get', + }); +} + +export function extractAccountPool(module, data) { + return request({ + url: `${base(module)}/extract`, + method: 'post', + data, + }); +} + +export function updateAccountPoolRemark(module, data) { + return request({ + url: `${base(module)}/updateRemark`, + method: 'post', + data, + }); +} + +export function setAccountPoolUnavailable(module, data) { + return request({ + url: `${base(module)}/setUnavailable`, + method: 'post', + data, + }); +} + +export function updateAccountPoolUsable(module, data) { + return request({ + url: `${base(module)}/updateUsable`, + method: 'post', + data, + }); +} + +export function updateAccountPoolPlatform(module, data) { + return request({ + url: `${base(module)}/updatePlatform`, + method: 'post', + data, + }); +} + +export function unextractAccountPool(module, data) { + return request({ + url: `${base(module)}/unextract`, + method: 'post', + data, + }); +} + +export function replenishAccountPool(module, data) { + return request({ + url: `${base(module)}/replenish`, + method: 'post', + data, + }); +} + +/** 使用厂商 Token 探测是否可用(服务端转发)。Cursor 传 { id, accessToken }(会话 JWT)以便回写 is_used;仅传 accessToken 也可探测但不更新库;Windsurf/Kiro 传 { id } */ +export function probeAccountPoolToken(module, data) { + return request({ + url: `${base(module)}/probeToken`, + method: 'post', + data, + }); +} diff --git a/platform/src/api/analytics.js b/platform/src/api/analytics.js index a22c3f7..797fe8e 100644 --- a/platform/src/api/analytics.js +++ b/platform/src/api/analytics.js @@ -1,18 +1,18 @@ -// 数据统计相关API -import request from "@/utils/request"; - -// 获取内容统计 -export function getContentStats() { - return request({ - url: "/platform/contentstats", - method: "get", - }); -} - -// 获取用户统计 -export function getUserStats() { - return request({ - url: "/platform/usersstats", - method: "get", - }); +// 数据统计相关API +import request from "@/utils/request"; + +// 获取内容统计 +export function getContentStats() { + return request({ + url: "/platform/contentstats", + method: "get", + }); +} + +// 获取用户统计 +export function getUserStats() { + return request({ + url: "/platform/usersstats", + method: "get", + }); } \ No newline at end of file diff --git a/platform/src/api/article.js b/platform/src/api/article.js index a0eca89..c5f69b6 100644 --- a/platform/src/api/article.js +++ b/platform/src/api/article.js @@ -1,170 +1,170 @@ -// 文章管理相关API -import request from "@/utils/request"; - -// 获取文章列表 -export function listArticles(params) { - return request({ - url: `/platform/articlesList`, - method: "get", - params, - }); -} - -// 获取文章所有文章 -export function listAllArticles(params) { - return request({ - url: `/platform/allarticles`, - method: "get", - params, - }); -} - -// 获取文章详情 -export function getArticle(id) { - return request({ - url: `/platform/articles/${id}`, - method: "get", - }); -} - -// 创建文章 -export function createArticle(data) { - return request({ - url: '/platform/createarticle', - method: 'post', - data, - }); -} - -// 编辑文章 -export function editArticle(id, data) { - return request({ - url: `/platform/editarticle/${id}`, - method: 'post', - data, - }); -} - -// 删除文章 -export function deleteArticle(id) { - return request({ - url: `/platform/deletearticle/${id}`, - method: "delete", - }); -} - -// 发布文章 -export function publishArticle(id,uid) { - return request({ - url: `/platform/publisharticle/${id}`, - method: 'post', - data: { - uid - } - }); -} - -// 下架文章 -export function unPublishArticle(id) { - return request({ - url: `/platform/unPublisharticle/${id}`, - method: 'post' - }); -} - -// 文章推荐 -export function articleRecommend(id) { - return request({ - url: `/platform/articleRecommend/${id}`, - method: 'post' - }); -} - -// 取消文章推荐 -export function unArticleRecommend(id) { - return request({ - url: `/platform/unArticleRecommend/${id}`, - method: 'post' - }); -} - -// 文章置顶 -export function articleTop(id) { - return request({ - url: `/platform/articleTop/${id}`, - method: 'post' - }); -} - -// 取消文章置顶 -export function unArticleTop(id) { - return request({ - url: `/platform/unArticleTop/${id}`, - method: 'post' - }); -} - - - -////////////////////////////分类相关//////////////////////////// - -// 获取所有分类列表 -export function allCategories(params) { - return request({ - url: `/platform/allcategories`, - method: "get", - params, - }); -} - -// 获取分类列表 -export function listCategories(params) { - return request({ - url: `/platform/categories`, - method: "get", - params, - }); -} - -// 获取分类详情 -export function getCategory(id) { - return request({ - url: `/platform/categories/${id}`, - method: "get", - }); -} - -// 创建分类 -export function createCategory(data) { - return request({ - url: `/platform/createCategory`, - method: "post", - data, - }); -} - -// 更新分类 -export function editCategory(id, data) { - return request({ - url: `/platform/editCategory/${id}`, - method: "post", - data, - }); -} - -// 删除分类 -export function deleteCategory(id) { - return request({ - url: `/platform/categories/${id}`, - method: "delete", - }); -} - -// 更新分类状态 -export function updateCategoryStatus(id, status) { - return request({ - url: `/platform/categories/${id}/status`, - method: "patch", - data: { status }, - }); +// 文章管理相关API +import request from "@/utils/request"; + +// 获取文章列表 +export function listArticles(params) { + return request({ + url: `/platform/articlesList`, + method: "get", + params, + }); +} + +// 获取文章所有文章 +export function listAllArticles(params) { + return request({ + url: `/platform/allarticles`, + method: "get", + params, + }); +} + +// 获取文章详情 +export function getArticle(id) { + return request({ + url: `/platform/articles/${id}`, + method: "get", + }); +} + +// 创建文章 +export function createArticle(data) { + return request({ + url: '/platform/createarticle', + method: 'post', + data, + }); +} + +// 编辑文章 +export function editArticle(id, data) { + return request({ + url: `/platform/editarticle/${id}`, + method: 'post', + data, + }); +} + +// 删除文章 +export function deleteArticle(id) { + return request({ + url: `/platform/deletearticle/${id}`, + method: "delete", + }); +} + +// 发布文章 +export function publishArticle(id,uid) { + return request({ + url: `/platform/publisharticle/${id}`, + method: 'post', + data: { + uid + } + }); +} + +// 下架文章 +export function unPublishArticle(id) { + return request({ + url: `/platform/unPublisharticle/${id}`, + method: 'post' + }); +} + +// 文章推荐 +export function articleRecommend(id) { + return request({ + url: `/platform/articleRecommend/${id}`, + method: 'post' + }); +} + +// 取消文章推荐 +export function unArticleRecommend(id) { + return request({ + url: `/platform/unArticleRecommend/${id}`, + method: 'post' + }); +} + +// 文章置顶 +export function articleTop(id) { + return request({ + url: `/platform/articleTop/${id}`, + method: 'post' + }); +} + +// 取消文章置顶 +export function unArticleTop(id) { + return request({ + url: `/platform/unArticleTop/${id}`, + method: 'post' + }); +} + + + +////////////////////////////分类相关//////////////////////////// + +// 获取所有分类列表 +export function allCategories(params) { + return request({ + url: `/platform/allcategories`, + method: "get", + params, + }); +} + +// 获取分类列表 +export function listCategories(params) { + return request({ + url: `/platform/categories`, + method: "get", + params, + }); +} + +// 获取分类详情 +export function getCategory(id) { + return request({ + url: `/platform/categories/${id}`, + method: "get", + }); +} + +// 创建分类 +export function createCategory(data) { + return request({ + url: `/platform/createCategory`, + method: "post", + data, + }); +} + +// 更新分类 +export function editCategory(id, data) { + return request({ + url: `/platform/editCategory/${id}`, + method: "post", + data, + }); +} + +// 删除分类 +export function deleteCategory(id) { + return request({ + url: `/platform/categories/${id}`, + method: "delete", + }); +} + +// 更新分类状态 +export function updateCategoryStatus(id, status) { + return request({ + url: `/platform/categories/${id}/status`, + method: "patch", + data: { status }, + }); } \ No newline at end of file diff --git a/platform/src/api/babyhealth.js b/platform/src/api/babyhealth.js index 0ea110a..df6262c 100644 --- a/platform/src/api/babyhealth.js +++ b/platform/src/api/babyhealth.js @@ -1,179 +1,179 @@ -import request from "@/utils/request"; - -/************************************************* - ****************** 宝贝相关接口 ****************** - *************************************************/ - -/** - * 获取宝贝列表 - * @returns {Promise} - */ -export function getBabyList() { - return request({ - url: '/platform/babys/list', - method: 'get' - }); -} - -/** - * 获取宝贝详情 - * @param {number} id 宝贝ID - * @returns {Promise} - */ -export function getBabyDetail(id) { - return request({ - url: `/platform/babys/${id}`, - method: "get", - }); -} - -/** - * 创建宝贝数据 - * @param {Object} data 宝贝数据 - * @returns {Promise} - */ -export function createBaby(data) { - return request({ - url: "/platform/babys", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -// 更新宝贝信息 -export function editBaby(id, data) { - return request({ - url: `/platform/baby/update/${id}`, - method: 'post', - data: data - }); -} - -/** - * 删除宝贝数据 - * @param {number} id 宝贝ID - * @returns {Promise} - */ -export function deleteBaby(id) { - return request({ - url: `/platform/babys/${id}`, - method: "delete", - }); -} - -/** - * 绑定父母 - * @param {number} id 宝贝ID - * @param {Object} data 绑定数据 - * @returns {Promise} - */ -export function bindParent(id, data) { - return request({ - url: `/platform/babys/bindparents/${id}`, - method: "post", - data: data, - }); -} - -/** - * 获取父母 - * @param {number} id 宝贝ID - * @returns {Promise} - */ -export function getParents(id) { - return request({ - url: `/platform/babys/getParents/${id}`, - method: "get", - }); -} - -/************************************************* - ****************** 用户相关接口 ****************** - *************************************************/ - -/** - * 获取用户列表 - * @returns {Promise} - */ -export function getUserList() { - return request({ - url: "/platform/babyhealthUser/list", - method: "get", - }); -} - -/** - * 获取用户详情 - * @param {number} id 用户ID - * @returns {Promise} - */ -export function getUserDetail(id) { - return request({ - url: `/platform/babyhealthUser/detail/${id}`, - method: "get", - }); -} - -/** - * 创建用户数据 - * @param {Object} data 用户数据 - * @returns {Promise} - */ -export function createUser(data) { - return request({ - url: "/platform/babyhealthUser/create", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 更新用户数据 - * @param {number} id 用户ID - * @param {Object} data 更新的数据 - * @returns {Promise} - */ -export function updateUser(id, data) { - return request({ - url: `/platform/babyhealthUser/update/${id}`, - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 删除用户数据 - * @param {number} id 用户ID - * @returns {Promise} - */ -export function deleteUser(id) { - return request({ - url: `/platform/babyhealthUser/delete/${id}`, - method: "delete", - }); -} - - -/************************************************* - ****************** 仪表盘相关接口 ****************** - *************************************************/ - -/** - * dashborad总体输出 - * @returns {Promise} - */ -export function getDashborad() { - return request({ - url: "/platform/babyhealthDashborad/dashborad", - method: "get", - }); +import request from "@/utils/request"; + +/************************************************* + ****************** 宝贝相关接口 ****************** + *************************************************/ + +/** + * 获取宝贝列表 + * @returns {Promise} + */ +export function getBabyList() { + return request({ + url: '/platform/babys/list', + method: 'get' + }); +} + +/** + * 获取宝贝详情 + * @param {number} id 宝贝ID + * @returns {Promise} + */ +export function getBabyDetail(id) { + return request({ + url: `/platform/babys/${id}`, + method: "get", + }); +} + +/** + * 创建宝贝数据 + * @param {Object} data 宝贝数据 + * @returns {Promise} + */ +export function createBaby(data) { + return request({ + url: "/platform/babys", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +// 更新宝贝信息 +export function editBaby(id, data) { + return request({ + url: `/platform/baby/update/${id}`, + method: 'post', + data: data + }); +} + +/** + * 删除宝贝数据 + * @param {number} id 宝贝ID + * @returns {Promise} + */ +export function deleteBaby(id) { + return request({ + url: `/platform/babys/${id}`, + method: "delete", + }); +} + +/** + * 绑定父母 + * @param {number} id 宝贝ID + * @param {Object} data 绑定数据 + * @returns {Promise} + */ +export function bindParent(id, data) { + return request({ + url: `/platform/babys/bindparents/${id}`, + method: "post", + data: data, + }); +} + +/** + * 获取父母 + * @param {number} id 宝贝ID + * @returns {Promise} + */ +export function getParents(id) { + return request({ + url: `/platform/babys/getParents/${id}`, + method: "get", + }); +} + +/************************************************* + ****************** 用户相关接口 ****************** + *************************************************/ + +/** + * 获取用户列表 + * @returns {Promise} + */ +export function getUserList() { + return request({ + url: "/platform/babyhealthUser/list", + method: "get", + }); +} + +/** + * 获取用户详情 + * @param {number} id 用户ID + * @returns {Promise} + */ +export function getUserDetail(id) { + return request({ + url: `/platform/babyhealthUser/detail/${id}`, + method: "get", + }); +} + +/** + * 创建用户数据 + * @param {Object} data 用户数据 + * @returns {Promise} + */ +export function createUser(data) { + return request({ + url: "/platform/babyhealthUser/create", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 更新用户数据 + * @param {number} id 用户ID + * @param {Object} data 更新的数据 + * @returns {Promise} + */ +export function updateUser(id, data) { + return request({ + url: `/platform/babyhealthUser/update/${id}`, + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 删除用户数据 + * @param {number} id 用户ID + * @returns {Promise} + */ +export function deleteUser(id) { + return request({ + url: `/platform/babyhealthUser/delete/${id}`, + method: "delete", + }); +} + + +/************************************************* + ****************** 仪表盘相关接口 ****************** + *************************************************/ + +/** + * dashborad总体输出 + * @returns {Promise} + */ +export function getDashborad() { + return request({ + url: "/platform/babyhealthDashborad/dashborad", + method: "get", + }); } \ No newline at end of file diff --git a/platform/src/api/banner.js b/platform/src/api/banner.js index 3b6303a..bf28d2a 100644 --- a/platform/src/api/banner.js +++ b/platform/src/api/banner.js @@ -1,55 +1,55 @@ -import request from "@/utils/request"; - -/** - * 获取所有Banner - * @returns {Promise} - */ -export function getBanners() { - return request({ - url: "/platform/allbanners", - method: "get", - }); -} - -/** - * 创建Banner - * @param {Object} bannerData Banner数据 - * @returns {Promise} - */ -export function createBanner(formData) { - return request({ - url: "/platform/createbanner", - method: "post", - data: formData, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 编辑Banner - * @param {number|string} id Banner ID - * @param {Object} bannerData 更新的数据 - * @returns {Promise} - */ -export function editBanner(id, bannerData) { - return request({ - url: `/platform/editbanner/${id}`, - method: "post", - data: bannerData, - }); -} - -/** - * 删除Banner - * @param {number|string} id Banner ID - * @returns {Promise} - */ -export function deleteBanner(id) { - return request({ - url: `/platform/deletebanner/${id}`, - method: "delete", - }); -} - +import request from "@/utils/request"; + +/** + * 获取所有Banner + * @returns {Promise} + */ +export function getBanners() { + return request({ + url: "/platform/allbanners", + method: "get", + }); +} + +/** + * 创建Banner + * @param {Object} bannerData Banner数据 + * @returns {Promise} + */ +export function createBanner(formData) { + return request({ + url: "/platform/createbanner", + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 编辑Banner + * @param {number|string} id Banner ID + * @param {Object} bannerData 更新的数据 + * @returns {Promise} + */ +export function editBanner(id, bannerData) { + return request({ + url: `/platform/editbanner/${id}`, + method: "post", + data: bannerData, + }); +} + +/** + * 删除Banner + * @param {number|string} id Banner ID + * @returns {Promise} + */ +export function deleteBanner(id) { + return request({ + url: `/platform/deletebanner/${id}`, + method: "delete", + }); +} + diff --git a/platform/src/api/complaint.js b/platform/src/api/complaint.js index acc40ef..3a7901a 100644 --- a/platform/src/api/complaint.js +++ b/platform/src/api/complaint.js @@ -1,78 +1,78 @@ -import request from "@/utils/request"; - -/** 投诉建议列表 */ -export function getComplaintList(params) { - return request({ - url: "/platform/complaint/list", - method: "get", - params, - }); -} - -export function getComplaintDetail(id) { - return request({ - url: `/platform/complaint/${id}`, - method: "get", - }); -} - -export function createComplaint(data) { - return request({ - url: "/platform/complaint", - method: "post", - data, - }); -} - -export function updateComplaint(id, data) { - return request({ - url: `/platform/complaint/${id}`, - method: "post", - data, - }); -} - -export function deleteComplaint(id) { - return request({ - url: `/platform/complaint/${id}`, - method: "delete", - }); -} - -/** 产品分类(投诉建议用) */ -export function getComplaintCategoryList() { - return request({ - url: "/platform/complaintCategory/list", - method: "get", - }); -} - -export function getComplaintCategorySelect() { - return request({ - url: "/platform/complaintCategory/select", - method: "get", - }); -} - -export function createComplaintCategory(data) { - return request({ - url: "/platform/complaintCategory", - method: "post", - data, - }); -} - -export function updateComplaintCategory(id, data) { - return request({ - url: `/platform/complaintCategory/${id}`, - method: "post", - data, - }); -} - -export function deleteComplaintCategory(id) { - return request({ - url: `/platform/complaintCategory/${id}`, - method: "delete", - }); -} +import request from "@/utils/request"; + +/** 投诉建议列表 */ +export function getComplaintList(params) { + return request({ + url: "/platform/complaint/list", + method: "get", + params, + }); +} + +export function getComplaintDetail(id) { + return request({ + url: `/platform/complaint/${id}`, + method: "get", + }); +} + +export function createComplaint(data) { + return request({ + url: "/platform/complaint", + method: "post", + data, + }); +} + +export function updateComplaint(id, data) { + return request({ + url: `/platform/complaint/${id}`, + method: "post", + data, + }); +} + +export function deleteComplaint(id) { + return request({ + url: `/platform/complaint/${id}`, + method: "delete", + }); +} + +/** 产品分类(投诉建议用) */ +export function getComplaintCategoryList() { + return request({ + url: "/platform/complaintCategory/list", + method: "get", + }); +} + +export function getComplaintCategorySelect() { + return request({ + url: "/platform/complaintCategory/select", + method: "get", + }); +} + +export function createComplaintCategory(data) { + return request({ + url: "/platform/complaintCategory", + method: "post", + data, + }); +} + +export function updateComplaintCategory(id, data) { + return request({ + url: `/platform/complaintCategory/${id}`, + method: "post", + data, + }); +} + +export function deleteComplaintCategory(id) { + return request({ + url: `/platform/complaintCategory/${id}`, + method: "delete", + }); +} diff --git a/platform/src/api/contact.js b/platform/src/api/contact.js index 31125c4..6ccb5d8 100644 --- a/platform/src/api/contact.js +++ b/platform/src/api/contact.js @@ -1,33 +1,33 @@ -import request from '@/utils/request' - -export function listContacts(params) { - return request({ - url: '/platform/crm/contact/list', - method: 'get', - params, - }) -} - -export function createContact(data) { - return request({ - url: '/platform/crm/contact/add', - method: 'post', - data, - }) -} - -export function updateContact(data) { - return request({ - url: '/platform/crm/contact/edit', - method: 'post', - data, - }) -} - -export function deleteContact(data) { - return request({ - url: '/platform/crm/contact/delete', - method: 'post', - data, - }) -} +import request from '@/utils/request' + +export function listContacts(params) { + return request({ + url: '/platform/crm/contact/list', + method: 'get', + params, + }) +} + +export function createContact(data) { + return request({ + url: '/platform/crm/contact/add', + method: 'post', + data, + }) +} + +export function updateContact(data) { + return request({ + url: '/platform/crm/contact/edit', + method: 'post', + data, + }) +} + +export function deleteContact(data) { + return request({ + url: '/platform/crm/contact/delete', + method: 'post', + data, + }) +} diff --git a/platform/src/api/cursorActivationCode.ts b/platform/src/api/cursorActivationCode.ts index 83deb7e..15d653e 100644 --- a/platform/src/api/cursorActivationCode.ts +++ b/platform/src/api/cursorActivationCode.ts @@ -1,106 +1,106 @@ -// @ts-ignore request 封装是 JS 文件,项目未提供 TS 声明 -import request from '@/utils/request'; - -const baseUrl = '/platform/cursor/activationcode'; - -export interface CursorActivationCodeQuery { - page?: number; - pageSize?: number; - keyword?: string; - status?: number | string; - type?: number | string; - bindStatus?: number | string; -} - -export interface CursorActivationCodePayload { - id?: number | string; - code?: string; - type?: number; - status?: number; - durationDays?: number; - bindAccount?: string; - bindDeviceId?: number | string; - ownerUserId?: number | string; - ownerUserName?: string; - activatedAt?: string; - expiredAt?: string; - remark?: string; -} - -export interface GenerateActivationCodePayload { - count: number; - type?: number; - durationDays?: number; - ownerUserId?: number | string; - ownerUserName?: string; - remark?: string; -} - -export function getCursorActivationCodeList(params: CursorActivationCodeQuery) { - return request({ - url: `${baseUrl}/list`, - method: 'get', - params, - }); -} - -export function getCursorActivationCodeDetail(id: number | string) { - return request({ - url: `${baseUrl}/detail/${id}`, - method: 'get', - }); -} - -export function addCursorActivationCode(data: CursorActivationCodePayload) { - return request({ - url: `${baseUrl}/add`, - method: 'post', - data, - }); -} - -export function updateCursorActivationCode(data: CursorActivationCodePayload) { - return request({ - url: `${baseUrl}/update`, - method: 'post', - data, - }); -} - -export function deleteCursorActivationCode(id: number | string) { - return request({ - url: `${baseUrl}/delete/${id}`, - method: 'post', - }); -} - -export function generateCursorActivationCode(data: GenerateActivationCodePayload) { - return request({ - url: `${baseUrl}/generate`, - method: 'post', - data, - }); -} - -export function enableCursorActivationCode(id: number | string) { - return request({ - url: `${baseUrl}/enable/${id}`, - method: 'post', - }); -} - -export function disableCursorActivationCode(id: number | string) { - return request({ - url: `${baseUrl}/disable/${id}`, - method: 'post', - }); -} - -export function exportCursorActivationCode(params: CursorActivationCodeQuery) { - return request({ - url: `${baseUrl}/export`, - method: 'get', - params, - responseType: 'blob', - }); -} +// @ts-ignore request 封装是 JS 文件,项目未提供 TS 声明 +import request from '@/utils/request'; + +const baseUrl = '/platform/cursor/activationcode'; + +export interface CursorActivationCodeQuery { + page?: number; + pageSize?: number; + keyword?: string; + status?: number | string; + type?: number | string; + bindStatus?: number | string; +} + +export interface CursorActivationCodePayload { + id?: number | string; + code?: string; + type?: number; + status?: number; + durationDays?: number; + bindAccount?: string; + bindDeviceId?: number | string; + ownerUserId?: number | string; + ownerUserName?: string; + activatedAt?: string; + expiredAt?: string; + remark?: string; +} + +export interface GenerateActivationCodePayload { + count: number; + type?: number; + durationDays?: number; + ownerUserId?: number | string; + ownerUserName?: string; + remark?: string; +} + +export function getCursorActivationCodeList(params: CursorActivationCodeQuery) { + return request({ + url: `${baseUrl}/list`, + method: 'get', + params, + }); +} + +export function getCursorActivationCodeDetail(id: number | string) { + return request({ + url: `${baseUrl}/detail/${id}`, + method: 'get', + }); +} + +export function addCursorActivationCode(data: CursorActivationCodePayload) { + return request({ + url: `${baseUrl}/add`, + method: 'post', + data, + }); +} + +export function updateCursorActivationCode(data: CursorActivationCodePayload) { + return request({ + url: `${baseUrl}/update`, + method: 'post', + data, + }); +} + +export function deleteCursorActivationCode(id: number | string) { + return request({ + url: `${baseUrl}/delete/${id}`, + method: 'post', + }); +} + +export function generateCursorActivationCode(data: GenerateActivationCodePayload) { + return request({ + url: `${baseUrl}/generate`, + method: 'post', + data, + }); +} + +export function enableCursorActivationCode(id: number | string) { + return request({ + url: `${baseUrl}/enable/${id}`, + method: 'post', + }); +} + +export function disableCursorActivationCode(id: number | string) { + return request({ + url: `${baseUrl}/disable/${id}`, + method: 'post', + }); +} + +export function exportCursorActivationCode(params: CursorActivationCodeQuery) { + return request({ + url: `${baseUrl}/export`, + method: 'get', + params, + responseType: 'blob', + }); +} diff --git a/platform/src/api/cursorEquipment.js b/platform/src/api/cursorEquipment.js index 30c208f..ba98cf5 100644 --- a/platform/src/api/cursorEquipment.js +++ b/platform/src/api/cursorEquipment.js @@ -1,73 +1,73 @@ -import request from '@/utils/request'; - -const baseUrl = '/platform/cursor/equipment'; - -export function getCursorEquipmentList(params) { - return request({ - url: `${baseUrl}/list`, - method: 'get', - params, - }); -} - -export function getCursorEquipmentDetail(id) { - return request({ - url: `${baseUrl}/detail/${id}`, - method: 'get', - }); -} - -export function addCursorEquipment(data) { - return request({ - url: `${baseUrl}/add`, - method: 'post', - data, - }); -} - -export function updateCursorEquipment(data) { - return request({ - url: `${baseUrl}/update`, - method: 'post', - data, - }); -} - -export function deleteCursorEquipment(id) { - return request({ - url: `${baseUrl}/delete/${id}`, - method: 'post', - }); -} - -export function activateCursorEquipment(data) { - return request({ - url: `${baseUrl}/activate`, - method: 'post', - data, - }); -} - -export function getCursorEquipmentActivationRecords(params) { - return request({ - url: `${baseUrl}/activationRecords`, - method: 'get', - params, - }); -} - -export function getCursorEquipmentExtractRecords(params) { - return request({ - url: `${baseUrl}/extractRecords`, - method: 'get', - params, - }); -} - -export function getCursorEquipmentIpLogs(params) { - return request({ - url: `${baseUrl}/ipLogs`, - method: 'get', - params, - }); -} +import request from '@/utils/request'; + +const baseUrl = '/platform/cursor/equipment'; + +export function getCursorEquipmentList(params) { + return request({ + url: `${baseUrl}/list`, + method: 'get', + params, + }); +} + +export function getCursorEquipmentDetail(id) { + return request({ + url: `${baseUrl}/detail/${id}`, + method: 'get', + }); +} + +export function addCursorEquipment(data) { + return request({ + url: `${baseUrl}/add`, + method: 'post', + data, + }); +} + +export function updateCursorEquipment(data) { + return request({ + url: `${baseUrl}/update`, + method: 'post', + data, + }); +} + +export function deleteCursorEquipment(id) { + return request({ + url: `${baseUrl}/delete/${id}`, + method: 'post', + }); +} + +export function activateCursorEquipment(data) { + return request({ + url: `${baseUrl}/activate`, + method: 'post', + data, + }); +} + +export function getCursorEquipmentActivationRecords(params) { + return request({ + url: `${baseUrl}/activationRecords`, + method: 'get', + params, + }); +} + +export function getCursorEquipmentExtractRecords(params) { + return request({ + url: `${baseUrl}/extractRecords`, + method: 'get', + params, + }); +} + +export function getCursorEquipmentIpLogs(params) { + return request({ + url: `${baseUrl}/ipLogs`, + method: 'get', + params, + }); +} diff --git a/platform/src/api/cursorEquipment.ts b/platform/src/api/cursorEquipment.ts index 71a1863..1017bff 100644 --- a/platform/src/api/cursorEquipment.ts +++ b/platform/src/api/cursorEquipment.ts @@ -1,98 +1,98 @@ -// @ts-ignore request 封装是 JS 文件,项目未提供 TS 声明 -import request from '@/utils/request'; - -const baseUrl = '/platform/cursor/equipment'; - -export interface CursorEquipmentQuery { - page?: number; - pageSize?: number; - keyword?: string; - status?: number | string; - system?: string; - os?: string; -} - -export interface CursorEquipmentPayload { - id?: number; - deviceInfo?: string; - machineCode?: string; - status?: number; - system?: string; - version?: string; - bindAccount?: string; - ownerUserId?: number; - ownerUserName?: string; - activationTime?: string; - expireTime?: string; - remark?: string; -} - -export function getCursorEquipmentList(params: CursorEquipmentQuery) { - return request({ - url: `${baseUrl}/list`, - method: 'get', - params, - }); -} - -export function getCursorEquipmentDetail(id: number | string) { - return request({ - url: `${baseUrl}/detail/${id}`, - method: 'get', - }); -} - -export function addCursorEquipment(data: CursorEquipmentPayload) { - return request({ - url: `${baseUrl}/add`, - method: 'post', - data, - }); -} - -export function updateCursorEquipment(data: CursorEquipmentPayload) { - return request({ - url: `${baseUrl}/update`, - method: 'post', - data, - }); -} - -export function deleteCursorEquipment(id: number | string) { - return request({ - url: `${baseUrl}/delete/${id}`, - method: 'post', - }); -} - -export function activateCursorEquipment(data: { id: number | string }) { - return request({ - url: `${baseUrl}/activate`, - method: 'post', - data, - }); -} - -export function getCursorEquipmentActivationRecords(params: Record) { - return request({ - url: `${baseUrl}/activationRecords`, - method: 'get', - params, - }); -} - -export function getCursorEquipmentExtractRecords(params: Record) { - return request({ - url: `${baseUrl}/extractRecords`, - method: 'get', - params, - }); -} - -export function getCursorEquipmentIpLogs(params: Record) { - return request({ - url: `${baseUrl}/ipLogs`, - method: 'get', - params, - }); -} +// @ts-ignore request 封装是 JS 文件,项目未提供 TS 声明 +import request from '@/utils/request'; + +const baseUrl = '/platform/cursor/equipment'; + +export interface CursorEquipmentQuery { + page?: number; + pageSize?: number; + keyword?: string; + status?: number | string; + system?: string; + os?: string; +} + +export interface CursorEquipmentPayload { + id?: number; + deviceInfo?: string; + machineCode?: string; + status?: number; + system?: string; + version?: string; + bindAccount?: string; + ownerUserId?: number; + ownerUserName?: string; + activationTime?: string; + expireTime?: string; + remark?: string; +} + +export function getCursorEquipmentList(params: CursorEquipmentQuery) { + return request({ + url: `${baseUrl}/list`, + method: 'get', + params, + }); +} + +export function getCursorEquipmentDetail(id: number | string) { + return request({ + url: `${baseUrl}/detail/${id}`, + method: 'get', + }); +} + +export function addCursorEquipment(data: CursorEquipmentPayload) { + return request({ + url: `${baseUrl}/add`, + method: 'post', + data, + }); +} + +export function updateCursorEquipment(data: CursorEquipmentPayload) { + return request({ + url: `${baseUrl}/update`, + method: 'post', + data, + }); +} + +export function deleteCursorEquipment(id: number | string) { + return request({ + url: `${baseUrl}/delete/${id}`, + method: 'post', + }); +} + +export function activateCursorEquipment(data: { id: number | string }) { + return request({ + url: `${baseUrl}/activate`, + method: 'post', + data, + }); +} + +export function getCursorEquipmentActivationRecords(params: Record) { + return request({ + url: `${baseUrl}/activationRecords`, + method: 'get', + params, + }); +} + +export function getCursorEquipmentExtractRecords(params: Record) { + return request({ + url: `${baseUrl}/extractRecords`, + method: 'get', + params, + }); +} + +export function getCursorEquipmentIpLogs(params: Record) { + return request({ + url: `${baseUrl}/ipLogs`, + method: 'get', + params, + }); +} diff --git a/platform/src/api/dashboard.js b/platform/src/api/dashboard.js index 3eab321..4ad392f 100644 --- a/platform/src/api/dashboard.js +++ b/platform/src/api/dashboard.js @@ -1,41 +1,41 @@ -import request from "@/utils/request"; - -/** - * 获取平台统计数据(平台用户使用) - * @returns {Promise} - */ -export function getPlatformStats() { - return request({ - url: "/platform/dashboard/platform-stats", - method: "get", - }); -} - -/** - * 获取租户统计数据(租户员工使用) - * @returns {Promise} - */ -export function getTenantStats() { - return request({ - url: "/platform/dashboard/tenant-stats", - method: "get", - }); -} - -/** - * 获取用户活动日志(操作日志和登录日志) - * @param {number} pageNum - 页码 - * @param {number} pageSize - 每页数量 - * @returns {Promise} - */ -export function getActivityLogs(pageNum = 1, pageSize = 10) { - return request({ - url: "/platform/dashboard/user-activity-logs", - method: "get", - params: { - page_num: pageNum, - page_size: pageSize, - }, - }); -} - +import request from "@/utils/request"; + +/** + * 获取平台统计数据(平台用户使用) + * @returns {Promise} + */ +export function getPlatformStats() { + return request({ + url: "/platform/dashboard/platform-stats", + method: "get", + }); +} + +/** + * 获取租户统计数据(租户员工使用) + * @returns {Promise} + */ +export function getTenantStats() { + return request({ + url: "/platform/dashboard/tenant-stats", + method: "get", + }); +} + +/** + * 获取用户活动日志(操作日志和登录日志) + * @param {number} pageNum - 页码 + * @param {number} pageSize - 每页数量 + * @returns {Promise} + */ +export function getActivityLogs(pageNum = 1, pageSize = 10) { + return request({ + url: "/platform/dashboard/user-activity-logs", + method: "get", + params: { + page_num: pageNum, + page_size: pageSize, + }, + }); +} + diff --git a/platform/src/api/demand.js b/platform/src/api/demand.js index 2d1c56f..5f969c5 100644 --- a/platform/src/api/demand.js +++ b/platform/src/api/demand.js @@ -1,51 +1,51 @@ -import request from "@/utils/request"; - -/** - * 获取需求列表 - * @returns {Promise} - */ -export function getDemandList() { - return request({ - url: "/platform/demandList", - method: "get", - }); -} - -/** - * 新增需求 - * @param {Object} data 需求数据 - * @returns {Promise} - */ -export function addDemand(data) { - return request({ - url: "/platform/addDemand", - method: "post", - data, - }); -} - -/** - * 编辑需求 - * @param {number} id 需求ID - * @param {Object} data 需求数据 - * @returns {Promise} - */ -export function editDemand(id, data) { - return request({ - url: `/platform/editDemand/${id}`, - method: "post", - data, - }); -} - -/** - * 删除需求 - * @param {number} id 需求ID - * @returns {Promise} - */ -export function deleteDemand(id) { - return request({ - url: `/platform/deleteDemand/${id}`, - method: "post", - }); -} +import request from "@/utils/request"; + +/** + * 获取需求列表 + * @returns {Promise} + */ +export function getDemandList() { + return request({ + url: "/platform/demandList", + method: "get", + }); +} + +/** + * 新增需求 + * @param {Object} data 需求数据 + * @returns {Promise} + */ +export function addDemand(data) { + return request({ + url: "/platform/addDemand", + method: "post", + data, + }); +} + +/** + * 编辑需求 + * @param {number} id 需求ID + * @param {Object} data 需求数据 + * @returns {Promise} + */ +export function editDemand(id, data) { + return request({ + url: `/platform/editDemand/${id}`, + method: "post", + data, + }); +} + +/** + * 删除需求 + * @param {number} id 需求ID + * @returns {Promise} + */ +export function deleteDemand(id) { + return request({ + url: `/platform/deleteDemand/${id}`, + method: "post", + }); +} diff --git a/platform/src/api/department.js b/platform/src/api/department.js index d81dc94..fcd6826 100644 --- a/platform/src/api/department.js +++ b/platform/src/api/department.js @@ -1,44 +1,44 @@ -import request from '@/utils/request'; - -// 获取租户下的所有部门 -export function getTenantDepartments(tenantId) { - return request({ - url: `/platform/departments/tenant/${tenantId}`, - method: 'get', - }); -} - -// 获取部门详情 -export function getDepartmentInfo(departmentId) { - return request({ - url: `/platform/departments/${departmentId}`, - method: 'get', - }); -} - -// 添加部门 -export function addDepartment(data) { - return request({ - url: '/platform/departments', - method: 'post', - data, - }); -} - -// 更新部门信息 -export function editDepartment(departmentId, data) { - return request({ - url: `/platform/departments/${departmentId}`, - method: 'put', - data, - }); -} - -// 删除部门 -export function deleteDepartment(departmentId) { - return request({ - url: `/platform/departments/${departmentId}`, - method: 'delete', - }); -} - +import request from '@/utils/request'; + +// 获取租户下的所有部门 +export function getTenantDepartments(tenantId) { + return request({ + url: `/platform/departments/tenant/${tenantId}`, + method: 'get', + }); +} + +// 获取部门详情 +export function getDepartmentInfo(departmentId) { + return request({ + url: `/platform/departments/${departmentId}`, + method: 'get', + }); +} + +// 添加部门 +export function addDepartment(data) { + return request({ + url: '/platform/departments', + method: 'post', + data, + }); +} + +// 更新部门信息 +export function editDepartment(departmentId, data) { + return request({ + url: `/platform/departments/${departmentId}`, + method: 'put', + data, + }); +} + +// 删除部门 +export function deleteDepartment(departmentId) { + return request({ + url: `/platform/departments/${departmentId}`, + method: 'delete', + }); +} + diff --git a/platform/src/api/dict.js b/platform/src/api/dict.js index e92e6e0..3a5cb07 100644 --- a/platform/src/api/dict.js +++ b/platform/src/api/dict.js @@ -1,114 +1,114 @@ -import request from '@/utils/request' - -// 获取字典类型列表 -export function getDictTypes(params) { - return request({ - url: '/platform/dict/types', - method: 'get', - params - }) -} - -// 根据ID获取字典类型 -export function getDictTypeById(id) { - return request({ - url: `/platform/dict/types/${id}`, - method: 'get' - }) -} - -// 添加字典类型 -export function addDictType(data) { - return request({ - url: '/platform/dict/types', - method: 'post', - data: { - ...data, - is_global: data.is_global !== undefined ? data.is_global : 0 - } - }) -} - -// 更新字典类型 -export function updateDictType(id, data) { - return request({ - url: `/platform/dict/types/${id}`, - method: 'put', - data: { - ...data, - is_global: data.is_global !== undefined ? data.is_global : 0 - } - }) -} - -// 删除字典类型 -export function deleteDictType(id) { - return request({ - url: `/platform/dict/types/${id}`, - method: 'delete' - }) -} - -// 获取字典项列表 -export function getDictItems(params) { - return request({ - url: '/platform/dict/items', - method: 'get', - params - }) -} - -// 根据ID获取字典项 -export function getDictItemById(id) { - return request({ - url: `/platform/dict/items/${id}`, - method: 'get' - }) -} - -// 添加字典项 -export function addDictItem(data) { - return request({ - url: '/platform/dict/items', - method: 'post', - data - }) -} - -// 更新字典项 -export function updateDictItem(id, data) { - return request({ - url: `/platform/dict/items/${id}`, - method: 'put', - data - }) -} - -// 删除字典项 -export function deleteDictItem(id) { - return request({ - url: `/platform/dict/items/${id}`, - method: 'delete' - }) -} - -// 根据字典编码获取字典项(用于业务查询) -export function getDictItemsByCode(code, includeDisabled = false) { - return request({ - url: `/platform/dict/items/code/${code}`, - method: 'get', - params: { - include_disabled: includeDisabled ? '1' : '0' - } - }) -} - -// 批量更新字典项排序 -export function batchUpdateDictItemSort(data) { - return request({ - url: '/platform/dict/items/sort', - method: 'put', - data - }) -} - +import request from '@/utils/request' + +// 获取字典类型列表 +export function getDictTypes(params) { + return request({ + url: '/platform/dict/types', + method: 'get', + params + }) +} + +// 根据ID获取字典类型 +export function getDictTypeById(id) { + return request({ + url: `/platform/dict/types/${id}`, + method: 'get' + }) +} + +// 添加字典类型 +export function addDictType(data) { + return request({ + url: '/platform/dict/types', + method: 'post', + data: { + ...data, + is_global: data.is_global !== undefined ? data.is_global : 0 + } + }) +} + +// 更新字典类型 +export function updateDictType(id, data) { + return request({ + url: `/platform/dict/types/${id}`, + method: 'put', + data: { + ...data, + is_global: data.is_global !== undefined ? data.is_global : 0 + } + }) +} + +// 删除字典类型 +export function deleteDictType(id) { + return request({ + url: `/platform/dict/types/${id}`, + method: 'delete' + }) +} + +// 获取字典项列表 +export function getDictItems(params) { + return request({ + url: '/platform/dict/items', + method: 'get', + params + }) +} + +// 根据ID获取字典项 +export function getDictItemById(id) { + return request({ + url: `/platform/dict/items/${id}`, + method: 'get' + }) +} + +// 添加字典项 +export function addDictItem(data) { + return request({ + url: '/platform/dict/items', + method: 'post', + data + }) +} + +// 更新字典项 +export function updateDictItem(id, data) { + return request({ + url: `/platform/dict/items/${id}`, + method: 'put', + data + }) +} + +// 删除字典项 +export function deleteDictItem(id) { + return request({ + url: `/platform/dict/items/${id}`, + method: 'delete' + }) +} + +// 根据字典编码获取字典项(用于业务查询) +export function getDictItemsByCode(code, includeDisabled = false) { + return request({ + url: `/platform/dict/items/code/${code}`, + method: 'get', + params: { + include_disabled: includeDisabled ? '1' : '0' + } + }) +} + +// 批量更新字典项排序 +export function batchUpdateDictItemSort(data) { + return request({ + url: '/platform/dict/items/sort', + method: 'put', + data + }) +} + diff --git a/platform/src/api/domain.js b/platform/src/api/domain.js index 4bb8a46..08d4c3a 100644 --- a/platform/src/api/domain.js +++ b/platform/src/api/domain.js @@ -1,110 +1,110 @@ -import request from '@/utils/request' - -// ==================== 主域名池管理 ==================== - -// 获取域名池列表 -export function getDomainPoolList(params) { - return request({ - url: '/platform/domain/pool/index', - method: 'get', - params - }) -} - -// 获取启用的主域名列表 -export function getEnabledDomains() { - return request({ - url: '/platform/domain/pool/getEnabledDomains', - method: 'get' - }) -} - -// 创建主域名 -export function createDomainPool(data) { - return request({ - url: '/platform/domain/pool/create', - method: 'post', - data - }) -} - -// 更新主域名 -export function updateDomainPool(data) { - return request({ - url: '/platform/domain/pool/update', - method: 'post', - data - }) -} - -// 删除主域名 -export function deleteDomainPool(id) { - return request({ - url: `/platform/domain/pool/delete/${id}`, - method: 'delete' - }) -} - -// 切换主域名状态 -export function toggleDomainPoolStatus(id) { - return request({ - url: '/platform/domain/pool/toggleStatus', - method: 'post', - data: { id } - }) -} - -// ==================== 租户域名管理 ==================== - -// 获取租户域名列表(管理员) -export function getTenantDomainList(params) { - return request({ - url: '/platform/domain/tenant/index', - method: 'get', - params - }) -} - -// 获取当前租户的域名列表 -export function getMyDomains(params) { - return request({ - url: '/platform/domain/tenant/myDomains', - method: 'get', - params - }) -} - -// 申请二级域名 -export function applyTenantDomain(data) { - return request({ - url: '/platform/domain/tenant/apply', - method: 'post', - data - }) -} - -// 审核租户域名 -export function auditTenantDomain(data) { - return request({ - url: '/platform/domain/tenant/audit', - method: 'post', - data - }) -} - -// 禁用/启用租户域名 -export function toggleTenantDomainStatus(id) { - return request({ - url: '/platform/domain/tenant/toggleStatus', - method: 'post', - data: { id } - }) -} - -// 删除租户域名 -export function deleteTenantDomain(id) { - return request({ - url: `/platform/domain/tenant/delete/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +// ==================== 主域名池管理 ==================== + +// 获取域名池列表 +export function getDomainPoolList(params) { + return request({ + url: '/platform/domain/pool/index', + method: 'get', + params + }) +} + +// 获取启用的主域名列表 +export function getEnabledDomains() { + return request({ + url: '/platform/domain/pool/getEnabledDomains', + method: 'get' + }) +} + +// 创建主域名 +export function createDomainPool(data) { + return request({ + url: '/platform/domain/pool/create', + method: 'post', + data + }) +} + +// 更新主域名 +export function updateDomainPool(data) { + return request({ + url: '/platform/domain/pool/update', + method: 'post', + data + }) +} + +// 删除主域名 +export function deleteDomainPool(id) { + return request({ + url: `/platform/domain/pool/delete/${id}`, + method: 'delete' + }) +} + +// 切换主域名状态 +export function toggleDomainPoolStatus(id) { + return request({ + url: '/platform/domain/pool/toggleStatus', + method: 'post', + data: { id } + }) +} + +// ==================== 租户域名管理 ==================== + +// 获取租户域名列表(管理员) +export function getTenantDomainList(params) { + return request({ + url: '/platform/domain/tenant/index', + method: 'get', + params + }) +} + +// 获取当前租户的域名列表 +export function getMyDomains(params) { + return request({ + url: '/platform/domain/tenant/myDomains', + method: 'get', + params + }) +} + +// 申请二级域名 +export function applyTenantDomain(data) { + return request({ + url: '/platform/domain/tenant/apply', + method: 'post', + data + }) +} + +// 审核租户域名 +export function auditTenantDomain(data) { + return request({ + url: '/platform/domain/tenant/audit', + method: 'post', + data + }) +} + +// 禁用/启用租户域名 +export function toggleTenantDomainStatus(id) { + return request({ + url: '/platform/domain/tenant/toggleStatus', + method: 'post', + data: { id } + }) +} + +// 删除租户域名 +export function deleteTenantDomain(id) { + return request({ + url: `/platform/domain/tenant/delete/${id}`, + method: 'delete' + }) +} diff --git a/platform/src/api/email.js b/platform/src/api/email.js index 411f49f..9cf60ba 100644 --- a/platform/src/api/email.js +++ b/platform/src/api/email.js @@ -1,36 +1,36 @@ -import request from "@/utils/request"; - -/** - * 获取邮箱信息 - * @returns {Promise} - */ -export function getEmailInfo() { - return request({ - url: "/platform/email/info", - method: "get", - }); -} - -/** - * 编辑邮箱信息 - * @returns {Promise} - */ -export function editEmailInfo(data) { - return request({ - url: "/platform/email/editinfo", - method: "post", - data, - }); -} - -/** - * 发送测试邮件 - * @returns {Promise} - */ -export function sendTestEmail(data) { - return request({ - url: "/platform/email/sendtestemail", - method: "post", - data, - }); +import request from "@/utils/request"; + +/** + * 获取邮箱信息 + * @returns {Promise} + */ +export function getEmailInfo() { + return request({ + url: "/platform/email/info", + method: "get", + }); +} + +/** + * 编辑邮箱信息 + * @returns {Promise} + */ +export function editEmailInfo(data) { + return request({ + url: "/platform/email/editinfo", + method: "post", + data, + }); +} + +/** + * 发送测试邮件 + * @returns {Promise} + */ +export function sendTestEmail(data) { + return request({ + url: "/platform/email/sendtestemail", + method: "post", + data, + }); } \ No newline at end of file diff --git a/platform/src/api/erp.js b/platform/src/api/erp.js index 5bb8f67..1512ed4 100644 --- a/platform/src/api/erp.js +++ b/platform/src/api/erp.js @@ -1,155 +1,155 @@ -import request from "@/utils/request"; - -/************************************************* - ****************** 组织机构相关接口 ****************** - *************************************************/ - -/** - * 获取组织机构列表 - * @returns {Promise} - */ -export function getOrganizationList() { - return request({ - url: '/platform/erp/getOrganization', - method: 'get' - }); -} - -/** - * 获取组织机构详情 - * @param {number} id 组织机构ID - * @returns {Promise} - */ -export function getOrganizationDetail(id) { - return request({ - url: `/platform/erp/getOrganizationDetail/${id}`, - method: "get", - }); -} - -/** - * 创建组织机构数据 - * @param {Object} data 组织机构数据 - * @returns {Promise} - */ -export function createOrganization(data) { - return request({ - url: "/platform/erp/createOrganization", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -// 更新组织机构信息 -export function editOrganization(id, data) { - return request({ - url: `/platform/erp/editOrganization/${id}`, - method: 'post', - data: data - }); -} - -/** - * 删除组织机构数据 - * @param {number} id 组织机构ID - * @returns {Promise} - */ -export function deleteOrganization(id) { - return request({ - url: `/platform/erp/deleteOrganization/${id}`, - method: "delete", - }); -} - -/** - * 获取企业单位列表 - * @returns {Promise} - */ -export function getCompanys() { - return request({ - url: '/platform/erp/getCompanys', - method: 'get' - }); -} - -/** - * 获取部门列表 - * @param {number} parentId 隶属单位ID - * @returns {Promise} - */ -export function getDepartments(parentId) { - return request({ - url: '/platform/erp/getDepartments', - method: 'get', - params: parentId ? { parent_id: parentId } : {} - }); -} - -/************************************************* - ****************** 员工相关接口 ****************** - *************************************************/ - -/** - * 获取员工列表 - * @param {number} tenantId 租户ID - * @returns {Promise} - */ -export function getEmployeeList(tenantId) { - return request({ - url: '/platform/erp/getEmployee', - method: 'get', - params: { tid: tenantId } - }); -} - -/** - * 获取员工详情 - * @param {number} id 员工ID - * @returns {Promise} - */ -export function getEmployeeDetail(id) { - return request({ - url: `/platform/erp/getEmployeeDetail/${id}`, - method: "get", - }); -} - -/** - * 创建员工数据 - * @param {Object} data 员工数据 - * @returns {Promise} - */ -export function createEmployee(data) { - return request({ - url: "/platform/erp/createEmployee", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -// 更新员工信息 -export function editEmployee(id, data) { - return request({ - url: `/platform/erp/editEmployee/${id}`, - method: 'post', - data: data - }); -} - -/** - * 删除员工数据 - * @param {number} id 员工ID - * @returns {Promise} - */ -export function deleteEmployee(id) { - return request({ - url: `/platform/erp/deleteEmployee/${id}`, - method: "delete", - }); -} +import request from "@/utils/request"; + +/************************************************* + ****************** 组织机构相关接口 ****************** + *************************************************/ + +/** + * 获取组织机构列表 + * @returns {Promise} + */ +export function getOrganizationList() { + return request({ + url: '/platform/erp/getOrganization', + method: 'get' + }); +} + +/** + * 获取组织机构详情 + * @param {number} id 组织机构ID + * @returns {Promise} + */ +export function getOrganizationDetail(id) { + return request({ + url: `/platform/erp/getOrganizationDetail/${id}`, + method: "get", + }); +} + +/** + * 创建组织机构数据 + * @param {Object} data 组织机构数据 + * @returns {Promise} + */ +export function createOrganization(data) { + return request({ + url: "/platform/erp/createOrganization", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +// 更新组织机构信息 +export function editOrganization(id, data) { + return request({ + url: `/platform/erp/editOrganization/${id}`, + method: 'post', + data: data + }); +} + +/** + * 删除组织机构数据 + * @param {number} id 组织机构ID + * @returns {Promise} + */ +export function deleteOrganization(id) { + return request({ + url: `/platform/erp/deleteOrganization/${id}`, + method: "delete", + }); +} + +/** + * 获取企业单位列表 + * @returns {Promise} + */ +export function getCompanys() { + return request({ + url: '/platform/erp/getCompanys', + method: 'get' + }); +} + +/** + * 获取部门列表 + * @param {number} parentId 隶属单位ID + * @returns {Promise} + */ +export function getDepartments(parentId) { + return request({ + url: '/platform/erp/getDepartments', + method: 'get', + params: parentId ? { parent_id: parentId } : {} + }); +} + +/************************************************* + ****************** 员工相关接口 ****************** + *************************************************/ + +/** + * 获取员工列表 + * @param {number} tenantId 租户ID + * @returns {Promise} + */ +export function getEmployeeList(tenantId) { + return request({ + url: '/platform/erp/getEmployee', + method: 'get', + params: { tid: tenantId } + }); +} + +/** + * 获取员工详情 + * @param {number} id 员工ID + * @returns {Promise} + */ +export function getEmployeeDetail(id) { + return request({ + url: `/platform/erp/getEmployeeDetail/${id}`, + method: "get", + }); +} + +/** + * 创建员工数据 + * @param {Object} data 员工数据 + * @returns {Promise} + */ +export function createEmployee(data) { + return request({ + url: "/platform/erp/createEmployee", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +// 更新员工信息 +export function editEmployee(id, data) { + return request({ + url: `/platform/erp/editEmployee/${id}`, + method: 'post', + data: data + }); +} + +/** + * 删除员工数据 + * @param {number} id 员工ID + * @returns {Promise} + */ +export function deleteEmployee(id) { + return request({ + url: `/platform/erp/deleteEmployee/${id}`, + method: "delete", + }); +} diff --git a/platform/src/api/file.js b/platform/src/api/file.js index b6d4a61..14b903d 100644 --- a/platform/src/api/file.js +++ b/platform/src/api/file.js @@ -1,222 +1,222 @@ -import request from "@/utils/request"; - -/** - * 获取用户分类 - * @returns {Promise} - */ -export function getUserCate() { - return request({ - url: `/platform/usercate`, - method: "get", - }); -} - -/** - * 获取所有文件(支持分页、分类、关键词,与后端 query 一致) - * @param {Object} [params] page, pageSize, cate, keyword - * @returns {Promise} - */ -export function getAllFiles(params = {}) { - return request({ - url: "/platform/allfiles", - method: "get", - params, - }); -} - -/** - * 新建文件分组 - * @param {Object} data 文件分组数据 - * @returns {Promise} - */ -export function createFileCate(data) { - return request({ - url: "/platform/createfilecate", - method: "post", - data, - }); -} - -/** - * 重命名文件分组 - * @param {number|string} id 文件分组ID - * @param {Object} data 文件分组数据 - * @returns {Promise} - */ -export function renameFileCate(id, data) { - return request({ - url: `/platform/renamefilecate/${id}`, - method: "post", - data, - }); -} - -/** - * 删除文件分组 - * @param {number|string} id 文件分组ID - * @returns {Promise} - */ -export function deleteFileCate(id) { - return request({ - url: `/platform/deletefilecate/${id}`, - method: "delete", - }); -} - -/** - * 根据分类ID获取文件 - * @param {number|string} id 分类ID - * @param {number} page 页码,默认1 - * @param {number} pageSize 每页数量,默认24 - * @param {string} keyword 搜索关键词,可选 - * @returns {Promise} - */ -export function getCateFiles(id, page = 1, pageSize = 24, keyword = "") { - const params = { - page, - pageSize, - }; - if (keyword) { - params.keyword = keyword; - } - return request({ - url: `/platform/catefiles/${id}`, - method: "get", - params, - }); -} - -/** - * 根据文件 ID 获取单条文件信息(非分类列表) - * @param {number|string} id 文件主键 ID - * @returns {Promise} - */ -export function getFileById(id) { - return request({ - url: `/platform/file/${id}`, - method: "get", - }); -} - -/** - * 上传文件 - * @param {FormData} formData 文件数据 - * @param {Object} options 额外选项 - * @param {string|number} [options.cate] 文件分组,0 为未分类 - * @param {string|number} [options.tuid] 租户用户 yz_tenant_user.id;租户侧上传时传,平台管理员不传 - * @param {(e: { loaded: number; total?: number }) => void} [options.onUploadProgress] 上传进度(浏览器 XHR) - * @returns {Promise} - */ -export function uploadFile(formData, options = {}) { - // 0 表示「未分类」,不能用 truthy 判断 - if (options.cate !== undefined && options.cate !== null && options.cate !== "") { - formData.append("cate", String(options.cate)); - } - if (options.tuid !== undefined && options.tuid !== null && options.tuid !== "") { - formData.append("tuid", String(options.tuid)); - } - - const config = { - url: "/platform/uploadfile", - method: "post", - data: formData, - // 不设置超时时间,等待文件上传完毕;勿设置 Content-Type(由浏览器自动带 boundary) - timeout: 0, - }; - if (typeof options.onUploadProgress === "function") { - config.onUploadProgress = options.onUploadProgress; - } - return request(config); -} - -/** - * 更新文件信息 - * @param {number|string} id 文件ID - * @param {Object} fileData 更新的数据 - * @returns {Promise} - */ -export function updateFile(id, fileData) { - return request({ - url: `/platform/updatefile/${id}`, - method: "post", - data: fileData, - }); -} - -/** - * 删除文件 - * @param {number|string} id 文件ID - * @returns {Promise} - */ -export function deleteFile(id) { - return request({ - url: `/platform/deletefile/${id}`, - method: "delete", - }); -} - -/** - * 删除文件 - * @param {number|string} id 文件ID - * @returns {Promise} - */ -export function deleteFilePermanently(id) { - return request({ - url: `/platform/deletefilepermanently/${id}`, - method: "delete", - }); -} - -/** - * 移动文件 - * @param {number|string} id 文件ID - * @param {Object} fileData 更新的数据 - * @returns {Promise} - */ -export function moveFile(id, cate) { - return request({ - url: `/platform/movefile/${id}`, - method: "get", - params: { cate }, - }); -} - -/** - * 批量删除文件 - * @param {Array} ids 文件ID数组 - * @returns {Promise} - */ -export function batchDeleteFiles(ids) { - return request({ - url: "/platform/batchdeletefiles", - method: "post", - data: { ids }, - }); -} - -/** - * 批量彻底删除文件 - * @param {Array} ids 文件ID数组 - * @returns {Promise} - */ -export function batchDeleteFilesPermanently(ids) { - return request({ - url: "/platform/batchDeleteFilesPermanently", - method: "post", - data: { ids }, - }); -} - -/** - * 批量移动文件 - * @param {Array} ids 文件ID数组 - * @param {number} cate 目标分类ID - * @returns {Promise} - */ -export function batchMoveFiles(ids, cate) { - return request({ - url: "/platform/batchMoveFiles", - method: "post", - data: { ids, cate }, - }); +import request from "@/utils/request"; + +/** + * 获取用户分类 + * @returns {Promise} + */ +export function getUserCate() { + return request({ + url: `/platform/usercate`, + method: "get", + }); +} + +/** + * 获取所有文件(支持分页、分类、关键词,与后端 query 一致) + * @param {Object} [params] page, pageSize, cate, keyword + * @returns {Promise} + */ +export function getAllFiles(params = {}) { + return request({ + url: "/platform/allfiles", + method: "get", + params, + }); +} + +/** + * 新建文件分组 + * @param {Object} data 文件分组数据 + * @returns {Promise} + */ +export function createFileCate(data) { + return request({ + url: "/platform/createfilecate", + method: "post", + data, + }); +} + +/** + * 重命名文件分组 + * @param {number|string} id 文件分组ID + * @param {Object} data 文件分组数据 + * @returns {Promise} + */ +export function renameFileCate(id, data) { + return request({ + url: `/platform/renamefilecate/${id}`, + method: "post", + data, + }); +} + +/** + * 删除文件分组 + * @param {number|string} id 文件分组ID + * @returns {Promise} + */ +export function deleteFileCate(id) { + return request({ + url: `/platform/deletefilecate/${id}`, + method: "delete", + }); +} + +/** + * 根据分类ID获取文件 + * @param {number|string} id 分类ID + * @param {number} page 页码,默认1 + * @param {number} pageSize 每页数量,默认24 + * @param {string} keyword 搜索关键词,可选 + * @returns {Promise} + */ +export function getCateFiles(id, page = 1, pageSize = 24, keyword = "") { + const params = { + page, + pageSize, + }; + if (keyword) { + params.keyword = keyword; + } + return request({ + url: `/platform/catefiles/${id}`, + method: "get", + params, + }); +} + +/** + * 根据文件 ID 获取单条文件信息(非分类列表) + * @param {number|string} id 文件主键 ID + * @returns {Promise} + */ +export function getFileById(id) { + return request({ + url: `/platform/file/${id}`, + method: "get", + }); +} + +/** + * 上传文件 + * @param {FormData} formData 文件数据 + * @param {Object} options 额外选项 + * @param {string|number} [options.cate] 文件分组,0 为未分类 + * @param {string|number} [options.tuid] 租户用户 yz_tenant_user.id;租户侧上传时传,平台管理员不传 + * @param {(e: { loaded: number; total?: number }) => void} [options.onUploadProgress] 上传进度(浏览器 XHR) + * @returns {Promise} + */ +export function uploadFile(formData, options = {}) { + // 0 表示「未分类」,不能用 truthy 判断 + if (options.cate !== undefined && options.cate !== null && options.cate !== "") { + formData.append("cate", String(options.cate)); + } + if (options.tuid !== undefined && options.tuid !== null && options.tuid !== "") { + formData.append("tuid", String(options.tuid)); + } + + const config = { + url: "/platform/uploadfile", + method: "post", + data: formData, + // 不设置超时时间,等待文件上传完毕;勿设置 Content-Type(由浏览器自动带 boundary) + timeout: 0, + }; + if (typeof options.onUploadProgress === "function") { + config.onUploadProgress = options.onUploadProgress; + } + return request(config); +} + +/** + * 更新文件信息 + * @param {number|string} id 文件ID + * @param {Object} fileData 更新的数据 + * @returns {Promise} + */ +export function updateFile(id, fileData) { + return request({ + url: `/platform/updatefile/${id}`, + method: "post", + data: fileData, + }); +} + +/** + * 删除文件 + * @param {number|string} id 文件ID + * @returns {Promise} + */ +export function deleteFile(id) { + return request({ + url: `/platform/deletefile/${id}`, + method: "delete", + }); +} + +/** + * 删除文件 + * @param {number|string} id 文件ID + * @returns {Promise} + */ +export function deleteFilePermanently(id) { + return request({ + url: `/platform/deletefilepermanently/${id}`, + method: "delete", + }); +} + +/** + * 移动文件 + * @param {number|string} id 文件ID + * @param {Object} fileData 更新的数据 + * @returns {Promise} + */ +export function moveFile(id, cate) { + return request({ + url: `/platform/movefile/${id}`, + method: "get", + params: { cate }, + }); +} + +/** + * 批量删除文件 + * @param {Array} ids 文件ID数组 + * @returns {Promise} + */ +export function batchDeleteFiles(ids) { + return request({ + url: "/platform/batchdeletefiles", + method: "post", + data: { ids }, + }); +} + +/** + * 批量彻底删除文件 + * @param {Array} ids 文件ID数组 + * @returns {Promise} + */ +export function batchDeleteFilesPermanently(ids) { + return request({ + url: "/platform/batchDeleteFilesPermanently", + method: "post", + data: { ids }, + }); +} + +/** + * 批量移动文件 + * @param {Array} ids 文件ID数组 + * @param {number} cate 目标分类ID + * @returns {Promise} + */ +export function batchMoveFiles(ids, cate) { + return request({ + url: "/platform/batchMoveFiles", + method: "post", + data: { ids, cate }, + }); } \ No newline at end of file diff --git a/platform/src/api/friendlink.js b/platform/src/api/friendlink.js index bd147cc..653be4b 100644 --- a/platform/src/api/friendlink.js +++ b/platform/src/api/friendlink.js @@ -1,77 +1,77 @@ -import request from '@/utils/request' - -/** - * 获取友情链接列表 - * @param {Object} params - 查询参数 - * @returns {Promise} - */ -export function getFriendlinkList(params) { - return request({ - url: '/platform/friendlinks', - method: 'get', - params - }) -} - -/** - * 获取所有友情链接(下拉选择用) - * @returns {Promise} - */ -export function getAllFriendlinks() { - return request({ - url: '/platform/friendlinks/all', - method: 'get' - }) -} - -/** - * 添加友情链接 - * @param {Object} data - 链接数据 - * @returns {Promise} - */ -export function addFriendlink(data) { - return request({ - url: '/platform/friendlinks', - method: 'post', - data - }) -} - -/** - * 更新友情链接 - * @param {number} id - 链接ID - * @param {Object} data - 链接数据 - * @returns {Promise} - */ -export function updateFriendlink(id, data) { - return request({ - url: `/platform/friendlinks/${id}`, - method: 'put', - data - }) -} - -/** - * 删除友情链接 - * @param {number} id - 链接ID - * @returns {Promise} - */ -export function deleteFriendlink(id) { - return request({ - url: `/platform/friendlinks/${id}`, - method: 'delete' - }) -} - -/** - * 批量删除友情链接 - * @param {Array} ids - 链接ID数组 - * @returns {Promise} - */ -export function batchDeleteFriendlinks(ids) { - return request({ - url: '/platform/friendlinks/batchdelete', - method: 'post', - data: { ids } - }) -} +import request from '@/utils/request' + +/** + * 获取友情链接列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getFriendlinkList(params) { + return request({ + url: '/platform/friendlinks', + method: 'get', + params + }) +} + +/** + * 获取所有友情链接(下拉选择用) + * @returns {Promise} + */ +export function getAllFriendlinks() { + return request({ + url: '/platform/friendlinks/all', + method: 'get' + }) +} + +/** + * 添加友情链接 + * @param {Object} data - 链接数据 + * @returns {Promise} + */ +export function addFriendlink(data) { + return request({ + url: '/platform/friendlinks', + method: 'post', + data + }) +} + +/** + * 更新友情链接 + * @param {number} id - 链接ID + * @param {Object} data - 链接数据 + * @returns {Promise} + */ +export function updateFriendlink(id, data) { + return request({ + url: `/platform/friendlinks/${id}`, + method: 'put', + data + }) +} + +/** + * 删除友情链接 + * @param {number} id - 链接ID + * @returns {Promise} + */ +export function deleteFriendlink(id) { + return request({ + url: `/platform/friendlinks/${id}`, + method: 'delete' + }) +} + +/** + * 批量删除友情链接 + * @param {Array} ids - 链接ID数组 + * @returns {Promise} + */ +export function batchDeleteFriendlinks(ids) { + return request({ + url: '/platform/friendlinks/batchdelete', + method: 'post', + data: { ids } + }) +} diff --git a/platform/src/api/frontMenu.js b/platform/src/api/frontMenu.js index 7e5a8a4..753988f 100644 --- a/platform/src/api/frontMenu.js +++ b/platform/src/api/frontMenu.js @@ -1,55 +1,55 @@ -import request from "@/utils/request"; - -/** - * 获取所有前端导航 - * @returns {Promise} - */ -export function getFrontMenus() { - return request({ - url: "/platform/frontmenus", - method: "get", - }); -} - -/** - * 创建前端导航 - * @param {Object} frontMenuData 前端导航数据 - * @returns {Promise} - */ -export function createFrontMenu(formData, options = {}) { - return request({ - url: "/platform/createfrontmenu", - method: "post", - data: formData, - headers: { - "Content-Type": "multipart/form-data" - } - }); -} - -/** - * 编辑前端导航 - * @param {number|string} id 前端导航ID - * @param {Object} frontMenuData 更新的数据 - * @returns {Promise} - */ -export function editFrontMenu(id, frontMenuData) { - return request({ - url: `/platform/editfrontmenu/${id}`, - method: "post", - data: frontMenuData, - }); -} - -/** - * 删除前端导航 - * @param {number|string} id 前端导航ID - * @returns {Promise} - */ -export function deleteFrontMenu(id) { - return request({ - url: `/platform/deletefrontmenu/${id}`, - method: "delete", - }); -} - +import request from "@/utils/request"; + +/** + * 获取所有前端导航 + * @returns {Promise} + */ +export function getFrontMenus() { + return request({ + url: "/platform/frontmenus", + method: "get", + }); +} + +/** + * 创建前端导航 + * @param {Object} frontMenuData 前端导航数据 + * @returns {Promise} + */ +export function createFrontMenu(formData, options = {}) { + return request({ + url: "/platform/createfrontmenu", + method: "post", + data: formData, + headers: { + "Content-Type": "multipart/form-data" + } + }); +} + +/** + * 编辑前端导航 + * @param {number|string} id 前端导航ID + * @param {Object} frontMenuData 更新的数据 + * @returns {Promise} + */ +export function editFrontMenu(id, frontMenuData) { + return request({ + url: `/platform/editfrontmenu/${id}`, + method: "post", + data: frontMenuData, + }); +} + +/** + * 删除前端导航 + * @param {number|string} id 前端导航ID + * @returns {Promise} + */ +export function deleteFrontMenu(id) { + return request({ + url: `/platform/deletefrontmenu/${id}`, + method: "delete", + }); +} + diff --git a/platform/src/api/home.js b/platform/src/api/home.js index bb92152..326491a 100644 --- a/platform/src/api/home.js +++ b/platform/src/api/home.js @@ -1,21 +1,21 @@ -import request from '@/utils/request'; - -/** - * 按天统计号池已提取(售卖)数量,依据 extracted_time - * @param {{ days?: number }} params days 默认 14,最大 90 - */ -export function getAccountPoolDailyExtract(params) { - return request({ - url: '/platform/home/accountPoolDailyExtract', - method: 'get', - params, - }); -} - -/** 号池账号总数 / 已售卖(按 Cursor、Kiro、Windsurf) */ -export function getAccountPoolInventoryTotals() { - return request({ - url: '/platform/home/accountPoolInventoryTotals', - method: 'get', - }); -} +import request from '@/utils/request'; + +/** + * 按天统计号池已提取(售卖)数量,依据 extracted_time + * @param {{ days?: number }} params days 默认 14,最大 90 + */ +export function getAccountPoolDailyExtract(params) { + return request({ + url: '/platform/home/accountPoolDailyExtract', + method: 'get', + params, + }); +} + +/** 号池账号总数 / 已售卖(按 Cursor、Kiro、Windsurf) */ +export function getAccountPoolInventoryTotals() { + return request({ + url: '/platform/home/accountPoolInventoryTotals', + method: 'get', + }); +} diff --git a/platform/src/api/login.js b/platform/src/api/login.js index f2b89d3..ae2471c 100644 --- a/platform/src/api/login.js +++ b/platform/src/api/login.js @@ -1,124 +1,124 @@ -import request from "@/utils/request"; - -// 登录(平台端 / 使用租户名称) -export function login(data) { - return request({ - url: `/platform/login`, - method: "post", - data, - }); -} - -/** 当前登录用户信息(含角色名称),需携带 token */ -export function getCurrentUser() { - return request({ - url: `/platform/currentUser`, - method: "get", - }); -} - -// 发送登录验证码(手机号) -export function sendLoginCode(data) { - return request({ - url: "/platform/sendLoginCode", - method: "post", - data, - }); -} - -// 手机号验证码登录 -export function loginBySms(data) { - return request({ - url: "/platform/loginBySms", - method: "post", - data, - }); -} -// 登出 -export function logout(userInfo = null) { - // 如果没有传入 userInfo,尝试从 localStorage 获取 - if (!userInfo) { - const cachedUserInfo = localStorage.getItem('userInfo'); - if (cachedUserInfo) { - try { - userInfo = JSON.parse(cachedUserInfo); - } catch (e) { - console.error('Failed to parse userInfo from localStorage:', e); - } - } - } - - return request({ - url: `/platform/logout`, - method: "post", - data: userInfo ? { userInfo: userInfo } : {}, - }); -} - -/** - * 获取极验3.0数据 - * @returns {Promise} - */ -export function getGeetest3Infos() { - return request({ - url: '/platform/login/getGeetest3Infos', - method: 'get' - }); -} - -/** - * 获取极验4.0数据 - * @returns {Promise} - */ -export function getGeetest4Infos() { - return request({ - url: '/platform/login/getGeetest4Infos', - method: 'get' - }); -} - -/** - * 判断是否开启验证 - * @returns {Promise} - */ -export function getOpenVerify() { - return request({ - url: '/platform/login/getOpenVerify', - method: 'get' - }); -} - -// 忘记密码重置 -export function resetPassword(data) { - return request({ - url: "/platform/resetPassword", - method: "post", - data, - }); -} - -// 发送找回密码验证码 -export function sendResetCode(data) { - return request({ - url: "/platform/sendResetCode", - method: "post", - data, - }); -} - -// 租户端自助注册(/backend/*,与 go-platform routers/backend 一致) -export function register(data) { - return request({ - url: "/backend/register", - method: "post", - data, - }); -} - -export function sendRegisterCode(data) { - return request({ - url: "/backend/sendRegisterCode", - method: "post", - data, - }); +import request from "@/utils/request"; + +// 登录(平台端 / 使用租户名称) +export function login(data) { + return request({ + url: `/platform/login`, + method: "post", + data, + }); +} + +/** 当前登录用户信息(含角色名称),需携带 token */ +export function getCurrentUser() { + return request({ + url: `/platform/currentUser`, + method: "get", + }); +} + +// 发送登录验证码(手机号) +export function sendLoginCode(data) { + return request({ + url: "/platform/sendLoginCode", + method: "post", + data, + }); +} + +// 手机号验证码登录 +export function loginBySms(data) { + return request({ + url: "/platform/loginBySms", + method: "post", + data, + }); +} +// 登出 +export function logout(userInfo = null) { + // 如果没有传入 userInfo,尝试从 localStorage 获取 + if (!userInfo) { + const cachedUserInfo = localStorage.getItem('userInfo'); + if (cachedUserInfo) { + try { + userInfo = JSON.parse(cachedUserInfo); + } catch (e) { + console.error('Failed to parse userInfo from localStorage:', e); + } + } + } + + return request({ + url: `/platform/logout`, + method: "post", + data: userInfo ? { userInfo: userInfo } : {}, + }); +} + +/** + * 获取极验3.0数据 + * @returns {Promise} + */ +export function getGeetest3Infos() { + return request({ + url: '/platform/login/getGeetest3Infos', + method: 'get' + }); +} + +/** + * 获取极验4.0数据 + * @returns {Promise} + */ +export function getGeetest4Infos() { + return request({ + url: '/platform/login/getGeetest4Infos', + method: 'get' + }); +} + +/** + * 判断是否开启验证 + * @returns {Promise} + */ +export function getOpenVerify() { + return request({ + url: '/platform/login/getOpenVerify', + method: 'get' + }); +} + +// 忘记密码重置 +export function resetPassword(data) { + return request({ + url: "/platform/resetPassword", + method: "post", + data, + }); +} + +// 发送找回密码验证码 +export function sendResetCode(data) { + return request({ + url: "/platform/sendResetCode", + method: "post", + data, + }); +} + +// 租户端自助注册(/backend/*,与 go-platform routers/backend 一致) +export function register(data) { + return request({ + url: "/backend/register", + method: "post", + data, + }); +} + +export function sendRegisterCode(data) { + return request({ + url: "/backend/sendRegisterCode", + method: "post", + data, + }); } \ No newline at end of file diff --git a/platform/src/api/menu.js b/platform/src/api/menu.js index de87f2f..a92f9ca 100644 --- a/platform/src/api/menu.js +++ b/platform/src/api/menu.js @@ -1,53 +1,53 @@ -import request from "@/utils/request"; - -// 获取所有菜单 -export function getAllMenus(params) { - return request({ - url: `/platform/allmenu`, - method: "get", - params, - }); -} - -//获取用户菜单 -export function getMenus(id){ - return request({ - url: `/platform/menu/${parseInt(id)}`, - method: "get", - }); -} - -// 更新菜单状态 -export function updateMenuStatus(menuId, status) { - return request({ - url: `/platform/menu/status/${menuId}`, - method: "patch", - data: { status }, - }); -} - -// 创建菜单 -export function createMenu(menuData) { - return request({ - url: `/platform/createmenu`, - method: "post", - data: menuData, - }); -} - -// 更新菜单 -export function updateMenu(menuId, menuData) { - return request({ - url: `/platform/updatemenu/${menuId}`, - method: "put", - data: menuData, - }); -} - -// 删除菜单 -export function deleteMenu(menuId) { - return request({ - url: `/platform/deletemenu/${menuId}`, - method: "delete", - }); -} +import request from "@/utils/request"; + +// 获取所有菜单 +export function getAllMenus(params) { + return request({ + url: `/platform/allmenu`, + method: "get", + params, + }); +} + +//获取用户菜单 +export function getMenus(id){ + return request({ + url: `/platform/menu/${parseInt(id)}`, + method: "get", + }); +} + +// 更新菜单状态 +export function updateMenuStatus(menuId, status) { + return request({ + url: `/platform/menu/status/${menuId}`, + method: "patch", + data: { status }, + }); +} + +// 创建菜单 +export function createMenu(menuData) { + return request({ + url: `/platform/createmenu`, + method: "post", + data: menuData, + }); +} + +// 更新菜单 +export function updateMenu(menuId, menuData) { + return request({ + url: `/platform/updatemenu/${menuId}`, + method: "put", + data: menuData, + }); +} + +// 删除菜单 +export function deleteMenu(menuId) { + return request({ + url: `/platform/deletemenu/${menuId}`, + method: "delete", + }); +} diff --git a/platform/src/api/moduleCenter.js b/platform/src/api/moduleCenter.js index 878eb13..416a82c 100644 --- a/platform/src/api/moduleCenter.js +++ b/platform/src/api/moduleCenter.js @@ -1,57 +1,57 @@ -import request from "@/utils/request"; - -/** - * 获取模块中心分类 - * @returns {Promise} - */ -export function getModuleCategory() { - return request({ - url: "/platform/moduleCategory", - method: "get", - }); -} - -/** - * 获取模块中心列表 - * @param {number} cid 分类id - * @returns {Promise} - */ -export function getModules(cid) { - return request({ - url: "/platform/moduleCenter/modules", - method: "get", - params: { cid } - }); -} - -/** - * 编辑模块分类 - * @param {Object} data 分类数据 - * @param {number} data.id 分类id(编辑时必填,新增时不填) - * @param {string} data.title 分类名称 - * @param {number} data.status 分类状态 - * @returns {Promise} - */ -export function editModuleCategory(data) { - return request({ - url: "/platform/moduleCenter/editCategory", - method: "post", - data - }); -} - -/** - * 编辑模块 - * @param {Object} data 模块数据 - * @param {number} data.id 模块id(编辑时必填,新增时不填) - * @param {string} data.title 模块名称 - * @param {number} data.status 模块状态 - * @returns {Promise} - */ -export function editModules(data) { - return request({ - url: "/platform/moduleCenter/editModules", - method: "post", - data - }); -} +import request from "@/utils/request"; + +/** + * 获取模块中心分类 + * @returns {Promise} + */ +export function getModuleCategory() { + return request({ + url: "/platform/moduleCategory", + method: "get", + }); +} + +/** + * 获取模块中心列表 + * @param {number} cid 分类id + * @returns {Promise} + */ +export function getModules(cid) { + return request({ + url: "/platform/moduleCenter/modules", + method: "get", + params: { cid } + }); +} + +/** + * 编辑模块分类 + * @param {Object} data 分类数据 + * @param {number} data.id 分类id(编辑时必填,新增时不填) + * @param {string} data.title 分类名称 + * @param {number} data.status 分类状态 + * @returns {Promise} + */ +export function editModuleCategory(data) { + return request({ + url: "/platform/moduleCenter/editCategory", + method: "post", + data + }); +} + +/** + * 编辑模块 + * @param {Object} data 模块数据 + * @param {number} data.id 模块id(编辑时必填,新增时不填) + * @param {string} data.title 模块名称 + * @param {number} data.status 模块状态 + * @returns {Promise} + */ +export function editModules(data) { + return request({ + url: "/platform/moduleCenter/editModules", + method: "post", + data + }); +} diff --git a/platform/src/api/modules.js b/platform/src/api/modules.js index f3a3df5..9b6a040 100644 --- a/platform/src/api/modules.js +++ b/platform/src/api/modules.js @@ -1,68 +1,68 @@ -import request from '@/utils/request'; - -export function getModulesList() { - return request({ - url: '/platform/modules/list', - method: 'get', - }); -} - -export function getTenantList() { - return request({ - url: '/platform/modules/getTenantList', - method: 'get', - }); -} - -export function getModuleDetail(id) { - return request({ - url: `/platform/modules/${id}`, - method: 'get', - }); -} - -export function addModule(data) { - return request({ - url: '/platform/modules', - method: 'post', - data, - }); -} - -export function editModule(id, data) { - return request({ - url: `/platform/modules/${id}`, - method: 'put', - data, - }); -} - -export function deleteModule(id) { - return request({ - url: `/platform/modules/${id}`, - method: 'delete', - }); -} - -export function batchDeleteModules(ids) { - return request({ - url: '/platform/modules/batchDelete', - method: 'post', - data: { ids }, - }); -} - -export function changeModuleStatus(id, status) { - return request({ - url: '/platform/modules/status', - method: 'post', - data: { id, status }, - }); -} - -export function getModulesSelectList() { - return request({ - url: '/platform/modules/select/list', - method: 'get', - }); -} +import request from '@/utils/request'; + +export function getModulesList() { + return request({ + url: '/platform/modules/list', + method: 'get', + }); +} + +export function getTenantList() { + return request({ + url: '/platform/modules/getTenantList', + method: 'get', + }); +} + +export function getModuleDetail(id) { + return request({ + url: `/platform/modules/${id}`, + method: 'get', + }); +} + +export function addModule(data) { + return request({ + url: '/platform/modules', + method: 'post', + data, + }); +} + +export function editModule(id, data) { + return request({ + url: `/platform/modules/${id}`, + method: 'put', + data, + }); +} + +export function deleteModule(id) { + return request({ + url: `/platform/modules/${id}`, + method: 'delete', + }); +} + +export function batchDeleteModules(ids) { + return request({ + url: '/platform/modules/batchDelete', + method: 'post', + data: { ids }, + }); +} + +export function changeModuleStatus(id, status) { + return request({ + url: '/platform/modules/status', + method: 'post', + data: { id, status }, + }); +} + +export function getModulesSelectList() { + return request({ + url: '/platform/modules/select/list', + method: 'get', + }); +} diff --git a/platform/src/api/notebook.js b/platform/src/api/notebook.js index 412ac89..0eef7fb 100644 --- a/platform/src/api/notebook.js +++ b/platform/src/api/notebook.js @@ -1,67 +1,67 @@ -import request from '@/utils/request' - -/** - * 获取笔记列表 - * @param {Object} params - 查询参数 - * @param {number} params.page - 页码 - * @param {number} params.pageSize - 每页数量 - * @param {string} params.keyword - 搜索关键词 - */ -export function getNotebookList(params) { - return request({ - url: '/platform/notebook/list', - method: 'get', - params - }) -} - -/** - * 获取笔记详情 - * @param {number} id - 笔记ID - */ -export function getNotebookDetail(id) { - return request({ - url: `/platform/notebook/detail/${id}`, - method: 'get' - }) -} - -/** - * 创建笔记 - * @param {Object} data - 笔记数据 - * @param {string} data.title - 笔记标题 - * @param {string} data.content - 笔记内容 - */ -export function createNotebook(data) { - return request({ - url: '/platform/notebook/create', - method: 'post', - data - }) -} - -/** - * 更新笔记 - * @param {number} id - 笔记ID - * @param {Object} data - 笔记数据 - * @param {string} data.title - 笔记标题 - * @param {string} data.content - 笔记内容 - */ -export function updateNotebook(id, data) { - return request({ - url: `/platform/notebook/update/${id}`, - method: 'post', - data - }) -} - -/** - * 删除笔记 - * @param {number} id - 笔记ID - */ -export function deleteNotebook(id) { - return request({ - url: `/platform/notebook/delete/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +/** + * 获取笔记列表 + * @param {Object} params - 查询参数 + * @param {number} params.page - 页码 + * @param {number} params.pageSize - 每页数量 + * @param {string} params.keyword - 搜索关键词 + */ +export function getNotebookList(params) { + return request({ + url: '/platform/notebook/list', + method: 'get', + params + }) +} + +/** + * 获取笔记详情 + * @param {number} id - 笔记ID + */ +export function getNotebookDetail(id) { + return request({ + url: `/platform/notebook/detail/${id}`, + method: 'get' + }) +} + +/** + * 创建笔记 + * @param {Object} data - 笔记数据 + * @param {string} data.title - 笔记标题 + * @param {string} data.content - 笔记内容 + */ +export function createNotebook(data) { + return request({ + url: '/platform/notebook/create', + method: 'post', + data + }) +} + +/** + * 更新笔记 + * @param {number} id - 笔记ID + * @param {Object} data - 笔记数据 + * @param {string} data.title - 笔记标题 + * @param {string} data.content - 笔记内容 + */ +export function updateNotebook(id, data) { + return request({ + url: `/platform/notebook/update/${id}`, + method: 'post', + data + }) +} + +/** + * 删除笔记 + * @param {number} id - 笔记ID + */ +export function deleteNotebook(id) { + return request({ + url: `/platform/notebook/delete/${id}`, + method: 'delete' + }) +} diff --git a/platform/src/api/onepage.js b/platform/src/api/onepage.js index d6ef61e..4a1b0c1 100644 --- a/platform/src/api/onepage.js +++ b/platform/src/api/onepage.js @@ -1,64 +1,64 @@ -import request from "@/utils/request"; - -/** - * 获取所有单页 - * @returns {Promise} - */ -export function getOnePages() { - return request({ - url: "/platform/allonepages", - method: "get", - }); -} - -/** - * 创建单页 - * @param {Object} onePageData 单页数据 - * @returns {Promise} - */ -export function createOnePage(formData) { - return request({ - url: "/platform/createonepage", - method: "post", - data: formData, - }); -} - -/** - * 编辑单页 - * @param {number|string} id 单页ID - * @param {Object} onePageData 更新的数据 - * @returns {Promise} - */ -export function editOnePage(id, onePageData) { - return request({ - url: `/platform/editonepage/${id}`, - method: "post", - data: onePageData, - }); -} - -/** - * 删除单页 - * @param {number|string} id 单页ID - * @returns {Promise} - */ -export function deleteOnePage(id) { - return request({ - url: `/platform/deleteonepage/${id}`, - method: "delete", - }); -} - -/** - * 根据路径获取单页(前端使用) - * @param {string} path 路由路径 - * @returns {Promise} - */ -export function getOnePageByPath(path) { - return request({ - url: `/index/onepage/${encodeURIComponent(path)}`, - method: "get", - }); -} - +import request from "@/utils/request"; + +/** + * 获取所有单页 + * @returns {Promise} + */ +export function getOnePages() { + return request({ + url: "/platform/allonepages", + method: "get", + }); +} + +/** + * 创建单页 + * @param {Object} onePageData 单页数据 + * @returns {Promise} + */ +export function createOnePage(formData) { + return request({ + url: "/platform/createonepage", + method: "post", + data: formData, + }); +} + +/** + * 编辑单页 + * @param {number|string} id 单页ID + * @param {Object} onePageData 更新的数据 + * @returns {Promise} + */ +export function editOnePage(id, onePageData) { + return request({ + url: `/platform/editonepage/${id}`, + method: "post", + data: onePageData, + }); +} + +/** + * 删除单页 + * @param {number|string} id 单页ID + * @returns {Promise} + */ +export function deleteOnePage(id) { + return request({ + url: `/platform/deleteonepage/${id}`, + method: "delete", + }); +} + +/** + * 根据路径获取单页(前端使用) + * @param {string} path 路由路径 + * @returns {Promise} + */ +export function getOnePageByPath(path) { + return request({ + url: `/index/onepage/${encodeURIComponent(path)}`, + method: "get", + }); +} + diff --git a/platform/src/api/operationLog.js b/platform/src/api/operationLog.js index 128f8f7..9abadc0 100644 --- a/platform/src/api/operationLog.js +++ b/platform/src/api/operationLog.js @@ -1,70 +1,70 @@ -import request from "@/utils/request"; - -/** - * 获取操作日志列表 - * @param {Object} params 查询参数 - * @param {number} params.page 页码 - * @param {number} params.pageSize 每页数量 - * @param {string} params.keyword 关键词搜索 - * @param {string} params.module 模块筛选 - * @param {string} params.action 操作动作筛选 - * @param {string} params.status 状态筛选 - * @param {string} params.startTime 开始时间 - * @param {string} params.endTime 结束时间 - * @returns {Promise} - */ -export function getOperationLogs(params) { - return request({ - url: "/platform/operationLogs", - method: "get", - params, - }); -} - -/** - * 获取操作日志详情 - * @param {number|string} id 日志ID - * @returns {Promise} - */ -export function getOperationLogDetail(id) { - return request({ - url: `/platform/operationLogs/${id}`, - method: "get", - }); -} - -/** - * 删除操作日志 - * @param {number|string} id 日志ID - * @returns {Promise} - */ -export function deleteOperationLog(id) { - return request({ - url: `/platform/operationLogs/${id}`, - method: "delete", - }); -} - -/** - * 批量删除操作日志 - * @param {Array} ids 日志ID数组 - * @returns {Promise} - */ -export function batchDeleteOperationLogs(ids) { - return request({ - url: "/platform/operationLogs/batchDelete", - method: "post", - data: { ids }, - }); -} - -/** - * 获取操作统计信息 - * @returns {Promise} - */ -export function getOperationStatistics() { - return request({ - url: "/platform/operationLogs/statistics", - method: "get", - }); +import request from "@/utils/request"; + +/** + * 获取操作日志列表 + * @param {Object} params 查询参数 + * @param {number} params.page 页码 + * @param {number} params.pageSize 每页数量 + * @param {string} params.keyword 关键词搜索 + * @param {string} params.module 模块筛选 + * @param {string} params.action 操作动作筛选 + * @param {string} params.status 状态筛选 + * @param {string} params.startTime 开始时间 + * @param {string} params.endTime 结束时间 + * @returns {Promise} + */ +export function getOperationLogs(params) { + return request({ + url: "/platform/operationLogs", + method: "get", + params, + }); +} + +/** + * 获取操作日志详情 + * @param {number|string} id 日志ID + * @returns {Promise} + */ +export function getOperationLogDetail(id) { + return request({ + url: `/platform/operationLogs/${id}`, + method: "get", + }); +} + +/** + * 删除操作日志 + * @param {number|string} id 日志ID + * @returns {Promise} + */ +export function deleteOperationLog(id) { + return request({ + url: `/platform/operationLogs/${id}`, + method: "delete", + }); +} + +/** + * 批量删除操作日志 + * @param {Array} ids 日志ID数组 + * @returns {Promise} + */ +export function batchDeleteOperationLogs(ids) { + return request({ + url: "/platform/operationLogs/batchDelete", + method: "post", + data: { ids }, + }); +} + +/** + * 获取操作统计信息 + * @returns {Promise} + */ +export function getOperationStatistics() { + return request({ + url: "/platform/operationLogs/statistics", + method: "get", + }); } \ No newline at end of file diff --git a/platform/src/api/permission.js b/platform/src/api/permission.js index 2af440b..8af83db 100644 --- a/platform/src/api/permission.js +++ b/platform/src/api/permission.js @@ -1,24 +1,24 @@ -import request from '@/utils/request'; - -export function getAllMenuPermissions(params = {}) { - return request({ - url: '/platform/allmenupermissions', - method: 'get', - params - }); -} - -export function getRolePermissions(roleId) { - return request({ - url: `/platform/rolepermissions/${roleId}`, - method: 'get' - }); -} - -export function assignRolePermissions(roleId, permissions) { - return request({ - url: `/platform/assignrolepermissions/${roleId}`, - method: 'post', - data: { permissions } - }); -} +import request from '@/utils/request'; + +export function getAllMenuPermissions(params = {}) { + return request({ + url: '/platform/allmenupermissions', + method: 'get', + params + }); +} + +export function getRolePermissions(roleId) { + return request({ + url: `/platform/rolepermissions/${roleId}`, + method: 'get' + }); +} + +export function assignRolePermissions(roleId, permissions) { + return request({ + url: `/platform/assignrolepermissions/${roleId}`, + method: 'post', + data: { permissions } + }); +} diff --git a/platform/src/api/position.js b/platform/src/api/position.js index 8efdc8f..c24e29c 100644 --- a/platform/src/api/position.js +++ b/platform/src/api/position.js @@ -1,52 +1,52 @@ -import request from '@/utils/request'; - -// 获取租户下的所有职位 -export function getTenantPositions(tenantId) { - return request({ - url: `/platform/positions/tenant/${tenantId}`, - method: 'get', - }); -} - -// 根据部门ID获取职位列表 -export function getPositionsByDepartment(departmentId) { - return request({ - url: `/platform/positions/department/${departmentId}`, - method: 'get', - }); -} - -// 获取职位详情 -export function getPositionInfo(positionId) { - return request({ - url: `/platform/positions/${positionId}`, - method: 'get', - }); -} - -// 添加职位 -export function addPosition(data) { - return request({ - url: '/platform/positions', - method: 'post', - data, - }); -} - -// 更新职位信息 -export function editPosition(positionId, data) { - return request({ - url: `/platform/positions/${positionId}`, - method: 'put', - data, - }); -} - -// 删除职位 -export function deletePosition(positionId) { - return request({ - url: `/platform/positions/${positionId}`, - method: 'delete', - }); -} - +import request from '@/utils/request'; + +// 获取租户下的所有职位 +export function getTenantPositions(tenantId) { + return request({ + url: `/platform/positions/tenant/${tenantId}`, + method: 'get', + }); +} + +// 根据部门ID获取职位列表 +export function getPositionsByDepartment(departmentId) { + return request({ + url: `/platform/positions/department/${departmentId}`, + method: 'get', + }); +} + +// 获取职位详情 +export function getPositionInfo(positionId) { + return request({ + url: `/platform/positions/${positionId}`, + method: 'get', + }); +} + +// 添加职位 +export function addPosition(data) { + return request({ + url: '/platform/positions', + method: 'post', + data, + }); +} + +// 更新职位信息 +export function editPosition(positionId, data) { + return request({ + url: `/platform/positions/${positionId}`, + method: 'put', + data, + }); +} + +// 删除职位 +export function deletePosition(positionId) { + return request({ + url: `/platform/positions/${positionId}`, + method: 'delete', + }); +} + diff --git a/platform/src/api/products.js b/platform/src/api/products.js index 865d403..22a8aa8 100644 --- a/platform/src/api/products.js +++ b/platform/src/api/products.js @@ -1,105 +1,105 @@ -import request from '@/utils/request' - -/** - * 获取特色产品列表 - * @param {Object} params - 查询参数 - * @returns {Promise} - */ -export function getProductsList(params) { - return request({ - url: '/platform/productsList', - method: 'get', - params - }) -} - -/** - * 添加特色产品 - * @param {Object} data - 产品数据 - * @returns {Promise} - */ -export function addProducts(data) { - return request({ - url: '/platform/addProducts', - method: 'post', - data - }) -} - -/** - * 更新特色产品 - * @param {number} id - 产品ID - * @param {Object} data - 产品数据 - * @returns {Promise} - */ -export function updateProducts(id, data) { - return request({ - url: `/platform/editProducts/${id}`, - method: 'put', - data - }) -} - -/** - * 删除特色产品 - * @param {number} id - 产品ID - * @returns {Promise} - */ -export function deleteProducts(id) { - return request({ - url: `/platform/deleteProducts/${id}`, - method: 'delete' - }) -} - -/** - * 获取产品分类列表 - * @param {Object} params - 查询参数 - * @returns {Promise} - */ -export function getProductsTypesList(params) { - return request({ - url: '/platform/productsTypesList', - method: 'get', - params - }) -} - -/** - * 添加产品分类 - * @param {Object} data - 分类数据 - * @returns {Promise} - */ -export function addProductsTypes(data) { - return request({ - url: '/platform/addProductsTypes', - method: 'post', - data - }) -} - -/** - * 更新产品分类 - * @param {number} id - 分类ID - * @param {Object} data - 分类数据 - * @returns {Promise} - */ -export function updateProductsTypes(id, data) { - return request({ - url: `/platform/editProductsTypes/${id}`, - method: 'put', - data - }) -} - -/** - * 删除产品分类 - * @param {number} id - 分类ID - * @returns {Promise} - */ -export function deleteProductsTypes(id) { - return request({ - url: `/platform/deleteProductsTypes/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +/** + * 获取特色产品列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getProductsList(params) { + return request({ + url: '/platform/productsList', + method: 'get', + params + }) +} + +/** + * 添加特色产品 + * @param {Object} data - 产品数据 + * @returns {Promise} + */ +export function addProducts(data) { + return request({ + url: '/platform/addProducts', + method: 'post', + data + }) +} + +/** + * 更新特色产品 + * @param {number} id - 产品ID + * @param {Object} data - 产品数据 + * @returns {Promise} + */ +export function updateProducts(id, data) { + return request({ + url: `/platform/editProducts/${id}`, + method: 'put', + data + }) +} + +/** + * 删除特色产品 + * @param {number} id - 产品ID + * @returns {Promise} + */ +export function deleteProducts(id) { + return request({ + url: `/platform/deleteProducts/${id}`, + method: 'delete' + }) +} + +/** + * 获取产品分类列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getProductsTypesList(params) { + return request({ + url: '/platform/productsTypesList', + method: 'get', + params + }) +} + +/** + * 添加产品分类 + * @param {Object} data - 分类数据 + * @returns {Promise} + */ +export function addProductsTypes(data) { + return request({ + url: '/platform/addProductsTypes', + method: 'post', + data + }) +} + +/** + * 更新产品分类 + * @param {number} id - 分类ID + * @param {Object} data - 分类数据 + * @returns {Promise} + */ +export function updateProductsTypes(id, data) { + return request({ + url: `/platform/editProductsTypes/${id}`, + method: 'put', + data + }) +} + +/** + * 删除产品分类 + * @param {number} id - 分类ID + * @returns {Promise} + */ +export function deleteProductsTypes(id) { + return request({ + url: `/platform/deleteProductsTypes/${id}`, + method: 'delete' + }) +} diff --git a/platform/src/api/reminder.js b/platform/src/api/reminder.js index 3e29489..ee10511 100644 --- a/platform/src/api/reminder.js +++ b/platform/src/api/reminder.js @@ -1,70 +1,70 @@ -import request from "@/utils/request"; - -/** 提醒列表 */ -export function getReminderList(params) { - return request({ - url: "/platform/reminder/list", - method: "get", - params, - }); -} - -/** 提醒详情 */ -export function getReminderDetail(id) { - return request({ - url: `/platform/reminder/${id}`, - method: "get", - }); -} - -/** 新增提醒 */ -export function createReminder(data) { - return request({ - url: "/platform/reminder", - method: "post", - data, - }); -} - -/** 更新提醒 */ -export function updateReminder(id, data) { - return request({ - url: `/platform/reminder/${id}`, - method: "put", - data, - }); -} - -/** 删除提醒 */ -export function deleteReminder(id) { - return request({ - url: `/platform/reminder/${id}`, - method: "delete", - }); -} - -/** 批量删除提醒 */ -export function batchDeleteReminder(ids) { - return request({ - url: "/platform/reminder/batchDelete", - method: "post", - data: { ids }, - }); -} - -/** 测试提醒渠道 */ -export function testReminder(data) { - return request({ - url: "/platform/reminder/test", - method: "post", - data, - }); -} - -/** 结束提醒 */ -export function finishReminder(id) { - return request({ - url: `/platform/reminder/${id}/finish`, - method: "post", - }); -} +import request from "@/utils/request"; + +/** 提醒列表 */ +export function getReminderList(params) { + return request({ + url: "/platform/reminder/list", + method: "get", + params, + }); +} + +/** 提醒详情 */ +export function getReminderDetail(id) { + return request({ + url: `/platform/reminder/${id}`, + method: "get", + }); +} + +/** 新增提醒 */ +export function createReminder(data) { + return request({ + url: "/platform/reminder", + method: "post", + data, + }); +} + +/** 更新提醒 */ +export function updateReminder(id, data) { + return request({ + url: `/platform/reminder/${id}`, + method: "put", + data, + }); +} + +/** 删除提醒 */ +export function deleteReminder(id) { + return request({ + url: `/platform/reminder/${id}`, + method: "delete", + }); +} + +/** 批量删除提醒 */ +export function batchDeleteReminder(ids) { + return request({ + url: "/platform/reminder/batchDelete", + method: "post", + data: { ids }, + }); +} + +/** 测试提醒渠道 */ +export function testReminder(data) { + return request({ + url: "/platform/reminder/test", + method: "post", + data, + }); +} + +/** 结束提醒 */ +export function finishReminder(id) { + return request({ + url: `/platform/reminder/${id}/finish`, + method: "post", + }); +} diff --git a/platform/src/api/role.js b/platform/src/api/role.js index d4d73bf..83c0cc8 100644 --- a/platform/src/api/role.js +++ b/platform/src/api/role.js @@ -1,38 +1,38 @@ -import request from '@/utils/request' - -export function getAllRoles() { - return request({ - url: '/platform/allRoles', - method: 'get' - }) -} - -export function getRoleById(id) { - return request({ - url: `/platform/roles/${id}`, - method: 'get' - }) -} - -export function createRole(data) { - return request({ - url: '/platform/roles', - method: 'post', - data - }) -} - -export function updateRole(id, data) { - return request({ - url: `/platform/roles/${id}`, - method: 'put', - data - }) -} - -export function deleteRole(id) { - return request({ - url: `/platform/roles/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +export function getAllRoles() { + return request({ + url: '/platform/allRoles', + method: 'get' + }) +} + +export function getRoleById(id) { + return request({ + url: `/platform/roles/${id}`, + method: 'get' + }) +} + +export function createRole(data) { + return request({ + url: '/platform/roles', + method: 'post', + data + }) +} + +export function updateRole(id, data) { + return request({ + url: `/platform/roles/${id}`, + method: 'put', + data + }) +} + +export function deleteRole(id) { + return request({ + url: `/platform/roles/${id}`, + method: 'delete' + }) +} diff --git a/platform/src/api/services.js b/platform/src/api/services.js index 24467fd..797d6c3 100644 --- a/platform/src/api/services.js +++ b/platform/src/api/services.js @@ -1,53 +1,53 @@ -import request from '@/utils/request' - -/** - * 获取特色服务列表 - * @param {Object} params - 查询参数 - * @returns {Promise} - */ -export function getServiceList(params) { - return request({ - url: '/platform/servicesList', - method: 'get', - params - }) -} - -/** - * 添加特色服务 - * @param {Object} data - 服务数据 - * @returns {Promise} - */ -export function addService(data) { - return request({ - url: '/platform/addServices', - method: 'post', - data - }) -} - -/** - * 更新特色服务 - * @param {number} id - 服务ID - * @param {Object} data - 服务数据 - * @returns {Promise} - */ -export function updateService(id, data) { - return request({ - url: `/platform/editServices/${id}`, - method: 'put', - data - }) -} - -/** - * 删除特色服务 - * @param {number} id - 服务ID - * @returns {Promise} - */ -export function deleteService(id) { - return request({ - url: `/platform/deleteServices/${id}`, - method: 'delete' - }) -} +import request from '@/utils/request' + +/** + * 获取特色服务列表 + * @param {Object} params - 查询参数 + * @returns {Promise} + */ +export function getServiceList(params) { + return request({ + url: '/platform/servicesList', + method: 'get', + params + }) +} + +/** + * 添加特色服务 + * @param {Object} data - 服务数据 + * @returns {Promise} + */ +export function addService(data) { + return request({ + url: '/platform/addServices', + method: 'post', + data + }) +} + +/** + * 更新特色服务 + * @param {number} id - 服务ID + * @param {Object} data - 服务数据 + * @returns {Promise} + */ +export function updateService(id, data) { + return request({ + url: `/platform/editServices/${id}`, + method: 'put', + data + }) +} + +/** + * 删除特色服务 + * @param {number} id - 服务ID + * @returns {Promise} + */ +export function deleteService(id) { + return request({ + url: `/platform/deleteServices/${id}`, + method: 'delete' + }) +} diff --git a/platform/src/api/sitereminder.js b/platform/src/api/sitereminder.js index 6f6b71b..507b09b 100644 --- a/platform/src/api/sitereminder.js +++ b/platform/src/api/sitereminder.js @@ -1,89 +1,89 @@ -import request from "@/utils/request"; - -/** 获取站内信配置 */ -export function getSiteReminderConfig() { - return request({ - url: "/platform/sitereminder/config", - method: "get", - }); -} - -/** 保存站内信配置 */ -export function saveSiteReminderConfig(data) { - return request({ - url: "/platform/sitereminder/config", - method: "post", - data, - }); -} - -/** 发送站内信 */ -export function sendSiteReminder(data) { - return request({ - url: "/platform/sitereminder/send", - method: "post", - data, - }); -} - -/** 获取我的消息列表 */ -export function getMySiteReminders(params) { - return request({ - url: "/platform/sitereminder/myList", - method: "get", - params, - }); -} - -/** 标记消息为已读 */ -export function readSiteReminder(id) { - return request({ - url: "/platform/sitereminder/read", - method: "post", - data: { id }, - }); -} - -/** 一键全部已读 */ -export function readAllSiteReminders() { - return request({ - url: "/platform/sitereminder/readall", - method: "post", - }); -} - -/** 删除消息 */ -export function deleteSiteReminder(id) { - return request({ - url: "/platform/sitereminder/delete", - method: "post", - data: { id }, - }); -} - -/** 获取已发送的站内信列表(按 batch_id 分组) */ -export function getSentSiteReminders(params) { - return request({ - url: "/platform/sitereminder/sentList", - method: "get", - params, - }); -} - -/** 编辑/更新已发送的站内信 */ -export function updateSentSiteReminder(data) { - return request({ - url: "/platform/sitereminder/updateSent", - method: "post", - data, - }); -} - -/** 删除已发送的站内信批次 */ -export function deleteSentSiteReminderBatch(batch_id) { - return request({ - url: "/platform/sitereminder/deleteSent", - method: "post", - data: { batch_id }, - }); -} +import request from "@/utils/request"; + +/** 获取站内信配置 */ +export function getSiteReminderConfig() { + return request({ + url: "/platform/sitereminder/config", + method: "get", + }); +} + +/** 保存站内信配置 */ +export function saveSiteReminderConfig(data) { + return request({ + url: "/platform/sitereminder/config", + method: "post", + data, + }); +} + +/** 发送站内信 */ +export function sendSiteReminder(data) { + return request({ + url: "/platform/sitereminder/send", + method: "post", + data, + }); +} + +/** 获取我的消息列表 */ +export function getMySiteReminders(params) { + return request({ + url: "/platform/sitereminder/myList", + method: "get", + params, + }); +} + +/** 标记消息为已读 */ +export function readSiteReminder(id) { + return request({ + url: "/platform/sitereminder/read", + method: "post", + data: { id }, + }); +} + +/** 一键全部已读 */ +export function readAllSiteReminders() { + return request({ + url: "/platform/sitereminder/readall", + method: "post", + }); +} + +/** 删除消息 */ +export function deleteSiteReminder(id) { + return request({ + url: "/platform/sitereminder/delete", + method: "post", + data: { id }, + }); +} + +/** 获取已发送的站内信列表(按 batch_id 分组) */ +export function getSentSiteReminders(params) { + return request({ + url: "/platform/sitereminder/sentList", + method: "get", + params, + }); +} + +/** 编辑/更新已发送的站内信 */ +export function updateSentSiteReminder(data) { + return request({ + url: "/platform/sitereminder/updateSent", + method: "post", + data, + }); +} + +/** 删除已发送的站内信批次 */ +export function deleteSentSiteReminderBatch(batch_id) { + return request({ + url: "/platform/sitereminder/deleteSent", + method: "post", + data: { batch_id }, + }); +} diff --git a/platform/src/api/sitesettings.js b/platform/src/api/sitesettings.js index 2d0bf45..4421b4c 100644 --- a/platform/src/api/sitesettings.js +++ b/platform/src/api/sitesettings.js @@ -1,190 +1,190 @@ -import request from "@/utils/request"; - -/** - * 获取基本信息 - * @param {number} tid 租户ID - * @returns {Promise} - */ -export function getNormalInfos(tid) { - return request({ - url: "/platform/normalInfos", - method: "get", - params: { tid } - }); -} - -/** - * 保存基本信息 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveNormalInfos(data) { - return request({ - url: "/platform/saveNormalInfos", - method: "post", - data: data, - }); -} - -/** - * 获取登录验证数据 - * @returns {Promise} - */ -export function getVerifyInfos() { - return request({ - url: "/platform/loginVerifyInfos", - method: "get", - }); -} - -/** - * 保存登录验证数据 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveVerifyInfos(data) { - return request({ - url: "/platform/saveloginVerifyInfos", - method: "post", - data: data, - }); -} - -/** - * 获取法律声明和隐私条款 - * @param {number} tid 租户ID - * @returns {Promise} - */ -export function getLegalInfos(tid) { - return request({ - url: "/platform/legalInfos", - method: "get", - params: { tid } - }); -} - -/** - * 保存法律声明和隐私条款 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveLegalInfos(data) { - return request({ - url: "/platform/saveLegalInfos", - method: "post", - data: data, - }); -} - -/** - * 获取企业信息 - * @param {number} tid 租户ID - * @returns {Promise} - */ -export function getCompanyInfos(tid) { - return request({ - url: "/platform/companyInfos", - method: "get", - params: { tid } - }); -} - -/** - * 保存企业信息 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveCompanyInfos(data) { - return request({ - url: "/platform/saveCompanyInfos", - method: "post", - data: data, - }); -} - -/** - * 获取企业SEO - * @param {number} tid 租户ID - * @returns {Promise} - */ -export function getCompanySeo(tid) { - return request({ - url: "/platform/companySeo", - method: "get", - params: { tid } - }); -} - -/** - * 保存企业SEO - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveCompanySeo(data) { - return request({ - url: "/platform/saveCompanySeo", - method: "post", - data: data, - }); -} - -/** - * 获取存储配置 - * @returns {Promise} - */ -export function getStorageConfig() { - return request({ - url: "/platform/storageConfig", - method: "get", - }); -} - -/** - * 保存存储配置 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveStorageConfig(data) { - return request({ - url: "/platform/saveStorageConfig", - method: "post", - data: data, - }); -} - -/** - * 获取 Bark 配置 - * @returns {Promise} - */ -export function getBarkConfig() { - return request({ - url: "/platform/bark/info", - method: "get", - }); -} - -/** - * 保存 Bark 配置 - * @param {Object} data 要保存的数据 - * @returns {Promise} - */ -export function saveBarkConfig(data) { - return request({ - url: "/platform/bark/editinfo", - method: "post", - data: data, - }); -} - -/** - * 发送测试 Bark 推送 - * @param {Object} data 测试数据 - * @returns {Promise} - */ -export function sendTestBark(data) { - return request({ - url: "/platform/bark/sendtest", - method: "post", - data: data, - }); +import request from "@/utils/request"; + +/** + * 获取基本信息 + * @param {number} tid 租户ID + * @returns {Promise} + */ +export function getNormalInfos(tid) { + return request({ + url: "/platform/normalInfos", + method: "get", + params: { tid } + }); +} + +/** + * 保存基本信息 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveNormalInfos(data) { + return request({ + url: "/platform/saveNormalInfos", + method: "post", + data: data, + }); +} + +/** + * 获取登录验证数据 + * @returns {Promise} + */ +export function getVerifyInfos() { + return request({ + url: "/platform/loginVerifyInfos", + method: "get", + }); +} + +/** + * 保存登录验证数据 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveVerifyInfos(data) { + return request({ + url: "/platform/saveloginVerifyInfos", + method: "post", + data: data, + }); +} + +/** + * 获取法律声明和隐私条款 + * @param {number} tid 租户ID + * @returns {Promise} + */ +export function getLegalInfos(tid) { + return request({ + url: "/platform/legalInfos", + method: "get", + params: { tid } + }); +} + +/** + * 保存法律声明和隐私条款 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveLegalInfos(data) { + return request({ + url: "/platform/saveLegalInfos", + method: "post", + data: data, + }); +} + +/** + * 获取企业信息 + * @param {number} tid 租户ID + * @returns {Promise} + */ +export function getCompanyInfos(tid) { + return request({ + url: "/platform/companyInfos", + method: "get", + params: { tid } + }); +} + +/** + * 保存企业信息 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveCompanyInfos(data) { + return request({ + url: "/platform/saveCompanyInfos", + method: "post", + data: data, + }); +} + +/** + * 获取企业SEO + * @param {number} tid 租户ID + * @returns {Promise} + */ +export function getCompanySeo(tid) { + return request({ + url: "/platform/companySeo", + method: "get", + params: { tid } + }); +} + +/** + * 保存企业SEO + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveCompanySeo(data) { + return request({ + url: "/platform/saveCompanySeo", + method: "post", + data: data, + }); +} + +/** + * 获取存储配置 + * @returns {Promise} + */ +export function getStorageConfig() { + return request({ + url: "/platform/storageConfig", + method: "get", + }); +} + +/** + * 保存存储配置 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveStorageConfig(data) { + return request({ + url: "/platform/saveStorageConfig", + method: "post", + data: data, + }); +} + +/** + * 获取 Bark 配置 + * @returns {Promise} + */ +export function getBarkConfig() { + return request({ + url: "/platform/bark/info", + method: "get", + }); +} + +/** + * 保存 Bark 配置 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveBarkConfig(data) { + return request({ + url: "/platform/bark/editinfo", + method: "post", + data: data, + }); +} + +/** + * 发送测试 Bark 推送 + * @param {Object} data 测试数据 + * @returns {Promise} + */ +export function sendTestBark(data) { + return request({ + url: "/platform/bark/sendtest", + method: "post", + data: data, + }); } \ No newline at end of file diff --git a/platform/src/api/sms.ts b/platform/src/api/sms.ts index c38fe7b..af50960 100644 --- a/platform/src/api/sms.ts +++ b/platform/src/api/sms.ts @@ -1,56 +1,56 @@ -import request from "@/utils/request"; - -/** - * 获取短信网关配置 - */ -export function getSmsInfo() { - return request({ - url: "/platform/sms/info", - method: "get", - }); -} - -/** - * 编辑短信网关配置 - */ -export function editSmsInfo(data: any) { - return request({ - url: "/platform/sms/editinfo", - method: "post", - data, - }); -} - -/** - * 发送测试短信(入队任务,等待网关发送) - */ -export function sendTestSms(data: any) { - return request({ - url: "/platform/sms/sendtest", - method: "post", - data, - }); -} - -/** - * 获取短信任务列表(租户隔离) - */ -export function getSmsTaskList(params: { status?: string | number; phone?: string } = {}) { - return request({ - url: "/platform/sms/taskList", - method: "get", - params, - }); -} - -/** - * 编辑短信任务 - */ -export function editSmsTask(id: number | string, data: any) { - return request({ - url: `/platform/sms/taskEdit/${id}`, - method: "post", - data, - }); -} - +import request from "@/utils/request"; + +/** + * 获取短信网关配置 + */ +export function getSmsInfo() { + return request({ + url: "/platform/sms/info", + method: "get", + }); +} + +/** + * 编辑短信网关配置 + */ +export function editSmsInfo(data: any) { + return request({ + url: "/platform/sms/editinfo", + method: "post", + data, + }); +} + +/** + * 发送测试短信(入队任务,等待网关发送) + */ +export function sendTestSms(data: any) { + return request({ + url: "/platform/sms/sendtest", + method: "post", + data, + }); +} + +/** + * 获取短信任务列表(租户隔离) + */ +export function getSmsTaskList(params: { status?: string | number; phone?: string } = {}) { + return request({ + url: "/platform/sms/taskList", + method: "get", + params, + }); +} + +/** + * 编辑短信任务 + */ +export function editSmsTask(id: number | string, data: any) { + return request({ + url: `/platform/sms/taskEdit/${id}`, + method: "post", + data, + }); +} + diff --git a/platform/src/api/softwareUpgrade.js b/platform/src/api/softwareUpgrade.js index c3a4f24..0e12123 100644 --- a/platform/src/api/softwareUpgrade.js +++ b/platform/src/api/softwareUpgrade.js @@ -1,39 +1,39 @@ -import request from "@/utils/request"; - -export function getSoftwareUpgradeList(params) { - return request({ - url: "/platform/softwareupgrade/list", - method: "get", - params, - }); -} - -export function getSoftwareUpgradeDetail(id) { - return request({ - url: `/platform/softwareupgrade/${id}`, - method: "get", - }); -} - -export function createSoftwareUpgrade(data) { - return request({ - url: "/platform/softwareupgrade", - method: "post", - data, - }); -} - -export function updateSoftwareUpgrade(id, data) { - return request({ - url: `/platform/softwareupgrade/${id}`, - method: "post", - data, - }); -} - -export function deleteSoftwareUpgrade(id) { - return request({ - url: `/platform/softwareupgrade/${id}`, - method: "delete", - }); -} +import request from "@/utils/request"; + +export function getSoftwareUpgradeList(params) { + return request({ + url: "/platform/softwareupgrade/list", + method: "get", + params, + }); +} + +export function getSoftwareUpgradeDetail(id) { + return request({ + url: `/platform/softwareupgrade/${id}`, + method: "get", + }); +} + +export function createSoftwareUpgrade(data) { + return request({ + url: "/platform/softwareupgrade", + method: "post", + data, + }); +} + +export function updateSoftwareUpgrade(id, data) { + return request({ + url: `/platform/softwareupgrade/${id}`, + method: "post", + data, + }); +} + +export function deleteSoftwareUpgrade(id) { + return request({ + url: `/platform/softwareupgrade/${id}`, + method: "delete", + }); +} diff --git a/platform/src/api/tenant.js b/platform/src/api/tenant.js index 99cfbbd..dd6a506 100644 --- a/platform/src/api/tenant.js +++ b/platform/src/api/tenant.js @@ -1,84 +1,84 @@ -import request from "@/utils/request"; - -/************************************************* - ****************** 租户相关接口 ****************** - *************************************************/ - -/** - * 获取租户列表 - * @param {Object} params 包含 page 和 pageSize - * @returns {Promise} - */ -export function getTenantList(params) { - return request({ - url: "/platform/tenant/getTenant", - method: "get", - params: params, - }); -} - -/** - * 获取租户详情 - * @param {number} id 租户ID - * @returns {Promise} - */ -export function getTenantDetail(id) { - return request({ - url: `/platform/tenant/getTenantDetail/${id}`, - method: "get", - }); -} - -/** - * 创建租户数据 - * @param {Object} data 租户数据 - * @returns {Promise} - */ -export function createTenant(data) { - return request({ - url: "/platform/tenant/createTenant", - method: "post", - data: data, - headers: { - "Content-Type": "multipart/form-data", - }, - }); -} - -/** - * 更新租户数据 - * @param {Object} data 租户数据 - * @returns {Promise} - */ -export function editTenant(id, data) { - return request({ - url: `/platform/tenant/editTenant/${id}`, - method: "post", - data: data, - }); -} - -/** - * 删除租户数据 - * @param {number} id 租户ID - * @returns {Promise} - */ -export function deleteTenant(id) { - return request({ - url: `/platform/tenant/deleteTenant/${id}`, - method: "delete", - }); -} - -/** - * 校验租户编码是否重复 - * @param {string} tenant_code 编码 - * @param {number} id 可选,当前编辑的租户ID - */ -export function checkTenantCode(tenant_code) { - return request({ - url: '/platform/tenant/findTenantCode', - method: 'get', - params: { tenant_code } - }); +import request from "@/utils/request"; + +/************************************************* + ****************** 租户相关接口 ****************** + *************************************************/ + +/** + * 获取租户列表 + * @param {Object} params 包含 page 和 pageSize + * @returns {Promise} + */ +export function getTenantList(params) { + return request({ + url: "/platform/tenant/getTenant", + method: "get", + params: params, + }); +} + +/** + * 获取租户详情 + * @param {number} id 租户ID + * @returns {Promise} + */ +export function getTenantDetail(id) { + return request({ + url: `/platform/tenant/getTenantDetail/${id}`, + method: "get", + }); +} + +/** + * 创建租户数据 + * @param {Object} data 租户数据 + * @returns {Promise} + */ +export function createTenant(data) { + return request({ + url: "/platform/tenant/createTenant", + method: "post", + data: data, + headers: { + "Content-Type": "multipart/form-data", + }, + }); +} + +/** + * 更新租户数据 + * @param {Object} data 租户数据 + * @returns {Promise} + */ +export function editTenant(id, data) { + return request({ + url: `/platform/tenant/editTenant/${id}`, + method: "post", + data: data, + }); +} + +/** + * 删除租户数据 + * @param {number} id 租户ID + * @returns {Promise} + */ +export function deleteTenant(id) { + return request({ + url: `/platform/tenant/deleteTenant/${id}`, + method: "delete", + }); +} + +/** + * 校验租户编码是否重复 + * @param {string} tenant_code 编码 + * @param {number} id 可选,当前编辑的租户ID + */ +export function checkTenantCode(tenant_code) { + return request({ + url: '/platform/tenant/findTenantCode', + method: 'get', + params: { tenant_code } + }); } \ No newline at end of file diff --git a/platform/src/api/tenantUser.js b/platform/src/api/tenantUser.js index 16490b7..8af0590 100644 --- a/platform/src/api/tenantUser.js +++ b/platform/src/api/tenantUser.js @@ -1,29 +1,29 @@ -import request from "@/utils/request"; - -/** 获取租户用户列表;params 可含 tid、uid、keyword(模糊匹配姓名/手机/邮箱/账号) */ -export function getTenantUserList(params) { - return request({ - url: "/platform/tenantUser/list", - method: "get", - params, - }); -} - -/** 创建租户用户绑定(后端写入 yz_tenant_user) */ -export function createTenantUser(data) { - return request({ - url: "/platform/tenantUser/create", - method: "post", - data, - }); -} - -// 修改租户用户绑定信息(用于修改密码等) -export function editTenantUser(id, data) { - return request({ - url: `/platform/tenantUser/edit/${id}`, - method: "post", - data, - }); -} - +import request from "@/utils/request"; + +/** 获取租户用户列表;params 可含 tid、uid、keyword(模糊匹配姓名/手机/邮箱/账号) */ +export function getTenantUserList(params) { + return request({ + url: "/platform/tenantUser/list", + method: "get", + params, + }); +} + +/** 创建租户用户绑定(后端写入 yz_tenant_user) */ +export function createTenantUser(data) { + return request({ + url: "/platform/tenantUser/create", + method: "post", + data, + }); +} + +// 修改租户用户绑定信息(用于修改密码等) +export function editTenantUser(id, data) { + return request({ + url: `/platform/tenantUser/edit/${id}`, + method: "post", + data, + }); +} + diff --git a/platform/src/api/theme.js b/platform/src/api/theme.js index f64a8a7..5cce7cf 100644 --- a/platform/src/api/theme.js +++ b/platform/src/api/theme.js @@ -1,36 +1,36 @@ -import request from '@/utils/request' - -// 获取模板列表 -export function getThemeList() { - return request({ - url: '/platform/theme', - method: 'get' - }) -} - -// 切换模板 -export function switchTheme(data) { - return request({ - url: '/platform/theme/switch', - method: 'post', - data - }) -} - -// 获取模板数据 -export function getThemeData(params) { - return request({ - url: '/platform/theme/data', - method: 'get', - params - }) -} - -// 保存模板数据 -export function saveThemeData(data) { - return request({ - url: '/platform/theme/data', - method: 'post', - data - }) -} +import request from '@/utils/request' + +// 获取模板列表 +export function getThemeList() { + return request({ + url: '/platform/theme', + method: 'get' + }) +} + +// 切换模板 +export function switchTheme(data) { + return request({ + url: '/platform/theme/switch', + method: 'post', + data + }) +} + +// 获取模板数据 +export function getThemeData(params) { + return request({ + url: '/platform/theme/data', + method: 'get', + params + }) +} + +// 保存模板数据 +export function saveThemeData(data) { + return request({ + url: '/platform/theme/data', + method: 'post', + data + }) +} diff --git a/platform/src/api/user.js b/platform/src/api/user.js index 0e34be0..0cdfb74 100644 --- a/platform/src/api/user.js +++ b/platform/src/api/user.js @@ -1,68 +1,68 @@ -import request from '@/utils/request'; - -//获取所有用户信息 -export function getAllUsers() { - return request({ - url: '/platform/getAllUsers', - method: 'get', - }); -} - -//获取租户用户 -export function getTenantUsers(tenantId) { - return request({ - url: `/platform/getTenantUsers/${tenantId}`, - method: 'get', - }); -} - -// 获取用户信息 -export function getUserInfo(userId) { - return request({ - url: `/platform/getUserInfo/${userId}`, - method: 'get', - }); -} - -// 添加用户 -export function addUser(data) { - return request({ - url: '/platform/addUser', - method: 'post', - data, - }); -} - -// 编辑用户信息 -export function editUser(userId, data) { - return request({ - url: `/platform/editUser/${userId}`, - method: 'post', - data, - }); -} - -// 更新用户信息(编辑用户的别名) -export function updateUserInfo(userId, data) { - return editUser(userId, data); -} - -// 删除用户 -export function deleteUser(userId) { - return request({ - url: `/platform/deleteUser/${userId}`, - method: 'delete', - }); -} - -// 修改密码 -export function changePassword(userId, data) { - return request({ - url: '/platform/changePassword', - method: 'post', - data: { - id: userId, - password: data.newPassword - }, - }); +import request from '@/utils/request'; + +//获取所有用户信息 +export function getAllUsers() { + return request({ + url: '/platform/getAllUsers', + method: 'get', + }); +} + +//获取租户用户 +export function getTenantUsers(tenantId) { + return request({ + url: `/platform/getTenantUsers/${tenantId}`, + method: 'get', + }); +} + +// 获取用户信息 +export function getUserInfo(userId) { + return request({ + url: `/platform/getUserInfo/${userId}`, + method: 'get', + }); +} + +// 添加用户 +export function addUser(data) { + return request({ + url: '/platform/addUser', + method: 'post', + data, + }); +} + +// 编辑用户信息 +export function editUser(userId, data) { + return request({ + url: `/platform/editUser/${userId}`, + method: 'post', + data, + }); +} + +// 更新用户信息(编辑用户的别名) +export function updateUserInfo(userId, data) { + return editUser(userId, data); +} + +// 删除用户 +export function deleteUser(userId) { + return request({ + url: `/platform/deleteUser/${userId}`, + method: 'delete', + }); +} + +// 修改密码 +export function changePassword(userId, data) { + return request({ + url: '/platform/changePassword', + method: 'post', + data: { + id: userId, + password: data.newPassword + }, + }); } \ No newline at end of file diff --git a/platform/src/api/workbench.js b/platform/src/api/workbench.js index 1fd035d..1efdeb0 100644 --- a/platform/src/api/workbench.js +++ b/platform/src/api/workbench.js @@ -1,11 +1,11 @@ -// 文章管理相关API -import request from "@/utils/request"; - -// 获取文章列表 -export function GetCRMWorkbench(params) { - return request({ - url: `/platform/workbench/crm`, - method: "get", - params, - }); -} +// 文章管理相关API +import request from "@/utils/request"; + +// 获取文章列表 +export function GetCRMWorkbench(params) { + return request({ + url: `/platform/workbench/crm`, + method: "get", + params, + }); +} diff --git a/platform/src/assets/css/all.min.css b/platform/src/assets/css/all.min.css index 6591894..b9cb950 100644 --- a/platform/src/assets/css/all.min.css +++ b/platform/src/assets/css/all.min.css @@ -1,9 +1,9 @@ -/*! - * Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - * Copyright 2025 Fonticons, Inc. - */ -.fa,.fa-brands,.fa-classic,.fa-regular,.fa-solid,.fab,.far,.fas{--_fa-family:var(--fa-family,var(--fa-style-family,"Font Awesome 7 Free"));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:var(--fa-display,inline-block);font-family:var(--_fa-family);font-feature-settings:normal;font-style:normal;font-synthesis:none;font-variant:normal;font-weight:var(--fa-style,900);line-height:1;text-align:center;text-rendering:auto;width:var(--fa-width,1.25em)}:is(.fas,.far,.fab,.fa-solid,.fa-regular,.fa-brands,.fa-classic,.fa):before{content:var(--fa);content:var(--fa)/""}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-width-auto{--fa-width:auto}.fa-fw,.fa-width-fixed{--fa-width:1.25em}.fa-ul{list-style-type:none;margin-inline-start:var(--fa-li-margin,2.5em);padding-inline-start:0}.fa-ul>li{position:relative}.fa-li{inset-inline-start:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.0625em) var(--fa-border-style,solid) var(--fa-border-color,#eee);box-sizing:var(--fa-border-box-sizing,content-box);padding:var(--fa-border-padding,.1875em .25em)}.fa-pull-left,.fa-pull-start{float:inline-start;margin-inline-end:var(--fa-pull-margin,.3em)}.fa-pull-end,.fa-pull-right{float:inline-end;margin-inline-start:var(--fa-pull-margin,.3em)}.fa-beat{animation-name:fa-beat;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{animation-name:fa-bounce;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{animation-name:fa-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{animation-name:fa-beat-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{animation-name:fa-flip;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{animation-name:fa-shake;animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{animation-name:fa-spin;animation-duration:var(--fa-animation-duration,2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{animation-name:fa-spin;animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{animation:none!important;transition:none!important}}@keyframes fa-beat{0%,90%{transform:scale(1)}45%{transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-bounce{0%{transform:scale(1) translateY(0)}10%{transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{transform:scale(1) translateY(0)}to{transform:scale(1) translateY(0)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);transform:scale(1)}50%{opacity:1;transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-flip{50%{transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-shake{0%{transform:rotate(-15deg)}4%{transform:rotate(15deg)}8%,24%{transform:rotate(-18deg)}12%,28%{transform:rotate(18deg)}16%{transform:rotate(-22deg)}20%{transform:rotate(22deg)}32%{transform:rotate(-12deg)}36%{transform:rotate(12deg)}40%,to{transform:rotate(0deg)}}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}.fa-rotate-by{transform:rotate(var(--fa-rotate-angle,0))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} - -.fa-0{--fa:"\30 "}.fa-1{--fa:"\31 "}.fa-2{--fa:"\32 "}.fa-3{--fa:"\33 "}.fa-4{--fa:"\34 "}.fa-5{--fa:"\35 "}.fa-6{--fa:"\36 "}.fa-7{--fa:"\37 "}.fa-8{--fa:"\38 "}.fa-9{--fa:"\39 "}.fa-exclamation{--fa:"\!"}.fa-hashtag{--fa:"\#"}.fa-dollar,.fa-dollar-sign,.fa-usd{--fa:"\$"}.fa-percent,.fa-percentage{--fa:"\%"}.fa-asterisk{--fa:"\*"}.fa-add,.fa-plus{--fa:"\+"}.fa-less-than{--fa:"\<"}.fa-equals{--fa:"\="}.fa-greater-than{--fa:"\>"}.fa-question{--fa:"\?"}.fa-at{--fa:"\@"}.fa-a{--fa:"A"}.fa-b{--fa:"B"}.fa-c{--fa:"C"}.fa-d{--fa:"D"}.fa-e{--fa:"E"}.fa-f{--fa:"F"}.fa-g{--fa:"G"}.fa-h{--fa:"H"}.fa-i{--fa:"I"}.fa-j{--fa:"J"}.fa-k{--fa:"K"}.fa-l{--fa:"L"}.fa-m{--fa:"M"}.fa-n{--fa:"N"}.fa-o{--fa:"O"}.fa-p{--fa:"P"}.fa-q{--fa:"Q"}.fa-r{--fa:"R"}.fa-s{--fa:"S"}.fa-t{--fa:"T"}.fa-u{--fa:"U"}.fa-v{--fa:"V"}.fa-w{--fa:"W"}.fa-x{--fa:"X"}.fa-y{--fa:"Y"}.fa-z{--fa:"Z"}.fa-faucet{--fa:"\e005"}.fa-faucet-drip{--fa:"\e006"}.fa-house-chimney-window{--fa:"\e00d"}.fa-house-signal{--fa:"\e012"}.fa-temperature-arrow-down,.fa-temperature-down{--fa:"\e03f"}.fa-temperature-arrow-up,.fa-temperature-up{--fa:"\e040"}.fa-trailer{--fa:"\e041"}.fa-bacteria{--fa:"\e059"}.fa-bacterium{--fa:"\e05a"}.fa-box-tissue{--fa:"\e05b"}.fa-hand-holding-medical{--fa:"\e05c"}.fa-hand-sparkles{--fa:"\e05d"}.fa-hands-bubbles,.fa-hands-wash{--fa:"\e05e"}.fa-handshake-alt-slash,.fa-handshake-simple-slash,.fa-handshake-slash{--fa:"\e060"}.fa-head-side-cough{--fa:"\e061"}.fa-head-side-cough-slash{--fa:"\e062"}.fa-head-side-mask{--fa:"\e063"}.fa-head-side-virus{--fa:"\e064"}.fa-house-chimney-user{--fa:"\e065"}.fa-house-laptop,.fa-laptop-house{--fa:"\e066"}.fa-lungs-virus{--fa:"\e067"}.fa-people-arrows,.fa-people-arrows-left-right{--fa:"\e068"}.fa-plane-slash{--fa:"\e069"}.fa-pump-medical{--fa:"\e06a"}.fa-pump-soap{--fa:"\e06b"}.fa-shield-virus{--fa:"\e06c"}.fa-sink{--fa:"\e06d"}.fa-soap{--fa:"\e06e"}.fa-stopwatch-20{--fa:"\e06f"}.fa-shop-slash,.fa-store-alt-slash{--fa:"\e070"}.fa-store-slash{--fa:"\e071"}.fa-toilet-paper-slash{--fa:"\e072"}.fa-users-slash{--fa:"\e073"}.fa-virus{--fa:"\e074"}.fa-virus-slash{--fa:"\e075"}.fa-viruses{--fa:"\e076"}.fa-vest{--fa:"\e085"}.fa-vest-patches{--fa:"\e086"}.fa-arrow-trend-down{--fa:"\e097"}.fa-arrow-trend-up{--fa:"\e098"}.fa-arrow-up-from-bracket{--fa:"\e09a"}.fa-austral-sign{--fa:"\e0a9"}.fa-baht-sign{--fa:"\e0ac"}.fa-bitcoin-sign{--fa:"\e0b4"}.fa-bolt-lightning{--fa:"\e0b7"}.fa-book-bookmark{--fa:"\e0bb"}.fa-camera-rotate{--fa:"\e0d8"}.fa-cedi-sign{--fa:"\e0df"}.fa-chart-column{--fa:"\e0e3"}.fa-chart-gantt{--fa:"\e0e4"}.fa-clapperboard{--fa:"\e131"}.fa-clover{--fa:"\e139"}.fa-code-compare{--fa:"\e13a"}.fa-code-fork{--fa:"\e13b"}.fa-code-pull-request{--fa:"\e13c"}.fa-colon-sign{--fa:"\e140"}.fa-cruzeiro-sign{--fa:"\e152"}.fa-display{--fa:"\e163"}.fa-dong-sign{--fa:"\e169"}.fa-elevator{--fa:"\e16d"}.fa-filter-circle-xmark{--fa:"\e17b"}.fa-florin-sign{--fa:"\e184"}.fa-folder-closed{--fa:"\e185"}.fa-franc-sign{--fa:"\e18f"}.fa-guarani-sign{--fa:"\e19a"}.fa-gun{--fa:"\e19b"}.fa-hands-clapping{--fa:"\e1a8"}.fa-home-user,.fa-house-user{--fa:"\e1b0"}.fa-indian-rupee,.fa-indian-rupee-sign,.fa-inr{--fa:"\e1bc"}.fa-kip-sign{--fa:"\e1c4"}.fa-lari-sign{--fa:"\e1c8"}.fa-litecoin-sign{--fa:"\e1d3"}.fa-manat-sign{--fa:"\e1d5"}.fa-mask-face{--fa:"\e1d7"}.fa-mill-sign{--fa:"\e1ed"}.fa-money-bills{--fa:"\e1f3"}.fa-naira-sign{--fa:"\e1f6"}.fa-notdef{--fa:"\e1fe"}.fa-panorama{--fa:"\e209"}.fa-peseta-sign{--fa:"\e221"}.fa-peso-sign{--fa:"\e222"}.fa-plane-up{--fa:"\e22d"}.fa-rupiah-sign{--fa:"\e23d"}.fa-stairs{--fa:"\e289"}.fa-timeline{--fa:"\e29c"}.fa-truck-front{--fa:"\e2b7"}.fa-try,.fa-turkish-lira,.fa-turkish-lira-sign{--fa:"\e2bb"}.fa-vault{--fa:"\e2c5"}.fa-magic-wand-sparkles,.fa-wand-magic-sparkles{--fa:"\e2ca"}.fa-wheat-alt,.fa-wheat-awn{--fa:"\e2cd"}.fa-wheelchair-alt,.fa-wheelchair-move{--fa:"\e2ce"}.fa-bangladeshi-taka-sign{--fa:"\e2e6"}.fa-bowl-rice{--fa:"\e2eb"}.fa-person-pregnant{--fa:"\e31e"}.fa-home-lg,.fa-house-chimney{--fa:"\e3af"}.fa-house-crack{--fa:"\e3b1"}.fa-house-medical{--fa:"\e3b2"}.fa-cent-sign{--fa:"\e3f5"}.fa-plus-minus{--fa:"\e43c"}.fa-sailboat{--fa:"\e445"}.fa-section{--fa:"\e447"}.fa-shrimp{--fa:"\e448"}.fa-brazilian-real-sign{--fa:"\e46c"}.fa-chart-simple{--fa:"\e473"}.fa-diagram-next{--fa:"\e476"}.fa-diagram-predecessor{--fa:"\e477"}.fa-diagram-successor{--fa:"\e47a"}.fa-earth-oceania,.fa-globe-oceania{--fa:"\e47b"}.fa-bug-slash{--fa:"\e490"}.fa-file-circle-plus{--fa:"\e494"}.fa-shop-lock{--fa:"\e4a5"}.fa-virus-covid{--fa:"\e4a8"}.fa-virus-covid-slash{--fa:"\e4a9"}.fa-anchor-circle-check{--fa:"\e4aa"}.fa-anchor-circle-exclamation{--fa:"\e4ab"}.fa-anchor-circle-xmark{--fa:"\e4ac"}.fa-anchor-lock{--fa:"\e4ad"}.fa-arrow-down-up-across-line{--fa:"\e4af"}.fa-arrow-down-up-lock{--fa:"\e4b0"}.fa-arrow-right-to-city{--fa:"\e4b3"}.fa-arrow-up-from-ground-water{--fa:"\e4b5"}.fa-arrow-up-from-water-pump{--fa:"\e4b6"}.fa-arrow-up-right-dots{--fa:"\e4b7"}.fa-arrows-down-to-line{--fa:"\e4b8"}.fa-arrows-down-to-people{--fa:"\e4b9"}.fa-arrows-left-right-to-line{--fa:"\e4ba"}.fa-arrows-spin{--fa:"\e4bb"}.fa-arrows-split-up-and-left{--fa:"\e4bc"}.fa-arrows-to-circle{--fa:"\e4bd"}.fa-arrows-to-dot{--fa:"\e4be"}.fa-arrows-to-eye{--fa:"\e4bf"}.fa-arrows-turn-right{--fa:"\e4c0"}.fa-arrows-turn-to-dots{--fa:"\e4c1"}.fa-arrows-up-to-line{--fa:"\e4c2"}.fa-bore-hole{--fa:"\e4c3"}.fa-bottle-droplet{--fa:"\e4c4"}.fa-bottle-water{--fa:"\e4c5"}.fa-bowl-food{--fa:"\e4c6"}.fa-boxes-packing{--fa:"\e4c7"}.fa-bridge{--fa:"\e4c8"}.fa-bridge-circle-check{--fa:"\e4c9"}.fa-bridge-circle-exclamation{--fa:"\e4ca"}.fa-bridge-circle-xmark{--fa:"\e4cb"}.fa-bridge-lock{--fa:"\e4cc"}.fa-bridge-water{--fa:"\e4ce"}.fa-bucket{--fa:"\e4cf"}.fa-bugs{--fa:"\e4d0"}.fa-building-circle-arrow-right{--fa:"\e4d1"}.fa-building-circle-check{--fa:"\e4d2"}.fa-building-circle-exclamation{--fa:"\e4d3"}.fa-building-circle-xmark{--fa:"\e4d4"}.fa-building-flag{--fa:"\e4d5"}.fa-building-lock{--fa:"\e4d6"}.fa-building-ngo{--fa:"\e4d7"}.fa-building-shield{--fa:"\e4d8"}.fa-building-un{--fa:"\e4d9"}.fa-building-user{--fa:"\e4da"}.fa-building-wheat{--fa:"\e4db"}.fa-burst{--fa:"\e4dc"}.fa-car-on{--fa:"\e4dd"}.fa-car-tunnel{--fa:"\e4de"}.fa-child-combatant,.fa-child-rifle{--fa:"\e4e0"}.fa-children{--fa:"\e4e1"}.fa-circle-nodes{--fa:"\e4e2"}.fa-clipboard-question{--fa:"\e4e3"}.fa-cloud-showers-water{--fa:"\e4e4"}.fa-computer{--fa:"\e4e5"}.fa-cubes-stacked{--fa:"\e4e6"}.fa-envelope-circle-check{--fa:"\e4e8"}.fa-explosion{--fa:"\e4e9"}.fa-ferry{--fa:"\e4ea"}.fa-file-circle-exclamation{--fa:"\e4eb"}.fa-file-circle-minus{--fa:"\e4ed"}.fa-file-circle-question{--fa:"\e4ef"}.fa-file-shield{--fa:"\e4f0"}.fa-fire-burner{--fa:"\e4f1"}.fa-fish-fins{--fa:"\e4f2"}.fa-flask-vial{--fa:"\e4f3"}.fa-glass-water{--fa:"\e4f4"}.fa-glass-water-droplet{--fa:"\e4f5"}.fa-group-arrows-rotate{--fa:"\e4f6"}.fa-hand-holding-hand{--fa:"\e4f7"}.fa-handcuffs{--fa:"\e4f8"}.fa-hands-bound{--fa:"\e4f9"}.fa-hands-holding-child{--fa:"\e4fa"}.fa-hands-holding-circle{--fa:"\e4fb"}.fa-heart-circle-bolt{--fa:"\e4fc"}.fa-heart-circle-check{--fa:"\e4fd"}.fa-heart-circle-exclamation{--fa:"\e4fe"}.fa-heart-circle-minus{--fa:"\e4ff"}.fa-heart-circle-plus{--fa:"\e500"}.fa-heart-circle-xmark{--fa:"\e501"}.fa-helicopter-symbol{--fa:"\e502"}.fa-helmet-un{--fa:"\e503"}.fa-hill-avalanche{--fa:"\e507"}.fa-hill-rockslide{--fa:"\e508"}.fa-house-circle-check{--fa:"\e509"}.fa-house-circle-exclamation{--fa:"\e50a"}.fa-house-circle-xmark{--fa:"\e50b"}.fa-house-fire{--fa:"\e50c"}.fa-house-flag{--fa:"\e50d"}.fa-house-flood-water{--fa:"\e50e"}.fa-house-flood-water-circle-arrow-right{--fa:"\e50f"}.fa-house-lock{--fa:"\e510"}.fa-house-medical-circle-check{--fa:"\e511"}.fa-house-medical-circle-exclamation{--fa:"\e512"}.fa-house-medical-circle-xmark{--fa:"\e513"}.fa-house-medical-flag{--fa:"\e514"}.fa-house-tsunami{--fa:"\e515"}.fa-jar{--fa:"\e516"}.fa-jar-wheat{--fa:"\e517"}.fa-jet-fighter-up{--fa:"\e518"}.fa-jug-detergent{--fa:"\e519"}.fa-kitchen-set{--fa:"\e51a"}.fa-land-mine-on{--fa:"\e51b"}.fa-landmark-flag{--fa:"\e51c"}.fa-laptop-file{--fa:"\e51d"}.fa-lines-leaning{--fa:"\e51e"}.fa-location-pin-lock{--fa:"\e51f"}.fa-locust{--fa:"\e520"}.fa-magnifying-glass-arrow-right{--fa:"\e521"}.fa-magnifying-glass-chart{--fa:"\e522"}.fa-mars-and-venus-burst{--fa:"\e523"}.fa-mask-ventilator{--fa:"\e524"}.fa-mattress-pillow{--fa:"\e525"}.fa-mobile-retro{--fa:"\e527"}.fa-money-bill-transfer{--fa:"\e528"}.fa-money-bill-trend-up{--fa:"\e529"}.fa-money-bill-wheat{--fa:"\e52a"}.fa-mosquito{--fa:"\e52b"}.fa-mosquito-net{--fa:"\e52c"}.fa-mound{--fa:"\e52d"}.fa-mountain-city{--fa:"\e52e"}.fa-mountain-sun{--fa:"\e52f"}.fa-oil-well{--fa:"\e532"}.fa-people-group{--fa:"\e533"}.fa-people-line{--fa:"\e534"}.fa-people-pulling{--fa:"\e535"}.fa-people-robbery{--fa:"\e536"}.fa-people-roof{--fa:"\e537"}.fa-person-arrow-down-to-line{--fa:"\e538"}.fa-person-arrow-up-from-line{--fa:"\e539"}.fa-person-breastfeeding{--fa:"\e53a"}.fa-person-burst{--fa:"\e53b"}.fa-person-cane{--fa:"\e53c"}.fa-person-chalkboard{--fa:"\e53d"}.fa-person-circle-check{--fa:"\e53e"}.fa-person-circle-exclamation{--fa:"\e53f"}.fa-person-circle-minus{--fa:"\e540"}.fa-person-circle-plus{--fa:"\e541"}.fa-person-circle-question{--fa:"\e542"}.fa-person-circle-xmark{--fa:"\e543"}.fa-person-dress-burst{--fa:"\e544"}.fa-person-drowning{--fa:"\e545"}.fa-person-falling{--fa:"\e546"}.fa-person-falling-burst{--fa:"\e547"}.fa-person-half-dress{--fa:"\e548"}.fa-person-harassing{--fa:"\e549"}.fa-person-military-pointing{--fa:"\e54a"}.fa-person-military-rifle{--fa:"\e54b"}.fa-person-military-to-person{--fa:"\e54c"}.fa-person-rays{--fa:"\e54d"}.fa-person-rifle{--fa:"\e54e"}.fa-person-shelter{--fa:"\e54f"}.fa-person-walking-arrow-loop-left{--fa:"\e551"}.fa-person-walking-arrow-right{--fa:"\e552"}.fa-person-walking-dashed-line-arrow-right{--fa:"\e553"}.fa-person-walking-luggage{--fa:"\e554"}.fa-plane-circle-check{--fa:"\e555"}.fa-plane-circle-exclamation{--fa:"\e556"}.fa-plane-circle-xmark{--fa:"\e557"}.fa-plane-lock{--fa:"\e558"}.fa-plate-wheat{--fa:"\e55a"}.fa-plug-circle-bolt{--fa:"\e55b"}.fa-plug-circle-check{--fa:"\e55c"}.fa-plug-circle-exclamation{--fa:"\e55d"}.fa-plug-circle-minus{--fa:"\e55e"}.fa-plug-circle-plus{--fa:"\e55f"}.fa-plug-circle-xmark{--fa:"\e560"}.fa-ranking-star{--fa:"\e561"}.fa-road-barrier{--fa:"\e562"}.fa-road-bridge{--fa:"\e563"}.fa-road-circle-check{--fa:"\e564"}.fa-road-circle-exclamation{--fa:"\e565"}.fa-road-circle-xmark{--fa:"\e566"}.fa-road-lock{--fa:"\e567"}.fa-road-spikes{--fa:"\e568"}.fa-rug{--fa:"\e569"}.fa-sack-xmark{--fa:"\e56a"}.fa-school-circle-check{--fa:"\e56b"}.fa-school-circle-exclamation{--fa:"\e56c"}.fa-school-circle-xmark{--fa:"\e56d"}.fa-school-flag{--fa:"\e56e"}.fa-school-lock{--fa:"\e56f"}.fa-sheet-plastic{--fa:"\e571"}.fa-shield-cat{--fa:"\e572"}.fa-shield-dog{--fa:"\e573"}.fa-shield-heart{--fa:"\e574"}.fa-square-nfi{--fa:"\e576"}.fa-square-person-confined{--fa:"\e577"}.fa-square-virus{--fa:"\e578"}.fa-rod-asclepius,.fa-rod-snake,.fa-staff-aesculapius,.fa-staff-snake{--fa:"\e579"}.fa-sun-plant-wilt{--fa:"\e57a"}.fa-tarp{--fa:"\e57b"}.fa-tarp-droplet{--fa:"\e57c"}.fa-tent{--fa:"\e57d"}.fa-tent-arrow-down-to-line{--fa:"\e57e"}.fa-tent-arrow-left-right{--fa:"\e57f"}.fa-tent-arrow-turn-left{--fa:"\e580"}.fa-tent-arrows-down{--fa:"\e581"}.fa-tents{--fa:"\e582"}.fa-toilet-portable{--fa:"\e583"}.fa-toilets-portable{--fa:"\e584"}.fa-tower-cell{--fa:"\e585"}.fa-tower-observation{--fa:"\e586"}.fa-tree-city{--fa:"\e587"}.fa-trowel{--fa:"\e589"}.fa-trowel-bricks{--fa:"\e58a"}.fa-truck-arrow-right{--fa:"\e58b"}.fa-truck-droplet{--fa:"\e58c"}.fa-truck-field{--fa:"\e58d"}.fa-truck-field-un{--fa:"\e58e"}.fa-truck-plane{--fa:"\e58f"}.fa-users-between-lines{--fa:"\e591"}.fa-users-line{--fa:"\e592"}.fa-users-rays{--fa:"\e593"}.fa-users-rectangle{--fa:"\e594"}.fa-users-viewfinder{--fa:"\e595"}.fa-vial-circle-check{--fa:"\e596"}.fa-vial-virus{--fa:"\e597"}.fa-wheat-awn-circle-exclamation{--fa:"\e598"}.fa-worm{--fa:"\e599"}.fa-xmarks-lines{--fa:"\e59a"}.fa-child-dress{--fa:"\e59c"}.fa-child-reaching{--fa:"\e59d"}.fa-file-circle-check{--fa:"\e5a0"}.fa-file-circle-xmark{--fa:"\e5a1"}.fa-person-through-window{--fa:"\e5a9"}.fa-plant-wilt{--fa:"\e5aa"}.fa-stapler{--fa:"\e5af"}.fa-train-tram{--fa:"\e5b4"}.fa-table-cells-column-lock{--fa:"\e678"}.fa-table-cells-row-lock{--fa:"\e67a"}.fa-thumb-tack-slash,.fa-thumbtack-slash{--fa:"\e68f"}.fa-table-cells-row-unlock{--fa:"\e691"}.fa-chart-diagram{--fa:"\e695"}.fa-comment-nodes{--fa:"\e696"}.fa-file-fragment{--fa:"\e697"}.fa-file-half-dashed{--fa:"\e698"}.fa-hexagon-nodes{--fa:"\e699"}.fa-hexagon-nodes-bolt{--fa:"\e69a"}.fa-square-binary{--fa:"\e69b"}.fa-pentagon{--fa:"\e790"}.fa-non-binary{--fa:"\e807"}.fa-spiral{--fa:"\e80a"}.fa-mobile-vibrate{--fa:"\e816"}.fa-single-quote-left{--fa:"\e81b"}.fa-single-quote-right{--fa:"\e81c"}.fa-bus-side{--fa:"\e81d"}.fa-heptagon,.fa-septagon{--fa:"\e820"}.fa-glass-martini,.fa-martini-glass-empty{--fa:"\f000"}.fa-music{--fa:"\f001"}.fa-magnifying-glass,.fa-search{--fa:"\f002"}.fa-heart{--fa:"\f004"}.fa-star{--fa:"\f005"}.fa-user,.fa-user-alt,.fa-user-large{--fa:"\f007"}.fa-film,.fa-film-alt,.fa-film-simple{--fa:"\f008"}.fa-table-cells-large,.fa-th-large{--fa:"\f009"}.fa-table-cells,.fa-th{--fa:"\f00a"}.fa-table-list,.fa-th-list{--fa:"\f00b"}.fa-check{--fa:"\f00c"}.fa-close,.fa-multiply,.fa-remove,.fa-times,.fa-xmark{--fa:"\f00d"}.fa-magnifying-glass-plus,.fa-search-plus{--fa:"\f00e"}.fa-magnifying-glass-minus,.fa-search-minus{--fa:"\f010"}.fa-power-off{--fa:"\f011"}.fa-signal,.fa-signal-5,.fa-signal-perfect{--fa:"\f012"}.fa-cog,.fa-gear{--fa:"\f013"}.fa-home,.fa-home-alt,.fa-home-lg-alt,.fa-house{--fa:"\f015"}.fa-clock,.fa-clock-four{--fa:"\f017"}.fa-road{--fa:"\f018"}.fa-download{--fa:"\f019"}.fa-inbox{--fa:"\f01c"}.fa-arrow-right-rotate,.fa-arrow-rotate-forward,.fa-arrow-rotate-right,.fa-redo{--fa:"\f01e"}.fa-arrows-rotate,.fa-refresh,.fa-sync{--fa:"\f021"}.fa-list-alt,.fa-rectangle-list{--fa:"\f022"}.fa-lock{--fa:"\f023"}.fa-flag{--fa:"\f024"}.fa-headphones,.fa-headphones-alt,.fa-headphones-simple{--fa:"\f025"}.fa-volume-off{--fa:"\f026"}.fa-volume-down,.fa-volume-low{--fa:"\f027"}.fa-volume-high,.fa-volume-up{--fa:"\f028"}.fa-qrcode{--fa:"\f029"}.fa-barcode{--fa:"\f02a"}.fa-tag{--fa:"\f02b"}.fa-tags{--fa:"\f02c"}.fa-book{--fa:"\f02d"}.fa-bookmark{--fa:"\f02e"}.fa-print{--fa:"\f02f"}.fa-camera,.fa-camera-alt{--fa:"\f030"}.fa-font{--fa:"\f031"}.fa-bold{--fa:"\f032"}.fa-italic{--fa:"\f033"}.fa-text-height{--fa:"\f034"}.fa-text-width{--fa:"\f035"}.fa-align-left{--fa:"\f036"}.fa-align-center{--fa:"\f037"}.fa-align-right{--fa:"\f038"}.fa-align-justify{--fa:"\f039"}.fa-list,.fa-list-squares{--fa:"\f03a"}.fa-dedent,.fa-outdent{--fa:"\f03b"}.fa-indent{--fa:"\f03c"}.fa-video,.fa-video-camera{--fa:"\f03d"}.fa-image{--fa:"\f03e"}.fa-location-pin,.fa-map-marker{--fa:"\f041"}.fa-adjust,.fa-circle-half-stroke{--fa:"\f042"}.fa-droplet,.fa-tint{--fa:"\f043"}.fa-edit,.fa-pen-to-square{--fa:"\f044"}.fa-arrows,.fa-arrows-up-down-left-right{--fa:"\f047"}.fa-backward-step,.fa-step-backward{--fa:"\f048"}.fa-backward-fast,.fa-fast-backward{--fa:"\f049"}.fa-backward{--fa:"\f04a"}.fa-play{--fa:"\f04b"}.fa-pause{--fa:"\f04c"}.fa-stop{--fa:"\f04d"}.fa-forward{--fa:"\f04e"}.fa-fast-forward,.fa-forward-fast{--fa:"\f050"}.fa-forward-step,.fa-step-forward{--fa:"\f051"}.fa-eject{--fa:"\f052"}.fa-chevron-left{--fa:"\f053"}.fa-chevron-right{--fa:"\f054"}.fa-circle-plus,.fa-plus-circle{--fa:"\f055"}.fa-circle-minus,.fa-minus-circle{--fa:"\f056"}.fa-circle-xmark,.fa-times-circle,.fa-xmark-circle{--fa:"\f057"}.fa-check-circle,.fa-circle-check{--fa:"\f058"}.fa-circle-question,.fa-question-circle{--fa:"\f059"}.fa-circle-info,.fa-info-circle{--fa:"\f05a"}.fa-crosshairs{--fa:"\f05b"}.fa-ban,.fa-cancel{--fa:"\f05e"}.fa-arrow-left{--fa:"\f060"}.fa-arrow-right{--fa:"\f061"}.fa-arrow-up{--fa:"\f062"}.fa-arrow-down{--fa:"\f063"}.fa-mail-forward,.fa-share{--fa:"\f064"}.fa-expand{--fa:"\f065"}.fa-compress{--fa:"\f066"}.fa-minus,.fa-subtract{--fa:"\f068"}.fa-circle-exclamation,.fa-exclamation-circle{--fa:"\f06a"}.fa-gift{--fa:"\f06b"}.fa-leaf{--fa:"\f06c"}.fa-fire{--fa:"\f06d"}.fa-eye{--fa:"\f06e"}.fa-eye-slash{--fa:"\f070"}.fa-exclamation-triangle,.fa-triangle-exclamation,.fa-warning{--fa:"\f071"}.fa-plane{--fa:"\f072"}.fa-calendar-alt,.fa-calendar-days{--fa:"\f073"}.fa-random,.fa-shuffle{--fa:"\f074"}.fa-comment{--fa:"\f075"}.fa-magnet{--fa:"\f076"}.fa-chevron-up{--fa:"\f077"}.fa-chevron-down{--fa:"\f078"}.fa-retweet{--fa:"\f079"}.fa-cart-shopping,.fa-shopping-cart{--fa:"\f07a"}.fa-folder,.fa-folder-blank{--fa:"\f07b"}.fa-folder-open{--fa:"\f07c"}.fa-arrows-up-down,.fa-arrows-v{--fa:"\f07d"}.fa-arrows-h,.fa-arrows-left-right{--fa:"\f07e"}.fa-bar-chart,.fa-chart-bar{--fa:"\f080"}.fa-camera-retro{--fa:"\f083"}.fa-key{--fa:"\f084"}.fa-cogs,.fa-gears{--fa:"\f085"}.fa-comments{--fa:"\f086"}.fa-star-half{--fa:"\f089"}.fa-arrow-right-from-bracket,.fa-sign-out{--fa:"\f08b"}.fa-thumb-tack,.fa-thumbtack{--fa:"\f08d"}.fa-arrow-up-right-from-square,.fa-external-link{--fa:"\f08e"}.fa-arrow-right-to-bracket,.fa-sign-in{--fa:"\f090"}.fa-trophy{--fa:"\f091"}.fa-upload{--fa:"\f093"}.fa-lemon{--fa:"\f094"}.fa-phone{--fa:"\f095"}.fa-phone-square,.fa-square-phone{--fa:"\f098"}.fa-unlock{--fa:"\f09c"}.fa-credit-card,.fa-credit-card-alt{--fa:"\f09d"}.fa-feed,.fa-rss{--fa:"\f09e"}.fa-hard-drive,.fa-hdd{--fa:"\f0a0"}.fa-bullhorn{--fa:"\f0a1"}.fa-certificate{--fa:"\f0a3"}.fa-hand-point-right{--fa:"\f0a4"}.fa-hand-point-left{--fa:"\f0a5"}.fa-hand-point-up{--fa:"\f0a6"}.fa-hand-point-down{--fa:"\f0a7"}.fa-arrow-circle-left,.fa-circle-arrow-left{--fa:"\f0a8"}.fa-arrow-circle-right,.fa-circle-arrow-right{--fa:"\f0a9"}.fa-arrow-circle-up,.fa-circle-arrow-up{--fa:"\f0aa"}.fa-arrow-circle-down,.fa-circle-arrow-down{--fa:"\f0ab"}.fa-globe{--fa:"\f0ac"}.fa-wrench{--fa:"\f0ad"}.fa-list-check,.fa-tasks{--fa:"\f0ae"}.fa-filter{--fa:"\f0b0"}.fa-briefcase{--fa:"\f0b1"}.fa-arrows-alt,.fa-up-down-left-right{--fa:"\f0b2"}.fa-users{--fa:"\f0c0"}.fa-chain,.fa-link{--fa:"\f0c1"}.fa-cloud{--fa:"\f0c2"}.fa-flask{--fa:"\f0c3"}.fa-cut,.fa-scissors{--fa:"\f0c4"}.fa-copy{--fa:"\f0c5"}.fa-paperclip{--fa:"\f0c6"}.fa-floppy-disk,.fa-save{--fa:"\f0c7"}.fa-square{--fa:"\f0c8"}.fa-bars,.fa-navicon{--fa:"\f0c9"}.fa-list-dots,.fa-list-ul{--fa:"\f0ca"}.fa-list-1-2,.fa-list-numeric,.fa-list-ol{--fa:"\f0cb"}.fa-strikethrough{--fa:"\f0cc"}.fa-underline{--fa:"\f0cd"}.fa-table{--fa:"\f0ce"}.fa-magic,.fa-wand-magic{--fa:"\f0d0"}.fa-truck{--fa:"\f0d1"}.fa-money-bill{--fa:"\f0d6"}.fa-caret-down{--fa:"\f0d7"}.fa-caret-up{--fa:"\f0d8"}.fa-caret-left{--fa:"\f0d9"}.fa-caret-right{--fa:"\f0da"}.fa-columns,.fa-table-columns{--fa:"\f0db"}.fa-sort,.fa-unsorted{--fa:"\f0dc"}.fa-sort-desc,.fa-sort-down{--fa:"\f0dd"}.fa-sort-asc,.fa-sort-up{--fa:"\f0de"}.fa-envelope{--fa:"\f0e0"}.fa-arrow-left-rotate,.fa-arrow-rotate-back,.fa-arrow-rotate-backward,.fa-arrow-rotate-left,.fa-undo{--fa:"\f0e2"}.fa-gavel,.fa-legal{--fa:"\f0e3"}.fa-bolt,.fa-zap{--fa:"\f0e7"}.fa-sitemap{--fa:"\f0e8"}.fa-umbrella{--fa:"\f0e9"}.fa-file-clipboard,.fa-paste{--fa:"\f0ea"}.fa-lightbulb{--fa:"\f0eb"}.fa-arrow-right-arrow-left,.fa-exchange{--fa:"\f0ec"}.fa-cloud-arrow-down,.fa-cloud-download,.fa-cloud-download-alt{--fa:"\f0ed"}.fa-cloud-arrow-up,.fa-cloud-upload,.fa-cloud-upload-alt{--fa:"\f0ee"}.fa-user-doctor,.fa-user-md{--fa:"\f0f0"}.fa-stethoscope{--fa:"\f0f1"}.fa-suitcase{--fa:"\f0f2"}.fa-bell{--fa:"\f0f3"}.fa-coffee,.fa-mug-saucer{--fa:"\f0f4"}.fa-hospital,.fa-hospital-alt,.fa-hospital-wide{--fa:"\f0f8"}.fa-ambulance,.fa-truck-medical{--fa:"\f0f9"}.fa-medkit,.fa-suitcase-medical{--fa:"\f0fa"}.fa-fighter-jet,.fa-jet-fighter{--fa:"\f0fb"}.fa-beer,.fa-beer-mug-empty{--fa:"\f0fc"}.fa-h-square,.fa-square-h{--fa:"\f0fd"}.fa-plus-square,.fa-square-plus{--fa:"\f0fe"}.fa-angle-double-left,.fa-angles-left{--fa:"\f100"}.fa-angle-double-right,.fa-angles-right{--fa:"\f101"}.fa-angle-double-up,.fa-angles-up{--fa:"\f102"}.fa-angle-double-down,.fa-angles-down{--fa:"\f103"}.fa-angle-left{--fa:"\f104"}.fa-angle-right{--fa:"\f105"}.fa-angle-up{--fa:"\f106"}.fa-angle-down{--fa:"\f107"}.fa-laptop{--fa:"\f109"}.fa-tablet-button{--fa:"\f10a"}.fa-mobile-button{--fa:"\f10b"}.fa-quote-left,.fa-quote-left-alt{--fa:"\f10d"}.fa-quote-right,.fa-quote-right-alt{--fa:"\f10e"}.fa-spinner{--fa:"\f110"}.fa-circle{--fa:"\f111"}.fa-face-smile,.fa-smile{--fa:"\f118"}.fa-face-frown,.fa-frown{--fa:"\f119"}.fa-face-meh,.fa-meh{--fa:"\f11a"}.fa-gamepad{--fa:"\f11b"}.fa-keyboard{--fa:"\f11c"}.fa-flag-checkered{--fa:"\f11e"}.fa-terminal{--fa:"\f120"}.fa-code{--fa:"\f121"}.fa-mail-reply-all,.fa-reply-all{--fa:"\f122"}.fa-location-arrow{--fa:"\f124"}.fa-crop{--fa:"\f125"}.fa-code-branch{--fa:"\f126"}.fa-chain-broken,.fa-chain-slash,.fa-link-slash,.fa-unlink{--fa:"\f127"}.fa-info{--fa:"\f129"}.fa-superscript{--fa:"\f12b"}.fa-subscript{--fa:"\f12c"}.fa-eraser{--fa:"\f12d"}.fa-puzzle-piece{--fa:"\f12e"}.fa-microphone{--fa:"\f130"}.fa-microphone-slash{--fa:"\f131"}.fa-shield,.fa-shield-blank{--fa:"\f132"}.fa-calendar{--fa:"\f133"}.fa-fire-extinguisher{--fa:"\f134"}.fa-rocket{--fa:"\f135"}.fa-chevron-circle-left,.fa-circle-chevron-left{--fa:"\f137"}.fa-chevron-circle-right,.fa-circle-chevron-right{--fa:"\f138"}.fa-chevron-circle-up,.fa-circle-chevron-up{--fa:"\f139"}.fa-chevron-circle-down,.fa-circle-chevron-down{--fa:"\f13a"}.fa-anchor{--fa:"\f13d"}.fa-unlock-alt,.fa-unlock-keyhole{--fa:"\f13e"}.fa-bullseye{--fa:"\f140"}.fa-ellipsis,.fa-ellipsis-h{--fa:"\f141"}.fa-ellipsis-v,.fa-ellipsis-vertical{--fa:"\f142"}.fa-rss-square,.fa-square-rss{--fa:"\f143"}.fa-circle-play,.fa-play-circle{--fa:"\f144"}.fa-ticket{--fa:"\f145"}.fa-minus-square,.fa-square-minus{--fa:"\f146"}.fa-arrow-turn-up,.fa-level-up{--fa:"\f148"}.fa-arrow-turn-down,.fa-level-down{--fa:"\f149"}.fa-check-square,.fa-square-check{--fa:"\f14a"}.fa-pen-square,.fa-pencil-square,.fa-square-pen{--fa:"\f14b"}.fa-external-link-square,.fa-square-arrow-up-right{--fa:"\f14c"}.fa-share-from-square,.fa-share-square{--fa:"\f14d"}.fa-compass{--fa:"\f14e"}.fa-caret-square-down,.fa-square-caret-down{--fa:"\f150"}.fa-caret-square-up,.fa-square-caret-up{--fa:"\f151"}.fa-caret-square-right,.fa-square-caret-right{--fa:"\f152"}.fa-eur,.fa-euro,.fa-euro-sign{--fa:"\f153"}.fa-gbp,.fa-pound-sign,.fa-sterling-sign{--fa:"\f154"}.fa-rupee,.fa-rupee-sign{--fa:"\f156"}.fa-cny,.fa-jpy,.fa-rmb,.fa-yen,.fa-yen-sign{--fa:"\f157"}.fa-rouble,.fa-rub,.fa-ruble,.fa-ruble-sign{--fa:"\f158"}.fa-krw,.fa-won,.fa-won-sign{--fa:"\f159"}.fa-file{--fa:"\f15b"}.fa-file-alt,.fa-file-lines,.fa-file-text{--fa:"\f15c"}.fa-arrow-down-a-z,.fa-sort-alpha-asc,.fa-sort-alpha-down{--fa:"\f15d"}.fa-arrow-up-a-z,.fa-sort-alpha-up{--fa:"\f15e"}.fa-arrow-down-wide-short,.fa-sort-amount-asc,.fa-sort-amount-down{--fa:"\f160"}.fa-arrow-up-wide-short,.fa-sort-amount-up{--fa:"\f161"}.fa-arrow-down-1-9,.fa-sort-numeric-asc,.fa-sort-numeric-down{--fa:"\f162"}.fa-arrow-up-1-9,.fa-sort-numeric-up{--fa:"\f163"}.fa-thumbs-up{--fa:"\f164"}.fa-thumbs-down{--fa:"\f165"}.fa-arrow-down-long,.fa-long-arrow-down{--fa:"\f175"}.fa-arrow-up-long,.fa-long-arrow-up{--fa:"\f176"}.fa-arrow-left-long,.fa-long-arrow-left{--fa:"\f177"}.fa-arrow-right-long,.fa-long-arrow-right{--fa:"\f178"}.fa-female,.fa-person-dress{--fa:"\f182"}.fa-male,.fa-person{--fa:"\f183"}.fa-sun{--fa:"\f185"}.fa-moon{--fa:"\f186"}.fa-archive,.fa-box-archive{--fa:"\f187"}.fa-bug{--fa:"\f188"}.fa-caret-square-left,.fa-square-caret-left{--fa:"\f191"}.fa-circle-dot,.fa-dot-circle{--fa:"\f192"}.fa-wheelchair{--fa:"\f193"}.fa-lira-sign{--fa:"\f195"}.fa-shuttle-space,.fa-space-shuttle{--fa:"\f197"}.fa-envelope-square,.fa-square-envelope{--fa:"\f199"}.fa-bank,.fa-building-columns,.fa-institution,.fa-museum,.fa-university{--fa:"\f19c"}.fa-graduation-cap,.fa-mortar-board{--fa:"\f19d"}.fa-language{--fa:"\f1ab"}.fa-fax{--fa:"\f1ac"}.fa-building{--fa:"\f1ad"}.fa-child{--fa:"\f1ae"}.fa-paw{--fa:"\f1b0"}.fa-cube{--fa:"\f1b2"}.fa-cubes{--fa:"\f1b3"}.fa-recycle{--fa:"\f1b8"}.fa-automobile,.fa-car{--fa:"\f1b9"}.fa-cab,.fa-taxi{--fa:"\f1ba"}.fa-tree{--fa:"\f1bb"}.fa-database{--fa:"\f1c0"}.fa-file-pdf{--fa:"\f1c1"}.fa-file-word{--fa:"\f1c2"}.fa-file-excel{--fa:"\f1c3"}.fa-file-powerpoint{--fa:"\f1c4"}.fa-file-image{--fa:"\f1c5"}.fa-file-archive,.fa-file-zipper{--fa:"\f1c6"}.fa-file-audio{--fa:"\f1c7"}.fa-file-video{--fa:"\f1c8"}.fa-file-code{--fa:"\f1c9"}.fa-life-ring{--fa:"\f1cd"}.fa-circle-notch{--fa:"\f1ce"}.fa-paper-plane{--fa:"\f1d8"}.fa-clock-rotate-left,.fa-history{--fa:"\f1da"}.fa-header,.fa-heading{--fa:"\f1dc"}.fa-paragraph{--fa:"\f1dd"}.fa-sliders,.fa-sliders-h{--fa:"\f1de"}.fa-share-alt,.fa-share-nodes{--fa:"\f1e0"}.fa-share-alt-square,.fa-square-share-nodes{--fa:"\f1e1"}.fa-bomb{--fa:"\f1e2"}.fa-futbol,.fa-futbol-ball,.fa-soccer-ball{--fa:"\f1e3"}.fa-teletype,.fa-tty{--fa:"\f1e4"}.fa-binoculars{--fa:"\f1e5"}.fa-plug{--fa:"\f1e6"}.fa-newspaper{--fa:"\f1ea"}.fa-wifi,.fa-wifi-3,.fa-wifi-strong{--fa:"\f1eb"}.fa-calculator{--fa:"\f1ec"}.fa-bell-slash{--fa:"\f1f6"}.fa-trash{--fa:"\f1f8"}.fa-copyright{--fa:"\f1f9"}.fa-eye-dropper,.fa-eye-dropper-empty,.fa-eyedropper{--fa:"\f1fb"}.fa-paint-brush,.fa-paintbrush{--fa:"\f1fc"}.fa-birthday-cake,.fa-cake,.fa-cake-candles{--fa:"\f1fd"}.fa-area-chart,.fa-chart-area{--fa:"\f1fe"}.fa-chart-pie,.fa-pie-chart{--fa:"\f200"}.fa-chart-line,.fa-line-chart{--fa:"\f201"}.fa-toggle-off{--fa:"\f204"}.fa-toggle-on{--fa:"\f205"}.fa-bicycle{--fa:"\f206"}.fa-bus{--fa:"\f207"}.fa-closed-captioning{--fa:"\f20a"}.fa-ils,.fa-shekel,.fa-shekel-sign,.fa-sheqel,.fa-sheqel-sign{--fa:"\f20b"}.fa-cart-plus{--fa:"\f217"}.fa-cart-arrow-down{--fa:"\f218"}.fa-diamond{--fa:"\f219"}.fa-ship{--fa:"\f21a"}.fa-user-secret{--fa:"\f21b"}.fa-motorcycle{--fa:"\f21c"}.fa-street-view{--fa:"\f21d"}.fa-heart-pulse,.fa-heartbeat{--fa:"\f21e"}.fa-venus{--fa:"\f221"}.fa-mars{--fa:"\f222"}.fa-mercury{--fa:"\f223"}.fa-mars-and-venus{--fa:"\f224"}.fa-transgender,.fa-transgender-alt{--fa:"\f225"}.fa-venus-double{--fa:"\f226"}.fa-mars-double{--fa:"\f227"}.fa-venus-mars{--fa:"\f228"}.fa-mars-stroke{--fa:"\f229"}.fa-mars-stroke-up,.fa-mars-stroke-v{--fa:"\f22a"}.fa-mars-stroke-h,.fa-mars-stroke-right{--fa:"\f22b"}.fa-neuter{--fa:"\f22c"}.fa-genderless{--fa:"\f22d"}.fa-server{--fa:"\f233"}.fa-user-plus{--fa:"\f234"}.fa-user-times,.fa-user-xmark{--fa:"\f235"}.fa-bed{--fa:"\f236"}.fa-train{--fa:"\f238"}.fa-subway,.fa-train-subway{--fa:"\f239"}.fa-battery,.fa-battery-5,.fa-battery-full{--fa:"\f240"}.fa-battery-4,.fa-battery-three-quarters{--fa:"\f241"}.fa-battery-3,.fa-battery-half{--fa:"\f242"}.fa-battery-2,.fa-battery-quarter{--fa:"\f243"}.fa-battery-0,.fa-battery-empty{--fa:"\f244"}.fa-arrow-pointer,.fa-mouse-pointer{--fa:"\f245"}.fa-i-cursor{--fa:"\f246"}.fa-object-group{--fa:"\f247"}.fa-object-ungroup{--fa:"\f248"}.fa-note-sticky,.fa-sticky-note{--fa:"\f249"}.fa-clone{--fa:"\f24d"}.fa-balance-scale,.fa-scale-balanced{--fa:"\f24e"}.fa-hourglass-1,.fa-hourglass-start{--fa:"\f251"}.fa-hourglass-2,.fa-hourglass-half{--fa:"\f252"}.fa-hourglass-3,.fa-hourglass-end{--fa:"\f253"}.fa-hourglass,.fa-hourglass-empty{--fa:"\f254"}.fa-hand-back-fist,.fa-hand-rock{--fa:"\f255"}.fa-hand,.fa-hand-paper{--fa:"\f256"}.fa-hand-scissors{--fa:"\f257"}.fa-hand-lizard{--fa:"\f258"}.fa-hand-spock{--fa:"\f259"}.fa-hand-pointer{--fa:"\f25a"}.fa-hand-peace{--fa:"\f25b"}.fa-trademark{--fa:"\f25c"}.fa-registered{--fa:"\f25d"}.fa-television,.fa-tv,.fa-tv-alt{--fa:"\f26c"}.fa-calendar-plus{--fa:"\f271"}.fa-calendar-minus{--fa:"\f272"}.fa-calendar-times,.fa-calendar-xmark{--fa:"\f273"}.fa-calendar-check{--fa:"\f274"}.fa-industry{--fa:"\f275"}.fa-map-pin{--fa:"\f276"}.fa-map-signs,.fa-signs-post{--fa:"\f277"}.fa-map{--fa:"\f279"}.fa-comment-alt,.fa-message{--fa:"\f27a"}.fa-circle-pause,.fa-pause-circle{--fa:"\f28b"}.fa-circle-stop,.fa-stop-circle{--fa:"\f28d"}.fa-bag-shopping,.fa-shopping-bag{--fa:"\f290"}.fa-basket-shopping,.fa-shopping-basket{--fa:"\f291"}.fa-universal-access{--fa:"\f29a"}.fa-blind,.fa-person-walking-with-cane{--fa:"\f29d"}.fa-audio-description{--fa:"\f29e"}.fa-phone-volume,.fa-volume-control-phone{--fa:"\f2a0"}.fa-braille{--fa:"\f2a1"}.fa-assistive-listening-systems,.fa-ear-listen{--fa:"\f2a2"}.fa-american-sign-language-interpreting,.fa-asl-interpreting,.fa-hands-american-sign-language-interpreting,.fa-hands-asl-interpreting{--fa:"\f2a3"}.fa-deaf,.fa-deafness,.fa-ear-deaf,.fa-hard-of-hearing{--fa:"\f2a4"}.fa-hands,.fa-sign-language,.fa-signing{--fa:"\f2a7"}.fa-eye-low-vision,.fa-low-vision{--fa:"\f2a8"}.fa-handshake,.fa-handshake-alt,.fa-handshake-simple{--fa:"\f2b5"}.fa-envelope-open{--fa:"\f2b6"}.fa-address-book,.fa-contact-book{--fa:"\f2b9"}.fa-address-card,.fa-contact-card,.fa-vcard{--fa:"\f2bb"}.fa-circle-user,.fa-user-circle{--fa:"\f2bd"}.fa-id-badge{--fa:"\f2c1"}.fa-drivers-license,.fa-id-card{--fa:"\f2c2"}.fa-temperature-4,.fa-temperature-full,.fa-thermometer-4,.fa-thermometer-full{--fa:"\f2c7"}.fa-temperature-3,.fa-temperature-three-quarters,.fa-thermometer-3,.fa-thermometer-three-quarters{--fa:"\f2c8"}.fa-temperature-2,.fa-temperature-half,.fa-thermometer-2,.fa-thermometer-half{--fa:"\f2c9"}.fa-temperature-1,.fa-temperature-quarter,.fa-thermometer-1,.fa-thermometer-quarter{--fa:"\f2ca"}.fa-temperature-0,.fa-temperature-empty,.fa-thermometer-0,.fa-thermometer-empty{--fa:"\f2cb"}.fa-shower{--fa:"\f2cc"}.fa-bath,.fa-bathtub{--fa:"\f2cd"}.fa-podcast{--fa:"\f2ce"}.fa-window-maximize{--fa:"\f2d0"}.fa-window-minimize{--fa:"\f2d1"}.fa-window-restore{--fa:"\f2d2"}.fa-square-xmark,.fa-times-square,.fa-xmark-square{--fa:"\f2d3"}.fa-microchip{--fa:"\f2db"}.fa-snowflake{--fa:"\f2dc"}.fa-spoon,.fa-utensil-spoon{--fa:"\f2e5"}.fa-cutlery,.fa-utensils{--fa:"\f2e7"}.fa-rotate-back,.fa-rotate-backward,.fa-rotate-left,.fa-undo-alt{--fa:"\f2ea"}.fa-trash-alt,.fa-trash-can{--fa:"\f2ed"}.fa-rotate,.fa-sync-alt{--fa:"\f2f1"}.fa-stopwatch{--fa:"\f2f2"}.fa-right-from-bracket,.fa-sign-out-alt{--fa:"\f2f5"}.fa-right-to-bracket,.fa-sign-in-alt{--fa:"\f2f6"}.fa-redo-alt,.fa-rotate-forward,.fa-rotate-right{--fa:"\f2f9"}.fa-poo{--fa:"\f2fe"}.fa-images{--fa:"\f302"}.fa-pencil,.fa-pencil-alt{--fa:"\f303"}.fa-pen{--fa:"\f304"}.fa-pen-alt,.fa-pen-clip{--fa:"\f305"}.fa-octagon{--fa:"\f306"}.fa-down-long,.fa-long-arrow-alt-down{--fa:"\f309"}.fa-left-long,.fa-long-arrow-alt-left{--fa:"\f30a"}.fa-long-arrow-alt-right,.fa-right-long{--fa:"\f30b"}.fa-long-arrow-alt-up,.fa-up-long{--fa:"\f30c"}.fa-hexagon{--fa:"\f312"}.fa-file-edit,.fa-file-pen{--fa:"\f31c"}.fa-expand-arrows-alt,.fa-maximize{--fa:"\f31e"}.fa-clipboard{--fa:"\f328"}.fa-arrows-alt-h,.fa-left-right{--fa:"\f337"}.fa-arrows-alt-v,.fa-up-down{--fa:"\f338"}.fa-alarm-clock{--fa:"\f34e"}.fa-arrow-alt-circle-down,.fa-circle-down{--fa:"\f358"}.fa-arrow-alt-circle-left,.fa-circle-left{--fa:"\f359"}.fa-arrow-alt-circle-right,.fa-circle-right{--fa:"\f35a"}.fa-arrow-alt-circle-up,.fa-circle-up{--fa:"\f35b"}.fa-external-link-alt,.fa-up-right-from-square{--fa:"\f35d"}.fa-external-link-square-alt,.fa-square-up-right{--fa:"\f360"}.fa-exchange-alt,.fa-right-left{--fa:"\f362"}.fa-repeat{--fa:"\f363"}.fa-code-commit{--fa:"\f386"}.fa-code-merge{--fa:"\f387"}.fa-desktop,.fa-desktop-alt{--fa:"\f390"}.fa-gem{--fa:"\f3a5"}.fa-level-down-alt,.fa-turn-down{--fa:"\f3be"}.fa-level-up-alt,.fa-turn-up{--fa:"\f3bf"}.fa-lock-open{--fa:"\f3c1"}.fa-location-dot,.fa-map-marker-alt{--fa:"\f3c5"}.fa-microphone-alt,.fa-microphone-lines{--fa:"\f3c9"}.fa-mobile-alt,.fa-mobile-screen-button{--fa:"\f3cd"}.fa-mobile,.fa-mobile-android,.fa-mobile-phone{--fa:"\f3ce"}.fa-mobile-android-alt,.fa-mobile-screen{--fa:"\f3cf"}.fa-money-bill-1,.fa-money-bill-alt{--fa:"\f3d1"}.fa-phone-slash{--fa:"\f3dd"}.fa-image-portrait,.fa-portrait{--fa:"\f3e0"}.fa-mail-reply,.fa-reply{--fa:"\f3e5"}.fa-shield-alt,.fa-shield-halved{--fa:"\f3ed"}.fa-tablet-alt,.fa-tablet-screen-button{--fa:"\f3fa"}.fa-tablet,.fa-tablet-android{--fa:"\f3fb"}.fa-ticket-alt,.fa-ticket-simple{--fa:"\f3ff"}.fa-rectangle-times,.fa-rectangle-xmark,.fa-times-rectangle,.fa-window-close{--fa:"\f410"}.fa-compress-alt,.fa-down-left-and-up-right-to-center{--fa:"\f422"}.fa-expand-alt,.fa-up-right-and-down-left-from-center{--fa:"\f424"}.fa-baseball-bat-ball{--fa:"\f432"}.fa-baseball,.fa-baseball-ball{--fa:"\f433"}.fa-basketball,.fa-basketball-ball{--fa:"\f434"}.fa-bowling-ball{--fa:"\f436"}.fa-chess{--fa:"\f439"}.fa-chess-bishop{--fa:"\f43a"}.fa-chess-board{--fa:"\f43c"}.fa-chess-king{--fa:"\f43f"}.fa-chess-knight{--fa:"\f441"}.fa-chess-pawn{--fa:"\f443"}.fa-chess-queen{--fa:"\f445"}.fa-chess-rook{--fa:"\f447"}.fa-dumbbell{--fa:"\f44b"}.fa-football,.fa-football-ball{--fa:"\f44e"}.fa-golf-ball,.fa-golf-ball-tee{--fa:"\f450"}.fa-hockey-puck{--fa:"\f453"}.fa-broom-ball,.fa-quidditch,.fa-quidditch-broom-ball{--fa:"\f458"}.fa-square-full{--fa:"\f45c"}.fa-ping-pong-paddle-ball,.fa-table-tennis,.fa-table-tennis-paddle-ball{--fa:"\f45d"}.fa-volleyball,.fa-volleyball-ball{--fa:"\f45f"}.fa-allergies,.fa-hand-dots{--fa:"\f461"}.fa-band-aid,.fa-bandage{--fa:"\f462"}.fa-box{--fa:"\f466"}.fa-boxes,.fa-boxes-alt,.fa-boxes-stacked{--fa:"\f468"}.fa-briefcase-medical{--fa:"\f469"}.fa-burn,.fa-fire-flame-simple{--fa:"\f46a"}.fa-capsules{--fa:"\f46b"}.fa-clipboard-check{--fa:"\f46c"}.fa-clipboard-list{--fa:"\f46d"}.fa-diagnoses,.fa-person-dots-from-line{--fa:"\f470"}.fa-dna{--fa:"\f471"}.fa-dolly,.fa-dolly-box{--fa:"\f472"}.fa-cart-flatbed,.fa-dolly-flatbed{--fa:"\f474"}.fa-file-medical{--fa:"\f477"}.fa-file-medical-alt,.fa-file-waveform{--fa:"\f478"}.fa-first-aid,.fa-kit-medical{--fa:"\f479"}.fa-circle-h,.fa-hospital-symbol{--fa:"\f47e"}.fa-id-card-alt,.fa-id-card-clip{--fa:"\f47f"}.fa-notes-medical{--fa:"\f481"}.fa-pallet{--fa:"\f482"}.fa-pills{--fa:"\f484"}.fa-prescription-bottle{--fa:"\f485"}.fa-prescription-bottle-alt,.fa-prescription-bottle-medical{--fa:"\f486"}.fa-bed-pulse,.fa-procedures{--fa:"\f487"}.fa-shipping-fast,.fa-truck-fast{--fa:"\f48b"}.fa-smoking{--fa:"\f48d"}.fa-syringe{--fa:"\f48e"}.fa-tablets{--fa:"\f490"}.fa-thermometer{--fa:"\f491"}.fa-vial{--fa:"\f492"}.fa-vials{--fa:"\f493"}.fa-warehouse{--fa:"\f494"}.fa-weight,.fa-weight-scale{--fa:"\f496"}.fa-x-ray{--fa:"\f497"}.fa-box-open{--fa:"\f49e"}.fa-comment-dots,.fa-commenting{--fa:"\f4ad"}.fa-comment-slash{--fa:"\f4b3"}.fa-couch{--fa:"\f4b8"}.fa-circle-dollar-to-slot,.fa-donate{--fa:"\f4b9"}.fa-dove{--fa:"\f4ba"}.fa-hand-holding{--fa:"\f4bd"}.fa-hand-holding-heart{--fa:"\f4be"}.fa-hand-holding-dollar,.fa-hand-holding-usd{--fa:"\f4c0"}.fa-hand-holding-droplet,.fa-hand-holding-water{--fa:"\f4c1"}.fa-hands-holding{--fa:"\f4c2"}.fa-hands-helping,.fa-handshake-angle{--fa:"\f4c4"}.fa-parachute-box{--fa:"\f4cd"}.fa-people-carry,.fa-people-carry-box{--fa:"\f4ce"}.fa-piggy-bank{--fa:"\f4d3"}.fa-ribbon{--fa:"\f4d6"}.fa-route{--fa:"\f4d7"}.fa-seedling,.fa-sprout{--fa:"\f4d8"}.fa-sign,.fa-sign-hanging{--fa:"\f4d9"}.fa-face-smile-wink,.fa-smile-wink{--fa:"\f4da"}.fa-tape{--fa:"\f4db"}.fa-truck-loading,.fa-truck-ramp-box{--fa:"\f4de"}.fa-truck-moving{--fa:"\f4df"}.fa-video-slash{--fa:"\f4e2"}.fa-wine-glass{--fa:"\f4e3"}.fa-user-astronaut{--fa:"\f4fb"}.fa-user-check{--fa:"\f4fc"}.fa-user-clock{--fa:"\f4fd"}.fa-user-cog,.fa-user-gear{--fa:"\f4fe"}.fa-user-edit,.fa-user-pen{--fa:"\f4ff"}.fa-user-friends,.fa-user-group{--fa:"\f500"}.fa-user-graduate{--fa:"\f501"}.fa-user-lock{--fa:"\f502"}.fa-user-minus{--fa:"\f503"}.fa-user-ninja{--fa:"\f504"}.fa-user-shield{--fa:"\f505"}.fa-user-alt-slash,.fa-user-large-slash,.fa-user-slash{--fa:"\f506"}.fa-user-tag{--fa:"\f507"}.fa-user-tie{--fa:"\f508"}.fa-users-cog,.fa-users-gear{--fa:"\f509"}.fa-balance-scale-left,.fa-scale-unbalanced{--fa:"\f515"}.fa-balance-scale-right,.fa-scale-unbalanced-flip{--fa:"\f516"}.fa-blender{--fa:"\f517"}.fa-book-open{--fa:"\f518"}.fa-broadcast-tower,.fa-tower-broadcast{--fa:"\f519"}.fa-broom{--fa:"\f51a"}.fa-blackboard,.fa-chalkboard{--fa:"\f51b"}.fa-chalkboard-teacher,.fa-chalkboard-user{--fa:"\f51c"}.fa-church{--fa:"\f51d"}.fa-coins{--fa:"\f51e"}.fa-compact-disc{--fa:"\f51f"}.fa-crow{--fa:"\f520"}.fa-crown{--fa:"\f521"}.fa-dice{--fa:"\f522"}.fa-dice-five{--fa:"\f523"}.fa-dice-four{--fa:"\f524"}.fa-dice-one{--fa:"\f525"}.fa-dice-six{--fa:"\f526"}.fa-dice-three{--fa:"\f527"}.fa-dice-two{--fa:"\f528"}.fa-divide{--fa:"\f529"}.fa-door-closed{--fa:"\f52a"}.fa-door-open{--fa:"\f52b"}.fa-feather{--fa:"\f52d"}.fa-frog{--fa:"\f52e"}.fa-gas-pump{--fa:"\f52f"}.fa-glasses{--fa:"\f530"}.fa-greater-than-equal{--fa:"\f532"}.fa-helicopter{--fa:"\f533"}.fa-infinity{--fa:"\f534"}.fa-kiwi-bird{--fa:"\f535"}.fa-less-than-equal{--fa:"\f537"}.fa-memory{--fa:"\f538"}.fa-microphone-alt-slash,.fa-microphone-lines-slash{--fa:"\f539"}.fa-money-bill-wave{--fa:"\f53a"}.fa-money-bill-1-wave,.fa-money-bill-wave-alt{--fa:"\f53b"}.fa-money-check{--fa:"\f53c"}.fa-money-check-alt,.fa-money-check-dollar{--fa:"\f53d"}.fa-not-equal{--fa:"\f53e"}.fa-palette{--fa:"\f53f"}.fa-parking,.fa-square-parking{--fa:"\f540"}.fa-diagram-project,.fa-project-diagram{--fa:"\f542"}.fa-receipt{--fa:"\f543"}.fa-robot{--fa:"\f544"}.fa-ruler{--fa:"\f545"}.fa-ruler-combined{--fa:"\f546"}.fa-ruler-horizontal{--fa:"\f547"}.fa-ruler-vertical{--fa:"\f548"}.fa-school{--fa:"\f549"}.fa-screwdriver{--fa:"\f54a"}.fa-shoe-prints{--fa:"\f54b"}.fa-skull{--fa:"\f54c"}.fa-ban-smoking,.fa-smoking-ban{--fa:"\f54d"}.fa-store{--fa:"\f54e"}.fa-shop,.fa-store-alt{--fa:"\f54f"}.fa-bars-staggered,.fa-reorder,.fa-stream{--fa:"\f550"}.fa-stroopwafel{--fa:"\f551"}.fa-toolbox{--fa:"\f552"}.fa-shirt,.fa-t-shirt,.fa-tshirt{--fa:"\f553"}.fa-person-walking,.fa-walking{--fa:"\f554"}.fa-wallet{--fa:"\f555"}.fa-angry,.fa-face-angry{--fa:"\f556"}.fa-archway{--fa:"\f557"}.fa-atlas,.fa-book-atlas{--fa:"\f558"}.fa-award{--fa:"\f559"}.fa-backspace,.fa-delete-left{--fa:"\f55a"}.fa-bezier-curve{--fa:"\f55b"}.fa-bong{--fa:"\f55c"}.fa-brush{--fa:"\f55d"}.fa-bus-alt,.fa-bus-simple{--fa:"\f55e"}.fa-cannabis{--fa:"\f55f"}.fa-check-double{--fa:"\f560"}.fa-cocktail,.fa-martini-glass-citrus{--fa:"\f561"}.fa-bell-concierge,.fa-concierge-bell{--fa:"\f562"}.fa-cookie{--fa:"\f563"}.fa-cookie-bite{--fa:"\f564"}.fa-crop-alt,.fa-crop-simple{--fa:"\f565"}.fa-digital-tachograph,.fa-tachograph-digital{--fa:"\f566"}.fa-dizzy,.fa-face-dizzy{--fa:"\f567"}.fa-compass-drafting,.fa-drafting-compass{--fa:"\f568"}.fa-drum{--fa:"\f569"}.fa-drum-steelpan{--fa:"\f56a"}.fa-feather-alt,.fa-feather-pointed{--fa:"\f56b"}.fa-file-contract{--fa:"\f56c"}.fa-file-arrow-down,.fa-file-download{--fa:"\f56d"}.fa-arrow-right-from-file,.fa-file-export{--fa:"\f56e"}.fa-arrow-right-to-file,.fa-file-import{--fa:"\f56f"}.fa-file-invoice{--fa:"\f570"}.fa-file-invoice-dollar{--fa:"\f571"}.fa-file-prescription{--fa:"\f572"}.fa-file-signature{--fa:"\f573"}.fa-file-arrow-up,.fa-file-upload{--fa:"\f574"}.fa-fill{--fa:"\f575"}.fa-fill-drip{--fa:"\f576"}.fa-fingerprint{--fa:"\f577"}.fa-fish{--fa:"\f578"}.fa-face-flushed,.fa-flushed{--fa:"\f579"}.fa-face-frown-open,.fa-frown-open{--fa:"\f57a"}.fa-glass-martini-alt,.fa-martini-glass{--fa:"\f57b"}.fa-earth-africa,.fa-globe-africa{--fa:"\f57c"}.fa-earth,.fa-earth-america,.fa-earth-americas,.fa-globe-americas{--fa:"\f57d"}.fa-earth-asia,.fa-globe-asia{--fa:"\f57e"}.fa-face-grimace,.fa-grimace{--fa:"\f57f"}.fa-face-grin,.fa-grin{--fa:"\f580"}.fa-face-grin-wide,.fa-grin-alt{--fa:"\f581"}.fa-face-grin-beam,.fa-grin-beam{--fa:"\f582"}.fa-face-grin-beam-sweat,.fa-grin-beam-sweat{--fa:"\f583"}.fa-face-grin-hearts,.fa-grin-hearts{--fa:"\f584"}.fa-face-grin-squint,.fa-grin-squint{--fa:"\f585"}.fa-face-grin-squint-tears,.fa-grin-squint-tears{--fa:"\f586"}.fa-face-grin-stars,.fa-grin-stars{--fa:"\f587"}.fa-face-grin-tears,.fa-grin-tears{--fa:"\f588"}.fa-face-grin-tongue,.fa-grin-tongue{--fa:"\f589"}.fa-face-grin-tongue-squint,.fa-grin-tongue-squint{--fa:"\f58a"}.fa-face-grin-tongue-wink,.fa-grin-tongue-wink{--fa:"\f58b"}.fa-face-grin-wink,.fa-grin-wink{--fa:"\f58c"}.fa-grid-horizontal,.fa-grip,.fa-grip-horizontal{--fa:"\f58d"}.fa-grid-vertical,.fa-grip-vertical{--fa:"\f58e"}.fa-headset{--fa:"\f590"}.fa-highlighter{--fa:"\f591"}.fa-hot-tub,.fa-hot-tub-person{--fa:"\f593"}.fa-hotel{--fa:"\f594"}.fa-joint{--fa:"\f595"}.fa-face-kiss,.fa-kiss{--fa:"\f596"}.fa-face-kiss-beam,.fa-kiss-beam{--fa:"\f597"}.fa-face-kiss-wink-heart,.fa-kiss-wink-heart{--fa:"\f598"}.fa-face-laugh,.fa-laugh{--fa:"\f599"}.fa-face-laugh-beam,.fa-laugh-beam{--fa:"\f59a"}.fa-face-laugh-squint,.fa-laugh-squint{--fa:"\f59b"}.fa-face-laugh-wink,.fa-laugh-wink{--fa:"\f59c"}.fa-cart-flatbed-suitcase,.fa-luggage-cart{--fa:"\f59d"}.fa-map-location,.fa-map-marked{--fa:"\f59f"}.fa-map-location-dot,.fa-map-marked-alt{--fa:"\f5a0"}.fa-marker{--fa:"\f5a1"}.fa-medal{--fa:"\f5a2"}.fa-face-meh-blank,.fa-meh-blank{--fa:"\f5a4"}.fa-face-rolling-eyes,.fa-meh-rolling-eyes{--fa:"\f5a5"}.fa-monument{--fa:"\f5a6"}.fa-mortar-pestle{--fa:"\f5a7"}.fa-paint-roller{--fa:"\f5aa"}.fa-passport{--fa:"\f5ab"}.fa-pen-fancy{--fa:"\f5ac"}.fa-pen-nib{--fa:"\f5ad"}.fa-pen-ruler,.fa-pencil-ruler{--fa:"\f5ae"}.fa-plane-arrival{--fa:"\f5af"}.fa-plane-departure{--fa:"\f5b0"}.fa-prescription{--fa:"\f5b1"}.fa-face-sad-cry,.fa-sad-cry{--fa:"\f5b3"}.fa-face-sad-tear,.fa-sad-tear{--fa:"\f5b4"}.fa-shuttle-van,.fa-van-shuttle{--fa:"\f5b6"}.fa-signature{--fa:"\f5b7"}.fa-face-smile-beam,.fa-smile-beam{--fa:"\f5b8"}.fa-solar-panel{--fa:"\f5ba"}.fa-spa{--fa:"\f5bb"}.fa-splotch{--fa:"\f5bc"}.fa-spray-can{--fa:"\f5bd"}.fa-stamp{--fa:"\f5bf"}.fa-star-half-alt,.fa-star-half-stroke{--fa:"\f5c0"}.fa-suitcase-rolling{--fa:"\f5c1"}.fa-face-surprise,.fa-surprise{--fa:"\f5c2"}.fa-swatchbook{--fa:"\f5c3"}.fa-person-swimming,.fa-swimmer{--fa:"\f5c4"}.fa-ladder-water,.fa-swimming-pool,.fa-water-ladder{--fa:"\f5c5"}.fa-droplet-slash,.fa-tint-slash{--fa:"\f5c7"}.fa-face-tired,.fa-tired{--fa:"\f5c8"}.fa-tooth{--fa:"\f5c9"}.fa-umbrella-beach{--fa:"\f5ca"}.fa-weight-hanging{--fa:"\f5cd"}.fa-wine-glass-alt,.fa-wine-glass-empty{--fa:"\f5ce"}.fa-air-freshener,.fa-spray-can-sparkles{--fa:"\f5d0"}.fa-apple-alt,.fa-apple-whole{--fa:"\f5d1"}.fa-atom{--fa:"\f5d2"}.fa-bone{--fa:"\f5d7"}.fa-book-open-reader,.fa-book-reader{--fa:"\f5da"}.fa-brain{--fa:"\f5dc"}.fa-car-alt,.fa-car-rear{--fa:"\f5de"}.fa-battery-car,.fa-car-battery{--fa:"\f5df"}.fa-car-burst,.fa-car-crash{--fa:"\f5e1"}.fa-car-side{--fa:"\f5e4"}.fa-charging-station{--fa:"\f5e7"}.fa-diamond-turn-right,.fa-directions{--fa:"\f5eb"}.fa-draw-polygon,.fa-vector-polygon{--fa:"\f5ee"}.fa-laptop-code{--fa:"\f5fc"}.fa-layer-group{--fa:"\f5fd"}.fa-location,.fa-location-crosshairs{--fa:"\f601"}.fa-lungs{--fa:"\f604"}.fa-microscope{--fa:"\f610"}.fa-oil-can{--fa:"\f613"}.fa-poop{--fa:"\f619"}.fa-shapes,.fa-triangle-circle-square{--fa:"\f61f"}.fa-star-of-life{--fa:"\f621"}.fa-dashboard,.fa-gauge,.fa-gauge-med,.fa-tachometer-alt-average{--fa:"\f624"}.fa-gauge-high,.fa-tachometer-alt,.fa-tachometer-alt-fast{--fa:"\f625"}.fa-gauge-simple,.fa-gauge-simple-med,.fa-tachometer-average{--fa:"\f629"}.fa-gauge-simple-high,.fa-tachometer,.fa-tachometer-fast{--fa:"\f62a"}.fa-teeth{--fa:"\f62e"}.fa-teeth-open{--fa:"\f62f"}.fa-masks-theater,.fa-theater-masks{--fa:"\f630"}.fa-traffic-light{--fa:"\f637"}.fa-truck-monster{--fa:"\f63b"}.fa-truck-pickup{--fa:"\f63c"}.fa-ad,.fa-rectangle-ad{--fa:"\f641"}.fa-ankh{--fa:"\f644"}.fa-bible,.fa-book-bible{--fa:"\f647"}.fa-briefcase-clock,.fa-business-time{--fa:"\f64a"}.fa-city{--fa:"\f64f"}.fa-comment-dollar{--fa:"\f651"}.fa-comments-dollar{--fa:"\f653"}.fa-cross{--fa:"\f654"}.fa-dharmachakra{--fa:"\f655"}.fa-envelope-open-text{--fa:"\f658"}.fa-folder-minus{--fa:"\f65d"}.fa-folder-plus{--fa:"\f65e"}.fa-filter-circle-dollar,.fa-funnel-dollar{--fa:"\f662"}.fa-gopuram{--fa:"\f664"}.fa-hamsa{--fa:"\f665"}.fa-bahai,.fa-haykal{--fa:"\f666"}.fa-jedi{--fa:"\f669"}.fa-book-journal-whills,.fa-journal-whills{--fa:"\f66a"}.fa-kaaba{--fa:"\f66b"}.fa-khanda{--fa:"\f66d"}.fa-landmark{--fa:"\f66f"}.fa-envelopes-bulk,.fa-mail-bulk{--fa:"\f674"}.fa-menorah{--fa:"\f676"}.fa-mosque{--fa:"\f678"}.fa-om{--fa:"\f679"}.fa-pastafarianism,.fa-spaghetti-monster-flying{--fa:"\f67b"}.fa-peace{--fa:"\f67c"}.fa-place-of-worship{--fa:"\f67f"}.fa-poll,.fa-square-poll-vertical{--fa:"\f681"}.fa-poll-h,.fa-square-poll-horizontal{--fa:"\f682"}.fa-person-praying,.fa-pray{--fa:"\f683"}.fa-hands-praying,.fa-praying-hands{--fa:"\f684"}.fa-book-quran,.fa-quran{--fa:"\f687"}.fa-magnifying-glass-dollar,.fa-search-dollar{--fa:"\f688"}.fa-magnifying-glass-location,.fa-search-location{--fa:"\f689"}.fa-socks{--fa:"\f696"}.fa-square-root-alt,.fa-square-root-variable{--fa:"\f698"}.fa-star-and-crescent{--fa:"\f699"}.fa-star-of-david{--fa:"\f69a"}.fa-synagogue{--fa:"\f69b"}.fa-scroll-torah,.fa-torah{--fa:"\f6a0"}.fa-torii-gate{--fa:"\f6a1"}.fa-vihara{--fa:"\f6a7"}.fa-volume-mute,.fa-volume-times,.fa-volume-xmark{--fa:"\f6a9"}.fa-yin-yang{--fa:"\f6ad"}.fa-blender-phone{--fa:"\f6b6"}.fa-book-dead,.fa-book-skull{--fa:"\f6b7"}.fa-campground{--fa:"\f6bb"}.fa-cat{--fa:"\f6be"}.fa-chair{--fa:"\f6c0"}.fa-cloud-moon{--fa:"\f6c3"}.fa-cloud-sun{--fa:"\f6c4"}.fa-cow{--fa:"\f6c8"}.fa-dice-d20{--fa:"\f6cf"}.fa-dice-d6{--fa:"\f6d1"}.fa-dog{--fa:"\f6d3"}.fa-dragon{--fa:"\f6d5"}.fa-drumstick-bite{--fa:"\f6d7"}.fa-dungeon{--fa:"\f6d9"}.fa-file-csv{--fa:"\f6dd"}.fa-fist-raised,.fa-hand-fist{--fa:"\f6de"}.fa-ghost{--fa:"\f6e2"}.fa-hammer{--fa:"\f6e3"}.fa-hanukiah{--fa:"\f6e6"}.fa-hat-wizard{--fa:"\f6e8"}.fa-hiking,.fa-person-hiking{--fa:"\f6ec"}.fa-hippo{--fa:"\f6ed"}.fa-horse{--fa:"\f6f0"}.fa-house-chimney-crack,.fa-house-damage{--fa:"\f6f1"}.fa-hryvnia,.fa-hryvnia-sign{--fa:"\f6f2"}.fa-mask{--fa:"\f6fa"}.fa-mountain{--fa:"\f6fc"}.fa-network-wired{--fa:"\f6ff"}.fa-otter{--fa:"\f700"}.fa-ring{--fa:"\f70b"}.fa-person-running,.fa-running{--fa:"\f70c"}.fa-scroll{--fa:"\f70e"}.fa-skull-crossbones{--fa:"\f714"}.fa-slash{--fa:"\f715"}.fa-spider{--fa:"\f717"}.fa-toilet-paper,.fa-toilet-paper-alt,.fa-toilet-paper-blank{--fa:"\f71e"}.fa-tractor{--fa:"\f722"}.fa-user-injured{--fa:"\f728"}.fa-vr-cardboard{--fa:"\f729"}.fa-wand-sparkles{--fa:"\f72b"}.fa-wind{--fa:"\f72e"}.fa-wine-bottle{--fa:"\f72f"}.fa-cloud-meatball{--fa:"\f73b"}.fa-cloud-moon-rain{--fa:"\f73c"}.fa-cloud-rain{--fa:"\f73d"}.fa-cloud-showers-heavy{--fa:"\f740"}.fa-cloud-sun-rain{--fa:"\f743"}.fa-democrat{--fa:"\f747"}.fa-flag-usa{--fa:"\f74d"}.fa-hurricane{--fa:"\f751"}.fa-landmark-alt,.fa-landmark-dome{--fa:"\f752"}.fa-meteor{--fa:"\f753"}.fa-person-booth{--fa:"\f756"}.fa-poo-bolt,.fa-poo-storm{--fa:"\f75a"}.fa-rainbow{--fa:"\f75b"}.fa-republican{--fa:"\f75e"}.fa-smog{--fa:"\f75f"}.fa-temperature-high{--fa:"\f769"}.fa-temperature-low{--fa:"\f76b"}.fa-cloud-bolt,.fa-thunderstorm{--fa:"\f76c"}.fa-tornado{--fa:"\f76f"}.fa-volcano{--fa:"\f770"}.fa-check-to-slot,.fa-vote-yea{--fa:"\f772"}.fa-water{--fa:"\f773"}.fa-baby{--fa:"\f77c"}.fa-baby-carriage,.fa-carriage-baby{--fa:"\f77d"}.fa-biohazard{--fa:"\f780"}.fa-blog{--fa:"\f781"}.fa-calendar-day{--fa:"\f783"}.fa-calendar-week{--fa:"\f784"}.fa-candy-cane{--fa:"\f786"}.fa-carrot{--fa:"\f787"}.fa-cash-register{--fa:"\f788"}.fa-compress-arrows-alt,.fa-minimize{--fa:"\f78c"}.fa-dumpster{--fa:"\f793"}.fa-dumpster-fire{--fa:"\f794"}.fa-ethernet{--fa:"\f796"}.fa-gifts{--fa:"\f79c"}.fa-champagne-glasses,.fa-glass-cheers{--fa:"\f79f"}.fa-glass-whiskey,.fa-whiskey-glass{--fa:"\f7a0"}.fa-earth-europe,.fa-globe-europe{--fa:"\f7a2"}.fa-grip-lines{--fa:"\f7a4"}.fa-grip-lines-vertical{--fa:"\f7a5"}.fa-guitar{--fa:"\f7a6"}.fa-heart-broken,.fa-heart-crack{--fa:"\f7a9"}.fa-holly-berry{--fa:"\f7aa"}.fa-horse-head{--fa:"\f7ab"}.fa-icicles{--fa:"\f7ad"}.fa-igloo{--fa:"\f7ae"}.fa-mitten{--fa:"\f7b5"}.fa-mug-hot{--fa:"\f7b6"}.fa-radiation{--fa:"\f7b9"}.fa-circle-radiation,.fa-radiation-alt{--fa:"\f7ba"}.fa-restroom{--fa:"\f7bd"}.fa-satellite{--fa:"\f7bf"}.fa-satellite-dish{--fa:"\f7c0"}.fa-sd-card{--fa:"\f7c2"}.fa-sim-card{--fa:"\f7c4"}.fa-person-skating,.fa-skating{--fa:"\f7c5"}.fa-person-skiing,.fa-skiing{--fa:"\f7c9"}.fa-person-skiing-nordic,.fa-skiing-nordic{--fa:"\f7ca"}.fa-sleigh{--fa:"\f7cc"}.fa-comment-sms,.fa-sms{--fa:"\f7cd"}.fa-person-snowboarding,.fa-snowboarding{--fa:"\f7ce"}.fa-snowman{--fa:"\f7d0"}.fa-snowplow{--fa:"\f7d2"}.fa-tenge,.fa-tenge-sign{--fa:"\f7d7"}.fa-toilet{--fa:"\f7d8"}.fa-screwdriver-wrench,.fa-tools{--fa:"\f7d9"}.fa-cable-car,.fa-tram{--fa:"\f7da"}.fa-fire-alt,.fa-fire-flame-curved{--fa:"\f7e4"}.fa-bacon{--fa:"\f7e5"}.fa-book-medical{--fa:"\f7e6"}.fa-bread-slice{--fa:"\f7ec"}.fa-cheese{--fa:"\f7ef"}.fa-clinic-medical,.fa-house-chimney-medical{--fa:"\f7f2"}.fa-clipboard-user{--fa:"\f7f3"}.fa-comment-medical{--fa:"\f7f5"}.fa-crutch{--fa:"\f7f7"}.fa-disease{--fa:"\f7fa"}.fa-egg{--fa:"\f7fb"}.fa-folder-tree{--fa:"\f802"}.fa-burger,.fa-hamburger{--fa:"\f805"}.fa-hand-middle-finger{--fa:"\f806"}.fa-hard-hat,.fa-hat-hard,.fa-helmet-safety{--fa:"\f807"}.fa-hospital-user{--fa:"\f80d"}.fa-hotdog{--fa:"\f80f"}.fa-ice-cream{--fa:"\f810"}.fa-laptop-medical{--fa:"\f812"}.fa-pager{--fa:"\f815"}.fa-pepper-hot{--fa:"\f816"}.fa-pizza-slice{--fa:"\f818"}.fa-sack-dollar{--fa:"\f81d"}.fa-book-tanakh,.fa-tanakh{--fa:"\f827"}.fa-bars-progress,.fa-tasks-alt{--fa:"\f828"}.fa-trash-arrow-up,.fa-trash-restore{--fa:"\f829"}.fa-trash-can-arrow-up,.fa-trash-restore-alt{--fa:"\f82a"}.fa-user-nurse{--fa:"\f82f"}.fa-wave-square{--fa:"\f83e"}.fa-biking,.fa-person-biking{--fa:"\f84a"}.fa-border-all{--fa:"\f84c"}.fa-border-none{--fa:"\f850"}.fa-border-style,.fa-border-top-left{--fa:"\f853"}.fa-digging,.fa-person-digging{--fa:"\f85e"}.fa-fan{--fa:"\f863"}.fa-heart-music-camera-bolt,.fa-icons{--fa:"\f86d"}.fa-phone-alt,.fa-phone-flip{--fa:"\f879"}.fa-phone-square-alt,.fa-square-phone-flip{--fa:"\f87b"}.fa-photo-film,.fa-photo-video{--fa:"\f87c"}.fa-remove-format,.fa-text-slash{--fa:"\f87d"}.fa-arrow-down-z-a,.fa-sort-alpha-desc,.fa-sort-alpha-down-alt{--fa:"\f881"}.fa-arrow-up-z-a,.fa-sort-alpha-up-alt{--fa:"\f882"}.fa-arrow-down-short-wide,.fa-sort-amount-desc,.fa-sort-amount-down-alt{--fa:"\f884"}.fa-arrow-up-short-wide,.fa-sort-amount-up-alt{--fa:"\f885"}.fa-arrow-down-9-1,.fa-sort-numeric-desc,.fa-sort-numeric-down-alt{--fa:"\f886"}.fa-arrow-up-9-1,.fa-sort-numeric-up-alt{--fa:"\f887"}.fa-spell-check{--fa:"\f891"}.fa-voicemail{--fa:"\f897"}.fa-hat-cowboy{--fa:"\f8c0"}.fa-hat-cowboy-side{--fa:"\f8c1"}.fa-computer-mouse,.fa-mouse{--fa:"\f8cc"}.fa-radio{--fa:"\f8d7"}.fa-record-vinyl{--fa:"\f8d9"}.fa-walkie-talkie{--fa:"\f8ef"}.fa-caravan{--fa:"\f8ff"} +/*! + * Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2025 Fonticons, Inc. + */ +.fa,.fa-brands,.fa-classic,.fa-regular,.fa-solid,.fab,.far,.fas{--_fa-family:var(--fa-family,var(--fa-style-family,"Font Awesome 7 Free"));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:var(--fa-display,inline-block);font-family:var(--_fa-family);font-feature-settings:normal;font-style:normal;font-synthesis:none;font-variant:normal;font-weight:var(--fa-style,900);line-height:1;text-align:center;text-rendering:auto;width:var(--fa-width,1.25em)}:is(.fas,.far,.fab,.fa-solid,.fa-regular,.fa-brands,.fa-classic,.fa):before{content:var(--fa);content:var(--fa)/""}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-width-auto{--fa-width:auto}.fa-fw,.fa-width-fixed{--fa-width:1.25em}.fa-ul{list-style-type:none;margin-inline-start:var(--fa-li-margin,2.5em);padding-inline-start:0}.fa-ul>li{position:relative}.fa-li{inset-inline-start:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.0625em) var(--fa-border-style,solid) var(--fa-border-color,#eee);box-sizing:var(--fa-border-box-sizing,content-box);padding:var(--fa-border-padding,.1875em .25em)}.fa-pull-left,.fa-pull-start{float:inline-start;margin-inline-end:var(--fa-pull-margin,.3em)}.fa-pull-end,.fa-pull-right{float:inline-end;margin-inline-start:var(--fa-pull-margin,.3em)}.fa-beat{animation-name:fa-beat;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{animation-name:fa-bounce;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{animation-name:fa-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{animation-name:fa-beat-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{animation-name:fa-flip;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{animation-name:fa-shake;animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{animation-name:fa-spin;animation-duration:var(--fa-animation-duration,2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{animation-name:fa-spin;animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{animation:none!important;transition:none!important}}@keyframes fa-beat{0%,90%{transform:scale(1)}45%{transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-bounce{0%{transform:scale(1) translateY(0)}10%{transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{transform:scale(1) translateY(0)}to{transform:scale(1) translateY(0)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);transform:scale(1)}50%{opacity:1;transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-flip{50%{transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-shake{0%{transform:rotate(-15deg)}4%{transform:rotate(15deg)}8%,24%{transform:rotate(-18deg)}12%,28%{transform:rotate(18deg)}16%{transform:rotate(-22deg)}20%{transform:rotate(22deg)}32%{transform:rotate(-12deg)}36%{transform:rotate(12deg)}40%,to{transform:rotate(0deg)}}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}.fa-rotate-by{transform:rotate(var(--fa-rotate-angle,0))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} + +.fa-0{--fa:"\30 "}.fa-1{--fa:"\31 "}.fa-2{--fa:"\32 "}.fa-3{--fa:"\33 "}.fa-4{--fa:"\34 "}.fa-5{--fa:"\35 "}.fa-6{--fa:"\36 "}.fa-7{--fa:"\37 "}.fa-8{--fa:"\38 "}.fa-9{--fa:"\39 "}.fa-exclamation{--fa:"\!"}.fa-hashtag{--fa:"\#"}.fa-dollar,.fa-dollar-sign,.fa-usd{--fa:"\$"}.fa-percent,.fa-percentage{--fa:"\%"}.fa-asterisk{--fa:"\*"}.fa-add,.fa-plus{--fa:"\+"}.fa-less-than{--fa:"\<"}.fa-equals{--fa:"\="}.fa-greater-than{--fa:"\>"}.fa-question{--fa:"\?"}.fa-at{--fa:"\@"}.fa-a{--fa:"A"}.fa-b{--fa:"B"}.fa-c{--fa:"C"}.fa-d{--fa:"D"}.fa-e{--fa:"E"}.fa-f{--fa:"F"}.fa-g{--fa:"G"}.fa-h{--fa:"H"}.fa-i{--fa:"I"}.fa-j{--fa:"J"}.fa-k{--fa:"K"}.fa-l{--fa:"L"}.fa-m{--fa:"M"}.fa-n{--fa:"N"}.fa-o{--fa:"O"}.fa-p{--fa:"P"}.fa-q{--fa:"Q"}.fa-r{--fa:"R"}.fa-s{--fa:"S"}.fa-t{--fa:"T"}.fa-u{--fa:"U"}.fa-v{--fa:"V"}.fa-w{--fa:"W"}.fa-x{--fa:"X"}.fa-y{--fa:"Y"}.fa-z{--fa:"Z"}.fa-faucet{--fa:"\e005"}.fa-faucet-drip{--fa:"\e006"}.fa-house-chimney-window{--fa:"\e00d"}.fa-house-signal{--fa:"\e012"}.fa-temperature-arrow-down,.fa-temperature-down{--fa:"\e03f"}.fa-temperature-arrow-up,.fa-temperature-up{--fa:"\e040"}.fa-trailer{--fa:"\e041"}.fa-bacteria{--fa:"\e059"}.fa-bacterium{--fa:"\e05a"}.fa-box-tissue{--fa:"\e05b"}.fa-hand-holding-medical{--fa:"\e05c"}.fa-hand-sparkles{--fa:"\e05d"}.fa-hands-bubbles,.fa-hands-wash{--fa:"\e05e"}.fa-handshake-alt-slash,.fa-handshake-simple-slash,.fa-handshake-slash{--fa:"\e060"}.fa-head-side-cough{--fa:"\e061"}.fa-head-side-cough-slash{--fa:"\e062"}.fa-head-side-mask{--fa:"\e063"}.fa-head-side-virus{--fa:"\e064"}.fa-house-chimney-user{--fa:"\e065"}.fa-house-laptop,.fa-laptop-house{--fa:"\e066"}.fa-lungs-virus{--fa:"\e067"}.fa-people-arrows,.fa-people-arrows-left-right{--fa:"\e068"}.fa-plane-slash{--fa:"\e069"}.fa-pump-medical{--fa:"\e06a"}.fa-pump-soap{--fa:"\e06b"}.fa-shield-virus{--fa:"\e06c"}.fa-sink{--fa:"\e06d"}.fa-soap{--fa:"\e06e"}.fa-stopwatch-20{--fa:"\e06f"}.fa-shop-slash,.fa-store-alt-slash{--fa:"\e070"}.fa-store-slash{--fa:"\e071"}.fa-toilet-paper-slash{--fa:"\e072"}.fa-users-slash{--fa:"\e073"}.fa-virus{--fa:"\e074"}.fa-virus-slash{--fa:"\e075"}.fa-viruses{--fa:"\e076"}.fa-vest{--fa:"\e085"}.fa-vest-patches{--fa:"\e086"}.fa-arrow-trend-down{--fa:"\e097"}.fa-arrow-trend-up{--fa:"\e098"}.fa-arrow-up-from-bracket{--fa:"\e09a"}.fa-austral-sign{--fa:"\e0a9"}.fa-baht-sign{--fa:"\e0ac"}.fa-bitcoin-sign{--fa:"\e0b4"}.fa-bolt-lightning{--fa:"\e0b7"}.fa-book-bookmark{--fa:"\e0bb"}.fa-camera-rotate{--fa:"\e0d8"}.fa-cedi-sign{--fa:"\e0df"}.fa-chart-column{--fa:"\e0e3"}.fa-chart-gantt{--fa:"\e0e4"}.fa-clapperboard{--fa:"\e131"}.fa-clover{--fa:"\e139"}.fa-code-compare{--fa:"\e13a"}.fa-code-fork{--fa:"\e13b"}.fa-code-pull-request{--fa:"\e13c"}.fa-colon-sign{--fa:"\e140"}.fa-cruzeiro-sign{--fa:"\e152"}.fa-display{--fa:"\e163"}.fa-dong-sign{--fa:"\e169"}.fa-elevator{--fa:"\e16d"}.fa-filter-circle-xmark{--fa:"\e17b"}.fa-florin-sign{--fa:"\e184"}.fa-folder-closed{--fa:"\e185"}.fa-franc-sign{--fa:"\e18f"}.fa-guarani-sign{--fa:"\e19a"}.fa-gun{--fa:"\e19b"}.fa-hands-clapping{--fa:"\e1a8"}.fa-home-user,.fa-house-user{--fa:"\e1b0"}.fa-indian-rupee,.fa-indian-rupee-sign,.fa-inr{--fa:"\e1bc"}.fa-kip-sign{--fa:"\e1c4"}.fa-lari-sign{--fa:"\e1c8"}.fa-litecoin-sign{--fa:"\e1d3"}.fa-manat-sign{--fa:"\e1d5"}.fa-mask-face{--fa:"\e1d7"}.fa-mill-sign{--fa:"\e1ed"}.fa-money-bills{--fa:"\e1f3"}.fa-naira-sign{--fa:"\e1f6"}.fa-notdef{--fa:"\e1fe"}.fa-panorama{--fa:"\e209"}.fa-peseta-sign{--fa:"\e221"}.fa-peso-sign{--fa:"\e222"}.fa-plane-up{--fa:"\e22d"}.fa-rupiah-sign{--fa:"\e23d"}.fa-stairs{--fa:"\e289"}.fa-timeline{--fa:"\e29c"}.fa-truck-front{--fa:"\e2b7"}.fa-try,.fa-turkish-lira,.fa-turkish-lira-sign{--fa:"\e2bb"}.fa-vault{--fa:"\e2c5"}.fa-magic-wand-sparkles,.fa-wand-magic-sparkles{--fa:"\e2ca"}.fa-wheat-alt,.fa-wheat-awn{--fa:"\e2cd"}.fa-wheelchair-alt,.fa-wheelchair-move{--fa:"\e2ce"}.fa-bangladeshi-taka-sign{--fa:"\e2e6"}.fa-bowl-rice{--fa:"\e2eb"}.fa-person-pregnant{--fa:"\e31e"}.fa-home-lg,.fa-house-chimney{--fa:"\e3af"}.fa-house-crack{--fa:"\e3b1"}.fa-house-medical{--fa:"\e3b2"}.fa-cent-sign{--fa:"\e3f5"}.fa-plus-minus{--fa:"\e43c"}.fa-sailboat{--fa:"\e445"}.fa-section{--fa:"\e447"}.fa-shrimp{--fa:"\e448"}.fa-brazilian-real-sign{--fa:"\e46c"}.fa-chart-simple{--fa:"\e473"}.fa-diagram-next{--fa:"\e476"}.fa-diagram-predecessor{--fa:"\e477"}.fa-diagram-successor{--fa:"\e47a"}.fa-earth-oceania,.fa-globe-oceania{--fa:"\e47b"}.fa-bug-slash{--fa:"\e490"}.fa-file-circle-plus{--fa:"\e494"}.fa-shop-lock{--fa:"\e4a5"}.fa-virus-covid{--fa:"\e4a8"}.fa-virus-covid-slash{--fa:"\e4a9"}.fa-anchor-circle-check{--fa:"\e4aa"}.fa-anchor-circle-exclamation{--fa:"\e4ab"}.fa-anchor-circle-xmark{--fa:"\e4ac"}.fa-anchor-lock{--fa:"\e4ad"}.fa-arrow-down-up-across-line{--fa:"\e4af"}.fa-arrow-down-up-lock{--fa:"\e4b0"}.fa-arrow-right-to-city{--fa:"\e4b3"}.fa-arrow-up-from-ground-water{--fa:"\e4b5"}.fa-arrow-up-from-water-pump{--fa:"\e4b6"}.fa-arrow-up-right-dots{--fa:"\e4b7"}.fa-arrows-down-to-line{--fa:"\e4b8"}.fa-arrows-down-to-people{--fa:"\e4b9"}.fa-arrows-left-right-to-line{--fa:"\e4ba"}.fa-arrows-spin{--fa:"\e4bb"}.fa-arrows-split-up-and-left{--fa:"\e4bc"}.fa-arrows-to-circle{--fa:"\e4bd"}.fa-arrows-to-dot{--fa:"\e4be"}.fa-arrows-to-eye{--fa:"\e4bf"}.fa-arrows-turn-right{--fa:"\e4c0"}.fa-arrows-turn-to-dots{--fa:"\e4c1"}.fa-arrows-up-to-line{--fa:"\e4c2"}.fa-bore-hole{--fa:"\e4c3"}.fa-bottle-droplet{--fa:"\e4c4"}.fa-bottle-water{--fa:"\e4c5"}.fa-bowl-food{--fa:"\e4c6"}.fa-boxes-packing{--fa:"\e4c7"}.fa-bridge{--fa:"\e4c8"}.fa-bridge-circle-check{--fa:"\e4c9"}.fa-bridge-circle-exclamation{--fa:"\e4ca"}.fa-bridge-circle-xmark{--fa:"\e4cb"}.fa-bridge-lock{--fa:"\e4cc"}.fa-bridge-water{--fa:"\e4ce"}.fa-bucket{--fa:"\e4cf"}.fa-bugs{--fa:"\e4d0"}.fa-building-circle-arrow-right{--fa:"\e4d1"}.fa-building-circle-check{--fa:"\e4d2"}.fa-building-circle-exclamation{--fa:"\e4d3"}.fa-building-circle-xmark{--fa:"\e4d4"}.fa-building-flag{--fa:"\e4d5"}.fa-building-lock{--fa:"\e4d6"}.fa-building-ngo{--fa:"\e4d7"}.fa-building-shield{--fa:"\e4d8"}.fa-building-un{--fa:"\e4d9"}.fa-building-user{--fa:"\e4da"}.fa-building-wheat{--fa:"\e4db"}.fa-burst{--fa:"\e4dc"}.fa-car-on{--fa:"\e4dd"}.fa-car-tunnel{--fa:"\e4de"}.fa-child-combatant,.fa-child-rifle{--fa:"\e4e0"}.fa-children{--fa:"\e4e1"}.fa-circle-nodes{--fa:"\e4e2"}.fa-clipboard-question{--fa:"\e4e3"}.fa-cloud-showers-water{--fa:"\e4e4"}.fa-computer{--fa:"\e4e5"}.fa-cubes-stacked{--fa:"\e4e6"}.fa-envelope-circle-check{--fa:"\e4e8"}.fa-explosion{--fa:"\e4e9"}.fa-ferry{--fa:"\e4ea"}.fa-file-circle-exclamation{--fa:"\e4eb"}.fa-file-circle-minus{--fa:"\e4ed"}.fa-file-circle-question{--fa:"\e4ef"}.fa-file-shield{--fa:"\e4f0"}.fa-fire-burner{--fa:"\e4f1"}.fa-fish-fins{--fa:"\e4f2"}.fa-flask-vial{--fa:"\e4f3"}.fa-glass-water{--fa:"\e4f4"}.fa-glass-water-droplet{--fa:"\e4f5"}.fa-group-arrows-rotate{--fa:"\e4f6"}.fa-hand-holding-hand{--fa:"\e4f7"}.fa-handcuffs{--fa:"\e4f8"}.fa-hands-bound{--fa:"\e4f9"}.fa-hands-holding-child{--fa:"\e4fa"}.fa-hands-holding-circle{--fa:"\e4fb"}.fa-heart-circle-bolt{--fa:"\e4fc"}.fa-heart-circle-check{--fa:"\e4fd"}.fa-heart-circle-exclamation{--fa:"\e4fe"}.fa-heart-circle-minus{--fa:"\e4ff"}.fa-heart-circle-plus{--fa:"\e500"}.fa-heart-circle-xmark{--fa:"\e501"}.fa-helicopter-symbol{--fa:"\e502"}.fa-helmet-un{--fa:"\e503"}.fa-hill-avalanche{--fa:"\e507"}.fa-hill-rockslide{--fa:"\e508"}.fa-house-circle-check{--fa:"\e509"}.fa-house-circle-exclamation{--fa:"\e50a"}.fa-house-circle-xmark{--fa:"\e50b"}.fa-house-fire{--fa:"\e50c"}.fa-house-flag{--fa:"\e50d"}.fa-house-flood-water{--fa:"\e50e"}.fa-house-flood-water-circle-arrow-right{--fa:"\e50f"}.fa-house-lock{--fa:"\e510"}.fa-house-medical-circle-check{--fa:"\e511"}.fa-house-medical-circle-exclamation{--fa:"\e512"}.fa-house-medical-circle-xmark{--fa:"\e513"}.fa-house-medical-flag{--fa:"\e514"}.fa-house-tsunami{--fa:"\e515"}.fa-jar{--fa:"\e516"}.fa-jar-wheat{--fa:"\e517"}.fa-jet-fighter-up{--fa:"\e518"}.fa-jug-detergent{--fa:"\e519"}.fa-kitchen-set{--fa:"\e51a"}.fa-land-mine-on{--fa:"\e51b"}.fa-landmark-flag{--fa:"\e51c"}.fa-laptop-file{--fa:"\e51d"}.fa-lines-leaning{--fa:"\e51e"}.fa-location-pin-lock{--fa:"\e51f"}.fa-locust{--fa:"\e520"}.fa-magnifying-glass-arrow-right{--fa:"\e521"}.fa-magnifying-glass-chart{--fa:"\e522"}.fa-mars-and-venus-burst{--fa:"\e523"}.fa-mask-ventilator{--fa:"\e524"}.fa-mattress-pillow{--fa:"\e525"}.fa-mobile-retro{--fa:"\e527"}.fa-money-bill-transfer{--fa:"\e528"}.fa-money-bill-trend-up{--fa:"\e529"}.fa-money-bill-wheat{--fa:"\e52a"}.fa-mosquito{--fa:"\e52b"}.fa-mosquito-net{--fa:"\e52c"}.fa-mound{--fa:"\e52d"}.fa-mountain-city{--fa:"\e52e"}.fa-mountain-sun{--fa:"\e52f"}.fa-oil-well{--fa:"\e532"}.fa-people-group{--fa:"\e533"}.fa-people-line{--fa:"\e534"}.fa-people-pulling{--fa:"\e535"}.fa-people-robbery{--fa:"\e536"}.fa-people-roof{--fa:"\e537"}.fa-person-arrow-down-to-line{--fa:"\e538"}.fa-person-arrow-up-from-line{--fa:"\e539"}.fa-person-breastfeeding{--fa:"\e53a"}.fa-person-burst{--fa:"\e53b"}.fa-person-cane{--fa:"\e53c"}.fa-person-chalkboard{--fa:"\e53d"}.fa-person-circle-check{--fa:"\e53e"}.fa-person-circle-exclamation{--fa:"\e53f"}.fa-person-circle-minus{--fa:"\e540"}.fa-person-circle-plus{--fa:"\e541"}.fa-person-circle-question{--fa:"\e542"}.fa-person-circle-xmark{--fa:"\e543"}.fa-person-dress-burst{--fa:"\e544"}.fa-person-drowning{--fa:"\e545"}.fa-person-falling{--fa:"\e546"}.fa-person-falling-burst{--fa:"\e547"}.fa-person-half-dress{--fa:"\e548"}.fa-person-harassing{--fa:"\e549"}.fa-person-military-pointing{--fa:"\e54a"}.fa-person-military-rifle{--fa:"\e54b"}.fa-person-military-to-person{--fa:"\e54c"}.fa-person-rays{--fa:"\e54d"}.fa-person-rifle{--fa:"\e54e"}.fa-person-shelter{--fa:"\e54f"}.fa-person-walking-arrow-loop-left{--fa:"\e551"}.fa-person-walking-arrow-right{--fa:"\e552"}.fa-person-walking-dashed-line-arrow-right{--fa:"\e553"}.fa-person-walking-luggage{--fa:"\e554"}.fa-plane-circle-check{--fa:"\e555"}.fa-plane-circle-exclamation{--fa:"\e556"}.fa-plane-circle-xmark{--fa:"\e557"}.fa-plane-lock{--fa:"\e558"}.fa-plate-wheat{--fa:"\e55a"}.fa-plug-circle-bolt{--fa:"\e55b"}.fa-plug-circle-check{--fa:"\e55c"}.fa-plug-circle-exclamation{--fa:"\e55d"}.fa-plug-circle-minus{--fa:"\e55e"}.fa-plug-circle-plus{--fa:"\e55f"}.fa-plug-circle-xmark{--fa:"\e560"}.fa-ranking-star{--fa:"\e561"}.fa-road-barrier{--fa:"\e562"}.fa-road-bridge{--fa:"\e563"}.fa-road-circle-check{--fa:"\e564"}.fa-road-circle-exclamation{--fa:"\e565"}.fa-road-circle-xmark{--fa:"\e566"}.fa-road-lock{--fa:"\e567"}.fa-road-spikes{--fa:"\e568"}.fa-rug{--fa:"\e569"}.fa-sack-xmark{--fa:"\e56a"}.fa-school-circle-check{--fa:"\e56b"}.fa-school-circle-exclamation{--fa:"\e56c"}.fa-school-circle-xmark{--fa:"\e56d"}.fa-school-flag{--fa:"\e56e"}.fa-school-lock{--fa:"\e56f"}.fa-sheet-plastic{--fa:"\e571"}.fa-shield-cat{--fa:"\e572"}.fa-shield-dog{--fa:"\e573"}.fa-shield-heart{--fa:"\e574"}.fa-square-nfi{--fa:"\e576"}.fa-square-person-confined{--fa:"\e577"}.fa-square-virus{--fa:"\e578"}.fa-rod-asclepius,.fa-rod-snake,.fa-staff-aesculapius,.fa-staff-snake{--fa:"\e579"}.fa-sun-plant-wilt{--fa:"\e57a"}.fa-tarp{--fa:"\e57b"}.fa-tarp-droplet{--fa:"\e57c"}.fa-tent{--fa:"\e57d"}.fa-tent-arrow-down-to-line{--fa:"\e57e"}.fa-tent-arrow-left-right{--fa:"\e57f"}.fa-tent-arrow-turn-left{--fa:"\e580"}.fa-tent-arrows-down{--fa:"\e581"}.fa-tents{--fa:"\e582"}.fa-toilet-portable{--fa:"\e583"}.fa-toilets-portable{--fa:"\e584"}.fa-tower-cell{--fa:"\e585"}.fa-tower-observation{--fa:"\e586"}.fa-tree-city{--fa:"\e587"}.fa-trowel{--fa:"\e589"}.fa-trowel-bricks{--fa:"\e58a"}.fa-truck-arrow-right{--fa:"\e58b"}.fa-truck-droplet{--fa:"\e58c"}.fa-truck-field{--fa:"\e58d"}.fa-truck-field-un{--fa:"\e58e"}.fa-truck-plane{--fa:"\e58f"}.fa-users-between-lines{--fa:"\e591"}.fa-users-line{--fa:"\e592"}.fa-users-rays{--fa:"\e593"}.fa-users-rectangle{--fa:"\e594"}.fa-users-viewfinder{--fa:"\e595"}.fa-vial-circle-check{--fa:"\e596"}.fa-vial-virus{--fa:"\e597"}.fa-wheat-awn-circle-exclamation{--fa:"\e598"}.fa-worm{--fa:"\e599"}.fa-xmarks-lines{--fa:"\e59a"}.fa-child-dress{--fa:"\e59c"}.fa-child-reaching{--fa:"\e59d"}.fa-file-circle-check{--fa:"\e5a0"}.fa-file-circle-xmark{--fa:"\e5a1"}.fa-person-through-window{--fa:"\e5a9"}.fa-plant-wilt{--fa:"\e5aa"}.fa-stapler{--fa:"\e5af"}.fa-train-tram{--fa:"\e5b4"}.fa-table-cells-column-lock{--fa:"\e678"}.fa-table-cells-row-lock{--fa:"\e67a"}.fa-thumb-tack-slash,.fa-thumbtack-slash{--fa:"\e68f"}.fa-table-cells-row-unlock{--fa:"\e691"}.fa-chart-diagram{--fa:"\e695"}.fa-comment-nodes{--fa:"\e696"}.fa-file-fragment{--fa:"\e697"}.fa-file-half-dashed{--fa:"\e698"}.fa-hexagon-nodes{--fa:"\e699"}.fa-hexagon-nodes-bolt{--fa:"\e69a"}.fa-square-binary{--fa:"\e69b"}.fa-pentagon{--fa:"\e790"}.fa-non-binary{--fa:"\e807"}.fa-spiral{--fa:"\e80a"}.fa-mobile-vibrate{--fa:"\e816"}.fa-single-quote-left{--fa:"\e81b"}.fa-single-quote-right{--fa:"\e81c"}.fa-bus-side{--fa:"\e81d"}.fa-heptagon,.fa-septagon{--fa:"\e820"}.fa-glass-martini,.fa-martini-glass-empty{--fa:"\f000"}.fa-music{--fa:"\f001"}.fa-magnifying-glass,.fa-search{--fa:"\f002"}.fa-heart{--fa:"\f004"}.fa-star{--fa:"\f005"}.fa-user,.fa-user-alt,.fa-user-large{--fa:"\f007"}.fa-film,.fa-film-alt,.fa-film-simple{--fa:"\f008"}.fa-table-cells-large,.fa-th-large{--fa:"\f009"}.fa-table-cells,.fa-th{--fa:"\f00a"}.fa-table-list,.fa-th-list{--fa:"\f00b"}.fa-check{--fa:"\f00c"}.fa-close,.fa-multiply,.fa-remove,.fa-times,.fa-xmark{--fa:"\f00d"}.fa-magnifying-glass-plus,.fa-search-plus{--fa:"\f00e"}.fa-magnifying-glass-minus,.fa-search-minus{--fa:"\f010"}.fa-power-off{--fa:"\f011"}.fa-signal,.fa-signal-5,.fa-signal-perfect{--fa:"\f012"}.fa-cog,.fa-gear{--fa:"\f013"}.fa-home,.fa-home-alt,.fa-home-lg-alt,.fa-house{--fa:"\f015"}.fa-clock,.fa-clock-four{--fa:"\f017"}.fa-road{--fa:"\f018"}.fa-download{--fa:"\f019"}.fa-inbox{--fa:"\f01c"}.fa-arrow-right-rotate,.fa-arrow-rotate-forward,.fa-arrow-rotate-right,.fa-redo{--fa:"\f01e"}.fa-arrows-rotate,.fa-refresh,.fa-sync{--fa:"\f021"}.fa-list-alt,.fa-rectangle-list{--fa:"\f022"}.fa-lock{--fa:"\f023"}.fa-flag{--fa:"\f024"}.fa-headphones,.fa-headphones-alt,.fa-headphones-simple{--fa:"\f025"}.fa-volume-off{--fa:"\f026"}.fa-volume-down,.fa-volume-low{--fa:"\f027"}.fa-volume-high,.fa-volume-up{--fa:"\f028"}.fa-qrcode{--fa:"\f029"}.fa-barcode{--fa:"\f02a"}.fa-tag{--fa:"\f02b"}.fa-tags{--fa:"\f02c"}.fa-book{--fa:"\f02d"}.fa-bookmark{--fa:"\f02e"}.fa-print{--fa:"\f02f"}.fa-camera,.fa-camera-alt{--fa:"\f030"}.fa-font{--fa:"\f031"}.fa-bold{--fa:"\f032"}.fa-italic{--fa:"\f033"}.fa-text-height{--fa:"\f034"}.fa-text-width{--fa:"\f035"}.fa-align-left{--fa:"\f036"}.fa-align-center{--fa:"\f037"}.fa-align-right{--fa:"\f038"}.fa-align-justify{--fa:"\f039"}.fa-list,.fa-list-squares{--fa:"\f03a"}.fa-dedent,.fa-outdent{--fa:"\f03b"}.fa-indent{--fa:"\f03c"}.fa-video,.fa-video-camera{--fa:"\f03d"}.fa-image{--fa:"\f03e"}.fa-location-pin,.fa-map-marker{--fa:"\f041"}.fa-adjust,.fa-circle-half-stroke{--fa:"\f042"}.fa-droplet,.fa-tint{--fa:"\f043"}.fa-edit,.fa-pen-to-square{--fa:"\f044"}.fa-arrows,.fa-arrows-up-down-left-right{--fa:"\f047"}.fa-backward-step,.fa-step-backward{--fa:"\f048"}.fa-backward-fast,.fa-fast-backward{--fa:"\f049"}.fa-backward{--fa:"\f04a"}.fa-play{--fa:"\f04b"}.fa-pause{--fa:"\f04c"}.fa-stop{--fa:"\f04d"}.fa-forward{--fa:"\f04e"}.fa-fast-forward,.fa-forward-fast{--fa:"\f050"}.fa-forward-step,.fa-step-forward{--fa:"\f051"}.fa-eject{--fa:"\f052"}.fa-chevron-left{--fa:"\f053"}.fa-chevron-right{--fa:"\f054"}.fa-circle-plus,.fa-plus-circle{--fa:"\f055"}.fa-circle-minus,.fa-minus-circle{--fa:"\f056"}.fa-circle-xmark,.fa-times-circle,.fa-xmark-circle{--fa:"\f057"}.fa-check-circle,.fa-circle-check{--fa:"\f058"}.fa-circle-question,.fa-question-circle{--fa:"\f059"}.fa-circle-info,.fa-info-circle{--fa:"\f05a"}.fa-crosshairs{--fa:"\f05b"}.fa-ban,.fa-cancel{--fa:"\f05e"}.fa-arrow-left{--fa:"\f060"}.fa-arrow-right{--fa:"\f061"}.fa-arrow-up{--fa:"\f062"}.fa-arrow-down{--fa:"\f063"}.fa-mail-forward,.fa-share{--fa:"\f064"}.fa-expand{--fa:"\f065"}.fa-compress{--fa:"\f066"}.fa-minus,.fa-subtract{--fa:"\f068"}.fa-circle-exclamation,.fa-exclamation-circle{--fa:"\f06a"}.fa-gift{--fa:"\f06b"}.fa-leaf{--fa:"\f06c"}.fa-fire{--fa:"\f06d"}.fa-eye{--fa:"\f06e"}.fa-eye-slash{--fa:"\f070"}.fa-exclamation-triangle,.fa-triangle-exclamation,.fa-warning{--fa:"\f071"}.fa-plane{--fa:"\f072"}.fa-calendar-alt,.fa-calendar-days{--fa:"\f073"}.fa-random,.fa-shuffle{--fa:"\f074"}.fa-comment{--fa:"\f075"}.fa-magnet{--fa:"\f076"}.fa-chevron-up{--fa:"\f077"}.fa-chevron-down{--fa:"\f078"}.fa-retweet{--fa:"\f079"}.fa-cart-shopping,.fa-shopping-cart{--fa:"\f07a"}.fa-folder,.fa-folder-blank{--fa:"\f07b"}.fa-folder-open{--fa:"\f07c"}.fa-arrows-up-down,.fa-arrows-v{--fa:"\f07d"}.fa-arrows-h,.fa-arrows-left-right{--fa:"\f07e"}.fa-bar-chart,.fa-chart-bar{--fa:"\f080"}.fa-camera-retro{--fa:"\f083"}.fa-key{--fa:"\f084"}.fa-cogs,.fa-gears{--fa:"\f085"}.fa-comments{--fa:"\f086"}.fa-star-half{--fa:"\f089"}.fa-arrow-right-from-bracket,.fa-sign-out{--fa:"\f08b"}.fa-thumb-tack,.fa-thumbtack{--fa:"\f08d"}.fa-arrow-up-right-from-square,.fa-external-link{--fa:"\f08e"}.fa-arrow-right-to-bracket,.fa-sign-in{--fa:"\f090"}.fa-trophy{--fa:"\f091"}.fa-upload{--fa:"\f093"}.fa-lemon{--fa:"\f094"}.fa-phone{--fa:"\f095"}.fa-phone-square,.fa-square-phone{--fa:"\f098"}.fa-unlock{--fa:"\f09c"}.fa-credit-card,.fa-credit-card-alt{--fa:"\f09d"}.fa-feed,.fa-rss{--fa:"\f09e"}.fa-hard-drive,.fa-hdd{--fa:"\f0a0"}.fa-bullhorn{--fa:"\f0a1"}.fa-certificate{--fa:"\f0a3"}.fa-hand-point-right{--fa:"\f0a4"}.fa-hand-point-left{--fa:"\f0a5"}.fa-hand-point-up{--fa:"\f0a6"}.fa-hand-point-down{--fa:"\f0a7"}.fa-arrow-circle-left,.fa-circle-arrow-left{--fa:"\f0a8"}.fa-arrow-circle-right,.fa-circle-arrow-right{--fa:"\f0a9"}.fa-arrow-circle-up,.fa-circle-arrow-up{--fa:"\f0aa"}.fa-arrow-circle-down,.fa-circle-arrow-down{--fa:"\f0ab"}.fa-globe{--fa:"\f0ac"}.fa-wrench{--fa:"\f0ad"}.fa-list-check,.fa-tasks{--fa:"\f0ae"}.fa-filter{--fa:"\f0b0"}.fa-briefcase{--fa:"\f0b1"}.fa-arrows-alt,.fa-up-down-left-right{--fa:"\f0b2"}.fa-users{--fa:"\f0c0"}.fa-chain,.fa-link{--fa:"\f0c1"}.fa-cloud{--fa:"\f0c2"}.fa-flask{--fa:"\f0c3"}.fa-cut,.fa-scissors{--fa:"\f0c4"}.fa-copy{--fa:"\f0c5"}.fa-paperclip{--fa:"\f0c6"}.fa-floppy-disk,.fa-save{--fa:"\f0c7"}.fa-square{--fa:"\f0c8"}.fa-bars,.fa-navicon{--fa:"\f0c9"}.fa-list-dots,.fa-list-ul{--fa:"\f0ca"}.fa-list-1-2,.fa-list-numeric,.fa-list-ol{--fa:"\f0cb"}.fa-strikethrough{--fa:"\f0cc"}.fa-underline{--fa:"\f0cd"}.fa-table{--fa:"\f0ce"}.fa-magic,.fa-wand-magic{--fa:"\f0d0"}.fa-truck{--fa:"\f0d1"}.fa-money-bill{--fa:"\f0d6"}.fa-caret-down{--fa:"\f0d7"}.fa-caret-up{--fa:"\f0d8"}.fa-caret-left{--fa:"\f0d9"}.fa-caret-right{--fa:"\f0da"}.fa-columns,.fa-table-columns{--fa:"\f0db"}.fa-sort,.fa-unsorted{--fa:"\f0dc"}.fa-sort-desc,.fa-sort-down{--fa:"\f0dd"}.fa-sort-asc,.fa-sort-up{--fa:"\f0de"}.fa-envelope{--fa:"\f0e0"}.fa-arrow-left-rotate,.fa-arrow-rotate-back,.fa-arrow-rotate-backward,.fa-arrow-rotate-left,.fa-undo{--fa:"\f0e2"}.fa-gavel,.fa-legal{--fa:"\f0e3"}.fa-bolt,.fa-zap{--fa:"\f0e7"}.fa-sitemap{--fa:"\f0e8"}.fa-umbrella{--fa:"\f0e9"}.fa-file-clipboard,.fa-paste{--fa:"\f0ea"}.fa-lightbulb{--fa:"\f0eb"}.fa-arrow-right-arrow-left,.fa-exchange{--fa:"\f0ec"}.fa-cloud-arrow-down,.fa-cloud-download,.fa-cloud-download-alt{--fa:"\f0ed"}.fa-cloud-arrow-up,.fa-cloud-upload,.fa-cloud-upload-alt{--fa:"\f0ee"}.fa-user-doctor,.fa-user-md{--fa:"\f0f0"}.fa-stethoscope{--fa:"\f0f1"}.fa-suitcase{--fa:"\f0f2"}.fa-bell{--fa:"\f0f3"}.fa-coffee,.fa-mug-saucer{--fa:"\f0f4"}.fa-hospital,.fa-hospital-alt,.fa-hospital-wide{--fa:"\f0f8"}.fa-ambulance,.fa-truck-medical{--fa:"\f0f9"}.fa-medkit,.fa-suitcase-medical{--fa:"\f0fa"}.fa-fighter-jet,.fa-jet-fighter{--fa:"\f0fb"}.fa-beer,.fa-beer-mug-empty{--fa:"\f0fc"}.fa-h-square,.fa-square-h{--fa:"\f0fd"}.fa-plus-square,.fa-square-plus{--fa:"\f0fe"}.fa-angle-double-left,.fa-angles-left{--fa:"\f100"}.fa-angle-double-right,.fa-angles-right{--fa:"\f101"}.fa-angle-double-up,.fa-angles-up{--fa:"\f102"}.fa-angle-double-down,.fa-angles-down{--fa:"\f103"}.fa-angle-left{--fa:"\f104"}.fa-angle-right{--fa:"\f105"}.fa-angle-up{--fa:"\f106"}.fa-angle-down{--fa:"\f107"}.fa-laptop{--fa:"\f109"}.fa-tablet-button{--fa:"\f10a"}.fa-mobile-button{--fa:"\f10b"}.fa-quote-left,.fa-quote-left-alt{--fa:"\f10d"}.fa-quote-right,.fa-quote-right-alt{--fa:"\f10e"}.fa-spinner{--fa:"\f110"}.fa-circle{--fa:"\f111"}.fa-face-smile,.fa-smile{--fa:"\f118"}.fa-face-frown,.fa-frown{--fa:"\f119"}.fa-face-meh,.fa-meh{--fa:"\f11a"}.fa-gamepad{--fa:"\f11b"}.fa-keyboard{--fa:"\f11c"}.fa-flag-checkered{--fa:"\f11e"}.fa-terminal{--fa:"\f120"}.fa-code{--fa:"\f121"}.fa-mail-reply-all,.fa-reply-all{--fa:"\f122"}.fa-location-arrow{--fa:"\f124"}.fa-crop{--fa:"\f125"}.fa-code-branch{--fa:"\f126"}.fa-chain-broken,.fa-chain-slash,.fa-link-slash,.fa-unlink{--fa:"\f127"}.fa-info{--fa:"\f129"}.fa-superscript{--fa:"\f12b"}.fa-subscript{--fa:"\f12c"}.fa-eraser{--fa:"\f12d"}.fa-puzzle-piece{--fa:"\f12e"}.fa-microphone{--fa:"\f130"}.fa-microphone-slash{--fa:"\f131"}.fa-shield,.fa-shield-blank{--fa:"\f132"}.fa-calendar{--fa:"\f133"}.fa-fire-extinguisher{--fa:"\f134"}.fa-rocket{--fa:"\f135"}.fa-chevron-circle-left,.fa-circle-chevron-left{--fa:"\f137"}.fa-chevron-circle-right,.fa-circle-chevron-right{--fa:"\f138"}.fa-chevron-circle-up,.fa-circle-chevron-up{--fa:"\f139"}.fa-chevron-circle-down,.fa-circle-chevron-down{--fa:"\f13a"}.fa-anchor{--fa:"\f13d"}.fa-unlock-alt,.fa-unlock-keyhole{--fa:"\f13e"}.fa-bullseye{--fa:"\f140"}.fa-ellipsis,.fa-ellipsis-h{--fa:"\f141"}.fa-ellipsis-v,.fa-ellipsis-vertical{--fa:"\f142"}.fa-rss-square,.fa-square-rss{--fa:"\f143"}.fa-circle-play,.fa-play-circle{--fa:"\f144"}.fa-ticket{--fa:"\f145"}.fa-minus-square,.fa-square-minus{--fa:"\f146"}.fa-arrow-turn-up,.fa-level-up{--fa:"\f148"}.fa-arrow-turn-down,.fa-level-down{--fa:"\f149"}.fa-check-square,.fa-square-check{--fa:"\f14a"}.fa-pen-square,.fa-pencil-square,.fa-square-pen{--fa:"\f14b"}.fa-external-link-square,.fa-square-arrow-up-right{--fa:"\f14c"}.fa-share-from-square,.fa-share-square{--fa:"\f14d"}.fa-compass{--fa:"\f14e"}.fa-caret-square-down,.fa-square-caret-down{--fa:"\f150"}.fa-caret-square-up,.fa-square-caret-up{--fa:"\f151"}.fa-caret-square-right,.fa-square-caret-right{--fa:"\f152"}.fa-eur,.fa-euro,.fa-euro-sign{--fa:"\f153"}.fa-gbp,.fa-pound-sign,.fa-sterling-sign{--fa:"\f154"}.fa-rupee,.fa-rupee-sign{--fa:"\f156"}.fa-cny,.fa-jpy,.fa-rmb,.fa-yen,.fa-yen-sign{--fa:"\f157"}.fa-rouble,.fa-rub,.fa-ruble,.fa-ruble-sign{--fa:"\f158"}.fa-krw,.fa-won,.fa-won-sign{--fa:"\f159"}.fa-file{--fa:"\f15b"}.fa-file-alt,.fa-file-lines,.fa-file-text{--fa:"\f15c"}.fa-arrow-down-a-z,.fa-sort-alpha-asc,.fa-sort-alpha-down{--fa:"\f15d"}.fa-arrow-up-a-z,.fa-sort-alpha-up{--fa:"\f15e"}.fa-arrow-down-wide-short,.fa-sort-amount-asc,.fa-sort-amount-down{--fa:"\f160"}.fa-arrow-up-wide-short,.fa-sort-amount-up{--fa:"\f161"}.fa-arrow-down-1-9,.fa-sort-numeric-asc,.fa-sort-numeric-down{--fa:"\f162"}.fa-arrow-up-1-9,.fa-sort-numeric-up{--fa:"\f163"}.fa-thumbs-up{--fa:"\f164"}.fa-thumbs-down{--fa:"\f165"}.fa-arrow-down-long,.fa-long-arrow-down{--fa:"\f175"}.fa-arrow-up-long,.fa-long-arrow-up{--fa:"\f176"}.fa-arrow-left-long,.fa-long-arrow-left{--fa:"\f177"}.fa-arrow-right-long,.fa-long-arrow-right{--fa:"\f178"}.fa-female,.fa-person-dress{--fa:"\f182"}.fa-male,.fa-person{--fa:"\f183"}.fa-sun{--fa:"\f185"}.fa-moon{--fa:"\f186"}.fa-archive,.fa-box-archive{--fa:"\f187"}.fa-bug{--fa:"\f188"}.fa-caret-square-left,.fa-square-caret-left{--fa:"\f191"}.fa-circle-dot,.fa-dot-circle{--fa:"\f192"}.fa-wheelchair{--fa:"\f193"}.fa-lira-sign{--fa:"\f195"}.fa-shuttle-space,.fa-space-shuttle{--fa:"\f197"}.fa-envelope-square,.fa-square-envelope{--fa:"\f199"}.fa-bank,.fa-building-columns,.fa-institution,.fa-museum,.fa-university{--fa:"\f19c"}.fa-graduation-cap,.fa-mortar-board{--fa:"\f19d"}.fa-language{--fa:"\f1ab"}.fa-fax{--fa:"\f1ac"}.fa-building{--fa:"\f1ad"}.fa-child{--fa:"\f1ae"}.fa-paw{--fa:"\f1b0"}.fa-cube{--fa:"\f1b2"}.fa-cubes{--fa:"\f1b3"}.fa-recycle{--fa:"\f1b8"}.fa-automobile,.fa-car{--fa:"\f1b9"}.fa-cab,.fa-taxi{--fa:"\f1ba"}.fa-tree{--fa:"\f1bb"}.fa-database{--fa:"\f1c0"}.fa-file-pdf{--fa:"\f1c1"}.fa-file-word{--fa:"\f1c2"}.fa-file-excel{--fa:"\f1c3"}.fa-file-powerpoint{--fa:"\f1c4"}.fa-file-image{--fa:"\f1c5"}.fa-file-archive,.fa-file-zipper{--fa:"\f1c6"}.fa-file-audio{--fa:"\f1c7"}.fa-file-video{--fa:"\f1c8"}.fa-file-code{--fa:"\f1c9"}.fa-life-ring{--fa:"\f1cd"}.fa-circle-notch{--fa:"\f1ce"}.fa-paper-plane{--fa:"\f1d8"}.fa-clock-rotate-left,.fa-history{--fa:"\f1da"}.fa-header,.fa-heading{--fa:"\f1dc"}.fa-paragraph{--fa:"\f1dd"}.fa-sliders,.fa-sliders-h{--fa:"\f1de"}.fa-share-alt,.fa-share-nodes{--fa:"\f1e0"}.fa-share-alt-square,.fa-square-share-nodes{--fa:"\f1e1"}.fa-bomb{--fa:"\f1e2"}.fa-futbol,.fa-futbol-ball,.fa-soccer-ball{--fa:"\f1e3"}.fa-teletype,.fa-tty{--fa:"\f1e4"}.fa-binoculars{--fa:"\f1e5"}.fa-plug{--fa:"\f1e6"}.fa-newspaper{--fa:"\f1ea"}.fa-wifi,.fa-wifi-3,.fa-wifi-strong{--fa:"\f1eb"}.fa-calculator{--fa:"\f1ec"}.fa-bell-slash{--fa:"\f1f6"}.fa-trash{--fa:"\f1f8"}.fa-copyright{--fa:"\f1f9"}.fa-eye-dropper,.fa-eye-dropper-empty,.fa-eyedropper{--fa:"\f1fb"}.fa-paint-brush,.fa-paintbrush{--fa:"\f1fc"}.fa-birthday-cake,.fa-cake,.fa-cake-candles{--fa:"\f1fd"}.fa-area-chart,.fa-chart-area{--fa:"\f1fe"}.fa-chart-pie,.fa-pie-chart{--fa:"\f200"}.fa-chart-line,.fa-line-chart{--fa:"\f201"}.fa-toggle-off{--fa:"\f204"}.fa-toggle-on{--fa:"\f205"}.fa-bicycle{--fa:"\f206"}.fa-bus{--fa:"\f207"}.fa-closed-captioning{--fa:"\f20a"}.fa-ils,.fa-shekel,.fa-shekel-sign,.fa-sheqel,.fa-sheqel-sign{--fa:"\f20b"}.fa-cart-plus{--fa:"\f217"}.fa-cart-arrow-down{--fa:"\f218"}.fa-diamond{--fa:"\f219"}.fa-ship{--fa:"\f21a"}.fa-user-secret{--fa:"\f21b"}.fa-motorcycle{--fa:"\f21c"}.fa-street-view{--fa:"\f21d"}.fa-heart-pulse,.fa-heartbeat{--fa:"\f21e"}.fa-venus{--fa:"\f221"}.fa-mars{--fa:"\f222"}.fa-mercury{--fa:"\f223"}.fa-mars-and-venus{--fa:"\f224"}.fa-transgender,.fa-transgender-alt{--fa:"\f225"}.fa-venus-double{--fa:"\f226"}.fa-mars-double{--fa:"\f227"}.fa-venus-mars{--fa:"\f228"}.fa-mars-stroke{--fa:"\f229"}.fa-mars-stroke-up,.fa-mars-stroke-v{--fa:"\f22a"}.fa-mars-stroke-h,.fa-mars-stroke-right{--fa:"\f22b"}.fa-neuter{--fa:"\f22c"}.fa-genderless{--fa:"\f22d"}.fa-server{--fa:"\f233"}.fa-user-plus{--fa:"\f234"}.fa-user-times,.fa-user-xmark{--fa:"\f235"}.fa-bed{--fa:"\f236"}.fa-train{--fa:"\f238"}.fa-subway,.fa-train-subway{--fa:"\f239"}.fa-battery,.fa-battery-5,.fa-battery-full{--fa:"\f240"}.fa-battery-4,.fa-battery-three-quarters{--fa:"\f241"}.fa-battery-3,.fa-battery-half{--fa:"\f242"}.fa-battery-2,.fa-battery-quarter{--fa:"\f243"}.fa-battery-0,.fa-battery-empty{--fa:"\f244"}.fa-arrow-pointer,.fa-mouse-pointer{--fa:"\f245"}.fa-i-cursor{--fa:"\f246"}.fa-object-group{--fa:"\f247"}.fa-object-ungroup{--fa:"\f248"}.fa-note-sticky,.fa-sticky-note{--fa:"\f249"}.fa-clone{--fa:"\f24d"}.fa-balance-scale,.fa-scale-balanced{--fa:"\f24e"}.fa-hourglass-1,.fa-hourglass-start{--fa:"\f251"}.fa-hourglass-2,.fa-hourglass-half{--fa:"\f252"}.fa-hourglass-3,.fa-hourglass-end{--fa:"\f253"}.fa-hourglass,.fa-hourglass-empty{--fa:"\f254"}.fa-hand-back-fist,.fa-hand-rock{--fa:"\f255"}.fa-hand,.fa-hand-paper{--fa:"\f256"}.fa-hand-scissors{--fa:"\f257"}.fa-hand-lizard{--fa:"\f258"}.fa-hand-spock{--fa:"\f259"}.fa-hand-pointer{--fa:"\f25a"}.fa-hand-peace{--fa:"\f25b"}.fa-trademark{--fa:"\f25c"}.fa-registered{--fa:"\f25d"}.fa-television,.fa-tv,.fa-tv-alt{--fa:"\f26c"}.fa-calendar-plus{--fa:"\f271"}.fa-calendar-minus{--fa:"\f272"}.fa-calendar-times,.fa-calendar-xmark{--fa:"\f273"}.fa-calendar-check{--fa:"\f274"}.fa-industry{--fa:"\f275"}.fa-map-pin{--fa:"\f276"}.fa-map-signs,.fa-signs-post{--fa:"\f277"}.fa-map{--fa:"\f279"}.fa-comment-alt,.fa-message{--fa:"\f27a"}.fa-circle-pause,.fa-pause-circle{--fa:"\f28b"}.fa-circle-stop,.fa-stop-circle{--fa:"\f28d"}.fa-bag-shopping,.fa-shopping-bag{--fa:"\f290"}.fa-basket-shopping,.fa-shopping-basket{--fa:"\f291"}.fa-universal-access{--fa:"\f29a"}.fa-blind,.fa-person-walking-with-cane{--fa:"\f29d"}.fa-audio-description{--fa:"\f29e"}.fa-phone-volume,.fa-volume-control-phone{--fa:"\f2a0"}.fa-braille{--fa:"\f2a1"}.fa-assistive-listening-systems,.fa-ear-listen{--fa:"\f2a2"}.fa-american-sign-language-interpreting,.fa-asl-interpreting,.fa-hands-american-sign-language-interpreting,.fa-hands-asl-interpreting{--fa:"\f2a3"}.fa-deaf,.fa-deafness,.fa-ear-deaf,.fa-hard-of-hearing{--fa:"\f2a4"}.fa-hands,.fa-sign-language,.fa-signing{--fa:"\f2a7"}.fa-eye-low-vision,.fa-low-vision{--fa:"\f2a8"}.fa-handshake,.fa-handshake-alt,.fa-handshake-simple{--fa:"\f2b5"}.fa-envelope-open{--fa:"\f2b6"}.fa-address-book,.fa-contact-book{--fa:"\f2b9"}.fa-address-card,.fa-contact-card,.fa-vcard{--fa:"\f2bb"}.fa-circle-user,.fa-user-circle{--fa:"\f2bd"}.fa-id-badge{--fa:"\f2c1"}.fa-drivers-license,.fa-id-card{--fa:"\f2c2"}.fa-temperature-4,.fa-temperature-full,.fa-thermometer-4,.fa-thermometer-full{--fa:"\f2c7"}.fa-temperature-3,.fa-temperature-three-quarters,.fa-thermometer-3,.fa-thermometer-three-quarters{--fa:"\f2c8"}.fa-temperature-2,.fa-temperature-half,.fa-thermometer-2,.fa-thermometer-half{--fa:"\f2c9"}.fa-temperature-1,.fa-temperature-quarter,.fa-thermometer-1,.fa-thermometer-quarter{--fa:"\f2ca"}.fa-temperature-0,.fa-temperature-empty,.fa-thermometer-0,.fa-thermometer-empty{--fa:"\f2cb"}.fa-shower{--fa:"\f2cc"}.fa-bath,.fa-bathtub{--fa:"\f2cd"}.fa-podcast{--fa:"\f2ce"}.fa-window-maximize{--fa:"\f2d0"}.fa-window-minimize{--fa:"\f2d1"}.fa-window-restore{--fa:"\f2d2"}.fa-square-xmark,.fa-times-square,.fa-xmark-square{--fa:"\f2d3"}.fa-microchip{--fa:"\f2db"}.fa-snowflake{--fa:"\f2dc"}.fa-spoon,.fa-utensil-spoon{--fa:"\f2e5"}.fa-cutlery,.fa-utensils{--fa:"\f2e7"}.fa-rotate-back,.fa-rotate-backward,.fa-rotate-left,.fa-undo-alt{--fa:"\f2ea"}.fa-trash-alt,.fa-trash-can{--fa:"\f2ed"}.fa-rotate,.fa-sync-alt{--fa:"\f2f1"}.fa-stopwatch{--fa:"\f2f2"}.fa-right-from-bracket,.fa-sign-out-alt{--fa:"\f2f5"}.fa-right-to-bracket,.fa-sign-in-alt{--fa:"\f2f6"}.fa-redo-alt,.fa-rotate-forward,.fa-rotate-right{--fa:"\f2f9"}.fa-poo{--fa:"\f2fe"}.fa-images{--fa:"\f302"}.fa-pencil,.fa-pencil-alt{--fa:"\f303"}.fa-pen{--fa:"\f304"}.fa-pen-alt,.fa-pen-clip{--fa:"\f305"}.fa-octagon{--fa:"\f306"}.fa-down-long,.fa-long-arrow-alt-down{--fa:"\f309"}.fa-left-long,.fa-long-arrow-alt-left{--fa:"\f30a"}.fa-long-arrow-alt-right,.fa-right-long{--fa:"\f30b"}.fa-long-arrow-alt-up,.fa-up-long{--fa:"\f30c"}.fa-hexagon{--fa:"\f312"}.fa-file-edit,.fa-file-pen{--fa:"\f31c"}.fa-expand-arrows-alt,.fa-maximize{--fa:"\f31e"}.fa-clipboard{--fa:"\f328"}.fa-arrows-alt-h,.fa-left-right{--fa:"\f337"}.fa-arrows-alt-v,.fa-up-down{--fa:"\f338"}.fa-alarm-clock{--fa:"\f34e"}.fa-arrow-alt-circle-down,.fa-circle-down{--fa:"\f358"}.fa-arrow-alt-circle-left,.fa-circle-left{--fa:"\f359"}.fa-arrow-alt-circle-right,.fa-circle-right{--fa:"\f35a"}.fa-arrow-alt-circle-up,.fa-circle-up{--fa:"\f35b"}.fa-external-link-alt,.fa-up-right-from-square{--fa:"\f35d"}.fa-external-link-square-alt,.fa-square-up-right{--fa:"\f360"}.fa-exchange-alt,.fa-right-left{--fa:"\f362"}.fa-repeat{--fa:"\f363"}.fa-code-commit{--fa:"\f386"}.fa-code-merge{--fa:"\f387"}.fa-desktop,.fa-desktop-alt{--fa:"\f390"}.fa-gem{--fa:"\f3a5"}.fa-level-down-alt,.fa-turn-down{--fa:"\f3be"}.fa-level-up-alt,.fa-turn-up{--fa:"\f3bf"}.fa-lock-open{--fa:"\f3c1"}.fa-location-dot,.fa-map-marker-alt{--fa:"\f3c5"}.fa-microphone-alt,.fa-microphone-lines{--fa:"\f3c9"}.fa-mobile-alt,.fa-mobile-screen-button{--fa:"\f3cd"}.fa-mobile,.fa-mobile-android,.fa-mobile-phone{--fa:"\f3ce"}.fa-mobile-android-alt,.fa-mobile-screen{--fa:"\f3cf"}.fa-money-bill-1,.fa-money-bill-alt{--fa:"\f3d1"}.fa-phone-slash{--fa:"\f3dd"}.fa-image-portrait,.fa-portrait{--fa:"\f3e0"}.fa-mail-reply,.fa-reply{--fa:"\f3e5"}.fa-shield-alt,.fa-shield-halved{--fa:"\f3ed"}.fa-tablet-alt,.fa-tablet-screen-button{--fa:"\f3fa"}.fa-tablet,.fa-tablet-android{--fa:"\f3fb"}.fa-ticket-alt,.fa-ticket-simple{--fa:"\f3ff"}.fa-rectangle-times,.fa-rectangle-xmark,.fa-times-rectangle,.fa-window-close{--fa:"\f410"}.fa-compress-alt,.fa-down-left-and-up-right-to-center{--fa:"\f422"}.fa-expand-alt,.fa-up-right-and-down-left-from-center{--fa:"\f424"}.fa-baseball-bat-ball{--fa:"\f432"}.fa-baseball,.fa-baseball-ball{--fa:"\f433"}.fa-basketball,.fa-basketball-ball{--fa:"\f434"}.fa-bowling-ball{--fa:"\f436"}.fa-chess{--fa:"\f439"}.fa-chess-bishop{--fa:"\f43a"}.fa-chess-board{--fa:"\f43c"}.fa-chess-king{--fa:"\f43f"}.fa-chess-knight{--fa:"\f441"}.fa-chess-pawn{--fa:"\f443"}.fa-chess-queen{--fa:"\f445"}.fa-chess-rook{--fa:"\f447"}.fa-dumbbell{--fa:"\f44b"}.fa-football,.fa-football-ball{--fa:"\f44e"}.fa-golf-ball,.fa-golf-ball-tee{--fa:"\f450"}.fa-hockey-puck{--fa:"\f453"}.fa-broom-ball,.fa-quidditch,.fa-quidditch-broom-ball{--fa:"\f458"}.fa-square-full{--fa:"\f45c"}.fa-ping-pong-paddle-ball,.fa-table-tennis,.fa-table-tennis-paddle-ball{--fa:"\f45d"}.fa-volleyball,.fa-volleyball-ball{--fa:"\f45f"}.fa-allergies,.fa-hand-dots{--fa:"\f461"}.fa-band-aid,.fa-bandage{--fa:"\f462"}.fa-box{--fa:"\f466"}.fa-boxes,.fa-boxes-alt,.fa-boxes-stacked{--fa:"\f468"}.fa-briefcase-medical{--fa:"\f469"}.fa-burn,.fa-fire-flame-simple{--fa:"\f46a"}.fa-capsules{--fa:"\f46b"}.fa-clipboard-check{--fa:"\f46c"}.fa-clipboard-list{--fa:"\f46d"}.fa-diagnoses,.fa-person-dots-from-line{--fa:"\f470"}.fa-dna{--fa:"\f471"}.fa-dolly,.fa-dolly-box{--fa:"\f472"}.fa-cart-flatbed,.fa-dolly-flatbed{--fa:"\f474"}.fa-file-medical{--fa:"\f477"}.fa-file-medical-alt,.fa-file-waveform{--fa:"\f478"}.fa-first-aid,.fa-kit-medical{--fa:"\f479"}.fa-circle-h,.fa-hospital-symbol{--fa:"\f47e"}.fa-id-card-alt,.fa-id-card-clip{--fa:"\f47f"}.fa-notes-medical{--fa:"\f481"}.fa-pallet{--fa:"\f482"}.fa-pills{--fa:"\f484"}.fa-prescription-bottle{--fa:"\f485"}.fa-prescription-bottle-alt,.fa-prescription-bottle-medical{--fa:"\f486"}.fa-bed-pulse,.fa-procedures{--fa:"\f487"}.fa-shipping-fast,.fa-truck-fast{--fa:"\f48b"}.fa-smoking{--fa:"\f48d"}.fa-syringe{--fa:"\f48e"}.fa-tablets{--fa:"\f490"}.fa-thermometer{--fa:"\f491"}.fa-vial{--fa:"\f492"}.fa-vials{--fa:"\f493"}.fa-warehouse{--fa:"\f494"}.fa-weight,.fa-weight-scale{--fa:"\f496"}.fa-x-ray{--fa:"\f497"}.fa-box-open{--fa:"\f49e"}.fa-comment-dots,.fa-commenting{--fa:"\f4ad"}.fa-comment-slash{--fa:"\f4b3"}.fa-couch{--fa:"\f4b8"}.fa-circle-dollar-to-slot,.fa-donate{--fa:"\f4b9"}.fa-dove{--fa:"\f4ba"}.fa-hand-holding{--fa:"\f4bd"}.fa-hand-holding-heart{--fa:"\f4be"}.fa-hand-holding-dollar,.fa-hand-holding-usd{--fa:"\f4c0"}.fa-hand-holding-droplet,.fa-hand-holding-water{--fa:"\f4c1"}.fa-hands-holding{--fa:"\f4c2"}.fa-hands-helping,.fa-handshake-angle{--fa:"\f4c4"}.fa-parachute-box{--fa:"\f4cd"}.fa-people-carry,.fa-people-carry-box{--fa:"\f4ce"}.fa-piggy-bank{--fa:"\f4d3"}.fa-ribbon{--fa:"\f4d6"}.fa-route{--fa:"\f4d7"}.fa-seedling,.fa-sprout{--fa:"\f4d8"}.fa-sign,.fa-sign-hanging{--fa:"\f4d9"}.fa-face-smile-wink,.fa-smile-wink{--fa:"\f4da"}.fa-tape{--fa:"\f4db"}.fa-truck-loading,.fa-truck-ramp-box{--fa:"\f4de"}.fa-truck-moving{--fa:"\f4df"}.fa-video-slash{--fa:"\f4e2"}.fa-wine-glass{--fa:"\f4e3"}.fa-user-astronaut{--fa:"\f4fb"}.fa-user-check{--fa:"\f4fc"}.fa-user-clock{--fa:"\f4fd"}.fa-user-cog,.fa-user-gear{--fa:"\f4fe"}.fa-user-edit,.fa-user-pen{--fa:"\f4ff"}.fa-user-friends,.fa-user-group{--fa:"\f500"}.fa-user-graduate{--fa:"\f501"}.fa-user-lock{--fa:"\f502"}.fa-user-minus{--fa:"\f503"}.fa-user-ninja{--fa:"\f504"}.fa-user-shield{--fa:"\f505"}.fa-user-alt-slash,.fa-user-large-slash,.fa-user-slash{--fa:"\f506"}.fa-user-tag{--fa:"\f507"}.fa-user-tie{--fa:"\f508"}.fa-users-cog,.fa-users-gear{--fa:"\f509"}.fa-balance-scale-left,.fa-scale-unbalanced{--fa:"\f515"}.fa-balance-scale-right,.fa-scale-unbalanced-flip{--fa:"\f516"}.fa-blender{--fa:"\f517"}.fa-book-open{--fa:"\f518"}.fa-broadcast-tower,.fa-tower-broadcast{--fa:"\f519"}.fa-broom{--fa:"\f51a"}.fa-blackboard,.fa-chalkboard{--fa:"\f51b"}.fa-chalkboard-teacher,.fa-chalkboard-user{--fa:"\f51c"}.fa-church{--fa:"\f51d"}.fa-coins{--fa:"\f51e"}.fa-compact-disc{--fa:"\f51f"}.fa-crow{--fa:"\f520"}.fa-crown{--fa:"\f521"}.fa-dice{--fa:"\f522"}.fa-dice-five{--fa:"\f523"}.fa-dice-four{--fa:"\f524"}.fa-dice-one{--fa:"\f525"}.fa-dice-six{--fa:"\f526"}.fa-dice-three{--fa:"\f527"}.fa-dice-two{--fa:"\f528"}.fa-divide{--fa:"\f529"}.fa-door-closed{--fa:"\f52a"}.fa-door-open{--fa:"\f52b"}.fa-feather{--fa:"\f52d"}.fa-frog{--fa:"\f52e"}.fa-gas-pump{--fa:"\f52f"}.fa-glasses{--fa:"\f530"}.fa-greater-than-equal{--fa:"\f532"}.fa-helicopter{--fa:"\f533"}.fa-infinity{--fa:"\f534"}.fa-kiwi-bird{--fa:"\f535"}.fa-less-than-equal{--fa:"\f537"}.fa-memory{--fa:"\f538"}.fa-microphone-alt-slash,.fa-microphone-lines-slash{--fa:"\f539"}.fa-money-bill-wave{--fa:"\f53a"}.fa-money-bill-1-wave,.fa-money-bill-wave-alt{--fa:"\f53b"}.fa-money-check{--fa:"\f53c"}.fa-money-check-alt,.fa-money-check-dollar{--fa:"\f53d"}.fa-not-equal{--fa:"\f53e"}.fa-palette{--fa:"\f53f"}.fa-parking,.fa-square-parking{--fa:"\f540"}.fa-diagram-project,.fa-project-diagram{--fa:"\f542"}.fa-receipt{--fa:"\f543"}.fa-robot{--fa:"\f544"}.fa-ruler{--fa:"\f545"}.fa-ruler-combined{--fa:"\f546"}.fa-ruler-horizontal{--fa:"\f547"}.fa-ruler-vertical{--fa:"\f548"}.fa-school{--fa:"\f549"}.fa-screwdriver{--fa:"\f54a"}.fa-shoe-prints{--fa:"\f54b"}.fa-skull{--fa:"\f54c"}.fa-ban-smoking,.fa-smoking-ban{--fa:"\f54d"}.fa-store{--fa:"\f54e"}.fa-shop,.fa-store-alt{--fa:"\f54f"}.fa-bars-staggered,.fa-reorder,.fa-stream{--fa:"\f550"}.fa-stroopwafel{--fa:"\f551"}.fa-toolbox{--fa:"\f552"}.fa-shirt,.fa-t-shirt,.fa-tshirt{--fa:"\f553"}.fa-person-walking,.fa-walking{--fa:"\f554"}.fa-wallet{--fa:"\f555"}.fa-angry,.fa-face-angry{--fa:"\f556"}.fa-archway{--fa:"\f557"}.fa-atlas,.fa-book-atlas{--fa:"\f558"}.fa-award{--fa:"\f559"}.fa-backspace,.fa-delete-left{--fa:"\f55a"}.fa-bezier-curve{--fa:"\f55b"}.fa-bong{--fa:"\f55c"}.fa-brush{--fa:"\f55d"}.fa-bus-alt,.fa-bus-simple{--fa:"\f55e"}.fa-cannabis{--fa:"\f55f"}.fa-check-double{--fa:"\f560"}.fa-cocktail,.fa-martini-glass-citrus{--fa:"\f561"}.fa-bell-concierge,.fa-concierge-bell{--fa:"\f562"}.fa-cookie{--fa:"\f563"}.fa-cookie-bite{--fa:"\f564"}.fa-crop-alt,.fa-crop-simple{--fa:"\f565"}.fa-digital-tachograph,.fa-tachograph-digital{--fa:"\f566"}.fa-dizzy,.fa-face-dizzy{--fa:"\f567"}.fa-compass-drafting,.fa-drafting-compass{--fa:"\f568"}.fa-drum{--fa:"\f569"}.fa-drum-steelpan{--fa:"\f56a"}.fa-feather-alt,.fa-feather-pointed{--fa:"\f56b"}.fa-file-contract{--fa:"\f56c"}.fa-file-arrow-down,.fa-file-download{--fa:"\f56d"}.fa-arrow-right-from-file,.fa-file-export{--fa:"\f56e"}.fa-arrow-right-to-file,.fa-file-import{--fa:"\f56f"}.fa-file-invoice{--fa:"\f570"}.fa-file-invoice-dollar{--fa:"\f571"}.fa-file-prescription{--fa:"\f572"}.fa-file-signature{--fa:"\f573"}.fa-file-arrow-up,.fa-file-upload{--fa:"\f574"}.fa-fill{--fa:"\f575"}.fa-fill-drip{--fa:"\f576"}.fa-fingerprint{--fa:"\f577"}.fa-fish{--fa:"\f578"}.fa-face-flushed,.fa-flushed{--fa:"\f579"}.fa-face-frown-open,.fa-frown-open{--fa:"\f57a"}.fa-glass-martini-alt,.fa-martini-glass{--fa:"\f57b"}.fa-earth-africa,.fa-globe-africa{--fa:"\f57c"}.fa-earth,.fa-earth-america,.fa-earth-americas,.fa-globe-americas{--fa:"\f57d"}.fa-earth-asia,.fa-globe-asia{--fa:"\f57e"}.fa-face-grimace,.fa-grimace{--fa:"\f57f"}.fa-face-grin,.fa-grin{--fa:"\f580"}.fa-face-grin-wide,.fa-grin-alt{--fa:"\f581"}.fa-face-grin-beam,.fa-grin-beam{--fa:"\f582"}.fa-face-grin-beam-sweat,.fa-grin-beam-sweat{--fa:"\f583"}.fa-face-grin-hearts,.fa-grin-hearts{--fa:"\f584"}.fa-face-grin-squint,.fa-grin-squint{--fa:"\f585"}.fa-face-grin-squint-tears,.fa-grin-squint-tears{--fa:"\f586"}.fa-face-grin-stars,.fa-grin-stars{--fa:"\f587"}.fa-face-grin-tears,.fa-grin-tears{--fa:"\f588"}.fa-face-grin-tongue,.fa-grin-tongue{--fa:"\f589"}.fa-face-grin-tongue-squint,.fa-grin-tongue-squint{--fa:"\f58a"}.fa-face-grin-tongue-wink,.fa-grin-tongue-wink{--fa:"\f58b"}.fa-face-grin-wink,.fa-grin-wink{--fa:"\f58c"}.fa-grid-horizontal,.fa-grip,.fa-grip-horizontal{--fa:"\f58d"}.fa-grid-vertical,.fa-grip-vertical{--fa:"\f58e"}.fa-headset{--fa:"\f590"}.fa-highlighter{--fa:"\f591"}.fa-hot-tub,.fa-hot-tub-person{--fa:"\f593"}.fa-hotel{--fa:"\f594"}.fa-joint{--fa:"\f595"}.fa-face-kiss,.fa-kiss{--fa:"\f596"}.fa-face-kiss-beam,.fa-kiss-beam{--fa:"\f597"}.fa-face-kiss-wink-heart,.fa-kiss-wink-heart{--fa:"\f598"}.fa-face-laugh,.fa-laugh{--fa:"\f599"}.fa-face-laugh-beam,.fa-laugh-beam{--fa:"\f59a"}.fa-face-laugh-squint,.fa-laugh-squint{--fa:"\f59b"}.fa-face-laugh-wink,.fa-laugh-wink{--fa:"\f59c"}.fa-cart-flatbed-suitcase,.fa-luggage-cart{--fa:"\f59d"}.fa-map-location,.fa-map-marked{--fa:"\f59f"}.fa-map-location-dot,.fa-map-marked-alt{--fa:"\f5a0"}.fa-marker{--fa:"\f5a1"}.fa-medal{--fa:"\f5a2"}.fa-face-meh-blank,.fa-meh-blank{--fa:"\f5a4"}.fa-face-rolling-eyes,.fa-meh-rolling-eyes{--fa:"\f5a5"}.fa-monument{--fa:"\f5a6"}.fa-mortar-pestle{--fa:"\f5a7"}.fa-paint-roller{--fa:"\f5aa"}.fa-passport{--fa:"\f5ab"}.fa-pen-fancy{--fa:"\f5ac"}.fa-pen-nib{--fa:"\f5ad"}.fa-pen-ruler,.fa-pencil-ruler{--fa:"\f5ae"}.fa-plane-arrival{--fa:"\f5af"}.fa-plane-departure{--fa:"\f5b0"}.fa-prescription{--fa:"\f5b1"}.fa-face-sad-cry,.fa-sad-cry{--fa:"\f5b3"}.fa-face-sad-tear,.fa-sad-tear{--fa:"\f5b4"}.fa-shuttle-van,.fa-van-shuttle{--fa:"\f5b6"}.fa-signature{--fa:"\f5b7"}.fa-face-smile-beam,.fa-smile-beam{--fa:"\f5b8"}.fa-solar-panel{--fa:"\f5ba"}.fa-spa{--fa:"\f5bb"}.fa-splotch{--fa:"\f5bc"}.fa-spray-can{--fa:"\f5bd"}.fa-stamp{--fa:"\f5bf"}.fa-star-half-alt,.fa-star-half-stroke{--fa:"\f5c0"}.fa-suitcase-rolling{--fa:"\f5c1"}.fa-face-surprise,.fa-surprise{--fa:"\f5c2"}.fa-swatchbook{--fa:"\f5c3"}.fa-person-swimming,.fa-swimmer{--fa:"\f5c4"}.fa-ladder-water,.fa-swimming-pool,.fa-water-ladder{--fa:"\f5c5"}.fa-droplet-slash,.fa-tint-slash{--fa:"\f5c7"}.fa-face-tired,.fa-tired{--fa:"\f5c8"}.fa-tooth{--fa:"\f5c9"}.fa-umbrella-beach{--fa:"\f5ca"}.fa-weight-hanging{--fa:"\f5cd"}.fa-wine-glass-alt,.fa-wine-glass-empty{--fa:"\f5ce"}.fa-air-freshener,.fa-spray-can-sparkles{--fa:"\f5d0"}.fa-apple-alt,.fa-apple-whole{--fa:"\f5d1"}.fa-atom{--fa:"\f5d2"}.fa-bone{--fa:"\f5d7"}.fa-book-open-reader,.fa-book-reader{--fa:"\f5da"}.fa-brain{--fa:"\f5dc"}.fa-car-alt,.fa-car-rear{--fa:"\f5de"}.fa-battery-car,.fa-car-battery{--fa:"\f5df"}.fa-car-burst,.fa-car-crash{--fa:"\f5e1"}.fa-car-side{--fa:"\f5e4"}.fa-charging-station{--fa:"\f5e7"}.fa-diamond-turn-right,.fa-directions{--fa:"\f5eb"}.fa-draw-polygon,.fa-vector-polygon{--fa:"\f5ee"}.fa-laptop-code{--fa:"\f5fc"}.fa-layer-group{--fa:"\f5fd"}.fa-location,.fa-location-crosshairs{--fa:"\f601"}.fa-lungs{--fa:"\f604"}.fa-microscope{--fa:"\f610"}.fa-oil-can{--fa:"\f613"}.fa-poop{--fa:"\f619"}.fa-shapes,.fa-triangle-circle-square{--fa:"\f61f"}.fa-star-of-life{--fa:"\f621"}.fa-dashboard,.fa-gauge,.fa-gauge-med,.fa-tachometer-alt-average{--fa:"\f624"}.fa-gauge-high,.fa-tachometer-alt,.fa-tachometer-alt-fast{--fa:"\f625"}.fa-gauge-simple,.fa-gauge-simple-med,.fa-tachometer-average{--fa:"\f629"}.fa-gauge-simple-high,.fa-tachometer,.fa-tachometer-fast{--fa:"\f62a"}.fa-teeth{--fa:"\f62e"}.fa-teeth-open{--fa:"\f62f"}.fa-masks-theater,.fa-theater-masks{--fa:"\f630"}.fa-traffic-light{--fa:"\f637"}.fa-truck-monster{--fa:"\f63b"}.fa-truck-pickup{--fa:"\f63c"}.fa-ad,.fa-rectangle-ad{--fa:"\f641"}.fa-ankh{--fa:"\f644"}.fa-bible,.fa-book-bible{--fa:"\f647"}.fa-briefcase-clock,.fa-business-time{--fa:"\f64a"}.fa-city{--fa:"\f64f"}.fa-comment-dollar{--fa:"\f651"}.fa-comments-dollar{--fa:"\f653"}.fa-cross{--fa:"\f654"}.fa-dharmachakra{--fa:"\f655"}.fa-envelope-open-text{--fa:"\f658"}.fa-folder-minus{--fa:"\f65d"}.fa-folder-plus{--fa:"\f65e"}.fa-filter-circle-dollar,.fa-funnel-dollar{--fa:"\f662"}.fa-gopuram{--fa:"\f664"}.fa-hamsa{--fa:"\f665"}.fa-bahai,.fa-haykal{--fa:"\f666"}.fa-jedi{--fa:"\f669"}.fa-book-journal-whills,.fa-journal-whills{--fa:"\f66a"}.fa-kaaba{--fa:"\f66b"}.fa-khanda{--fa:"\f66d"}.fa-landmark{--fa:"\f66f"}.fa-envelopes-bulk,.fa-mail-bulk{--fa:"\f674"}.fa-menorah{--fa:"\f676"}.fa-mosque{--fa:"\f678"}.fa-om{--fa:"\f679"}.fa-pastafarianism,.fa-spaghetti-monster-flying{--fa:"\f67b"}.fa-peace{--fa:"\f67c"}.fa-place-of-worship{--fa:"\f67f"}.fa-poll,.fa-square-poll-vertical{--fa:"\f681"}.fa-poll-h,.fa-square-poll-horizontal{--fa:"\f682"}.fa-person-praying,.fa-pray{--fa:"\f683"}.fa-hands-praying,.fa-praying-hands{--fa:"\f684"}.fa-book-quran,.fa-quran{--fa:"\f687"}.fa-magnifying-glass-dollar,.fa-search-dollar{--fa:"\f688"}.fa-magnifying-glass-location,.fa-search-location{--fa:"\f689"}.fa-socks{--fa:"\f696"}.fa-square-root-alt,.fa-square-root-variable{--fa:"\f698"}.fa-star-and-crescent{--fa:"\f699"}.fa-star-of-david{--fa:"\f69a"}.fa-synagogue{--fa:"\f69b"}.fa-scroll-torah,.fa-torah{--fa:"\f6a0"}.fa-torii-gate{--fa:"\f6a1"}.fa-vihara{--fa:"\f6a7"}.fa-volume-mute,.fa-volume-times,.fa-volume-xmark{--fa:"\f6a9"}.fa-yin-yang{--fa:"\f6ad"}.fa-blender-phone{--fa:"\f6b6"}.fa-book-dead,.fa-book-skull{--fa:"\f6b7"}.fa-campground{--fa:"\f6bb"}.fa-cat{--fa:"\f6be"}.fa-chair{--fa:"\f6c0"}.fa-cloud-moon{--fa:"\f6c3"}.fa-cloud-sun{--fa:"\f6c4"}.fa-cow{--fa:"\f6c8"}.fa-dice-d20{--fa:"\f6cf"}.fa-dice-d6{--fa:"\f6d1"}.fa-dog{--fa:"\f6d3"}.fa-dragon{--fa:"\f6d5"}.fa-drumstick-bite{--fa:"\f6d7"}.fa-dungeon{--fa:"\f6d9"}.fa-file-csv{--fa:"\f6dd"}.fa-fist-raised,.fa-hand-fist{--fa:"\f6de"}.fa-ghost{--fa:"\f6e2"}.fa-hammer{--fa:"\f6e3"}.fa-hanukiah{--fa:"\f6e6"}.fa-hat-wizard{--fa:"\f6e8"}.fa-hiking,.fa-person-hiking{--fa:"\f6ec"}.fa-hippo{--fa:"\f6ed"}.fa-horse{--fa:"\f6f0"}.fa-house-chimney-crack,.fa-house-damage{--fa:"\f6f1"}.fa-hryvnia,.fa-hryvnia-sign{--fa:"\f6f2"}.fa-mask{--fa:"\f6fa"}.fa-mountain{--fa:"\f6fc"}.fa-network-wired{--fa:"\f6ff"}.fa-otter{--fa:"\f700"}.fa-ring{--fa:"\f70b"}.fa-person-running,.fa-running{--fa:"\f70c"}.fa-scroll{--fa:"\f70e"}.fa-skull-crossbones{--fa:"\f714"}.fa-slash{--fa:"\f715"}.fa-spider{--fa:"\f717"}.fa-toilet-paper,.fa-toilet-paper-alt,.fa-toilet-paper-blank{--fa:"\f71e"}.fa-tractor{--fa:"\f722"}.fa-user-injured{--fa:"\f728"}.fa-vr-cardboard{--fa:"\f729"}.fa-wand-sparkles{--fa:"\f72b"}.fa-wind{--fa:"\f72e"}.fa-wine-bottle{--fa:"\f72f"}.fa-cloud-meatball{--fa:"\f73b"}.fa-cloud-moon-rain{--fa:"\f73c"}.fa-cloud-rain{--fa:"\f73d"}.fa-cloud-showers-heavy{--fa:"\f740"}.fa-cloud-sun-rain{--fa:"\f743"}.fa-democrat{--fa:"\f747"}.fa-flag-usa{--fa:"\f74d"}.fa-hurricane{--fa:"\f751"}.fa-landmark-alt,.fa-landmark-dome{--fa:"\f752"}.fa-meteor{--fa:"\f753"}.fa-person-booth{--fa:"\f756"}.fa-poo-bolt,.fa-poo-storm{--fa:"\f75a"}.fa-rainbow{--fa:"\f75b"}.fa-republican{--fa:"\f75e"}.fa-smog{--fa:"\f75f"}.fa-temperature-high{--fa:"\f769"}.fa-temperature-low{--fa:"\f76b"}.fa-cloud-bolt,.fa-thunderstorm{--fa:"\f76c"}.fa-tornado{--fa:"\f76f"}.fa-volcano{--fa:"\f770"}.fa-check-to-slot,.fa-vote-yea{--fa:"\f772"}.fa-water{--fa:"\f773"}.fa-baby{--fa:"\f77c"}.fa-baby-carriage,.fa-carriage-baby{--fa:"\f77d"}.fa-biohazard{--fa:"\f780"}.fa-blog{--fa:"\f781"}.fa-calendar-day{--fa:"\f783"}.fa-calendar-week{--fa:"\f784"}.fa-candy-cane{--fa:"\f786"}.fa-carrot{--fa:"\f787"}.fa-cash-register{--fa:"\f788"}.fa-compress-arrows-alt,.fa-minimize{--fa:"\f78c"}.fa-dumpster{--fa:"\f793"}.fa-dumpster-fire{--fa:"\f794"}.fa-ethernet{--fa:"\f796"}.fa-gifts{--fa:"\f79c"}.fa-champagne-glasses,.fa-glass-cheers{--fa:"\f79f"}.fa-glass-whiskey,.fa-whiskey-glass{--fa:"\f7a0"}.fa-earth-europe,.fa-globe-europe{--fa:"\f7a2"}.fa-grip-lines{--fa:"\f7a4"}.fa-grip-lines-vertical{--fa:"\f7a5"}.fa-guitar{--fa:"\f7a6"}.fa-heart-broken,.fa-heart-crack{--fa:"\f7a9"}.fa-holly-berry{--fa:"\f7aa"}.fa-horse-head{--fa:"\f7ab"}.fa-icicles{--fa:"\f7ad"}.fa-igloo{--fa:"\f7ae"}.fa-mitten{--fa:"\f7b5"}.fa-mug-hot{--fa:"\f7b6"}.fa-radiation{--fa:"\f7b9"}.fa-circle-radiation,.fa-radiation-alt{--fa:"\f7ba"}.fa-restroom{--fa:"\f7bd"}.fa-satellite{--fa:"\f7bf"}.fa-satellite-dish{--fa:"\f7c0"}.fa-sd-card{--fa:"\f7c2"}.fa-sim-card{--fa:"\f7c4"}.fa-person-skating,.fa-skating{--fa:"\f7c5"}.fa-person-skiing,.fa-skiing{--fa:"\f7c9"}.fa-person-skiing-nordic,.fa-skiing-nordic{--fa:"\f7ca"}.fa-sleigh{--fa:"\f7cc"}.fa-comment-sms,.fa-sms{--fa:"\f7cd"}.fa-person-snowboarding,.fa-snowboarding{--fa:"\f7ce"}.fa-snowman{--fa:"\f7d0"}.fa-snowplow{--fa:"\f7d2"}.fa-tenge,.fa-tenge-sign{--fa:"\f7d7"}.fa-toilet{--fa:"\f7d8"}.fa-screwdriver-wrench,.fa-tools{--fa:"\f7d9"}.fa-cable-car,.fa-tram{--fa:"\f7da"}.fa-fire-alt,.fa-fire-flame-curved{--fa:"\f7e4"}.fa-bacon{--fa:"\f7e5"}.fa-book-medical{--fa:"\f7e6"}.fa-bread-slice{--fa:"\f7ec"}.fa-cheese{--fa:"\f7ef"}.fa-clinic-medical,.fa-house-chimney-medical{--fa:"\f7f2"}.fa-clipboard-user{--fa:"\f7f3"}.fa-comment-medical{--fa:"\f7f5"}.fa-crutch{--fa:"\f7f7"}.fa-disease{--fa:"\f7fa"}.fa-egg{--fa:"\f7fb"}.fa-folder-tree{--fa:"\f802"}.fa-burger,.fa-hamburger{--fa:"\f805"}.fa-hand-middle-finger{--fa:"\f806"}.fa-hard-hat,.fa-hat-hard,.fa-helmet-safety{--fa:"\f807"}.fa-hospital-user{--fa:"\f80d"}.fa-hotdog{--fa:"\f80f"}.fa-ice-cream{--fa:"\f810"}.fa-laptop-medical{--fa:"\f812"}.fa-pager{--fa:"\f815"}.fa-pepper-hot{--fa:"\f816"}.fa-pizza-slice{--fa:"\f818"}.fa-sack-dollar{--fa:"\f81d"}.fa-book-tanakh,.fa-tanakh{--fa:"\f827"}.fa-bars-progress,.fa-tasks-alt{--fa:"\f828"}.fa-trash-arrow-up,.fa-trash-restore{--fa:"\f829"}.fa-trash-can-arrow-up,.fa-trash-restore-alt{--fa:"\f82a"}.fa-user-nurse{--fa:"\f82f"}.fa-wave-square{--fa:"\f83e"}.fa-biking,.fa-person-biking{--fa:"\f84a"}.fa-border-all{--fa:"\f84c"}.fa-border-none{--fa:"\f850"}.fa-border-style,.fa-border-top-left{--fa:"\f853"}.fa-digging,.fa-person-digging{--fa:"\f85e"}.fa-fan{--fa:"\f863"}.fa-heart-music-camera-bolt,.fa-icons{--fa:"\f86d"}.fa-phone-alt,.fa-phone-flip{--fa:"\f879"}.fa-phone-square-alt,.fa-square-phone-flip{--fa:"\f87b"}.fa-photo-film,.fa-photo-video{--fa:"\f87c"}.fa-remove-format,.fa-text-slash{--fa:"\f87d"}.fa-arrow-down-z-a,.fa-sort-alpha-desc,.fa-sort-alpha-down-alt{--fa:"\f881"}.fa-arrow-up-z-a,.fa-sort-alpha-up-alt{--fa:"\f882"}.fa-arrow-down-short-wide,.fa-sort-amount-desc,.fa-sort-amount-down-alt{--fa:"\f884"}.fa-arrow-up-short-wide,.fa-sort-amount-up-alt{--fa:"\f885"}.fa-arrow-down-9-1,.fa-sort-numeric-desc,.fa-sort-numeric-down-alt{--fa:"\f886"}.fa-arrow-up-9-1,.fa-sort-numeric-up-alt{--fa:"\f887"}.fa-spell-check{--fa:"\f891"}.fa-voicemail{--fa:"\f897"}.fa-hat-cowboy{--fa:"\f8c0"}.fa-hat-cowboy-side{--fa:"\f8c1"}.fa-computer-mouse,.fa-mouse{--fa:"\f8cc"}.fa-radio{--fa:"\f8d7"}.fa-record-vinyl{--fa:"\f8d9"}.fa-walkie-talkie{--fa:"\f8ef"}.fa-caravan{--fa:"\f8ff"} :host,:root{--fa-family-brands:"Font Awesome 7 Brands";--fa-font-brands:normal 400 1em/1 var(--fa-family-brands)}@font-face{font-family:"Font Awesome 7 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.woff2)}.fa-brands,.fa-classic.fa-brands,.fab{--fa-family:var(--fa-family-brands);--fa-style:400}.fa-firefox-browser{--fa:"\e007"}.fa-ideal{--fa:"\e013"}.fa-microblog{--fa:"\e01a"}.fa-pied-piper-square,.fa-square-pied-piper{--fa:"\e01e"}.fa-unity{--fa:"\e049"}.fa-dailymotion{--fa:"\e052"}.fa-instagram-square,.fa-square-instagram{--fa:"\e055"}.fa-mixer{--fa:"\e056"}.fa-shopify{--fa:"\e057"}.fa-deezer{--fa:"\e077"}.fa-edge-legacy{--fa:"\e078"}.fa-google-pay{--fa:"\e079"}.fa-rust{--fa:"\e07a"}.fa-tiktok{--fa:"\e07b"}.fa-unsplash{--fa:"\e07c"}.fa-cloudflare{--fa:"\e07d"}.fa-guilded{--fa:"\e07e"}.fa-hive{--fa:"\e07f"}.fa-42-group,.fa-innosoft{--fa:"\e080"}.fa-instalod{--fa:"\e081"}.fa-octopus-deploy{--fa:"\e082"}.fa-perbyte{--fa:"\e083"}.fa-uncharted{--fa:"\e084"}.fa-watchman-monitoring{--fa:"\e087"}.fa-wodu{--fa:"\e088"}.fa-wirsindhandwerk,.fa-wsh{--fa:"\e2d0"}.fa-bots{--fa:"\e340"}.fa-cmplid{--fa:"\e360"}.fa-bilibili{--fa:"\e3d9"}.fa-golang{--fa:"\e40f"}.fa-pix{--fa:"\e43a"}.fa-sitrox{--fa:"\e44a"}.fa-hashnode{--fa:"\e499"}.fa-meta{--fa:"\e49b"}.fa-padlet{--fa:"\e4a0"}.fa-nfc-directional{--fa:"\e530"}.fa-nfc-symbol{--fa:"\e531"}.fa-screenpal{--fa:"\e570"}.fa-space-awesome{--fa:"\e5ac"}.fa-square-font-awesome{--fa:"\e5ad"}.fa-gitlab-square,.fa-square-gitlab{--fa:"\e5ae"}.fa-odysee{--fa:"\e5c6"}.fa-stubber{--fa:"\e5c7"}.fa-debian{--fa:"\e60b"}.fa-shoelace{--fa:"\e60c"}.fa-threads{--fa:"\e618"}.fa-square-threads{--fa:"\e619"}.fa-square-x-twitter{--fa:"\e61a"}.fa-x-twitter{--fa:"\e61b"}.fa-opensuse{--fa:"\e62b"}.fa-letterboxd{--fa:"\e62d"}.fa-square-letterboxd{--fa:"\e62e"}.fa-mintbit{--fa:"\e62f"}.fa-google-scholar{--fa:"\e63b"}.fa-brave{--fa:"\e63c"}.fa-brave-reverse{--fa:"\e63d"}.fa-pixiv{--fa:"\e640"}.fa-upwork{--fa:"\e641"}.fa-webflow{--fa:"\e65c"}.fa-signal-messenger{--fa:"\e663"}.fa-bluesky{--fa:"\e671"}.fa-jxl{--fa:"\e67b"}.fa-square-upwork{--fa:"\e67c"}.fa-web-awesome{--fa:"\e682"}.fa-square-web-awesome{--fa:"\e683"}.fa-square-web-awesome-stroke{--fa:"\e684"}.fa-dart-lang{--fa:"\e693"}.fa-flutter{--fa:"\e694"}.fa-files-pinwheel{--fa:"\e69f"}.fa-css{--fa:"\e6a2"}.fa-square-bluesky{--fa:"\e6a3"}.fa-openai{--fa:"\e7cf"}.fa-square-linkedin{--fa:"\e7d0"}.fa-cash-app{--fa:"\e7d4"}.fa-disqus{--fa:"\e7d5"}.fa-11ty,.fa-eleventy{--fa:"\e7d6"}.fa-kakao-talk{--fa:"\e7d7"}.fa-linktree{--fa:"\e7d8"}.fa-notion{--fa:"\e7d9"}.fa-pandora{--fa:"\e7da"}.fa-pixelfed{--fa:"\e7db"}.fa-tidal{--fa:"\e7dc"}.fa-vsco{--fa:"\e7dd"}.fa-w3c{--fa:"\e7de"}.fa-lumon{--fa:"\e7e2"}.fa-lumon-drop{--fa:"\e7e3"}.fa-square-figma{--fa:"\e7e4"}.fa-tex{--fa:"\e7ff"}.fa-duolingo{--fa:"\e812"}.fa-square-twitter,.fa-twitter-square{--fa:"\f081"}.fa-facebook-square,.fa-square-facebook{--fa:"\f082"}.fa-linkedin{--fa:"\f08c"}.fa-github-square,.fa-square-github{--fa:"\f092"}.fa-twitter{--fa:"\f099"}.fa-facebook{--fa:"\f09a"}.fa-github{--fa:"\f09b"}.fa-pinterest{--fa:"\f0d2"}.fa-pinterest-square,.fa-square-pinterest{--fa:"\f0d3"}.fa-google-plus-square,.fa-square-google-plus{--fa:"\f0d4"}.fa-google-plus-g{--fa:"\f0d5"}.fa-linkedin-in{--fa:"\f0e1"}.fa-github-alt{--fa:"\f113"}.fa-maxcdn{--fa:"\f136"}.fa-html5{--fa:"\f13b"}.fa-css3{--fa:"\f13c"}.fa-btc{--fa:"\f15a"}.fa-youtube{--fa:"\f167"}.fa-xing{--fa:"\f168"}.fa-square-xing,.fa-xing-square{--fa:"\f169"}.fa-dropbox{--fa:"\f16b"}.fa-stack-overflow{--fa:"\f16c"}.fa-instagram{--fa:"\f16d"}.fa-flickr{--fa:"\f16e"}.fa-adn{--fa:"\f170"}.fa-bitbucket{--fa:"\f171"}.fa-tumblr{--fa:"\f173"}.fa-square-tumblr,.fa-tumblr-square{--fa:"\f174"}.fa-apple{--fa:"\f179"}.fa-windows{--fa:"\f17a"}.fa-android{--fa:"\f17b"}.fa-linux{--fa:"\f17c"}.fa-dribbble{--fa:"\f17d"}.fa-skype{--fa:"\f17e"}.fa-foursquare{--fa:"\f180"}.fa-trello{--fa:"\f181"}.fa-gratipay{--fa:"\f184"}.fa-vk{--fa:"\f189"}.fa-weibo{--fa:"\f18a"}.fa-renren{--fa:"\f18b"}.fa-pagelines{--fa:"\f18c"}.fa-stack-exchange{--fa:"\f18d"}.fa-square-vimeo,.fa-vimeo-square{--fa:"\f194"}.fa-slack,.fa-slack-hash{--fa:"\f198"}.fa-wordpress{--fa:"\f19a"}.fa-openid{--fa:"\f19b"}.fa-yahoo{--fa:"\f19e"}.fa-google{--fa:"\f1a0"}.fa-reddit{--fa:"\f1a1"}.fa-reddit-square,.fa-square-reddit{--fa:"\f1a2"}.fa-stumbleupon-circle{--fa:"\f1a3"}.fa-stumbleupon{--fa:"\f1a4"}.fa-delicious{--fa:"\f1a5"}.fa-digg{--fa:"\f1a6"}.fa-pied-piper-pp{--fa:"\f1a7"}.fa-pied-piper-alt{--fa:"\f1a8"}.fa-drupal{--fa:"\f1a9"}.fa-joomla{--fa:"\f1aa"}.fa-behance{--fa:"\f1b4"}.fa-behance-square,.fa-square-behance{--fa:"\f1b5"}.fa-steam{--fa:"\f1b6"}.fa-square-steam,.fa-steam-square{--fa:"\f1b7"}.fa-spotify{--fa:"\f1bc"}.fa-deviantart{--fa:"\f1bd"}.fa-soundcloud{--fa:"\f1be"}.fa-vine{--fa:"\f1ca"}.fa-codepen{--fa:"\f1cb"}.fa-jsfiddle{--fa:"\f1cc"}.fa-rebel{--fa:"\f1d0"}.fa-empire{--fa:"\f1d1"}.fa-git-square,.fa-square-git{--fa:"\f1d2"}.fa-git{--fa:"\f1d3"}.fa-hacker-news{--fa:"\f1d4"}.fa-tencent-weibo{--fa:"\f1d5"}.fa-qq{--fa:"\f1d6"}.fa-weixin{--fa:"\f1d7"}.fa-slideshare{--fa:"\f1e7"}.fa-twitch{--fa:"\f1e8"}.fa-yelp{--fa:"\f1e9"}.fa-paypal{--fa:"\f1ed"}.fa-google-wallet{--fa:"\f1ee"}.fa-cc-visa{--fa:"\f1f0"}.fa-cc-mastercard{--fa:"\f1f1"}.fa-cc-discover{--fa:"\f1f2"}.fa-cc-amex{--fa:"\f1f3"}.fa-cc-paypal{--fa:"\f1f4"}.fa-cc-stripe{--fa:"\f1f5"}.fa-lastfm{--fa:"\f202"}.fa-lastfm-square,.fa-square-lastfm{--fa:"\f203"}.fa-ioxhost{--fa:"\f208"}.fa-angellist{--fa:"\f209"}.fa-buysellads{--fa:"\f20d"}.fa-connectdevelop{--fa:"\f20e"}.fa-dashcube{--fa:"\f210"}.fa-forumbee{--fa:"\f211"}.fa-leanpub{--fa:"\f212"}.fa-sellsy{--fa:"\f213"}.fa-shirtsinbulk{--fa:"\f214"}.fa-simplybuilt{--fa:"\f215"}.fa-skyatlas{--fa:"\f216"}.fa-pinterest-p{--fa:"\f231"}.fa-whatsapp{--fa:"\f232"}.fa-viacoin{--fa:"\f237"}.fa-medium,.fa-medium-m{--fa:"\f23a"}.fa-y-combinator{--fa:"\f23b"}.fa-optin-monster{--fa:"\f23c"}.fa-opencart{--fa:"\f23d"}.fa-expeditedssl{--fa:"\f23e"}.fa-cc-jcb{--fa:"\f24b"}.fa-cc-diners-club{--fa:"\f24c"}.fa-creative-commons{--fa:"\f25e"}.fa-gg{--fa:"\f260"}.fa-gg-circle{--fa:"\f261"}.fa-odnoklassniki{--fa:"\f263"}.fa-odnoklassniki-square,.fa-square-odnoklassniki{--fa:"\f264"}.fa-get-pocket{--fa:"\f265"}.fa-wikipedia-w{--fa:"\f266"}.fa-safari{--fa:"\f267"}.fa-chrome{--fa:"\f268"}.fa-firefox{--fa:"\f269"}.fa-opera{--fa:"\f26a"}.fa-internet-explorer{--fa:"\f26b"}.fa-contao{--fa:"\f26d"}.fa-500px{--fa:"\f26e"}.fa-amazon{--fa:"\f270"}.fa-houzz{--fa:"\f27c"}.fa-vimeo-v{--fa:"\f27d"}.fa-black-tie{--fa:"\f27e"}.fa-fonticons{--fa:"\f280"}.fa-reddit-alien{--fa:"\f281"}.fa-edge{--fa:"\f282"}.fa-codiepie{--fa:"\f284"}.fa-modx{--fa:"\f285"}.fa-fort-awesome{--fa:"\f286"}.fa-usb{--fa:"\f287"}.fa-product-hunt{--fa:"\f288"}.fa-mixcloud{--fa:"\f289"}.fa-scribd{--fa:"\f28a"}.fa-bluetooth{--fa:"\f293"}.fa-bluetooth-b{--fa:"\f294"}.fa-gitlab{--fa:"\f296"}.fa-wpbeginner{--fa:"\f297"}.fa-wpforms{--fa:"\f298"}.fa-envira{--fa:"\f299"}.fa-glide{--fa:"\f2a5"}.fa-glide-g{--fa:"\f2a6"}.fa-viadeo{--fa:"\f2a9"}.fa-square-viadeo,.fa-viadeo-square{--fa:"\f2aa"}.fa-snapchat,.fa-snapchat-ghost{--fa:"\f2ab"}.fa-snapchat-square,.fa-square-snapchat{--fa:"\f2ad"}.fa-pied-piper{--fa:"\f2ae"}.fa-first-order{--fa:"\f2b0"}.fa-yoast{--fa:"\f2b1"}.fa-themeisle{--fa:"\f2b2"}.fa-google-plus{--fa:"\f2b3"}.fa-font-awesome,.fa-font-awesome-flag,.fa-font-awesome-logo-full{--fa:"\f2b4"}.fa-linode{--fa:"\f2b8"}.fa-quora{--fa:"\f2c4"}.fa-free-code-camp{--fa:"\f2c5"}.fa-telegram,.fa-telegram-plane{--fa:"\f2c6"}.fa-bandcamp{--fa:"\f2d5"}.fa-grav{--fa:"\f2d6"}.fa-etsy{--fa:"\f2d7"}.fa-imdb{--fa:"\f2d8"}.fa-ravelry{--fa:"\f2d9"}.fa-sellcast{--fa:"\f2da"}.fa-superpowers{--fa:"\f2dd"}.fa-wpexplorer{--fa:"\f2de"}.fa-meetup{--fa:"\f2e0"}.fa-font-awesome-alt,.fa-square-font-awesome-stroke{--fa:"\f35c"}.fa-accessible-icon{--fa:"\f368"}.fa-accusoft{--fa:"\f369"}.fa-adversal{--fa:"\f36a"}.fa-affiliatetheme{--fa:"\f36b"}.fa-algolia{--fa:"\f36c"}.fa-amilia{--fa:"\f36d"}.fa-angrycreative{--fa:"\f36e"}.fa-app-store{--fa:"\f36f"}.fa-app-store-ios{--fa:"\f370"}.fa-apper{--fa:"\f371"}.fa-asymmetrik{--fa:"\f372"}.fa-audible{--fa:"\f373"}.fa-avianex{--fa:"\f374"}.fa-aws{--fa:"\f375"}.fa-bimobject{--fa:"\f378"}.fa-bitcoin{--fa:"\f379"}.fa-bity{--fa:"\f37a"}.fa-blackberry{--fa:"\f37b"}.fa-blogger{--fa:"\f37c"}.fa-blogger-b{--fa:"\f37d"}.fa-buromobelexperte{--fa:"\f37f"}.fa-centercode{--fa:"\f380"}.fa-cloudscale{--fa:"\f383"}.fa-cloudsmith{--fa:"\f384"}.fa-cloudversify{--fa:"\f385"}.fa-cpanel{--fa:"\f388"}.fa-css3-alt{--fa:"\f38b"}.fa-cuttlefish{--fa:"\f38c"}.fa-d-and-d{--fa:"\f38d"}.fa-deploydog{--fa:"\f38e"}.fa-deskpro{--fa:"\f38f"}.fa-digital-ocean{--fa:"\f391"}.fa-discord{--fa:"\f392"}.fa-discourse{--fa:"\f393"}.fa-dochub{--fa:"\f394"}.fa-docker{--fa:"\f395"}.fa-draft2digital{--fa:"\f396"}.fa-dribbble-square,.fa-square-dribbble{--fa:"\f397"}.fa-dyalog{--fa:"\f399"}.fa-earlybirds{--fa:"\f39a"}.fa-erlang{--fa:"\f39d"}.fa-facebook-f{--fa:"\f39e"}.fa-facebook-messenger{--fa:"\f39f"}.fa-firstdraft{--fa:"\f3a1"}.fa-fonticons-fi{--fa:"\f3a2"}.fa-fort-awesome-alt{--fa:"\f3a3"}.fa-freebsd{--fa:"\f3a4"}.fa-gitkraken{--fa:"\f3a6"}.fa-gofore{--fa:"\f3a7"}.fa-goodreads{--fa:"\f3a8"}.fa-goodreads-g{--fa:"\f3a9"}.fa-google-drive{--fa:"\f3aa"}.fa-google-play{--fa:"\f3ab"}.fa-gripfire{--fa:"\f3ac"}.fa-grunt{--fa:"\f3ad"}.fa-gulp{--fa:"\f3ae"}.fa-hacker-news-square,.fa-square-hacker-news{--fa:"\f3af"}.fa-hire-a-helper{--fa:"\f3b0"}.fa-hotjar{--fa:"\f3b1"}.fa-hubspot{--fa:"\f3b2"}.fa-itunes{--fa:"\f3b4"}.fa-itunes-note{--fa:"\f3b5"}.fa-jenkins{--fa:"\f3b6"}.fa-joget{--fa:"\f3b7"}.fa-js{--fa:"\f3b8"}.fa-js-square,.fa-square-js{--fa:"\f3b9"}.fa-keycdn{--fa:"\f3ba"}.fa-kickstarter,.fa-square-kickstarter{--fa:"\f3bb"}.fa-kickstarter-k{--fa:"\f3bc"}.fa-laravel{--fa:"\f3bd"}.fa-line{--fa:"\f3c0"}.fa-lyft{--fa:"\f3c3"}.fa-magento{--fa:"\f3c4"}.fa-medapps{--fa:"\f3c6"}.fa-medrt{--fa:"\f3c8"}.fa-microsoft{--fa:"\f3ca"}.fa-mix{--fa:"\f3cb"}.fa-mizuni{--fa:"\f3cc"}.fa-monero{--fa:"\f3d0"}.fa-napster{--fa:"\f3d2"}.fa-node-js{--fa:"\f3d3"}.fa-npm{--fa:"\f3d4"}.fa-ns8{--fa:"\f3d5"}.fa-nutritionix{--fa:"\f3d6"}.fa-page4{--fa:"\f3d7"}.fa-palfed{--fa:"\f3d8"}.fa-patreon{--fa:"\f3d9"}.fa-periscope{--fa:"\f3da"}.fa-phabricator{--fa:"\f3db"}.fa-phoenix-framework{--fa:"\f3dc"}.fa-playstation{--fa:"\f3df"}.fa-pushed{--fa:"\f3e1"}.fa-python{--fa:"\f3e2"}.fa-red-river{--fa:"\f3e3"}.fa-rendact,.fa-wpressr{--fa:"\f3e4"}.fa-replyd{--fa:"\f3e6"}.fa-resolving{--fa:"\f3e7"}.fa-rocketchat{--fa:"\f3e8"}.fa-rockrms{--fa:"\f3e9"}.fa-schlix{--fa:"\f3ea"}.fa-searchengin{--fa:"\f3eb"}.fa-servicestack{--fa:"\f3ec"}.fa-sistrix{--fa:"\f3ee"}.fa-speakap{--fa:"\f3f3"}.fa-staylinked{--fa:"\f3f5"}.fa-steam-symbol{--fa:"\f3f6"}.fa-sticker-mule{--fa:"\f3f7"}.fa-studiovinari{--fa:"\f3f8"}.fa-supple{--fa:"\f3f9"}.fa-uber{--fa:"\f402"}.fa-uikit{--fa:"\f403"}.fa-uniregistry{--fa:"\f404"}.fa-untappd{--fa:"\f405"}.fa-ussunnah{--fa:"\f407"}.fa-vaadin{--fa:"\f408"}.fa-viber{--fa:"\f409"}.fa-vimeo{--fa:"\f40a"}.fa-vnv{--fa:"\f40b"}.fa-square-whatsapp,.fa-whatsapp-square{--fa:"\f40c"}.fa-whmcs{--fa:"\f40d"}.fa-wordpress-simple{--fa:"\f411"}.fa-xbox{--fa:"\f412"}.fa-yandex{--fa:"\f413"}.fa-yandex-international{--fa:"\f414"}.fa-apple-pay{--fa:"\f415"}.fa-cc-apple-pay{--fa:"\f416"}.fa-fly{--fa:"\f417"}.fa-node{--fa:"\f419"}.fa-osi{--fa:"\f41a"}.fa-react{--fa:"\f41b"}.fa-autoprefixer{--fa:"\f41c"}.fa-less{--fa:"\f41d"}.fa-sass{--fa:"\f41e"}.fa-vuejs{--fa:"\f41f"}.fa-angular{--fa:"\f420"}.fa-aviato{--fa:"\f421"}.fa-ember{--fa:"\f423"}.fa-gitter{--fa:"\f426"}.fa-hooli{--fa:"\f427"}.fa-strava{--fa:"\f428"}.fa-stripe{--fa:"\f429"}.fa-stripe-s{--fa:"\f42a"}.fa-typo3{--fa:"\f42b"}.fa-amazon-pay{--fa:"\f42c"}.fa-cc-amazon-pay{--fa:"\f42d"}.fa-ethereum{--fa:"\f42e"}.fa-korvue{--fa:"\f42f"}.fa-elementor{--fa:"\f430"}.fa-square-youtube,.fa-youtube-square{--fa:"\f431"}.fa-flipboard{--fa:"\f44d"}.fa-hips{--fa:"\f452"}.fa-php{--fa:"\f457"}.fa-quinscape{--fa:"\f459"}.fa-readme{--fa:"\f4d5"}.fa-java{--fa:"\f4e4"}.fa-pied-piper-hat{--fa:"\f4e5"}.fa-creative-commons-by{--fa:"\f4e7"}.fa-creative-commons-nc{--fa:"\f4e8"}.fa-creative-commons-nc-eu{--fa:"\f4e9"}.fa-creative-commons-nc-jp{--fa:"\f4ea"}.fa-creative-commons-nd{--fa:"\f4eb"}.fa-creative-commons-pd{--fa:"\f4ec"}.fa-creative-commons-pd-alt{--fa:"\f4ed"}.fa-creative-commons-remix{--fa:"\f4ee"}.fa-creative-commons-sa{--fa:"\f4ef"}.fa-creative-commons-sampling{--fa:"\f4f0"}.fa-creative-commons-sampling-plus{--fa:"\f4f1"}.fa-creative-commons-share{--fa:"\f4f2"}.fa-creative-commons-zero{--fa:"\f4f3"}.fa-ebay{--fa:"\f4f4"}.fa-keybase{--fa:"\f4f5"}.fa-mastodon{--fa:"\f4f6"}.fa-r-project{--fa:"\f4f7"}.fa-researchgate{--fa:"\f4f8"}.fa-teamspeak{--fa:"\f4f9"}.fa-first-order-alt{--fa:"\f50a"}.fa-fulcrum{--fa:"\f50b"}.fa-galactic-republic{--fa:"\f50c"}.fa-galactic-senate{--fa:"\f50d"}.fa-jedi-order{--fa:"\f50e"}.fa-mandalorian{--fa:"\f50f"}.fa-old-republic{--fa:"\f510"}.fa-phoenix-squadron{--fa:"\f511"}.fa-sith{--fa:"\f512"}.fa-trade-federation{--fa:"\f513"}.fa-wolf-pack-battalion{--fa:"\f514"}.fa-hornbill{--fa:"\f592"}.fa-mailchimp{--fa:"\f59e"}.fa-megaport{--fa:"\f5a3"}.fa-nimblr{--fa:"\f5a8"}.fa-rev{--fa:"\f5b2"}.fa-shopware{--fa:"\f5b5"}.fa-squarespace{--fa:"\f5be"}.fa-themeco{--fa:"\f5c6"}.fa-weebly{--fa:"\f5cc"}.fa-wix{--fa:"\f5cf"}.fa-ello{--fa:"\f5f1"}.fa-hackerrank{--fa:"\f5f7"}.fa-kaggle{--fa:"\f5fa"}.fa-markdown{--fa:"\f60f"}.fa-neos{--fa:"\f612"}.fa-zhihu{--fa:"\f63f"}.fa-alipay{--fa:"\f642"}.fa-the-red-yeti{--fa:"\f69d"}.fa-critical-role{--fa:"\f6c9"}.fa-d-and-d-beyond{--fa:"\f6ca"}.fa-dev{--fa:"\f6cc"}.fa-fantasy-flight-games{--fa:"\f6dc"}.fa-wizards-of-the-coast{--fa:"\f730"}.fa-think-peaks{--fa:"\f731"}.fa-reacteurope{--fa:"\f75d"}.fa-artstation{--fa:"\f77a"}.fa-atlassian{--fa:"\f77b"}.fa-canadian-maple-leaf{--fa:"\f785"}.fa-centos{--fa:"\f789"}.fa-confluence{--fa:"\f78d"}.fa-dhl{--fa:"\f790"}.fa-diaspora{--fa:"\f791"}.fa-fedex{--fa:"\f797"}.fa-fedora{--fa:"\f798"}.fa-figma{--fa:"\f799"}.fa-intercom{--fa:"\f7af"}.fa-invision{--fa:"\f7b0"}.fa-jira{--fa:"\f7b1"}.fa-mendeley{--fa:"\f7b3"}.fa-raspberry-pi{--fa:"\f7bb"}.fa-redhat{--fa:"\f7bc"}.fa-sketch{--fa:"\f7c6"}.fa-sourcetree{--fa:"\f7d3"}.fa-suse{--fa:"\f7d6"}.fa-ubuntu{--fa:"\f7df"}.fa-ups{--fa:"\f7e0"}.fa-usps{--fa:"\f7e1"}.fa-yarn{--fa:"\f7e3"}.fa-airbnb{--fa:"\f834"}.fa-battle-net{--fa:"\f835"}.fa-bootstrap{--fa:"\f836"}.fa-buffer{--fa:"\f837"}.fa-chromecast{--fa:"\f838"}.fa-evernote{--fa:"\f839"}.fa-itch-io{--fa:"\f83a"}.fa-salesforce{--fa:"\f83b"}.fa-speaker-deck{--fa:"\f83c"}.fa-symfony{--fa:"\f83d"}.fa-waze{--fa:"\f83f"}.fa-yammer{--fa:"\f840"}.fa-git-alt{--fa:"\f841"}.fa-stackpath{--fa:"\f842"}.fa-cotton-bureau{--fa:"\f89e"}.fa-buy-n-large{--fa:"\f8a6"}.fa-mdb{--fa:"\f8ca"}.fa-orcid{--fa:"\f8d2"}.fa-swift{--fa:"\f8e1"}.fa-umbraco{--fa:"\f8e8"}:host,:root{--fa-font-regular:normal 400 1em/1 var(--fa-family-classic)}@font-face{font-family:"Font Awesome 7 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.woff2)}.far{--fa-family:var(--fa-family-classic)}.fa-regular,.far{--fa-style:400}:host,:root{--fa-family-classic:"Font Awesome 7 Free";--fa-font-solid:normal 900 1em/1 var(--fa-family-classic);--fa-style-family-classic:var(--fa-family-classic)}@font-face{font-family:"Font Awesome 7 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.woff2)}.fas{--fa-style:900}.fa-classic,.fas{--fa-family:var(--fa-family-classic)}.fa-solid{--fa-style:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(../webfonts/fa-brands-400.woff2) format("woff2")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(../webfonts/fa-solid-900.woff2) format("woff2")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(../webfonts/fa-regular-400.woff2) format("woff2")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-solid-900.woff2) format("woff2")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-brands-400.woff2) format("woff2")}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-regular-400.woff2) format("woff2");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(../webfonts/fa-v4compatibility.woff2) format("woff2");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} \ No newline at end of file diff --git a/platform/src/assets/js/all.min.js b/platform/src/assets/js/all.min.js index a763a12..29ec616 100644 --- a/platform/src/assets/js/all.min.js +++ b/platform/src/assets/js/all.min.js @@ -1,6 +1,6 @@ -/*! - * Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - * Copyright 2025 Fonticons, Inc. - */ +/*! + * Font Awesome Free 7.0.0 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2025 Fonticons, Inc. + */ (()=>{var c={},l={};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document)}catch(c){}var s=void 0===(s=(c.navigator||{}).userAgent)?"":s;function a(c,l){(null==l||l>c.length)&&(l=c.length);for(var s=0,a=Array(l);s{if("object"!=typeof c||!c)return c;var s=c[Symbol.toPrimitive];if(void 0===s)return("string"===l?String:Number)(c);if("object"!=typeof(s=s.call(c,l||"default")))return s;throw new TypeError("@@toPrimitive must return a primitive value.")})(l,"string"))?a:a+"")in c?Object.defineProperty(c,l,{value:s,enumerable:!0,configurable:!0,writable:!0}):c[l]=s,c;var a}function z(l,c){var s,a=Object.keys(l);return Object.getOwnPropertySymbols&&(s=Object.getOwnPropertySymbols(l),c&&(s=s.filter(function(c){return Object.getOwnPropertyDescriptor(l,c).enumerable})),a.push.apply(a,s)),a}function t(l){for(var c=1;c{if(Array.isArray(c))return a(c)})(c)||(c=>{if("undefined"!=typeof Symbol&&null!=c[Symbol.iterator]||null!=c["@@iterator"])return Array.from(c)})(c)||((c,l)=>{var s;if(c)return"string"==typeof c?a(c,l):"Map"===(s="Object"===(s={}.toString.call(c).slice(8,-1))&&c.constructor?c.constructor.name:s)||"Set"===s?Array.from(c):"Arguments"===s||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s)?a(c,l):void 0})(c)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}c.document,l.documentElement&&l.head&&"function"==typeof l.addEventListener&&l.createElement,~s.indexOf("MSIE")||s.indexOf("Trident/");var l={classic:{fa:"solid",fas:"solid","fa-solid":"solid",far:"regular","fa-regular":"regular",fal:"light","fa-light":"light",fat:"thin","fa-thin":"thin",fab:"brands","fa-brands":"brands"},duotone:{fa:"solid",fad:"solid","fa-solid":"solid","fa-duotone":"solid",fadr:"regular","fa-regular":"regular",fadl:"light","fa-light":"light",fadt:"thin","fa-thin":"thin"},sharp:{fa:"solid",fass:"solid","fa-solid":"solid",fasr:"regular","fa-regular":"regular",fasl:"light","fa-light":"light",fast:"thin","fa-thin":"thin"},"sharp-duotone":{fa:"solid",fasds:"solid","fa-solid":"solid",fasdr:"regular","fa-regular":"regular",fasdl:"light","fa-light":"light",fasdt:"thin","fa-thin":"thin"},slab:{"fa-regular":"regular",faslr:"regular"},"slab-press":{"fa-regular":"regular",faslpr:"regular"},thumbprint:{"fa-light":"light",fatl:"light"},whiteboard:{"fa-semibold":"semibold",fawsb:"semibold"},notdog:{"fa-solid":"solid",fans:"solid"},"notdog-duo":{"fa-solid":"solid",fands:"solid"},etch:{"fa-solid":"solid",faes:"solid"},jelly:{"fa-regular":"regular",fajr:"regular"},"jelly-fill":{"fa-regular":"regular",fajfr:"regular"},"jelly-duo":{"fa-regular":"regular",fajdr:"regular"},chisel:{"fa-regular":"regular",facr:"regular"}},n="classic",s=(e(e(e(e(e(e(e(e(e(e(s={},n,"Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(s,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),{fak:"kit","fa-kit":"kit"}),M={fakd:"kit-duotone","fa-kit-duotone":"kit-duotone"},o=(e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),{kit:"fak"}),f={"kit-duotone":"fakd"},i="duotone-group",m="swap-opacity",L="primary",d="secondary",u=(e(e(e(e(e(e(e(e(e(e(u={},"classic","Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(u,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),[1,2,3,4,5,6,7,8,9,10]),h=u.concat([11,12,13,14,15,16,17,18,19,20]),i=[].concat(r(Object.keys({classic:["fas","far","fal","fat","fad"],duotone:["fadr","fadl","fadt"],sharp:["fass","fasr","fasl","fast"],"sharp-duotone":["fasds","fasdr","fasdl","fasdt"],slab:["faslr"],"slab-press":["faslpr"],whiteboard:["fawsb"],thumbprint:["fatl"],notdog:["fans"],"notdog-duo":["fands"],etch:["faes"],jelly:["fajr"],"jelly-fill":["fajfr"],"jelly-duo":["fajdr"],chisel:["facr"]})),["solid","regular","light","thin","duotone","brands","semibold"],["aw","fw","pull-left","pull-right"],["2xs","xs","sm","lg","xl","2xl","beat","border","fade","beat-fade","bounce","flip-both","flip-horizontal","flip-vertical","flip","inverse","layers","layers-bottom-left","layers-bottom-right","layers-counter","layers-text","layers-top-left","layers-top-right","li","pull-end","pull-start","pulse","rotate-180","rotate-270","rotate-90","rotate-by","shake","spin-pulse","spin-reverse","spin","stack-1x","stack-2x","stack","ul","width-auto","width-fixed",i,m,L,d]).concat(u.map(function(c){return"".concat(c,"x")})).concat(h.map(function(c){return"w-".concat(c)})),m="___FONT_AWESOME___",C=(()=>{try{return!0}catch(c){return!1}})();function g(c){return new Proxy(c,{get:function(c,l){return l in c?c[l]:c[n]}})}(L=t({},l))[n]=t(t(t(t({},{"fa-duotone":"duotone"}),l[n]),s),M),g(L),(d=t({},{chisel:{regular:"facr"},classic:{brands:"fab",light:"fal",regular:"far",solid:"fas",thin:"fat"},duotone:{light:"fadl",regular:"fadr",solid:"fad",thin:"fadt"},etch:{solid:"faes"},jelly:{regular:"fajr"},"jelly-duo":{regular:"fajdr"},"jelly-fill":{regular:"fajfr"},notdog:{solid:"fans"},"notdog-duo":{solid:"fands"},sharp:{light:"fasl",regular:"fasr",solid:"fass",thin:"fast"},"sharp-duotone":{light:"fasdl",regular:"fasdr",solid:"fasds",thin:"fasdt"},slab:{regular:"faslr"},"slab-press":{regular:"faslpr"},thumbprint:{light:"fatl"},whiteboard:{semibold:"fawsb"}}))[n]=t(t(t(t({},{duotone:"fad"}),d[n]),o),f),g(d),(u=t({},{classic:{fab:"fa-brands",fad:"fa-duotone",fal:"fa-light",far:"fa-regular",fas:"fa-solid",fat:"fa-thin"},duotone:{fadr:"fa-regular",fadl:"fa-light",fadt:"fa-thin"},sharp:{fass:"fa-solid",fasr:"fa-regular",fasl:"fa-light",fast:"fa-thin"},"sharp-duotone":{fasds:"fa-solid",fasdr:"fa-regular",fasdl:"fa-light",fasdt:"fa-thin"},slab:{faslr:"fa-regular"},"slab-press":{faslpr:"fa-regular"},whiteboard:{fawsb:"fa-semibold"},thumbprint:{fatl:"fa-light"},notdog:{fans:"fa-solid"},"notdog-duo":{fands:"fa-solid"},etch:{faes:"fa-solid"},jelly:{fajr:"fa-regular"},"jelly-fill":{fajfr:"fa-regular"},"jelly-duo":{fajdr:"fa-regular"},chisel:{facr:"fa-regular"}}))[n]=t(t({},u[n]),{fak:"fa-kit"}),g(u),(h=t({},{classic:{"fa-brands":"fab","fa-duotone":"fad","fa-light":"fal","fa-regular":"far","fa-solid":"fas","fa-thin":"fat"},duotone:{"fa-regular":"fadr","fa-light":"fadl","fa-thin":"fadt"},sharp:{"fa-solid":"fass","fa-regular":"fasr","fa-light":"fasl","fa-thin":"fast"},"sharp-duotone":{"fa-solid":"fasds","fa-regular":"fasdr","fa-light":"fasdl","fa-thin":"fasdt"},slab:{"fa-regular":"faslr"},"slab-press":{"fa-regular":"faslpr"},whiteboard:{"fa-semibold":"fawsb"},thumbprint:{"fa-light":"fatl"},notdog:{"fa-solid":"fans"},"notdog-duo":{"fa-solid":"fands"},etch:{"fa-solid":"faes"},jelly:{"fa-regular":"fajr"},"jelly-fill":{"fa-regular":"fajfr"},"jelly-duo":{"fa-regular":"fajdr"},chisel:{"fa-regular":"facr"}}))[n]=t(t({},h[n]),{"fa-kit":"fak"}),g(h),g(t({},{classic:{900:"fas",400:"far",normal:"far",300:"fal",100:"fat"},duotone:{900:"fad",400:"fadr",300:"fadl",100:"fadt"},sharp:{900:"fass",400:"fasr",300:"fasl",100:"fast"},"sharp-duotone":{900:"fasds",400:"fasdr",300:"fasdl",100:"fasdt"},slab:{400:"faslr"},"slab-press":{400:"faslpr"},whiteboard:{600:"fawsb"},thumbprint:{300:"fatl"},notdog:{900:"fans"},"notdog-duo":{900:"fands"},etch:{900:"faes"},chisel:{400:"facr"},jelly:{400:"fajr"},"jelly-fill":{400:"fajfr"},"jelly-duo":{400:"fajdr"}})),[].concat(r(["kit"]),r(i));(l=c||{})[m]||(l[m]={}),l[m].styles||(l[m].styles={}),l[m].hooks||(l[m].hooks={}),l[m].shims||(l[m].shims=[]);var p=l[m];function b(a){return Object.keys(a).reduce(function(c,l){var s=a[l];return!!s.icon?c[s.iconName]=s.icon:c[l]=s,c},{})}function S(c,l,s){var a=(2{var c={},l={};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document)}catch(c){}var s=void 0===(s=(c.navigator||{}).userAgent)?"":s;function a(c,l){(null==l||l>c.length)&&(l=c.length);for(var s=0,a=Array(l);s{if("object"!=typeof c||!c)return c;var s=c[Symbol.toPrimitive];if(void 0===s)return("string"===l?String:Number)(c);if("object"!=typeof(s=s.call(c,l||"default")))return s;throw new TypeError("@@toPrimitive must return a primitive value.")})(l,"string"))?a:a+"")in c?Object.defineProperty(c,l,{value:s,enumerable:!0,configurable:!0,writable:!0}):c[l]=s,c;var a}function z(l,c){var s,a=Object.keys(l);return Object.getOwnPropertySymbols&&(s=Object.getOwnPropertySymbols(l),c&&(s=s.filter(function(c){return Object.getOwnPropertyDescriptor(l,c).enumerable})),a.push.apply(a,s)),a}function t(l){for(var c=1;c{if(Array.isArray(c))return a(c)})(c)||(c=>{if("undefined"!=typeof Symbol&&null!=c[Symbol.iterator]||null!=c["@@iterator"])return Array.from(c)})(c)||((c,l)=>{var s;if(c)return"string"==typeof c?a(c,l):"Map"===(s="Object"===(s={}.toString.call(c).slice(8,-1))&&c.constructor?c.constructor.name:s)||"Set"===s?Array.from(c):"Arguments"===s||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s)?a(c,l):void 0})(c)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}c.document,l.documentElement&&l.head&&"function"==typeof l.addEventListener&&l.createElement,~s.indexOf("MSIE")||s.indexOf("Trident/");var l={classic:{fa:"solid",fas:"solid","fa-solid":"solid",far:"regular","fa-regular":"regular",fal:"light","fa-light":"light",fat:"thin","fa-thin":"thin",fab:"brands","fa-brands":"brands"},duotone:{fa:"solid",fad:"solid","fa-solid":"solid","fa-duotone":"solid",fadr:"regular","fa-regular":"regular",fadl:"light","fa-light":"light",fadt:"thin","fa-thin":"thin"},sharp:{fa:"solid",fass:"solid","fa-solid":"solid",fasr:"regular","fa-regular":"regular",fasl:"light","fa-light":"light",fast:"thin","fa-thin":"thin"},"sharp-duotone":{fa:"solid",fasds:"solid","fa-solid":"solid",fasdr:"regular","fa-regular":"regular",fasdl:"light","fa-light":"light",fasdt:"thin","fa-thin":"thin"},slab:{"fa-regular":"regular",faslr:"regular"},"slab-press":{"fa-regular":"regular",faslpr:"regular"},thumbprint:{"fa-light":"light",fatl:"light"},whiteboard:{"fa-semibold":"semibold",fawsb:"semibold"},notdog:{"fa-solid":"solid",fans:"solid"},"notdog-duo":{"fa-solid":"solid",fands:"solid"},etch:{"fa-solid":"solid",faes:"solid"},jelly:{"fa-regular":"regular",fajr:"regular"},"jelly-fill":{"fa-regular":"regular",fajfr:"regular"},"jelly-duo":{"fa-regular":"regular",fajdr:"regular"},chisel:{"fa-regular":"regular",facr:"regular"}},n="classic",s=(e(e(e(e(e(e(e(e(e(e(s={},n,"Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(s,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),{fak:"kit","fa-kit":"kit"}),M={fakd:"kit-duotone","fa-kit-duotone":"kit-duotone"},o=(e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),{kit:"fak"}),f={"kit-duotone":"fakd"},i="duotone-group",m="swap-opacity",L="primary",d="secondary",u=(e(e(e(e(e(e(e(e(e(e(u={},"classic","Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(u,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),[1,2,3,4,5,6,7,8,9,10]),h=u.concat([11,12,13,14,15,16,17,18,19,20]),i=[].concat(r(Object.keys({classic:["fas","far","fal","fat","fad"],duotone:["fadr","fadl","fadt"],sharp:["fass","fasr","fasl","fast"],"sharp-duotone":["fasds","fasdr","fasdl","fasdt"],slab:["faslr"],"slab-press":["faslpr"],whiteboard:["fawsb"],thumbprint:["fatl"],notdog:["fans"],"notdog-duo":["fands"],etch:["faes"],jelly:["fajr"],"jelly-fill":["fajfr"],"jelly-duo":["fajdr"],chisel:["facr"]})),["solid","regular","light","thin","duotone","brands","semibold"],["aw","fw","pull-left","pull-right"],["2xs","xs","sm","lg","xl","2xl","beat","border","fade","beat-fade","bounce","flip-both","flip-horizontal","flip-vertical","flip","inverse","layers","layers-bottom-left","layers-bottom-right","layers-counter","layers-text","layers-top-left","layers-top-right","li","pull-end","pull-start","pulse","rotate-180","rotate-270","rotate-90","rotate-by","shake","spin-pulse","spin-reverse","spin","stack-1x","stack-2x","stack","ul","width-auto","width-fixed",i,m,L,d]).concat(u.map(function(c){return"".concat(c,"x")})).concat(h.map(function(c){return"w-".concat(c)})),m="___FONT_AWESOME___",C=(()=>{try{return!0}catch(c){return!1}})();function g(c){return new Proxy(c,{get:function(c,l){return l in c?c[l]:c[n]}})}(L=t({},l))[n]=t(t(t(t({},{"fa-duotone":"duotone"}),l[n]),s),M),g(L),(d=t({},{chisel:{regular:"facr"},classic:{brands:"fab",light:"fal",regular:"far",solid:"fas",thin:"fat"},duotone:{light:"fadl",regular:"fadr",solid:"fad",thin:"fadt"},etch:{solid:"faes"},jelly:{regular:"fajr"},"jelly-duo":{regular:"fajdr"},"jelly-fill":{regular:"fajfr"},notdog:{solid:"fans"},"notdog-duo":{solid:"fands"},sharp:{light:"fasl",regular:"fasr",solid:"fass",thin:"fast"},"sharp-duotone":{light:"fasdl",regular:"fasdr",solid:"fasds",thin:"fasdt"},slab:{regular:"faslr"},"slab-press":{regular:"faslpr"},thumbprint:{light:"fatl"},whiteboard:{semibold:"fawsb"}}))[n]=t(t(t(t({},{duotone:"fad"}),d[n]),o),f),g(d),(u=t({},{classic:{fab:"fa-brands",fad:"fa-duotone",fal:"fa-light",far:"fa-regular",fas:"fa-solid",fat:"fa-thin"},duotone:{fadr:"fa-regular",fadl:"fa-light",fadt:"fa-thin"},sharp:{fass:"fa-solid",fasr:"fa-regular",fasl:"fa-light",fast:"fa-thin"},"sharp-duotone":{fasds:"fa-solid",fasdr:"fa-regular",fasdl:"fa-light",fasdt:"fa-thin"},slab:{faslr:"fa-regular"},"slab-press":{faslpr:"fa-regular"},whiteboard:{fawsb:"fa-semibold"},thumbprint:{fatl:"fa-light"},notdog:{fans:"fa-solid"},"notdog-duo":{fands:"fa-solid"},etch:{faes:"fa-solid"},jelly:{fajr:"fa-regular"},"jelly-fill":{fajfr:"fa-regular"},"jelly-duo":{fajdr:"fa-regular"},chisel:{facr:"fa-regular"}}))[n]=t(t({},u[n]),{fak:"fa-kit"}),g(u),(h=t({},{classic:{"fa-brands":"fab","fa-duotone":"fad","fa-light":"fal","fa-regular":"far","fa-solid":"fas","fa-thin":"fat"},duotone:{"fa-regular":"fadr","fa-light":"fadl","fa-thin":"fadt"},sharp:{"fa-solid":"fass","fa-regular":"fasr","fa-light":"fasl","fa-thin":"fast"},"sharp-duotone":{"fa-solid":"fasds","fa-regular":"fasdr","fa-light":"fasdl","fa-thin":"fasdt"},slab:{"fa-regular":"faslr"},"slab-press":{"fa-regular":"faslpr"},whiteboard:{"fa-semibold":"fawsb"},thumbprint:{"fa-light":"fatl"},notdog:{"fa-solid":"fans"},"notdog-duo":{"fa-solid":"fands"},etch:{"fa-solid":"faes"},jelly:{"fa-regular":"fajr"},"jelly-fill":{"fa-regular":"fajfr"},"jelly-duo":{"fa-regular":"fajdr"},chisel:{"fa-regular":"facr"}}))[n]=t(t({},h[n]),{"fa-kit":"fak"}),g(h),g(t({},{classic:{900:"fas",400:"far",normal:"far",300:"fal",100:"fat"},duotone:{900:"fad",400:"fadr",300:"fadl",100:"fadt"},sharp:{900:"fass",400:"fasr",300:"fasl",100:"fast"},"sharp-duotone":{900:"fasds",400:"fasdr",300:"fasdl",100:"fasdt"},slab:{400:"faslr"},"slab-press":{400:"faslpr"},whiteboard:{600:"fawsb"},thumbprint:{300:"fatl"},notdog:{900:"fans"},"notdog-duo":{900:"fands"},etch:{900:"faes"},chisel:{400:"facr"},jelly:{400:"fajr"},"jelly-fill":{400:"fajfr"},"jelly-duo":{400:"fajdr"}})),[].concat(r(["kit"]),r(i));(l=c||{})[m]||(l[m]={}),l[m].styles||(l[m].styles={}),l[m].hooks||(l[m].hooks={}),l[m].shims||(l[m].shims=[]);var p=l[m];function b(a){return Object.keys(a).reduce(function(c,l){var s=a[l];return!!s.icon?c[s.iconName]=s.icon:c[l]=s,c},{})}function S(c,l,s){var a=(2{var c={},l={};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document)}catch(c){}var s=void 0===(s=(c.navigator||{}).userAgent)?"":s;function a(c,l){(null==l||l>c.length)&&(l=c.length);for(var s=0,a=Array(l);s{if("object"!=typeof c||!c)return c;var s=c[Symbol.toPrimitive];if(void 0===s)return("string"===l?String:Number)(c);if("object"!=typeof(s=s.call(c,l||"default")))return s;throw new TypeError("@@toPrimitive must return a primitive value.")})(l,"string"))?a:a+"")in c?Object.defineProperty(c,l,{value:s,enumerable:!0,configurable:!0,writable:!0}):c[l]=s,c;var a}function z(l,c){var s,a=Object.keys(l);return Object.getOwnPropertySymbols&&(s=Object.getOwnPropertySymbols(l),c&&(s=s.filter(function(c){return Object.getOwnPropertyDescriptor(l,c).enumerable})),a.push.apply(a,s)),a}function t(l){for(var c=1;c{if(Array.isArray(c))return a(c)})(c)||(c=>{if("undefined"!=typeof Symbol&&null!=c[Symbol.iterator]||null!=c["@@iterator"])return Array.from(c)})(c)||((c,l)=>{var s;if(c)return"string"==typeof c?a(c,l):"Map"===(s="Object"===(s={}.toString.call(c).slice(8,-1))&&c.constructor?c.constructor.name:s)||"Set"===s?Array.from(c):"Arguments"===s||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s)?a(c,l):void 0})(c)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}c.document,l.documentElement&&l.head&&"function"==typeof l.addEventListener&&l.createElement,~s.indexOf("MSIE")||s.indexOf("Trident/");var l={classic:{fa:"solid",fas:"solid","fa-solid":"solid",far:"regular","fa-regular":"regular",fal:"light","fa-light":"light",fat:"thin","fa-thin":"thin",fab:"brands","fa-brands":"brands"},duotone:{fa:"solid",fad:"solid","fa-solid":"solid","fa-duotone":"solid",fadr:"regular","fa-regular":"regular",fadl:"light","fa-light":"light",fadt:"thin","fa-thin":"thin"},sharp:{fa:"solid",fass:"solid","fa-solid":"solid",fasr:"regular","fa-regular":"regular",fasl:"light","fa-light":"light",fast:"thin","fa-thin":"thin"},"sharp-duotone":{fa:"solid",fasds:"solid","fa-solid":"solid",fasdr:"regular","fa-regular":"regular",fasdl:"light","fa-light":"light",fasdt:"thin","fa-thin":"thin"},slab:{"fa-regular":"regular",faslr:"regular"},"slab-press":{"fa-regular":"regular",faslpr:"regular"},thumbprint:{"fa-light":"light",fatl:"light"},whiteboard:{"fa-semibold":"semibold",fawsb:"semibold"},notdog:{"fa-solid":"solid",fans:"solid"},"notdog-duo":{"fa-solid":"solid",fands:"solid"},etch:{"fa-solid":"solid",faes:"solid"},jelly:{"fa-regular":"regular",fajr:"regular"},"jelly-fill":{"fa-regular":"regular",fajfr:"regular"},"jelly-duo":{"fa-regular":"regular",fajdr:"regular"},chisel:{"fa-regular":"regular",facr:"regular"}},n="classic",s=(e(e(e(e(e(e(e(e(e(e(s={},n,"Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(s,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),{fak:"kit","fa-kit":"kit"}),M={fakd:"kit-duotone","fa-kit-duotone":"kit-duotone"},o=(e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),{kit:"fak"}),f={"kit-duotone":"fakd"},i="duotone-group",m="swap-opacity",L="primary",d="secondary",u=(e(e(e(e(e(e(e(e(e(e(u={},"classic","Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),e(e(e(e(e(u,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),e(e({},"kit","Kit"),"kit-duotone","Kit Duotone"),[1,2,3,4,5,6,7,8,9,10]),h=u.concat([11,12,13,14,15,16,17,18,19,20]),i=[].concat(r(Object.keys({classic:["fas","far","fal","fat","fad"],duotone:["fadr","fadl","fadt"],sharp:["fass","fasr","fasl","fast"],"sharp-duotone":["fasds","fasdr","fasdl","fasdt"],slab:["faslr"],"slab-press":["faslpr"],whiteboard:["fawsb"],thumbprint:["fatl"],notdog:["fans"],"notdog-duo":["fands"],etch:["faes"],jelly:["fajr"],"jelly-fill":["fajfr"],"jelly-duo":["fajdr"],chisel:["facr"]})),["solid","regular","light","thin","duotone","brands","semibold"],["aw","fw","pull-left","pull-right"],["2xs","xs","sm","lg","xl","2xl","beat","border","fade","beat-fade","bounce","flip-both","flip-horizontal","flip-vertical","flip","inverse","layers","layers-bottom-left","layers-bottom-right","layers-counter","layers-text","layers-top-left","layers-top-right","li","pull-end","pull-start","pulse","rotate-180","rotate-270","rotate-90","rotate-by","shake","spin-pulse","spin-reverse","spin","stack-1x","stack-2x","stack","ul","width-auto","width-fixed",i,m,L,d]).concat(u.map(function(c){return"".concat(c,"x")})).concat(h.map(function(c){return"w-".concat(c)})),m="___FONT_AWESOME___",C=(()=>{try{return!0}catch(c){return!1}})();function g(c){return new Proxy(c,{get:function(c,l){return l in c?c[l]:c[n]}})}(L=t({},l))[n]=t(t(t(t({},{"fa-duotone":"duotone"}),l[n]),s),M),g(L),(d=t({},{chisel:{regular:"facr"},classic:{brands:"fab",light:"fal",regular:"far",solid:"fas",thin:"fat"},duotone:{light:"fadl",regular:"fadr",solid:"fad",thin:"fadt"},etch:{solid:"faes"},jelly:{regular:"fajr"},"jelly-duo":{regular:"fajdr"},"jelly-fill":{regular:"fajfr"},notdog:{solid:"fans"},"notdog-duo":{solid:"fands"},sharp:{light:"fasl",regular:"fasr",solid:"fass",thin:"fast"},"sharp-duotone":{light:"fasdl",regular:"fasdr",solid:"fasds",thin:"fasdt"},slab:{regular:"faslr"},"slab-press":{regular:"faslpr"},thumbprint:{light:"fatl"},whiteboard:{semibold:"fawsb"}}))[n]=t(t(t(t({},{duotone:"fad"}),d[n]),o),f),g(d),(u=t({},{classic:{fab:"fa-brands",fad:"fa-duotone",fal:"fa-light",far:"fa-regular",fas:"fa-solid",fat:"fa-thin"},duotone:{fadr:"fa-regular",fadl:"fa-light",fadt:"fa-thin"},sharp:{fass:"fa-solid",fasr:"fa-regular",fasl:"fa-light",fast:"fa-thin"},"sharp-duotone":{fasds:"fa-solid",fasdr:"fa-regular",fasdl:"fa-light",fasdt:"fa-thin"},slab:{faslr:"fa-regular"},"slab-press":{faslpr:"fa-regular"},whiteboard:{fawsb:"fa-semibold"},thumbprint:{fatl:"fa-light"},notdog:{fans:"fa-solid"},"notdog-duo":{fands:"fa-solid"},etch:{faes:"fa-solid"},jelly:{fajr:"fa-regular"},"jelly-fill":{fajfr:"fa-regular"},"jelly-duo":{fajdr:"fa-regular"},chisel:{facr:"fa-regular"}}))[n]=t(t({},u[n]),{fak:"fa-kit"}),g(u),(h=t({},{classic:{"fa-brands":"fab","fa-duotone":"fad","fa-light":"fal","fa-regular":"far","fa-solid":"fas","fa-thin":"fat"},duotone:{"fa-regular":"fadr","fa-light":"fadl","fa-thin":"fadt"},sharp:{"fa-solid":"fass","fa-regular":"fasr","fa-light":"fasl","fa-thin":"fast"},"sharp-duotone":{"fa-solid":"fasds","fa-regular":"fasdr","fa-light":"fasdl","fa-thin":"fasdt"},slab:{"fa-regular":"faslr"},"slab-press":{"fa-regular":"faslpr"},whiteboard:{"fa-semibold":"fawsb"},thumbprint:{"fa-light":"fatl"},notdog:{"fa-solid":"fans"},"notdog-duo":{"fa-solid":"fands"},etch:{"fa-solid":"faes"},jelly:{"fa-regular":"fajr"},"jelly-fill":{"fa-regular":"fajfr"},"jelly-duo":{"fa-regular":"fajdr"},chisel:{"fa-regular":"facr"}}))[n]=t(t({},h[n]),{"fa-kit":"fak"}),g(h),g(t({},{classic:{900:"fas",400:"far",normal:"far",300:"fal",100:"fat"},duotone:{900:"fad",400:"fadr",300:"fadl",100:"fadt"},sharp:{900:"fass",400:"fasr",300:"fasl",100:"fast"},"sharp-duotone":{900:"fasds",400:"fasdr",300:"fasdl",100:"fasdt"},slab:{400:"faslr"},"slab-press":{400:"faslpr"},whiteboard:{600:"fawsb"},thumbprint:{300:"fatl"},notdog:{900:"fans"},"notdog-duo":{900:"fands"},etch:{900:"faes"},chisel:{400:"facr"},jelly:{400:"fajr"},"jelly-fill":{400:"fajfr"},"jelly-duo":{400:"fajdr"}})),[].concat(r(["kit"]),r(i));(l=c||{})[m]||(l[m]={}),l[m].styles||(l[m].styles={}),l[m].hooks||(l[m].hooks={}),l[m].shims||(l[m].shims=[]);var p=l[m];function b(a){return Object.keys(a).reduce(function(c,l){var s=a[l];return!!s.icon?c[s.iconName]=s.icon:c[l]=s,c},{})}function S(c,l,s){var a=(2{function I(c,l){(null==l||l>c.length)&&(l=c.length);for(var s=0,a=Array(l);s=c.length?{done:!0}:{done:!1,value:c[z++]}},e:function(c){throw c},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function a(c,l,s){return(l=R(l))in c?Object.defineProperty(c,l,{value:s,enumerable:!0,configurable:!0,writable:!0}):c[l]=s,c}function W(l,c){var s,a=Object.keys(l);return Object.getOwnPropertySymbols&&(s=Object.getOwnPropertySymbols(l),c&&(s=s.filter(function(c){return Object.getOwnPropertyDescriptor(l,c).enumerable})),a.push.apply(a,s)),a}function u(l){for(var c=1;c{if(Array.isArray(c))return c})(c)||((c,l)=>{var s=null==c?null:"undefined"!=typeof Symbol&&c[Symbol.iterator]||c["@@iterator"];if(null!=s){var a,e,z,t,r=[],n=!0,M=!1;try{if(z=(s=s.call(c)).next,0===l){if(Object(s)!==s)return;n=!1}else for(;!(n=(a=z.call(s)).done)&&(r.push(a.value),r.length!==l);n=!0);}catch(c){M=!0,e=c}finally{try{if(!n&&null!=s.return&&(t=s.return(),Object(t)!==t))return}finally{if(M)throw e}}return r}})(c,l)||_(c,l)||(()=>{throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function h(c){return(c=>{if(Array.isArray(c))return I(c)})(c)||(c=>{if("undefined"!=typeof Symbol&&null!=c[Symbol.iterator]||null!=c["@@iterator"])return Array.from(c)})(c)||_(c)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function R(c){var l=((c,l)=>{if("object"!=typeof c||!c)return c;var s=c[Symbol.toPrimitive];if(void 0===s)return("string"===l?String:Number)(c);if("object"!=typeof(s=s.call(c,l||"default")))return s;throw new TypeError("@@toPrimitive must return a primitive value.")})(c,"string");return"symbol"==typeof l?l:l+""}function J(c){return(J="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(c){return typeof c}:function(c){return c&&"function"==typeof Symbol&&c.constructor===Symbol&&c!==Symbol.prototype?"symbol":typeof c})(c)}function _(c,l){var s;if(c)return"string"==typeof c?I(c,l):"Map"===(s="Object"===(s={}.toString.call(c).slice(8,-1))&&c.constructor?c.constructor.name:s)||"Set"===s?Array.from(c):"Arguments"===s||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(s)?I(c,l):void 0}function Y(){}var c={},l={},s=null,e={mark:Y,measure:Y};try{"undefined"!=typeof window&&(c=window),"undefined"!=typeof document&&(l=document),"undefined"!=typeof MutationObserver&&(s=MutationObserver),"undefined"!=typeof performance&&(e=performance)}catch(V){}var z=void 0===(z=(c.navigator||{}).userAgent)?"":z,C=c,g=l,K=s,c=e,H=!!C.document,i=!!g.documentElement&&!!g.head&&"function"==typeof g.addEventListener&&"function"==typeof g.createElement,U=~z.indexOf("MSIE")||~z.indexOf("Trident/"),l={classic:{fa:"solid",fas:"solid","fa-solid":"solid",far:"regular","fa-regular":"regular",fal:"light","fa-light":"light",fat:"thin","fa-thin":"thin",fab:"brands","fa-brands":"brands"},duotone:{fa:"solid",fad:"solid","fa-solid":"solid","fa-duotone":"solid",fadr:"regular","fa-regular":"regular",fadl:"light","fa-light":"light",fadt:"thin","fa-thin":"thin"},sharp:{fa:"solid",fass:"solid","fa-solid":"solid",fasr:"regular","fa-regular":"regular",fasl:"light","fa-light":"light",fast:"thin","fa-thin":"thin"},"sharp-duotone":{fa:"solid",fasds:"solid","fa-solid":"solid",fasdr:"regular","fa-regular":"regular",fasdl:"light","fa-light":"light",fasdt:"thin","fa-thin":"thin"},slab:{"fa-regular":"regular",faslr:"regular"},"slab-press":{"fa-regular":"regular",faslpr:"regular"},thumbprint:{"fa-light":"light",fatl:"light"},whiteboard:{"fa-semibold":"semibold",fawsb:"semibold"},notdog:{"fa-solid":"solid",fans:"solid"},"notdog-duo":{"fa-solid":"solid",fands:"solid"},etch:{"fa-solid":"solid",faes:"solid"},jelly:{"fa-regular":"regular",fajr:"regular"},"jelly-fill":{"fa-regular":"regular",fajfr:"regular"},"jelly-duo":{"fa-regular":"regular",fajdr:"regular"},chisel:{"fa-regular":"regular",facr:"regular"}},B=["fa-classic","fa-duotone","fa-sharp","fa-sharp-duotone","fa-thumbprint","fa-whiteboard","fa-notdog","fa-notdog-duo","fa-chisel","fa-etch","fa-jelly","fa-jelly-fill","fa-jelly-duo","fa-slab","fa-slab-press"],L="classic",d="duotone",V="thumbprint",X=[L,d,"sharp",s="sharp-duotone","chisel","etch","jelly",e="jelly-duo",z="jelly-fill","notdog",Z="notdog-duo","slab",t="slab-press",V,r="whiteboard"],$=(a(a(a(a(a(a(a(a(a(a(Q={},L,"Classic"),d,"Duotone"),"sharp","Sharp"),s,"Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),e,"Jelly Duo"),z,"Jelly Fill"),"notdog","Notdog"),a(a(a(a(a(Q,Z,"Notdog Duo"),"slab","Slab"),t,"Slab Press"),V,"Thumbprint"),r,"Whiteboard"),new Map([["classic",{defaultShortPrefixId:"fas",defaultStyleId:"solid",styleIds:["solid","regular","light","thin","brands"],futureStyleIds:[],defaultFontWeight:900}],["duotone",{defaultShortPrefixId:"fad",defaultStyleId:"solid",styleIds:["solid","regular","light","thin"],futureStyleIds:[],defaultFontWeight:900}],["sharp",{defaultShortPrefixId:"fass",defaultStyleId:"solid",styleIds:["solid","regular","light","thin"],futureStyleIds:[],defaultFontWeight:900}],["sharp-duotone",{defaultShortPrefixId:"fasds",defaultStyleId:"solid",styleIds:["solid","regular","light","thin"],futureStyleIds:[],defaultFontWeight:900}],["chisel",{defaultShortPrefixId:"facr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["etch",{defaultShortPrefixId:"faes",defaultStyleId:"solid",styleIds:["solid"],futureStyleIds:[],defaultFontWeight:900}],["jelly",{defaultShortPrefixId:"fajr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["jelly-duo",{defaultShortPrefixId:"fajdr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["jelly-fill",{defaultShortPrefixId:"fajfr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["notdog",{defaultShortPrefixId:"fans",defaultStyleId:"solid",styleIds:["solid"],futureStyleIds:[],defaultFontWeight:900}],["notdog-duo",{defaultShortPrefixId:"fands",defaultStyleId:"solid",styleIds:["solid"],futureStyleIds:[],defaultFontWeight:900}],["slab",{defaultShortPrefixId:"faslr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["slab-press",{defaultShortPrefixId:"faslpr",defaultStyleId:"regular",styleIds:["regular"],futureStyleIds:[],defaultFontWeight:400}],["thumbprint",{defaultShortPrefixId:"fatl",defaultStyleId:"light",styleIds:["light"],futureStyleIds:[],defaultFontWeight:300}],["whiteboard",{defaultShortPrefixId:"fawsb",defaultStyleId:"semibold",styleIds:["semibold"],futureStyleIds:[],defaultFontWeight:600}]])),G=["fak","fa-kit","fakd","fa-kit-duotone"],s={fak:"kit","fa-kit":"kit"},e={fakd:"kit-duotone","fa-kit-duotone":"kit-duotone"},z=(a(a({},"kit","Kit"),"kit-duotone","Kit Duotone"),{kit:"fak"}),Q={"kit-duotone":"fakd"},Z="duotone-group",t="swap-opacity",r="primary",n="secondary",c1=(a(a(a(a(a(a(a(a(a(a(F={},"classic","Classic"),"duotone","Duotone"),"sharp","Sharp"),"sharp-duotone","Sharp Duotone"),"chisel","Chisel"),"etch","Etch"),"jelly","Jelly"),"jelly-duo","Jelly Duo"),"jelly-fill","Jelly Fill"),"notdog","Notdog"),a(a(a(a(a(F,"notdog-duo","Notdog Duo"),"slab","Slab"),"slab-press","Slab Press"),"thumbprint","Thumbprint"),"whiteboard","Whiteboard"),a(a({},"kit","Kit"),"kit-duotone","Kit Duotone"),{classic:{fab:"fa-brands",fad:"fa-duotone",fal:"fa-light",far:"fa-regular",fas:"fa-solid",fat:"fa-thin"},duotone:{fadr:"fa-regular",fadl:"fa-light",fadt:"fa-thin"},sharp:{fass:"fa-solid",fasr:"fa-regular",fasl:"fa-light",fast:"fa-thin"},"sharp-duotone":{fasds:"fa-solid",fasdr:"fa-regular",fasdl:"fa-light",fasdt:"fa-thin"},slab:{faslr:"fa-regular"},"slab-press":{faslpr:"fa-regular"},whiteboard:{fawsb:"fa-semibold"},thumbprint:{fatl:"fa-light"},notdog:{fans:"fa-solid"},"notdog-duo":{fands:"fa-solid"},etch:{faes:"fa-solid"},jelly:{fajr:"fa-regular"},"jelly-fill":{fajfr:"fa-regular"},"jelly-duo":{fajdr:"fa-regular"},chisel:{facr:"fa-regular"}}),l1=["fa","fas","far","fal","fat","fad","fadr","fadl","fadt","fab","fass","fasr","fasl","fast","fasds","fasdr","fasdl","fasdt","faslr","faslpr","fawsb","fatl","fans","fands","faes","fajr","fajfr","fajdr","facr"].concat(["fa-classic","fa-duotone","fa-sharp","fa-sharp-duotone","fa-thumbprint","fa-whiteboard","fa-notdog","fa-notdog-duo","fa-chisel","fa-etch","fa-jelly","fa-jelly-fill","fa-jelly-duo","fa-slab","fa-slab-press"],["fa-solid","fa-regular","fa-light","fa-thin","fa-duotone","fa-brands","fa-semibold"]),M=(F=[1,2,3,4,5,6,7,8,9,10]).concat([11,12,13,14,15,16,17,18,19,20]),Z=[].concat(h(Object.keys({classic:["fas","far","fal","fat","fad"],duotone:["fadr","fadl","fadt"],sharp:["fass","fasr","fasl","fast"],"sharp-duotone":["fasds","fasdr","fasdl","fasdt"],slab:["faslr"],"slab-press":["faslpr"],whiteboard:["fawsb"],thumbprint:["fatl"],notdog:["fans"],"notdog-duo":["fands"],etch:["faes"],jelly:["fajr"],"jelly-fill":["fajfr"],"jelly-duo":["fajdr"],chisel:["facr"]})),["solid","regular","light","thin","duotone","brands","semibold"],["aw","fw","pull-left","pull-right"],["2xs","xs","sm","lg","xl","2xl","beat","border","fade","beat-fade","bounce","flip-both","flip-horizontal","flip-vertical","flip","inverse","layers","layers-bottom-left","layers-bottom-right","layers-counter","layers-text","layers-top-left","layers-top-right","li","pull-end","pull-start","pulse","rotate-180","rotate-270","rotate-90","rotate-by","shake","spin-pulse","spin-reverse","spin","stack-1x","stack-2x","stack","ul","width-auto","width-fixed",Z,t,r,n]).concat(F.map(function(c){return"".concat(c,"x")})).concat(M.map(function(c){return"w-".concat(c)})),t="___FONT_AWESOME___",s1=16,a1="svg-inline--fa",p="data-fa-i2svg",e1="data-fa-pseudo-element",z1="data-fa-pseudo-element-pending",t1="data-prefix",r1="data-icon",n1="fontawesome-i2svg",M1="async",o1=["HTML","HEAD","STYLE","SCRIPT"],f1=["::before","::after",":before",":after"],i1=(()=>{try{return!0}catch(c){return!1}})();function o(c){return new Proxy(c,{get:function(c,l){return l in c?c[l]:c[L]}})}(r=u({},l))[L]=u(u(u(u({},{"fa-duotone":"duotone"}),l[L]),s),e);var m1=o(r),L1=((n=u({},{chisel:{regular:"facr"},classic:{brands:"fab",light:"fal",regular:"far",solid:"fas",thin:"fat"},duotone:{light:"fadl",regular:"fadr",solid:"fad",thin:"fadt"},etch:{solid:"faes"},jelly:{regular:"fajr"},"jelly-duo":{regular:"fajdr"},"jelly-fill":{regular:"fajfr"},notdog:{solid:"fans"},"notdog-duo":{solid:"fands"},sharp:{light:"fasl",regular:"fasr",solid:"fass",thin:"fast"},"sharp-duotone":{light:"fasdl",regular:"fasdr",solid:"fasds",thin:"fasdt"},slab:{regular:"faslr"},"slab-press":{regular:"faslpr"},thumbprint:{light:"fatl"},whiteboard:{semibold:"fawsb"}}))[L]=u(u(u(u({},{duotone:"fad"}),n[L]),z),Q),o(n)),d1=((F=u({},c1))[L]=u(u({},F[L]),{fak:"fa-kit"}),o(F)),u1=((M=u({},{classic:{"fa-brands":"fab","fa-duotone":"fad","fa-light":"fal","fa-regular":"far","fa-solid":"fas","fa-thin":"fat"},duotone:{"fa-regular":"fadr","fa-light":"fadl","fa-thin":"fadt"},sharp:{"fa-solid":"fass","fa-regular":"fasr","fa-light":"fasl","fa-thin":"fast"},"sharp-duotone":{"fa-solid":"fasds","fa-regular":"fasdr","fa-light":"fasdl","fa-thin":"fasdt"},slab:{"fa-regular":"faslr"},"slab-press":{"fa-regular":"faslpr"},whiteboard:{"fa-semibold":"fawsb"},thumbprint:{"fa-light":"fatl"},notdog:{"fa-solid":"fans"},"notdog-duo":{"fa-solid":"fands"},etch:{"fa-solid":"faes"},jelly:{"fa-regular":"fajr"},"jelly-fill":{"fa-regular":"fajfr"},"jelly-duo":{"fa-regular":"fajdr"},chisel:{"fa-regular":"facr"}}))[L]=u(u({},M[L]),{"fa-kit":"fak"}),o(M),/fa(k|kd|s|r|l|t|d|dr|dl|dt|b|slr|slpr|wsb|tl|ns|nds|es|jr|jfr|jdr|cr|ss|sr|sl|st|sds|sdr|sdl|sdt)?[\-\ ]/),h1="fa-layers-text",C1=/Font ?Awesome ?([567 ]*)(Solid|Regular|Light|Thin|Duotone|Brands|Free|Pro|Sharp Duotone|Sharp|Kit|Notdog Duo|Notdog|Chisel|Etch|Thumbprint|Jelly Fill|Jelly Duo|Jelly|Slab Press|Slab|Whiteboard)?.*/i,g1=(o(u({},{classic:{900:"fas",400:"far",normal:"far",300:"fal",100:"fat"},duotone:{900:"fad",400:"fadr",300:"fadl",100:"fadt"},sharp:{900:"fass",400:"fasr",300:"fasl",100:"fast"},"sharp-duotone":{900:"fasds",400:"fasdr",300:"fasdl",100:"fasdt"},slab:{400:"faslr"},"slab-press":{400:"faslpr"},whiteboard:{600:"fawsb"},thumbprint:{300:"fatl"},notdog:{900:"fans"},"notdog-duo":{900:"fands"},etch:{900:"faes"},chisel:{400:"facr"},jelly:{400:"fajr"},"jelly-fill":{400:"fajfr"},"jelly-duo":{400:"fajdr"}})),["class","data-prefix","data-icon","data-fa-transform","data-fa-mask"]),p1={GROUP:"duotone-group",SWAP_OPACITY:"swap-opacity",PRIMARY:"primary",SECONDARY:"secondary"},b1=[].concat(h(["kit"]),h(Z)),f=C.FontAwesomeConfig||{},l=(g&&"function"==typeof g.querySelector&&[["data-family-prefix","familyPrefix"],["data-css-prefix","cssPrefix"],["data-family-default","familyDefault"],["data-style-default","styleDefault"],["data-replacement-class","replacementClass"],["data-auto-replace-svg","autoReplaceSvg"],["data-auto-add-css","autoAddCss"],["data-search-pseudo-elements","searchPseudoElements"],["data-search-pseudo-elements-warnings","searchPseudoElementsWarnings"],["data-search-pseudo-elements-full-scan","searchPseudoElementsFullScan"],["data-observe-mutations","observeMutations"],["data-mutate-approach","mutateApproach"],["data-keep-original-source","keepOriginalSource"],["data-measure-performance","measurePerformance"],["data-show-missing-icons","showMissingIcons"]].forEach(function(c){var l=m(c,2),s=l[0],l=l[1],s=""===(c=(c=>{var l=g.querySelector("script["+c+"]");if(l)return l.getAttribute(c)})(s))||"false"!==c&&("true"===c||c);null!=s&&(f[l]=s)}),{styleDefault:"solid",familyDefault:L,cssPrefix:"fa",replacementClass:a1,autoReplaceSvg:!0,autoAddCss:!0,searchPseudoElements:!1,searchPseudoElementsWarnings:!0,searchPseudoElementsFullScan:!1,observeMutations:!0,mutateApproach:"async",keepOriginalSource:!0,measurePerformance:!1,showMissingIcons:!0}),b=(f.familyPrefix&&(f.cssPrefix=f.familyPrefix),u(u({},l),f)),S=(b.autoReplaceSvg||(b.observeMutations=!1),{}),S1=(Object.keys(l).forEach(function(l){Object.defineProperty(S,l,{enumerable:!0,set:function(c){b[l]=c,S1.forEach(function(c){return c(S)})},get:function(){return b[l]}})}),Object.defineProperty(S,"familyPrefix",{enumerable:!0,set:function(c){b.cssPrefix=c,S1.forEach(function(c){return c(S)})},get:function(){return b.cssPrefix}}),C.FontAwesomeConfig=S,[]),y=s1,v={size:16,x:0,y:0,rotate:0,flipX:!1,flipY:!1};function y1(){for(var c=12,l="";0>>0;s--;)l[s]=c[s];return l}function v1(c){return c.classList?w(c.classList):(c.getAttribute("class")||"").split(" ").filter(function(c){return c})}function w1(c){return"".concat(c).replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function k1(s){return Object.keys(s||{}).reduce(function(c,l){return c+"".concat(l,": ").concat(s[l].trim(),";")},"")}function x1(c){return c.size!==v.size||c.x!==v.x||c.y!==v.y||c.rotate!==v.rotate||c.flipX||c.flipY}function j1(){var c,l,s=a1,a=S.cssPrefix,e=S.replacementClass,z=':root, :host {\n --fa-font-solid: normal 900 1em/1 "Font Awesome 7 Free";\n --fa-font-regular: normal 400 1em/1 "Font Awesome 7 Free";\n --fa-font-light: normal 300 1em/1 "Font Awesome 7 Pro";\n --fa-font-thin: normal 100 1em/1 "Font Awesome 7 Pro";\n --fa-font-duotone: normal 900 1em/1 "Font Awesome 7 Duotone";\n --fa-font-duotone-regular: normal 400 1em/1 "Font Awesome 7 Duotone";\n --fa-font-duotone-light: normal 300 1em/1 "Font Awesome 7 Duotone";\n --fa-font-duotone-thin: normal 100 1em/1 "Font Awesome 7 Duotone";\n --fa-font-brands: normal 400 1em/1 "Font Awesome 7 Brands";\n --fa-font-sharp-solid: normal 900 1em/1 "Font Awesome 7 Sharp";\n --fa-font-sharp-regular: normal 400 1em/1 "Font Awesome 7 Sharp";\n --fa-font-sharp-light: normal 300 1em/1 "Font Awesome 7 Sharp";\n --fa-font-sharp-thin: normal 100 1em/1 "Font Awesome 7 Sharp";\n --fa-font-sharp-duotone-solid: normal 900 1em/1 "Font Awesome 7 Sharp Duotone";\n --fa-font-sharp-duotone-regular: normal 400 1em/1 "Font Awesome 7 Sharp Duotone";\n --fa-font-sharp-duotone-light: normal 300 1em/1 "Font Awesome 7 Sharp Duotone";\n --fa-font-sharp-duotone-thin: normal 100 1em/1 "Font Awesome 7 Sharp Duotone";\n --fa-font-slab-regular: normal 400 1em/1 "Font Awesome 7 Slab";\n --fa-font-slab-press-regular: normal 400 1em/1 "Font Awesome 7 Slab Press";\n --fa-font-whiteboard-semibold: normal 600 1em/1 "Font Awesome 7 Whiteboard";\n --fa-font-thumbprint-light: normal 300 1em/1 "Font Awesome 7 Thumbprint";\n --fa-font-notdog-solid: normal 900 1em/1 "Font Awesome 7 Notdog";\n --fa-font-notdog-duo-solid: normal 900 1em/1 "Font Awesome 7 Notdog Duo";\n --fa-font-etch-solid: normal 900 1em/1 "Font Awesome 7 Etch";\n --fa-font-jelly-regular: normal 400 1em/1 "Font Awesome 7 Jelly";\n --fa-font-jelly-fill-regular: normal 400 1em/1 "Font Awesome 7 Jelly Fill";\n --fa-font-jelly-duo-regular: normal 400 1em/1 "Font Awesome 7 Jelly Duo";\n --fa-font-chisel-regular: normal 400 1em/1 "Font Awesome 7 Chisel";\n}\n\n.svg-inline--fa {\n box-sizing: content-box;\n display: var(--fa-display, inline-block);\n height: 1em;\n overflow: visible;\n vertical-align: -0.125em;\n width: var(--fa-width, 1.25em);\n}\n.svg-inline--fa.fa-2xs {\n vertical-align: 0.1em;\n}\n.svg-inline--fa.fa-xs {\n vertical-align: 0em;\n}\n.svg-inline--fa.fa-sm {\n vertical-align: -0.0714285714em;\n}\n.svg-inline--fa.fa-lg {\n vertical-align: -0.2em;\n}\n.svg-inline--fa.fa-xl {\n vertical-align: -0.25em;\n}\n.svg-inline--fa.fa-2xl {\n vertical-align: -0.3125em;\n}\n.svg-inline--fa.fa-pull-left,\n.svg-inline--fa .fa-pull-start {\n float: inline-start;\n margin-inline-end: var(--fa-pull-margin, 0.3em);\n}\n.svg-inline--fa.fa-pull-right,\n.svg-inline--fa .fa-pull-end {\n float: inline-end;\n margin-inline-start: var(--fa-pull-margin, 0.3em);\n}\n.svg-inline--fa.fa-li {\n width: var(--fa-li-width, 2em);\n inset-inline-start: calc(-1 * var(--fa-li-width, 2em));\n inset-block-start: 0.25em; /* syncing vertical alignment with Web Font rendering */\n}\n\n.fa-layers-counter, .fa-layers-text {\n display: inline-block;\n position: absolute;\n text-align: center;\n}\n\n.fa-layers {\n display: inline-block;\n height: 1em;\n position: relative;\n text-align: center;\n vertical-align: -0.125em;\n width: var(--fa-width, 1.25em);\n}\n.fa-layers .svg-inline--fa {\n inset: 0;\n margin: auto;\n position: absolute;\n transform-origin: center center;\n}\n\n.fa-layers-text {\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n transform-origin: center center;\n}\n\n.fa-layers-counter {\n background-color: var(--fa-counter-background-color, #ff253a);\n border-radius: var(--fa-counter-border-radius, 1em);\n box-sizing: border-box;\n color: var(--fa-inverse, #fff);\n line-height: var(--fa-counter-line-height, 1);\n max-width: var(--fa-counter-max-width, 5em);\n min-width: var(--fa-counter-min-width, 1.5em);\n overflow: hidden;\n padding: var(--fa-counter-padding, 0.25em 0.5em);\n right: var(--fa-right, 0);\n text-overflow: ellipsis;\n top: var(--fa-top, 0);\n transform: scale(var(--fa-counter-scale, 0.25));\n transform-origin: top right;\n}\n\n.fa-layers-bottom-right {\n bottom: var(--fa-bottom, 0);\n right: var(--fa-right, 0);\n top: auto;\n transform: scale(var(--fa-layers-scale, 0.25));\n transform-origin: bottom right;\n}\n\n.fa-layers-bottom-left {\n bottom: var(--fa-bottom, 0);\n left: var(--fa-left, 0);\n right: auto;\n top: auto;\n transform: scale(var(--fa-layers-scale, 0.25));\n transform-origin: bottom left;\n}\n\n.fa-layers-top-right {\n top: var(--fa-top, 0);\n right: var(--fa-right, 0);\n transform: scale(var(--fa-layers-scale, 0.25));\n transform-origin: top right;\n}\n\n.fa-layers-top-left {\n left: var(--fa-left, 0);\n right: auto;\n top: var(--fa-top, 0);\n transform: scale(var(--fa-layers-scale, 0.25));\n transform-origin: top left;\n}\n\n.fa-1x {\n font-size: 1em;\n}\n\n.fa-2x {\n font-size: 2em;\n}\n\n.fa-3x {\n font-size: 3em;\n}\n\n.fa-4x {\n font-size: 4em;\n}\n\n.fa-5x {\n font-size: 5em;\n}\n\n.fa-6x {\n font-size: 6em;\n}\n\n.fa-7x {\n font-size: 7em;\n}\n\n.fa-8x {\n font-size: 8em;\n}\n\n.fa-9x {\n font-size: 9em;\n}\n\n.fa-10x {\n font-size: 10em;\n}\n\n.fa-2xs {\n font-size: calc(10 / 16 * 1em); /* converts a 10px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 10 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 10 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-xs {\n font-size: calc(12 / 16 * 1em); /* converts a 12px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 12 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 12 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-sm {\n font-size: calc(14 / 16 * 1em); /* converts a 14px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 14 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 14 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-lg {\n font-size: calc(20 / 16 * 1em); /* converts a 20px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 20 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 20 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-xl {\n font-size: calc(24 / 16 * 1em); /* converts a 24px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 24 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 24 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-2xl {\n font-size: calc(32 / 16 * 1em); /* converts a 32px size into an em-based value that\'s relative to the scale\'s 16px base */\n line-height: calc(1 / 32 * 1em); /* sets the line-height of the icon back to that of it\'s parent */\n vertical-align: calc((6 / 32 - 0.375) * 1em); /* vertically centers the icon taking into account the surrounding text\'s descender */\n}\n\n.fa-width-auto {\n --fa-width: auto;\n}\n\n.fa-fw,\n.fa-width-fixed {\n --fa-width: 1.25em;\n}\n\n.fa-ul {\n list-style-type: none;\n margin-inline-start: var(--fa-li-margin, 2.5em);\n padding-inline-start: 0;\n}\n.fa-ul > li {\n position: relative;\n}\n\n.fa-li {\n inset-inline-start: calc(-1 * var(--fa-li-width, 2em));\n position: absolute;\n text-align: center;\n width: var(--fa-li-width, 2em);\n line-height: inherit;\n}\n\n/* Heads Up: Bordered Icons will not be supported in the future!\n - This feature will be deprecated in the next major release of Font Awesome (v8)!\n - You may continue to use it in this version *v7), but it will not be supported in Font Awesome v8.\n*/\n/* Notes:\n* --@{v.$css-prefix}-border-width = 1/16 by default (to render as ~1px based on a 16px default font-size)\n* --@{v.$css-prefix}-border-padding =\n ** 3/16 for vertical padding (to give ~2px of vertical whitespace around an icon considering it\'s vertical alignment)\n ** 4/16 for horizontal padding (to give ~4px of horizontal whitespace around an icon)\n*/\n.fa-border {\n border-color: var(--fa-border-color, #eee);\n border-radius: var(--fa-border-radius, 0.1em);\n border-style: var(--fa-border-style, solid);\n border-width: var(--fa-border-width, 0.0625em);\n box-sizing: var(--fa-border-box-sizing, content-box);\n padding: var(--fa-border-padding, 0.1875em 0.25em);\n}\n\n.fa-pull-left,\n.fa-pull-start {\n float: inline-start;\n margin-inline-end: var(--fa-pull-margin, 0.3em);\n}\n\n.fa-pull-right,\n.fa-pull-end {\n float: inline-end;\n margin-inline-start: var(--fa-pull-margin, 0.3em);\n}\n\n.fa-beat {\n animation-name: fa-beat;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, ease-in-out);\n}\n\n.fa-bounce {\n animation-name: fa-bounce;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1));\n}\n\n.fa-fade {\n animation-name: fa-fade;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));\n}\n\n.fa-beat-fade {\n animation-name: fa-beat-fade;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.4, 0, 0.6, 1));\n}\n\n.fa-flip {\n animation-name: fa-flip;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, ease-in-out);\n}\n\n.fa-shake {\n animation-name: fa-shake;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, linear);\n}\n\n.fa-spin {\n animation-name: fa-spin;\n animation-delay: var(--fa-animation-delay, 0s);\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 2s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, linear);\n}\n\n.fa-spin-reverse {\n --fa-animation-direction: reverse;\n}\n\n.fa-pulse,\n.fa-spin-pulse {\n animation-name: fa-spin;\n animation-direction: var(--fa-animation-direction, normal);\n animation-duration: var(--fa-animation-duration, 1s);\n animation-iteration-count: var(--fa-animation-iteration-count, infinite);\n animation-timing-function: var(--fa-animation-timing, steps(8));\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fa-beat,\n .fa-bounce,\n .fa-fade,\n .fa-beat-fade,\n .fa-flip,\n .fa-pulse,\n .fa-shake,\n .fa-spin,\n .fa-spin-pulse {\n animation: none !important;\n transition: none !important;\n }\n}\n@keyframes fa-beat {\n 0%, 90% {\n transform: scale(1);\n }\n 45% {\n transform: scale(var(--fa-beat-scale, 1.25));\n }\n}\n@keyframes fa-bounce {\n 0% {\n transform: scale(1, 1) translateY(0);\n }\n 10% {\n transform: scale(var(--fa-bounce-start-scale-x, 1.1), var(--fa-bounce-start-scale-y, 0.9)) translateY(0);\n }\n 30% {\n transform: scale(var(--fa-bounce-jump-scale-x, 0.9), var(--fa-bounce-jump-scale-y, 1.1)) translateY(var(--fa-bounce-height, -0.5em));\n }\n 50% {\n transform: scale(var(--fa-bounce-land-scale-x, 1.05), var(--fa-bounce-land-scale-y, 0.95)) translateY(0);\n }\n 57% {\n transform: scale(1, 1) translateY(var(--fa-bounce-rebound, -0.125em));\n }\n 64% {\n transform: scale(1, 1) translateY(0);\n }\n 100% {\n transform: scale(1, 1) translateY(0);\n }\n}\n@keyframes fa-fade {\n 50% {\n opacity: var(--fa-fade-opacity, 0.4);\n }\n}\n@keyframes fa-beat-fade {\n 0%, 100% {\n opacity: var(--fa-beat-fade-opacity, 0.4);\n transform: scale(1);\n }\n 50% {\n opacity: 1;\n transform: scale(var(--fa-beat-fade-scale, 1.125));\n }\n}\n@keyframes fa-flip {\n 50% {\n transform: rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -180deg));\n }\n}\n@keyframes fa-shake {\n 0% {\n transform: rotate(-15deg);\n }\n 4% {\n transform: rotate(15deg);\n }\n 8%, 24% {\n transform: rotate(-18deg);\n }\n 12%, 28% {\n transform: rotate(18deg);\n }\n 16% {\n transform: rotate(-22deg);\n }\n 20% {\n transform: rotate(22deg);\n }\n 32% {\n transform: rotate(-12deg);\n }\n 36% {\n transform: rotate(12deg);\n }\n 40%, 100% {\n transform: rotate(0deg);\n }\n}\n@keyframes fa-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n.fa-rotate-90 {\n transform: rotate(90deg);\n}\n\n.fa-rotate-180 {\n transform: rotate(180deg);\n}\n\n.fa-rotate-270 {\n transform: rotate(270deg);\n}\n\n.fa-flip-horizontal {\n transform: scale(-1, 1);\n}\n\n.fa-flip-vertical {\n transform: scale(1, -1);\n}\n\n.fa-flip-both,\n.fa-flip-horizontal.fa-flip-vertical {\n transform: scale(-1, -1);\n}\n\n.fa-rotate-by {\n transform: rotate(var(--fa-rotate-angle, 0));\n}\n\n.svg-inline--fa .fa-primary {\n fill: var(--fa-primary-color, currentColor);\n opacity: var(--fa-primary-opacity, 1);\n}\n\n.svg-inline--fa .fa-secondary {\n fill: var(--fa-secondary-color, currentColor);\n opacity: var(--fa-secondary-opacity, 0.4);\n}\n\n.svg-inline--fa.fa-swap-opacity .fa-primary {\n opacity: var(--fa-secondary-opacity, 0.4);\n}\n\n.svg-inline--fa.fa-swap-opacity .fa-secondary {\n opacity: var(--fa-primary-opacity, 1);\n}\n\n.svg-inline--fa mask .fa-primary,\n.svg-inline--fa mask .fa-secondary {\n fill: black;\n}\n\n.svg-inline--fa.fa-inverse {\n fill: var(--fa-inverse, #fff);\n}\n\n.fa-stack {\n display: inline-block;\n height: 2em;\n line-height: 2em;\n position: relative;\n vertical-align: middle;\n width: 2.5em;\n}\n\n.fa-inverse {\n color: var(--fa-inverse, #fff);\n}\n\n.svg-inline--fa.fa-stack-1x {\n height: 1em;\n width: 1.25em;\n}\n.svg-inline--fa.fa-stack-2x {\n height: 2em;\n width: 2.5em;\n}\n\n.fa-stack-1x,\n.fa-stack-2x {\n bottom: 0;\n left: 0;\n margin: auto;\n position: absolute;\n right: 0;\n top: 0;\n z-index: var(--fa-stack-z-index, auto);\n}';return"fa"===a&&e===s||(c=new RegExp("\\.".concat("fa","\\-"),"g"),l=new RegExp("\\--".concat("fa","\\-"),"g"),s=new RegExp("\\.".concat(s),"g"),z=z.replace(c,".".concat(a,"-")).replace(l,"--".concat(a,"-")).replace(s,".".concat(e))),z}var q1=!1;function A1(){if(S.autoAddCss&&!q1){var c=j1();if(c&&i){for(var l=g.createElement("style"),s=(l.setAttribute("type","text/css"),l.innerHTML=c,g.head.childNodes),a=null,e=s.length-1;-1").concat(e.map(x).join(""),"")}function F1(c,l,s){if(c&&c[l]&&c[l][s])return{prefix:l,iconName:s,icon:c[l][s]}}function I1(c,l,s,a){for(var e,z,t=Object.keys(c),r=t.length,n=void 0!==a?D1(l,a):l,M=void 0===s?(e=1,c[t[0]]):(e=0,s);e{var l=c.values,s=c.family,a=c.canonical,e=void 0===(e=c.givenPrefix)?"":e,z=void 0===(z=c.styles)?{}:z,t=void 0===(t=c.config)?{}:t,r=s===d,n=l.includes("fa-duotone")||l.includes("fad"),M="duotone"===t.familyDefault,o="fad"===a.prefix||"fa-duotone"===a.prefix;return!r&&(n||M||o)&&(a.prefix="fad"),(l.includes("fa-brands")||l.includes("fab"))&&(a.prefix="fab"),!a.prefix&&e2.includes(s)&&(Object.keys(z).find(function(c){return z2.includes(c)})||t.autoFetchSvg)&&(r=$.get(s).defaultShortPrefixId,a.prefix=r,a.iconName=A(a.prefix,a.iconName)||a.iconName),"fa"!==a.prefix&&"fa"!==e||(a.prefix=q||"fas"),a})({values:c,family:o,styles:j,config:S,canonical:f,givenPrefix:M})),(l=n,c=M,i=(r=f).prefix,o=r.iconName,!l&&i&&o&&(n="fa"===c?Q1(o):{},f=A(i,o),o=n.iconName||f||o,"far"!==(i=n.prefix||i)||j.far||!j.fas||S.autoFetchSvg||(i="fas")),{prefix:i,iconName:o}))}var e2=X.filter(function(c){return c!==L||c!==d}),z2=Object.keys(c1).filter(function(c){return c!==L}).map(function(c){return Object.keys(c1[c])}).flat(),r=(()=>{function c(){if(!(this instanceof c))throw new TypeError("Cannot call a class as a function");this.definitions={}}return l=c,(s=[{key:"add",value:function(){for(var l=this,c=arguments.length,s=new Array(c),a=0;a=K2[0]&&M<=K2[1],n=2===n.length&&n[0]===n[1],M=M||n||s,n=G1(e,i),z=n,f&&(s=B1[i],f=G1("fas",i),(i=s||(f?{prefix:"fas",iconName:f}:null)||{prefix:null,iconName:null}).iconName)&&i.prefix&&(n=i.iconName,e=i.prefix),!n)||M||l&&l.getAttribute(t1)===e&&l.getAttribute(r1)===z?a():(m.setAttribute(d,z),l&&m.removeChild(l),(r=(t={iconName:null,prefix:null,transform:v,symbol:!1,mask:{iconName:null,prefix:null,rest:[]},maskId:null,extra:{classes:[],styles:{},attributes:{}}}).extra).attributes[e1]=L,C2(n,e).then(function(c){var l=m2(u(u({},t),{},{icons:{main:c,mask:Z1()},prefix:e,iconName:z,extra:r,watchable:!0})),s=g.createElementNS("http://www.w3.org/2000/svg","svg");"::before"===L?m.insertBefore(s,m.firstChild):m.appendChild(s),s.outerHTML=l.map(x).join("\n"),m.removeAttribute(d),a()}).catch(c))))})}function X2(c){return Promise.all([V2(c,"::before"),V2(c,"::after")])}function $2(c){return!(c.parentNode===document.head||~o1.indexOf(c.tagName.toUpperCase())||c.getAttribute(e1)||c.parentNode&&"svg"===c.parentNode.tagName)}function G2(c){if(!c)return[];for(var l=new Set,s=[c],a=0,e=[/(?=\s:)/,/(?<=\)\)?[^,]*,)/];a{var l=e[a];s=s.flatMap(function(c){return c.split(l).map(function(c){return c.replace(/,\s*$/,"").trim()})})})();var z,t=T(s=s.flatMap(function(c){return c.includes("(")?c:c.split(",").map(function(c){return c.trim()})}));try{for(t.s();!(z=t.n()).done;){var r,n=z.value;Q2(n)&&""!==(r=f1.reduce(function(c,l){return c.replace(l,"")},n))&&"*"!==r&&l.add(r)}}catch(c){t.e(c)}finally{t.f()}return l}var Q2=function(l){return!!l&&f1.some(function(c){return l.includes(c)})};function Z2(c){var e,l=1, enable searchPseudoElementsFullScan for slower but more thorough DOM parsing, or suppress this warning by setting searchPseudoElementsWarnings to false.'))}}}catch(c){z.e(c)}finally{z.f()}if(!a.size)return;l=Array.from(a).join(", ");try{e=c.querySelectorAll(l)}catch(c){}}return new Promise(function(c,l){var s=w(e).filter($2).map(X2),a=y2.begin("searchPseudoElements");O2(),Promise.all(s).then(function(){a(),E2(),c()}).catch(function(){a(),E2(),l()})})}}function c4(c){return c.toLowerCase().split(" ").reduce(function(c,l){var s=l.toLowerCase().split("-"),a=s[0],e=s.slice(1).join("-");if(a&&"h"===e)c.flipX=!0;else if(a&&"v"===e)c.flipY=!0;else if(e=parseFloat(e),!isNaN(e))switch(a){case"grow":c.size=c.size+e;break;case"shrink":c.size=c.size-e;break;case"left":c.x=c.x-e;break;case"right":c.x=c.x+e;break;case"up":c.y=c.y-e;break;case"down":c.y=c.y+e;break;case"rotate":c.rotate=c.rotate+e}return c},{size:16,x:0,y:0,flipX:!1,flipY:!1,rotate:0})}var l4,s4=!1,a4={x:0,y:0,width:"100%",height:"100%"};function e4(c){return c.attributes&&(c.attributes.fill||(!(1= 1 && month <= 9) { - month = '0' + month; - } - if (day >= 0 && day <= 9) { - day = '0' + day; - } - if (hours >= 0 && hours <= 9) { - hours = '0' + hours; - } - if (minutes >= 0 && minutes <= 9) { - minutes = '0' + minutes; - } - if (seconds >= 0 && seconds <= 9) { - seconds = '0' + seconds; - } - var currentdate = year + '-' + month + '-' + day + " " + hours + ":" + minutes + ":" + seconds; - return currentdate; -} - -var random = function () { - return parseInt(Math.random() * 10000) + (new Date()).valueOf(); -}; - -var loadScript = function (url, cb) { - var script = document.createElement("script"); - script.charset = "UTF-8"; - script.async = true; - - // 对geetestçš„é™æ€èµ„æºæ·»åŠ crossOrigin - if ( /static\.geetest\.com/g.test(url)) { - script.crossOrigin = "anonymous"; - } - - script.onerror = function () { - cb(true); - }; - var loaded = false; - script.onload = script.onreadystatechange = function () { - if (!loaded && - (!script.readyState || - "loaded" === script.readyState || - "complete" === script.readyState)) { - - loaded = true; - setTimeout(function () { - cb(false); - }, 0); - } - }; - script.src = url; - head.appendChild(script); -}; - -var normalizeDomain = function (domain) { - // special domain: uems.sysu.edu.cn/jwxt/geetest/ - // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn - return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest -}; -var normalizePath = function (path) { - path = path.replace(/\/+/g, '/'); - if (path.indexOf('/') !== 0) { - path = '/' + path; - } - return path; -}; -var normalizeQuery = function (query) { - if (!query) { - return ''; - } - var q = '?'; - new _Object(query)._each(function (key, value) { - if (isString(value) || isNumber(value) || isBoolean(value)) { - q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; - } - }); - if (q === '?') { - q = ''; - } - return q.replace(/&$/, ''); -}; -var makeURL = function (protocol, domain, path, query) { - domain = normalizeDomain(domain); - - var url = normalizePath(path) + normalizeQuery(query); - if (domain) { - url = protocol + domain + url; - } - - return url; -}; - -var load = function (config, send, protocol, domains, path, query, cb) { - var tryRequest = function (at) { - - var url = makeURL(protocol, domains[at], path, query); - loadScript(url, function (err) { - if (err) { - if (at >= domains.length - 1) { - cb(true); - // report gettype error - if (send) { - config.error_code = 508; - var url = protocol + domains[at] + path; - reportError(config, url); - } - } else { - tryRequest(at + 1); - } - } else { - cb(false); - } - }); - }; - tryRequest(0); -}; - - -var jsonp = function (domains, path, config, callback) { - if (isObject(config.getLib)) { - config._extend(config.getLib); - callback(config); - return; - } - if (config.offline) { - callback(config._get_fallback_config()); - return; - } - - var cb = "geetest_" + random(); - window[cb] = function (data) { - if (data.status == 'success') { - callback(data.data); - } else if (!data.status) { - callback(data); - } else { - callback(config._get_fallback_config()); - } - window[cb] = undefined; - try { - delete window[cb]; - } catch (e) { - } - }; - load(config, true, config.protocol, domains, path, { - gt: config.gt, - callback: cb - }, function (err) { - if (err) { - callback(config._get_fallback_config()); - } - }); -}; - -var reportError = function (config, url) { - load(config, false, config.protocol, ['monitor.geetest.com'], '/monitor/send', { - time: nowDate(), - captcha_id: config.gt, - challenge: config.challenge, - pt: pt, - exception_url: url, - error_code: config.error_code - }, function (err) {}) -} - -var throwError = function (errorType, config) { - var errors = { - networkError: '网络错误', - gtTypeError: 'gt字段不是字符串类型' - }; - if (typeof config.onError === 'function') { - config.onError(errors[errorType]); - } else { - throw new Error(errors[errorType]); - } -}; - -var detect = function () { - return window.Geetest || document.getElementById("gt_lib"); -}; - -if (detect()) { - status.slide = "loaded"; -} - -window.initGeetest = function (userConfig, callback) { - - var config = new Config(userConfig); - - if (userConfig.https) { - config.protocol = 'https://'; - } else if (!userConfig.protocol) { - config.protocol = window.location.protocol + '//'; - } - - // for KFC - if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || - userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { - config.apiserver = 'yumchina.geetest.com/'; // for old js - config.api_server = 'yumchina.geetest.com'; - } - - if(userConfig.gt){ - window.GeeGT = userConfig.gt - } - - if(userConfig.challenge){ - window.GeeChallenge = userConfig.challenge - } - - if (isObject(userConfig.getType)) { - config._extend(userConfig.getType); - } - jsonp((config.api_server_v3 || [config.api_server || config.apiserver]), config.typePath, config, function (newConfig) { - var type = newConfig.type; - var init = function () { - config._extend(newConfig); - callback(new window.Geetest(config)); - }; - - callbacks[type] = callbacks[type] || []; - var s = status[type] || 'init'; - if (s === 'init') { - status[type] = 'loading'; - - callbacks[type].push(init); - - load(config, true, config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { - if (err) { - status[type] = 'fail'; - throwError('networkError', config); - } else { - status[type] = 'loaded'; - var cbs = callbacks[type]; - for (var i = 0, len = cbs.length; i < len; i = i + 1) { - var cb = cbs[i]; - if (isFunction(cb)) { - cb(); - } - } - callbacks[type] = []; - } - }); - } else if (s === "loaded") { - init(); - } else if (s === "fail") { - throwError('networkError', config); - } else if (s === "loading") { - callbacks[type].push(init); - } - }); - -}; - - -})(window); +"v0.5.0 Geetest Inc."; + +(function (window) { + "use strict"; + if (typeof window === 'undefined') { + throw new Error('Geetest requires browser environment'); + } + +var document = window.document; +var Math = window.Math; +var head = document.getElementsByTagName("head")[0]; + +function _Object(obj) { + this._obj = obj; +} + +_Object.prototype = { + _each: function (process) { + var _obj = this._obj; + for (var k in _obj) { + if (_obj.hasOwnProperty(k)) { + process(k, _obj[k]); + } + } + return this; + } +}; + +function Config(config) { + var self = this; + new _Object(config)._each(function (key, value) { + self[key] = value; + }); +} + +Config.prototype = { + api_server: 'api.geetest.com', + protocol: 'http://', + typePath: '/gettype.php', + fallback_config: { + slide: { + static_servers: ["static.geetest.com", "static.geevisit.com"], + type: 'slide', + slide: '/static/js/geetest.0.0.0.js' + }, + fullpage: { + static_servers: ["static.geetest.com", "static.geevisit.com"], + type: 'fullpage', + fullpage: '/static/js/fullpage.0.0.0.js' + } + }, + _get_fallback_config: function () { + var self = this; + if (isString(self.type)) { + return self.fallback_config[self.type]; + } else if (self.new_captcha) { + return self.fallback_config.fullpage; + } else { + return self.fallback_config.slide; + } + }, + _extend: function (obj) { + var self = this; + new _Object(obj)._each(function (key, value) { + self[key] = value; + }) + } +}; +var isNumber = function (value) { + return (typeof value === 'number'); +}; +var isString = function (value) { + return (typeof value === 'string'); +}; +var isBoolean = function (value) { + return (typeof value === 'boolean'); +}; +var isObject = function (value) { + return (typeof value === 'object' && value !== null); +}; +var isFunction = function (value) { + return (typeof value === 'function'); +}; +var MOBILE = /Mobi/i.test(navigator.userAgent); +var pt = MOBILE ? 3 : 0; + +var callbacks = {}; +var status = {}; + +var nowDate = function () { + var date = new Date(); + var year = date.getFullYear(); + var month = date.getMonth() + 1; + var day = date.getDate(); + var hours = date.getHours(); + var minutes = date.getMinutes(); + var seconds = date.getSeconds(); + + if (month >= 1 && month <= 9) { + month = '0' + month; + } + if (day >= 0 && day <= 9) { + day = '0' + day; + } + if (hours >= 0 && hours <= 9) { + hours = '0' + hours; + } + if (minutes >= 0 && minutes <= 9) { + minutes = '0' + minutes; + } + if (seconds >= 0 && seconds <= 9) { + seconds = '0' + seconds; + } + var currentdate = year + '-' + month + '-' + day + " " + hours + ":" + minutes + ":" + seconds; + return currentdate; +} + +var random = function () { + return parseInt(Math.random() * 10000) + (new Date()).valueOf(); +}; + +var loadScript = function (url, cb) { + var script = document.createElement("script"); + script.charset = "UTF-8"; + script.async = true; + + // 对geetestçš„é™æ€èµ„æºæ·»åŠ crossOrigin + if ( /static\.geetest\.com/g.test(url)) { + script.crossOrigin = "anonymous"; + } + + script.onerror = function () { + cb(true); + }; + var loaded = false; + script.onload = script.onreadystatechange = function () { + if (!loaded && + (!script.readyState || + "loaded" === script.readyState || + "complete" === script.readyState)) { + + loaded = true; + setTimeout(function () { + cb(false); + }, 0); + } + }; + script.src = url; + head.appendChild(script); +}; + +var normalizeDomain = function (domain) { + // special domain: uems.sysu.edu.cn/jwxt/geetest/ + // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn + return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest +}; +var normalizePath = function (path) { + path = path.replace(/\/+/g, '/'); + if (path.indexOf('/') !== 0) { + path = '/' + path; + } + return path; +}; +var normalizeQuery = function (query) { + if (!query) { + return ''; + } + var q = '?'; + new _Object(query)._each(function (key, value) { + if (isString(value) || isNumber(value) || isBoolean(value)) { + q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; + } + }); + if (q === '?') { + q = ''; + } + return q.replace(/&$/, ''); +}; +var makeURL = function (protocol, domain, path, query) { + domain = normalizeDomain(domain); + + var url = normalizePath(path) + normalizeQuery(query); + if (domain) { + url = protocol + domain + url; + } + + return url; +}; + +var load = function (config, send, protocol, domains, path, query, cb) { + var tryRequest = function (at) { + + var url = makeURL(protocol, domains[at], path, query); + loadScript(url, function (err) { + if (err) { + if (at >= domains.length - 1) { + cb(true); + // report gettype error + if (send) { + config.error_code = 508; + var url = protocol + domains[at] + path; + reportError(config, url); + } + } else { + tryRequest(at + 1); + } + } else { + cb(false); + } + }); + }; + tryRequest(0); +}; + + +var jsonp = function (domains, path, config, callback) { + if (isObject(config.getLib)) { + config._extend(config.getLib); + callback(config); + return; + } + if (config.offline) { + callback(config._get_fallback_config()); + return; + } + + var cb = "geetest_" + random(); + window[cb] = function (data) { + if (data.status == 'success') { + callback(data.data); + } else if (!data.status) { + callback(data); + } else { + callback(config._get_fallback_config()); + } + window[cb] = undefined; + try { + delete window[cb]; + } catch (e) { + } + }; + load(config, true, config.protocol, domains, path, { + gt: config.gt, + callback: cb + }, function (err) { + if (err) { + callback(config._get_fallback_config()); + } + }); +}; + +var reportError = function (config, url) { + load(config, false, config.protocol, ['monitor.geetest.com'], '/monitor/send', { + time: nowDate(), + captcha_id: config.gt, + challenge: config.challenge, + pt: pt, + exception_url: url, + error_code: config.error_code + }, function (err) {}) +} + +var throwError = function (errorType, config) { + var errors = { + networkError: '网络错误', + gtTypeError: 'gt字段不是字符串类型' + }; + if (typeof config.onError === 'function') { + config.onError(errors[errorType]); + } else { + throw new Error(errors[errorType]); + } +}; + +var detect = function () { + return window.Geetest || document.getElementById("gt_lib"); +}; + +if (detect()) { + status.slide = "loaded"; +} + +window.initGeetest = function (userConfig, callback) { + + var config = new Config(userConfig); + + if (userConfig.https) { + config.protocol = 'https://'; + } else if (!userConfig.protocol) { + config.protocol = window.location.protocol + '//'; + } + + // for KFC + if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || + userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { + config.apiserver = 'yumchina.geetest.com/'; // for old js + config.api_server = 'yumchina.geetest.com'; + } + + if(userConfig.gt){ + window.GeeGT = userConfig.gt + } + + if(userConfig.challenge){ + window.GeeChallenge = userConfig.challenge + } + + if (isObject(userConfig.getType)) { + config._extend(userConfig.getType); + } + jsonp((config.api_server_v3 || [config.api_server || config.apiserver]), config.typePath, config, function (newConfig) { + var type = newConfig.type; + var init = function () { + config._extend(newConfig); + callback(new window.Geetest(config)); + }; + + callbacks[type] = callbacks[type] || []; + var s = status[type] || 'init'; + if (s === 'init') { + status[type] = 'loading'; + + callbacks[type].push(init); + + load(config, true, config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { + if (err) { + status[type] = 'fail'; + throwError('networkError', config); + } else { + status[type] = 'loaded'; + var cbs = callbacks[type]; + for (var i = 0, len = cbs.length; i < len; i = i + 1) { + var cb = cbs[i]; + if (isFunction(cb)) { + cb(); + } + } + callbacks[type] = []; + } + }); + } else if (s === "loaded") { + init(); + } else if (s === "fail") { + throwError('networkError', config); + } else if (s === "loading") { + callbacks[type].push(init); + } + }); + +}; + + +})(window); diff --git a/platform/src/assets/js/gt4.js b/platform/src/assets/js/gt4.js index 6b2f6ae..7d63a3e 100644 --- a/platform/src/assets/js/gt4.js +++ b/platform/src/assets/js/gt4.js @@ -1,487 +1,487 @@ -"v4.2.0 Geetest Inc."; - -(function (window) { - "use strict"; - if (typeof window === 'undefined') { - throw new Error('Geetest requires browser environment'); - } - -var document = window.document; -var Math = window.Math; -var head = document.getElementsByTagName("head")[0]; -var TIMEOUT = 10000; - -function _Object(obj) { - this._obj = obj; -} - -_Object.prototype = { - _each: function (process) { - var _obj = this._obj; - for (var k in _obj) { - if (_obj.hasOwnProperty(k)) { - process(k, _obj[k]); - } - } - return this; - }, - _extend: function (obj){ - var self = this; - new _Object(obj)._each(function (key, value){ - self._obj[key] = value; - }) - } -}; - -var uuid = function () { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0; - var v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - }; - -function Config(config) { - var self = this; - new _Object(config)._each(function (key, value) { - self[key] = value; - }); -} - -Config.prototype = { - apiServers: ['gcaptcha4.geetest.com','gcaptcha4.geevisit.com','gcaptcha4.gsensebot.com'], - staticServers: ["static.geetest.com",'static.geevisit.com'], - protocol: 'http://', - typePath: '/load', - fallback_config: { - bypass: { - staticServers: ["static.geetest.com",'static.geevisit.com'], - type: 'bypass', - bypass: '/v4/bypass.js' - } - }, - _get_fallback_config: function () { - var self = this; - if (isString(self.type)) { - return self.fallback_config[self.type]; - } else { - return self.fallback_config.bypass; - } - }, - _extend: function (obj) { - var self = this; - new _Object(obj)._each(function (key, value) { - self[key] = value; - }) - } -}; -var isNumber = function (value) { - return (typeof value === 'number'); -}; -var isString = function (value) { - return (typeof value === 'string'); -}; -var isBoolean = function (value) { - return (typeof value === 'boolean'); -}; -var isObject = function (value) { - return (typeof value === 'object' && value !== null); -}; -var isFunction = function (value) { - return (typeof value === 'function'); -}; -var MOBILE = /Mobi/i.test(navigator.userAgent); - -var callbacks = {}; -var status = {}; - -var random = function () { - return parseInt(Math.random() * 10000) + (new Date()).valueOf(); -}; - -// bind 函数polify, 不带new功能的bind - -var bind = function(target,context){ - if(typeof target !== 'function'){ - return; - } - var args = Array.prototype.slice.call(arguments,2); - - if(Function.prototype.bind){ - return target.bind(context, args); - }else { - return function(){ - var _args = Array.prototype.slice.call(arguments); - return target.apply(context,args.concat(_args)); - } - } -} - - - -var toString = Object.prototype.toString; - -var _isFunction = function(obj) { - return typeof(obj) === 'function'; -}; -var _isObject = function(obj) { - return obj === Object(obj); -}; -var _isArray = function(obj) { - return toString.call(obj) == '[object Array]'; -}; -var _isDate = function(obj) { - return toString.call(obj) == '[object Date]'; -}; -var _isRegExp = function(obj) { - return toString.call(obj) == '[object RegExp]'; -}; -var _isBoolean = function(obj) { - return toString.call(obj) == '[object Boolean]'; -}; - - -function resolveKey(input){ - return input.replace(/(\S)(_([a-zA-Z]))/g, function(match, $1, $2, $3){ - return $1 + $3.toUpperCase() || ""; - }) -} - -function camelizeKeys(input, convert){ - if(!_isObject(input) || _isDate(input) || _isRegExp(input) || _isBoolean(input) || _isFunction(input)){ - return convert ? resolveKey(input) : input; - } - - if(_isArray(input)){ - var temp = []; - for(var i = 0; i < input.length; i++){ - temp.push(camelizeKeys(input[i])); - } - - }else { - var temp = {}; - for(var prop in input){ - if(input.hasOwnProperty(prop)){ - temp[camelizeKeys(prop, true)] = camelizeKeys(input[prop]); - } - } - } - return temp; -} - -var loadScript = function (url, cb, timeout) { - var script = document.createElement("script"); - script.charset = "UTF-8"; - script.async = true; - - // 对geetestçš„é™æ€èµ„æºæ·»åŠ crossOrigin - if ( /static\.geetest\.com/g.test(url)) { - script.crossOrigin = "anonymous"; - } - - script.onerror = function () { - cb(true); - // 错误触发了,超时逻辑就不用了 - loaded = true; - }; - var loaded = false; - script.onload = script.onreadystatechange = function () { - if (!loaded && - (!script.readyState || - "loaded" === script.readyState || - "complete" === script.readyState)) { - - loaded = true; - setTimeout(function () { - cb(false); - }, 0); - } - }; - script.src = url; - head.appendChild(script); - - setTimeout(function () { - if (!loaded) { - script.onerror = script.onload = null; - script.remove && script.remove(); - cb(true); - } - }, timeout || TIMEOUT); -}; - -var normalizeDomain = function (domain) { - // special domain: uems.sysu.edu.cn/jwxt/geetest/ - // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn - return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest -}; -var normalizePath = function (path) { - - path = path && path.replace(/\/+/g, '/'); - if (path.indexOf('/') !== 0) { - path = '/' + path; - } - return path; -}; -var normalizeQuery = function (query) { - if (!query) { - return ''; - } - var q = '?'; - new _Object(query)._each(function (key, value) { - if (isString(value) || isNumber(value) || isBoolean(value)) { - q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; - } - }); - if (q === '?') { - q = ''; - } - return q.replace(/&$/, ''); -}; -var makeURL = function (protocol, domain, path, query) { - domain = normalizeDomain(domain); - - var url = normalizePath(path) + normalizeQuery(query); - if (domain) { - url = protocol + domain + url; - } - - return url; -}; - -var load = function (config, protocol, domains, path, query, cb, handleCb) { - var tryRequest = function (at) { - // 处理jsonp回调,这里为了保证每个不同jsonp都有唯一的回调函数 - if(handleCb){ - var cbName = "geetest_" + random(); - // 需要与预先定义好cbnameå‚æ•°ï¼Œåˆ é™¤å¯¹è±¡ - window[cbName] = bind(handleCb, null, cbName); - query.callback = cbName; - } - var url = makeURL(protocol, domains[at], path, query); - loadScript(url, function (err) { - if (err) { - // 超时或者出错的时候 移除回调 - if(cbName){ - try { - window[cbName] = function(){ - window[cbName] = null; - } - } catch (e) {} - } - - if (at >= domains.length - 1) { - cb(true); - // report gettype error - } else { - tryRequest(at + 1); - } - } else { - cb(false); - } - }, config.timeout); - }; - tryRequest(0); -}; - - -var jsonp = function (domains, path, config, callback) { - - var handleCb = function (cbName, data) { - - // 保证只执行一次,全部超时的情况下不会再触发; - - if (data.status == 'success') { - callback(data.data); - } else if (!data.status) { - callback(data); - } else { - //接口有返回,但是返回了错误状态,进入报错逻辑 - callback(data); - } - window[cbName] = undefined; - try { - delete window[cbName]; - } catch (e) { - } - }; - load(config, config.protocol, domains, path, { - callback: '', - captcha_id: config.captchaId, - challenge: config.challenge || uuid(), - client_type: config.clientType ? config.clientType : (MOBILE? 'h5':'web'), - risk_type: config.riskType, - user_info: config.userInfo, - call_type: config.callType, - lang: config.language? config.language : navigator.appName === 'Netscape' ? navigator.language.toLowerCase() : navigator.userLanguage.toLowerCase() - }, function (err) { - // ç½‘ç»œé—®é¢˜æŽ¥å£æ²¡æœ‰è¿”å›žï¼Œç›´æŽ¥ä½¿ç”¨æœ¬åœ°éªŒè¯ç ï¼Œèµ°å®•æœºæ¨¡å¼ - // è¿™é‡Œå¯ä»¥æ·»åŠ ç”¨æˆ·çš„é€»è¾‘ - if(err && typeof config.offlineCb === 'function'){ - // 执行自己的宕机 - config.offlineCb(); - return; - } - if(err){ - callback(config._get_fallback_config()); - } - }, handleCb); -}; - -var reportError = function (config, url) { - load(config, config.protocol, ['monitor.geetest.com'], '/monitor/send', { - time: Date.now().getTime(), - captcha_id: config.gt, - challenge: config.challenge, - exception_url: url, - error_code: config.error_code - }, function (err) {}) -} - -var throwError = function (errorType, config, errObj) { - var errors = { - networkError: '网络错误', - gtTypeError: 'gt字段不是字符串类型' - }; - if (typeof config.onError === 'function') { - config.onError({ - desc: errObj.desc, - msg: errObj.msg, - code: errObj.code - }); - } else { - throw new Error(errors[errorType]); - } -}; - -var detect = function () { - return window.Geetest || document.getElementById("gt_lib"); -}; - -if (detect()) { - status.slide = "loaded"; -} -var GeetestIsLoad = function (fname) { - var GeetestIsLoad = false; - var tags = { js: 'script', css: 'link' }; - var tagname = fname && tags[fname.split('.').pop()]; - if (tagname !== undefined) { - var elts = document.getElementsByTagName(tagname); - for (var i in elts) { - if ((elts[i].href && elts[i].href.toString().indexOf(fname) > 0) - || (elts[i].src && elts[i].src.toString().indexOf(fname) > 0)) { - GeetestIsLoad = true; - } - } - } - return GeetestIsLoad; -}; -window.initGeetest4 = function (userConfig,callback) { - - var config = new Config(userConfig); - if (userConfig.https) { - config.protocol = 'https://'; - } else if (!userConfig.protocol) { - config.protocol = window.location.protocol + '//'; - } - - - if (isObject(userConfig.getType)) { - config._extend(userConfig.getType); - } - - jsonp(config.apiServers , config.typePath, config, function (newConfig) { - //错误捕获,第一个load请求可能直接报错 - var newConfig = camelizeKeys(newConfig); - - if(newConfig.status === 'error'){ - return throwError('networkError', config, newConfig); - } - - var type = newConfig.type; - if(config.debug){ - new _Object(newConfig)._extend(config.debug) - } - var init = function () { - config._extend(newConfig); - callback(new window.Geetest4(config)); - }; - - callbacks[type] = callbacks[type] || []; - - var s = status[type] || 'init'; - if (s === 'init') { - status[type] = 'loading'; - - callbacks[type].push(init); - - if(newConfig.gctPath){ - load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ - if(err){ - throwError('networkError', config, { - code: '60205', - msg: 'Network failure', - desc: { - detail: 'gct resource load timeout' - } - }); - } - }) - } - - load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers, newConfig.bypass || (newConfig.staticPath + newConfig.js), null, function (err) { - if (err) { - status[type] = 'fail'; - throwError('networkError', config, { - code: '60204', - msg: 'Network failure', - desc: { - detail: 'js resource load timeout' - } - }); - } else { - - status[type] = 'loaded'; - var cbs = callbacks[type]; - for (var i = 0, len = cbs.length; i < len; i = i + 1) { - var cb = cbs[i]; - if (isFunction(cb)) { - cb(); - } - } - callbacks[type] = []; - status[type] = 'init'; - } - }); - } else if (s === "loaded") { - // 判断gctæ˜¯å¦éœ€è¦é‡æ–°åŠ è½½ - if(newConfig.gctPath && !GeetestIsLoad(newConfig.gctPath)){ - load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ - if(err){ - throwError('networkError', config, { - code: '60205', - msg: 'Network failure', - desc: { - detail: 'gct resource load timeout' - } - }); - } - }) - } - return init(); - } else if (s === "fail") { - throwError('networkError', config, { - code: '60204', - msg: 'Network failure', - desc: { - detail: 'js resource load timeout' - } - }); - } else if (s === "loading") { - callbacks[type].push(init); - } - }); - -}; - - -})(window); +"v4.2.0 Geetest Inc."; + +(function (window) { + "use strict"; + if (typeof window === 'undefined') { + throw new Error('Geetest requires browser environment'); + } + +var document = window.document; +var Math = window.Math; +var head = document.getElementsByTagName("head")[0]; +var TIMEOUT = 10000; + +function _Object(obj) { + this._obj = obj; +} + +_Object.prototype = { + _each: function (process) { + var _obj = this._obj; + for (var k in _obj) { + if (_obj.hasOwnProperty(k)) { + process(k, _obj[k]); + } + } + return this; + }, + _extend: function (obj){ + var self = this; + new _Object(obj)._each(function (key, value){ + self._obj[key] = value; + }) + } +}; + +var uuid = function () { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0; + var v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }; + +function Config(config) { + var self = this; + new _Object(config)._each(function (key, value) { + self[key] = value; + }); +} + +Config.prototype = { + apiServers: ['gcaptcha4.geetest.com','gcaptcha4.geevisit.com','gcaptcha4.gsensebot.com'], + staticServers: ["static.geetest.com",'static.geevisit.com'], + protocol: 'http://', + typePath: '/load', + fallback_config: { + bypass: { + staticServers: ["static.geetest.com",'static.geevisit.com'], + type: 'bypass', + bypass: '/v4/bypass.js' + } + }, + _get_fallback_config: function () { + var self = this; + if (isString(self.type)) { + return self.fallback_config[self.type]; + } else { + return self.fallback_config.bypass; + } + }, + _extend: function (obj) { + var self = this; + new _Object(obj)._each(function (key, value) { + self[key] = value; + }) + } +}; +var isNumber = function (value) { + return (typeof value === 'number'); +}; +var isString = function (value) { + return (typeof value === 'string'); +}; +var isBoolean = function (value) { + return (typeof value === 'boolean'); +}; +var isObject = function (value) { + return (typeof value === 'object' && value !== null); +}; +var isFunction = function (value) { + return (typeof value === 'function'); +}; +var MOBILE = /Mobi/i.test(navigator.userAgent); + +var callbacks = {}; +var status = {}; + +var random = function () { + return parseInt(Math.random() * 10000) + (new Date()).valueOf(); +}; + +// bind 函数polify, 不带new功能的bind + +var bind = function(target,context){ + if(typeof target !== 'function'){ + return; + } + var args = Array.prototype.slice.call(arguments,2); + + if(Function.prototype.bind){ + return target.bind(context, args); + }else { + return function(){ + var _args = Array.prototype.slice.call(arguments); + return target.apply(context,args.concat(_args)); + } + } +} + + + +var toString = Object.prototype.toString; + +var _isFunction = function(obj) { + return typeof(obj) === 'function'; +}; +var _isObject = function(obj) { + return obj === Object(obj); +}; +var _isArray = function(obj) { + return toString.call(obj) == '[object Array]'; +}; +var _isDate = function(obj) { + return toString.call(obj) == '[object Date]'; +}; +var _isRegExp = function(obj) { + return toString.call(obj) == '[object RegExp]'; +}; +var _isBoolean = function(obj) { + return toString.call(obj) == '[object Boolean]'; +}; + + +function resolveKey(input){ + return input.replace(/(\S)(_([a-zA-Z]))/g, function(match, $1, $2, $3){ + return $1 + $3.toUpperCase() || ""; + }) +} + +function camelizeKeys(input, convert){ + if(!_isObject(input) || _isDate(input) || _isRegExp(input) || _isBoolean(input) || _isFunction(input)){ + return convert ? resolveKey(input) : input; + } + + if(_isArray(input)){ + var temp = []; + for(var i = 0; i < input.length; i++){ + temp.push(camelizeKeys(input[i])); + } + + }else { + var temp = {}; + for(var prop in input){ + if(input.hasOwnProperty(prop)){ + temp[camelizeKeys(prop, true)] = camelizeKeys(input[prop]); + } + } + } + return temp; +} + +var loadScript = function (url, cb, timeout) { + var script = document.createElement("script"); + script.charset = "UTF-8"; + script.async = true; + + // 对geetestçš„é™æ€èµ„æºæ·»åŠ crossOrigin + if ( /static\.geetest\.com/g.test(url)) { + script.crossOrigin = "anonymous"; + } + + script.onerror = function () { + cb(true); + // 错误触发了,超时逻辑就不用了 + loaded = true; + }; + var loaded = false; + script.onload = script.onreadystatechange = function () { + if (!loaded && + (!script.readyState || + "loaded" === script.readyState || + "complete" === script.readyState)) { + + loaded = true; + setTimeout(function () { + cb(false); + }, 0); + } + }; + script.src = url; + head.appendChild(script); + + setTimeout(function () { + if (!loaded) { + script.onerror = script.onload = null; + script.remove && script.remove(); + cb(true); + } + }, timeout || TIMEOUT); +}; + +var normalizeDomain = function (domain) { + // special domain: uems.sysu.edu.cn/jwxt/geetest/ + // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn + return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest +}; +var normalizePath = function (path) { + + path = path && path.replace(/\/+/g, '/'); + if (path.indexOf('/') !== 0) { + path = '/' + path; + } + return path; +}; +var normalizeQuery = function (query) { + if (!query) { + return ''; + } + var q = '?'; + new _Object(query)._each(function (key, value) { + if (isString(value) || isNumber(value) || isBoolean(value)) { + q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; + } + }); + if (q === '?') { + q = ''; + } + return q.replace(/&$/, ''); +}; +var makeURL = function (protocol, domain, path, query) { + domain = normalizeDomain(domain); + + var url = normalizePath(path) + normalizeQuery(query); + if (domain) { + url = protocol + domain + url; + } + + return url; +}; + +var load = function (config, protocol, domains, path, query, cb, handleCb) { + var tryRequest = function (at) { + // 处理jsonp回调,这里为了保证每个不同jsonp都有唯一的回调函数 + if(handleCb){ + var cbName = "geetest_" + random(); + // 需要与预先定义好cbnameå‚æ•°ï¼Œåˆ é™¤å¯¹è±¡ + window[cbName] = bind(handleCb, null, cbName); + query.callback = cbName; + } + var url = makeURL(protocol, domains[at], path, query); + loadScript(url, function (err) { + if (err) { + // 超时或者出错的时候 移除回调 + if(cbName){ + try { + window[cbName] = function(){ + window[cbName] = null; + } + } catch (e) {} + } + + if (at >= domains.length - 1) { + cb(true); + // report gettype error + } else { + tryRequest(at + 1); + } + } else { + cb(false); + } + }, config.timeout); + }; + tryRequest(0); +}; + + +var jsonp = function (domains, path, config, callback) { + + var handleCb = function (cbName, data) { + + // 保证只执行一次,全部超时的情况下不会再触发; + + if (data.status == 'success') { + callback(data.data); + } else if (!data.status) { + callback(data); + } else { + //接口有返回,但是返回了错误状态,进入报错逻辑 + callback(data); + } + window[cbName] = undefined; + try { + delete window[cbName]; + } catch (e) { + } + }; + load(config, config.protocol, domains, path, { + callback: '', + captcha_id: config.captchaId, + challenge: config.challenge || uuid(), + client_type: config.clientType ? config.clientType : (MOBILE? 'h5':'web'), + risk_type: config.riskType, + user_info: config.userInfo, + call_type: config.callType, + lang: config.language? config.language : navigator.appName === 'Netscape' ? navigator.language.toLowerCase() : navigator.userLanguage.toLowerCase() + }, function (err) { + // ç½‘ç»œé—®é¢˜æŽ¥å£æ²¡æœ‰è¿”å›žï¼Œç›´æŽ¥ä½¿ç”¨æœ¬åœ°éªŒè¯ç ï¼Œèµ°å®•æœºæ¨¡å¼ + // è¿™é‡Œå¯ä»¥æ·»åŠ ç”¨æˆ·çš„é€»è¾‘ + if(err && typeof config.offlineCb === 'function'){ + // 执行自己的宕机 + config.offlineCb(); + return; + } + if(err){ + callback(config._get_fallback_config()); + } + }, handleCb); +}; + +var reportError = function (config, url) { + load(config, config.protocol, ['monitor.geetest.com'], '/monitor/send', { + time: Date.now().getTime(), + captcha_id: config.gt, + challenge: config.challenge, + exception_url: url, + error_code: config.error_code + }, function (err) {}) +} + +var throwError = function (errorType, config, errObj) { + var errors = { + networkError: '网络错误', + gtTypeError: 'gt字段不是字符串类型' + }; + if (typeof config.onError === 'function') { + config.onError({ + desc: errObj.desc, + msg: errObj.msg, + code: errObj.code + }); + } else { + throw new Error(errors[errorType]); + } +}; + +var detect = function () { + return window.Geetest || document.getElementById("gt_lib"); +}; + +if (detect()) { + status.slide = "loaded"; +} +var GeetestIsLoad = function (fname) { + var GeetestIsLoad = false; + var tags = { js: 'script', css: 'link' }; + var tagname = fname && tags[fname.split('.').pop()]; + if (tagname !== undefined) { + var elts = document.getElementsByTagName(tagname); + for (var i in elts) { + if ((elts[i].href && elts[i].href.toString().indexOf(fname) > 0) + || (elts[i].src && elts[i].src.toString().indexOf(fname) > 0)) { + GeetestIsLoad = true; + } + } + } + return GeetestIsLoad; +}; +window.initGeetest4 = function (userConfig,callback) { + + var config = new Config(userConfig); + if (userConfig.https) { + config.protocol = 'https://'; + } else if (!userConfig.protocol) { + config.protocol = window.location.protocol + '//'; + } + + + if (isObject(userConfig.getType)) { + config._extend(userConfig.getType); + } + + jsonp(config.apiServers , config.typePath, config, function (newConfig) { + //错误捕获,第一个load请求可能直接报错 + var newConfig = camelizeKeys(newConfig); + + if(newConfig.status === 'error'){ + return throwError('networkError', config, newConfig); + } + + var type = newConfig.type; + if(config.debug){ + new _Object(newConfig)._extend(config.debug) + } + var init = function () { + config._extend(newConfig); + callback(new window.Geetest4(config)); + }; + + callbacks[type] = callbacks[type] || []; + + var s = status[type] || 'init'; + if (s === 'init') { + status[type] = 'loading'; + + callbacks[type].push(init); + + if(newConfig.gctPath){ + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ + if(err){ + throwError('networkError', config, { + code: '60205', + msg: 'Network failure', + desc: { + detail: 'gct resource load timeout' + } + }); + } + }) + } + + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers, newConfig.bypass || (newConfig.staticPath + newConfig.js), null, function (err) { + if (err) { + status[type] = 'fail'; + throwError('networkError', config, { + code: '60204', + msg: 'Network failure', + desc: { + detail: 'js resource load timeout' + } + }); + } else { + + status[type] = 'loaded'; + var cbs = callbacks[type]; + for (var i = 0, len = cbs.length; i < len; i = i + 1) { + var cb = cbs[i]; + if (isFunction(cb)) { + cb(); + } + } + callbacks[type] = []; + status[type] = 'init'; + } + }); + } else if (s === "loaded") { + // 判断gctæ˜¯å¦éœ€è¦é‡æ–°åŠ è½½ + if(newConfig.gctPath && !GeetestIsLoad(newConfig.gctPath)){ + load(config, config.protocol, Object.hasOwnProperty.call(config, 'staticServers') ? config.staticServers : newConfig.staticServers || config.staticServers , newConfig.gctPath, null, function (err){ + if(err){ + throwError('networkError', config, { + code: '60205', + msg: 'Network failure', + desc: { + detail: 'gct resource load timeout' + } + }); + } + }) + } + return init(); + } else if (s === "fail") { + throwError('networkError', config, { + code: '60204', + msg: 'Network failure', + desc: { + detail: 'js resource load timeout' + } + }); + } else if (s === "loading") { + callbacks[type].push(init); + } + }); + +}; + + +})(window); diff --git a/platform/src/assets/less/index.less b/platform/src/assets/less/index.less index 21ba6db..d81a23b 100644 --- a/platform/src/assets/less/index.less +++ b/platform/src/assets/less/index.less @@ -1,2 +1,2 @@ -@import './reset.less'; +@import './reset.less'; @import './style.less'; \ No newline at end of file diff --git a/platform/src/assets/less/reset.less b/platform/src/assets/less/reset.less index bc30922..dc2365a 100644 --- a/platform/src/assets/less/reset.less +++ b/platform/src/assets/less/reset.less @@ -1,192 +1,192 @@ -// reset.less - 现代 CSS 样式重置 -// 统一盒模型为 border-box -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -// 基础字体与颜色设置 -html { - // 基础字体大小 (1rem = 16px) - font-size: 16px; - // 平滑滚动 - scroll-behavior: smooth; - height: 100%; - width: 100%; -} - -body { - // 继承父级字体设置 - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; - font-size: 1rem; - line-height: 1.5; // 舒适行高 - color: #333; // 基础文本色 - // background-color: #fff; // 基础背景色 - -webkit-text-size-adjust: 100%; // 防止iOS横屏字体放大 - height: 100%; - width: 100%; - margin: 0; - padding: 0; -} - -// 移除默认边框 -img, -iframe, -embed, -object, -video { - border: 0; -} - -// 图片与媒体元素自适应 -img, -svg, -video, -canvas, -audio, -iframe, -embed, -object { - display: block; - max-width: 100%; - height: auto; -} - -// 表格重置 -table { - border-collapse: collapse; - border-spacing: 0; - width: 100%; -} - -// 列表样式重置 -ul, -ol, -li { - list-style: none; -} - -// 文本元素重置 -a { - color: inherit; // 继承父级颜色 - text-decoration: none; - background-color: transparent; -} - -a:hover, -a:focus { - outline: none; -} - -// 标题元素重置 -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: inherit; - font-weight: inherit; - margin: 0; -} - -// 表单元素重置 -button, -input, -optgroup, -select, -textarea { - font-family: inherit; - font-size: 100%; - line-height: 1.15; - margin: 0; - padding: 0; - border: none; - background: transparent; - color: inherit; -} - -button, -input { - overflow: visible; -} - -button, -select { - text-transform: none; -} - -// 按钮样式重置 -button, -[type="button"], -[type="reset"], -[type="submit"] { - -webkit-appearance: button; - cursor: pointer; -} - -button::-moz-focus-inner, -[type="button"]::-moz-focus-inner, -[type="reset"]::-moz-focus-inner, -[type="submit"]::-moz-focus-inner { - border-style: none; - padding: 0; -} - -// 输入框聚焦样式 -input:focus, -select:focus, -textarea:focus, -button:focus { - outline: none; -} - -// 文本区域不允许拖拽调整大小 -textarea { - overflow: auto; - resize: vertical; // 仅允许垂直调整 -} - -// 移除占位符默认样式 -::-webkit-input-placeholder { - color: #999; - opacity: 1; -} - -::-moz-placeholder { - color: #999; - opacity: 1; -} - -:-ms-input-placeholder { - color: #999; - opacity: 1; -} - -::placeholder { - color: #999; - opacity: 1; -} - -// 清除浮动 -.clearfix::after { - content: ""; - display: table; - clear: both; -} - -// 隐藏元素(屏幕阅读器可见) -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border-width: 0; +// reset.less - 现代 CSS 样式重置 +// 统一盒模型为 border-box +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +// 基础字体与颜色设置 +html { + // 基础字体大小 (1rem = 16px) + font-size: 16px; + // 平滑滚动 + scroll-behavior: smooth; + height: 100%; + width: 100%; +} + +body { + // 继承父级字体设置 + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; + font-size: 1rem; + line-height: 1.5; // 舒适行高 + color: #333; // 基础文本色 + // background-color: #fff; // 基础背景色 + -webkit-text-size-adjust: 100%; // 防止iOS横屏字体放大 + height: 100%; + width: 100%; + margin: 0; + padding: 0; +} + +// 移除默认边框 +img, +iframe, +embed, +object, +video { + border: 0; +} + +// 图片与媒体元素自适应 +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + max-width: 100%; + height: auto; +} + +// 表格重置 +table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; +} + +// 列表样式重置 +ul, +ol, +li { + list-style: none; +} + +// 文本元素重置 +a { + color: inherit; // 继承父级颜色 + text-decoration: none; + background-color: transparent; +} + +a:hover, +a:focus { + outline: none; +} + +// 标题元素重置 +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; + margin: 0; +} + +// 表单元素重置 +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; + padding: 0; + border: none; + background: transparent; + color: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +// 按钮样式重置 +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +// 输入框聚焦样式 +input:focus, +select:focus, +textarea:focus, +button:focus { + outline: none; +} + +// 文本区域不允许拖拽调整大小 +textarea { + overflow: auto; + resize: vertical; // 仅允许垂直调整 +} + +// 移除占位符默认样式 +::-webkit-input-placeholder { + color: #999; + opacity: 1; +} + +::-moz-placeholder { + color: #999; + opacity: 1; +} + +:-ms-input-placeholder { + color: #999; + opacity: 1; +} + +::placeholder { + color: #999; + opacity: 1; +} + +// 清除浮动 +.clearfix::after { + content: ""; + display: table; + clear: both; +} + +// 隐藏元素(屏幕阅读器可见) +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; } \ No newline at end of file diff --git a/platform/src/assets/less/style.less b/platform/src/assets/less/style.less index 4ecac9f..212f329 100644 --- a/platform/src/assets/less/style.less +++ b/platform/src/assets/less/style.less @@ -1,432 +1,432 @@ -// Element Plus Message z-index -:root { - --el-message-z-index: 9999; -} - -// body 样式 -body { - // background-color: #f5f7fa; - color: #303133; - transition: background-color 0.3s ease, color 0.3s ease; -} - -// container-box 样式 -.container-box { - // background-color: #ffffff; - // border: 1px solid #ebeef5; - padding: 24px; - background-color: var(--el-bg-color); - border: 1px solid var(--el-border-color-lighter); - box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04); - border-radius: 8px; - padding: 24px; - transition: background-color 0.3s, border-color 0.3s, box-shadow 0.3s; -} - -.header-bar { - display: flex; - align-items: center; - justify-content: space-between; -} - -.pagination-bar { - display: flex; - justify-content: flex-end; - margin: 14px 0 0 0; -} - -// 修复 ElMessage 显示问题 -// 只修复定位,保持 Element Plus 官方样式 -.el-message { - // 确保消息固定在页面顶部中央,不受父容器影响 - position: fixed !important; - z-index: var(--el-message-z-index, 9999) !important; - pointer-events: auto !important; -} - -.wang-editor-wrapper{ - border: 1px solid #dcdfe6 !important; - - .toolbar-container{ - border-bottom: 1px solid #dcdfe6 !important; - } - - .editor-container { - background-color: #ffffff !important; - } - - :deep(.w-e-text), - :deep(.w-e-text-container) { - background-color: transparent !important; - - * { - color: #1a1a2e !important; - } - - p { - color: #1a1a2e !important; - margin: 8px 0 !important; - line-height: 1.8 !important; - font-size: 14px !important; - text-indent: 0 !important; - } - - span { - color: #1a1a2e !important; - font-size: 14px !important; - line-height: 1.8 !important; - } - - strong, b { - font-weight: 600 !important; - color: #1a1a2e !important; - } - - em, i { - font-style: italic !important; - } - - u { - text-decoration: underline !important; - } - - s, del { - text-decoration: line-through !important; - } - - h1 { - color: #1a1a2e !important; - font-size: 28px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h2 { - color: #1a1a2e !important; - font-size: 24px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h3 { - color: #1a1a2e !important; - font-size: 20px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h4 { - color: #1a1a2e !important; - font-size: 18px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h5 { - color: #1a1a2e !important; - font-size: 16px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h6 { - color: #1a1a2e !important; - font-size: 14px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - a { - color: #3973ff !important; - text-decoration: underline !important; - - &:hover { - color: #3973ff !important; - opacity: 0.8 !important; - } - } - - code { - background-color: #f5f7fa !important; - color: #1a1a2e !important; - border: 1px solid #e4e7ed !important; - border-radius: 3px !important; - padding: 2px 6px !important; - font-family: 'Consolas', 'Monaco', monospace !important; - font-size: 13px !important; - } - - pre { - background-color: #f5f7fa !important; - border: 1px solid #e4e7ed !important; - border-radius: 4px !important; - color: #1a1a2e !important; - padding: 12px 16px !important; - margin: 12px 0 !important; - overflow-x: auto; - - code { - background-color: transparent !important; - border: none !important; - padding: 0 !important; - color: #1a1a2e !important; - font-size: 13px !important; - line-height: 1.6 !important; - } - } - - blockquote { - border-left: 4px solid #3973ff !important; - background-color: #f5f7fa !important; - color: #606266 !important; - padding: 8px 16px !important; - margin: 12px 0 !important; - } - - table { - border-collapse: collapse !important; - border: 1px solid #e4e7ed !important; - width: 100% !important; - - th, td { - border: 1px solid #e4e7ed !important; - background-color: #ffffff !important; - color: #1a1a2e !important; - padding: 8px 12px !important; - min-width: 60px; - } - - th { - background-color: #f5f7fa !important; - font-weight: 600 !important; - } - } - - ul { - list-style-type: disc !important; - color: #1a1a2e !important; - padding-left: 24px !important; - margin: 8px 0 !important; - } - - ol { - list-style-type: decimal !important; - color: #1a1a2e !important; - padding-left: 24px !important; - margin: 8px 0 !important; - } - - li { - color: #1a1a2e !important; - line-height: 1.8 !important; - margin: 4px 0 !important; - } - - hr { - border-top: 1px solid #e4e7ed !important; - margin: 16px 0 !important; - } - - img { - max-width: 100% !important; - border-radius: 4px !important; - margin: 8px 0 !important; - } - - video { - max-width: 100% !important; - border-radius: 4px !important; - margin: 8px 0 !important; - } - - .w-e-panel-tab-content { - color: #1a1a2e !important; - } - } -} - -html.dark { - .wang-editor-wrapper { - border-color: #3d3d3d !important; - background-color: #1a1a1a !important; - - .toolbar-container { - background-color: #2d2d2d !important; - border-color: #3d3d3d !important; - } - - .editor-container { - background-color: #1a1a1a !important; - - &::-webkit-scrollbar-thumb { - background: #4d4d4d !important; - } - - &::-webkit-scrollbar-track { - background: #2d2d2d !important; - } - } - - :deep(.w-e-text), - :deep(.w-e-text-container) { - background-color: transparent !important; - - * { - color: #e0e0e0 !important; - } - - p { - color: #e0e0e0 !important; - margin: 8px 0 !important; - line-height: 1.8 !important; - font-size: 14px !important; - text-indent: 0 !important; - } - - span { - color: #e0e0e0 !important; - font-size: 14px !important; - line-height: 1.8 !important; - } - - strong, b { - font-weight: 600 !important; - color: #e0e0e0 !important; - } - - em, i { - font-style: italic !important; - } - - u { - text-decoration: underline !important; - } - - s, del { - text-decoration: line-through !important; - } - - h1 { - color: #e0e0e0 !important; - font-size: 28px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h2 { - color: #e0e0e0 !important; - font-size: 24px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h3 { - color: #e0e0e0 !important; - font-size: 20px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h4 { - color: #e0e0e0 !important; - font-size: 18px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h5 { - color: #e0e0e0 !important; - font-size: 16px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - h6 { - color: #e0e0e0 !important; - font-size: 14px !important; - font-weight: 600 !important; - margin: 16px 0 12px !important; - line-height: 1.4 !important; - } - - a { - color: #4f84ff !important; - text-decoration: underline !important; - - &:hover { - color: #4f84ff !important; - opacity: 0.8 !important; - } - } - - code { - background-color: #2d2d2d !important; - color: #e0e0e0 !important; - border-color: #3d3d3d !important; - } - - pre { - background-color: #2d2d2d !important; - border-color: #3d3d3d !important; - color: #e0e0e0 !important; - - code { - background-color: transparent !important; - border: none !important; - color: #e0e0e0 !important; - } - } - - blockquote { - border-left-color: #4f84ff !important; - background-color: #2d2d2d !important; - color: #b0b0b0 !important; - } - - table { - border-color: #3d3d3d !important; - - th, td { - border-color: #3d3d3d !important; - background-color: #1a1a1a !important; - color: #e0e0e0 !important; - } - - th { - background-color: #2d2d2d !important; - } - } - - ul, ol, li { - color: #e0e0e0 !important; - } - - hr { - border-top-color: #3d3d3d !important; - } - - img, video { - max-width: 100% !important; - border-radius: 4px !important; - } - - .w-e-panel-tab-content { - color: #e0e0e0 !important; - } - } - } -} -.el-form-item__label{ - min-width: 80px !important; +// Element Plus Message z-index +:root { + --el-message-z-index: 9999; +} + +// body 样式 +body { + // background-color: #f5f7fa; + color: #303133; + transition: background-color 0.3s ease, color 0.3s ease; +} + +// container-box 样式 +.container-box { + // background-color: #ffffff; + // border: 1px solid #ebeef5; + padding: 24px; + background-color: var(--el-bg-color); + border: 1px solid var(--el-border-color-lighter); + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.04); + border-radius: 8px; + padding: 24px; + transition: background-color 0.3s, border-color 0.3s, box-shadow 0.3s; +} + +.header-bar { + display: flex; + align-items: center; + justify-content: space-between; +} + +.pagination-bar { + display: flex; + justify-content: flex-end; + margin: 14px 0 0 0; +} + +// 修复 ElMessage 显示问题 +// 只修复定位,保持 Element Plus 官方样式 +.el-message { + // 确保消息固定在页面顶部中央,不受父容器影响 + position: fixed !important; + z-index: var(--el-message-z-index, 9999) !important; + pointer-events: auto !important; +} + +.wang-editor-wrapper{ + border: 1px solid #dcdfe6 !important; + + .toolbar-container{ + border-bottom: 1px solid #dcdfe6 !important; + } + + .editor-container { + background-color: #ffffff !important; + } + + :deep(.w-e-text), + :deep(.w-e-text-container) { + background-color: transparent !important; + + * { + color: #1a1a2e !important; + } + + p { + color: #1a1a2e !important; + margin: 8px 0 !important; + line-height: 1.8 !important; + font-size: 14px !important; + text-indent: 0 !important; + } + + span { + color: #1a1a2e !important; + font-size: 14px !important; + line-height: 1.8 !important; + } + + strong, b { + font-weight: 600 !important; + color: #1a1a2e !important; + } + + em, i { + font-style: italic !important; + } + + u { + text-decoration: underline !important; + } + + s, del { + text-decoration: line-through !important; + } + + h1 { + color: #1a1a2e !important; + font-size: 28px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h2 { + color: #1a1a2e !important; + font-size: 24px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h3 { + color: #1a1a2e !important; + font-size: 20px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h4 { + color: #1a1a2e !important; + font-size: 18px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h5 { + color: #1a1a2e !important; + font-size: 16px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h6 { + color: #1a1a2e !important; + font-size: 14px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + a { + color: #3973ff !important; + text-decoration: underline !important; + + &:hover { + color: #3973ff !important; + opacity: 0.8 !important; + } + } + + code { + background-color: #f5f7fa !important; + color: #1a1a2e !important; + border: 1px solid #e4e7ed !important; + border-radius: 3px !important; + padding: 2px 6px !important; + font-family: 'Consolas', 'Monaco', monospace !important; + font-size: 13px !important; + } + + pre { + background-color: #f5f7fa !important; + border: 1px solid #e4e7ed !important; + border-radius: 4px !important; + color: #1a1a2e !important; + padding: 12px 16px !important; + margin: 12px 0 !important; + overflow-x: auto; + + code { + background-color: transparent !important; + border: none !important; + padding: 0 !important; + color: #1a1a2e !important; + font-size: 13px !important; + line-height: 1.6 !important; + } + } + + blockquote { + border-left: 4px solid #3973ff !important; + background-color: #f5f7fa !important; + color: #606266 !important; + padding: 8px 16px !important; + margin: 12px 0 !important; + } + + table { + border-collapse: collapse !important; + border: 1px solid #e4e7ed !important; + width: 100% !important; + + th, td { + border: 1px solid #e4e7ed !important; + background-color: #ffffff !important; + color: #1a1a2e !important; + padding: 8px 12px !important; + min-width: 60px; + } + + th { + background-color: #f5f7fa !important; + font-weight: 600 !important; + } + } + + ul { + list-style-type: disc !important; + color: #1a1a2e !important; + padding-left: 24px !important; + margin: 8px 0 !important; + } + + ol { + list-style-type: decimal !important; + color: #1a1a2e !important; + padding-left: 24px !important; + margin: 8px 0 !important; + } + + li { + color: #1a1a2e !important; + line-height: 1.8 !important; + margin: 4px 0 !important; + } + + hr { + border-top: 1px solid #e4e7ed !important; + margin: 16px 0 !important; + } + + img { + max-width: 100% !important; + border-radius: 4px !important; + margin: 8px 0 !important; + } + + video { + max-width: 100% !important; + border-radius: 4px !important; + margin: 8px 0 !important; + } + + .w-e-panel-tab-content { + color: #1a1a2e !important; + } + } +} + +html.dark { + .wang-editor-wrapper { + border-color: #3d3d3d !important; + background-color: #1a1a1a !important; + + .toolbar-container { + background-color: #2d2d2d !important; + border-color: #3d3d3d !important; + } + + .editor-container { + background-color: #1a1a1a !important; + + &::-webkit-scrollbar-thumb { + background: #4d4d4d !important; + } + + &::-webkit-scrollbar-track { + background: #2d2d2d !important; + } + } + + :deep(.w-e-text), + :deep(.w-e-text-container) { + background-color: transparent !important; + + * { + color: #e0e0e0 !important; + } + + p { + color: #e0e0e0 !important; + margin: 8px 0 !important; + line-height: 1.8 !important; + font-size: 14px !important; + text-indent: 0 !important; + } + + span { + color: #e0e0e0 !important; + font-size: 14px !important; + line-height: 1.8 !important; + } + + strong, b { + font-weight: 600 !important; + color: #e0e0e0 !important; + } + + em, i { + font-style: italic !important; + } + + u { + text-decoration: underline !important; + } + + s, del { + text-decoration: line-through !important; + } + + h1 { + color: #e0e0e0 !important; + font-size: 28px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h2 { + color: #e0e0e0 !important; + font-size: 24px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h3 { + color: #e0e0e0 !important; + font-size: 20px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h4 { + color: #e0e0e0 !important; + font-size: 18px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h5 { + color: #e0e0e0 !important; + font-size: 16px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + h6 { + color: #e0e0e0 !important; + font-size: 14px !important; + font-weight: 600 !important; + margin: 16px 0 12px !important; + line-height: 1.4 !important; + } + + a { + color: #4f84ff !important; + text-decoration: underline !important; + + &:hover { + color: #4f84ff !important; + opacity: 0.8 !important; + } + } + + code { + background-color: #2d2d2d !important; + color: #e0e0e0 !important; + border-color: #3d3d3d !important; + } + + pre { + background-color: #2d2d2d !important; + border-color: #3d3d3d !important; + color: #e0e0e0 !important; + + code { + background-color: transparent !important; + border: none !important; + color: #e0e0e0 !important; + } + } + + blockquote { + border-left-color: #4f84ff !important; + background-color: #2d2d2d !important; + color: #b0b0b0 !important; + } + + table { + border-color: #3d3d3d !important; + + th, td { + border-color: #3d3d3d !important; + background-color: #1a1a1a !important; + color: #e0e0e0 !important; + } + + th { + background-color: #2d2d2d !important; + } + } + + ul, ol, li { + color: #e0e0e0 !important; + } + + hr { + border-top-color: #3d3d3d !important; + } + + img, video { + max-width: 100% !important; + border-radius: 4px !important; + } + + .w-e-panel-tab-content { + color: #e0e0e0 !important; + } + } + } +} +.el-form-item__label{ + min-width: 80px !important; } \ No newline at end of file diff --git a/platform/src/auto-imports.d.ts b/platform/src/auto-imports.d.ts index 9d24007..34d4958 100644 --- a/platform/src/auto-imports.d.ts +++ b/platform/src/auto-imports.d.ts @@ -1,10 +1,10 @@ -/* eslint-disable */ -/* prettier-ignore */ -// @ts-nocheck -// noinspection JSUnusedGlobalSymbols -// Generated by unplugin-auto-import -// biome-ignore lint: disable -export {} -declare global { - -} +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by unplugin-auto-import +// biome-ignore lint: disable +export {} +declare global { + +} diff --git a/platform/src/components.d.ts b/platform/src/components.d.ts index f6e645c..3f9b2b9 100644 --- a/platform/src/components.d.ts +++ b/platform/src/components.d.ts @@ -1,18 +1,18 @@ -/* eslint-disable */ -// @ts-nocheck -// biome-ignore lint: disable -// oxlint-disable -// ------ -// Generated by unplugin-vue-components -// Read more: https://github.com/vuejs/core/pull/3399 - -export {} - -/* prettier-ignore */ -declare module 'vue' { - export interface GlobalComponents { - ElButton: typeof import('element-plus/es')['ElButton'] - RouterLink: typeof import('vue-router')['RouterLink'] - RouterView: typeof import('vue-router')['RouterView'] - } -} +/* eslint-disable */ +// @ts-nocheck +// biome-ignore lint: disable +// oxlint-disable +// ------ +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 + +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + ElButton: typeof import('element-plus/es')['ElButton'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + } +} diff --git a/platform/src/components/CommonAside.vue b/platform/src/components/CommonAside.vue index 056f255..470ae4c 100644 --- a/platform/src/components/CommonAside.vue +++ b/platform/src/components/CommonAside.vue @@ -1,730 +1,730 @@ - - - - - - - + + + + + + + diff --git a/platform/src/components/CommonHeader.vue b/platform/src/components/CommonHeader.vue index d7e2664..ad9229d 100644 --- a/platform/src/components/CommonHeader.vue +++ b/platform/src/components/CommonHeader.vue @@ -1,836 +1,836 @@ - - - - - + + + + + diff --git a/platform/src/env.d.ts b/platform/src/env.d.ts index 08f9c63..64e9ce0 100644 --- a/platform/src/env.d.ts +++ b/platform/src/env.d.ts @@ -1,28 +1,28 @@ -/// - -declare module '*.vue' { - import type { DefineComponent } from 'vue'; - const component: DefineComponent<{}, {}, any>; - export default component; -} - -declare module '@/*' { - import type { ComponentOptions } from 'vue'; - const component: ComponentOptions; - export default component; -} - -interface ImportMetaEnv { - readonly VITE_API_BASE_URL: string; - // 添加其他环境变量... -} - -interface ImportMeta { - readonly env: ImportMetaEnv; -} - -declare module 'vue-cropper' { - import { DefineComponent } from 'vue'; - const VueCropper: DefineComponent<{}, {}, any>; - export default VueCropper; -} +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + const component: DefineComponent<{}, {}, any>; + export default component; +} + +declare module '@/*' { + import type { ComponentOptions } from 'vue'; + const component: ComponentOptions; + export default component; +} + +interface ImportMetaEnv { + readonly VITE_API_BASE_URL: string; + // 添加其他环境变量... +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + +declare module 'vue-cropper' { + import { DefineComponent } from 'vue'; + const VueCropper: DefineComponent<{}, {}, any>; + export default VueCropper; +} diff --git a/platform/src/main.js b/platform/src/main.js index 0478597..b0a1386 100644 --- a/platform/src/main.js +++ b/platform/src/main.js @@ -1,54 +1,54 @@ -import { createApp } from 'vue' -import App from '@/App.vue' -import * as ElementPlusIconsVue from '@element-plus/icons-vue' -// 导入 Element Plus 样式(必须) -import 'element-plus/dist/index.css' -// 导入 Element Plus 暗黑模式样式 -import 'element-plus/theme-chalk/dark/css-vars.css' -import '@/assets/less/index.less' -import '@/assets/css/all.min.css' -import '@/assets/js/all.min.js' -import router from './router' -import { loadAndAddDynamicRoutes } from './router' -import { createPinia } from 'pinia' -import { useAuthStore } from './stores/auth' -// import { initTheme } from './utils/theme' -// 导入全局组件 -import WangEditor from '@/views/components/WangEditor.vue'; - -const app = createApp(App) -const pinia = createPinia() -// 全局注册 WangEditor 组件 -app.component('WangEditor', WangEditor); - -for (const [key, component] of Object.entries(ElementPlusIconsVue)) { - app.component(key, component) -} - -app.use(pinia) -app.use(router) - -// 初始化主题(必须在挂载前执行) -// initTheme() - -// 初始化时检查认证状态 -const authStore = useAuthStore() -authStore.checkAuth() - -// 如果用户已登录,在应用启动时加载动态路由 -if (authStore.isLoggedIn) { - loadAndAddDynamicRoutes() - .catch(err => { - console.error('应用启动时加载动态路由失败:', err); - }) - .then(() => { - // 检查是否因为 token 无效而导致路由加载失败 - const token = localStorage.getItem('token'); - if (!token) { - authStore.clearToken(); - window.location.href = '#/login'; - } - }); -} - +import { createApp } from 'vue' +import App from '@/App.vue' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +// 导入 Element Plus 样式(必须) +import 'element-plus/dist/index.css' +// 导入 Element Plus 暗黑模式样式 +import 'element-plus/theme-chalk/dark/css-vars.css' +import '@/assets/less/index.less' +import '@/assets/css/all.min.css' +import '@/assets/js/all.min.js' +import router from './router' +import { loadAndAddDynamicRoutes } from './router' +import { createPinia } from 'pinia' +import { useAuthStore } from './stores/auth' +// import { initTheme } from './utils/theme' +// 导入全局组件 +import WangEditor from '@/views/components/WangEditor.vue'; + +const app = createApp(App) +const pinia = createPinia() +// 全局注册 WangEditor 组件 +app.component('WangEditor', WangEditor); + +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(pinia) +app.use(router) + +// 初始化主题(必须在挂载前执行) +// initTheme() + +// 初始化时检查认证状态 +const authStore = useAuthStore() +authStore.checkAuth() + +// 如果用户已登录,在应用启动时加载动态路由 +if (authStore.isLoggedIn) { + loadAndAddDynamicRoutes() + .catch(err => { + console.error('应用启动时加载动态路由失败:', err); + }) + .then(() => { + // 检查是否因为 token 无效而导致路由加载失败 + const token = localStorage.getItem('token'); + if (!token) { + authStore.clearToken(); + window.location.href = '#/login'; + } + }); +} + app.mount('#app') \ No newline at end of file diff --git a/platform/src/router/dynamicRoutes.js b/platform/src/router/dynamicRoutes.js index c08c8f8..38aa986 100644 --- a/platform/src/router/dynamicRoutes.js +++ b/platform/src/router/dynamicRoutes.js @@ -1,179 +1,179 @@ -import { createComponentLoader } from '@/utils/pathResolver'; - -function computeFullPath(menuPath, parentPath) { - if (!menuPath) return parentPath || ''; - if (menuPath.startsWith('/')) { - return menuPath.replace(/\/+/g, '/'); - } - const base = (parentPath || '').replace(/\/$/, ''); - return `${base}/${menuPath}`.replace(/\/+/g, '/'); -} - -/** 将子路由的绝对路径转为相对父布局的路径,供 Vue Router 嵌套使用 */ -function toRelativeChildPath(parentAbs, childAbs) { - const base = (parentAbs || '').replace(/\/$/, ''); - const target = (childAbs || '').replace(/\/$/, ''); - if (!target) return ''; - if (target === base) return ''; - const prefix = `${base}/`; - if (target.startsWith(prefix)) { - return target.slice(prefix.length); - } - // 兜底:取最后一段(菜单 path 配置异常时) - const parts = target.split('/').filter(Boolean); - return parts.length ? parts[parts.length - 1] : ''; -} - -function hasPageComponent(menu) { - return menu.type === 4 || (menu.component_path && String(menu.component_path).trim() !== ''); -} - -function resolvePageComponent(menu) { - if (menu.type === 4) { - return () => import('@/views/onepage/index.vue'); - } - if (menu.component_path && String(menu.component_path).trim() !== '') { - return createComponentLoader(menu.component_path); - } - return () => import('@/views/404/404.vue'); -} - -/** - * 菜单子节点 -> 嵌套路由(path 相对 layoutAbsPath) - */ -function convertNestedMenuChildren(children, layoutAbsPath) { - if (!children || children.length === 0) return []; - return children.map((child) => nestedMenuToRoute(child, layoutAbsPath)); -} - -function nestedMenuToRoute(menu, layoutAbsPath) { - const childAbs = computeFullPath(menu.path, layoutAbsPath); - const relPath = toRelativeChildPath(layoutAbsPath, childAbs); - const hasChildren = menu.children && menu.children.length > 0; - const ownPage = hasPageComponent(menu); - - const meta = { - title: menu.title, - icon: menu.icon, - id: menu.id, - componentPath: menu.component_path, - }; - - // 既有自己的页面又有子菜单:套一层 EmptyLayout,避免父页面组件里没有 导致子路由无法渲染 - if (hasChildren && ownPage) { - return { - path: relPath, - name: `menu_${menu.id}`, - meta, - component: () => import('@/views/layouts/EmptyLayout.vue'), - children: [ - { - path: '', - name: `menu_${menu.id}_index`, - meta: { ...meta }, - component: resolvePageComponent(menu), - }, - ...convertNestedMenuChildren(menu.children, childAbs), - ], - }; - } - - // 纯目录 + 子节点 - if (hasChildren && !ownPage) { - const route = { - path: relPath, - name: `menu_${menu.id}`, - meta, - component: () => import('@/views/layouts/EmptyLayout.vue'), - children: convertNestedMenuChildren(menu.children, childAbs), - }; - const firstChild = menu.children[0]; - if (firstChild && firstChild.path) { - const firstAbs = computeFullPath(firstChild.path, childAbs); - const firstRel = toRelativeChildPath(childAbs, firstAbs); - if (firstRel) { - route.redirect = firstRel; - } - } - return route; - } - - // 叶子页面 - return { - path: relPath, - name: `menu_${menu.id}`, - meta, - component: resolvePageComponent(menu), - }; -} - -// 递归转换嵌套菜单为嵌套路由 -export function convertMenusToRoutes(menus, parentPath = '') { - if (!menus || menus.length === 0) return []; - - return menus - // 平台端:只转换 isPlatform !== 0 的菜单(默认视为平台可见) - .filter((menu) => menu.isPlatform === undefined || Number(menu.isPlatform) !== 0) - .map((menu) => { - const fullPath = menu.path - ? menu.path.startsWith('/') - ? menu.path.replace(/\/+/g, '/') - : `${(parentPath || '').replace(/\/$/, '')}/${menu.path}`.replace(/\/+/g, '/') - : ''; - - const hasChildren = menu.children && menu.children.length > 0; - const ownPage = hasPageComponent(menu); - - const meta = { - title: menu.title, - icon: menu.icon, - id: menu.id, - componentPath: menu.component_path, - }; - - // 顶层:有页面 + 有子菜单 -> EmptyLayout + 默认子路由 + 相对 path 子路由 - if (hasChildren && ownPage) { - return { - path: fullPath || menu.path || '', - name: `menu_${menu.id}`, - meta, - component: () => import('@/views/layouts/EmptyLayout.vue'), - children: [ - { - path: '', - name: `menu_${menu.id}_index`, - meta: { ...meta }, - component: resolvePageComponent(menu), - }, - ...convertNestedMenuChildren(menu.children, fullPath), - ], - }; - } - - const route = { - path: fullPath || menu.path || '', - name: `menu_${menu.id}`, - meta, - }; - - if (menu.type === 4) { - route.component = () => import('@/views/onepage/index.vue'); - } else if (menu.component_path && menu.component_path.trim() !== '') { - route.component = createComponentLoader(menu.component_path); - } else if (hasChildren) { - route.component = () => import('@/views/layouts/EmptyLayout.vue'); - route.children = convertMenusToRoutes(menu.children, fullPath); - const firstChild = menu.children[0]; - if (firstChild && firstChild.path) { - const childFullPath = firstChild.path.startsWith('/') - ? firstChild.path - : `${fullPath}/${firstChild.path}`; - route.redirect = childFullPath; - } - } else { - route.component = () => import('@/views/404/404.vue'); - } - - return route; - }); -} +import { createComponentLoader } from '@/utils/pathResolver'; + +function computeFullPath(menuPath, parentPath) { + if (!menuPath) return parentPath || ''; + if (menuPath.startsWith('/')) { + return menuPath.replace(/\/+/g, '/'); + } + const base = (parentPath || '').replace(/\/$/, ''); + return `${base}/${menuPath}`.replace(/\/+/g, '/'); +} + +/** 将子路由的绝对路径转为相对父布局的路径,供 Vue Router 嵌套使用 */ +function toRelativeChildPath(parentAbs, childAbs) { + const base = (parentAbs || '').replace(/\/$/, ''); + const target = (childAbs || '').replace(/\/$/, ''); + if (!target) return ''; + if (target === base) return ''; + const prefix = `${base}/`; + if (target.startsWith(prefix)) { + return target.slice(prefix.length); + } + // 兜底:取最后一段(菜单 path 配置异常时) + const parts = target.split('/').filter(Boolean); + return parts.length ? parts[parts.length - 1] : ''; +} + +function hasPageComponent(menu) { + return menu.type === 4 || (menu.component_path && String(menu.component_path).trim() !== ''); +} + +function resolvePageComponent(menu) { + if (menu.type === 4) { + return () => import('@/views/onepage/index.vue'); + } + if (menu.component_path && String(menu.component_path).trim() !== '') { + return createComponentLoader(menu.component_path); + } + return () => import('@/views/404/404.vue'); +} + +/** + * 菜单子节点 -> 嵌套路由(path 相对 layoutAbsPath) + */ +function convertNestedMenuChildren(children, layoutAbsPath) { + if (!children || children.length === 0) return []; + return children.map((child) => nestedMenuToRoute(child, layoutAbsPath)); +} + +function nestedMenuToRoute(menu, layoutAbsPath) { + const childAbs = computeFullPath(menu.path, layoutAbsPath); + const relPath = toRelativeChildPath(layoutAbsPath, childAbs); + const hasChildren = menu.children && menu.children.length > 0; + const ownPage = hasPageComponent(menu); + + const meta = { + title: menu.title, + icon: menu.icon, + id: menu.id, + componentPath: menu.component_path, + }; + + // 既有自己的页面又有子菜单:套一层 EmptyLayout,避免父页面组件里没有 导致子路由无法渲染 + if (hasChildren && ownPage) { + return { + path: relPath, + name: `menu_${menu.id}`, + meta, + component: () => import('@/views/layouts/EmptyLayout.vue'), + children: [ + { + path: '', + name: `menu_${menu.id}_index`, + meta: { ...meta }, + component: resolvePageComponent(menu), + }, + ...convertNestedMenuChildren(menu.children, childAbs), + ], + }; + } + + // 纯目录 + 子节点 + if (hasChildren && !ownPage) { + const route = { + path: relPath, + name: `menu_${menu.id}`, + meta, + component: () => import('@/views/layouts/EmptyLayout.vue'), + children: convertNestedMenuChildren(menu.children, childAbs), + }; + const firstChild = menu.children[0]; + if (firstChild && firstChild.path) { + const firstAbs = computeFullPath(firstChild.path, childAbs); + const firstRel = toRelativeChildPath(childAbs, firstAbs); + if (firstRel) { + route.redirect = firstRel; + } + } + return route; + } + + // 叶子页面 + return { + path: relPath, + name: `menu_${menu.id}`, + meta, + component: resolvePageComponent(menu), + }; +} + +// 递归转换嵌套菜单为嵌套路由 +export function convertMenusToRoutes(menus, parentPath = '') { + if (!menus || menus.length === 0) return []; + + return menus + // 平台端:只转换 isPlatform !== 0 的菜单(默认视为平台可见) + .filter((menu) => menu.isPlatform === undefined || Number(menu.isPlatform) !== 0) + .map((menu) => { + const fullPath = menu.path + ? menu.path.startsWith('/') + ? menu.path.replace(/\/+/g, '/') + : `${(parentPath || '').replace(/\/$/, '')}/${menu.path}`.replace(/\/+/g, '/') + : ''; + + const hasChildren = menu.children && menu.children.length > 0; + const ownPage = hasPageComponent(menu); + + const meta = { + title: menu.title, + icon: menu.icon, + id: menu.id, + componentPath: menu.component_path, + }; + + // 顶层:有页面 + 有子菜单 -> EmptyLayout + 默认子路由 + 相对 path 子路由 + if (hasChildren && ownPage) { + return { + path: fullPath || menu.path || '', + name: `menu_${menu.id}`, + meta, + component: () => import('@/views/layouts/EmptyLayout.vue'), + children: [ + { + path: '', + name: `menu_${menu.id}_index`, + meta: { ...meta }, + component: resolvePageComponent(menu), + }, + ...convertNestedMenuChildren(menu.children, fullPath), + ], + }; + } + + const route = { + path: fullPath || menu.path || '', + name: `menu_${menu.id}`, + meta, + }; + + if (menu.type === 4) { + route.component = () => import('@/views/onepage/index.vue'); + } else if (menu.component_path && menu.component_path.trim() !== '') { + route.component = createComponentLoader(menu.component_path); + } else if (hasChildren) { + route.component = () => import('@/views/layouts/EmptyLayout.vue'); + route.children = convertMenusToRoutes(menu.children, fullPath); + const firstChild = menu.children[0]; + if (firstChild && firstChild.path) { + const childFullPath = firstChild.path.startsWith('/') + ? firstChild.path + : `${fullPath}/${firstChild.path}`; + route.redirect = childFullPath; + } + } else { + route.component = () => import('@/views/404/404.vue'); + } + + return route; + }); +} diff --git a/platform/src/router/index.js b/platform/src/router/index.js index b560bad..c038c5a 100644 --- a/platform/src/router/index.js +++ b/platform/src/router/index.js @@ -1,211 +1,211 @@ -import { createRouter, createWebHashHistory } from "vue-router"; -import { convertMenusToRoutes } from "./dynamicRoutes"; - -// 静态子路由:需要在 Main 框架内显示的页面 -const staticMainChildren = [ - { - path: "/home", - name: "Home", - component: () => import("@/views/home/index.vue"), - meta: { requiresAuth: true, title: "首页" } - }, - { - path: "/user/userProfile", - name: "userProfile", - component: () => import("@/views/user/userProfile.vue"), - meta: { requiresAuth: true, title: "用户中心" } - }, - { - path: "/system/email", - name: "SystemEmail", - component: () => import("@/views/system/email/index.vue"), - meta: { requiresAuth: true, title: "邮箱管理" } - }, - // 兼容拼写错误的路径重定向 - { - path: "/apps/erp/dashborad", - redirect: "/apps/erp/dashboard" - } -]; - -// 静态路由:登录页独立、home 导航门户独立、404 页面独立 -const staticRoutes = [ - { - path: "/login", - name: "Login", - component: () => import("@/views/login/index.vue"), - meta: { requiresAuth: false } - }, - { - path: "/register", - name: "Register", - component: () => import("@/views/login/register.vue"), - meta: { requiresAuth: false } - }, - { - path: "/forget", - name: "ForgetPassword", - component: () => import("@/views/login/forget.vue"), - meta: { requiresAuth: false } - }, - - // 兼容路径拼写错误:dashborad -> dashboard - { - path: "/apps/erp/dashborad", - redirect: "/apps/erp/dashboard" - }, - { - path: "/:pathMatch(.*)*", - name: "NotFound", - component: () => import("@/views/404/404.vue"), - meta: { requiresAuth: false } - } -]; - -const router = createRouter({ - history: createWebHashHistory(), - routes: staticRoutes -}); - -let dynamicRoutesAdded = false; -let dynamicRoutesData = []; -let routesLoadingPromise = null; - -export function resetDynamicRoutes() { - dynamicRoutesAdded = false; - routesLoadingPromise = null; -} - -export async function loadAndAddDynamicRoutes() { - if (routesLoadingPromise) { - return routesLoadingPromise; - } - - if (dynamicRoutesAdded) { - return Promise.resolve(); - } - - routesLoadingPromise = (async () => { - try { - const { useMenuStore } = await import("@/stores/menu"); - const menuStore = useMenuStore(); - const menuData = await menuStore.fetchMenus(); - - if (menuData && menuData.length > 0) { - addDynamicRoutes(menuData); - dynamicRoutesAdded = true; - routesLoadingPromise = null; - return Promise.resolve(); - } else { - dynamicRoutesAdded = true; - routesLoadingPromise = null; - return Promise.resolve(); - } - } catch (error) { - console.error('加载动态路由失败:', error); - dynamicRoutesAdded = true; - routesLoadingPromise = null; - throw error; - } - })(); - - return routesLoadingPromise; -} - -// 核心修改:移除扁平化,直接使用嵌套菜单生成路由 -function addDynamicRoutes(menus) { - if (!menus?.length) { - return; - } - - // 直接转换嵌套菜单为嵌套路由(不再扁平化) - const dynamicRoutes = convertMenusToRoutes(menus); - - if (router.hasRoute('Main')) { - router.removeRoute('Main'); - } - - // 重新添加主路由,合并静态子路由和动态路由 - router.addRoute({ - path: "/", - name: "Main", - component: () => import("@/views/Main.vue"), - redirect: "/home", - meta: { requiresAuth: true }, - children: [...staticMainChildren, ...dynamicRoutes] // 合并静态和动态子路由 - }); - - dynamicRoutesAdded = true; -} - -function findRouteByName(routes, routeName) { - for (const route of routes) { - if (route.name === routeName) { - return route; - } - if (route.children) { - const found = findRouteByName(route.children, routeName); - if (found) { - return found; - } - } - } - return null; -} - -function findFirstValidRoute(routes) { - for (const route of routes) { - if (route.component) { - return route; - } - if (route.children && route.children.length > 0) { - const childRoute = findFirstValidRoute(route.children); - if (childRoute) { - return childRoute; - } - } - } - return null; -} - -router.beforeEach(async (to, from, next) => { - const token = localStorage.getItem("token"); - const publicPaths = ["/login", "/register", "/forget"]; - - if (publicPaths.includes(to.path)) { - if (token) { - if (!dynamicRoutesAdded) { - await loadAndAddDynamicRoutes(); - } - next({ path: "/home" }); - } else { - next(); - } - return; - } - - if (!token) { - next({ path: "/login", query: { redirect: to.path } }); - return; - } - - if (!dynamicRoutesAdded) { - try { - await loadAndAddDynamicRoutes(); - } catch (error) { - console.error('动态路由加载失败:', error); - // token 无效时跳转登录 - if (error?.message === 'token无效' || error?.response?.status === 401) { - next({ path: '/login' }); - return; - } - } - // 路由加载后重新导航,确保路由匹配正确 - next({ path: to.path, replace: true }); - return; - } - - next(); -}); - -export default router; +import { createRouter, createWebHashHistory } from "vue-router"; +import { convertMenusToRoutes } from "./dynamicRoutes"; + +// 静态子路由:需要在 Main 框架内显示的页面 +const staticMainChildren = [ + { + path: "/home", + name: "Home", + component: () => import("@/views/home/index.vue"), + meta: { requiresAuth: true, title: "首页" } + }, + { + path: "/user/userProfile", + name: "userProfile", + component: () => import("@/views/user/userProfile.vue"), + meta: { requiresAuth: true, title: "用户中心" } + }, + { + path: "/system/email", + name: "SystemEmail", + component: () => import("@/views/system/email/index.vue"), + meta: { requiresAuth: true, title: "邮箱管理" } + }, + // 兼容拼写错误的路径重定向 + { + path: "/apps/erp/dashborad", + redirect: "/apps/erp/dashboard" + } +]; + +// 静态路由:登录页独立、home 导航门户独立、404 页面独立 +const staticRoutes = [ + { + path: "/login", + name: "Login", + component: () => import("@/views/login/index.vue"), + meta: { requiresAuth: false } + }, + { + path: "/register", + name: "Register", + component: () => import("@/views/login/register.vue"), + meta: { requiresAuth: false } + }, + { + path: "/forget", + name: "ForgetPassword", + component: () => import("@/views/login/forget.vue"), + meta: { requiresAuth: false } + }, + + // 兼容路径拼写错误:dashborad -> dashboard + { + path: "/apps/erp/dashborad", + redirect: "/apps/erp/dashboard" + }, + { + path: "/:pathMatch(.*)*", + name: "NotFound", + component: () => import("@/views/404/404.vue"), + meta: { requiresAuth: false } + } +]; + +const router = createRouter({ + history: createWebHashHistory(), + routes: staticRoutes +}); + +let dynamicRoutesAdded = false; +let dynamicRoutesData = []; +let routesLoadingPromise = null; + +export function resetDynamicRoutes() { + dynamicRoutesAdded = false; + routesLoadingPromise = null; +} + +export async function loadAndAddDynamicRoutes() { + if (routesLoadingPromise) { + return routesLoadingPromise; + } + + if (dynamicRoutesAdded) { + return Promise.resolve(); + } + + routesLoadingPromise = (async () => { + try { + const { useMenuStore } = await import("@/stores/menu"); + const menuStore = useMenuStore(); + const menuData = await menuStore.fetchMenus(); + + if (menuData && menuData.length > 0) { + addDynamicRoutes(menuData); + dynamicRoutesAdded = true; + routesLoadingPromise = null; + return Promise.resolve(); + } else { + dynamicRoutesAdded = true; + routesLoadingPromise = null; + return Promise.resolve(); + } + } catch (error) { + console.error('加载动态路由失败:', error); + dynamicRoutesAdded = true; + routesLoadingPromise = null; + throw error; + } + })(); + + return routesLoadingPromise; +} + +// 核心修改:移除扁平化,直接使用嵌套菜单生成路由 +function addDynamicRoutes(menus) { + if (!menus?.length) { + return; + } + + // 直接转换嵌套菜单为嵌套路由(不再扁平化) + const dynamicRoutes = convertMenusToRoutes(menus); + + if (router.hasRoute('Main')) { + router.removeRoute('Main'); + } + + // 重新添加主路由,合并静态子路由和动态路由 + router.addRoute({ + path: "/", + name: "Main", + component: () => import("@/views/Main.vue"), + redirect: "/home", + meta: { requiresAuth: true }, + children: [...staticMainChildren, ...dynamicRoutes] // 合并静态和动态子路由 + }); + + dynamicRoutesAdded = true; +} + +function findRouteByName(routes, routeName) { + for (const route of routes) { + if (route.name === routeName) { + return route; + } + if (route.children) { + const found = findRouteByName(route.children, routeName); + if (found) { + return found; + } + } + } + return null; +} + +function findFirstValidRoute(routes) { + for (const route of routes) { + if (route.component) { + return route; + } + if (route.children && route.children.length > 0) { + const childRoute = findFirstValidRoute(route.children); + if (childRoute) { + return childRoute; + } + } + } + return null; +} + +router.beforeEach(async (to, from, next) => { + const token = localStorage.getItem("token"); + const publicPaths = ["/login", "/register", "/forget"]; + + if (publicPaths.includes(to.path)) { + if (token) { + if (!dynamicRoutesAdded) { + await loadAndAddDynamicRoutes(); + } + next({ path: "/home" }); + } else { + next(); + } + return; + } + + if (!token) { + next({ path: "/login", query: { redirect: to.path } }); + return; + } + + if (!dynamicRoutesAdded) { + try { + await loadAndAddDynamicRoutes(); + } catch (error) { + console.error('动态路由加载失败:', error); + // token 无效时跳转登录 + if (error?.message === 'token无效' || error?.response?.status === 401) { + next({ path: '/login' }); + return; + } + } + // 路由加载后重新导航,确保路由匹配正确 + next({ path: to.path, replace: true }); + return; + } + + next(); +}); + +export default router; diff --git a/platform/src/stores/auth.js b/platform/src/stores/auth.js index 35f58b0..9df868e 100644 --- a/platform/src/stores/auth.js +++ b/platform/src/stores/auth.js @@ -1,124 +1,124 @@ -import { defineStore } from 'pinia' -import { ref, reactive } from 'vue' - -// 平台登录缓存仅保留以下字段(不含 tid、group_id) -const defaultUser = { - id: '', - account: '', - name: '', - rid: '', - avatar: '', - role_name: '' -} - -/** - * 规范化写入 localStorage 的用户信息,去掉 tid、group_id 等废弃字段; - * 若仅有历史 group_id,则迁移到 rid。 - */ -function normalizePlatformUser(raw) { - if (!raw || typeof raw !== 'object') { - return { ...defaultUser } - } - let rid = raw.rid - if (rid === undefined || rid === null || rid === '') { - const legacy = raw.group_id - if (legacy !== undefined && legacy !== null && legacy !== '') { - rid = legacy - } - } - return { - id: parseInt(raw.id, 10) || null, - account: raw.account || '', - name: raw.name || '', - rid: rid !== undefined && rid !== null && rid !== '' ? rid : '', - avatar: raw.avatar || '', - role_name: raw.role_name || '' - } -} - -export const useAuthStore = defineStore('auth', () => { - const token = ref(localStorage.getItem('token') || '') - const isLoggedIn = ref(!!token.value) - const user = reactive({ ...defaultUser }) - - // 从缓存加载用户信息 - function loadUserFromCache() { - const cachedUser = localStorage.getItem('userInfo') - if (cachedUser) { - try { - const parsed = JSON.parse(cachedUser) - const normalized = normalizePlatformUser(parsed) - Object.assign(user, normalized) - localStorage.setItem('userInfo', JSON.stringify(normalized)) - } catch (e) { - console.error('Failed to parse user info from cache:', e) - } - } - } - - // 初始化时加载用户信息 - loadUserFromCache() - - // 保存登录信息(token 和用户信息) - function setLoginInfo(loginData) { - const userInfo = loginData.user || loginData - const normalizedUser = normalizePlatformUser(userInfo) - - const accessToken = loginData.token || '' - - token.value = accessToken - isLoggedIn.value = !!accessToken - localStorage.setItem('token', accessToken) - - Object.assign(user, normalizedUser) - localStorage.setItem('userInfo', JSON.stringify(normalizedUser)) - } - - // 设置 token(兼容旧代码) - function setToken(newToken) { - token.value = newToken - isLoggedIn.value = true - localStorage.setItem('token', newToken) - } - - // 清除登录信息 - function clearToken() { - token.value = '' - isLoggedIn.value = false - Object.assign(user, { ...defaultUser }) - localStorage.removeItem('token') - localStorage.removeItem('userInfo') - } - - // 检查认证状态 - function checkAuth() { - const storedToken = localStorage.getItem('token') - if (storedToken) { - token.value = storedToken - isLoggedIn.value = true - loadUserFromCache() - } else { - token.value = '' - isLoggedIn.value = false - Object.assign(user, { ...defaultUser }) - } - } - - // 更新用户信息(合并后仍只持久化平台字段) - function updateUserInfo(partial) { - const merged = normalizePlatformUser({ ...user, ...partial }) - Object.assign(user, merged) - localStorage.setItem('userInfo', JSON.stringify(merged)) - } - - return { - token, - isLoggedIn, - user, - setLoginInfo, - setToken, - clearToken, - checkAuth, - updateUserInfo - } -}) +import { defineStore } from 'pinia' +import { ref, reactive } from 'vue' + +// 平台登录缓存仅保留以下字段(不含 tid、group_id) +const defaultUser = { + id: '', + account: '', + name: '', + rid: '', + avatar: '', + role_name: '' +} + +/** + * 规范化写入 localStorage 的用户信息,去掉 tid、group_id 等废弃字段; + * 若仅有历史 group_id,则迁移到 rid。 + */ +function normalizePlatformUser(raw) { + if (!raw || typeof raw !== 'object') { + return { ...defaultUser } + } + let rid = raw.rid + if (rid === undefined || rid === null || rid === '') { + const legacy = raw.group_id + if (legacy !== undefined && legacy !== null && legacy !== '') { + rid = legacy + } + } + return { + id: parseInt(raw.id, 10) || null, + account: raw.account || '', + name: raw.name || '', + rid: rid !== undefined && rid !== null && rid !== '' ? rid : '', + avatar: raw.avatar || '', + role_name: raw.role_name || '' + } +} + +export const useAuthStore = defineStore('auth', () => { + const token = ref(localStorage.getItem('token') || '') + const isLoggedIn = ref(!!token.value) + const user = reactive({ ...defaultUser }) + + // 从缓存加载用户信息 + function loadUserFromCache() { + const cachedUser = localStorage.getItem('userInfo') + if (cachedUser) { + try { + const parsed = JSON.parse(cachedUser) + const normalized = normalizePlatformUser(parsed) + Object.assign(user, normalized) + localStorage.setItem('userInfo', JSON.stringify(normalized)) + } catch (e) { + console.error('Failed to parse user info from cache:', e) + } + } + } + + // 初始化时加载用户信息 + loadUserFromCache() + + // 保存登录信息(token 和用户信息) + function setLoginInfo(loginData) { + const userInfo = loginData.user || loginData + const normalizedUser = normalizePlatformUser(userInfo) + + const accessToken = loginData.token || '' + + token.value = accessToken + isLoggedIn.value = !!accessToken + localStorage.setItem('token', accessToken) + + Object.assign(user, normalizedUser) + localStorage.setItem('userInfo', JSON.stringify(normalizedUser)) + } + + // 设置 token(兼容旧代码) + function setToken(newToken) { + token.value = newToken + isLoggedIn.value = true + localStorage.setItem('token', newToken) + } + + // 清除登录信息 + function clearToken() { + token.value = '' + isLoggedIn.value = false + Object.assign(user, { ...defaultUser }) + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + } + + // 检查认证状态 + function checkAuth() { + const storedToken = localStorage.getItem('token') + if (storedToken) { + token.value = storedToken + isLoggedIn.value = true + loadUserFromCache() + } else { + token.value = '' + isLoggedIn.value = false + Object.assign(user, { ...defaultUser }) + } + } + + // 更新用户信息(合并后仍只持久化平台字段) + function updateUserInfo(partial) { + const merged = normalizePlatformUser({ ...user, ...partial }) + Object.assign(user, merged) + localStorage.setItem('userInfo', JSON.stringify(merged)) + } + + return { + token, + isLoggedIn, + user, + setLoginInfo, + setToken, + clearToken, + checkAuth, + updateUserInfo + } +}) diff --git a/platform/src/stores/index.js b/platform/src/stores/index.js index 757f154..8076ca4 100644 --- a/platform/src/stores/index.js +++ b/platform/src/stores/index.js @@ -1,197 +1,197 @@ -import { defineStore } from 'pinia'; -import { ref, computed, reactive } from 'vue'; - -// ========== 全局状态 Store ========== -function initState() { - return { - isCollapse: false, - }; -} - -export const useAllDataStore = defineStore('allData', () => { - const state = reactive(initState()); - const count = ref(0); - const doubleCount = computed(() => count.value * 2); - function increment() { - count.value++; - } - return { - state, - count, - doubleCount, - increment, - }; -}); - -// ========== 多标签页 Tabs Store ========== -import { defineStore as defineTabsStore } from 'pinia'; -import { ref as vueRef } from 'vue'; - -/** - * 多标签页Tabs状态管理 - * tabList每个tab结构: { - * title: 标签显示名, - * fullPath: 路由路径(唯一key), - * name: 路由name, - * icon: 图标(可选) - * } - */ -export const useTabsStore = defineTabsStore('tabs', () => { - // 固定首页tab - const defaultDashboardPath = '/home'; - - // 从 localStorage 恢复 tabs 状态 - function loadTabsFromStorage() { - try { - const savedTabs = localStorage.getItem('tabs_list'); - const savedActiveTab = localStorage.getItem('active_tab'); - if (savedTabs) { - const tabs = JSON.parse(savedTabs); - // 确保至少包含首页 - const hasHome = tabs.some(t => t.fullPath === defaultDashboardPath); - if (!hasHome) { - tabs.unshift({ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }); - } - return tabs; - } - } catch (e) { - console.warn('恢复 tabs 失败:', e); - } - return [{ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }]; - } - - // 保存 tabs 到 localStorage - function saveTabsToStorage(tabs, active) { - try { - localStorage.setItem('tabs_list', JSON.stringify(tabs)); - if (active) { - localStorage.setItem('active_tab', active); - } - } catch (e) { - console.warn('保存 tabs 失败:', e); - } - } - - const tabList = vueRef(loadTabsFromStorage()); - const savedActiveTab = localStorage.getItem('active_tab'); - const activeTab = vueRef(savedActiveTab || defaultDashboardPath); - - // 添加tab,若已存在则激活 - function addTab(tab) { - const exist = tabList.value.find((t) => t.fullPath === tab.fullPath); - if (!exist) { - tabList.value.push(tab); - } - activeTab.value = tab.fullPath; - saveTabsToStorage(tabList.value, activeTab.value); - } - - // 删除指定tab并切换激活tab - function removeTab(fullPath) { - const idx = tabList.value.findIndex((t) => t.fullPath === fullPath); - if (idx > -1) { - tabList.value.splice(idx, 1); - // 只在关闭当前激活tab时切换激活tab - if (activeTab.value === fullPath) { - if (tabList.value.length > 0) { - // 优先激活右侧(如无则激活左侧) - const newIdx = idx >= tabList.value.length ? tabList.value.length - 1 : idx; - activeTab.value = tabList.value[newIdx].fullPath; - } else { - // 全部关闭,兜底首页 - activeTab.value = defaultDashboardPath; - } - } - saveTabsToStorage(tabList.value, activeTab.value); - } - } - - // 关闭其他,只留首页和当前激活tab - function closeOthers() { - tabList.value = tabList.value.filter( - (t) => t.fullPath === defaultDashboardPath || t.fullPath === activeTab.value - ); - saveTabsToStorage(tabList.value, activeTab.value); - } - - // 关闭左侧(关闭指定tab左侧的所有tab,保留首页和目标tab) - function closeLeft(targetFullPath) { - const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath); - if (targetIndex > -1) { - // 保留首页和目标tab及其右侧的所有tab - const beforeIndex = tabList.value.slice(0, targetIndex); - const hasCloseableLeft = beforeIndex.some(t => t.fullPath !== defaultDashboardPath); - - if (hasCloseableLeft) { - tabList.value = tabList.value.filter((t, index) => - t.fullPath === defaultDashboardPath || index >= targetIndex - ); - // 如果关闭的tab中包含了当前激活的tab,则激活目标tab - if (!tabList.value.find(t => t.fullPath === activeTab.value)) { - activeTab.value = targetFullPath; - } - saveTabsToStorage(tabList.value, activeTab.value); - } - } - } - - // 关闭右侧(关闭指定tab右侧的所有tab,保留首页和目标tab) - function closeRight(targetFullPath) { - const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath); - if (targetIndex > -1) { - // 保留首页和目标tab及其左侧的所有tab - const afterIndex = tabList.value.slice(targetIndex + 1); - const hasCloseableRight = afterIndex.length > 0; - - if (hasCloseableRight) { - tabList.value = tabList.value.filter((t, index) => - t.fullPath === defaultDashboardPath || index <= targetIndex - ); - // 如果关闭的tab中包含了当前激活的tab,则激活目标tab - if (!tabList.value.find(t => t.fullPath === activeTab.value)) { - activeTab.value = targetFullPath; - } - saveTabsToStorage(tabList.value, activeTab.value); - } - } - } - - // 关闭全部,只留首页 - function closeAll() { - tabList.value = tabList.value.filter((t) => t.fullPath === defaultDashboardPath); - activeTab.value = defaultDashboardPath; - saveTabsToStorage(tabList.value, activeTab.value); - } - - // 设置激活tab(不触发路由跳转,仅用于更新状态) - function setActiveTab(fullPath) { - activeTab.value = fullPath; - saveTabsToStorage(tabList.value, activeTab.value); - } - - // 重置 tabs store 到初始状态(登出时使用) - function resetTabs() { - tabList.value = [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }]; - activeTab.value = defaultDashboardPath; - // 清除 localStorage 中的 tabs 数据 - localStorage.removeItem('tabs_list'); - localStorage.removeItem('active_tab'); - } - - return { - tabList, - activeTab, - addTab, - removeTab, - closeOthers, - closeLeft, - closeRight, - closeAll, - setActiveTab, - saveTabsToStorage, - resetTabs, - }; -}); - -// ========== 菜单 Menu Store ========== +import { defineStore } from 'pinia'; +import { ref, computed, reactive } from 'vue'; + +// ========== 全局状态 Store ========== +function initState() { + return { + isCollapse: false, + }; +} + +export const useAllDataStore = defineStore('allData', () => { + const state = reactive(initState()); + const count = ref(0); + const doubleCount = computed(() => count.value * 2); + function increment() { + count.value++; + } + return { + state, + count, + doubleCount, + increment, + }; +}); + +// ========== 多标签页 Tabs Store ========== +import { defineStore as defineTabsStore } from 'pinia'; +import { ref as vueRef } from 'vue'; + +/** + * 多标签页Tabs状态管理 + * tabList每个tab结构: { + * title: 标签显示名, + * fullPath: 路由路径(唯一key), + * name: 路由name, + * icon: 图标(可选) + * } + */ +export const useTabsStore = defineTabsStore('tabs', () => { + // 固定首页tab + const defaultDashboardPath = '/home'; + + // 从 localStorage 恢复 tabs 状态 + function loadTabsFromStorage() { + try { + const savedTabs = localStorage.getItem('tabs_list'); + const savedActiveTab = localStorage.getItem('active_tab'); + if (savedTabs) { + const tabs = JSON.parse(savedTabs); + // 确保至少包含首页 + const hasHome = tabs.some(t => t.fullPath === defaultDashboardPath); + if (!hasHome) { + tabs.unshift({ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }); + } + return tabs; + } + } catch (e) { + console.warn('恢复 tabs 失败:', e); + } + return [{ title: '首页', fullPath: defaultDashboardPath, name: 'Home' }]; + } + + // 保存 tabs 到 localStorage + function saveTabsToStorage(tabs, active) { + try { + localStorage.setItem('tabs_list', JSON.stringify(tabs)); + if (active) { + localStorage.setItem('active_tab', active); + } + } catch (e) { + console.warn('保存 tabs 失败:', e); + } + } + + const tabList = vueRef(loadTabsFromStorage()); + const savedActiveTab = localStorage.getItem('active_tab'); + const activeTab = vueRef(savedActiveTab || defaultDashboardPath); + + // 添加tab,若已存在则激活 + function addTab(tab) { + const exist = tabList.value.find((t) => t.fullPath === tab.fullPath); + if (!exist) { + tabList.value.push(tab); + } + activeTab.value = tab.fullPath; + saveTabsToStorage(tabList.value, activeTab.value); + } + + // 删除指定tab并切换激活tab + function removeTab(fullPath) { + const idx = tabList.value.findIndex((t) => t.fullPath === fullPath); + if (idx > -1) { + tabList.value.splice(idx, 1); + // 只在关闭当前激活tab时切换激活tab + if (activeTab.value === fullPath) { + if (tabList.value.length > 0) { + // 优先激活右侧(如无则激活左侧) + const newIdx = idx >= tabList.value.length ? tabList.value.length - 1 : idx; + activeTab.value = tabList.value[newIdx].fullPath; + } else { + // 全部关闭,兜底首页 + activeTab.value = defaultDashboardPath; + } + } + saveTabsToStorage(tabList.value, activeTab.value); + } + } + + // 关闭其他,只留首页和当前激活tab + function closeOthers() { + tabList.value = tabList.value.filter( + (t) => t.fullPath === defaultDashboardPath || t.fullPath === activeTab.value + ); + saveTabsToStorage(tabList.value, activeTab.value); + } + + // 关闭左侧(关闭指定tab左侧的所有tab,保留首页和目标tab) + function closeLeft(targetFullPath) { + const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath); + if (targetIndex > -1) { + // 保留首页和目标tab及其右侧的所有tab + const beforeIndex = tabList.value.slice(0, targetIndex); + const hasCloseableLeft = beforeIndex.some(t => t.fullPath !== defaultDashboardPath); + + if (hasCloseableLeft) { + tabList.value = tabList.value.filter((t, index) => + t.fullPath === defaultDashboardPath || index >= targetIndex + ); + // 如果关闭的tab中包含了当前激活的tab,则激活目标tab + if (!tabList.value.find(t => t.fullPath === activeTab.value)) { + activeTab.value = targetFullPath; + } + saveTabsToStorage(tabList.value, activeTab.value); + } + } + } + + // 关闭右侧(关闭指定tab右侧的所有tab,保留首页和目标tab) + function closeRight(targetFullPath) { + const targetIndex = tabList.value.findIndex((t) => t.fullPath === targetFullPath); + if (targetIndex > -1) { + // 保留首页和目标tab及其左侧的所有tab + const afterIndex = tabList.value.slice(targetIndex + 1); + const hasCloseableRight = afterIndex.length > 0; + + if (hasCloseableRight) { + tabList.value = tabList.value.filter((t, index) => + t.fullPath === defaultDashboardPath || index <= targetIndex + ); + // 如果关闭的tab中包含了当前激活的tab,则激活目标tab + if (!tabList.value.find(t => t.fullPath === activeTab.value)) { + activeTab.value = targetFullPath; + } + saveTabsToStorage(tabList.value, activeTab.value); + } + } + } + + // 关闭全部,只留首页 + function closeAll() { + tabList.value = tabList.value.filter((t) => t.fullPath === defaultDashboardPath); + activeTab.value = defaultDashboardPath; + saveTabsToStorage(tabList.value, activeTab.value); + } + + // 设置激活tab(不触发路由跳转,仅用于更新状态) + function setActiveTab(fullPath) { + activeTab.value = fullPath; + saveTabsToStorage(tabList.value, activeTab.value); + } + + // 重置 tabs store 到初始状态(登出时使用) + function resetTabs() { + tabList.value = [{ title: '首页', fullPath: defaultDashboardPath, name: 'Dashboard' }]; + activeTab.value = defaultDashboardPath; + // 清除 localStorage 中的 tabs 数据 + localStorage.removeItem('tabs_list'); + localStorage.removeItem('active_tab'); + } + + return { + tabList, + activeTab, + addTab, + removeTab, + closeOthers, + closeLeft, + closeRight, + closeAll, + setActiveTab, + saveTabsToStorage, + resetTabs, + }; +}); + +// ========== 菜单 Menu Store ========== export { useMenuStore } from './menu'; \ No newline at end of file diff --git a/platform/src/stores/menu.js b/platform/src/stores/menu.js index 6aa9acc..e368f68 100644 --- a/platform/src/stores/menu.js +++ b/platform/src/stores/menu.js @@ -1,237 +1,237 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -// import { getUserInfo } from '@/utils/auth' -import { getMenus } from '@/api/menu'; - -export const useMenuStore = defineStore('menu', () => { - // 菜单数据 - const menus = ref([]); - - // 加载状态 - const loading = ref(false); - - // 加载错误 - const error = ref(null); - - // 正在加载的 Promise(用于避免重复请求) - let loadingPromise = null; - - // 菜单缓存 key(基于用户类型和角色ID) - const getCacheKey = () => { - try { - const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); - const loginType = userInfo.type || 'user'; - const roleId = userInfo.rid || 0; - return `menu_cache_${loginType}_${roleId}`; - } catch (e) { - return 'menu_cache_default'; - } - }; - - // 从缓存加载菜单 - const loadFromCache = () => { - try { - const cacheKey = getCacheKey(); - const cached = localStorage.getItem(cacheKey); - if (cached) { - const menuData = JSON.parse(cached); - // 检查缓存是否过期(5分钟过期) - if (menuData.timestamp && Date.now() - menuData.timestamp < 5 * 60 * 1000) { - return menuData.menus; - } - } - } catch (e) { - console.warn('加载菜单缓存失败:', e); - } - return null; - }; - - // 保存菜单到缓存 - const saveToCache = (menuData) => { - try { - const cacheKey = getCacheKey(); - localStorage.setItem(cacheKey, JSON.stringify({ - menus: menuData, - timestamp: Date.now() - })); - } catch (e) { - console.warn('保存菜单缓存失败:', e); - } - }; - - // 清除菜单缓存 - const clearCache = () => { - try { - const cacheKey = getCacheKey(); - localStorage.removeItem(cacheKey); - // 也清除其他可能的缓存key(兼容旧代码) - localStorage.removeItem('menu_cache'); - } catch (e) { - console.warn('清除菜单缓存失败:', e); - } - }; - - // 获取用户信息 - const getUserInfo = () => { - try { - return JSON.parse(localStorage.getItem('userInfo') || '{}'); - } catch (e) { - return {}; - } - }; - - // 从 API 加载菜单(核心方法,确保只请求一次) - const fetchMenus = async (forceRefresh = false) => { - // 如果已经有正在加载的请求,直接返回该 Promise - if (loadingPromise && !forceRefresh) { - return loadingPromise; - } - - // 如果不强制刷新,先尝试从缓存加载 - if (!forceRefresh) { - const cachedMenus = loadFromCache(); - if (cachedMenus && cachedMenus.length > 0) { - menus.value = cachedMenus; - return Promise.resolve(cachedMenus); - } - } - - // 如果正在加载且不是强制刷新,返回现有的 Promise - if (loading.value && !forceRefresh) { - return loadingPromise; - } - - // 创建新的加载 Promise - loadingPromise = (async () => { - loading.value = true; - error.value = null; - - try { - const userInfo = getUserInfo(); - const loginType = userInfo.type || 'user'; - const roleId = userInfo.rid || 0; - - let res; - - // 检查用户ID是否存在 - if (!userInfo.id) { - throw new Error('用户ID不存在,请重新登录'); - } - - // 用户登录,使用 getMenus 接口 - res = await getMenus(userInfo.id); - - // 检查响应格式 - if (!res) { - throw new Error('获取菜单失败:服务器无响应'); - } - - // 检查后端返回的 code 字段 - if (res.code !== 200) { - throw new Error(res.msg || '获取菜单失败'); - } - - // 如果 code 为 200,检查 data - if (res.code === 200) { - // data 可能是空数组,这也是有效的 - if (res.data !== undefined && res.data !== null) { - // 确保 data 是数组 - const menuData = Array.isArray(res.data) ? res.data : []; - // 直接使用后端返回的树形结构数据,不需要额外过滤 - menus.value = menuData; - // 保存到缓存 - saveToCache(menuData); - return menuData; - } else { - // data 为 null 或 undefined,使用空数组 - console.warn('菜单数据为空,使用空数组'); - menus.value = []; - saveToCache([]); - return []; - } - } - - // 如果响应格式不符合预期,尝试直接使用 res.data - if (res.data !== undefined) { - const menuData = Array.isArray(res.data) ? res.data : []; - const filtered = menuData.filter(m => (m.isShow ?? 1) !== 0); - menus.value = filtered; - saveToCache(filtered); - return filtered; - } - - // 如果都不符合,抛出错误 - throw new Error(res.message || '获取菜单失败:响应格式错误'); - } catch (err) { - error.value = err.message || '获取菜单失败'; - console.error('获取菜单失败:', err); - console.error('错误详情:', { - message: err.message, - response: err.response, - stack: err.stack - }); - - // 如果是 token 无效错误,不使用缓存,直接抛出 - if (err.message === 'token无效' || err.response?.status === 401) { - clearCache(); - menus.value = []; - throw err; - } - - // 如果出错,尝试使用缓存数据 - const cachedMenus = loadFromCache(); - if (cachedMenus && cachedMenus.length > 0) { - console.warn('使用缓存的菜单数据'); - menus.value = cachedMenus; - return cachedMenus; - } - - // 如果连缓存都没有,设置空数组,避免页面崩溃 - menus.value = []; - throw err; - } finally { - loading.value = false; - loadingPromise = null; - } - })(); - - return loadingPromise; - }; - - // 刷新菜单(强制从 API 获取) - const refreshMenus = async () => { - clearCache(); - return await fetchMenus(true); - }; - - // 重置菜单 store(登出时使用) - const resetMenus = () => { - menus.value = []; - loading.value = false; - error.value = null; - loadingPromise = null; - clearCache(); - }; - - // 计算属性:获取菜单列表 - const menuList = computed(() => menus.value); - - // 计算属性:菜单是否已加载 - const isLoaded = computed(() => menus.value.length > 0); - - return { - // 状态 - menus: menuList, - loading, - error, - isLoaded, - - // 方法 - fetchMenus, - refreshMenus, - resetMenus, - clearCache, - loadFromCache, - }; -}); - +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +// import { getUserInfo } from '@/utils/auth' +import { getMenus } from '@/api/menu'; + +export const useMenuStore = defineStore('menu', () => { + // 菜单数据 + const menus = ref([]); + + // 加载状态 + const loading = ref(false); + + // 加载错误 + const error = ref(null); + + // 正在加载的 Promise(用于避免重复请求) + let loadingPromise = null; + + // 菜单缓存 key(基于用户类型和角色ID) + const getCacheKey = () => { + try { + const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); + const loginType = userInfo.type || 'user'; + const roleId = userInfo.rid || 0; + return `menu_cache_${loginType}_${roleId}`; + } catch (e) { + return 'menu_cache_default'; + } + }; + + // 从缓存加载菜单 + const loadFromCache = () => { + try { + const cacheKey = getCacheKey(); + const cached = localStorage.getItem(cacheKey); + if (cached) { + const menuData = JSON.parse(cached); + // 检查缓存是否过期(5分钟过期) + if (menuData.timestamp && Date.now() - menuData.timestamp < 5 * 60 * 1000) { + return menuData.menus; + } + } + } catch (e) { + console.warn('加载菜单缓存失败:', e); + } + return null; + }; + + // 保存菜单到缓存 + const saveToCache = (menuData) => { + try { + const cacheKey = getCacheKey(); + localStorage.setItem(cacheKey, JSON.stringify({ + menus: menuData, + timestamp: Date.now() + })); + } catch (e) { + console.warn('保存菜单缓存失败:', e); + } + }; + + // 清除菜单缓存 + const clearCache = () => { + try { + const cacheKey = getCacheKey(); + localStorage.removeItem(cacheKey); + // 也清除其他可能的缓存key(兼容旧代码) + localStorage.removeItem('menu_cache'); + } catch (e) { + console.warn('清除菜单缓存失败:', e); + } + }; + + // 获取用户信息 + const getUserInfo = () => { + try { + return JSON.parse(localStorage.getItem('userInfo') || '{}'); + } catch (e) { + return {}; + } + }; + + // 从 API 加载菜单(核心方法,确保只请求一次) + const fetchMenus = async (forceRefresh = false) => { + // 如果已经有正在加载的请求,直接返回该 Promise + if (loadingPromise && !forceRefresh) { + return loadingPromise; + } + + // 如果不强制刷新,先尝试从缓存加载 + if (!forceRefresh) { + const cachedMenus = loadFromCache(); + if (cachedMenus && cachedMenus.length > 0) { + menus.value = cachedMenus; + return Promise.resolve(cachedMenus); + } + } + + // 如果正在加载且不是强制刷新,返回现有的 Promise + if (loading.value && !forceRefresh) { + return loadingPromise; + } + + // 创建新的加载 Promise + loadingPromise = (async () => { + loading.value = true; + error.value = null; + + try { + const userInfo = getUserInfo(); + const loginType = userInfo.type || 'user'; + const roleId = userInfo.rid || 0; + + let res; + + // 检查用户ID是否存在 + if (!userInfo.id) { + throw new Error('用户ID不存在,请重新登录'); + } + + // 用户登录,使用 getMenus 接口 + res = await getMenus(userInfo.id); + + // 检查响应格式 + if (!res) { + throw new Error('获取菜单失败:服务器无响应'); + } + + // 检查后端返回的 code 字段 + if (res.code !== 200) { + throw new Error(res.msg || '获取菜单失败'); + } + + // 如果 code 为 200,检查 data + if (res.code === 200) { + // data 可能是空数组,这也是有效的 + if (res.data !== undefined && res.data !== null) { + // 确保 data 是数组 + const menuData = Array.isArray(res.data) ? res.data : []; + // 直接使用后端返回的树形结构数据,不需要额外过滤 + menus.value = menuData; + // 保存到缓存 + saveToCache(menuData); + return menuData; + } else { + // data 为 null 或 undefined,使用空数组 + console.warn('菜单数据为空,使用空数组'); + menus.value = []; + saveToCache([]); + return []; + } + } + + // 如果响应格式不符合预期,尝试直接使用 res.data + if (res.data !== undefined) { + const menuData = Array.isArray(res.data) ? res.data : []; + const filtered = menuData.filter(m => (m.isShow ?? 1) !== 0); + menus.value = filtered; + saveToCache(filtered); + return filtered; + } + + // 如果都不符合,抛出错误 + throw new Error(res.message || '获取菜单失败:响应格式错误'); + } catch (err) { + error.value = err.message || '获取菜单失败'; + console.error('获取菜单失败:', err); + console.error('错误详情:', { + message: err.message, + response: err.response, + stack: err.stack + }); + + // 如果是 token 无效错误,不使用缓存,直接抛出 + if (err.message === 'token无效' || err.response?.status === 401) { + clearCache(); + menus.value = []; + throw err; + } + + // 如果出错,尝试使用缓存数据 + const cachedMenus = loadFromCache(); + if (cachedMenus && cachedMenus.length > 0) { + console.warn('使用缓存的菜单数据'); + menus.value = cachedMenus; + return cachedMenus; + } + + // 如果连缓存都没有,设置空数组,避免页面崩溃 + menus.value = []; + throw err; + } finally { + loading.value = false; + loadingPromise = null; + } + })(); + + return loadingPromise; + }; + + // 刷新菜单(强制从 API 获取) + const refreshMenus = async () => { + clearCache(); + return await fetchMenus(true); + }; + + // 重置菜单 store(登出时使用) + const resetMenus = () => { + menus.value = []; + loading.value = false; + error.value = null; + loadingPromise = null; + clearCache(); + }; + + // 计算属性:获取菜单列表 + const menuList = computed(() => menus.value); + + // 计算属性:菜单是否已加载 + const isLoaded = computed(() => menus.value.length > 0); + + return { + // 状态 + menus: menuList, + loading, + error, + isLoaded, + + // 方法 + fetchMenus, + refreshMenus, + resetMenus, + clearCache, + loadFromCache, + }; +}); + diff --git a/platform/src/types/vue-cropper.d.ts b/platform/src/types/vue-cropper.d.ts index 8877070..e13cf12 100644 --- a/platform/src/types/vue-cropper.d.ts +++ b/platform/src/types/vue-cropper.d.ts @@ -1,4 +1,4 @@ -declare module 'vue-cropper' { - import { Component } from 'vue' - export const VueCropper: Component -} +declare module 'vue-cropper' { + import { Component } from 'vue' + export const VueCropper: Component +} diff --git a/platform/src/utils/pathResolver.js b/platform/src/utils/pathResolver.js index 3ce2eea..213bfda 100644 --- a/platform/src/utils/pathResolver.js +++ b/platform/src/utils/pathResolver.js @@ -1,95 +1,95 @@ -/** - * 通用的别名路径解析工具 - * 用于在动态导入时解析 @ 别名路径 - */ -import { h } from 'vue'; - -// 使用 import.meta.glob 预加载所有组件 -const viewsModules = import.meta.glob('../views/**/*.vue'); - -// 创建路径映射表 -const pathMap = new Map(); - -// 初始化路径映射 -Object.keys(viewsModules).forEach(relativePath => { - // relativePath 示例: ../views/system/users.vue - - // 统一去掉扩展名进行存储,方便各种格式匹配 - const baseNoExt = relativePath.replace('../views/', '').replace('.vue', ''); - const baseWithExt = relativePath.replace('../views/', ''); - - // 1. 存储标准路径 - pathMap.set(relativePath, viewsModules[relativePath]); - // 2. 存储 @/views 路径 - pathMap.set(relativePath.replace('../views', '@/views'), viewsModules[relativePath]); - // 3. 存储 /system/users 格式(不带扩展名) - pathMap.set(`/${baseNoExt}`, viewsModules[relativePath]); - // 4. 存储 system/users 格式(不带扩展名) - pathMap.set(baseNoExt, viewsModules[relativePath]); - // 5. 存储 /system/users.vue 格式(带扩展名) - pathMap.set(`/${baseWithExt}`, viewsModules[relativePath]); - // 6. 存储 system/users.vue 格式(带扩展名) - pathMap.set(baseWithExt, viewsModules[relativePath]); -}); - -/** - * 解析别名路径为实际模块加载器 - * @param {string} path - 支持的路径格式: - * - @/views/dashboard/index.vue (别名格式) - * - /dashboard/index.vue (数据库格式,带前导斜杠) - * - dashboard/index.vue (相对格式) - * @returns {Function|null} 返回模块加载器函数,找不到时返回 null - */ -export function resolveComponent(path) { - if (!path) return null; - - // 预处理 path:去掉可能的 .vue 后缀统一查找 - const cleanPath = path.replace('.vue', ''); - - // 尝试直接匹配 - const loader = pathMap.get(path) || pathMap.get(cleanPath); - if (loader) return loader; - - // 数据库格式补全匹配 (针对 /system/users) - const dbFormat = cleanPath.startsWith('/') ? cleanPath : `/${cleanPath}`; - if (pathMap.get(dbFormat)) return pathMap.get(dbFormat); - - // 模糊匹配:文件名匹配 - const fileName = cleanPath.split('/').pop(); - for (const [mappedPath, loader] of pathMap.entries()) { - if (mappedPath.endsWith(`${fileName}.vue`) || mappedPath.endsWith(fileName)) { - return loader; - } - } - return null; -} - -/** - * 创建组件加载器 - * @param {string} componentPath - 组件路径 - * @returns {Function} Vue 路由组件加载函数 - */ -export function createComponentLoader(componentPath) { - const loader = resolveComponent(componentPath); - if (loader) return loader; - - console.error(`❌ [路由错误] 未找到组件: ${componentPath}`); - - // 返回一个标准的 Vue 组件对象,确保 Router 不报错 - return () => Promise.resolve({ - name: 'ComponentNotFound', - render: () => { - import('element-plus').then(El => El.ElMessage.error(`路径错误: ${componentPath}`)); - return h('div', { style: 'padding:20px; color:red;' }, `组件路径不存在: ${componentPath}`); - } - }); -} - -/** - * 获取所有已加载的模块路径(用于调试) - * @returns {Array} 所有可用的路径列表 - */ -export function getAllModulePaths() { - return Array.from(pathMap.keys()); -} - +/** + * 通用的别名路径解析工具 + * 用于在动态导入时解析 @ 别名路径 + */ +import { h } from 'vue'; + +// 使用 import.meta.glob 预加载所有组件 +const viewsModules = import.meta.glob('../views/**/*.vue'); + +// 创建路径映射表 +const pathMap = new Map(); + +// 初始化路径映射 +Object.keys(viewsModules).forEach(relativePath => { + // relativePath 示例: ../views/system/users.vue + + // 统一去掉扩展名进行存储,方便各种格式匹配 + const baseNoExt = relativePath.replace('../views/', '').replace('.vue', ''); + const baseWithExt = relativePath.replace('../views/', ''); + + // 1. 存储标准路径 + pathMap.set(relativePath, viewsModules[relativePath]); + // 2. 存储 @/views 路径 + pathMap.set(relativePath.replace('../views', '@/views'), viewsModules[relativePath]); + // 3. 存储 /system/users 格式(不带扩展名) + pathMap.set(`/${baseNoExt}`, viewsModules[relativePath]); + // 4. 存储 system/users 格式(不带扩展名) + pathMap.set(baseNoExt, viewsModules[relativePath]); + // 5. 存储 /system/users.vue 格式(带扩展名) + pathMap.set(`/${baseWithExt}`, viewsModules[relativePath]); + // 6. 存储 system/users.vue 格式(带扩展名) + pathMap.set(baseWithExt, viewsModules[relativePath]); +}); + +/** + * 解析别名路径为实际模块加载器 + * @param {string} path - 支持的路径格式: + * - @/views/dashboard/index.vue (别名格式) + * - /dashboard/index.vue (数据库格式,带前导斜杠) + * - dashboard/index.vue (相对格式) + * @returns {Function|null} 返回模块加载器函数,找不到时返回 null + */ +export function resolveComponent(path) { + if (!path) return null; + + // 预处理 path:去掉可能的 .vue 后缀统一查找 + const cleanPath = path.replace('.vue', ''); + + // 尝试直接匹配 + const loader = pathMap.get(path) || pathMap.get(cleanPath); + if (loader) return loader; + + // 数据库格式补全匹配 (针对 /system/users) + const dbFormat = cleanPath.startsWith('/') ? cleanPath : `/${cleanPath}`; + if (pathMap.get(dbFormat)) return pathMap.get(dbFormat); + + // 模糊匹配:文件名匹配 + const fileName = cleanPath.split('/').pop(); + for (const [mappedPath, loader] of pathMap.entries()) { + if (mappedPath.endsWith(`${fileName}.vue`) || mappedPath.endsWith(fileName)) { + return loader; + } + } + return null; +} + +/** + * 创建组件加载器 + * @param {string} componentPath - 组件路径 + * @returns {Function} Vue 路由组件加载函数 + */ +export function createComponentLoader(componentPath) { + const loader = resolveComponent(componentPath); + if (loader) return loader; + + console.error(`❌ [路由错误] 未找到组件: ${componentPath}`); + + // 返回一个标准的 Vue 组件对象,确保 Router 不报错 + return () => Promise.resolve({ + name: 'ComponentNotFound', + render: () => { + import('element-plus').then(El => El.ElMessage.error(`路径错误: ${componentPath}`)); + return h('div', { style: 'padding:20px; color:red;' }, `组件路径不存在: ${componentPath}`); + } + }); +} + +/** + * 获取所有已加载的模块路径(用于调试) + * @returns {Array} 所有可用的路径列表 + */ +export function getAllModulePaths() { + return Array.from(pathMap.keys()); +} + diff --git a/platform/src/utils/qiniuUpload.js b/platform/src/utils/qiniuUpload.js index 1584b16..d584286 100644 --- a/platform/src/utils/qiniuUpload.js +++ b/platform/src/utils/qiniuUpload.js @@ -1,252 +1,252 @@ -import request from '@/utils/request'; -import * as qiniu from 'qiniu-js'; - -/** - * 获取存储配置 - * @returns {Promise<{storageType: string, qiniuDomain?: string, qiniuRegion?: string}>} - */ -export async function getStorageConfig() { - const res = await request({ - url: '/platform/storage/config', - method: 'get', - }); - if (res?.code === 200) { - return res.data || { storageType: 'local' }; - } - return { storageType: 'local' }; -} - -/** - * 获取七牛云上传凭证 - * @returns {Promise} - */ -export async function getQiniuToken() { - return request({ - url: '/platform/qiniu/token', - method: 'get', - }); -} - -/** - * 保存文件记录到数据库 - * @param {Object} data 文件信息 - * @returns {Promise} - */ -export async function saveFileRecord(data) { - return request({ - url: '/platform/qiniu/save', - method: 'post', - data, - }); -} - -/** - * 上传文件(自动选择本地或七牛云) - * @param {File} file 文件对象 - * @param {Object} options 配置选项 - * @param {number} [options.cate] 文件分类 - * @param {Function} [options.onProgress] 进度回调 - * @returns {Promise<{url: string, id: number, name: string, key?: string}>} - */ -export async function smartUpload(file, options = {}) { - // 获取存储配置 - const config = await getStorageConfig(); - - if (config.storageType === 'qiniu') { - // 使用七牛云直传 - return uploadToQiniu(file, options); - } else { - // 使用本地上传(通过后端) - return uploadToLocal(file, options); - } -} - -/** - * 上传到七牛云(直传) - * @param {File} file 文件对象 - * @param {Object} options 配置选项 - * @returns {Promise} - */ -export async function uploadToQiniu(file, options = {}) { - // 1. 获取上传凭证 - const tokenRes = await getQiniuToken(); - if (tokenRes?.code !== 200) { - throw new Error(tokenRes?.msg || '获取上传凭证失败'); - } - - const { token, keyPrefix, domain, region, uploadUrl } = tokenRes.data; - - // 2. 生成文件 key - const ext = file.name.split('.').pop(); - const key = `${keyPrefix}.${ext}`; - - // 3. 配置上传参数 - const putExtra = { - fname: file.name, - mimeType: file.type || 'application/octet-stream', - }; - - // 4. 根据区域代码获取七牛云区域对象 - const qiniuRegion = getQiniuRegion(region); - - const config = { - useCdnDomain: true, - region: qiniuRegion, - }; - - // 5. 创建 observable 对象 - const observable = qiniu.upload(file, key, token, putExtra, config); - - // 5. 执行上传 - return new Promise((resolve, reject) => { - const subscription = observable.subscribe({ - next(res) { - // 进度回调 - if (options.onProgress) { - options.onProgress({ - loaded: res.total.loaded, - total: res.total.size, - percent: res.total.percent, - }); - } - }, - error(err) { - reject(new Error(err.message || '上传失败')); - }, - async complete(res) { - try { - // 6. 保存文件记录到数据库 - const saveRes = await saveFileRecord({ - key: res.key, - hash: res.hash, - size: file.size, - name: file.name, - mimeType: file.type, - cate: options.cate || 0, - }); - - if (saveRes?.code === 200 || saveRes?.code === 201) { - resolve({ - url: saveRes.data.url, - id: saveRes.data.id, - name: saveRes.data.name, - key: saveRes.data.key, - }); - } else { - reject(new Error(saveRes?.msg || '保存文件记录失败')); - } - } catch (error) { - reject(error); - } - }, - }); - }); -} - -/** - * 上传到本地(通过后端中转) - * @param {File} file 文件对象 - * @param {Object} options 配置选项 - * @returns {Promise} - */ -export async function uploadToLocal(file, options = {}) { - const formData = new FormData(); - formData.append('file', file); - - if (options.cate !== undefined) { - formData.append('cate', String(options.cate)); - } - - const config = { - url: '/platform/uploadfile', - method: 'post', - data: formData, - timeout: 0, // 不设置超时 - }; - - if (options.onProgress) { - config.onUploadProgress = (e) => { - options.onProgress({ - loaded: e.loaded, - total: e.total || 0, - percent: e.total > 0 ? Math.round((e.loaded * 100) / e.total) : 0, - }); - }; - } - - const res = await request(config); - - if (res?.code === 200 || res?.code === 201) { - return { - url: res.data.url, - id: res.data.id, - name: res.data.name, - }; - } else { - throw new Error(res?.msg || '上传失败'); - } -} - -/** - * 批量上传文件 - * @param {File[]} files 文件数组 - * @param {Object} options 配置选项 - * @param {Function} [options.onFileProgress] 单个文件进度回调 (file, progress) => void - * @param {Function} [options.onFileComplete] 单个文件完成回调 (file, result) => void - * @param {Function} [options.onFileError] 单个文件错误回调 (file, error) => void - * @returns {Promise} - */ -export async function batchUpload(files, options = {}) { - const results = []; - - for (const file of files) { - try { - const result = await smartUpload(file, { - ...options, - onProgress: (progress) => { - if (options.onFileProgress) { - options.onFileProgress(file, progress); - } - }, - }); - - results.push({ file, result, success: true }); - - if (options.onFileComplete) { - options.onFileComplete(file, result); - } - } catch (error) { - results.push({ file, error, success: false }); - - if (options.onFileError) { - options.onFileError(file, error); - } - } - } - - return results; -} - -/** - * 根据区域代码获取七牛云区域对象 - * @param {string} regionCode 区域代码 (z0, z1, z2, na0, as0, cn-east-2) - * @returns {Object} 七牛云区域对象 - */ -function getQiniuRegion(regionCode) { - switch (regionCode) { - case 'z0': - return qiniu.region.z0; // 华东 - case 'z1': - return qiniu.region.z1; // 华北 - case 'z2': - return qiniu.region.z2; // 华南 - case 'na0': - return qiniu.region.na0; // 北美 - case 'as0': - return qiniu.region.as0; // 新加坡 - case 'cn-east-2': - return qiniu.region.cnEast2; // 华东-浙江2 - default: - return qiniu.region.z0; // 默认华东 - } -} +import request from '@/utils/request'; +import * as qiniu from 'qiniu-js'; + +/** + * 获取存储配置 + * @returns {Promise<{storageType: string, qiniuDomain?: string, qiniuRegion?: string}>} + */ +export async function getStorageConfig() { + const res = await request({ + url: '/platform/storage/config', + method: 'get', + }); + if (res?.code === 200) { + return res.data || { storageType: 'local' }; + } + return { storageType: 'local' }; +} + +/** + * 获取七牛云上传凭证 + * @returns {Promise} + */ +export async function getQiniuToken() { + return request({ + url: '/platform/qiniu/token', + method: 'get', + }); +} + +/** + * 保存文件记录到数据库 + * @param {Object} data 文件信息 + * @returns {Promise} + */ +export async function saveFileRecord(data) { + return request({ + url: '/platform/qiniu/save', + method: 'post', + data, + }); +} + +/** + * 上传文件(自动选择本地或七牛云) + * @param {File} file 文件对象 + * @param {Object} options 配置选项 + * @param {number} [options.cate] 文件分类 + * @param {Function} [options.onProgress] 进度回调 + * @returns {Promise<{url: string, id: number, name: string, key?: string}>} + */ +export async function smartUpload(file, options = {}) { + // 获取存储配置 + const config = await getStorageConfig(); + + if (config.storageType === 'qiniu') { + // 使用七牛云直传 + return uploadToQiniu(file, options); + } else { + // 使用本地上传(通过后端) + return uploadToLocal(file, options); + } +} + +/** + * 上传到七牛云(直传) + * @param {File} file 文件对象 + * @param {Object} options 配置选项 + * @returns {Promise} + */ +export async function uploadToQiniu(file, options = {}) { + // 1. 获取上传凭证 + const tokenRes = await getQiniuToken(); + if (tokenRes?.code !== 200) { + throw new Error(tokenRes?.msg || '获取上传凭证失败'); + } + + const { token, keyPrefix, domain, region, uploadUrl } = tokenRes.data; + + // 2. 生成文件 key + const ext = file.name.split('.').pop(); + const key = `${keyPrefix}.${ext}`; + + // 3. 配置上传参数 + const putExtra = { + fname: file.name, + mimeType: file.type || 'application/octet-stream', + }; + + // 4. 根据区域代码获取七牛云区域对象 + const qiniuRegion = getQiniuRegion(region); + + const config = { + useCdnDomain: true, + region: qiniuRegion, + }; + + // 5. 创建 observable 对象 + const observable = qiniu.upload(file, key, token, putExtra, config); + + // 5. 执行上传 + return new Promise((resolve, reject) => { + const subscription = observable.subscribe({ + next(res) { + // 进度回调 + if (options.onProgress) { + options.onProgress({ + loaded: res.total.loaded, + total: res.total.size, + percent: res.total.percent, + }); + } + }, + error(err) { + reject(new Error(err.message || '上传失败')); + }, + async complete(res) { + try { + // 6. 保存文件记录到数据库 + const saveRes = await saveFileRecord({ + key: res.key, + hash: res.hash, + size: file.size, + name: file.name, + mimeType: file.type, + cate: options.cate || 0, + }); + + if (saveRes?.code === 200 || saveRes?.code === 201) { + resolve({ + url: saveRes.data.url, + id: saveRes.data.id, + name: saveRes.data.name, + key: saveRes.data.key, + }); + } else { + reject(new Error(saveRes?.msg || '保存文件记录失败')); + } + } catch (error) { + reject(error); + } + }, + }); + }); +} + +/** + * 上传到本地(通过后端中转) + * @param {File} file 文件对象 + * @param {Object} options 配置选项 + * @returns {Promise} + */ +export async function uploadToLocal(file, options = {}) { + const formData = new FormData(); + formData.append('file', file); + + if (options.cate !== undefined) { + formData.append('cate', String(options.cate)); + } + + const config = { + url: '/platform/uploadfile', + method: 'post', + data: formData, + timeout: 0, // 不设置超时 + }; + + if (options.onProgress) { + config.onUploadProgress = (e) => { + options.onProgress({ + loaded: e.loaded, + total: e.total || 0, + percent: e.total > 0 ? Math.round((e.loaded * 100) / e.total) : 0, + }); + }; + } + + const res = await request(config); + + if (res?.code === 200 || res?.code === 201) { + return { + url: res.data.url, + id: res.data.id, + name: res.data.name, + }; + } else { + throw new Error(res?.msg || '上传失败'); + } +} + +/** + * 批量上传文件 + * @param {File[]} files 文件数组 + * @param {Object} options 配置选项 + * @param {Function} [options.onFileProgress] 单个文件进度回调 (file, progress) => void + * @param {Function} [options.onFileComplete] 单个文件完成回调 (file, result) => void + * @param {Function} [options.onFileError] 单个文件错误回调 (file, error) => void + * @returns {Promise} + */ +export async function batchUpload(files, options = {}) { + const results = []; + + for (const file of files) { + try { + const result = await smartUpload(file, { + ...options, + onProgress: (progress) => { + if (options.onFileProgress) { + options.onFileProgress(file, progress); + } + }, + }); + + results.push({ file, result, success: true }); + + if (options.onFileComplete) { + options.onFileComplete(file, result); + } + } catch (error) { + results.push({ file, error, success: false }); + + if (options.onFileError) { + options.onFileError(file, error); + } + } + } + + return results; +} + +/** + * 根据区域代码获取七牛云区域对象 + * @param {string} regionCode 区域代码 (z0, z1, z2, na0, as0, cn-east-2) + * @returns {Object} 七牛云区域对象 + */ +function getQiniuRegion(regionCode) { + switch (regionCode) { + case 'z0': + return qiniu.region.z0; // 华东 + case 'z1': + return qiniu.region.z1; // 华北 + case 'z2': + return qiniu.region.z2; // 华南 + case 'na0': + return qiniu.region.na0; // 北美 + case 'as0': + return qiniu.region.as0; // 新加坡 + case 'cn-east-2': + return qiniu.region.cnEast2; // 华东-浙江2 + default: + return qiniu.region.z0; // 默认华东 + } +} diff --git a/platform/src/utils/request.js b/platform/src/utils/request.js index 4d632d6..f84d0a8 100644 --- a/platform/src/utils/request.js +++ b/platform/src/utils/request.js @@ -1,68 +1,68 @@ -import axios from 'axios'; - -// 获取API基础URL;开发环境可在 .env.development 留空,配合 Vite 代理访问 /platform -const apiBaseURL = import.meta.env.VITE_API_BASE_URL ?? ""; - -// 创建axios实例(普通接口 5min;大文件上传在 api/file.js 单独更长 timeout) -const service = axios.create({ - baseURL: apiBaseURL, - timeout: 300000, - withCredentials: false // JWT 不需要 Cookie -}); - -// 请求拦截器 -service.interceptors.request.use( - config => { - const token = localStorage.getItem('token'); - if (token) { - config.headers['Authorization'] = `Bearer ${token}`; - } - - // 对于有 body 的请求(POST、PUT、PATCH),默认 JSON;FormData 由浏览器带 multipart boundary,不可手写 Content-Type - if (config.data && ['post', 'put', 'patch'].includes(config.method?.toLowerCase())) { - if (config.data instanceof FormData) { - delete config.headers['Content-Type']; - delete config.headers['content-type']; - } else if (!config.headers['Content-Type'] && !config.headers['content-type']) { - config.headers['Content-Type'] = 'application/json'; - } - } - return config; - }, - error => { - return Promise.reject(error); - } -); - -// 响应拦截器 -service.interceptors.response.use( - response => { - return response.data; - }, - error => { - if (error.response) { - switch (error.response.status) { - case 401: - console.error('未授权,请重新登录'); - localStorage.removeItem('token'); - localStorage.removeItem('userInfo'); - if (window.location.hash !== '#/login') { - window.location.href = '#/login'; - } - return Promise.reject(new Error('token无效')); - case 404: - console.error('请求的资源不存在'); - break; - default: - console.error('请求失败,请稍后再试'); - } - } else if (error.request) { - console.error('请求失败,请检查网络连接'); - } else { - console.error('请求配置错误'); - } - return Promise.reject(error); - } -); - +import axios from 'axios'; + +// 获取API基础URL;开发环境可在 .env.development 留空,配合 Vite 代理访问 /platform +const apiBaseURL = import.meta.env.VITE_API_BASE_URL ?? ""; + +// 创建axios实例(普通接口 5min;大文件上传在 api/file.js 单独更长 timeout) +const service = axios.create({ + baseURL: apiBaseURL, + timeout: 300000, + withCredentials: false // JWT 不需要 Cookie +}); + +// 请求拦截器 +service.interceptors.request.use( + config => { + const token = localStorage.getItem('token'); + if (token) { + config.headers['Authorization'] = `Bearer ${token}`; + } + + // 对于有 body 的请求(POST、PUT、PATCH),默认 JSON;FormData 由浏览器带 multipart boundary,不可手写 Content-Type + if (config.data && ['post', 'put', 'patch'].includes(config.method?.toLowerCase())) { + if (config.data instanceof FormData) { + delete config.headers['Content-Type']; + delete config.headers['content-type']; + } else if (!config.headers['Content-Type'] && !config.headers['content-type']) { + config.headers['Content-Type'] = 'application/json'; + } + } + return config; + }, + error => { + return Promise.reject(error); + } +); + +// 响应拦截器 +service.interceptors.response.use( + response => { + return response.data; + }, + error => { + if (error.response) { + switch (error.response.status) { + case 401: + console.error('未授权,请重新登录'); + localStorage.removeItem('token'); + localStorage.removeItem('userInfo'); + if (window.location.hash !== '#/login') { + window.location.href = '#/login'; + } + return Promise.reject(new Error('token无效')); + case 404: + console.error('请求的资源不存在'); + break; + default: + console.error('请求失败,请稍后再试'); + } + } else if (error.request) { + console.error('请求失败,请检查网络连接'); + } else { + console.error('请求配置错误'); + } + return Promise.reject(error); + } +); + export default service; \ No newline at end of file diff --git a/platform/src/utils/url.js b/platform/src/utils/url.js index f10dee1..a5955d5 100644 --- a/platform/src/utils/url.js +++ b/platform/src/utils/url.js @@ -1,42 +1,42 @@ -/** - * URL工具函数 - */ - -/** - * 获取完整的文件URL - * 如果URL已经是完整URL(http://或https://开头),直接返回 - * 否则拼接API基础URL - * @param {string} url - 文件URL或路径 - * @returns {string} 完整的URL - */ -export function getFileUrl(url) { - if (!url) return ''; - - // 如果URL已经是完整的URL(以http://或https://开头),直接返回 - if (url.startsWith('http://') || url.startsWith('https://')) { - return url; - } - - // 否则拼接API基础URL - const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; - return `${API_BASE_URL}${url}`; -} - -/** - * 获取环境URL(getEnvUrl的别名) - * @param {string} path - 文件路径 - * @returns {string} 完整的URL - */ -export function getEnvUrl(path) { - return getFileUrl(path); -} - -/** - * 判断URL是否是完整URL - * @param {string} url - URL字符串 - * @returns {boolean} 是否是完整URL - */ -export function isFullUrl(url) { - if (!url) return false; - return url.startsWith('http://') || url.startsWith('https://'); -} +/** + * URL工具函数 + */ + +/** + * 获取完整的文件URL + * 如果URL已经是完整URL(http://或https://开头),直接返回 + * 否则拼接API基础URL + * @param {string} url - 文件URL或路径 + * @returns {string} 完整的URL + */ +export function getFileUrl(url) { + if (!url) return ''; + + // 如果URL已经是完整的URL(以http://或https://开头),直接返回 + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + + // 否则拼接API基础URL + const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; + return `${API_BASE_URL}${url}`; +} + +/** + * 获取环境URL(getEnvUrl的别名) + * @param {string} path - 文件路径 + * @returns {string} 完整的URL + */ +export function getEnvUrl(path) { + return getFileUrl(path); +} + +/** + * 判断URL是否是完整URL + * @param {string} url - URL字符串 + * @returns {boolean} 是否是完整URL + */ +export function isFullUrl(url) { + if (!url) return false; + return url.startsWith('http://') || url.startsWith('https://'); +} diff --git a/platform/src/views/404/404.vue b/platform/src/views/404/404.vue index c050a6d..bbab10e 100644 --- a/platform/src/views/404/404.vue +++ b/platform/src/views/404/404.vue @@ -1,93 +1,93 @@ - - - - - + + + + + diff --git a/platform/src/views/Main.vue b/platform/src/views/Main.vue index dec11b2..3bab5fb 100644 --- a/platform/src/views/Main.vue +++ b/platform/src/views/Main.vue @@ -1,750 +1,750 @@ - - - - - - - + + + + + + + diff --git a/platform/src/views/accountpool/codex/components/detail.vue b/platform/src/views/accountpool/codex/components/detail.vue index 37bed82..5737e59 100644 --- a/platform/src/views/accountpool/codex/components/detail.vue +++ b/platform/src/views/accountpool/codex/components/detail.vue @@ -1,600 +1,600 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/codex/components/edit.vue b/platform/src/views/accountpool/codex/components/edit.vue index 8f8c2d1..4fb75b2 100644 --- a/platform/src/views/accountpool/codex/components/edit.vue +++ b/platform/src/views/accountpool/codex/components/edit.vue @@ -1,259 +1,259 @@ - - - + + + diff --git a/platform/src/views/accountpool/codex/components/extract.vue b/platform/src/views/accountpool/codex/components/extract.vue index 476efb2..ee2f56a 100644 --- a/platform/src/views/accountpool/codex/components/extract.vue +++ b/platform/src/views/accountpool/codex/components/extract.vue @@ -1,103 +1,103 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/codex/components/replenish.vue b/platform/src/views/accountpool/codex/components/replenish.vue index 1f733de..a7ce6d2 100644 --- a/platform/src/views/accountpool/codex/components/replenish.vue +++ b/platform/src/views/accountpool/codex/components/replenish.vue @@ -1,49 +1,49 @@ - - - + + + diff --git a/platform/src/views/accountpool/codex/index.vue b/platform/src/views/accountpool/codex/index.vue index 01a5569..11a827e 100644 --- a/platform/src/views/accountpool/codex/index.vue +++ b/platform/src/views/accountpool/codex/index.vue @@ -1,1045 +1,1045 @@ - - - - - - - + + + + + + + diff --git a/platform/src/views/accountpool/components/patch.vue b/platform/src/views/accountpool/components/patch.vue index a6bdcd1..13e3890 100644 --- a/platform/src/views/accountpool/components/patch.vue +++ b/platform/src/views/accountpool/components/patch.vue @@ -1,204 +1,204 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/cursor/components/detail.vue b/platform/src/views/accountpool/cursor/components/detail.vue index 4c7d762..ad14915 100644 --- a/platform/src/views/accountpool/cursor/components/detail.vue +++ b/platform/src/views/accountpool/cursor/components/detail.vue @@ -1,772 +1,772 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/cursor/components/edit.vue b/platform/src/views/accountpool/cursor/components/edit.vue index dc58fa0..fdf8840 100644 --- a/platform/src/views/accountpool/cursor/components/edit.vue +++ b/platform/src/views/accountpool/cursor/components/edit.vue @@ -1,267 +1,267 @@ - - - + + + diff --git a/platform/src/views/accountpool/cursor/components/extract.vue b/platform/src/views/accountpool/cursor/components/extract.vue index 973d7e6..77707ea 100644 --- a/platform/src/views/accountpool/cursor/components/extract.vue +++ b/platform/src/views/accountpool/cursor/components/extract.vue @@ -1,131 +1,131 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/cursor/components/replenish.vue b/platform/src/views/accountpool/cursor/components/replenish.vue index 1f733de..a7ce6d2 100644 --- a/platform/src/views/accountpool/cursor/components/replenish.vue +++ b/platform/src/views/accountpool/cursor/components/replenish.vue @@ -1,49 +1,49 @@ - - - + + + diff --git a/platform/src/views/accountpool/cursor/index.vue b/platform/src/views/accountpool/cursor/index.vue index 7107fad..c30d488 100644 --- a/platform/src/views/accountpool/cursor/index.vue +++ b/platform/src/views/accountpool/cursor/index.vue @@ -1,1773 +1,1773 @@ - - - - - - - + + + + + + + diff --git a/platform/src/views/accountpool/index.vue b/platform/src/views/accountpool/index.vue index 2fa6465..2d8f283 100644 --- a/platform/src/views/accountpool/index.vue +++ b/platform/src/views/accountpool/index.vue @@ -1,11 +1,11 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/accountpool/kiro/components/detail.vue b/platform/src/views/accountpool/kiro/components/detail.vue index 37bed82..5737e59 100644 --- a/platform/src/views/accountpool/kiro/components/detail.vue +++ b/platform/src/views/accountpool/kiro/components/detail.vue @@ -1,600 +1,600 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/kiro/components/edit.vue b/platform/src/views/accountpool/kiro/components/edit.vue index 8f8c2d1..4fb75b2 100644 --- a/platform/src/views/accountpool/kiro/components/edit.vue +++ b/platform/src/views/accountpool/kiro/components/edit.vue @@ -1,259 +1,259 @@ - - - + + + diff --git a/platform/src/views/accountpool/kiro/components/extract.vue b/platform/src/views/accountpool/kiro/components/extract.vue index 476efb2..ee2f56a 100644 --- a/platform/src/views/accountpool/kiro/components/extract.vue +++ b/platform/src/views/accountpool/kiro/components/extract.vue @@ -1,103 +1,103 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/kiro/components/replenish.vue b/platform/src/views/accountpool/kiro/components/replenish.vue index 1f733de..a7ce6d2 100644 --- a/platform/src/views/accountpool/kiro/components/replenish.vue +++ b/platform/src/views/accountpool/kiro/components/replenish.vue @@ -1,49 +1,49 @@ - - - + + + diff --git a/platform/src/views/accountpool/kiro/index.vue b/platform/src/views/accountpool/kiro/index.vue index d035ed3..935e3ab 100644 --- a/platform/src/views/accountpool/kiro/index.vue +++ b/platform/src/views/accountpool/kiro/index.vue @@ -1,1044 +1,1044 @@ - - - - - - - + + + + + + + diff --git a/platform/src/views/accountpool/windsurf/components/detail.vue b/platform/src/views/accountpool/windsurf/components/detail.vue index 37bed82..5737e59 100644 --- a/platform/src/views/accountpool/windsurf/components/detail.vue +++ b/platform/src/views/accountpool/windsurf/components/detail.vue @@ -1,600 +1,600 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/windsurf/components/edit.vue b/platform/src/views/accountpool/windsurf/components/edit.vue index 8f8c2d1..4fb75b2 100644 --- a/platform/src/views/accountpool/windsurf/components/edit.vue +++ b/platform/src/views/accountpool/windsurf/components/edit.vue @@ -1,259 +1,259 @@ - - - + + + diff --git a/platform/src/views/accountpool/windsurf/components/extract.vue b/platform/src/views/accountpool/windsurf/components/extract.vue index 476efb2..ee2f56a 100644 --- a/platform/src/views/accountpool/windsurf/components/extract.vue +++ b/platform/src/views/accountpool/windsurf/components/extract.vue @@ -1,103 +1,103 @@ - - - - - + + + + + diff --git a/platform/src/views/accountpool/windsurf/components/replenish.vue b/platform/src/views/accountpool/windsurf/components/replenish.vue index 1f733de..a7ce6d2 100644 --- a/platform/src/views/accountpool/windsurf/components/replenish.vue +++ b/platform/src/views/accountpool/windsurf/components/replenish.vue @@ -1,49 +1,49 @@ - - - + + + diff --git a/platform/src/views/accountpool/windsurf/index.vue b/platform/src/views/accountpool/windsurf/index.vue index a884b89..86529c9 100644 --- a/platform/src/views/accountpool/windsurf/index.vue +++ b/platform/src/views/accountpool/windsurf/index.vue @@ -1,834 +1,834 @@ - - - - - - - + + + + + + + diff --git a/platform/src/views/analytics/users/index.vue b/platform/src/views/analytics/users/index.vue index 0978019..a2a4b4e 100644 --- a/platform/src/views/analytics/users/index.vue +++ b/platform/src/views/analytics/users/index.vue @@ -1,233 +1,233 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/apps/babyhealth/babys/components/bindParents.vue b/platform/src/views/apps/babyhealth/babys/components/bindParents.vue index a381229..51d59de 100644 --- a/platform/src/views/apps/babyhealth/babys/components/bindParents.vue +++ b/platform/src/views/apps/babyhealth/babys/components/bindParents.vue @@ -1,369 +1,369 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/babyhealth/babys/components/edit.vue b/platform/src/views/apps/babyhealth/babys/components/edit.vue index 7dac238..f58a572 100644 --- a/platform/src/views/apps/babyhealth/babys/components/edit.vue +++ b/platform/src/views/apps/babyhealth/babys/components/edit.vue @@ -1,428 +1,428 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/babyhealth/babys/index.vue b/platform/src/views/apps/babyhealth/babys/index.vue index 5ac0ba6..7f59a83 100644 --- a/platform/src/views/apps/babyhealth/babys/index.vue +++ b/platform/src/views/apps/babyhealth/babys/index.vue @@ -1,400 +1,400 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/babyhealth/dashborad/index.vue b/platform/src/views/apps/babyhealth/dashborad/index.vue index 2c0c28c..6a81aa4 100644 --- a/platform/src/views/apps/babyhealth/dashborad/index.vue +++ b/platform/src/views/apps/babyhealth/dashborad/index.vue @@ -1,293 +1,293 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/babyhealth/index.vue b/platform/src/views/apps/babyhealth/index.vue index 3ea5af6..1644a6c 100644 --- a/platform/src/views/apps/babyhealth/index.vue +++ b/platform/src/views/apps/babyhealth/index.vue @@ -1,8 +1,8 @@ - - - - - + + + + + \ No newline at end of file diff --git a/platform/src/views/apps/babyhealth/users/components/changePassword.vue b/platform/src/views/apps/babyhealth/users/components/changePassword.vue index 2577cac..8b64697 100644 --- a/platform/src/views/apps/babyhealth/users/components/changePassword.vue +++ b/platform/src/views/apps/babyhealth/users/components/changePassword.vue @@ -1,198 +1,198 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/babyhealth/users/components/preview.vue b/platform/src/views/apps/babyhealth/users/components/preview.vue index 76a3181..7dd05f5 100644 --- a/platform/src/views/apps/babyhealth/users/components/preview.vue +++ b/platform/src/views/apps/babyhealth/users/components/preview.vue @@ -1,190 +1,190 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/babyhealth/users/components/userEdit.vue b/platform/src/views/apps/babyhealth/users/components/userEdit.vue index 7285dce..3a28634 100644 --- a/platform/src/views/apps/babyhealth/users/components/userEdit.vue +++ b/platform/src/views/apps/babyhealth/users/components/userEdit.vue @@ -1,521 +1,521 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/babyhealth/users/index.vue b/platform/src/views/apps/babyhealth/users/index.vue index 95c9d57..dd72994 100644 --- a/platform/src/views/apps/babyhealth/users/index.vue +++ b/platform/src/views/apps/babyhealth/users/index.vue @@ -1,328 +1,328 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/erp/dashboard/index.vue b/platform/src/views/apps/erp/dashboard/index.vue index b29aa5c..67be465 100644 --- a/platform/src/views/apps/erp/dashboard/index.vue +++ b/platform/src/views/apps/erp/dashboard/index.vue @@ -1,329 +1,329 @@ - - - - - + + + + + diff --git a/platform/src/views/apps/erp/employee/components/changepass.vue b/platform/src/views/apps/erp/employee/components/changepass.vue index 0f835f9..8c5a24c 100644 --- a/platform/src/views/apps/erp/employee/components/changepass.vue +++ b/platform/src/views/apps/erp/employee/components/changepass.vue @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/platform/src/views/apps/erp/employee/components/edit.vue b/platform/src/views/apps/erp/employee/components/edit.vue index 59d3abb..439fcf3 100644 --- a/platform/src/views/apps/erp/employee/components/edit.vue +++ b/platform/src/views/apps/erp/employee/components/edit.vue @@ -1,491 +1,491 @@ - - - - - + + + + + \ No newline at end of file diff --git a/platform/src/views/apps/erp/employee/components/view.vue b/platform/src/views/apps/erp/employee/components/view.vue index 0f835f9..8c5a24c 100644 --- a/platform/src/views/apps/erp/employee/components/view.vue +++ b/platform/src/views/apps/erp/employee/components/view.vue @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/platform/src/views/apps/erp/employee/index.vue b/platform/src/views/apps/erp/employee/index.vue index d591a37..052942e 100644 --- a/platform/src/views/apps/erp/employee/index.vue +++ b/platform/src/views/apps/erp/employee/index.vue @@ -1,174 +1,174 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/apps/erp/index.vue b/platform/src/views/apps/erp/index.vue index 3ea5af6..1644a6c 100644 --- a/platform/src/views/apps/erp/index.vue +++ b/platform/src/views/apps/erp/index.vue @@ -1,8 +1,8 @@ - - - - - + + + + + \ No newline at end of file diff --git a/platform/src/views/apps/erp/organization/components/edit.vue b/platform/src/views/apps/erp/organization/components/edit.vue index 549201b..187ef3d 100644 --- a/platform/src/views/apps/erp/organization/components/edit.vue +++ b/platform/src/views/apps/erp/organization/components/edit.vue @@ -1,235 +1,235 @@ - - + + diff --git a/platform/src/views/apps/erp/organization/index.vue b/platform/src/views/apps/erp/organization/index.vue index d64fe3d..0a9ecd6 100644 --- a/platform/src/views/apps/erp/organization/index.vue +++ b/platform/src/views/apps/erp/organization/index.vue @@ -1,358 +1,358 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/roles/components/detail.vue b/platform/src/views/basicSettings/roles/components/detail.vue index 6622966..e5c0ae8 100644 --- a/platform/src/views/basicSettings/roles/components/detail.vue +++ b/platform/src/views/basicSettings/roles/components/detail.vue @@ -1,172 +1,172 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/roles/components/edit.vue b/platform/src/views/basicSettings/roles/components/edit.vue index 62384db..0296f33 100644 --- a/platform/src/views/basicSettings/roles/components/edit.vue +++ b/platform/src/views/basicSettings/roles/components/edit.vue @@ -1,295 +1,295 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/roles/index.vue b/platform/src/views/basicSettings/roles/index.vue index ceb7600..108351a 100644 --- a/platform/src/views/basicSettings/roles/index.vue +++ b/platform/src/views/basicSettings/roles/index.vue @@ -1,231 +1,231 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/siteSettings/components/contactSettings.vue b/platform/src/views/basicSettings/siteSettings/components/contactSettings.vue index 209d541..c58cf3c 100644 --- a/platform/src/views/basicSettings/siteSettings/components/contactSettings.vue +++ b/platform/src/views/basicSettings/siteSettings/components/contactSettings.vue @@ -1,92 +1,92 @@ - - - + + + diff --git a/platform/src/views/basicSettings/siteSettings/components/legalNotice.vue b/platform/src/views/basicSettings/siteSettings/components/legalNotice.vue index 320965c..950e1e1 100644 --- a/platform/src/views/basicSettings/siteSettings/components/legalNotice.vue +++ b/platform/src/views/basicSettings/siteSettings/components/legalNotice.vue @@ -1,102 +1,102 @@ - - - + + + diff --git a/platform/src/views/basicSettings/siteSettings/components/loginVerification.vue b/platform/src/views/basicSettings/siteSettings/components/loginVerification.vue index 3698994..30be346 100644 --- a/platform/src/views/basicSettings/siteSettings/components/loginVerification.vue +++ b/platform/src/views/basicSettings/siteSettings/components/loginVerification.vue @@ -1,81 +1,81 @@ - - - \ No newline at end of file diff --git a/platform/src/views/basicSettings/siteSettings/components/normalSettings.vue b/platform/src/views/basicSettings/siteSettings/components/normalSettings.vue index d4e4313..ad0a171 100644 --- a/platform/src/views/basicSettings/siteSettings/components/normalSettings.vue +++ b/platform/src/views/basicSettings/siteSettings/components/normalSettings.vue @@ -1,289 +1,289 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/siteSettings/components/otherSettings.vue b/platform/src/views/basicSettings/siteSettings/components/otherSettings.vue index 976f650..06722cd 100644 --- a/platform/src/views/basicSettings/siteSettings/components/otherSettings.vue +++ b/platform/src/views/basicSettings/siteSettings/components/otherSettings.vue @@ -1,58 +1,58 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/siteSettings/components/seoSettings.vue b/platform/src/views/basicSettings/siteSettings/components/seoSettings.vue index d2388b6..f2aaa99 100644 --- a/platform/src/views/basicSettings/siteSettings/components/seoSettings.vue +++ b/platform/src/views/basicSettings/siteSettings/components/seoSettings.vue @@ -1,101 +1,101 @@ - - - + + + diff --git a/platform/src/views/basicSettings/siteSettings/index.vue b/platform/src/views/basicSettings/siteSettings/index.vue index 200366c..d1fc30f 100644 --- a/platform/src/views/basicSettings/siteSettings/index.vue +++ b/platform/src/views/basicSettings/siteSettings/index.vue @@ -1,98 +1,98 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/sitereminder/components/detail.vue b/platform/src/views/basicSettings/sitereminder/components/detail.vue index 9cf4878..32e367a 100644 --- a/platform/src/views/basicSettings/sitereminder/components/detail.vue +++ b/platform/src/views/basicSettings/sitereminder/components/detail.vue @@ -1,118 +1,118 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/sitereminder/components/edit.vue b/platform/src/views/basicSettings/sitereminder/components/edit.vue index b14fd81..a19c86a 100644 --- a/platform/src/views/basicSettings/sitereminder/components/edit.vue +++ b/platform/src/views/basicSettings/sitereminder/components/edit.vue @@ -1,278 +1,278 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/sitereminder/index.vue b/platform/src/views/basicSettings/sitereminder/index.vue index 88a178e..1750fc4 100644 --- a/platform/src/views/basicSettings/sitereminder/index.vue +++ b/platform/src/views/basicSettings/sitereminder/index.vue @@ -1,280 +1,280 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/tenants/components/TenantUsersTab.vue b/platform/src/views/basicSettings/tenants/components/TenantUsersTab.vue index 2770c83..4cfe729 100644 --- a/platform/src/views/basicSettings/tenants/components/TenantUsersTab.vue +++ b/platform/src/views/basicSettings/tenants/components/TenantUsersTab.vue @@ -1,244 +1,244 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/tenants/components/adduser.vue b/platform/src/views/basicSettings/tenants/components/adduser.vue index 569bec5..e2f7f6a 100644 --- a/platform/src/views/basicSettings/tenants/components/adduser.vue +++ b/platform/src/views/basicSettings/tenants/components/adduser.vue @@ -1,144 +1,144 @@ - - - \ No newline at end of file diff --git a/platform/src/views/basicSettings/tenants/components/detail.vue b/platform/src/views/basicSettings/tenants/components/detail.vue index e29ff3b..3fc0336 100644 --- a/platform/src/views/basicSettings/tenants/components/detail.vue +++ b/platform/src/views/basicSettings/tenants/components/detail.vue @@ -1,160 +1,160 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/tenants/components/edit.vue b/platform/src/views/basicSettings/tenants/components/edit.vue index 9f558b8..fd8c66c 100644 --- a/platform/src/views/basicSettings/tenants/components/edit.vue +++ b/platform/src/views/basicSettings/tenants/components/edit.vue @@ -1,194 +1,194 @@ - - - \ No newline at end of file diff --git a/platform/src/views/basicSettings/tenants/components/qualification.vue b/platform/src/views/basicSettings/tenants/components/qualification.vue index 96cc3e0..65dc94d 100644 --- a/platform/src/views/basicSettings/tenants/components/qualification.vue +++ b/platform/src/views/basicSettings/tenants/components/qualification.vue @@ -1,166 +1,166 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/basicSettings/tenants/domain.vue b/platform/src/views/basicSettings/tenants/domain.vue index 510478c..43d259a 100644 --- a/platform/src/views/basicSettings/tenants/domain.vue +++ b/platform/src/views/basicSettings/tenants/domain.vue @@ -1,258 +1,258 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/tenants/index.vue b/platform/src/views/basicSettings/tenants/index.vue index 7b02ac8..616cdd0 100644 --- a/platform/src/views/basicSettings/tenants/index.vue +++ b/platform/src/views/basicSettings/tenants/index.vue @@ -1,299 +1,299 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/users/components/changePassword.vue b/platform/src/views/basicSettings/users/components/changePassword.vue index 2577cac..8b64697 100644 --- a/platform/src/views/basicSettings/users/components/changePassword.vue +++ b/platform/src/views/basicSettings/users/components/changePassword.vue @@ -1,198 +1,198 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/users/components/preview.vue b/platform/src/views/basicSettings/users/components/preview.vue index 76a3181..7dd05f5 100644 --- a/platform/src/views/basicSettings/users/components/preview.vue +++ b/platform/src/views/basicSettings/users/components/preview.vue @@ -1,190 +1,190 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/users/components/userEdit.vue b/platform/src/views/basicSettings/users/components/userEdit.vue index 18e3441..1bfe4de 100644 --- a/platform/src/views/basicSettings/users/components/userEdit.vue +++ b/platform/src/views/basicSettings/users/components/userEdit.vue @@ -1,443 +1,443 @@ - - - - - + + + + + diff --git a/platform/src/views/basicSettings/users/index.vue b/platform/src/views/basicSettings/users/index.vue index fcbf66a..57a90dc 100644 --- a/platform/src/views/basicSettings/users/index.vue +++ b/platform/src/views/basicSettings/users/index.vue @@ -1,296 +1,296 @@ - - - - - + + + + + diff --git a/platform/src/views/components/WangEditor.vue b/platform/src/views/components/WangEditor.vue index df322df..e2d9ec1 100644 --- a/platform/src/views/components/WangEditor.vue +++ b/platform/src/views/components/WangEditor.vue @@ -1,582 +1,582 @@ - - - - - + + + + + diff --git a/platform/src/views/cursor/activationcode/index.vue b/platform/src/views/cursor/activationcode/index.vue index 94e7162..89b25f9 100644 --- a/platform/src/views/cursor/activationcode/index.vue +++ b/platform/src/views/cursor/activationcode/index.vue @@ -1,957 +1,957 @@ - - - - - + + + + + diff --git a/platform/src/views/cursor/equipment/components/activationRecords.vue b/platform/src/views/cursor/equipment/components/activationRecords.vue index 6bf790c..26f076e 100644 --- a/platform/src/views/cursor/equipment/components/activationRecords.vue +++ b/platform/src/views/cursor/equipment/components/activationRecords.vue @@ -1,170 +1,170 @@ - - - - - + + + + + diff --git a/platform/src/views/cursor/equipment/components/delete.vue b/platform/src/views/cursor/equipment/components/delete.vue index 821d546..b199600 100644 --- a/platform/src/views/cursor/equipment/components/delete.vue +++ b/platform/src/views/cursor/equipment/components/delete.vue @@ -1,85 +1,85 @@ - - - - - + + + + + diff --git a/platform/src/views/cursor/equipment/components/detail.vue b/platform/src/views/cursor/equipment/components/detail.vue index 98e3ea8..789e83d 100644 --- a/platform/src/views/cursor/equipment/components/detail.vue +++ b/platform/src/views/cursor/equipment/components/detail.vue @@ -1,243 +1,243 @@ - - - - - + + + + + diff --git a/platform/src/views/cursor/equipment/components/edit.vue b/platform/src/views/cursor/equipment/components/edit.vue index 85ac5a2..c96372d 100644 --- a/platform/src/views/cursor/equipment/components/edit.vue +++ b/platform/src/views/cursor/equipment/components/edit.vue @@ -1,200 +1,200 @@ - - - - - + + + + + diff --git a/platform/src/views/cursor/equipment/components/extractRecords.vue b/platform/src/views/cursor/equipment/components/extractRecords.vue index 6c5503d..7c93244 100644 --- a/platform/src/views/cursor/equipment/components/extractRecords.vue +++ b/platform/src/views/cursor/equipment/components/extractRecords.vue @@ -1,190 +1,190 @@ - - - - - + + + + + diff --git a/platform/src/views/cursor/equipment/index.vue b/platform/src/views/cursor/equipment/index.vue index ac33a5f..7bc53e3 100644 --- a/platform/src/views/cursor/equipment/index.vue +++ b/platform/src/views/cursor/equipment/index.vue @@ -1,809 +1,809 @@ - - - - - + + + + + diff --git a/platform/src/views/dashboard/index.vue b/platform/src/views/dashboard/index.vue index 5097a89..9e83654 100644 --- a/platform/src/views/dashboard/index.vue +++ b/platform/src/views/dashboard/index.vue @@ -1,790 +1,790 @@ - - - - - + + + + + diff --git a/platform/src/views/home/index.vue b/platform/src/views/home/index.vue index da48abf..4a94124 100644 --- a/platform/src/views/home/index.vue +++ b/platform/src/views/home/index.vue @@ -1,438 +1,438 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/layouts/EmptyLayout.vue b/platform/src/views/layouts/EmptyLayout.vue index 953755f..5ec7b8e 100644 --- a/platform/src/views/layouts/EmptyLayout.vue +++ b/platform/src/views/layouts/EmptyLayout.vue @@ -1,7 +1,7 @@ - - - \ No newline at end of file diff --git a/platform/src/views/login/forget.vue b/platform/src/views/login/forget.vue index 6332023..bdbdbef 100644 --- a/platform/src/views/login/forget.vue +++ b/platform/src/views/login/forget.vue @@ -1,127 +1,127 @@ - - - - - - + + + + + + diff --git a/platform/src/views/login/index.vue b/platform/src/views/login/index.vue index 8d738b9..868b7b3 100644 --- a/platform/src/views/login/index.vue +++ b/platform/src/views/login/index.vue @@ -1,1055 +1,1055 @@ - - - - - + + + + + diff --git a/platform/src/views/login/register.vue b/platform/src/views/login/register.vue index 59a887c..d2c4ce8 100644 --- a/platform/src/views/login/register.vue +++ b/platform/src/views/login/register.vue @@ -1,131 +1,131 @@ - - - - - - + + + + + + diff --git a/platform/src/views/moduleshop/category/index.vue b/platform/src/views/moduleshop/category/index.vue index d655bf5..71beb53 100644 --- a/platform/src/views/moduleshop/category/index.vue +++ b/platform/src/views/moduleshop/category/index.vue @@ -1,313 +1,313 @@ - - - - - + + + + + diff --git a/platform/src/views/moduleshop/center/index.vue b/platform/src/views/moduleshop/center/index.vue index 0c526aa..287623c 100644 --- a/platform/src/views/moduleshop/center/index.vue +++ b/platform/src/views/moduleshop/center/index.vue @@ -1,680 +1,680 @@ - - - - - + + + + + diff --git a/platform/src/views/moduleshop/components/createModules.vue b/platform/src/views/moduleshop/components/createModules.vue index 4418c18..8882d50 100644 --- a/platform/src/views/moduleshop/components/createModules.vue +++ b/platform/src/views/moduleshop/components/createModules.vue @@ -1,312 +1,312 @@ - - - - - + + + + + diff --git a/platform/src/views/moduleshop/index.vue b/platform/src/views/moduleshop/index.vue index 2fa6465..2d8f283 100644 --- a/platform/src/views/moduleshop/index.vue +++ b/platform/src/views/moduleshop/index.vue @@ -1,11 +1,11 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/moduleshop/publish/index.vue b/platform/src/views/moduleshop/publish/index.vue index b086e6b..952024c 100644 --- a/platform/src/views/moduleshop/publish/index.vue +++ b/platform/src/views/moduleshop/publish/index.vue @@ -1,455 +1,455 @@ - - - - - + + + + + diff --git a/platform/src/views/onepage/index.vue b/platform/src/views/onepage/index.vue index 549a0f9..2c502c7 100644 --- a/platform/src/views/onepage/index.vue +++ b/platform/src/views/onepage/index.vue @@ -1,170 +1,170 @@ - - - - - - + + + + + + diff --git a/platform/src/views/platform/complaint/components/edit.vue b/platform/src/views/platform/complaint/components/edit.vue index d09ab81..5b7e74e 100644 --- a/platform/src/views/platform/complaint/components/edit.vue +++ b/platform/src/views/platform/complaint/components/edit.vue @@ -1,216 +1,216 @@ - - - + + + diff --git a/platform/src/views/platform/complaint/index.vue b/platform/src/views/platform/complaint/index.vue index 1a91d34..c6cdf5d 100644 --- a/platform/src/views/platform/complaint/index.vue +++ b/platform/src/views/platform/complaint/index.vue @@ -1,492 +1,492 @@ - - - - - + + + + + diff --git a/platform/src/views/platform/index.vue b/platform/src/views/platform/index.vue index 2fa6465..2d8f283 100644 --- a/platform/src/views/platform/index.vue +++ b/platform/src/views/platform/index.vue @@ -1,11 +1,11 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/platform/softwareupgrade/components/edit.vue b/platform/src/views/platform/softwareupgrade/components/edit.vue index 39cc2eb..7f63b3e 100644 --- a/platform/src/views/platform/softwareupgrade/components/edit.vue +++ b/platform/src/views/platform/softwareupgrade/components/edit.vue @@ -1,462 +1,530 @@ - - - - - + + + + + diff --git a/platform/src/views/platform/softwareupgrade/index.vue b/platform/src/views/platform/softwareupgrade/index.vue index 817fbeb..342497a 100644 --- a/platform/src/views/platform/softwareupgrade/index.vue +++ b/platform/src/views/platform/softwareupgrade/index.vue @@ -1,260 +1,299 @@ - - - - - + + + + + diff --git a/platform/src/views/settings/index.vue b/platform/src/views/settings/index.vue index 6c91165..45f699c 100644 --- a/platform/src/views/settings/index.vue +++ b/platform/src/views/settings/index.vue @@ -1,11 +1,11 @@ - - - - - + + + + + diff --git a/platform/src/views/settings/systeminfo/index.vue b/platform/src/views/settings/systeminfo/index.vue index c559b76..f970468 100644 --- a/platform/src/views/settings/systeminfo/index.vue +++ b/platform/src/views/settings/systeminfo/index.vue @@ -1,420 +1,420 @@ - - - - - - + + + + + + diff --git a/platform/src/views/system/dict/components/DictItemEdit.vue b/platform/src/views/system/dict/components/DictItemEdit.vue index 8dc7994..ed57bd9 100644 --- a/platform/src/views/system/dict/components/DictItemEdit.vue +++ b/platform/src/views/system/dict/components/DictItemEdit.vue @@ -1,335 +1,335 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/platform/src/views/system/dict/components/DictItemEditDialog.vue b/platform/src/views/system/dict/components/DictItemEditDialog.vue index 47b3691..9717310 100644 --- a/platform/src/views/system/dict/components/DictItemEditDialog.vue +++ b/platform/src/views/system/dict/components/DictItemEditDialog.vue @@ -1,316 +1,316 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/platform/src/views/system/dict/components/DictItemList.vue b/platform/src/views/system/dict/components/DictItemList.vue index 730c27e..e7303d6 100644 --- a/platform/src/views/system/dict/components/DictItemList.vue +++ b/platform/src/views/system/dict/components/DictItemList.vue @@ -1,422 +1,422 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/platform/src/views/system/dict/components/DictTypeEdit.vue b/platform/src/views/system/dict/components/DictTypeEdit.vue index 4db050f..23a2f97 100644 --- a/platform/src/views/system/dict/components/DictTypeEdit.vue +++ b/platform/src/views/system/dict/components/DictTypeEdit.vue @@ -1,241 +1,241 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/platform/src/views/system/dict/components/DictTypeList.vue b/platform/src/views/system/dict/components/DictTypeList.vue index 216ea72..60ad101 100644 --- a/platform/src/views/system/dict/components/DictTypeList.vue +++ b/platform/src/views/system/dict/components/DictTypeList.vue @@ -1,316 +1,316 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/platform/src/views/system/dict/index.vue b/platform/src/views/system/dict/index.vue index 8f1634f..60273da 100644 --- a/platform/src/views/system/dict/index.vue +++ b/platform/src/views/system/dict/index.vue @@ -1,368 +1,368 @@ - - - - - + + + + + diff --git a/platform/src/views/system/email/index.vue b/platform/src/views/system/email/index.vue index a280a71..ce6e613 100644 --- a/platform/src/views/system/email/index.vue +++ b/platform/src/views/system/email/index.vue @@ -1,261 +1,261 @@ - - - - - - \ No newline at end of file diff --git a/platform/src/views/system/fileManager/components/createCategory.vue b/platform/src/views/system/fileManager/components/createCategory.vue index 09ad8e5..569056c 100644 --- a/platform/src/views/system/fileManager/components/createCategory.vue +++ b/platform/src/views/system/fileManager/components/createCategory.vue @@ -1,155 +1,155 @@ - - - - - + + + + + diff --git a/platform/src/views/system/fileManager/components/moveFile.vue b/platform/src/views/system/fileManager/components/moveFile.vue index ba2b689..3268ded 100644 --- a/platform/src/views/system/fileManager/components/moveFile.vue +++ b/platform/src/views/system/fileManager/components/moveFile.vue @@ -1,124 +1,124 @@ - - - + + + diff --git a/platform/src/views/system/fileManager/components/renameCategory.vue b/platform/src/views/system/fileManager/components/renameCategory.vue index 0def892..de97f87 100644 --- a/platform/src/views/system/fileManager/components/renameCategory.vue +++ b/platform/src/views/system/fileManager/components/renameCategory.vue @@ -1,175 +1,175 @@ - - - - - + + + + + diff --git a/platform/src/views/system/fileManager/components/uploadFile.vue b/platform/src/views/system/fileManager/components/uploadFile.vue index d3c2052..fb2a58b 100644 --- a/platform/src/views/system/fileManager/components/uploadFile.vue +++ b/platform/src/views/system/fileManager/components/uploadFile.vue @@ -1,313 +1,313 @@ - - - - - - + + + + + + diff --git a/platform/src/views/system/fileManager/index.vue b/platform/src/views/system/fileManager/index.vue index 12ec0cf..6d6f935 100644 --- a/platform/src/views/system/fileManager/index.vue +++ b/platform/src/views/system/fileManager/index.vue @@ -1,1421 +1,1421 @@ - - - - - + + + + + diff --git a/platform/src/views/system/index.vue b/platform/src/views/system/index.vue index 2fa6465..2d8f283 100644 --- a/platform/src/views/system/index.vue +++ b/platform/src/views/system/index.vue @@ -1,11 +1,11 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/system/menus/components/edit.vue b/platform/src/views/system/menus/components/edit.vue index 484b554..74f1559 100644 --- a/platform/src/views/system/menus/components/edit.vue +++ b/platform/src/views/system/menus/components/edit.vue @@ -1,491 +1,491 @@ - - - - - \ No newline at end of file diff --git a/platform/src/views/system/menus/manager.vue b/platform/src/views/system/menus/manager.vue index b55580c..a7b5e0c 100644 --- a/platform/src/views/system/menus/manager.vue +++ b/platform/src/views/system/menus/manager.vue @@ -1,601 +1,601 @@ - - - - - + + + + + diff --git a/platform/src/views/system/modules/index.vue b/platform/src/views/system/modules/index.vue index b757fb3..15b3e23 100644 --- a/platform/src/views/system/modules/index.vue +++ b/platform/src/views/system/modules/index.vue @@ -1,489 +1,489 @@ - - - - - + + + + + diff --git a/platform/src/views/system/operationLog/components/detail.vue b/platform/src/views/system/operationLog/components/detail.vue index 465a602..992723a 100644 --- a/platform/src/views/system/operationLog/components/detail.vue +++ b/platform/src/views/system/operationLog/components/detail.vue @@ -1,272 +1,272 @@ - - - - - - + + + + + + diff --git a/platform/src/views/system/operationLog/index.vue b/platform/src/views/system/operationLog/index.vue index 5d9388c..b21f9fc 100644 --- a/platform/src/views/system/operationLog/index.vue +++ b/platform/src/views/system/operationLog/index.vue @@ -1,401 +1,401 @@ - - - - - + + + + + diff --git a/platform/src/views/system/permissions/index.vue b/platform/src/views/system/permissions/index.vue index 08885fb..5950f51 100644 --- a/platform/src/views/system/permissions/index.vue +++ b/platform/src/views/system/permissions/index.vue @@ -1,589 +1,589 @@ - - - - - + + + + + diff --git a/platform/src/views/system/platformsettings/components/notificationSettings.vue b/platform/src/views/system/platformsettings/components/notificationSettings.vue index d7379ca..5215f2f 100644 --- a/platform/src/views/system/platformsettings/components/notificationSettings.vue +++ b/platform/src/views/system/platformsettings/components/notificationSettings.vue @@ -1,849 +1,849 @@ - - - - - + + + + + diff --git a/platform/src/views/system/platformsettings/components/platformSettings.vue b/platform/src/views/system/platformsettings/components/platformSettings.vue index 2aac301..b694c60 100644 --- a/platform/src/views/system/platformsettings/components/platformSettings.vue +++ b/platform/src/views/system/platformsettings/components/platformSettings.vue @@ -1,182 +1,182 @@ - - - - - + + + + + diff --git a/platform/src/views/system/platformsettings/components/storageSettings.vue b/platform/src/views/system/platformsettings/components/storageSettings.vue index eb76229..47f13f3 100644 --- a/platform/src/views/system/platformsettings/components/storageSettings.vue +++ b/platform/src/views/system/platformsettings/components/storageSettings.vue @@ -1,288 +1,288 @@ - - - - - + + + + + diff --git a/platform/src/views/system/platformsettings/index.vue b/platform/src/views/system/platformsettings/index.vue index b525496..bdd2eb8 100644 --- a/platform/src/views/system/platformsettings/index.vue +++ b/platform/src/views/system/platformsettings/index.vue @@ -1,80 +1,80 @@ - - - - - + + + + + diff --git a/platform/src/views/system/programs/index.vue b/platform/src/views/system/programs/index.vue index b148701..ccaa218 100644 --- a/platform/src/views/system/programs/index.vue +++ b/platform/src/views/system/programs/index.vue @@ -1,244 +1,244 @@ - - - - - + + + + + diff --git a/platform/src/views/system/smssettings/components/edit.vue b/platform/src/views/system/smssettings/components/edit.vue index c1ff8fe..cec926b 100644 --- a/platform/src/views/system/smssettings/components/edit.vue +++ b/platform/src/views/system/smssettings/components/edit.vue @@ -1,149 +1,149 @@ - - - - - + + + + + \ No newline at end of file diff --git a/platform/src/views/system/smssettings/index.vue b/platform/src/views/system/smssettings/index.vue index 45edd56..54c00ec 100644 --- a/platform/src/views/system/smssettings/index.vue +++ b/platform/src/views/system/smssettings/index.vue @@ -1,60 +1,60 @@ - - - - - - \ No newline at end of file diff --git a/platform/src/views/system/smssettings/tasklist.vue b/platform/src/views/system/smssettings/tasklist.vue index 6760a44..c3f7370 100644 --- a/platform/src/views/system/smssettings/tasklist.vue +++ b/platform/src/views/system/smssettings/tasklist.vue @@ -1,188 +1,188 @@ - - - - - - + + + + + + diff --git a/platform/src/views/template/index.vue b/platform/src/views/template/index.vue index 04d3b9a..12622ec 100644 --- a/platform/src/views/template/index.vue +++ b/platform/src/views/template/index.vue @@ -1,11 +1,11 @@ - - - - - + + + + + diff --git a/platform/src/views/tools/notebook/README.md b/platform/src/views/tools/notebook/README.md index e99ac66..24b1306 100644 --- a/platform/src/views/tools/notebook/README.md +++ b/platform/src/views/tools/notebook/README.md @@ -1,247 +1,247 @@ -# 记事本模块使用说明 - -## 功能概述 - -这是一个完整的记事本应用模块,支持富文本编辑、笔记管理等功能。 - -## 目录结构 - -``` -notebook/ -├── index.vue # 主页面(列表+编辑器) -├── components/ -│ ├── edit.vue # 编辑器组件 -│ └── WangEditor.vue # WangEditor富文本编辑器封装 -└── README.md # 说明文档 -``` - -## 数据库 - -### 表名 -`yz_platform_notebook` - -### 表结构 -- `id` - 主键ID -- `title` - 笔记标题 -- `content` - 笔记内容(HTML格式) -- `user_id` - 创建用户ID -- `user_name` - 创建用户名 -- `is_deleted` - 是否删除(0-否 1-是) -- `create_time` - 创建时间 -- `update_time` - 更新时间 -- `delete_time` - 删除时间 - -### SQL文件位置 -`sql/yz_platform_notebook.sql` - -## 后端接口 - -### 模型文件 -`go/models/platform_notebook.go` - -### 控制器文件 -`go/controllers/platform_notebook.go` - -### API端点 - -1. **获取笔记列表** - - URL: `GET /platform/notebook/list` - - 参数: - - `page`: 页码(默认1) - - `pageSize`: 每页数量(默认20,最大100) - - `keyword`: 搜索关键词(可选) - - 返回: 笔记列表和总数 - -2. **获取笔记详情** - - URL: `GET /platform/notebook/detail/:id` - - 参数: `id` - 笔记ID - - 返回: 笔记详细信息 - -3. **创建笔记** - - URL: `POST /platform/notebook/create` - - 参数: - ```json - { - "title": "笔记标题", - "content": "

笔记内容

" - } - ``` - - 返回: 创建的笔记信息 - -4. **更新笔记** - - URL: `POST /platform/notebook/update/:id` - - 参数: - ```json - { - "title": "更新的标题", - "content": "

更新的内容

" - } - ``` - - 返回: 更新后的笔记信息 - -5. **删除笔记** - - URL: `DELETE /platform/notebook/delete/:id` - - 参数: `id` - 笔记ID - - 返回: 删除结果 - -## 前端API - -### API文件 -`platform/src/api/notebook.js` - -### 方法说明 - -```javascript -// 获取笔记列表 -getNotebookList({ page, pageSize, keyword }) - -// 获取笔记详情 -getNotebookDetail(id) - -// 创建笔记 -createNotebook({ title, content }) - -// 更新笔记 -updateNotebook(id, { title, content }) - -// 删除笔记 -deleteNotebook(id) -``` - -## 使用步骤 - -### 1. 初始化数据库 -```bash -# 在MySQL中执行SQL文件 -mysql -u用户名 -p数据库名 < sql/yz_platform_notebook.sql -``` - -### 2. 启动后端服务 -后端已自动注册模型和路由,直接启动即可: -```bash -cd go -go run main.go -``` - -### 3. 访问前端 -在浏览器中访问笔记本页面(路由需要在菜单中配置) - -## 功能特性 - -### 列表功能 -- ✅ 显示所有笔记 -- ✅ 搜索笔记(按标题) -- ✅ 创建新笔记 -- ✅ 删除笔记 -- ✅ 查看笔记预览 -- ✅ 显示更新时间 - -### 编辑器功能 -- ✅ 富文本编辑(基于WangEditor) -- ✅ 标题编辑 -- ✅ 内容编辑 -- ✅ 保存笔记 -- ✅ 自动识别新建/编辑模式 -- ✅ 加载状态显示 - -### WangEditor支持的功能 -- 文本样式(加粗、斜体、下划线等) -- 标题(H1-H6) -- 引用 -- 代码块 -- 有序/无序列表 -- 表格 -- 链接 -- 图片上传(需配置上传接口) -- 视频嵌入 - -## 权限说明 - -- 所有接口都需要平台用户登录(JWT Token验证) -- 每个用户只能查看、编辑、删除自己创建的笔记 -- 删除操作为软删除,数据仍保留在数据库中 - -## 扩展功能(可选) - -如需添加以下功能,可以扩展: - -1. **笔记分类** - - 添加分类表和分类字段 - - 支持笔记分类管理 - -2. **笔记标签** - - 添加标签表和关联表 - - 支持多标签筛选 - -3. **笔记分享** - - 添加分享链接生成功能 - - 支持公开/私密设置 - -4. **笔记导出** - - 支持导出为PDF - - 支持导出为Markdown - -5. **版本历史** - - 记录笔记的修改历史 - - 支持版本回退 - -6. **协作编辑** - - 支持多人协作编辑 - - 实时同步功能 - -## 注意事项 - -1. 图片上传需要配置文件上传接口 -2. 富文本内容存储为HTML格式,注意XSS防护 -3. 数据库content字段使用longtext类型,支持大容量内容 -4. 建议定期清理软删除的数据 -5. 生产环境建议添加内容审核机制 - -## 技术栈 - -- **前端**: Vue 3 + Element Plus + WangEditor -- **后端**: Go + Beego + MySQL -- **编辑器**: WangEditor 5.x - -## 开发调试 - -### 前端调试 -```bash -cd platform -npm run dev -``` - -### 后端调试 -```bash -cd go -go run main.go -``` - -### 查看API请求 -打开浏览器开发者工具 -> Network 标签页,查看API请求和响应 - -## 问题排查 - -1. **笔记列表为空** - - 检查数据库表是否创建成功 - - 检查用户是否已登录 - - 查看浏览器Console是否有错误 - -2. **保存失败** - - 检查JWT Token是否有效 - - 检查标题是否为空 - - 查看后端日志 - -3. **编辑器显示异常** - - 检查WangEditor是否正确安装 - - 查看浏览器Console错误信息 - - 检查CSS样式是否正确加载 - -## 更新日志 - -### v1.0.0 (2024) -- ✅ 完成基础功能 -- ✅ 支持笔记CRUD操作 -- ✅ 集成WangEditor富文本编辑器 -- ✅ 响应式设计支持 -- ✅ 暗色模式支持 +# 记事本模块使用说明 + +## 功能概述 + +这是一个完整的记事本应用模块,支持富文本编辑、笔记管理等功能。 + +## 目录结构 + +``` +notebook/ +├── index.vue # 主页面(列表+编辑器) +├── components/ +│ ├── edit.vue # 编辑器组件 +│ └── WangEditor.vue # WangEditor富文本编辑器封装 +└── README.md # 说明文档 +``` + +## 数据库 + +### 表名 +`yz_platform_notebook` + +### 表结构 +- `id` - 主键ID +- `title` - 笔记标题 +- `content` - 笔记内容(HTML格式) +- `user_id` - 创建用户ID +- `user_name` - 创建用户名 +- `is_deleted` - 是否删除(0-否 1-是) +- `create_time` - 创建时间 +- `update_time` - 更新时间 +- `delete_time` - 删除时间 + +### SQL文件位置 +`sql/yz_platform_notebook.sql` + +## 后端接口 + +### 模型文件 +`go/models/platform_notebook.go` + +### 控制器文件 +`go/controllers/platform_notebook.go` + +### API端点 + +1. **获取笔记列表** + - URL: `GET /platform/notebook/list` + - 参数: + - `page`: 页码(默认1) + - `pageSize`: 每页数量(默认20,最大100) + - `keyword`: 搜索关键词(可选) + - 返回: 笔记列表和总数 + +2. **获取笔记详情** + - URL: `GET /platform/notebook/detail/:id` + - 参数: `id` - 笔记ID + - 返回: 笔记详细信息 + +3. **创建笔记** + - URL: `POST /platform/notebook/create` + - 参数: + ```json + { + "title": "笔记标题", + "content": "

笔记内容

" + } + ``` + - 返回: 创建的笔记信息 + +4. **更新笔记** + - URL: `POST /platform/notebook/update/:id` + - 参数: + ```json + { + "title": "更新的标题", + "content": "

更新的内容

" + } + ``` + - 返回: 更新后的笔记信息 + +5. **删除笔记** + - URL: `DELETE /platform/notebook/delete/:id` + - 参数: `id` - 笔记ID + - 返回: 删除结果 + +## 前端API + +### API文件 +`platform/src/api/notebook.js` + +### 方法说明 + +```javascript +// 获取笔记列表 +getNotebookList({ page, pageSize, keyword }) + +// 获取笔记详情 +getNotebookDetail(id) + +// 创建笔记 +createNotebook({ title, content }) + +// 更新笔记 +updateNotebook(id, { title, content }) + +// 删除笔记 +deleteNotebook(id) +``` + +## 使用步骤 + +### 1. 初始化数据库 +```bash +# 在MySQL中执行SQL文件 +mysql -u用户名 -p数据库名 < sql/yz_platform_notebook.sql +``` + +### 2. 启动后端服务 +后端已自动注册模型和路由,直接启动即可: +```bash +cd go +go run main.go +``` + +### 3. 访问前端 +在浏览器中访问笔记本页面(路由需要在菜单中配置) + +## 功能特性 + +### 列表功能 +- ✅ 显示所有笔记 +- ✅ 搜索笔记(按标题) +- ✅ 创建新笔记 +- ✅ 删除笔记 +- ✅ 查看笔记预览 +- ✅ 显示更新时间 + +### 编辑器功能 +- ✅ 富文本编辑(基于WangEditor) +- ✅ 标题编辑 +- ✅ 内容编辑 +- ✅ 保存笔记 +- ✅ 自动识别新建/编辑模式 +- ✅ 加载状态显示 + +### WangEditor支持的功能 +- 文本样式(加粗、斜体、下划线等) +- 标题(H1-H6) +- 引用 +- 代码块 +- 有序/无序列表 +- 表格 +- 链接 +- 图片上传(需配置上传接口) +- 视频嵌入 + +## 权限说明 + +- 所有接口都需要平台用户登录(JWT Token验证) +- 每个用户只能查看、编辑、删除自己创建的笔记 +- 删除操作为软删除,数据仍保留在数据库中 + +## 扩展功能(可选) + +如需添加以下功能,可以扩展: + +1. **笔记分类** + - 添加分类表和分类字段 + - 支持笔记分类管理 + +2. **笔记标签** + - 添加标签表和关联表 + - 支持多标签筛选 + +3. **笔记分享** + - 添加分享链接生成功能 + - 支持公开/私密设置 + +4. **笔记导出** + - 支持导出为PDF + - 支持导出为Markdown + +5. **版本历史** + - 记录笔记的修改历史 + - 支持版本回退 + +6. **协作编辑** + - 支持多人协作编辑 + - 实时同步功能 + +## 注意事项 + +1. 图片上传需要配置文件上传接口 +2. 富文本内容存储为HTML格式,注意XSS防护 +3. 数据库content字段使用longtext类型,支持大容量内容 +4. 建议定期清理软删除的数据 +5. 生产环境建议添加内容审核机制 + +## 技术栈 + +- **前端**: Vue 3 + Element Plus + WangEditor +- **后端**: Go + Beego + MySQL +- **编辑器**: WangEditor 5.x + +## 开发调试 + +### 前端调试 +```bash +cd platform +npm run dev +``` + +### 后端调试 +```bash +cd go +go run main.go +``` + +### 查看API请求 +打开浏览器开发者工具 -> Network 标签页,查看API请求和响应 + +## 问题排查 + +1. **笔记列表为空** + - 检查数据库表是否创建成功 + - 检查用户是否已登录 + - 查看浏览器Console是否有错误 + +2. **保存失败** + - 检查JWT Token是否有效 + - 检查标题是否为空 + - 查看后端日志 + +3. **编辑器显示异常** + - 检查WangEditor是否正确安装 + - 查看浏览器Console错误信息 + - 检查CSS样式是否正确加载 + +## 更新日志 + +### v1.0.0 (2024) +- ✅ 完成基础功能 +- ✅ 支持笔记CRUD操作 +- ✅ 集成WangEditor富文本编辑器 +- ✅ 响应式设计支持 +- ✅ 暗色模式支持 diff --git a/platform/src/views/tools/notebook/components/WangEditor.vue b/platform/src/views/tools/notebook/components/WangEditor.vue index 03a48c3..0eef763 100644 --- a/platform/src/views/tools/notebook/components/WangEditor.vue +++ b/platform/src/views/tools/notebook/components/WangEditor.vue @@ -1,304 +1,304 @@ - - - - - + + + + + diff --git a/platform/src/views/tools/notebook/components/edit.vue b/platform/src/views/tools/notebook/components/edit.vue index 87e1ad7..ab87d86 100644 --- a/platform/src/views/tools/notebook/components/edit.vue +++ b/platform/src/views/tools/notebook/components/edit.vue @@ -1,188 +1,188 @@ - - - - - + + + + + diff --git a/platform/src/views/tools/notebook/index.vue b/platform/src/views/tools/notebook/index.vue index 6252f37..e09913a 100644 --- a/platform/src/views/tools/notebook/index.vue +++ b/platform/src/views/tools/notebook/index.vue @@ -1,379 +1,379 @@ - - - - - + + + + + diff --git a/platform/src/views/tools/reminder/components/detail.vue b/platform/src/views/tools/reminder/components/detail.vue index f1ba278..d627901 100644 --- a/platform/src/views/tools/reminder/components/detail.vue +++ b/platform/src/views/tools/reminder/components/detail.vue @@ -1,140 +1,140 @@ - - - - - + + + + + diff --git a/platform/src/views/tools/reminder/components/edit.vue b/platform/src/views/tools/reminder/components/edit.vue index d6f211b..2cefd0d 100644 --- a/platform/src/views/tools/reminder/components/edit.vue +++ b/platform/src/views/tools/reminder/components/edit.vue @@ -1,285 +1,285 @@ - - - - - + + + + + diff --git a/platform/src/views/tools/reminder/index.vue b/platform/src/views/tools/reminder/index.vue index 9952027..5835015 100644 --- a/platform/src/views/tools/reminder/index.vue +++ b/platform/src/views/tools/reminder/index.vue @@ -1,338 +1,338 @@ - - - - - + + + + + diff --git a/platform/src/views/user/userProfile.vue b/platform/src/views/user/userProfile.vue index 45d27ed..258e973 100644 --- a/platform/src/views/user/userProfile.vue +++ b/platform/src/views/user/userProfile.vue @@ -1,639 +1,639 @@ - - - - - + + + + + diff --git a/platform/src/vite-env.d.ts b/platform/src/vite-env.d.ts index d326d43..ea3b2e4 100644 --- a/platform/src/vite-env.d.ts +++ b/platform/src/vite-env.d.ts @@ -1,10 +1,10 @@ -/// - -interface ImportMetaEnv { - readonly VITE_API_BASE_URL: string -} - -interface ImportMeta { - readonly env: ImportMetaEnv -} - +/// + +interface ImportMetaEnv { + readonly VITE_API_BASE_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + diff --git a/platform/vite.config.js b/platform/vite.config.js index c19ad49..d4d939f 100644 --- a/platform/vite.config.js +++ b/platform/vite.config.js @@ -1,44 +1,44 @@ -import { defineConfig } from "vite"; -import vue from "@vitejs/plugin-vue"; -import { resolve } from "path"; -import AutoImport from "unplugin-auto-import/vite"; -import Components from "unplugin-vue-components/vite"; -import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; - -// https://vite.dev/config/ -export default defineConfig({ - // Windows 下若 dist 被占用,清空或覆盖会 EPERM;产物默认写到 output/(与 dist 分离)。部署时请同步指向 output。 - build: { - outDir: "output", - emptyOutDir: true, - }, - plugins: [ - vue(), - AutoImport({ - resolvers: [ElementPlusResolver()], - }), - Components({ - resolvers: [ElementPlusResolver()], - }), - ], - resolve: { - alias: { - "@": resolve(__dirname, "./src"), - }, - }, - server: { - host: "127.0.0.1", - port: 5000, - // 开发时前端在 5000,接口走相对路径 /platform/*、/backend/*,转发到本地 Go(当前 httpport=8081) - proxy: { - "/platform": { - target: "http://127.0.0.1:8081", - changeOrigin: true, - }, - "/backend": { - target: "http://127.0.0.1:8081", - changeOrigin: true, - }, - }, - }, -}); +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import { resolve } from "path"; +import AutoImport from "unplugin-auto-import/vite"; +import Components from "unplugin-vue-components/vite"; +import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; + +// https://vite.dev/config/ +export default defineConfig({ + // Windows 下若 dist 被占用,清空或覆盖会 EPERM;产物默认写到 output/(与 dist 分离)。部署时请同步指向 output。 + build: { + outDir: "output", + emptyOutDir: true, + }, + plugins: [ + vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + ], + resolve: { + alias: { + "@": resolve(__dirname, "./src"), + }, + }, + server: { + host: "127.0.0.1", + port: 5000, + // 开发时前端在 5000,接口走相对路径 /platform/*、/backend/*,转发到本地 Go(当前 httpport=8081) + proxy: { + "/platform": { + target: "http://127.0.0.1:8081", + changeOrigin: true, + }, + "/backend": { + target: "http://127.0.0.1:8081", + changeOrigin: true, + }, + }, + }, +}); diff --git a/sql/upgrade_yz_system_reminderlist.sql b/sql/upgrade_yz_system_reminderlist.sql index 792b6be..43857ca 100644 --- a/sql/upgrade_yz_system_reminderlist.sql +++ b/sql/upgrade_yz_system_reminderlist.sql @@ -1,12 +1,12 @@ --- 升级已有的 yz_system_reminderlist 表,添加 batch_id 以及发送目标相关字段 -ALTER TABLE `yz_system_reminderlist` - ADD COLUMN `batch_id` varchar(64) NOT NULL DEFAULT '' COMMENT '批次号/分组ID' AFTER `delete_time`, - ADD COLUMN `target_type` varchar(32) NOT NULL DEFAULT '' COMMENT '发送目标类型: platform, tenant_all, role, tenant' AFTER `batch_id`, - ADD COLUMN `target_role_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '目标角色ID' AFTER `target_type`, - ADD COLUMN `target_tenant_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '目标租户ID' AFTER `target_role_id`, - ADD INDEX `idx_batch` (`batch_id`); - --- 初始化已有记录的 batch_id (如果存在空值,使用 create_time 和 sender_id 进行分组初始化) -UPDATE `yz_system_reminderlist` -SET `batch_id` = CONCAT(UNIX_TIMESTAMP(COALESCE(`create_time`, NOW())), '_', `sender_id`) -WHERE `batch_id` = '' OR `batch_id` IS NULL; +-- 升级已有的 yz_system_reminderlist 表,添加 batch_id 以及发送目标相关字段 +ALTER TABLE `yz_system_reminderlist` + ADD COLUMN `batch_id` varchar(64) NOT NULL DEFAULT '' COMMENT '批次号/分组ID' AFTER `delete_time`, + ADD COLUMN `target_type` varchar(32) NOT NULL DEFAULT '' COMMENT '发送目标类型: platform, tenant_all, role, tenant' AFTER `batch_id`, + ADD COLUMN `target_role_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '目标角色ID' AFTER `target_type`, + ADD COLUMN `target_tenant_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '目标租户ID' AFTER `target_role_id`, + ADD INDEX `idx_batch` (`batch_id`); + +-- 初始化已有记录的 batch_id (如果存在空值,使用 create_time 和 sender_id 进行分组初始化) +UPDATE `yz_system_reminderlist` +SET `batch_id` = CONCAT(UNIX_TIMESTAMP(COALESCE(`create_time`, NOW())), '_', `sender_id`) +WHERE `batch_id` = '' OR `batch_id` IS NULL; diff --git a/sql/yz_platform_notebook.sql b/sql/yz_platform_notebook.sql index a546b7e..5f06b7c 100644 --- a/sql/yz_platform_notebook.sql +++ b/sql/yz_platform_notebook.sql @@ -1,16 +1,16 @@ --- 平台记事本表 -CREATE TABLE IF NOT EXISTS `yz_platform_notebook` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `title` varchar(255) NOT NULL DEFAULT '' COMMENT '笔记标题', - `content` longtext COMMENT '笔记内容(HTML格式)', - `user_id` bigint(20) unsigned DEFAULT NULL COMMENT '创建用户ID', - `user_name` varchar(100) DEFAULT NULL COMMENT '创建用户名', - `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除 0-否 1-是', - `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - PRIMARY KEY (`id`), - KEY `idx_user_id` (`user_id`), - KEY `idx_create_time` (`create_time`), - KEY `idx_is_deleted` (`is_deleted`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台记事本表'; +-- 平台记事本表 +CREATE TABLE IF NOT EXISTS `yz_platform_notebook` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `title` varchar(255) NOT NULL DEFAULT '' COMMENT '笔记标题', + `content` longtext COMMENT '笔记内容(HTML格式)', + `user_id` bigint(20) unsigned DEFAULT NULL COMMENT '创建用户ID', + `user_name` varchar(100) DEFAULT NULL COMMENT '创建用户名', + `is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除 0-否 1-是', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + PRIMARY KEY (`id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_create_time` (`create_time`), + KEY `idx_is_deleted` (`is_deleted`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台记事本表'; diff --git a/sql/yz_system_sitereminder.sql b/sql/yz_system_sitereminder.sql index e609d58..bf65bc0 100644 --- a/sql/yz_system_sitereminder.sql +++ b/sql/yz_system_sitereminder.sql @@ -1,36 +1,36 @@ --- 站内信配置表 -CREATE TABLE IF NOT EXISTS `yz_system_sitereminder` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', - `retention_days` int(11) NOT NULL DEFAULT '30' COMMENT '消息保留天数', - `auto_read` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否自动标记已读: 0-否, 1-是', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站内信配置表'; - --- 默认插入一条配置数据 -INSERT INTO `yz_system_sitereminder` (`id`, `retention_days`, `auto_read`, `create_time`, `update_time`) -VALUES (1, 30, 0, NOW(), NOW()) -ON DUPLICATE KEY UPDATE `update_time` = NOW(); - --- 站内信消息列表表 -CREATE TABLE IF NOT EXISTS `yz_system_reminderlist` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', - `title` varchar(255) NOT NULL COMMENT '标题', - `content` text NOT NULL COMMENT '内容', - `sender_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '发送者ID (0为系统)', - `sender_type` varchar(32) NOT NULL DEFAULT 'system' COMMENT '发送者类型: system, platform, tenant', - `receiver_id` bigint(20) unsigned NOT NULL COMMENT '接收者ID', - `receiver_type` varchar(32) NOT NULL DEFAULT 'receiver' COMMENT '接收者类型: platform, tenant', - `is_read` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否已读: 0-未读, 1-已读', - `read_time` datetime DEFAULT NULL COMMENT '已读时间', - `create_time` datetime DEFAULT NULL COMMENT '创建时间/发送时间', - `delete_time` datetime DEFAULT NULL COMMENT '删除时间', - `batch_id` varchar(64) NOT NULL DEFAULT '' COMMENT '批次号/分组ID', - `target_type` varchar(32) NOT NULL DEFAULT '' COMMENT '发送目标类型: platform, tenant_all, role, tenant', - `target_role_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '目标角色ID', - `target_tenant_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '目标租户ID', - PRIMARY KEY (`id`), - KEY `idx_receiver` (`receiver_type`, `receiver_id`, `is_read`), - KEY `idx_batch` (`batch_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站内信消息列表表'; +-- 站内信配置表 +CREATE TABLE IF NOT EXISTS `yz_system_sitereminder` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `retention_days` int(11) NOT NULL DEFAULT '30' COMMENT '消息保留天数', + `auto_read` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否自动标记已读: 0-否, 1-是', + `create_time` datetime DEFAULT NULL COMMENT '创建时间', + `update_time` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站内信配置表'; + +-- 默认插入一条配置数据 +INSERT INTO `yz_system_sitereminder` (`id`, `retention_days`, `auto_read`, `create_time`, `update_time`) +VALUES (1, 30, 0, NOW(), NOW()) +ON DUPLICATE KEY UPDATE `update_time` = NOW(); + +-- 站内信消息列表表 +CREATE TABLE IF NOT EXISTS `yz_system_reminderlist` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `title` varchar(255) NOT NULL COMMENT '标题', + `content` text NOT NULL COMMENT '内容', + `sender_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '发送者ID (0为系统)', + `sender_type` varchar(32) NOT NULL DEFAULT 'system' COMMENT '发送者类型: system, platform, tenant', + `receiver_id` bigint(20) unsigned NOT NULL COMMENT '接收者ID', + `receiver_type` varchar(32) NOT NULL DEFAULT 'receiver' COMMENT '接收者类型: platform, tenant', + `is_read` tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否已读: 0-未读, 1-已读', + `read_time` datetime DEFAULT NULL COMMENT '已读时间', + `create_time` datetime DEFAULT NULL COMMENT '创建时间/发送时间', + `delete_time` datetime DEFAULT NULL COMMENT '删除时间', + `batch_id` varchar(64) NOT NULL DEFAULT '' COMMENT '批次号/分组ID', + `target_type` varchar(32) NOT NULL DEFAULT '' COMMENT '发送目标类型: platform, tenant_all, role, tenant', + `target_role_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '目标角色ID', + `target_tenant_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '目标租户ID', + PRIMARY KEY (`id`), + KEY `idx_receiver` (`receiver_type`, `receiver_id`, `is_read`), + KEY `idx_batch` (`batch_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='站内信消息列表表';