From bdb9fa3a3938746a6d9ac3ccd365b50dedf39b0b Mon Sep 17 00:00:00 2001 From: 13121765685 Date: Fri, 19 Dec 2025 13:47:07 +0800 Subject: [PATCH] 22 --- .../Admin User Management Implementation.md | 51 ++ .trae/documents/实现手机号登录功能.md | 78 ++ .trae/documents/实现提示词管理功能.md | 197 +++++ .trae/documents/添加候补队列页面.md | 41 + apps/FrontendDesigner/package.json | 3 +- .../src/components/admin/AdminLayout.vue | 4 + .../src/components/admin/PromptCard.vue | 229 ++++++ .../components/admin/PromptCardHorizontal.vue | 282 +++++++ .../src/locales/lang/zh-CN.js | 26 + apps/FrontendDesigner/src/router/index.js | 9 + .../views/admin/AdminCommissionManagement.vue | 2 +- .../src/views/admin/AdminContent.vue | 3 +- .../src/views/admin/AdminContentReview.vue | 2 +- .../views/admin/AdminDisassemblyOrders.vue | 2 +- .../src/views/admin/AdminOrders copy.vue | 5 +- .../AdminPermissionManagement.vue | 3 +- .../src/views/admin/AdminPointsManagement.vue | 3 +- .../src/views/admin/AdminPromptManagement.vue | 656 ++++++++++++++++ .../AdminRoleManagement/AdminRoleDetail.vue | 3 +- .../AdminRoleManagement.vue | 3 +- .../src/views/admin/AdminRouteManagement.vue | 3 +- .../src/views/admin/AdminUserList.vue | 3 +- .../admin/AdminUsers/AdminUserInvites.vue | 4 +- .../src/views/admin/AdminUsers/AdminUsers.vue | 2 +- .../src/views/admin/AdminUsers/index.js | 147 +++- apps/frontend/package-lock.json | 10 - apps/frontend/package.json | 2 +- apps/frontend/src/components/IPCard/index.vue | 10 +- apps/frontend/src/components/IPCard/tsc.js | 14 + .../src/components/StripePaymentForm.vue | 15 +- .../src/components/auth/PhoneLoginForm.vue | 711 ++++++++++++++++++ .../src/components/iPandCardLeft/index.vue | 111 ++- .../src/components/layout/AppSidebar.vue | 2 +- apps/frontend/src/locales/index.js | 56 ++ apps/frontend/src/router/index.js | 69 +- apps/frontend/src/stores/auth.js | 36 +- apps/frontend/src/views/Login/Login.vue | 18 +- apps/frontend/src/views/Login/PhoneLogin.vue | 668 ++++++++++++++++ apps/frontend/src/views/Login/login.js | 26 + apps/frontend/src/views/Waitlist.vue | 197 +++++ package-lock.json | 41 +- .../src/api/FrontendDesigner/permission.js | 36 +- packages/utils/src/servers/giminiserver.js | 7 +- packages/utils/src/servers/payserver.js | 45 +- pnpm-lock.yaml | 171 ++++- 45 files changed, 3885 insertions(+), 121 deletions(-) create mode 100644 .trae/documents/Admin User Management Implementation.md create mode 100644 .trae/documents/实现手机号登录功能.md create mode 100644 .trae/documents/实现提示词管理功能.md create mode 100644 .trae/documents/添加候补队列页面.md create mode 100644 apps/FrontendDesigner/src/components/admin/PromptCard.vue create mode 100644 apps/FrontendDesigner/src/components/admin/PromptCardHorizontal.vue create mode 100644 apps/FrontendDesigner/src/views/admin/AdminPromptManagement.vue create mode 100644 apps/frontend/src/components/auth/PhoneLoginForm.vue create mode 100644 apps/frontend/src/views/Login/PhoneLogin.vue create mode 100644 apps/frontend/src/views/Waitlist.vue diff --git a/.trae/documents/Admin User Management Implementation.md b/.trae/documents/Admin User Management Implementation.md new file mode 100644 index 0000000..6555287 --- /dev/null +++ b/.trae/documents/Admin User Management Implementation.md @@ -0,0 +1,51 @@ +# 管理员用户管理功能实现计划 + +## 当前状态分析 +- `index.js` 中的 `AdminOrders` 类包含管理员用户管理方法 +- `AdminUsers.vue` 组件目前使用普通用户方法和模拟数据 +- 管理员特定方法已定义但未被使用 + +## 实现目标 +1. 更新Vue组件以使用管理员特定的API方法 +2. 实现完整的管理员用户管理功能 +3. 确保为所有管理操作提供适当的UI支持 + +## 拟做变更 + +### 1. 类名重构 +- 将 `AdminOrders` 类重命名为 `AdminUsersService` 以提高清晰度 + +### 2. Vue组件更新 + +#### 数据和方法 +- 更新组件使用 `getAdminUsersList` 替代 `getUsersList` +- 为所有管理员特定操作实现方法 +- 删除模拟数据,使用真实API响应 + +#### UI组件 +- 更新用户表格以显示管理员用户字段 +- 添加创建管理员用户的支持 +- 实现管理员用户状态切换 +- 添加密码重置功能 +- 实现管理员用户删除 +- 更新表单以匹配管理员用户数据结构 + +### 3. API集成 +- 确保正确调用所有管理员特定方法 +- 适当处理API响应和错误 +- 为所有异步操作添加加载状态 + +## 实施步骤 +1. 重构 `index.js` 中的类名 +2. 更新组件导入和服务初始化 +3. 更新数据获取以使用管理员特定API +4. 实现管理员用户创建表单 +5. 更新用户表格,添加管理员特定列和操作 +6. 实现管理员用户状态切换 +7. 实现密码重置功能 +8. 实现管理员用户删除 +9. 更新管理员用户详情视图 +10. 测试所有功能 + +## 预期结果 +一个功能完整的管理员用户管理界面,利用管理员特定的API方法来创建、列出、更新、启用/禁用、重置密码、查看详情和删除管理员用户。 \ No newline at end of file diff --git a/.trae/documents/实现手机号登录功能.md b/.trae/documents/实现手机号登录功能.md new file mode 100644 index 0000000..fbbf6d1 --- /dev/null +++ b/.trae/documents/实现手机号登录功能.md @@ -0,0 +1,78 @@ +# 实现手机号登录功能 + +## 需求分析 +- 在现有登录系统中添加手机号登录功能 +- 支持两种登录方式: + 1. 手机号+验证码直接登录 + 2. 手机号+验证码+密码注册密码登录 + +## 实现方案 + +### 1. 创建手机号登录页面组件 +- 创建 `PhoneLogin.vue` 页面组件,位于 `apps/frontend/src/views/Login/` +- 实现手机号登录表单,包括: + - 手机号输入框 + - 验证码输入框和获取验证码按钮 + - 密码输入框(可选,用于注册密码登录) + - 登录/注册按钮 + +### 2. 配置路由 +- 在 `apps/frontend/src/router/index.js` 中添加手机号登录路由 +- 路径:`/login/phone` +- 名称:`phone-login` + +### 3. 修改主登录页面 +- 在 `Login.vue` 中添加跳转到手机号登录的链接 +- 位置:在忘记密码和注册链接附近 + +### 4. 实现手机号登录表单组件 +- 创建 `PhoneLoginForm.vue` 组件,位于 `apps/frontend/src/components/auth/` +- 实现表单验证逻辑: + - 手机号格式验证 + - 验证码格式验证 + - 密码格式验证(当选择注册密码登录时) +- 实现获取验证码功能: + - 倒计时功能 + - 防重复发送机制 + +### 5. 实现登录逻辑 +- 在 `login.js` 中添加手机号登录相关方法 +- 实现验证码发送API调用 +- 实现手机号+验证码登录API调用 +- 实现手机号+验证码+密码注册密码登录API调用 + +### 6. 国际化支持 +- 在国际化文件中添加手机号登录相关的文本 +- 支持中英文切换 + +### 7. 样式适配 +- 确保手机号登录页面与现有登录页面样式保持一致 +- 支持响应式设计 +- 支持暗色主题 + +## 实现步骤 + +1. 创建 `PhoneLogin.vue` 页面组件 +2. 创建 `PhoneLoginForm.vue` 表单组件 +3. 配置手机号登录路由 +4. 修改主登录页面添加跳转链接 +5. 实现表单验证和获取验证码功能 +6. 实现登录逻辑 +7. 添加国际化支持 +8. 测试功能完整性 + +## 预期效果 +- 用户可以在登录页面选择手机号登录方式 +- 手机号登录页面支持两种登录模式 +- 表单验证逻辑完整 +- 界面样式与现有系统保持一致 +- 支持国际化和暗色主题 + +## 技术要点 +- Vue 3 Composition API +- Vue Router 4 +- Element Plus UI组件库 +- Vue I18n国际化 +- 表单验证(使用Vuelidate) +- 响应式设计 +- 暗色主题支持 \ No newline at end of file diff --git a/.trae/documents/实现提示词管理功能.md b/.trae/documents/实现提示词管理功能.md new file mode 100644 index 0000000..59e775f --- /dev/null +++ b/.trae/documents/实现提示词管理功能.md @@ -0,0 +1,197 @@ +# 实现提示词管理功能 + +## 1. 功能概述 +在管理后台侧边栏添加提示词管理功能,实现左右分栏的提示词管理界面,支持提示词的增删改查和拖拽排序。 + +## 2. 技术栈 +- Vue 3 + Vite +- Element Plus +- Vue Router +- Vue I18n + +## 3. 实现步骤 + +### 3.1 添加侧边栏菜单项 +- 修改 `src/components/admin/AdminLayout.vue`,在侧边栏菜单中添加提示词管理菜单项 +- 导入相应的图标组件 + +### 3.2 配置路由 +- 修改 `src/router/index.js`,添加提示词管理页面的路由配置 +- 使用懒加载方式引入组件 + +### 3.3 创建提示词管理页面 +- 创建 `src/views/admin/AdminPromptManagement.vue` 文件 +- 实现左右分栏布局 +- 左侧:未生效提示词区域,支持增删改查 +- 右侧:生效提示词区域,支持拖拽排序和删除功能 + +### 3.4 实现提示词卡片组件 +- 创建 `src/components/admin/PromptCard.vue` 组件 +- 支持展示参考图和标题 +- 支持点击查看详情 +- 支持删除操作 + +### 3.5 实现提示词添加/编辑弹窗 +- 实现添加提示词的弹窗组件 +- 包含提示词类型选择(动物/人物/通用) +- 包含提示词标题、内容输入 +- 包含参考图上传功能 + +### 3.6 实现拖拽功能 +- 使用 Element Plus 的 `el-transfer` 或自定义拖拽实现 +- 支持从左侧拖拽到右侧 +- 支持右侧区域内的拖拽排序 + +### 3.7 添加国际化支持 +- 在语言文件中添加提示词管理相关的翻译 + +### 3.8 模拟数据 +- 在组件中添加模拟数据,实现本地数据管理 +- 支持提示词的增删改查操作 + +## 4. 详细实现 + +### 4.1 侧边栏菜单项 +在 `AdminLayout.vue` 的侧边栏菜单中添加: +```vue + + + + +``` + +### 4.2 路由配置 +在 `router/index.js` 中添加: +```javascript +const AdminPromptManagement = () => import('@/views/admin/AdminPromptManagement.vue') + +// 在 admin 路由的 children 数组中添加 +{ + path: 'prompt-management', + name: 'AdminPromptManagement', + component: AdminPromptManagement, + meta: { + title: '提示词管理' + } +} +``` + +### 4.3 提示词管理页面结构 +```vue + +``` + +### 4.4 实现拖拽功能 +使用原生 HTML5 拖拽 API 或 Element Plus 的拖拽组件,实现: +- 从左侧拖拽到右侧,将提示词设为生效状态 +- 在右侧区域内拖拽,调整提示词顺序 + +### 4.5 模拟数据结构 +```javascript +const prompts = [ + { + id: 1, + title: '示例提示词', + content: '这是一个示例提示词', + type: 'general', // animal, person, general + referenceImage: 'https://example.com/image.jpg', + isActive: false, + createdAt: new Date() + } +] +``` + +### 4.6 右侧删除功能实现 +```javascript +// 从生效区域移除提示词 +const removeFromActive = (promptId) => { + const prompt = prompts.find(p => p.id === promptId) + if (prompt) { + prompt.isActive = false + // 更新响应式数据 + } +} +``` + +## 5. 预期效果 +- 侧边栏新增提示词管理菜单项 +- 提示词管理页面分为左右两栏 +- 左侧可添加、编辑、删除提示词 +- 支持拖拽提示词从左侧到右侧 +- 右侧提示词可拖拽排序和删除 +- 数据本地模拟实现 + +## 6. 文件修改清单 +- `src/components/admin/AdminLayout.vue` - 添加侧边栏菜单项 +- `src/router/index.js` - 添加路由配置 +- `src/views/admin/AdminPromptManagement.vue` - 创建提示词管理页面 +- `src/components/admin/PromptCard.vue` - 创建提示词卡片组件 +- 语言文件 - 添加国际化支持 + +## 7. 注意事项 +- 确保拖拽功能在各种浏览器中正常工作 +- 实现响应式设计,适配不同屏幕尺寸 +- 保持代码风格与现有代码一致 +- 添加适当的用户反馈和验证 \ No newline at end of file diff --git a/.trae/documents/添加候补队列页面.md b/.trae/documents/添加候补队列页面.md new file mode 100644 index 0000000..b630864 --- /dev/null +++ b/.trae/documents/添加候补队列页面.md @@ -0,0 +1,41 @@ +# 实现步骤 + +1. **创建新页面组件**:在 `src/views/` 目录下创建 `Waitlist.vue` 组件,用于展示"已加入候补队列,等待审核中"的提示 +2. **添加多语言支持**:在 `src/locales/index.js` 中添加对应的中英文文本 +3. **配置路由**:在 `src/router/index.js` 中添加一个新的路由,指向这个新组件 + +# 详细实现 + +## 1. 创建 Waitlist.vue 组件 + +* 参考 `NotFound.vue` 的结构和样式 + +* 展示"已加入候补队列,等待审核中"的提示信息 + +* 添加返回首页的按钮 + +* 适配响应式设计 + +## 2. 添加多语言支持 + +在 `src/locales/index.js` 的 `zh` 和 `en` 对象中分别添加: + +* `waitlist.title`: 标题文本 + +* `waitlist.description`: 描述文本 + +* `waitlist.goHome`: 返回首页按钮文本 + +## 3. 配置路由 + +在 `src/router/index.js` 中添加一个新的路由: + +```javascript +{ + path: '/waitlist', + name: 'Waitlist', + component: () => import('@/views/Waitlist.vue'), + meta: { requiresAuth: false, keepAlive: false, fullScreen: true } +} +``` + diff --git a/apps/FrontendDesigner/package.json b/apps/FrontendDesigner/package.json index f4a475b..3785971 100644 --- a/apps/FrontendDesigner/package.json +++ b/apps/FrontendDesigner/package.json @@ -19,7 +19,8 @@ "three": "^0.180.0", "vue": "^3.5.24", "vue-i18n": "^9.14.2", - "vue-router": "^4.4.5" + "vue-router": "^4.4.5", + "vuedraggable": "^4.1.0" }, "devDependencies": { "@vitejs/plugin-vue": "^6.0.1", diff --git a/apps/FrontendDesigner/src/components/admin/AdminLayout.vue b/apps/FrontendDesigner/src/components/admin/AdminLayout.vue index 60462a5..1bc3ce4 100644 --- a/apps/FrontendDesigner/src/components/admin/AdminLayout.vue +++ b/apps/FrontendDesigner/src/components/admin/AdminLayout.vue @@ -110,6 +110,10 @@ + + + +
- + + diff --git a/apps/FrontendDesigner/src/views/admin/AdminPointsManagement.vue b/apps/FrontendDesigner/src/views/admin/AdminPointsManagement.vue index 84d5ef4..6825da8 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminPointsManagement.vue +++ b/apps/FrontendDesigner/src/views/admin/AdminPointsManagement.vue @@ -13,7 +13,8 @@
- + +
- + + diff --git a/apps/FrontendDesigner/src/views/admin/AdminRoleManagement/AdminRoleManagement.vue b/apps/FrontendDesigner/src/views/admin/AdminRoleManagement/AdminRoleManagement.vue index f25833b..dc4fac5 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminRoleManagement/AdminRoleManagement.vue +++ b/apps/FrontendDesigner/src/views/admin/AdminRoleManagement/AdminRoleManagement.vue @@ -12,7 +12,8 @@
- + + diff --git a/apps/FrontendDesigner/src/views/admin/AdminRouteManagement.vue b/apps/FrontendDesigner/src/views/admin/AdminRouteManagement.vue index ab1ed5c..528bf56 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminRouteManagement.vue +++ b/apps/FrontendDesigner/src/views/admin/AdminRouteManagement.vue @@ -9,7 +9,8 @@
- + + diff --git a/apps/FrontendDesigner/src/views/admin/AdminUserList.vue b/apps/FrontendDesigner/src/views/admin/AdminUserList.vue index 070ca72..6e30e14 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminUserList.vue +++ b/apps/FrontendDesigner/src/views/admin/AdminUserList.vue @@ -22,7 +22,8 @@
- + + diff --git a/apps/FrontendDesigner/src/views/admin/AdminUsers/AdminUserInvites.vue b/apps/FrontendDesigner/src/views/admin/AdminUsers/AdminUserInvites.vue index 837cd02..5d8b9e8 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminUsers/AdminUserInvites.vue +++ b/apps/FrontendDesigner/src/views/admin/AdminUsers/AdminUserInvites.vue @@ -43,9 +43,9 @@
+ @@ -145,9 +145,9 @@
+ diff --git a/apps/FrontendDesigner/src/views/admin/AdminUsers/AdminUsers.vue b/apps/FrontendDesigner/src/views/admin/AdminUsers/AdminUsers.vue index 2b1e8a9..ec9e559 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminUsers/AdminUsers.vue +++ b/apps/FrontendDesigner/src/views/admin/AdminUsers/AdminUsers.vue @@ -76,9 +76,9 @@
+ diff --git a/apps/FrontendDesigner/src/views/admin/AdminUsers/index.js b/apps/FrontendDesigner/src/views/admin/AdminUsers/index.js index 452ab08..7f59a5b 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminUsers/index.js +++ b/apps/FrontendDesigner/src/views/admin/AdminUsers/index.js @@ -23,7 +23,8 @@ export class AdminOrders { } const requestUrl = { method: adminApi.default.getUserDetail.method, - url: adminApi.default.getUserDetail.url.replace('USERID', params.id) + url: adminApi.default.getUserDetail.url.replace('USERID', params.id), + isLoading: adminApi.default.getUserDetail.isLoading } return requestUtils.common(requestUrl, params); } @@ -35,7 +36,8 @@ export class AdminOrders { } const requestUrl = { method: adminApi.default.updateUserStatus.method, - url: (adminApi.default.updateUserStatus.url.replace('USERID', params.id))+'?'+'id='+data.id+'&status='+data.status + url: (adminApi.default.updateUserStatus.url.replace('USERID', params.id))+'?'+'id='+data.id+'&status='+data.status, + isLoading: adminApi.default.updateUserStatus.isLoading } return requestUtils.common(requestUrl, params); } @@ -47,7 +49,8 @@ export class AdminOrders { } const requestUrl = { method: adminApi.default.updateUserName.method, - url: adminApi.default.updateUserName.url.replace('USERID', params.id) + url: adminApi.default.updateUserName.url.replace('USERID', params.id), + isLoading: adminApi.default.updateUserName.isLoading } return requestUtils.common(requestUrl, params); } @@ -62,7 +65,8 @@ export class AdminOrders { } const requestUrl = { method: adminApi.default.getUsersinvites.method, - url: adminApi.default.getUsersinvites.url.replace('USERID', params.id) + url: adminApi.default.getUsersinvites.url.replace('USERID', params.id), + isLoading: adminApi.default.getUsersinvites.isLoading } return requestUtils.common(requestUrl, params); } @@ -74,7 +78,8 @@ export class AdminOrders { } const requestUrl = { method: adminApi.default.changeRole.method, - url: adminApi.default.changeRole.url.replace('USERID', params.id) + url: adminApi.default.changeRole.url.replace('USERID', params.id), + isLoading: adminApi.default.changeRole.isLoading } return requestUtils.common(requestUrl, params); } @@ -111,4 +116,136 @@ export class AdminOrders { } return requestUtils.common(requestUrl, params); } + //创建管理员用户 + async createAdminUser(data) { + let params = { + username: data.username || '', + email: data.email || '', + password: data.password || '', + fullName: data.fullName || '', + isActive: data.isActive !== undefined ? data.isActive : true, + isSuperuser: data.isSuperuser !== undefined ? data.isSuperuser : false, + roleIds: Array.isArray(data.roleIds) ? data.roleIds : [] + } + return requestUtils.common(adminApi.default.createAdminUser, params); + } + //分页查询管理员用户列表 + async getAdminUsersList(data) { + let params = { + username: data.username || '', + email: data.email || '', + isActive: data.isActive !== undefined ? data.isActive : true, + pageSize: data.pageSize || 10, + pageNum: data.pageNum || 1, + orderByColumn: data.orderByColumn || '', + isAsc: data.isAsc || 'asc' + } + return requestUtils.common(adminApi.default.getAdminUserList, params); + /** + 返回示例: + { + "code": 0, + "success": true, + "data": { + "total": 9007199254740991, + "rows": [ + { + "id": 9007199254740991, + "username": "string", + "email": "string", + "fullName": "string", + "isActive": true, + "isSuperuser": true, + "lastLogin": "2025-12-19T05:33:51.763Z", + "createdAt": "2025-12-19T05:33:51.763Z", + "updatedAt": "2025-12-19T05:33:51.763Z", + "roles": [ + { + "id": 9007199254740991, + "roleCode": "string", + "roleName": "string", + "description": "string" + } + ] + } + ], + "code": 1073741824, + "msg": "string" + }, + "message": "操作成功" +} + */ + } + //更新管理员用户 + async updateAdminUser(data) { + let params = { + id: data.id, + username: data.username, + email: data.email, + fullName: data.fullName, + isActive: data.isActive, + isSuperuser: data.isSuperuser, + roleIds: data.roleIds + } + return requestUtils.common(adminApi.default.updateAdminUser, params); + } + //启用/禁用用户 + async enableDisableUser(data) { + let params = { + userId: data.id, + isActive : data.isActive + } + return requestUtils.common(adminApi.default.toggleAdminUserStatus, params); + } + //重置用户密码 + async resetUserPassword(data) { + let params = { + userId: data.id, + newPassword: data.newPassword + } + return requestUtils.common(adminApi.default.resetAdminUserPassword, params); + } + //根据用户ID查询管理员用户详情 + async getAdminUserDetail(data) { + let params = { + userId : data.id || '' + } + const requestUrl = { + method: adminApi.default.getAdminUserDetail.method, + url: adminApi.default.getAdminUserDetail.url.replace('USERID', params.id), + isLoading: adminApi.default.getAdminUserDetail.isLoading + } + return requestUtils.common(requestUrl, params); + /** + { + "code": 0, + "success": true, + "data": { + "id": 9007199254740991, + "username": "string", + "email": "string", + "fullName": "string", + "isActive": true, + "isSuperuser": true, + "lastLogin": "2025-12-19T05:36:04.270Z", + "createdAt": "2025-12-19T05:36:04.270Z", + "updatedAt": "2025-12-19T05:36:04.270Z", + "roles": [ + { + "id": 9007199254740991, + "roleCode": "string", + "roleName": "string", + "description": "string" + } + ] + }, + "message": "操作成功" +} + */ + } + //批量删除管理员用户 + async deleteAdminUsers(data) { + let arr = data.ids || [] + return requestUtils.common(adminApi.default.batchDeleteAdminUser, arr); + } } \ No newline at end of file diff --git a/apps/frontend/package-lock.json b/apps/frontend/package-lock.json index 10d7335..09e5180 100644 --- a/apps/frontend/package-lock.json +++ b/apps/frontend/package-lock.json @@ -10,7 +10,6 @@ "dependencies": { "@element-plus/icons-vue": "^2.3.2", "@google/genai": "^1.27.0", - "@stripe/stripe-js": "^4.8.0", "@types/three": "^0.180.0", "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", @@ -1100,15 +1099,6 @@ "win32" ] }, - "node_modules/@stripe/stripe-js": { - "version": "4.10.0", - "resolved": "https://registry.npmmirror.com/@stripe/stripe-js/-/stripe-js-4.10.0.tgz", - "integrity": "sha512-KrMOL+sH69htCIXCaZ4JluJ35bchuCCznyPyrbN8JXSGQfwBI1SuIEMZNwvy8L8ykj29t6sa5BAAiL7fNoLZ8A==", - "license": "MIT", - "engines": { - "node": ">=12.16" - } - }, "node_modules/@tweenjs/tween.js": { "version": "23.1.3", "resolved": "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 8d8b57a..5d18916 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -13,7 +13,6 @@ "dependencies": { "@element-plus/icons-vue": "^2.3.2", "@google/genai": "^1.27.0", - "@stripe/stripe-js": "^4.8.0", "@twind/core": "^1.1.3", "@twind/preset-autoprefix": "^1.0.7", "@twind/preset-tailwind": "^1.1.4", @@ -24,6 +23,7 @@ "country-state-city": "^3.2.1", "dayjs": "^1.11.13", "element-plus": "^2.11.7", + "install": "^0.13.0", "jose": "^6.1.1", "motion-v": "^1.7.4", "normalize.css": "^8.0.1", diff --git a/apps/frontend/src/components/IPCard/index.vue b/apps/frontend/src/components/IPCard/index.vue index 035e839..e49d034 100644 --- a/apps/frontend/src/components/IPCard/index.vue +++ b/apps/frontend/src/components/IPCard/index.vue @@ -98,7 +98,9 @@ const isTouching = ref(false); const isControlsVisible = ref(false); const cjimg = 'https://draft-user.s3.us-east-2.amazonaws.com/images/14f98f33-06a7-4629-a42e-d7cfbced786f'; const anTypeImg = 'https://draft-user.s3.us-east-2.amazonaws.com/images/1e82b2b6-0e5d-4a62-b65f-098952eb2f67'; -const humanTypeImg = 'https://draft-user.s3.us-east-2.amazonaws.com/images/e3e60cc7-9777-41ba-9d1e-f5ffc92e4fac.webp'; +// const humanTypeImg = 'https://draft-user.s3.us-east-2.amazonaws.com/images/e3e60cc7-9777-41ba-9d1e-f5ffc92e4fac.webp'; +// const humanTypeImg = 'https://draft-user.s3.us-east-2.amazonaws.com/images/61770f50-4b87-40a0-9297-cabda0ec6317.webp' +const humanTypeImg = '' const giminiServer = new GiminiServer(); // 图片比例 const imageAspectRatio = ref(9/16); // 默认比例 @@ -218,9 +220,9 @@ const handleGenerateImage = async () => { } if(props?.cardData?.ipType){ if(props?.cardData?.ipType==1){ - referenceImages.push(humanTypeImg); + humanTypeImg&&referenceImages.push(humanTypeImg); }else{ - referenceImages.push(anTypeImg); + anTypeImg&&referenceImages.push(anTypeImg); } } if(iscjt){ @@ -277,6 +279,8 @@ const handleGenerateImage = async () => { 保证角色所有的服饰衣服都为木头材质颜色,并且要带一些木头纹理,颜色为#e2cfb3。 ` ; + // 角色姿势:${props.cardData.ipType==1?``:``} + if(props.cardData.prompt&&props.cardData.prompt.indexOf('nospec')!=-1){ prompt = '按原图生成' referenceImages = [props.cardData.inspirationImage]; diff --git a/apps/frontend/src/components/IPCard/tsc.js b/apps/frontend/src/components/IPCard/tsc.js index bf63c5b..9724db6 100644 --- a/apps/frontend/src/components/IPCard/tsc.js +++ b/apps/frontend/src/components/IPCard/tsc.js @@ -140,4 +140,18 @@ // 保证角色所有的服饰衣服都为木头材质颜色,并且要带一些木头纹理,颜色为#bfa888。 // ` +const qwentsc = ` +角色肤色和衣服材质都为纯色一种颜色如下: + 重点:保证角色所有的服饰衣服都为木头材质颜色,并且要带一些木头纹理,颜色为#e2cfb3 + 一个通体由单一纯色木材雕刻而成的角色,整体呈现木质雕塑或木偶风格. + A full-body character portrait + 角色特征:Q 版萌系造型,头身比例夸张(大头小身),神态纯真,服饰设计融合童话风与复古感 + 姿势:身体笔直,正对镜头,双臂自然下垂但略微外展,肘部微屈约15度,手掌张开、放松,掌心朝向身体内侧。 + 双腿并拢伸直,双脚平放于地面,脚尖朝前。 + 眼睛放大设计:眼眶结构宽大清晰,单侧眼球/眼窝直径不小于2cm(即半径≥1cm),预留足够空间用于嵌入镜头设备,眼型饱满、边缘厚实,适合3D打印安装。 + 嘴巴小巧,表情温柔童真。 + Style:潮玩盲盒角色设计,采用 3D 立体建模渲染,呈现细腻的质感与精致的细节。 + Note: The image should not have white borders. + 去除原图中复杂的背景,只保留人物角色的主体。 +` export const cjt = `将玩偶放在桌子玻璃正中间上,原图放在图中桌面上的相框里面[CJT_DEOTA]` diff --git a/apps/frontend/src/components/StripePaymentForm.vue b/apps/frontend/src/components/StripePaymentForm.vue index bbd8a87..4162542 100644 --- a/apps/frontend/src/components/StripePaymentForm.vue +++ b/apps/frontend/src/components/StripePaymentForm.vue @@ -61,11 +61,8 @@ import { ref, computed, onMounted, onUnmounted, watch } from 'vue' import { ElMessage, ElLoading } from 'element-plus' import { CreditCard, Lock } from '@element-plus/icons-vue' -import { loadStripe } from '@stripe/stripe-js' import { useI18n } from 'vue-i18n' import { PayServer } from '@deotaland/utils' -// 模拟Stripe公钥(生产环境中应该从环境变量获取) -const STRIPE_PUBLISHABLE_KEY = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY const { t } = useI18n() // Props const props = defineProps({ @@ -90,8 +87,6 @@ const props = defineProps({ // Emits const emit = defineEmits(['payment-success', 'payment-error', 'cancel']) -// 响应式数据 -const stripe = ref(null) const elements = ref(null) const cardElement = ref(null) const processing = ref(false) @@ -102,6 +97,7 @@ const couponSuccess = ref('') const discountAmount = ref(0) const taxAmount = ref(0) const shippingAmount = ref(0) +const isInitialized = ref(false) // 防止重复初始化 // 支付方式选项 // 仅支持 Stripe 卡片支付 @@ -115,6 +111,11 @@ const finalAmount = computed(() => { const payServer = new PayServer() // 初始化Stripe const initializeStripe = async () => { + // 防止重复初始化 + if (isInitialized.value) { + return; + } + try { const data = await payServer.createPaymentIntent(finalAmount.value, props.currency, { order_id: props.orderId, @@ -122,6 +123,8 @@ const initializeStripe = async () => { }) elements.value = data.element cardElement.value = data.cardElement + isInitialized.value = true; + // 监听卡片验证错误 cardElement.value.on('change', ({ error }) => { const displayError = document.getElementById('card-errors') @@ -249,6 +252,8 @@ onUnmounted(() => { if (elements.value) { elements.value = null } + // 重置初始化标志 + isInitialized.value = false; }) // 监听金额变化,重新计算费用 diff --git a/apps/frontend/src/components/auth/PhoneLoginForm.vue b/apps/frontend/src/components/auth/PhoneLoginForm.vue new file mode 100644 index 0000000..81d96ff --- /dev/null +++ b/apps/frontend/src/components/auth/PhoneLoginForm.vue @@ -0,0 +1,711 @@ + + + + + diff --git a/apps/frontend/src/components/iPandCardLeft/index.vue b/apps/frontend/src/components/iPandCardLeft/index.vue index 09fc183..f26c5ad 100644 --- a/apps/frontend/src/components/iPandCardLeft/index.vue +++ b/apps/frontend/src/components/iPandCardLeft/index.vue @@ -24,6 +24,34 @@
+ + +
+
+ + {{ $t('iPandCardLeft.modelSelection') }} + +
+ + +
+ {{ model.name }} + {{ model.description }} +
+
+
+
+
@@ -310,6 +338,48 @@ const handleIpTypeSelect = (type) => { // // 忽略本地存储异常 // } }; + +// 模型选择相关 +const selectedModelId = ref(null); +const availableModels = ref([ + { + id: 'nano_banana', + name: 'Nano Banana', + description: '快速生成', + model: 'gemini-2.5-flash-image' + }, + { + id: 'nano_banana_pro', + name: 'Nano Banana Pro', + description: '高质量生成', + model: 'gemini-3-pro-image-preview' + }, + { + id: 'doubao', + name: 'Doubao', + description: '豆包模型', + model: 'doubao' + }, + { + id: 'qwimg', + name: 'QwImg', + description: '通义千问', + model: 'ali' + } +]); + +// 处理模型选择 +const handleModelSelect = (modelId) => { + selectedModelId.value = modelId; + const selectedModel = availableModels.value.find(m => m.id === modelId); + // 保存到本地存储 + try { + localStorage.setItem('selectedModelId', modelId); + localStorage.setItem('selectedModel', JSON.stringify(selectedModel)); + } catch (e) { + // 忽略本地存储异常 + } +}; // 新增:电子模块和草图选择相关状态 const selectedModule = ref(null); // 选中的电子模块 const selectedSketch = ref(null); // 选中的草图 @@ -323,6 +393,19 @@ const selectedSkinColor = ref(null); // 选中的肤色 const expressions = ref([]); const init = () => { + // 从本地存储加载模型选择 + try { + const savedModelId = localStorage.getItem('selectedModelId'); + if (savedModelId) { + selectedModelId.value = savedModelId; + } else { + // 默认选择第一个模型 + selectedModelId.value = availableModels.value[0].id; + } + } catch (e) { + // 如果加载失败,使用默认模型 + selectedModelId.value = availableModels.value[0].id; + } } onMounted(() => { init() @@ -1008,6 +1091,27 @@ onMounted(() => { font-weight: 500; } +/* 模型选择样式 */ +.model-select { + width: 100%; +} + +.model-option { + display: flex; + flex-direction: column; + gap: 4px; +} + +.model-option .model-name { + font-weight: 600; + color: var(--el-text-color-primary); +} + +.model-option .model-description { + font-size: 12px; + color: var(--el-text-color-secondary); +} + /* 适应亮色主题 */ :root { --border-color: rgba(0, 0, 0, 0.1); @@ -1042,6 +1146,11 @@ onMounted(() => { border-color: #A78BFA; } +/* 模型选择在亮色主题下的样式 */ +.model-select { + width: 100%; +} + .logo-container { display: flex; align-items: center; @@ -1099,7 +1208,7 @@ onMounted(() => { /* 选择框样式 */ .model-select { width: 100%; - padding: 10px 12px; + /* padding: 10px 12px; */ background-color: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; diff --git a/apps/frontend/src/components/layout/AppSidebar.vue b/apps/frontend/src/components/layout/AppSidebar.vue index 93da40d..3e8231e 100644 --- a/apps/frontend/src/components/layout/AppSidebar.vue +++ b/apps/frontend/src/components/layout/AppSidebar.vue @@ -44,7 +44,7 @@ 🪄 {{ remainingPoints }}
--> - +
diff --git a/apps/frontend/src/locales/index.js b/apps/frontend/src/locales/index.js index 270b3e0..74486fa 100644 --- a/apps/frontend/src/locales/index.js +++ b/apps/frontend/src/locales/index.js @@ -603,6 +603,27 @@ export default { invite_code_empty_error: '请输入邀请码', join_waitlist: '加入候补队列', join_waitlist_success: '已成功加入候补队列,我们将尽快与您联系', + // 手机号登录相关 + phone_login_title: '手机号登录', + phone_login_subtitle: '使用手机号和验证码登录', + phone_label: '手机号', + phone_placeholder: '请输入您的手机号', + phone_empty_error: '请输入手机号', + phone_invalid_error: '请输入有效的手机号', + code_label: '验证码', + code_placeholder: '请输入验证码', + code_empty_error: '请输入验证码', + code_invalid_error: '请输入有效的验证码', + send_code: '发送验证码', + resend_code_after: '{}秒后重新发送', + phone_login_mode: '手机号登录', + phone_register_mode: '手机号注册', + phone_login_button: '登录', + phone_register_button: '注册', + phone_logging: '正在登录...', + phone_registering: '正在注册...', + phone_login_link: '使用手机号登录', + email_login_link: '使用邮箱登录', }, payment: { methods: '支付方式', @@ -1173,6 +1194,8 @@ export default { ipType: 'IP类型', character: '人物', animal: '动物', + modelSelection: '模型选择', + modelSelectPlaceholder: '请选择模型', characterImport: '角色导入', expression: { title: '表情选择', @@ -1261,6 +1284,11 @@ export default { description: '抱歉,您访问的页面不存在或已被移除。', goHome: '返回首页', goBack: '返回上一页' + }, + waitlist: { + title: '已加入候补队列', + description: '您的申请已提交,正在等待审核。我们将尽快处理您的请求。', + goHome: '返回首页' } }, en: { @@ -1966,6 +1994,27 @@ export default { invite_code_empty_error: 'Please enter invite code', join_waitlist: 'Join Waitlist', join_waitlist_success: 'Successfully joined the waitlist, we will contact you soon', + // Phone login related + phone_login_title: 'Phone Login', + phone_login_subtitle: 'Login with phone number and verification code', + phone_label: 'Phone Number', + phone_placeholder: 'Please enter your phone number', + phone_empty_error: 'Please enter phone number', + phone_invalid_error: 'Please enter a valid phone number', + code_label: 'Verification Code', + code_placeholder: 'Please enter verification code', + code_empty_error: 'Please enter verification code', + code_invalid_error: 'Please enter a valid verification code', + send_code: 'Send Code', + resend_code_after: 'Resend after {seconds}s', + phone_login_mode: 'Phone Login', + phone_register_mode: 'Phone Register', + phone_login_button: 'Login', + phone_register_button: 'Register', + phone_logging: 'Logging in...', + phone_registering: 'Registering...', + phone_login_link: 'Login with Phone', + email_login_link: 'Login with Email', }, payment: { methods: 'Payment Methods', @@ -2426,6 +2475,8 @@ export default { ipType: 'IP Type', character: 'Character', animal: 'Animal', + modelSelection: 'Model Selection', + modelSelectPlaceholder: 'Please select a model', characterImport: 'Character Import', expression: { title: 'Expression Selection', @@ -2514,6 +2565,11 @@ export default { description: 'Sorry, the page you are looking for does not exist or has been removed.', goHome: 'Go Home', goBack: 'Go Back' + }, + waitlist: { + title: 'Joined Waitlist', + description: 'Your application has been submitted and is waiting for review. We will process your request as soon as possible.', + goHome: 'Go Home' } } }, diff --git a/apps/frontend/src/router/index.js b/apps/frontend/src/router/index.js index f58921a..2fe01e0 100644 --- a/apps/frontend/src/router/index.js +++ b/apps/frontend/src/router/index.js @@ -20,6 +20,7 @@ const home = () => import('../views/home/index.vue') const PointsRecharge = () => import('../views/PointsRecharge.vue') const UserCenter = () => import('../views/user/index.vue') const NotFound = () => import('../views/NotFound.vue') +const Waitlist = () => import('../views/Waitlist.vue') NProgress.configure({ showSpinner: false, })// 开启轻量模式(顶部细线) @@ -30,17 +31,17 @@ const routes = [ name: 'home', component: home, meta: { fullScreen: false } - }, - { - path: '/:pathMatch(.*)*', - name: 'NotFound', - component: home, // 显示404页面组件 - meta: { requiresAuth: false, keepAlive: false, fullScreen: true } },{ path: '/login', name: 'login', component: Login, - meta: { requiresGuest: true } + meta: { requiresGuest: true } + }, + { + path: '/login/phone', + name: 'phone-login', + component: () => import('@/views/Login/PhoneLogin.vue'), + meta: { requiresGuest: true, fullScreen: true } }, { path: '/czhome', @@ -51,8 +52,8 @@ const routes = [ { path: '/register', name: 'register', - component: Register, - meta: { requiresGuest: true, fullScreen: true } + component: Register, + meta: { requiresGuest: true, fullScreen: true } }, { path: '/forgot-password', @@ -60,15 +61,15 @@ const routes = [ component: ForgotPassword, meta: { requiresGuest: true, fullScreen: true } }, + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: NotFound, // 显示404页面组件 + meta: { requiresAuth: false, keepAlive: false, fullScreen: true } + } ] //免费会员/达人会员动态路由 export const freeRoutes = [ - { - path: '/czhome', - name: 'czhome', - component: ModernHome, - meta: { requiresAuth: false, keepAlive: false } - }, { path: '/ui-test', name: 'ui-test', @@ -172,7 +173,8 @@ router.beforeEach(async (to, from, next) => { // window.localStorage.setItem('token','123') // return next() // } - if (to.meta.requiresAuth) { + const newto = freeRoutes.find(route => route.path == to.path) + if (newto?.meta?.requiresAuth) { const token = localStorage.getItem('token') // 如果没有 token,跳转到登录页 if (!token) { @@ -182,8 +184,8 @@ router.beforeEach(async (to, from, next) => { } // 检查是否需要添加动态路由 const authStore = useAuthStore(); - const user_role = authStore.user?.user_role || '0' - if(user_role != '0' && router.getRoutes().length <= routes.length) { + const user_role = authStore.user?.user_role; + if(user_role != '0' && router.getRoutes().length == routes.length) { // 添加动态路由 addDynamicRoutes(); if(isDynamicRoute(to.path)) { @@ -193,18 +195,38 @@ router.beforeEach(async (to, from, next) => { }, 20); return } + }else if(user_role == '0'){ + // 恢复默认路由 + removeDynamicRoutes() + router.addRoute( + { + path: '/:pathMatch(.*)*', + name: 'NotFound', + component: Waitlist, // 显示404页面组件 + meta: { requiresAuth: false, keepAlive: false, fullScreen: true } + } + ) } next() }) // 添加动态路由的函数 function addDynamicRoutes() { - console.log('添加动态路由前路由数量:', router.getRoutes().length); freeRoutes.forEach(route => { router.addRoute(route) }) } - +//恢复默认路由 +function removeDynamicRoutes() { + if(router.getRoutes().length == routes.length){ + return + } + router.getRoutes().forEach(route => { + if (route.name && !routes.some(r => r.name === route.name)) { + router.removeRoute(route.name) + } + }) +} // 检查是否是动态路由 function isDynamicRoute(path) { return freeRoutes.some(route => { @@ -216,11 +238,8 @@ function isDynamicRoute(path) { } window.Redirectlogin = () => { localStorage.removeItem('token') - router.getRoutes().forEach(route => { - if (route.name && !routes.some(r => r.name === route.name)) { - router.removeRoute(route.name) - } - }) + // 恢复默认路由 + removeDynamicRoutes() router.push('/login') } router.afterEach(() => { diff --git a/apps/frontend/src/stores/auth.js b/apps/frontend/src/stores/auth.js index d3fe948..7c69f98 100644 --- a/apps/frontend/src/stores/auth.js +++ b/apps/frontend/src/stores/auth.js @@ -6,7 +6,7 @@ export const useAuthStore = defineStore('auth', () => { // 状态定义 const user = ref({}) const token = ref('') - // 登录方法 + // 邮箱登录方法 const login = async (data,callback=null) => { try { const res = await requestUtils.common(clientApi.default.LOGIN, data) @@ -22,6 +22,38 @@ export const useAuthStore = defineStore('auth', () => { } finally { } } + // 手机号+验证码登录方法 + const phoneLogin = async (data,callback=null) => { + try { + const res = await requestUtils.common(clientApi.default.PHONE_LOGIN, data) + if(res.code === 0){ + let data = res.data; + // 登录成功,保存token和用户信息 + loginSuccess(data,callback) + return res + } + } catch (error) { + console.error('手机号登录失败:', error) + throw error + } finally { + } + } + // 手机号+验证码+密码注册方法 + const phoneRegister = async (data,callback=null) => { + try { + const res = await requestUtils.common(clientApi.default.PHONE_REGISTER, data) + if(res.code === 0){ + let data = res.data; + // 注册成功,保存token和用户信息 + loginSuccess(data,callback) + return res + } + } catch (error) { + console.error('手机号注册失败:', error) + throw error + } finally { + } + } //登录成功方法 const loginSuccess = (data,callback=null) => { token.value = data.accessToken @@ -52,6 +84,8 @@ export const useAuthStore = defineStore('auth', () => { user, token, login, + phoneLogin, + phoneRegister, logout, loginSuccess, } diff --git a/apps/frontend/src/views/Login/Login.vue b/apps/frontend/src/views/Login/Login.vue index da442e9..8d9ded1 100644 --- a/apps/frontend/src/views/Login/Login.vue +++ b/apps/frontend/src/views/Login/Login.vue @@ -48,6 +48,15 @@