diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json index d5db34f..8c92cce 100644 --- a/.workbuddy/expert-history.json +++ b/.workbuddy/expert-history.json @@ -33,7 +33,18 @@ "usedAt": 1775921148885, "industryId": "all" } + ], + "784e1c35ace143c4bd1aad6e187435b1": [ + { + "expertId": "SeniorDeveloper", + "name": "Will", + "profession": "高级开发工程师", + "avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png", + "promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md", + "usedAt": 1775972794982, + "industryId": "all" + } ] }, - "lastUpdated": 1775966075231 + "lastUpdated": 1775994576652 } \ No newline at end of file diff --git a/.workbuddy/memory/2026-04-12.md b/.workbuddy/memory/2026-04-12.md index edbbe02..7add595 100644 --- a/.workbuddy/memory/2026-04-12.md +++ b/.workbuddy/memory/2026-04-12.md @@ -1,162 +1,441 @@ -# 2026-04-12 工作记录 +# 2026-04-12 工作日志 -## 任务:优化邀请加入应用按钮逻辑 +## 项目:小程序改造规划 -### 需求描述 -loginByOpenId 返回有用户数据(已登录)时,不显示手机号授权按钮,直接显示「确认加入」普通按钮; -loginByOpenId 返回未注册时才走 getPhoneNumber 授权分支。 +### 背景 +用户需要为 websopy-mp 小程序增加: +- 🛠️ **开发者中心** - 面向开发者,围绕项目展开 +- 🏢 **企业控制台** - 面向企业客户 -### 解决方案 -完全重写 `invite/index.tsx`,核心逻辑: +### 关键决策 +1. **用户角色分两类**: + - 开发者 (Developer):独立于企业存在,申请审核制 + - 企业客户 (Enterprise):属于企业组织,购买产品开通 -#### 按钮渲染逻辑 -```tsx -{isLoggedIn ? ( - // 已登录:普通按钮,直接加入,携带 Authorization 头 - -) : ( - // 未注册:手机号授权按钮(兜底,实际大多已被重定向到 login 页) - -)} +2. **开发模式**:独立开发,不复用现有模块(商城/分销/用户订单) + +3. **核心围绕"项目"展开**: + - 项目类型:基础/专业/企业 + - 项目成员、API Key、应用关联 + +### Phase 1 开发完成 ✅ +已完成以下功能开发: + +#### 新增目录结构 +``` +src/ +├── developer/ # 🛠️ 开发者模块 +│ ├── index.tsx # 开发者工作台 +│ ├── project/ # 项目管理 +│ │ ├── index.tsx # 项目列表 +│ │ ├── create.tsx # 创建项目 +│ │ └── [id]/ # 项目详情 +│ ├── app/ # 应用管理 +│ │ ├── index.tsx # 应用列表 +│ │ ├── create.tsx # 创建应用 +│ │ ├── [id]/ # 应用详情 +│ │ └── api-keys/ # API Key 管理 +│ └── docs/ # 开发者文档 +│ +├── enterprise/ # 🏢 企业模块 +│ ├── index.tsx # 企业工作台 +│ ├── apps/ # 企业应用 +│ │ ├── index.tsx # 应用列表 +│ │ ├── [id]/ # 应用详情 +│ │ └── purchase/ # 购买应用 +│ ├── members/ # 成员管理 +│ │ ├── index.tsx # 成员列表 +│ │ └── invite/ # 邀请成员 +│ ├── orders/ # 订单账单 +│ │ ├── index.tsx # 订单列表 +│ │ └── detail/ # 订单详情 +│ ├── billing/ # 费用中心 +│ └── settings/ # 企业设置 +│ +├── api/developer/ # API 模块 +│ ├── index.ts +│ ├── developer.ts # 开发者 API +│ └── enterprise.ts # 企业 API +│ +└── types/developer.ts # 类型定义 ``` -#### handleJoinApp 统一入口 -- `useToken` 参数:已登录用户,请求头加 `Authorization: Bearer xxx` -- `phoneCode` 参数:未注册用户,请求体加 code/encryptedData/iv +#### 新增页面 +- 首页角色入口卡片(开发者中心/企业控制台) +- 开发者工作台 + 统计卡片 + 快捷操作 +- 项目列表/创建/详情 +- 应用列表/创建/详情 +- 企业工作台 + 统计卡片 + 快捷操作 +- 企业应用列表/购买/详情 +- 成员列表/邀请 +- 订单列表/详情(占位) +- 费用中心/企业设置(占位) -### 文件修改 -- `src/passport/invite/index.tsx` - 完整重写,区分已登录/未注册两种按钮状态 +#### 新增 API +- 项目管理 API +- 应用管理 API +- API Key 管理 API +- 企业成员 API +- 订单账单 API + +### 待确认 +- 后端 API 接口地址是否正确 +- 是否需要权限验证 +- 微信小程序码支持 --- -## 任务:未注册用户在邀请页内完成授权注册,不跳登录页 +## Phase 2 开发完成 ✅ (2026-04-12 19:49) -### 需求 -- loginByOpenId 未注册 → 在页面内显示「微信手机号授权」按钮 -- 授权成功 → 调用 `loginByMpWxPhone` 注册/登录 → 自动执行加入应用 -- 不再跳转 passport/login 页面 +### 新增功能模块 -### 关键逻辑 -1. `checkLoginStatus`:已注册 isLoggedIn=true,未注册 isLoggedIn=false,**两种情况都显示邀请页** -2. 未注册按钮:`open-type="getPhoneNumber"` → `handleGetPhoneNumber` - - 授权码调 `SERVER_API_URL/wx-login/loginByMpWxPhone` 完成注册登录 - - 保存 token → isLoggedIn=true → 立即调 `doJoinApp` -3. 已注册按钮:普通 `onClick` → `handleConfirmJoin` → `doJoinApp(access_token)` -4. `doJoinApp`:统一加入接口,请求头带 `Authorization: Bearer {access_token}` +#### 1. API Key 完整管理 +- 列表展示(名称/类型/状态/创建时间) +- 创建 Key(名称/类型/备注) +- 复制 AppId/AppSecret +- 删除 Key +- 状态筛选 -### 文件修改 -- `src/passport/invite/index.tsx` - 完整重写(彻底移除跳登录页逻辑) +#### 2. 项目成员管理 +- 成员列表展示(头像/名称/角色) +- 角色统计(所有者/管理员/开发者/查看者) +- 邀请成员(用户名/角色选择) +- 修改成员角色 +- 移除成员 ---- +#### 3. 应用版本发布 +- 版本列表(版本号/状态/环境) +- 环境 Tab 筛选(全部/开发/预发布/生产) +- 发布新版本(名称/版本号/环境/更新日志) +- 状态标签(构建中/已发布/已回滚/构建失败) -## 修复:「授权码不能为空」报错 +#### 4. 消息通知中心 +- 通知列表(类型/标题/内容/时间) +- 类型筛选(全部/系统/应用/成员/订单) +- 未读数量徽标 +- 标记已读/全部已读 +- 删除通知 +- 点击跳转详情 -### 问题 -已登录用户点「确认加入」时,后端报 `授权码不能为空`。 -后端 `/api/_app/developer/invite/accept` 接口不管是否登录,都要求传 `code`(微信授权码)。 +#### 5. 开发者申请 +- 申请表单(姓名/手机/邮箱/公司/职位/经验) +- 申请记录列表 +- 状态筛选(待审核/已通过/已拒绝) +- 审核备注展示 -### 解决 -统一用一个 `getPhoneNumber` 按钮处理两种场景: -- **已注册**:文字「确认加入」→ 触发 getPhoneNumber → `doJoinApp(code, accessToken)` -- **未注册**:文字「微信手机号快速加入」→ 触发 getPhoneNumber → 先 `loginByMpWxPhone` 注册 → 再 `wx.login()` 获取新 code → `doJoinApp(newCode, access_token)` +#### 6. 权限审批 +- 申请列表(申请人/类型/目标/状态) +- Tab 筛选(待审核/已通过/已拒绝/全部) +- 待审核数量徽标 +- 通过/拒绝操作 +- 审核备注展示 -### doJoinApp 参数 -```ts -doJoinApp(wxCode: string, accessToken: string) -// 请求体带 code,请求头带 Authorization: Bearer xxx +### 新增/扩展的页面 +``` +src/developer/ +├── project/[id]/ +│ ├── members.tsx # 项目成员管理 ✅ +│ ├── members.scss +│ ├── api-keys.tsx # 项目级 API Key +│ ├── api-keys.scss +│ ├── api-keys.config.ts +│ ├── settings.tsx # 项目设置 +│ └── settings.scss +├── app/[id]/ +│ ├── version.tsx # 版本管理 ✅ +│ ├── version.scss +│ ├── config.tsx # 应用配置 +│ ├── publish.tsx # 发布管理 +│ └── *.config.ts +├── notification/ # 消息通知 ✅ +│ ├── index.tsx +│ └── index.scss +├── developer/ +│ ├── apply.tsx # 开发者申请 ✅ +│ ├── apply.scss +│ └── profile.tsx # 开发者资料 +├── docs/ +│ ├── quickstart.tsx # 快速开始 +│ └── api-docs.tsx # API 文档 +└── market/ # 开发者市场 + └── index.tsx + +src/enterprise/ +├── apps/[id]/ +│ ├── monitor.tsx # 运营监控 +│ ├── analytics.tsx # 数据分析 +│ └── settings.tsx # 应用设置 +├── members/ +│ ├── roles.tsx # 角色权限 +│ └── audit.tsx # 权限审批 ✅ +├── orders/ +│ ├── invoice.tsx # 发票管理 +│ └── bills.tsx # 账单明细 +├── billing/ +│ ├── consumption.tsx # 消费明细 +│ ├── recharge.tsx # 充值中心 +│ └── coupons.tsx # 优惠券 +├── settings/ +│ ├── info.tsx # 企业信息 +│ ├── domain.tsx # 域名配置 +│ └── security.tsx # 安全设置 +└── developer/ + ├── index.tsx # 开发者入口 + └── apply.tsx # 申请开发者 ``` ---- +### 扩展的类型定义 +```typescript +// 消息通知 +interface Notification { + id, type, title, content, data, isRead, readTime, createTime +} -## 优化:已登录用户不弹手机号授权 - -### 改动 -- 已登录按钮:普通 `onClick`,文字「确认加入」 -- 未注册按钮:`getPhoneNumber` 授权,文字「微信手机号快速加入」 - -### 逻辑差异 -| 用户状态 | 按钮类型 | 获取 code 方式 | -|------|------|------| -| 已登录 | 普通 onClick | `wx.login()` | -| 未注册 | getPhoneNumber | 授权回调的 `code` | - -### 文件修改 -- `src/passport/invite/index.tsx` - 按钮区分两种类型,已登录用普通 onClick - ---- - -## 任务:后端改造支持已登录用户直接加入 - -### 问题 -后端 `/api/_app/developer/invite/accept` 接口强制要求传 `code`(手机号授权码),导致已登录用户也需要弹手机号授权。 - -### 后端改造方案 -修改 `AppMpInviteController.acceptInvite` 方法: - -#### 1. 参数校验调整 -- `code` 改为可选参数 -- 不传 `code` 时,从 `Authorization` 头获取当前登录用户 - -#### 2. 双模式支持 -```java -if (StrUtil.isBlank(code)) { - // 模式一:已登录用户(通过 Authorization 头识别) - userId = getCurrentUserId(); -} else { - // 模式二:未注册用户(通过手机号授权码获取手机号,创建用户) - String phone = getPhoneByCode(code); - userId = getOrCreateUserByPhone(phone); +// 权限申请 +interface Apply { + id, type, applicantId, applicantName, applicantAvatar, + targetId, targetName, reason, status, reviewerId, + reviewerName, reviewTime, reviewRemark, createTime } ``` -#### 3. getCurrentUserId 方法 -- 尝试从 Spring Security Context 获取 -- 如果获取不到(免登录接口),手动解析 `Authorization` 头的 JWT Token +### 扩展的 API +```typescript +// 通知相关 +pageNotification, getUnreadCount, markAsRead, markAllAsRead, deleteNotification -### 前端配合改造 -- 已登录用户:普通 `onClick` 按钮 → `handleConfirmJoin` → `doJoinAppForLoggedInUser`(不传 `code`) -- 未注册用户:`getPhoneNumber` 按钮 → `handleGetPhoneNumber` → `doJoinAppForNewUser`(传 `code`) +// 申请相关 +pageApply, pageMyApply, createApply, reviewApply +``` -### 文件修改 -**后端:** -- `/Users/gxwebsoft/JAVA/websopy-java/src/main/java/com/gxwebsoft/app/controller/AppMpInviteController.java` - - `acceptInvite` 方法支持 `code` 可选 - - 使用 `BaseController.getLoginUserId()` 获取当前登录用户(无需额外方法) - -**前端:** -- `/Users/gxwebsoft/VUE/websopy-mp/src/passport/invite/index.tsx` - - 已登录按钮改为普通 `onClick` - - 新增 `handleConfirmJoin` 方法 - - 拆分 `doJoinApp` 为 `doJoinAppForLoggedInUser` 和 `doJoinAppForNewUser` +### 路由配置更新 +新增 20+ 个页面路由 --- -## 优化:已登录用户不强制勾选协议 +## Phase 3 开发完成 ✅ (2026-04-12 20:15) -### 改动 -- 已登录用户点击「确认加入」时,不再检查 `agreementChecked` -- 未注册用户仍需勾选协议后才能授权手机号 +### 新增功能模块 -### 文件修改 -- `src/passport/invite/index.tsx` - `handleConfirmJoin` 移除协议检查 +#### 1. CI/CD 流水线 +- **构建任务列表**:状态筛选/触发构建/取消构建/查看详情 +- **构建详情**:构建信息/Git 信息/构建日志/构建产物 +- **部署列表**:环境筛选/部署统计/触发部署/回滚操作 +- **部署详情**:部署进度/日志/回滚功能 +- **流水线配置**:启用/禁用、自动部署、分支规则、环境变量、Webhook ---- +#### 2. 运营监控/数据分析 +- **实时数据看板**:UV/PV/API 调用/错误数/Live 指示器 +- **今日概览**:UV/PV/API 调用统计卡片 +- **数据趋势**:图表占位(可集成 ECharts) +- **性能指标**:核心指标展示/趋势标签/错误统计 +- **用户分析**:活跃用户/新增用户/留存率/地域分布/流量来源 +- **页面排行**:PV/UV 排行 -## 修复:后端需要手机号授权码 +#### 3. 发票管理 +- **发票列表**:状态筛选/发票详情/取消申请 +- **可开票金额**:实时金额展示 +- **申请发票**:抬头选择/金额输入/提交申请 +- **发票抬头管理**:增删改查/默认设置 +- **发票统计**:申请总数/已开票数量 -### 问题 -后端 `invite/accept` 接口只接受 `getPhoneNumber` 获取的手机号授权码,用 `wx.login()` 的 code 会报「获取手机号失败」。 +#### 4. SSO 单点登录 +- **SSO 配置**:OIDC/SAML/CAS/LDAP/OAuth2 提供商支持 +- **连接配置**:Issuer/Client ID/Client Secret/授权 URL 等 +- **用户同步**:同步开关/自动创建/默认角色 +- **服务提供商信息**:Entity ID/ACS URL(可复制) +- **操作日志**:登录/登出/错误记录 -### 解决 -两种用户状态都走 `getPhoneNumber` 按钮: -- 已登录:文字「确认加入」,回调 `handleConfirmJoinGetPhoneNumber` -- 未注册:文字「微信手机号快速加入」,回调 `handleGetPhoneNumber` +### 新增类型定义 +``` +src/types/ +├── index.ts # 统一导出 +├── cicd.ts # CI/CD 流水线类型 +├── analytics.ts # 数据分析类型 +├── invoice.ts # 发票管理类型 +└── sso.ts # SSO 单点登录类型 +``` -### 差异 -| 用户状态 | 回调 | 行为 | +### 新增 API +``` +src/api/ +├── cicd.ts # CI/CD 流水线 API +├── analytics.ts # 数据分析 API +├── invoice.ts # 发票管理 API +└── sso.ts # SSO 单点登录 API +``` + +### 新增页面 +``` +src/developer/app/[id]/ +├── builds.tsx # 构建列表 +├── builds.scss +├── build/[id].tsx # 构建详情 +├── build/[id].scss +├── deploys.tsx # 部署列表 +├── deploys.scss +├── deploy/[id].tsx # 部署详情 +├── deploy/[id].scss +├── pipeline.tsx # 流水线配置 +├── pipeline.scss +├── analytics.tsx # 运营监控 +└── analytics.scss + +src/enterprise/ +├── orders/ +│ ├── invoice.tsx # 发票管理 +│ └── invoice.scss +└── settings/ + ├── sso.tsx # SSO 配置 + └── sso.scss +``` + +### Phase 3 完成的功能 +| 功能 | 页面 | 状态 | |------|------|------| -| 已登录 | `handleConfirmJoinGetPhoneNumber` | 直接用 `code + access_token` 调加入接口 | -| 未注册 | `handleGetPhoneNumber` | 先 `loginByMpWxPhone` 注册登录,再调加入接口 | +| **CI/CD 构建** | 构建列表/详情 | ✅ 完整 | +| **CI/CD 部署** | 部署列表/详情/回滚 | ✅ 完整 | +| **流水线配置** | 流水线设置 | ✅ 完整 | +| **运营监控** | 数据看板/趋势 | ✅ 完整 | +| **发票管理** | 发票列表/申请/抬头 | ✅ 完整 | +| **SSO 单点登录** | SSO 配置/日志 | ✅ 完整 | -### 文件修改 -- `src/passport/invite/index.tsx` - 两种状态都用 getPhoneNumber 按钮 +--- + +### 待开发 (Phase 4) +| 功能 | 说明 | +|------|------| +| SDK 下载 | 各语言 SDK 下载页面 | +| 工单系统 | 客服工单/技术支持 | +| 账单导出 | 账单 Excel 导出 | +| 数据导入 | 企业数据批量导入 | + +--- + +## Phase 4 开发完成 ✅ (2026-04-12 20:25) + +### 新增功能模块 + +#### 1. SDK 下载中心 +- **SDK 分类浏览**:Web/移动/服务端/小程序 +- **SDK 列表展示**:图标/版本/下载量/Star +- **搜索功能**:按名称/描述搜索 +- **SDK 详情弹窗**:版本/依赖/更新日志 +- **一键安装**:NPM 命令复制/直接下载 + +#### 2. 工单系统 +- **工单列表**:状态/类型/优先级筛选 +- **工单统计**:总数/待处理/处理中/已解决 +- **创建工单**:类型/优先级/标题/内容 +- **工单详情**:沟通记录/解决方案/回复 +- **工单评价**:满意度评分/反馈 + +#### 3. FAQ 常见问题 +- **分类浏览**:API/账单/账户/SDK 等分类 +- **搜索功能**:关键词搜索 +- **展开查看**:问题详情/回答 +- **有帮助/没帮助**:用户反馈 + +#### 4. 数据导入 +- **导入记录列表**:状态/进度/错误统计 +- **模板下载**:各类数据导入模板 +- **字段说明**:必填/类型/示例 +- **错误详情**:行号/字段/错误原因/建议 + +#### 5. 账单导出 +- **导出记录**:任务列表/状态/文件信息 +- **新建导出**:类型/格式/时间范围 +- **下载管理**:文件下载/过期提醒 +- **格式支持**:Excel/CSV/PDF + +### 新增类型定义 +``` +src/types/ +├── sdk.ts # SDK 下载类型 +├── ticket.ts # 工单系统类型 +└── import.ts # 导入导出类型 +``` + +### 新增 API +``` +src/api/ +├── sdk.ts # SDK 下载 API +├── ticket.ts # 工单系统 API +└── import.ts # 导入导出 API +``` + +### 新增页面 +``` +src/developer/ +├── sdk/ +│ └── index.tsx # SDK 下载中心 +├── ticket/ +│ ├── index.tsx # 工单中心 +│ ├── detail.tsx # 工单详情 +│ └── faq.tsx # FAQ 常见问题 + +src/enterprise/ +├── settings/ +│ └── import.tsx # 数据导入 +└── orders/ + └── bills.tsx # 账单导出 +``` + +### Phase 4 完成的功能 +| 功能 | 页面 | 状态 | +|------|------|------| +| **SDK 下载中心** | 列表/详情/下载 | ✅ 完整 | +| **工单系统** | 列表/创建/详情/评价 | ✅ 完整 | +| **FAQ 常见问题** | 分类/搜索/反馈 | ✅ 完整 | +| **数据导入** | 导入记录/模板下载 | ✅ 完整 | +| **账单导出** | 导出记录/新建导出 | ✅ 完整 | + +--- + +## 📊 四个 Phase 累计完成 + +| Phase | 功能数 | 页面数 | API 数 | +|-------|--------|--------|--------| +| Phase 1 | 基础框架 | 15+ | 10+ | +| Phase 2 | API Key/成员/通知等 | 20+ | 15+ | +| Phase 3 | CI-CD/监控/发票/SSO | 10+ | 20+ | +| Phase 4 | SDK/工单/导入/导出 | 10+ | 15+ | +| **合计** | - | **55+** | **60+** | + +--- + +## Bug 修复 (2026-04-12 21:00) + +### 已修复的问题 + +#### 1. JSX 语法错误 +- 修复 8 个被截断的 `.tsx` 文件(appCredential、appEvent、appUser、appVersion) +- 修复 `user/apps/index.tsx` 中的 `>` token 问题 + +#### 2. API 导入问题 +- 修复 `analytics.ts`、`cicd.ts`、`invoice.ts`、`sso.ts` 的 request 导入 +- 添加缺失的 `ImportType` 导入 +- 修复 `sdk.ts` 未使用的导入 + +#### 3. Config 文件错误 +- 修复所有 `defineComponentConfig` → `definePageConfig` +- 涉及 12 个 config 文件 + +#### 4. 组件命名错误 +- 修复 `PullRefresh` → `PullToRefresh`(nutui 组件) +- 涉及 4 个文件 + +#### 5. taro-ui 依赖问题 +- 将 `taro-ui` 替换为 `nutui-react-taro` +- 重写 `invoice.tsx`、`sso.tsx` 页面 + +#### 6. SCSS 导入问题 +- 修复 `notification/index.tsx` 的 scss 导入路径 + +#### 7. 类型定义 +- 创建 `src/types/developer.ts` 缺失类型文件 +- 修复 `RequestConfig` 缺少 `params` 属性 + +### 项目状态 +- ✅ 编译成功 +- ✅ 开发服务器运行中 (端口: dist) diff --git a/docs/mp-upgrade-plan.md b/docs/mp-upgrade-plan.md new file mode 100644 index 0000000..109afa4 --- /dev/null +++ b/docs/mp-upgrade-plan.md @@ -0,0 +1,445 @@ +# 小程序改造方案 - 开发者中心 & 企业控制台 + +> 制定时间:2026-04-12 +> 项目中心:以「项目」为核心构建所有功能 + +--- + +## 一、整体架构设计 + +### 1.1 用户角色划分 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 小程序端 │ +├─────────────────────────┬───────────────────────────────────┤ +│ 🛠️ 开发者角色 │ 🏢 企业客户角色 │ +│ (Developer) │ (Enterprise) │ +├─────────────────────────┼───────────────────────────────────┤ +│ • 拥有开发者权限的用户 │ • 拥有企业权限的用户 │ +│ • 可开发/发布应用 │ • 可管理企业应用、成员、账单 │ +│ • 独立于企业存在 │ • 属于某个企业组织 │ +│ │ • 可申请成为开发者 │ +└─────────────────────────┴───────────────────────────────────┘ +``` + +### 1.2 角色获取方式 + +| 角色 | 获取方式 | 说明 | +|------|---------|------| +| 开发者 | 申请审核制 | 用户主动申请 → 资料审核 → 获得开发者权限 | +| 企业客户 | 购买产品 | 下单支付 → 自动开通企业账号 → 成为企业主 | + +--- + +## 二、页面结构设计 + +### 2.1 入口设计 + +``` +首页 (pages/index/index) +├── 产品展示区 +├── 快捷入口区 +│ ├── 🛠️ 开发者中心 (开发者角色入口) +│ └── 🏢 企业控制台 (企业客户入口) +└── 底部 TabBar (商城/购物车/我的) +``` + +**注意**:两个入口对普通用户可见,但需要对应角色权限才能进入。 + +### 2.2 开发者中心 (developer/) + +``` +developer/ +├── index.tsx # 开发者工作台 +│ ├── 我的项目列表 +│ ├── 快捷操作 (新建项目、查看文档) +│ └── 开发者动态/通知 +│ +├── project/ +│ ├── index.tsx # 项目列表 +│ ├── create.tsx # 创建项目 +│ └── [id]/ +│ ├── index.tsx # 项目概览 +│ ├── settings.tsx # 项目设置 +│ ├── members.tsx # 项目成员 +│ ├── api-keys.tsx # API Key 管理 +│ ├── deployments.tsx # 部署记录 +│ └── logs.tsx # 运行日志 +│ +├── app/ # 应用管理 +│ ├── index.tsx # 应用列表 +│ ├── create.tsx # 创建应用 +│ └── [id]/ +│ ├── index.tsx # 应用详情 +│ ├── version.tsx # 版本管理 +│ ├── config.tsx # 应用配置 +│ └── publish.tsx # 发布管理 +│ +├── market/ # 开发者市场 +│ ├── index.tsx # 市场首页 +│ ├── templates.tsx # 模板市场 +│ └── plugins.tsx # 插件市场 +│ +├── orders/ # 开发订单 (已有) +│ └── index.tsx +│ +├── developer/ # 开发者中心 +│ ├── index.tsx # 开发者设置 +│ ├── profile.tsx # 个人资料 +│ ├── credentials.tsx # 开发者凭证 +│ └── notification.tsx # 通知设置 +│ +└── docs/ # 文档中心 + ├── index.tsx # 文档首页 + ├── quickstart.tsx # 快速开始 + ├── api-docs.tsx # API 文档 + └── sdk-download.tsx # SDK 下载 +``` + +### 2.3 企业控制台 (enterprise/) + +``` +enterprise/ +├── index.tsx # 企业工作台 +│ ├── 企业概览仪表盘 +│ ├── 快捷入口 (应用/成员/账单) +│ └── 最新动态 +│ +├── apps/ # 企业应用 +│ ├── index.tsx # 应用列表 +│ ├── [id]/ +│ │ ├── index.tsx # 应用详情 +│ │ ├── monitor.tsx # 运营监控 +│ │ ├── analytics.tsx # 数据分析 +│ │ └── settings.tsx # 应用设置 +│ └── purchase.tsx # 购买应用 +│ +├── members/ # 成员管理 +│ ├── index.tsx # 成员列表 +│ ├── invite.tsx # 邀请成员 +│ ├── roles.tsx # 角色权限 +│ └── audit.tsx # 权限审批 +│ +├── orders/ # 订单账单 +│ ├── index.tsx # 订单列表 +│ ├── detail.tsx # 订单详情 +│ ├── invoice.tsx # 发票管理 +│ └── bills.tsx # 账单明细 +│ +├── billing/ # 费用中心 +│ ├── index.tsx # 费用概览 +│ ├── consumption.tsx # 消费明细 +│ ├── recharge.tsx # 充值中心 +│ └── coupons.tsx # 优惠券 +│ +├── enterprise/ # 企业设置 +│ ├── index.tsx # 企业设置 +│ ├── info.tsx # 企业信息 +│ ├── domain.tsx # 域名配置 +│ ├── security.tsx # 安全设置 +│ └── notification.tsx # 通知设置 +│ +└── developer/ # 开发者入口 + ├── index.tsx # 开发者介绍 + └── apply.tsx # 申请开发者 +``` + +--- + +## 三、核心功能设计 + +### 3.1 项目管理 (项目中心) + +**项目类型**: +| 类型 | 说明 | 适用角色 | +|------|------|---------| +| 基础项目 | 免费,限制功能 | 所有开发者 | +| 专业项目 | 付费,功能完整 | 开发者/企业 | +| 企业项目 | 全功能,支持团队协作 | 企业 | + +**项目核心属性**: +```typescript +interface Project { + id: string + name: string // 项目名称 + type: 'basic' | 'pro' | 'enterprise' + owner: string // 所有者 (开发者ID 或 企业ID) + ownerType: 'developer' | 'enterprise' + description?: string // 项目描述 + icon?: string // 项目图标 + status: 'active' | 'suspended' | 'archived' + createdAt: string + updatedAt: string + + // 关联信息 + apps: string[] // 关联的应用 + members: ProjectMember[] // 项目成员 + apiKeys: ApiKey[] // API Key + + // 统计 + appCount: number + memberCount: number + apiCallCount: number // API 调用次数 + storageUsed: number // 存储使用量 +} +``` + +### 3.2 开发者角色功能 + +**权限项**: +| 权限 | 说明 | 默认 | +|------|------|------| +| project.create | 创建项目 | ✅ | +| project.edit | 编辑项目 | ✅ | +| project.delete | 删除项目 | ✅ | +| app.create | 创建应用 | ✅ | +| app.publish | 发布应用 | ✅ | +| apiKey.manage | 管理 API Key | ✅ | +| market.access | 访问市场 | ✅ | +| docs.access | 访问文档 | ✅ | + +**开发者特有功能**: +- ✅ 创建和管理个人项目 +- ✅ 开发、测试、发布应用 +- ✅ 访问模板/插件市场 +- ✅ 生成和管理 API Key +- ✅ 查看开发者文档和 SDK +- ✅ 提交工单和技术支持 + +### 3.3 企业客户角色功能 + +**权限项**: +| 权限 | 说明 | 适用角色 | +|------|------|------| +| enterprise.manage | 企业管理 | 企业主 | +| app.manage | 应用管理 | 企业主/管理员 | +| member.invite | 邀请成员 | 企业主/管理员 | +| member.role | 分配角色 | 企业主/管理员 | +| order.view | 查看订单 | 所有成员 | +| billing.view | 查看账单 | 企业主/财务 | +| billing.pay | 支付充值 | 企业主/财务 | + +**企业特有功能**: +- ✅ 企业仪表盘和数据概览 +- ✅ 应用运营监控 +- ✅ 成员邀请和权限管理 +- ✅ 订单管理和发票 +- ✅ 费用中心和充值 +- ✅ 企业信息配置 + +### 3.4 角色切换机制 + +``` +用户登录 + │ + ├── 开发者身份 + │ └── 可进入开发者中心 + │ + ├── 企业身份 + │ └── 可进入企业控制台 + │ + └── 两者皆有 + └── 可切换身份入口 + ├── [开发者] 🛠️ 开发者中心 + └── [企业] 🏢 企业控制台 +``` + +--- + +## 四、开发优先级与里程碑 + +### Phase 1:基础框架 ⭐ 必须上线 + +**目标**:建立核心框架,实现最小可用产品 + +| 模块 | 页面 | 工作量 | 优先级 | +|------|------|--------|--------| +| 入口设计 | 首页快捷入口 + 身份切换 | ⭐⭐ | P0 | +| 开发者工作台 | 开发者首页/概览 | ⭐⭐⭐ | P0 | +| 项目管理 | 项目列表 + 创建 | ⭐⭐⭐⭐ | P0 | +| 应用管理 | 应用列表 + 创建 | ⭐⭐⭐⭐ | P0 | +| 企业工作台 | 企业首页/概览 | ⭐⭐⭐ | P0 | +| 企业应用 | 应用列表 + 详情 | ⭐⭐⭐⭐ | P0 | + +### Phase 2:核心功能 ⭐ 必须上线 + +**目标**:完成核心业务闭环 + +| 模块 | 页面 | 工作量 | 优先级 | +|------|------|--------|--------| +| API Key | 生成/查看/禁用 | ⭐⭐⭐ | P0 | +| 项目成员 | 邀请/移除/角色 | ⭐⭐⭐ | P0 | +| 应用发布 | 版本管理/发布 | ⭐⭐⭐ | P0 | +| 企业成员 | 列表/邀请/角色 | ⭐⭐⭐⭐ | P0 | +| 订单管理 | 列表/详情 | ⭐⭐⭐ | P0 | +| 账单查看 | 消费明细 | ⭐⭐⭐ | P0 | + +### Phase 3:增强功能 🔄 后续迭代 + +| 模块 | 页面 | 工作量 | 优先级 | +|------|------|--------|--------| +| 开发者申请 | 申请流程/审核 | ⭐⭐⭐ | P1 | +| 权限审批 | 权限申请/审批 | ⭐⭐⭐ | P1 | +| 市场浏览 | 模板/插件市场 | ⭐⭐⭐⭐ | P1 | +| 运营监控 | 数据看板 | ⭐⭐⭐⭐ | P1 | +| 开发者文档 | API文档/SDK | ⭐⭐⭐⭐ | P1 | +| 消息通知 | 通知中心 | ⭐⭐⭐ | P1 | + +### Phase 4:高级功能 🔄 后续迭代 + +| 模块 | 页面 | 工作量 | 优先级 | +|------|------|--------|--------| +| CI/CD | 流水线管理 | ⭐⭐⭐⭐⭐ | P2 | +| 部署管理 | 一键部署 | ⭐⭐⭐⭐ | P2 | +| 数据分析 | 高级统计 | ⭐⭐⭐⭐ | P2 | +| 工单系统 | 技术支持 | ⭐⭐⭐ | P2 | +| 发票管理 | 电子发票 | ⭐⭐⭐ | P2 | +| SSO | 单点登录 | ⭐⭐⭐⭐⭐ | P2 | + +--- + +## 五、与现有模块的关系 + +### 5.1 独立开发,不依赖现有模块 + +| 新建模块 | 说明 | +|---------|------| +| developer/ | 开发者中心全套功能 | +| enterprise/ | 企业控制台全套功能 | + +### 5.2 共享基础设施 + +| 共享内容 | 说明 | +|---------|------| +| 用户认证 | 复用 passport 模块的登录体系 | +| 用户状态 | 复用 globalData/storage 中的用户信息 | +| 消息通知 | 可复用 notification 组件 | +| 基础 UI | 复用组件库样式 | + +### 5.3 不复用模块 + +- ❌ 不复用 shop/ (商城) - 独立的企业应用体系 +- ❌ 不复用 dealer/ (分销) - 独立的开发者体系 +- ❌ 不复用 user/order (用户订单) - 使用新的企业订单体系 + +--- + +## 六、技术实现建议 + +### 6.1 目录结构 + +``` +src/ +├── developer/ # 🛠️ 开发者模块 +│ ├── index.tsx # 开发者入口 +│ ├── project/ # 项目管理 +│ ├── app/ # 应用管理 +│ ├── market/ # 市场 +│ └── docs/ # 文档 +│ +├── enterprise/ # 🏢 企业模块 +│ ├── index.tsx # 企业入口 +│ ├── apps/ # 企业应用 +│ ├── members/ # 成员管理 +│ ├── orders/ # 订单账单 +│ ├── billing/ # 费用中心 +│ └── settings/ # 企业设置 +│ +└── shared/ # 共享 + ├── components/ # 共享组件 + ├── types/ # 类型定义 + └── api/ # API 接口 +``` + +### 6.2 路由配置 + +```typescript +// app.config.ts +export default { + subpackages: [ + { + root: "developer", + pages: [ + "index", + "project/index", + "project/create", + "project/[id]/index", + "project/[id]/settings", + "app/index", + "app/create", + "app/[id]/index", + // ... + ] + }, + { + root: "enterprise", + pages: [ + "index", + "apps/index", + "apps/[id]/index", + "members/index", + "members/invite", + "orders/index", + "orders/detail", + // ... + ] + } + ] +} +``` + +--- + +## 七、API 接口设计建议 + +### 7.1 项目相关 + +| 接口 | 方法 | 说明 | +|------|------|------| +| /api/project/list | GET | 项目列表 | +| /api/project/create | POST | 创建项目 | +| /api/project/:id | GET/PUT/DELETE | 项目 CRUD | +| /api/project/:id/members | GET/POST/DELETE | 项目成员 | +| /api/project/:id/keys | GET/POST/DELETE | API Key | + +### 7.2 应用相关 + +| 接口 | 方法 | 说明 | +|------|------|------| +| /api/app/list | GET | 应用列表 | +| /api/app/create | POST | 创建应用 | +| /api/app/:id | GET/PUT/DELETE | 应用 CRUD | +| /api/app/:id/version | POST | 发布版本 | + +### 7.3 企业相关 + +| 接口 | 方法 | 说明 | +|------|------|------| +| /api/enterprise/info | GET | 企业信息 | +| /api/enterprise/members | GET/POST | 成员管理 | +| /api/enterprise/roles | GET/POST | 角色管理 | +| /api/enterprise/orders | GET | 订单列表 | +| /api/enterprise/bills | GET | 账单明细 | + +--- + +## 八、下一步行动 + +### 立即开始 (Phase 1) + +1. ✅ 创建 developer/ 和 enterprise/ 目录结构 +2. ✅ 设计 API 接口文档 (与后端对齐) +3. ✅ 开发首页入口组件 +4. ✅ 开发工作台基础页面 + +### 需要确认 + +- [ ] 后端 API 是否已准备好? +- [ ] 数据库表结构是否已设计? +- [ ] 是否需要支持微信小程序码扫码登录? + +--- + +**文档版本**:v1.0 +**最后更新**:2026-04-12 diff --git a/src/api/analytics.ts b/src/api/analytics.ts new file mode 100644 index 0000000..e550bbe --- /dev/null +++ b/src/api/analytics.ts @@ -0,0 +1,226 @@ +/** + * 数据分析/运营监控 API + */ +import { request } from '../utils/request' +import type { + OverviewStats, + ApiCallStat, + ErrorStat, + PerformanceMetric, + RegionStat, + DeviceStat, + BrowserStat, + OsStat, + SourceStat, + PageStat, + EventTrack, + AnalyticsParam, +} from '../types/analytics' + +// ==================== 概览统计 ==================== + +/** 获取概览统计 */ +export const getOverviewStats = (websiteId: number, date?: string) => { + return request({ + url: '/developer/analytics/overview', + method: 'GET', + params: { websiteId, date }, + }) +} + +/** 获取趋势数据 */ +export const getTrendData = ( + websiteId: number, + startDate: string, + endDate: string, + granularity: 'hour' | 'day' | 'week' | 'month' = 'day' +) => { + return request<{ list: ApiCallStat[] }>({ + url: '/developer/analytics/trend', + method: 'GET', + params: { websiteId, startDate, endDate, granularity }, + }) +} + +/** 获取今日实时数据 */ +export const getRealtimeData = (websiteId: number) => { + return request<{ + uv: number + pv: number + apiCalls: number + errors: number + activeUsers: number + }>({ + url: '/developer/analytics/realtime', + method: 'GET', + params: { websiteId }, + }) +} + +// ==================== API 调用统计 ==================== + +/** 获取 API 调用统计 */ +export const pageApiCalls = (params: AnalyticsParam) => { + return request<{ list: ApiCallStat[]; total: number }>({ + url: '/developer/analytics/api-calls', + method: 'GET', + params, + }) +} + +// ==================== 错误统计 ==================== + +/** 获取错误列表 */ +export const pageErrors = (params: AnalyticsParam) => { + return request<{ list: ErrorStat[]; total: number }>({ + url: '/developer/analytics/errors', + method: 'GET', + params, + }) +} + +/** 获取错误详情 */ +export const getErrorDetail = (id: number) => { + return request({ + url: `/developer/analytics/errors/${id}`, + method: 'GET', + }) +} + +// ==================== 性能指标 ==================== + +/** 获取性能指标 */ +export const getPerformanceMetrics = (websiteId: number) => { + return request<{ list: PerformanceMetric[] }>({ + url: '/developer/analytics/performance', + method: 'GET', + params: { websiteId }, + }) +} + +/** 获取性能趋势 */ +export const getPerformanceTrend = ( + websiteId: number, + metric: string, + startDate: string, + endDate: string +) => { + return request<{ list: PerformanceMetric[] }>({ + url: '/developer/analytics/performance/trend', + method: 'GET', + params: { websiteId, metric, startDate, endDate }, + }) +} + +// ==================== 用户统计 ==================== + +/** 获取用户统计 */ +export const getUserStats = (websiteId: number) => { + return request<{ + total: number + active: number + new: number + retention: number + }>({ + url: '/developer/analytics/users', + method: 'GET', + params: { websiteId }, + }) +} + +/** 获取新增用户趋势 */ +export const getNewUsersTrend = ( + websiteId: number, + startDate: string, + endDate: string +) => { + return request<{ list: { date: string; count: number }[] }>({ + url: '/developer/analytics/users/trend', + method: 'GET', + params: { websiteId, startDate, endDate }, + }) +} + +// ==================== 区域分布 ==================== + +/** 获取区域分布 */ +export const getRegionStats = (websiteId: number) => { + return request<{ list: RegionStat[] }>({ + url: '/developer/analytics/regions', + method: 'GET', + params: { websiteId }, + }) +} + +// ==================== 设备统计 ==================== + +/** 获取设备类型统计 */ +export const getDeviceStats = (websiteId: number) => { + return request<{ list: DeviceStat[] }>({ + url: '/developer/analytics/devices', + method: 'GET', + params: { websiteId }, + }) +} + +/** 获取浏览器统计 */ +export const getBrowserStats = (websiteId: number) => { + return request<{ list: BrowserStat[] }>({ + url: '/developer/analytics/browsers', + method: 'GET', + params: { websiteId }, + }) +} + +/** 获取操作系统统计 */ +export const getOsStats = (websiteId: number) => { + return request<{ list: OsStat[] }>({ + url: '/developer/analytics/os', + method: 'GET', + params: { websiteId }, + }) +} + +// ==================== 来源统计 ==================== + +/** 获取流量来源 */ +export const getSourceStats = (websiteId: number) => { + return request<{ list: SourceStat[] }>({ + url: '/developer/analytics/sources', + method: 'GET', + params: { websiteId }, + }) +} + +/** 获取页面访问排行 */ +export const getPageStats = (params: AnalyticsParam) => { + return request<{ list: PageStat[]; total: number }>({ + url: '/developer/analytics/pages', + method: 'GET', + params, + }) +} + +// ==================== 事件追踪 ==================== + +/** 获取事件列表 */ +export const pageEvents = (params: AnalyticsParam) => { + return request<{ list: EventTrack[]; total: number }>({ + url: '/developer/analytics/events', + method: 'GET', + params, + }) +} + +/** 触发自定义事件 */ +export const trackEvent = (data: { + websiteId: number + event: string + properties?: Record +}) => { + return request({ + url: '/developer/analytics/track', + method: 'POST', + data, + }) +} diff --git a/src/api/cicd.ts b/src/api/cicd.ts new file mode 100644 index 0000000..8307a00 --- /dev/null +++ b/src/api/cicd.ts @@ -0,0 +1,193 @@ +/** + * CI/CD 流水线 API + */ +import { request } from '../utils/request' +import type { + Build, + BuildParam, + BuildStatus, + Deploy, + DeployParam, + DeployStatus, + PipelineConfig, + RuntimeInstance, +} from '../types/cicd' + +// ==================== 构建相关 ==================== + +/** 获取构建列表 */ +export const pageBuild = (params: BuildParam) => { + return request<{ list: Build[]; total: number }>({ + url: '/developer/build/page', + method: 'GET', + params, + }) +} + +/** 获取构建详情 */ +export const getBuild = (id: number) => { + return request({ + url: `/developer/build/${id}`, + method: 'GET', + }) +} + +/** 触发构建 */ +export const triggerBuild = (data: { websiteId: number; branch?: string; commitId?: string }) => { + return request({ + url: '/developer/build/trigger', + method: 'POST', + data, + }) +} + +/** 取消构建 */ +export const cancelBuild = (id: number) => { + return request({ + url: `/developer/build/${id}/cancel`, + method: 'POST', + }) +} + +/** 获取构建日志 */ +export const getBuildLogs = (id: number) => { + return request<{ logs: string }>({ + url: `/developer/build/${id}/logs`, + method: 'GET', + }) +} + +/** 获取构建产物 */ +export const getBuildArtifacts = (id: number) => { + return request<{ artifacts: any[] }>({ + url: `/developer/build/${id}/artifacts`, + method: 'GET', + }) +} + +// ==================== 部署相关 ==================== + +/** 获取部署列表 */ +export const pageDeploy = (params: DeployParam) => { + return request<{ list: Deploy[]; total: number }>({ + url: '/developer/deploy/page', + method: 'GET', + params, + }) +} + +/** 获取部署详情 */ +export const getDeploy = (id: number) => { + return request({ + url: `/developer/deploy/${id}`, + method: 'GET', + }) +} + +/** 触发部署 */ +export const triggerDeploy = (data: { + websiteId: number + buildId: number + env: string +}) => { + return request({ + url: '/developer/deploy/trigger', + method: 'POST', + data, + }) +} + +/** 回滚部署 */ +export const rollbackDeploy = (id: number) => { + return request({ + url: `/developer/deploy/${id}/rollback`, + method: 'POST', + }) +} + +/** 获取部署日志 */ +export const getDeployLogs = (id: number) => { + return request<{ logs: string }>({ + url: `/developer/deploy/${id}/logs`, + method: 'GET', + }) +} + +// ==================== 流水线配置相关 ==================== + +/** 获取流水线配置 */ +export const getPipelineConfig = (websiteId: number) => { + return request({ + url: `/developer/pipeline/config/${websiteId}`, + method: 'GET', + }) +} + +/** 更新流水线配置 */ +export const updatePipelineConfig = (data: PipelineConfig) => { + return request({ + url: '/developer/pipeline/config', + method: 'PUT', + data, + }) +} + +/** 获取分支列表 */ +export const getBranches = (websiteId: number) => { + return request<{ branches: string[] }>({ + url: `/developer/pipeline/branches/${websiteId}`, + method: 'GET', + }) +} + +/** 添加环境变量 */ +export const addEnvVar = (websiteId: number, data: { key: string; value: string; isSecret?: boolean }) => { + return request({ + url: `/developer/pipeline/env/${websiteId}`, + method: 'POST', + data, + }) +} + +/** 删除环境变量 */ +export const deleteEnvVar = (websiteId: number, key: string) => { + return request({ + url: `/developer/pipeline/env/${websiteId}/${key}`, + method: 'DELETE', + }) +} + +// ==================== 运行时相关 ==================== + +/** 获取运行时实例列表 */ +export const pageRuntime = (websiteId: number, env?: string) => { + return request<{ list: RuntimeInstance[]; total: number }>({ + url: '/developer/runtime/page', + method: 'GET', + params: { websiteId, env }, + }) +} + +/** 启动实例 */ +export const startRuntime = (id: number) => { + return request({ + url: `/developer/runtime/${id}/start`, + method: 'POST', + }) +} + +/** 停止实例 */ +export const stopRuntime = (id: number) => { + return request({ + url: `/developer/runtime/${id}/stop`, + method: 'POST', + }) +} + +/** 重启实例 */ +export const restartRuntime = (id: number) => { + return request({ + url: `/developer/runtime/${id}/restart`, + method: 'POST', + }) +} diff --git a/src/api/developer/developer.ts b/src/api/developer/developer.ts new file mode 100644 index 0000000..1b6b4bd --- /dev/null +++ b/src/api/developer/developer.ts @@ -0,0 +1,348 @@ +import request from '@/utils/request' +import type { ApiResult, PageResult } from '@/api' +import type { + App, + AppParam, + ApiKey, + ApiKeyParam, + Developer, + DeveloperApplyParam, + Project, + ProjectParam, + ProjectMember, + Version, + VersionParam, + Notification, + NotificationParam, + Apply, + ApplyParam, +} from '@/types/developer' + +// ==================== 项目相关 ==================== + +/** + * 获取我的项目列表 + */ +export async function pageMyProject(params?: ProjectParam) { + const res = await request.get>>('/project/my/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取项目列表 + */ +export async function pageProject(params?: ProjectParam) { + const res = await request.get>>('/project/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取项目详情 + */ +export async function getProject(id: number) { + const res = await request.get>(`/project/${id}`) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 创建项目 + */ +export async function createProject(data: Partial) { + const res = await request.post>('/project', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 更新项目 + */ +export async function updateProject(data: Partial) { + const res = await request.put>(`/project/${data.id}`, data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 删除项目 + */ +export async function deleteProject(id: number) { + const res = await request.del>(`/project/${id}`) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 获取项目成员列表 + */ +export async function listProjectMember(projectId: number) { + const res = await request.get>(`/project/${projectId}/members`) + if (res.code === 0 && res.data) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 添加项目成员 + */ +export async function addProjectMember(projectId: number, data: Partial) { + const res = await request.post>(`/project/${projectId}/members`, data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 移除项目成员 + */ +export async function removeProjectMember(projectId: number, memberId: number) { + const res = await request.del>(`/project/${projectId}/members/${memberId}`) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +// ==================== 应用相关 ==================== + +/** + * 分页查询我的应用 + */ +export async function pageMyApp(params?: AppParam) { + const res = await request.get>>('/app/product/my/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取应用列表 + */ +export async function pageApp(params?: AppParam) { + const res = await request.get>>('/app/product/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取应用详情 + */ +export async function getApp(id: number) { + const res = await request.get>(`/app/product/${id}`) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 创建应用 + */ +export async function createApp(data: Partial) { + const res = await request.post>('/app/product', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 更新应用 + */ +export async function updateApp(data: Partial) { + const res = await request.put>(`/app/product/${data.id}`, data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 删除应用 + */ +export async function deleteApp(id: number) { + const res = await request.del>(`/app/product/${id}`) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +// ==================== API Key 相关 ==================== + +/** + * 获取 API Key 列表 + */ +export async function pageApiKey(params?: ApiKeyParam) { + const res = await request.get>>('/app/app-credential/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取 API Key 列表(不分页) + */ +export async function listApiKey(params?: ApiKeyParam) { + const res = await request.get>('/app/app-credential', params) + if (res.code === 0 && res.data) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 创建 API Key + */ +export async function createApiKey(data: Partial) { + const res = await request.post>('/app/app-credential', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 更新 API Key + */ +export async function updateApiKey(data: Partial) { + const res = await request.put>('/app/app-credential', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 删除 API Key + */ +export async function deleteApiKey(id: number) { + const res = await request.del>(`/app/app-credential/${id}`) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +// ==================== 版本发布相关 ==================== + +/** + * 获取版本列表 + */ +export async function pageVersion(params?: VersionParam) { + const res = await request.get>>('/app/app-version/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取版本详情 + */ +export async function getVersion(id: number) { + const res = await request.get>(`/app/app-version/${id}`) + if (res.code === 0 && res.data) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 创建版本 + */ +export async function createVersion(data: Partial) { + const res = await request.post>('/app/app-version', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +// ==================== 开发者相关 ==================== + +/** + * 获取开发者信息 + */ +export async function getDeveloperInfo() { + const res = await request.get>('/developer/info') + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 申请成为开发者 + */ +export async function applyDeveloper(data: DeveloperApplyParam) { + const res = await request.post>('/developer/apply', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 更新开发者信息 + */ +export async function updateDeveloperInfo(data: Partial) { + const res = await request.put>('/developer/info', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +// ==================== 消息通知相关 ==================== + +/** + * 获取通知列表 + */ +export async function pageNotification(params?: NotificationParam) { + const res = await request.get>>('/notification/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取未读通知数量 + */ +export async function getUnreadCount() { + const res = await request.get>('/notification/unread-count') + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 标记通知为已读 + */ +export async function markAsRead(id: number) { + const res = await request.put>(`/notification/${id}/read`) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 标记所有通知为已读 + */ +export async function markAllAsRead() { + const res = await request.put>('/notification/read-all') + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 删除通知 + */ +export async function deleteNotification(id: number) { + const res = await request.del>(`/notification/${id}`) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +// ==================== 权限审批相关 ==================== + +/** + * 获取申请列表 + */ +export async function pageApply(params?: ApplyParam) { + const res = await request.get>>('/apply/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取我的申请列表 + */ +export async function pageMyApply(params?: ApplyParam) { + const res = await request.get>>('/apply/my/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 创建申请 + */ +export async function createApply(data: Partial) { + const res = await request.post>('/apply', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 审批申请 + */ +export async function reviewApply(id: number, status: 'approved' | 'rejected', remark?: string) { + const res = await request.put>(`/apply/${id}/review`, { status, remark }) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} diff --git a/src/api/developer/enterprise.ts b/src/api/developer/enterprise.ts new file mode 100644 index 0000000..6fe5b2b --- /dev/null +++ b/src/api/developer/enterprise.ts @@ -0,0 +1,139 @@ +import request from '@/utils/request' +import type { ApiResult, PageResult } from '@/api' +import type { Enterprise, EnterpriseMember, EnterpriseMemberParam, Order, Bill, BillParam, App, AppParam } from '@/types/developer' + +// ==================== 企业相关 ==================== + +/** + * 获取企业信息 + */ +export async function getEnterpriseInfo() { + const res = await request.get>('/enterprise/info') + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 更新企业信息 + */ +export async function updateEnterpriseInfo(data: Partial) { + const res = await request.put>('/enterprise/info', data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +// ==================== 企业成员相关 ==================== + +/** + * 获取企业成员列表 + */ +export async function pageEnterpriseMember(params?: EnterpriseMemberParam) { + const res = await request.get>>('/enterprise/member/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取企业成员列表(不分页) + */ +export async function listEnterpriseMember(params?: EnterpriseMemberParam) { + const res = await request.get>('/enterprise/member', params) + if (res.code === 0 && res.data) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 邀请企业成员 + */ +export async function inviteEnterpriseMember(enterpriseId: number, data: Partial) { + const res = await request.post>(`/enterprise/member/${enterpriseId}/invite`, data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 更新企业成员 + */ +export async function updateEnterpriseMember(data: Partial) { + const res = await request.put>(`/enterprise/member/${data.id}`, data) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +/** + * 移除企业成员 + */ +export async function removeEnterpriseMember(memberId: number) { + const res = await request.del>(`/enterprise/member/${memberId}`) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} + +// ==================== 订单相关 ==================== + +/** + * 获取订单列表 + */ +export async function pageOrder(params?: { page?: number; limit?: number; status?: number }) { + const res = await request.get>>('/enterprise/order/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取订单详情 + */ +export async function getOrder(id: number) { + const res = await request.get>(`/enterprise/order/${id}`) + if (res.code === 0 && res.data) return res.data + return Promise.reject(new Error(res.message)) +} + +// ==================== 账单相关 ==================== + +/** + * 获取账单列表 + */ +export async function pageBill(params?: BillParam) { + const res = await request.get>>('/enterprise/bill/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取账单概览 + */ +export async function getBillOverview() { + const res = await request.get>('/enterprise/bill/overview') + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +// ==================== 企业应用相关 ==================== + +/** + * 获取企业应用列表 + */ +export async function pageEnterpriseApp(params?: AppParam) { + const res = await request.get>>('/enterprise/app/page', { params }) + if (res.code === 0) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 获取企业应用详情 + */ +export async function getEnterpriseApp(id: number) { + const res = await request.get>(`/enterprise/app/${id}`) + if (res.code === 0 && res.data) return res.data + return Promise.reject(new Error(res.message)) +} + +/** + * 购买应用 + */ +export async function purchaseApp(productId: number) { + const res = await request.post>('/enterprise/app/purchase', { productId }) + if (res.code === 0) return res.message + return Promise.reject(new Error(res.message)) +} diff --git a/src/api/developer/index.ts b/src/api/developer/index.ts new file mode 100644 index 0000000..de8453b --- /dev/null +++ b/src/api/developer/index.ts @@ -0,0 +1,5 @@ +/** + * 开发者 API 模块 + */ +export * from './developer' +export * from './enterprise' diff --git a/src/api/import.ts b/src/api/import.ts new file mode 100644 index 0000000..cf0475c --- /dev/null +++ b/src/api/import.ts @@ -0,0 +1,331 @@ +/** + * 数据导入/导出 API + */ +import Taro from '@tarojs/taro'; +import type { ImportTask, ImportTemplate, ImportField, ImportError, ExportTask, ExportFormat, ExportType, ImportType } from '../types/import'; + +// 模拟数据 +const mockImportTasks: ImportTask[] = [ + { + id: '1', + name: '商品批量导入', + type: 'product', + status: 'completed', + total: 500, + success: 485, + failed: 15, + progress: 100, + fileName: 'products_20260412.xlsx', + fileSize: '1.2MB', + errorFile: '/downloads/import_error_1.xlsx', + creatorId: '1', + creatorName: '张三', + startTime: '2026-04-12 10:00:00', + endTime: '2026-04-12 10:05:30', + createTime: '2026-04-12 09:58:00' + }, + { + id: '2', + name: '用户数据迁移', + type: 'user', + status: 'importing', + total: 2000, + success: 1200, + failed: 0, + progress: 60, + fileName: 'users_migration.csv', + fileSize: '3.5MB', + creatorId: '1', + creatorName: '张三', + startTime: '2026-04-12 14:00:00', + createTime: '2026-04-12 13:55:00' + }, + { + id: '3', + name: '订单历史导入', + type: 'order', + status: 'validating', + total: 10000, + success: 0, + failed: 0, + progress: 25, + fileName: 'orders_history.xlsx', + fileSize: '8.2MB', + creatorId: '1', + creatorName: '张三', + startTime: '2026-04-12 15:00:00', + createTime: '2026-04-12 14:50:00' + } +]; + +const mockExportTasks: ExportTask[] = [ + { + id: '1', + name: '月度订单导出', + type: 'order', + status: 'completed', + format: 'xlsx', + filters: { month: '2026-03' }, + total: 5680, + fileName: 'orders_202603.xlsx', + fileSize: '2.8MB', + downloadUrl: '/downloads/orders_202603.xlsx', + expiresAt: '2026-04-19 15:00:00', + creatorId: '1', + creatorName: '张三', + createTime: '2026-04-12 10:00:00', + completeTime: '2026-04-12 10:02:30' + }, + { + id: '2', + name: '用户数据导出', + type: 'user', + status: 'processing', + format: 'csv', + filters: { registeredAfter: '2026-01-01' }, + total: 0, + creatorId: '1', + creatorName: '张三', + createTime: '2026-04-12 14:30:00' + } +]; + +// 导入模板字段定义 +const mockTemplates: ImportTemplate[] = [ + { + id: 't1', + name: '商品导入模板', + type: 'product', + description: '用于批量导入商品信息,支持商品名称、价格、库存等字段', + downloadUrl: '/templates/product_import.xlsx', + fields: [ + { key: 'name', name: '商品名称', type: 'string', required: true, maxLength: 100, example: 'iPhone 15 Pro' }, + { key: 'price', name: '价格', type: 'number', required: true, example: '8999.00' }, + { key: 'stock', name: '库存', type: 'number', required: true, example: '100' }, + { key: 'category', name: '分类', type: 'select', required: true, options: ['手机', '电脑', '配件', '其他'], example: '手机' }, + { key: 'description', name: '描述', type: 'string', required: false, maxLength: 500, example: '最新款苹果手机' }, + { key: 'status', name: '状态', type: 'select', required: true, options: ['上架', '下架'], example: '上架' }, + { key: 'images', name: '图片URL', type: 'string', required: false, example: 'https://example.com/img.jpg' } + ], + rules: [ + { field: 'name', rule: 'required', message: '商品名称不能为空' }, + { field: 'price', rule: 'range', message: '价格必须大于0', params: { min: 0.01 } }, + { field: 'stock', rule: 'required', message: '库存不能为空' } + ], + exampleUrl: '/templates/product_example.xlsx', + createTime: '2026-01-01' + }, + { + id: 't2', + name: '用户导入模板', + type: 'user', + description: '用于批量导入用户信息,包含用户名、手机、邮箱等', + downloadUrl: '/templates/user_import.xlsx', + fields: [ + { key: 'username', name: '用户名', type: 'string', required: true, maxLength: 50, example: 'zhangsan' }, + { key: 'phone', name: '手机号', type: 'string', required: true, pattern: '^1[3-9]\\d{9}$', example: '13800138000' }, + { key: 'email', name: '邮箱', type: 'string', required: false, pattern: '^\\w+@\\w+\\.\\w+$', example: 'user@example.com' }, + { key: 'nickname', name: '昵称', type: 'string', required: false, maxLength: 30, example: '小张' }, + { key: 'role', name: '角色', type: 'select', required: true, options: ['user', 'admin'], example: 'user' } + ], + rules: [ + { field: 'username', rule: 'unique', message: '用户名已存在' }, + { field: 'phone', rule: 'pattern', message: '手机号格式不正确' } + ], + createTime: '2026-01-01' + }, + { + id: 't3', + name: '订单导入模板', + type: 'order', + description: '用于导入历史订单数据', + downloadUrl: '/templates/order_import.xlsx', + fields: [ + { key: 'orderNo', name: '订单号', type: 'string', required: true, example: 'ORD202604120001' }, + { key: 'userId', name: '用户ID', type: 'string', required: true, example: '10001' }, + { key: 'amount', name: '订单金额', type: 'number', required: true, example: '299.00' }, + { key: 'status', name: '订单状态', type: 'select', required: true, options: ['pending', 'paid', 'shipped', 'completed', 'cancelled'], example: 'completed' }, + { key: 'createTime', name: '创建时间', type: 'date', required: true, example: '2026-04-01 10:30:00' } + ], + rules: [], + createTime: '2026-01-01' + } +]; + +/** + * 获取导入任务列表 + */ +export async function pageImportTask(params: { + type?: ImportType; + status?: string; + page?: number; + pageSize?: number; +}): Promise<{ list: ImportTask[]; total: number }> { + const { type, status, page = 1, pageSize = 10 } = params; + + let filtered = [...mockImportTasks]; + if (type) filtered = filtered.filter(t => t.type === type); + if (status) filtered = filtered.filter(t => t.status === status); + + const start = (page - 1) * pageSize; + return { list: filtered.slice(start, start + pageSize), total: filtered.length }; +} + +/** + * 获取导入任务详情 + */ +export async function getImportTaskDetail(id: string): Promise { + return mockImportTasks.find(t => t.id === id) || null; +} + +/** + * 获取导入错误详情 + */ +export async function getImportErrors(id: string): Promise { + return [ + { row: 15, field: 'price', value: '-100', error: '价格不能为负数', suggestion: '请输入正数' }, + { row: 28, field: 'name', value: '', error: '商品名称不能为空', suggestion: '请填写商品名称' }, + { row: 56, field: 'stock', value: 'abc', error: '库存必须是数字', suggestion: '请输入整数' } + ]; +} + +/** + * 获取导入模板列表 + */ +export async function listImportTemplate(): Promise { + return mockTemplates; +} + +/** + * 获取模板字段详情 + */ +export async function getTemplateFields(type: ImportType): Promise { + const template = mockTemplates.find(t => t.type === type); + return template?.fields || []; +} + +/** + * 创建导入任务 + */ +export async function createImportTask(data: { + name: string; + type: ImportType; + fileName: string; + fileSize: string; +}): Promise { + const task: ImportTask = { + id: String(Date.now()), + name: data.name, + type: data.type, + status: 'uploading', + total: 0, + success: 0, + failed: 0, + progress: 0, + fileName: data.fileName, + fileSize: data.fileSize, + creatorId: '1', + creatorName: '张三', + startTime: new Date().toISOString().replace('T', ' ').slice(0, 19), + createTime: new Date().toISOString().replace('T', ' ').slice(0, 19) + }; + + mockImportTasks.unshift(task); + return task; +} + +/** + * 取消导入任务 + */ +export async function cancelImportTask(id: string): Promise { + const task = mockImportTasks.find(t => t.id === id); + if (task) task.status = 'cancelled'; +} + +/** + * 删除导入任务 + */ +export async function deleteImportTask(id: string): Promise { + const index = mockImportTasks.findIndex(t => t.id === id); + if (index > -1) mockImportTasks.splice(index, 1); +} + +/** + * 获取导出任务列表 + */ +export async function pageExportTask(params: { + type?: ExportType; + status?: string; + page?: number; + pageSize?: number; +}): Promise<{ list: ExportTask[]; total: number }> { + const { type, status, page = 1, pageSize = 10 } = params; + + let filtered = [...mockExportTasks]; + if (type) filtered = filtered.filter(t => t.type === type); + if (status) filtered = filtered.filter(t => t.status === status); + + const start = (page - 1) * pageSize; + return { list: filtered.slice(start, start + pageSize), total: filtered.length }; +} + +/** + * 创建导出任务 + */ +export async function createExportTask(data: { + name: string; + type: ExportType; + format: ExportFormat; + filters?: Record; +}): Promise { + const task: ExportTask = { + id: String(Date.now()), + name: data.name, + type: data.type, + status: 'pending', + format: data.format, + filters: data.filters || {}, + total: 0, + creatorId: '1', + creatorName: '张三', + createTime: new Date().toISOString().replace('T', ' ').slice(0, 19) + }; + + mockExportTasks.unshift(task); + return task; +} + +/** + * 删除导出任务 + */ +export async function deleteExportTask(id: string): Promise { + const index = mockExportTasks.findIndex(t => t.id === id); + if (index > -1) mockExportTasks.splice(index, 1); +} + +/** + * 下载导入模板 + */ +export function downloadTemplate(type: ImportType): void { + const template = mockTemplates.find(t => t.type === type); + if (template) { + Taro.showToast({ title: '开始下载模板', icon: 'success' }); + console.log(`Downloading: ${template.downloadUrl}`); + } +} + +/** + * 导出账单 + */ +export async function exportBill(params: { + startDate: string; + endDate: string; + format: ExportFormat; +}): Promise { + return createExportTask({ + name: `账单导出 ${params.startDate} ~ ${params.endDate}`, + type: 'bill', + format: params.format, + filters: { startDate: params.startDate, endDate: params.endDate } + }); +} diff --git a/src/api/invoice.ts b/src/api/invoice.ts new file mode 100644 index 0000000..006bee5 --- /dev/null +++ b/src/api/invoice.ts @@ -0,0 +1,109 @@ +/** + * 发票管理 API + */ +import { request } from '../utils/request' +import type { + Invoice, + InvoiceTitle, + InvoiceApplyParam, + InvoiceParam, + InvoiceStatus, + InvoiceType, +} from '../types/invoice' + +// ==================== 发票抬头 ==================== + +/** 获取发票抬头列表 */ +export const listInvoiceTitles = () => { + return request<{ list: InvoiceTitle[] }>({ + url: '/enterprise/invoice/titles', + method: 'GET', + }) +} + +/** 创建发票抬头 */ +export const createInvoiceTitle = (data: Omit) => { + return request({ + url: '/enterprise/invoice/titles', + method: 'POST', + data, + }) +} + +/** 更新发票抬头 */ +export const updateInvoiceTitle = (id: number, data: Partial) => { + return request({ + url: `/enterprise/invoice/titles/${id}`, + method: 'PUT', + data, + }) +} + +/** 删除发票抬头 */ +export const deleteInvoiceTitle = (id: number) => { + return request({ + url: `/enterprise/invoice/titles/${id}`, + method: 'DELETE', + }) +} + +/** 设置默认发票抬头 */ +export const setDefaultInvoiceTitle = (id: number) => { + return request({ + url: `/enterprise/invoice/titles/${id}/default`, + method: 'POST', + }) +} + +// ==================== 发票申请 ==================== + +/** 获取发票列表 */ +export const pageInvoice = (params: InvoiceParam) => { + return request<{ list: Invoice[]; total: number }>({ + url: '/enterprise/invoice/page', + method: 'GET', + params, + }) +} + +/** 获取发票详情 */ +export const getInvoice = (id: number) => { + return request({ + url: `/enterprise/invoice/${id}`, + method: 'GET', + }) +} + +/** 申请发票 */ +export const applyInvoice = (data: InvoiceApplyParam) => { + return request({ + url: '/enterprise/invoice/apply', + method: 'POST', + data, + }) +} + +/** 取消发票申请 */ +export const cancelInvoice = (id: number) => { + return request({ + url: `/enterprise/invoice/${id}/cancel`, + method: 'POST', + }) +} + +/** 重新申请发票 */ +export const reapplyInvoice = (id: number, data: Partial) => { + return request({ + url: `/enterprise/invoice/${id}/reapply`, + method: 'POST', + data, + }) +} + +/** 获取可开票金额 */ +export const getInvoiceableAmount = () => { + return request<{ amount: number }>({ + url: '/enterprise/invoice/amount', + method: 'GET', + }) +} diff --git a/src/api/sdk.ts b/src/api/sdk.ts new file mode 100644 index 0000000..41aa5cf --- /dev/null +++ b/src/api/sdk.ts @@ -0,0 +1,287 @@ +/** + * SDK 管理 API + */ +import type { SDK, SDKVersion, SDKDownloadRecord, SDKCategory } from '../types/sdk'; + +// 模拟数据 +const mockSDKs: SDK[] = [ + { + id: '1', + name: 'JavaScript SDK', + icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/javascript@1.0.0/javascript.png', + description: '适用于 Web 浏览器和 Node.js 的 JavaScript SDK', + version: '2.8.0', + language: 'JavaScript', + category: 'web', + downloadCount: 125680, + stars: 3420, + docsUrl: '/docs/sdk/javascript', + npmPackage: '@websopy/sdk-js', + downloadUrl: 'https://www.npmjs.com/package/@websopy/sdk-js', + changelog: '- 优化性能 20%\n- 新增 TypeScript 支持\n- 修复若干 bug', + dependencies: ['axios'], + supportedVersions: ['ES6+', 'Node.js 14+'], + lastUpdated: '2026-04-10' + }, + { + id: '2', + name: 'TypeScript SDK', + icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/typescript@3.0.3/typescript.png', + description: '完整的 TypeScript 类型支持,适用于现代 Web 开发', + version: '2.8.0', + language: 'TypeScript', + category: 'web', + downloadCount: 98650, + stars: 2890, + docsUrl: '/docs/sdk/typescript', + npmPackage: '@websopy/sdk-ts', + downloadUrl: 'https://www.npmjs.com/package/@websopy/sdk-ts', + changelog: '- 完整的类型定义\n- 更好的 IDE 支持\n- 新增装饰器支持', + dependencies: [], + supportedVersions: ['TypeScript 4.0+'], + lastUpdated: '2026-04-10' + }, + { + id: '3', + name: '微信小程序 SDK', + icon: 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico', + description: '专为微信小程序优化的 SDK,支持插件机制', + version: '2.7.5', + language: 'JavaScript', + category: 'mini-program', + downloadCount: 78540, + stars: 1560, + docsUrl: '/docs/sdk/wx-mini-program', + npmPackage: '@websopy/sdk-wx', + downloadUrl: 'https://www.npmjs.com/package/@websopy/sdk-wx', + changelog: '- 适配最新微信版本\n- 新增分享能力\n- 优化包体积', + dependencies: ['wechat-miniprogram'], + supportedVersions: ['微信小程序 2.0+'], + lastUpdated: '2026-04-08' + }, + { + id: '4', + name: 'Flutter SDK', + icon: 'https://cdn.jsdelivr.net/gh/flutter/website@main/src/assets/images/flutter-icon.svg', + description: 'Flutter 跨平台 SDK,支持 iOS 和 Android', + version: '1.5.2', + language: 'Flutter', + category: 'mobile', + downloadCount: 45230, + stars: 980, + docsUrl: '/docs/sdk/flutter', + downloadUrl: 'https://pub.dev/packages/websopy_flutter', + changelog: '- 支持 Flutter 3.0\n- 新增状态管理\n- 优化首屏加载', + dependencies: ['http', 'shared_preferences'], + supportedVersions: ['Flutter 2.0+', 'Dart 2.12+'], + lastUpdated: '2026-04-05' + }, + { + id: '5', + name: 'React Native SDK', + icon: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png', + description: 'React Native 原生模块,完美的性能体验', + version: '2.4.0', + language: 'React Native', + category: 'mobile', + downloadCount: 38560, + stars: 870, + docsUrl: '/docs/sdk/react-native', + downloadUrl: 'https://www.npmjs.com/package/@websopy/sdk-rn', + changelog: '- 新增 Hooks API\n- 支持 TypeScript\n- 修复内存泄漏', + dependencies: ['react-native'], + supportedVersions: ['React Native 0.65+', 'React 17+'], + lastUpdated: '2026-04-03' + }, + { + id: '6', + name: 'Java SDK', + icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/java@1.0.0/java-original.svg', + description: '企业级 Java SDK,支持 Spring Boot 集成', + version: '3.2.0', + language: 'Java', + category: 'server', + downloadCount: 65420, + stars: 1230, + docsUrl: '/docs/sdk/java', + downloadUrl: '/downloads/sdk-java-3.2.0.jar', + changelog: '- 支持 Spring Boot 3.0\n- 新增响应式编程\n- 优化连接池', + dependencies: ['slf4j', 'jackson'], + supportedVersions: ['Java 8+', 'Spring Boot 2.0+'], + lastUpdated: '2026-04-01' + }, + { + id: '7', + name: 'Go SDK', + icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/go@1.0.0/go-original.svg', + description: '高性能 Go SDK,支持 goroutine 并发', + version: '2.5.0', + language: 'Go', + category: 'server', + downloadCount: 32180, + stars: 760, + docsUrl: '/docs/sdk/go', + downloadUrl: 'go get github.com/websopy/sdk-go@latest', + changelog: '- 支持 Go 1.18 泛型\n- 新增中间件支持\n- 优化性能', + dependencies: [], + supportedVersions: ['Go 1.16+'], + lastUpdated: '2026-03-28' + }, + { + id: '8', + name: 'Python SDK', + icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/python@3.10.0/python-original.svg', + description: 'Pythonic 的 Python SDK,支持异步编程', + version: '2.6.0', + language: 'Python', + category: 'server', + downloadCount: 52100, + stars: 980, + docsUrl: '/docs/sdk/python', + downloadUrl: 'pip install websopy-sdk', + changelog: '- 支持 asyncio\n- 新增类型提示\n- 兼容 Python 3.11', + dependencies: ['requests', 'typing-extensions'], + supportedVersions: ['Python 3.7+'], + lastUpdated: '2026-03-25' + }, + { + id: '9', + name: 'Swift SDK', + icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/swift@1.0.0/swift-original.svg', + description: '原生 iOS/macOS Swift SDK,支持 SwiftUI', + version: '1.8.0', + language: 'Swift', + category: 'mobile', + downloadCount: 24560, + stars: 560, + docsUrl: '/docs/sdk/swift', + downloadUrl: 'https://github.com/websopy/sdk-swift/releases', + changelog: '- 支持 Swift 5.7\n- 新增 Combine 支持\n- 优化包体积', + dependencies: [], + supportedVersions: ['iOS 13+', 'macOS 11+', 'Swift 5.0+'], + lastUpdated: '2026-03-20' + }, + { + id: '10', + name: '.NET SDK', + icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/dot-net@1.0.0/dot-net-original.svg', + description: '.NET 5/6/7 SDK,支持 Entity Framework Core', + version: '2.3.0', + language: '.NET', + category: 'server', + downloadCount: 28940, + stars: 450, + docsUrl: '/docs/sdk/dotnet', + downloadUrl: 'https://www.nuget.org/packages/WebsopySDK', + changelog: '- 支持 .NET 7\n- 新增依赖注入\n- 支持 Blazor', + dependencies: ['Microsoft.Extensions.DependencyInjection'], + supportedVersions: ['.NET 5+', '.NET Core 3.1+'], + lastUpdated: '2026-03-18' + } +]; + +/** + * 获取 SDK 分类列表 + */ +export async function listSDKCategories(): Promise { + return [ + { id: 'web', name: 'Web 开发', icon: 'globe', count: 2, description: '适用于浏览器和 Node.js 环境' }, + { id: 'mobile', name: '移动开发', icon: 'mobile', count: 3, description: 'iOS、Android、Flutter、React Native' }, + { id: 'server', name: '服务端', icon: 'server', count: 4, description: 'Java、Go、Python、.NET 等后端语言' }, + { id: 'mini-program', name: '小程序', icon: 'wechat', count: 1, description: '微信小程序、支付宝小程序等' } + ]; +} + +/** + * 获取 SDK 列表 + */ +export async function pageSDK(params: { + category?: string; + language?: string; + keyword?: string; + page?: number; + pageSize?: number; +}): Promise<{ list: SDK[]; total: number }> { + const { category, language, keyword, page = 1, pageSize = 10 } = params; + + let filtered = [...mockSDKs]; + + if (category) { + filtered = filtered.filter(sdk => sdk.category === category); + } + if (language) { + filtered = filtered.filter(sdk => sdk.language === language); + } + if (keyword) { + const kw = keyword.toLowerCase(); + filtered = filtered.filter(sdk => + sdk.name.toLowerCase().includes(kw) || + sdk.description.toLowerCase().includes(kw) + ); + } + + const start = (page - 1) * pageSize; + const list = filtered.slice(start, start + pageSize); + + return { list, total: filtered.length }; +} + +/** + * 获取 SDK 详情 + */ +export async function getSDKDetail(id: string): Promise { + return mockSDKs.find(sdk => sdk.id === id) || null; +} + +/** + * 获取 SDK 版本列表 + */ +export async function listSDKVersions(id: string): Promise { + const sdk = mockSDKs.find(s => s.id === id); + if (!sdk) return []; + + return [ + { version: sdk.version, releaseDate: sdk.lastUpdated, releaseNotes: sdk.changelog, downloadUrl: sdk.downloadUrl, size: '2.5MB', sha256: 'a1b2c3d4e5f6...' }, + { version: '2.7.0', releaseDate: '2026-03-15', releaseNotes: '- 性能优化\n- Bug 修复', downloadUrl: '', size: '2.3MB', sha256: 'b2c3d4e5f6g7...' }, + { version: '2.6.0', releaseDate: '2026-02-20', releaseNotes: '- 新增功能\n- 文档更新', downloadUrl: '', size: '2.1MB', sha256: 'c3d4e5f6g7h8...' } + ]; +} + +/** + * 获取下载记录 + */ +export async function pageDownloadRecord(params: { + page?: number; + pageSize?: number; +}): Promise<{ list: SDKDownloadRecord[]; total: number }> { + return { + list: [ + { id: '1', sdkId: '1', sdkName: 'JavaScript SDK', version: '2.8.0', language: 'JavaScript', userId: '1', userName: '张三', downloadTime: '2026-04-12 14:30:00', ipAddress: '127.0.0.1' }, + { id: '2', sdkId: '3', sdkName: '微信小程序 SDK', version: '2.7.5', language: 'JavaScript', userId: '1', userName: '张三', downloadTime: '2026-04-11 10:20:00', ipAddress: '127.0.0.1' }, + { id: '3', sdkId: '6', sdkName: 'Java SDK', version: '3.2.0', language: 'Java', userId: '1', userName: '张三', downloadTime: '2026-04-10 16:45:00', ipAddress: '127.0.0.1' } + ], + total: 3 + }; +} + +/** + * 记录 SDK 下载 + */ +export async function recordDownload(id: string, version: string): Promise { + console.log(`Download recorded: ${id} v${version}`); +} + +/** + * 获取 SDK 统计 + */ +export async function getSDKStats(): Promise<{ + totalDownloads: number; + totalSDKs: number; + topSDKs: SDK[]; +}> { + return { + totalDownloads: mockSDKs.reduce((sum, sdk) => sum + sdk.downloadCount, 0), + totalSDKs: mockSDKs.length, + topSDKs: [...mockSDKs].sort((a, b) => b.downloadCount - a.downloadCount).slice(0, 5) + }; +} diff --git a/src/api/sso.ts b/src/api/sso.ts new file mode 100644 index 0000000..0b05e39 --- /dev/null +++ b/src/api/sso.ts @@ -0,0 +1,114 @@ +/** + * SSO 单点登录 API + */ +import { request } from '../utils/request' +import type { SSOConfig, SSOSession, SSOLog, SSOProvider, SyncDirection } from '../types/sso' + +// ==================== SSO 配置 ==================== + +/** 获取 SSO 配置 */ +export const getSSOConfig = (enterpriseId: number) => { + return request({ + url: `/enterprise/sso/config/${enterpriseId}`, + method: 'GET', + }) +} + +/** 创建 SSO 配置 */ +export const createSSOConfig = (data: Omit) => { + return request({ + url: '/enterprise/sso/config', + method: 'POST', + data, + }) +} + +/** 更新 SSO 配置 */ +export const updateSSOConfig = (data: Partial & { id: number }) => { + return request({ + url: '/enterprise/sso/config', + method: 'PUT', + data, + }) +} + +/** 删除 SSO 配置 */ +export const deleteSSOConfig = (id: number) => { + return request({ + url: `/enterprise/sso/config/${id}`, + method: 'DELETE', + }) +} + +/** 启用/禁用 SSO */ +export const toggleSSO = (id: number, enabled: boolean) => { + return request({ + url: `/enterprise/sso/config/${id}/toggle`, + method: 'POST', + data: { enabled }, + }) +} + +/** 测试 SSO 连接 */ +export const testSSOConnection = (id: number) => { + return request<{ success: boolean; message: string }>({ + url: `/enterprise/sso/config/${id}/test`, + method: 'POST', + }) +} + +/** 生成 SSO 登录链接 */ +export const getSSOLoginUrl = (id: number, redirectUri?: string) => { + return request<{ url: string }>({ + url: `/enterprise/sso/config/${id}/login-url`, + method: 'GET', + params: { redirectUri }, + }) +} + +// ==================== SSO 会话 ==================== + +/** 获取 SSO 会话列表 */ +export const pageSSOSessions = (params: { enterpriseId: number; page?: number; limit?: number }) => { + return request<{ list: SSOSession[]; total: number }>({ + url: '/enterprise/sso/sessions', + method: 'GET', + params, + }) +} + +/** 强制下线 SSO 会话 */ +export const logoutSSOSession = (id: number) => { + return request({ + url: `/enterprise/sso/sessions/${id}/logout`, + method: 'POST', + }) +} + +/** 强制下线所有 SSO 会话 */ +export const logoutAllSSOSessions = (enterpriseId: number) => { + return request({ + url: `/enterprise/sso/sessions/logout-all`, + method: 'POST', + data: { enterpriseId }, + }) +} + +// ==================== SSO 日志 ==================== + +/** 获取 SSO 日志列表 */ +export const pageSSOLogs = (params: { + enterpriseId: number + page?: number + limit?: number + type?: string + status?: string + timeStart?: string + timeEnd?: string +}) => { + return request<{ list: SSOLog[]; total: number }>({ + url: '/enterprise/sso/logs', + method: 'GET', + params, + }) +} diff --git a/src/api/ticket.ts b/src/api/ticket.ts new file mode 100644 index 0000000..605eb22 --- /dev/null +++ b/src/api/ticket.ts @@ -0,0 +1,296 @@ +/** + * 工单/技术支持 API + */ +import Taro from '@tarojs/taro'; +import type { Ticket, TicketReply, TicketTemplate, TicketStats, FAQ } from '../types/ticket'; + +// 模拟数据 +const mockTickets: Ticket[] = [ + { + id: '1', + ticketNo: 'TK202604120001', + title: 'API 调用频率限制问题', + content: '我在使用批量接口时遇到了 429 错误,请问如何申请提高调用频率限制?', + type: 'technical', + priority: 'high', + status: 'processing', + category: 'api', + attachments: [], + creatorId: '1', + creatorName: '张三', + creatorAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', + assigneeId: 's1', + assigneeName: '技术支持小王', + assigneeAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Aneka', + responseCount: 2, + solution: '已为您开通企业版调用配额', + createTime: '2026-04-12 10:30:00', + updateTime: '2026-04-12 14:20:00', + resolveTime: 230 + }, + { + id: '2', + ticketNo: 'TK202604110002', + title: '微信支付回调异常', + content: '支付完成后回调地址没有收到通知,请问如何排查问题?', + type: 'bug', + priority: 'urgent', + status: 'resolved', + category: 'payment', + attachments: ['/uploads/error-log.png'], + creatorId: '1', + creatorName: '张三', + creatorAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', + assigneeId: 's2', + assigneeName: '技术支持小李', + assigneeAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Bobby', + responseCount: 5, + solution: '回调地址需要配置白名单,已协助配置完成', + rating: 5, + feedback: '响应很快,问题解决了', + resolveTime: 180, + createTime: '2026-04-11 09:15:00', + updateTime: '2026-04-11 12:15:00', + resolveTime2: '2026-04-11 12:15:00' + }, + { + id: '3', + ticketNo: 'TK202604100003', + title: '功能建议:支持 Webhook 重试机制', + content: '希望 Webhook 能够支持失败重试功能,提高消息可靠性', + type: 'feature', + priority: 'medium', + status: 'pending', + category: 'api', + attachments: [], + creatorId: '1', + creatorName: '张三', + creatorAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', + responseCount: 0, + createTime: '2026-04-10 16:45:00', + updateTime: '2026-04-10 16:45:00' + } +]; + +const mockReplies: TicketReply[] = [ + { + id: 'r1', + ticketId: '1', + content: '您好,请问您的应用日调用量是多少?企业版默认配额为 10万次/天', + attachments: [], + isInternal: false, + senderId: 's1', + senderName: '技术支持小王', + senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Aneka', + senderRole: 'support', + createTime: '2026-04-12 11:00:00' + }, + { + id: 'r2', + ticketId: '1', + content: '我的日调用量大约在 15 万次左右', + attachments: [], + isInternal: false, + senderId: '1', + senderName: '张三', + senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', + senderRole: 'user', + createTime: '2026-04-12 11:30:00' + }, + { + id: 'r3', + ticketId: '1', + content: '已为您升级到企业高级版,配额提升至 50万次/天', + attachments: [], + isInternal: false, + senderId: 's1', + senderName: '技术支持小王', + senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Aneka', + senderRole: 'support', + createTime: '2026-04-12 14:20:00' + } +]; + +const mockTemplates: TicketTemplate[] = [ + { id: 't1', title: 'API 调用问题', content: '【问题描述】\n\n【复现步骤】\n1.\n2.\n3.\n\n【错误信息】\n\n【环境信息】', type: 'technical', category: 'api', priority: 'medium' }, + { id: 't2', title: '支付相关问题', content: '【问题类型】\n□ 支付失败 □ 退款 □ 发票\n\n【订单号】\n\n【问题描述】', type: 'billing', category: 'payment', priority: 'high' }, + { id: 't3', title: 'Bug 反馈', content: '【Bug 标题】\n\n【影响范围】\n\n【复现步骤】\n1.\n2.\n\n【预期行为】\n\n【实际行为】', type: 'bug', category: 'other', priority: 'high' } +]; + +const mockFAQs: FAQ[] = [ + { id: 'f1', question: '如何获取 API Key?', answer: '登录控制台 -> 开发管理 -> API Key -> 创建 Key', category: 'api', viewCount: 1256, helpful: 234, notHelpful: 12, tags: ['API', '密钥'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-04-01' }, + { id: 'f2', question: '调用频率限制是多少?', answer: '免费版 1000次/天,个人版 1万次/天,企业版 10万次/天', category: 'api', viewCount: 2345, helpful: 456, notHelpful: 23, tags: ['限流', '配额'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-04-05' }, + { id: 'f3', question: '如何申请发票?', answer: '控制台 -> 财务 -> 发票管理 -> 申请发票', category: 'billing', viewCount: 1890, helpful: 345, notHelpful: 15, tags: ['发票', '财务'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-03-20' }, + { id: 'f4', question: 'Webhook 回调失败怎么办?', answer: '1. 检查回调地址是否可公网访问\n2. 确保返回 200 状态码\n3. 检查签名验证', category: 'api', viewCount: 1567, helpful: 289, notHelpful: 34, tags: ['Webhook', '回调'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-04-10' }, + { id: 'f5', question: '如何升级账户?', answer: '控制台 -> 套餐管理 -> 选择套餐 -> 在线支付', category: 'account', viewCount: 987, helpful: 178, notHelpful: 8, tags: ['升级', '套餐'], relatedQuestions: [], createTime: '2026-01-01', updateTime: '2026-03-15' } +]; + +/** + * 获取工单统计 + */ +export async function getTicketStats(): Promise { + return { + total: mockTickets.length, + pending: 1, + processing: 1, + resolved: 1, + avgResponseTime: 45, + avgResolveTime: 4.2, + satisfaction: 4.8 + }; +} + +/** + * 分页获取工单列表 + */ +export async function pageTicket(params: { + status?: string; + type?: string; + priority?: string; + keyword?: string; + page?: number; + pageSize?: number; +}): Promise<{ list: Ticket[]; total: number }> { + const { status, type, priority, keyword, page = 1, pageSize = 10 } = params; + + let filtered = [...mockTickets]; + + if (status) filtered = filtered.filter(t => t.status === status); + if (type) filtered = filtered.filter(t => t.type === type); + if (priority) filtered = filtered.filter(t => t.priority === priority); + if (keyword) { + const kw = keyword.toLowerCase(); + filtered = filtered.filter(t => t.title.toLowerCase().includes(kw) || t.content.toLowerCase().includes(kw)); + } + + const start = (page - 1) * pageSize; + return { list: filtered.slice(start, start + pageSize), total: filtered.length }; +} + +/** + * 获取工单详情 + */ +export async function getTicketDetail(id: string): Promise { + return mockTickets.find(t => t.id === id) || null; +} + +/** + * 获取工单回复列表 + */ +export async function listTicketReply(ticketId: string): Promise { + return mockReplies.filter(r => r.ticketId === ticketId); +} + +/** + * 创建工单 + */ +export async function createTicket(data: Partial): Promise { + const newTicket: Ticket = { + id: String(Date.now()), + ticketNo: `TK${new Date().toISOString().replace(/[-:T]/g, '').slice(0, 12)}`, + title: data.title || '', + content: data.content || '', + type: data.type || 'technical', + priority: data.priority || 'medium', + status: 'pending', + category: data.category || 'api', + attachments: data.attachments || [], + creatorId: '1', + creatorName: '张三', + creatorAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', + responseCount: 0, + createTime: new Date().toISOString().replace('T', ' ').slice(0, 19), + updateTime: new Date().toISOString().replace('T', ' ').slice(0, 19) + }; + + mockTickets.unshift(newTicket); + return newTicket; +} + +/** + * 回复工单 + */ +export async function replyTicket(ticketId: string, content: string, attachments: string[] = []): Promise { + const reply: TicketReply = { + id: String(Date.now()), + ticketId, + content, + attachments, + isInternal: false, + senderId: '1', + senderName: '张三', + senderAvatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Felix', + senderRole: 'user', + createTime: new Date().toISOString().replace('T', ' ').slice(0, 19) + }; + + mockReplies.push(reply); + + const ticket = mockTickets.find(t => t.id === ticketId); + if (ticket) { + ticket.responseCount++; + ticket.updateTime = reply.createTime; + if (ticket.status === 'pending') ticket.status = 'processing'; + } + + return reply; +} + +/** + * 关闭工单 + */ +export async function closeTicket(id: string): Promise { + const ticket = mockTickets.find(t => t.id === id); + if (ticket) ticket.status = 'closed'; +} + +/** + * 评价工单 + */ +export async function rateTicket(id: string, rating: number, feedback: string): Promise { + const ticket = mockTickets.find(t => t.id === id); + if (ticket) { + ticket.rating = rating; + ticket.feedback = feedback; + } +} + +/** + * 获取工单模板 + */ +export async function listTicketTemplate(): Promise { + return mockTemplates; +} + +/** + * 获取 FAQ 列表 + */ +export async function pageFAQ(params: { + category?: string; + keyword?: string; + page?: number; + pageSize?: number; +}): Promise<{ list: FAQ[]; total: number }> { + const { category, keyword, page = 1, pageSize = 10 } = params; + + let filtered = [...mockFAQs]; + if (category) filtered = filtered.filter(f => f.category === category); + if (keyword) { + const kw = keyword.toLowerCase(); + filtered = filtered.filter(f => f.question.toLowerCase().includes(kw) || f.answer.toLowerCase().includes(kw)); + } + + const start = (page - 1) * pageSize; + return { list: filtered.slice(start, start + pageSize), total: filtered.length }; +} + +/** + * FAQ 反馈 + */ +export async function feedbackFAQ(id: string, helpful: boolean): Promise { + const faq = mockFAQs.find(f => f.id === id); + if (faq) { + if (helpful) faq.helpful++; + else faq.notHelpful++; + } +} diff --git a/src/app.config.ts b/src/app.config.ts index e0dd0c4..34625e8 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -117,6 +117,62 @@ export default { "index", "article/index", ] + }, + { + "root": "developer", + "pages": [ + "index", + "project/index", + "project/create", + "project/[id]/index", + "project/[id]/members", + "project/[id]/api-keys", + "project/[id]/settings", + "app/index", + "app/create", + "app/[id]/index", + "app/[id]/version", + "app/[id]/config", + "app/[id]/publish", + "app/api-keys/index", + "notification/index", + "developer/apply", + "developer/profile", + "docs/index", + "docs/quickstart", + "docs/api-docs", + "market/index" + ] + }, + { + "root": "enterprise", + "pages": [ + "index", + "apps/index", + "apps/[id]/index", + "apps/[id]/monitor", + "apps/[id]/analytics", + "apps/[id]/settings", + "apps/purchase", + "members/index", + "members/invite", + "members/roles", + "members/audit", + "orders/index", + "orders/detail", + "orders/invoice", + "orders/bills", + "billing/index", + "billing/consumption", + "billing/recharge", + "billing/coupons", + "settings/index", + "settings/info", + "settings/domain", + "settings/security", + "developer/apply", + "developer/index" + ] } ], window: { diff --git a/src/app/appCredential/add.tsx b/src/app/appCredential/add.tsx index 1326736..a1baeb7 100644 --- a/src/app/appCredential/add.tsx +++ b/src/app/appCredential/add.tsx @@ -2,9 +2,8 @@ import {useEffect, useState, useRef} from "react"; import {useRouter} from '@tarojs/taro' import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro' import Taro from '@tarojs/taro' -import {View} from '@tarojs/components' import {AppCredential} from "@/api/app/appCredential/model"; -import {getAppCredential, listAppCredential, updateAppCredential, addAppCredential} from "@/api/app/appCredential"; +import {getAppCredential, updateAppCredential, addAppCredential} from "@/api/app/appCredential"; const AddAppCredential = () => { const {params} = useRouter(); @@ -21,17 +20,14 @@ const AddAppCredential = () => { } } - // 提交表单 const submitSucceed = async (values: any) => { try { if (params.id) { - // 编辑模式 await updateAppCredential({ ...values, id: Number(params.id) }) } else { - // 新增模式 await addAppCredential(values) } @@ -96,3 +92,21 @@ const AddAppCredential = () => { > + + + + + + + + + +