Compare commits

..

5 Commits

Author SHA1 Message Date
611b488af3 refactor(api): 规范前端API接口地址并统一路径前缀
- 修正开发者、小程序和企业相关API的基础URL,避免重复添加/api前缀
- 统一调整developer、enterprise、invite等模块接口路径,保持与后端Controller一致
- 新增权限申请及消息通知相关API,并完善相关函数实现
- 规范请求参数传递,移除多余包装,改用直接传递对象方式
- 新增API Key重置、版本发布/回滚等接口支持

feat(invite): 重构邀请登录流程支持未注册快速加入

- 完整重写invite页,分离已注册用户和未注册用户的按钮显示和交互
- 实现未注册用户微信手机号授权后自动注册登录及自动加入应用
- 已注册用户直接确认加入应用,无需手机号授权弹窗
- 统一使用getPhoneNumber按钮处理两种状态并修复“授权码不能为空”报错
- 前端配合后端改造,支持已登录用户可直接通过Authorization头加入应用

fix(developer): 修复开发者中心应用加载问题

- 修复只查询创建的应用导致邀请加入的应用不显示的问题
- 同时请求创建应用和参与应用接口,合并并去重应用列表
- 新增pageJoinedApp接口调用及合并逻辑,提高应用列表完整性和体验
2026-04-13 15:30:32 +08:00
d11d64469c feat(userVerify): 添加阿里云身份证二要素核验功能
- 在接口层新增 verifyIdCard 接口,实现姓名与身份证号核验
- userVerify 页面提交时增加身份核验逻辑,核验失败阻止提交
- 调用核验接口时显示加载状态,并根据结果提示用户
- 后端依赖 aliyun cloudauth,提供实人认证相关服务接口
- 完成基本的错误捕获与用户提示,提升实名认证流程安全性
2026-04-13 03:22:43 +08:00
d4e7a163f7 feat(userVerify): 添加阿里云身份证二要素核验功能
- 在接口层新增 verifyIdCard 接口,实现姓名与身份证号核验
- userVerify 页面提交时增加身份核验逻辑,核验失败阻止提交
- 调用核验接口时显示加载状态,并根据结果提示用户
- 后端依赖 aliyun cloudauth,提供实人认证相关服务接口
- 完成基本的错误捕获与用户提示,提升实名认证流程安全性
2026-04-13 03:20:05 +08:00
a8688c0f4a refactor(config): 移除开发者中心和企业控制台的自定义导航样式
- 删除了开发者中心页面配置中的 navigationStyle: 'custom'
- 删除了企业控制台页面配置中的 navigationStyle: 'custom'
- 简化了页面配置,保持默认导航样式
- 有助于提升页面兼容性和维护性
2026-04-13 02:50:57 +08:00
16b7e2fb61 feat(api-keys): 优化隐私协议同意逻辑及样式
- 修正api-keys样式导入路径,改为developer路径
- 在开发者申请页面增加服务协议和隐私政策勾选框
- 提交时校验协议是否已勾选,未勾选时提示用户
- 优化申请页面协议区域样式及交互
- 在用户验证页面添加隐私政策同意勾选框
- 提交用户验证表单时检查协议同意状态
- 增加打开服务协议和隐私政策的链接跳转
- 更新项目配置,增加passport/webview路由配置
- 修复createTicket函数中日期格式正则表达式问题
2026-04-13 02:48:31 +08:00
11 changed files with 520 additions and 77 deletions

View File

@@ -44,7 +44,18 @@
"usedAt": 1775972794982,
"industryId": "all"
}
],
"e1bb762122264b5d9d218f575fecf04b": [
{
"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": 1776019266960,
"industryId": "all"
}
]
},
"lastUpdated": 1775994576652
"lastUpdated": 1776023218479
}

View File

@@ -439,3 +439,177 @@ src/enterprise/
### 项目状态
- ✅ 编译成功
- ✅ 开发服务器运行中 (端口: dist)
---
## 任务:改造 invite/index.tsx 登录流程
### 需求
- `loginByOpenId` 已注册 → 显示**「确认加入」**按钮
- `loginByOpenId` 未注册 → 显示**「微信手机号快速加入」**按钮(走授权流程)
### 关键逻辑
1. `checkLoginStatus`:调用 `loginByOpenId` 检查用户是否已注册
2. 已注册:`isLoggedIn = true`,显示「确认加入」按钮
3. 未注册:`isLoggedIn = false`,显示「微信手机号授权」按钮
4. 授权成功 → 调用 `loginByMpWxPhone` 完成注册/登录 → 自动执行加入应用
### 文件修改
- `src/passport/invite/index.tsx` - 完整重写,区分已登录/未注册两种按钮状态
---
## 任务:未注册用户在邀请页内完成授权注册,不跳登录页
### 需求
- 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}`
### 文件修改
- `src/passport/invite/index.tsx` - 完整重写(彻底移除跳登录页逻辑)
---
## 修复:「授权码不能为空」报错
### 问题
后端 `/api/_app/developer/invite/accept` 接口强制要求传 `code`(微信授权码),不传就报「授权码不能为空」。
### 解决
统一用一个 `getPhoneNumber` 按钮处理两种场景:
- **已注册**:文字「确认加入」→ 触发 getPhoneNumber → `doJoinApp(code, accessToken)`
- **未注册**:文字「微信手机号快速加入」→ 触发 getPhoneNumber → 先 `loginByMpWxPhone` 注册 → 再 `wx.login()` 获 code → `doJoinApp(newCode, access_token)`
### doJoinApp 参数
```ts
doJoinApp(wxCode: string, accessToken: string)
// 请求体带 code请求头带 Authorization: Bearer xxx
```
---
## 优化:已登录用户不弹手机号授权
### 改动
- 已登录按钮:普通 `onClick`,文字「确认加入」
- 未注册按钮:`getPhoneNumber` 授权,文字「微信手机号快速加入」
### 逻辑差异
| 用户状态 | 按钮类型 | 获取 code 方式 |
|------|------|------|
| 已登录 | 普通 onClick | `wx.login()` |
| 未注册 | getPhoneNumber | 授权回调的 `code` |
### 文件修改
- `src/passport/invite/index.tsx` - 按钮区分两种类型,已登录用普通 onClick
---
## 优化:已登录用户不强制勾选协议
### 改动
- 已登录用户点击「确认加入」时,不再检查 `agreementChecked`
- 未注册用户仍需勾选协议后才能授权手机号
### 文件修改
- `src/passport/invite/index.tsx` - `handleConfirmJoin` 移除协议检查
---
## 修复:后端需要手机号授权码
### 问题
后端 `invite/accept` 接口只接受 `getPhoneNumber` 获取的手机号授权码,用 `wx.login()` 的 code 会报「获取手机号失败」。
### 解决
两种用户状态都走 `getPhoneNumber` 按钮:
- 已登录:文字「确认加入」,回调 `handleConfirmJoinGetPhoneNumber`
- 未注册:文字「微信手机号快速加入」,回调 `handleGetPhoneNumber`
### 差异
| 用户状态 | 回调 | 行为 |
|------|------|------|
| 已登录 | `handleConfirmJoinGetPhoneNumber` | 直接用 `code + access_token` 调加入接口 |
| 未注册 | `handleGetPhoneNumber` | 先 `loginByMpWxPhone` 注册登录,再调加入接口 |
### 文件修改
- `src/passport/invite/index.tsx` - 两种状态都用 getPhoneNumber 按钮
---
## 任务:后端改造支持已登录用户直接加入
### 问题
后端 `/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);
}
```
#### 3. getCurrentUserId 方法
- 尝试从 Spring Security Context 获取
- 如果获取不到(免登录接口),手动解析 `Authorization` 头的 JWT Token
### 前端配合改造
- 已登录用户:普通 `onClick` 按钮 → `handleConfirmJoin``doJoinAppForLoggedInUser`(不传 `code`
- 未注册用户:`getPhoneNumber` 按钮 → `handleGetPhoneNumber``doJoinAppForNewUser`(传 `code`
### 文件修改
**后端:**
- `/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`
---
## 修复:开发者中心加载不到应用
### 问题
用户通过邀请加入应用后,开发者中心显示「加载中...」,无法显示应用列表。
### 原因
- 前端 `developer/index.tsx` 只调用了 `pageMyApp` 接口(查询用户**创建**的应用)
- 用户通过邀请加入的应用属于**参与**的应用,不是创建的应用
- 后端 `loginByOpenId` 返回了应用列表,但前端没有使用这个数据
### 解决
1. **前端改造**`loadData` 同时调用两个接口:
- `pageMyApp` - 查询创建的应用
- `pageJoinedApp` - 查询参与的应用(新增 API
- 合并两个列表,根据 `productId` 去重
2. **新增 API**`src/api/developer/developer.ts` 添加 `pageJoinedApp` 方法
### 文件修改
- `src/developer/index.tsx` - `loadData` 同时查询创建和参与的应用
- `src/api/developer/developer.ts` - 新增 `pageJoinedApp` 方法

View File

@@ -0,0 +1,77 @@
# 2026-04-13 工作日志
## 完成的工作
### 1. 阿里云实人认证接入
**目的**:为 user/userVerify 页面添加阿里云身份证二要素核验
**后端改动**JAVA/websopy-java
- pom.xml添加 `com.aliyun:cloudauth20190307:2.2.4` 依赖
- application.yml添加 `cloudauth` 配置项accessKeyId、accessKeySecret、endpoint、regionId
- 新增 `CloudAuthProperties.java`:配置属性类
- 新增 `IdVerificationService.java`:实人认证服务
- 新增 `IdVerificationController.java`:实人认证 API 控制器
**前端改动**VUE/websopy-mp
- api/system/userVerify/index.ts添加 `verifyIdCard()` API 调用
- user/userVerify/index.tsx
- 导入 verifyIdCard
- 修改 submitSucceed 函数:个人认证时先调用实名校验,核验通过后再提交
### 2. 调研结论
- **城市服务实名校验**已于2021年11月停止开放不可用
- **阿里云实人认证**推荐方案0.2元/次有100次免费试用额度
- **接入方式**:身份证二要素核验(姓名+身份证号)最简单
## 待办事项
- [x] 配置阿里云 AccessKey在 application-prod.yml 中设置 cloudauth.accessKeyId 和 cloudauth.accessKeySecret
- [ ] 在阿里云实人认证控制台开通服务并充值
- [ ] 测试验证接口是否正常工作
## 阿里云 AccessKey 配置
- 项目websopy-java
- 文件application-prod.yml
- accessKeyId: LTAI4GKGZ9Z2Z8JZ77c3GNZP
- 备注:与 OSS 使用同一个 AccessKey
## 3. 前端接口地址修正
**目的**:修正小程序端开发者相关 API 接口地址,与后端 Controller 路径保持一致
**问题发现**`BaseUrl` 配置已包含 `/api` 后缀 (`https://websopy-api.websoft.top/api`),前端代码中不应再重复添加 `/api` 前缀,否则会导致 `/api/api/` 路径错误。
**修正文件**
- `src/api/developer/enterprise.ts`
- 企业信息:`/system/tenant/info`, `/system/tenant`
- 企业成员:`/system/user/page`, `/system/user`
- 订单:`/system/order/page`, `/system/order`
- 账单:`/sys/recharge-order/page`
- 企业应用:`/app/product/page`
- 邀请:`/app/developer/invite`
- `src/api/developer/developer.ts`
- 项目/应用:`/app/product/*` (create, update, delete, detail, page, my/page, joined/page)
- API Key`/app/app-credential/*`
- 版本发布:`/app/app-version/*`
- 开发者信息:`/app/developer/git-account`, `/app/developer/gitea-info`
- 通知:`/app/notification/*`
- 权限申请:`/app/developer/permission-requests/*`
- `src/api/invite/index.ts`
- 所有接口移除 `/api` 前缀
**后端 Controller 对应关系**
| 前端 API | 后端 Controller | 路径前缀 |
|---------|----------------|---------|
| enterprise.ts | TenantController | /api/system/tenant |
| enterprise.ts | UserController | /api/system/user |
| enterprise.ts | OrderController | /api/system/order |
| enterprise.ts | RechargeOrderController | /api/sys/recharge-order |
| developer.ts | AppProductController | /api/app/product |
| developer.ts | AppCredentialController | /api/app/app-credential |
| developer.ts | AppVersionController | /api/app/app-version |
| developer.ts | GitAccountController | /api/app/developer |
| developer.ts | AppNotificationController | /api/app/notification |
| developer.ts | AppPermissionRequestController | /api/app/developer/permission-requests |
| invite/index.ts | - | /api/invite/* |
**重要配置**`config/env.ts``API_BASE_URL` 已包含 `/api` 后缀,前端代码路径不应再以 `/api` 开头。

View File

@@ -18,22 +18,23 @@ import type {
ApplyParam,
} from '@/types/developer'
// ==================== 项目相关 ====================
// ==================== 项目/应用相关 ====================
// 注意:后端使用 AppProduct 作为项目/应用的概念,路径为 /app/product
/**
* 获取我的项目列表
* 获取我的项目列表(我创建的应用)
*/
export async function pageMyProject(params?: ProjectParam) {
const res = await request.get<ApiResult<PageResult<Project>>>('/project/my/page', { params })
const res = await request.get<ApiResult<PageResult<Project>>>('/app/product/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<ApiResult<PageResult<Project>>>('/project/page', { params })
const res = await request.get<ApiResult<PageResult<Project>>>('/app/product/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -42,7 +43,7 @@ export async function pageProject(params?: ProjectParam) {
* 获取项目详情
*/
export async function getProject(id: number) {
const res = await request.get<ApiResult<Project>>(`/project/${id}`)
const res = await request.get<ApiResult<Project>>(`/app/product/detail/${id}`)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -51,7 +52,7 @@ export async function getProject(id: number) {
* 创建项目
*/
export async function createProject(data: Partial<Project>) {
const res = await request.post<ApiResult<unknown>>('/project', data)
const res = await request.post<ApiResult<unknown>>('/app/product/create', data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -60,7 +61,7 @@ export async function createProject(data: Partial<Project>) {
* 更新项目
*/
export async function updateProject(data: Partial<Project>) {
const res = await request.put<ApiResult<unknown>>(`/project/${data.id}`, data)
const res = await request.put<ApiResult<unknown>>('/app/product/update', data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -69,16 +70,16 @@ export async function updateProject(data: Partial<Project>) {
* 删除项目
*/
export async function deleteProject(id: number) {
const res = await request.del<ApiResult<unknown>>(`/project/${id}`)
const res = await request.del<ApiResult<unknown>>(`/app/product/delete/${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<ApiResult<ProjectMember[]>>(`/project/${projectId}/members`)
const res = await request.get<ApiResult<ProjectMember[]>>(`/app/app-user/page`, { appId: projectId })
if (res.code === 0 && res.data) return res.data
return Promise.reject(new Error(res.message))
}
@@ -87,7 +88,7 @@ export async function listProjectMember(projectId: number) {
* 添加项目成员
*/
export async function addProjectMember(projectId: number, data: Partial<ProjectMember>) {
const res = await request.post<ApiResult<unknown>>(`/project/${projectId}/members`, data)
const res = await request.post<ApiResult<unknown>>(`/app/app-user`, { ...data, appId: projectId })
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -96,18 +97,27 @@ export async function addProjectMember(projectId: number, data: Partial<ProjectM
* 移除项目成员
*/
export async function removeProjectMember(projectId: number, memberId: number) {
const res = await request.del<ApiResult<unknown>>(`/project/${projectId}/members/${memberId}`)
const res = await request.del<ApiResult<unknown>>(`/app/app-user/${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<ApiResult<PageResult<App>>>('/app/product/my/page', { params })
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/my/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
/**
* 分页查询我参与的应用(通过邀请加入的应用)
*/
export async function pageJoinedApp(params?: AppParam) {
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/joined/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -116,7 +126,7 @@ export async function pageMyApp(params?: AppParam) {
* 获取应用列表
*/
export async function pageApp(params?: AppParam) {
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/page', { params })
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -125,7 +135,7 @@ export async function pageApp(params?: AppParam) {
* 获取应用详情
*/
export async function getApp(id: number) {
const res = await request.get<ApiResult<App>>(`/app/product/${id}`)
const res = await request.get<ApiResult<App>>(`/app/product/detail/${id}`)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -134,7 +144,7 @@ export async function getApp(id: number) {
* 创建应用
*/
export async function createApp(data: Partial<App>) {
const res = await request.post<ApiResult<unknown>>('/app/product', data)
const res = await request.post<ApiResult<unknown>>('/app/product/create', data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -143,7 +153,7 @@ export async function createApp(data: Partial<App>) {
* 更新应用
*/
export async function updateApp(data: Partial<App>) {
const res = await request.put<ApiResult<unknown>>(`/app/product/${data.id}`, data)
const res = await request.put<ApiResult<unknown>>('/app/product/update', data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -152,18 +162,19 @@ export async function updateApp(data: Partial<App>) {
* 删除应用
*/
export async function deleteApp(id: number) {
const res = await request.del<ApiResult<unknown>>(`/app/product/${id}`)
const res = await request.del<ApiResult<unknown>>(`/app/product/delete/${id}`)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
// ==================== API Key 相关 ====================
// 对应后端 AppCredentialController路径为 /app/app-credential
/**
* 获取 API Key 列表
*/
export async function pageApiKey(params?: ApiKeyParam) {
const res = await request.get<ApiResult<PageResult<ApiKey>>>('/app/app-credential/page', { params })
const res = await request.get<ApiResult<PageResult<ApiKey>>>('/app/app-credential/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -204,13 +215,23 @@ export async function deleteApiKey(id: number) {
return Promise.reject(new Error(res.message))
}
/**
* 重置 API Key Secret
*/
export async function resetApiKeySecret(id: number) {
const res = await request.post<ApiResult<unknown>>(`/app/app-credential/resetSecret/${id}`)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
// ==================== 版本发布相关 ====================
// 对应后端 AppVersionController路径为 /app/app-version
/**
* 获取版本列表
*/
export async function pageVersion(params?: VersionParam) {
const res = await request.get<ApiResult<PageResult<Version>>>('/app/app-version/page', { params })
const res = await request.get<ApiResult<PageResult<Version>>>('/app/app-version/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -233,42 +254,80 @@ export async function createVersion(data: Partial<Version>) {
return Promise.reject(new Error(res.message))
}
// ==================== 开发者相关 ====================
/**
* 发布版本
*/
export async function publishVersion(id: number) {
const res = await request.post<ApiResult<unknown>>(`/app/app-version/publish/${id}`)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
/**
* 获取开发者信息
* 回滚版本
*/
export async function rollbackVersion(id: number) {
const res = await request.post<ApiResult<unknown>>(`/app/app-version/rollback/${id}`)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
// ==================== 开发者相关 ====================
// 对应后端 GitAccountController路径为 /app/developer
/**
* 获取开发者信息Git账号绑定状态
*/
export async function getDeveloperInfo() {
const res = await request.get<ApiResult<Developer>>('/developer/info')
const res = await request.get<ApiResult<Developer>>('/app/developer/git-account')
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
/**
* 申请成为开发者
* 申请成为开发者绑定Git账号
*/
export async function applyDeveloper(data: DeveloperApplyParam) {
const res = await request.post<ApiResult<unknown>>('/developer/apply', data)
const res = await request.post<ApiResult<unknown>>('/app/developer/git-account', data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
/**
* 更新开发者信息
* 更新开发者信息更新Git账号
*/
export async function updateDeveloperInfo(data: Partial<Developer>) {
const res = await request.put<ApiResult<unknown>>('/developer/info', data)
const res = await request.put<ApiResult<unknown>>('/app/developer/git-account', data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
/**
* 获取Gitea服务器信息
*/
export async function getGiteaInfo() {
const res = await request.get<ApiResult<{ url: string; version: string; registrationEnabled: boolean }>>('/app/developer/gitea-info')
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
// ==================== 消息通知相关 ====================
// 对应后端 AppNotificationController路径为 /app/notification
/**
* 获取通知列表
*/
export async function pageNotification(params?: NotificationParam) {
const res = await request.get<ApiResult<PageResult<Notification>>>('/notification/page', { params })
const res = await request.get<ApiResult<PageResult<Notification>>>('/app/notification/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
/**
* 获取最近通知(铃铛下拉)
*/
export async function getRecentNotifications(type?: string, limit?: number) {
const res = await request.get<ApiResult<Notification[]>>('/app/notification/recent', { type, limit })
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -277,7 +336,7 @@ export async function pageNotification(params?: NotificationParam) {
* 获取未读通知数量
*/
export async function getUnreadCount() {
const res = await request.get<ApiResult<{ count: number }>>('/notification/unread-count')
const res = await request.get<ApiResult<{ count: number }>>('/app/notification/unread-count')
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -286,7 +345,7 @@ export async function getUnreadCount() {
* 标记通知为已读
*/
export async function markAsRead(id: number) {
const res = await request.put<ApiResult<unknown>>(`/notification/${id}/read`)
const res = await request.put<ApiResult<unknown>>(`/app/notification/read/${id}`)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -294,8 +353,8 @@ export async function markAsRead(id: number) {
/**
* 标记所有通知为已读
*/
export async function markAllAsRead() {
const res = await request.put<ApiResult<unknown>>('/notification/read-all')
export async function markAllAsRead(type?: string) {
const res = await request.put<ApiResult<unknown>>('/app/notification/read-all', { type })
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -304,18 +363,28 @@ export async function markAllAsRead() {
* 删除通知
*/
export async function deleteNotification(id: number) {
const res = await request.del<ApiResult<unknown>>(`/notification/${id}`)
const res = await request.del<ApiResult<unknown>>(`/app/notification/${id}`)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
/**
* 清空已读通知
*/
export async function clearReadNotifications(type?: string) {
const res = await request.del<ApiResult<unknown>>('/app/notification/clear-read', { type })
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
// ==================== 权限审批相关 ====================
// 对应后端 AppPermissionRequestController路径为 /app/developer/permission-requests
/**
* 获取申请列表
*/
export async function pageApply(params?: ApplyParam) {
const res = await request.get<ApiResult<PageResult<Apply>>>('/apply/page', { params })
const res = await request.get<ApiResult<PageResult<Apply>>>('/app/developer/permission-requests/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -324,7 +393,25 @@ export async function pageApply(params?: ApplyParam) {
* 获取我的申请列表
*/
export async function pageMyApply(params?: ApplyParam) {
const res = await request.get<ApiResult<PageResult<Apply>>>('/apply/my/page', { params })
const res = await request.get<ApiResult<PageResult<Apply>>>('/app/developer/permission-requests', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
/**
* 获取权限申请统计
*/
export async function getApplyStats() {
const res = await request.get<ApiResult<{ [key: string]: number }>>('/app/developer/permission-requests/stats')
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
/**
* 获取可申请的仓库列表
*/
export async function getAvailableRepos() {
const res = await request.get<ApiResult<Array<{ name: string; fullName: string }>>>('/app/developer/permission-requests/available-repos')
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -332,17 +419,26 @@ export async function pageMyApply(params?: ApplyParam) {
/**
* 创建申请
*/
export async function createApply(data: Partial<Apply>) {
const res = await request.post<ApiResult<unknown>>('/apply', data)
export async function createApply(data: { repo: string; reason: string; gitUsername?: string }) {
const res = await request.post<ApiResult<unknown>>('/app/developer/permission-requests', 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<ApiResult<unknown>>(`/apply/${id}/review`, { status, remark })
export async function approveApply(id: number, note?: string) {
const res = await request.put<ApiResult<unknown>>(`/app/developer/permission-requests/${id}/approve`, { note })
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
/**
* 审批申请-拒绝
*/
export async function rejectApply(id: number, reason: string) {
const res = await request.put<ApiResult<unknown>>(`/app/developer/permission-requests/${id}/reject`, { reason })
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}

View File

@@ -2,13 +2,13 @@ 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<ApiResult<Enterprise>>('/enterprise/info')
const res = await request.get<ApiResult<Enterprise>>('/system/tenant/info')
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -17,18 +17,18 @@ export async function getEnterpriseInfo() {
* 更新企业信息
*/
export async function updateEnterpriseInfo(data: Partial<Enterprise>) {
const res = await request.put<ApiResult<unknown>>('/enterprise/info', data)
const res = await request.put<ApiResult<unknown>>('/system/tenant', 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<ApiResult<PageResult<EnterpriseMember>>>('/enterprise/member/page', { params })
const res = await request.get<ApiResult<PageResult<EnterpriseMember>>>('/system/user/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -37,7 +37,7 @@ export async function pageEnterpriseMember(params?: EnterpriseMemberParam) {
* 获取企业成员列表(不分页)
*/
export async function listEnterpriseMember(params?: EnterpriseMemberParam) {
const res = await request.get<ApiResult<EnterpriseMember[]>>('/enterprise/member', params)
const res = await request.get<ApiResult<EnterpriseMember[]>>('/system/user', params)
if (res.code === 0 && res.data) return res.data
return Promise.reject(new Error(res.message))
}
@@ -46,7 +46,7 @@ export async function listEnterpriseMember(params?: EnterpriseMemberParam) {
* 邀请企业成员
*/
export async function inviteEnterpriseMember(enterpriseId: number, data: Partial<EnterpriseMember>) {
const res = await request.post<ApiResult<unknown>>(`/enterprise/member/${enterpriseId}/invite`, data)
const res = await request.post<ApiResult<unknown>>(`/app/developer/invite/${enterpriseId}/invite`, data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -55,7 +55,7 @@ export async function inviteEnterpriseMember(enterpriseId: number, data: Partial
* 更新企业成员
*/
export async function updateEnterpriseMember(data: Partial<EnterpriseMember>) {
const res = await request.put<ApiResult<unknown>>(`/enterprise/member/${data.id}`, data)
const res = await request.put<ApiResult<unknown>>(`/system/user`, data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -64,7 +64,7 @@ export async function updateEnterpriseMember(data: Partial<EnterpriseMember>) {
* 移除企业成员
*/
export async function removeEnterpriseMember(memberId: number) {
const res = await request.del<ApiResult<unknown>>(`/enterprise/member/${memberId}`)
const res = await request.del<ApiResult<unknown>>(`/system/user/${memberId}`)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
@@ -75,7 +75,7 @@ export async function removeEnterpriseMember(memberId: number) {
* 获取订单列表
*/
export async function pageOrder(params?: { page?: number; limit?: number; status?: number }) {
const res = await request.get<ApiResult<PageResult<Order>>>('/enterprise/order/page', { params })
const res = await request.get<ApiResult<PageResult<Order>>>('/system/order/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -84,18 +84,27 @@ export async function pageOrder(params?: { page?: number; limit?: number; status
* 获取订单详情
*/
export async function getOrder(id: number) {
const res = await request.get<ApiResult<Order>>(`/enterprise/order/${id}`)
const res = await request.get<ApiResult<Order>>(`/system/order/${id}`)
if (res.code === 0 && res.data) return res.data
return Promise.reject(new Error(res.message))
}
// ==================== 账单相关 ====================
/**
* 创建订单
*/
export async function createOrder(data: any) {
const res = await request.post<ApiResult<unknown>>('/system/order', data)
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}
// ==================== 充值/账单相关 ====================
/**
* 获取账单列表
*/
export async function pageBill(params?: BillParam) {
const res = await request.get<ApiResult<PageResult<Bill>>>('/enterprise/bill/page', { params })
const res = await request.get<ApiResult<PageResult<Bill>>>('/sys/recharge-order/page', params)
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -104,7 +113,7 @@ export async function pageBill(params?: BillParam) {
* 获取账单概览
*/
export async function getBillOverview() {
const res = await request.get<ApiResult<{ balance: number; totalConsume: number; totalRecharge: number }>>('/enterprise/bill/overview')
const res = await request.get<ApiResult<{ balance: number; totalConsume: number; totalRecharge: number }>>('/sys/user-balance-log/overview')
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -115,7 +124,7 @@ export async function getBillOverview() {
* 获取企业应用列表
*/
export async function pageEnterpriseApp(params?: AppParam) {
const res = await request.get<ApiResult<PageResult<App>>>('/enterprise/app/page', { params })
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/page', { params })
if (res.code === 0) return res.data
return Promise.reject(new Error(res.message))
}
@@ -124,7 +133,7 @@ export async function pageEnterpriseApp(params?: AppParam) {
* 获取企业应用详情
*/
export async function getEnterpriseApp(id: number) {
const res = await request.get<ApiResult<App>>(`/enterprise/app/${id}`)
const res = await request.get<ApiResult<App>>(`/app/product/${id}`)
if (res.code === 0 && res.data) return res.data
return Promise.reject(new Error(res.message))
}
@@ -133,7 +142,7 @@ export async function getEnterpriseApp(id: number) {
* 购买应用
*/
export async function purchaseApp(productId: number) {
const res = await request.post<ApiResult<unknown>>('/enterprise/app/purchase', { productId })
const res = await request.post<ApiResult<unknown>>('/api/system/order/createOrder', { productId })
if (res.code === 0) return res.message
return Promise.reject(new Error(res.message))
}

View File

@@ -128,3 +128,24 @@ export async function submit(data: UserVerify) {
}
return Promise.reject(new Error(res.message));
}
/**
* 阿里云实人认证 - 身份证二要素核验
* 验证姓名和身份证号是否一致
*/
export async function verifyIdCard(realName: string, idCard: string) {
const res = await request.post<ApiResult<{
success: boolean;
isMatch: boolean;
bizCode: string;
message: string;
}>>(
SERVER_API_URL + '/id-verification/verify-id-card',
null,
{ params: { realName, idCard } }
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

View File

@@ -1,5 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '开发者中心',
navigationStyle: 'custom',
usingComponents: {},
})

View File

@@ -3,7 +3,7 @@ import { View, Text, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { Button, Empty } from '@nutui/nutui-react-taro'
import { useThemeStyles } from '@/hooks/useTheme'
import { pageMyApp } from '@/api/developer'
import { pageMyApp, pageJoinedApp } from '@/api/developer'
import type { App } from '@/types/developer'
import { APP_TYPE_NAME } from '@/types/developer'
import './index.scss'
@@ -18,19 +18,45 @@ const DeveloperIndex: React.FC = () => {
totalCalls: 0,
})
// 加载数据
// 加载数据 - 同时查询创建的应用和参与的应用
const loadData = async () => {
try {
setLoading(true)
const result = await pageMyApp({ current: 1, size: 10 })
if (result) {
setApps(result.list || [])
// 同时调用两个接口:创建的应用 + 参与的应用(通过邀请加入)
const [myAppsResult, joinedAppsResult] = await Promise.all([
pageMyApp({ current: 1, size: 10 }),
pageJoinedApp({ current: 1, size: 10 }),
])
// 合并两个列表,去重(根据 productId
const myApps = myAppsResult?.records || []
const joinedApps = joinedAppsResult?.records || []
// 使用 Map 去重,优先保留创建的应用(排在前面)
const appMap = new Map<number, App>()
// 先加入创建的应用
myApps.forEach((app) => {
if (app.productId) {
appMap.set(app.productId, { ...app, isOwner: true })
}
})
// 再加入参与的应用(如果已存在则跳过)
joinedApps.forEach((app) => {
if (app.productId && !appMap.has(app.productId)) {
appMap.set(app.productId, { ...app, isOwner: false })
}
})
const mergedApps = Array.from(appMap.values())
setApps(mergedApps)
setStats({
totalApps: result.count || 0,
runningApps: result.list?.filter((a) => a.status === 1).length || 0,
totalApps: mergedApps.length,
runningApps: mergedApps.filter((a) => a.status === 1).length,
totalCalls: 0,
})
}
} catch (error) {
console.error('加载数据失败', error)
Taro.showToast({ title: '加载失败', icon: 'none' })

View File

@@ -1,2 +1,3 @@
// 复用 developer/app/api-keys/index.scss 的样式
@import '../../developer/app/api-keys/index';
// 注意:使用 @use 替代 @import 是 Sass 的新推荐方式
@use '../../app/api-keys/index' as api-keys;

View File

@@ -1,5 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '企业控制台',
navigationStyle: 'custom',
usingComponents: {},
})

View File

@@ -11,7 +11,7 @@ import {
Checkbox,
} from '@nutui/nutui-react-taro'
import {UserVerify} from "@/api/system/userVerify/model";
import {addUserVerify, myUserVerify, updateUserVerify} from "@/api/system/userVerify";
import {addUserVerify, myUserVerify, updateUserVerify, verifyIdCard} from "@/api/system/userVerify";
import {uploadFile} from "@/api/system/file";
function Index() {
@@ -62,7 +62,7 @@ function Index() {
};
// 提交表单
const submitSucceed = (values: any) => {
const submitSucceed = async (values: any) => {
console.log('提交表单', values);
if (FormData.status != 2 && FormData.status != undefined) return false;
@@ -113,6 +113,36 @@ function Index() {
});
return false;
}
// 个人认证:先调用阿里云实人认证进行身份核验
if (FormData.type == 0) {
Taro.showLoading({ title: '身份核验中...' });
try {
const verifyResult = await verifyIdCard(FormData.realName!, FormData.idCard!);
Taro.hideLoading();
if (!verifyResult.isMatch) {
Taro.showToast({
title: '身份信息不一致,请核实后重新填写',
icon: 'none',
duration: 3000
});
return false;
}
// 核验通过,继续提交
} catch (error: any) {
Taro.hideLoading();
console.error('身份核验失败', error);
Taro.showToast({
title: error.message || '身份核验失败,请稍后重试',
icon: 'none',
duration: 3000
});
return false;
}
}
const saveOrUpdate = isUpdate ? updateUserVerify : addUserVerify;
saveOrUpdate({...FormData, status: 0}).then(() => {
Taro.showToast({title: `提交成功`, icon: 'success'})