重构 API 层架构:统一 HTTP 请求、新增算力调度后端路由
- 请求拦截器统一 Auth 头不带 Bearer 前缀,新增 /suanli 前缀路由到算力调度后端 - 任务创建/轮询/历史接口迁移至 apis/display,改为 axios 调用替代原始 fetch - 模型 API 分离为两层:apis 纯 HTTP 调用 + utils 缓存业务逻辑 - 新增历史任务列表接口 requestTaskHistory(支持 user_id/platform_code 筛选和分页) - 响应拦截器兼容 status/code 双字段,用户信息兼容新旧 data 格式 - 移除免费次数(freeTimes)体系 - 更新 CLAUDE.md 文档
This commit is contained in:
parent
72267ab2c9
commit
5da5496492
@ -3,8 +3,7 @@ VITE_BASE = '/'
|
|||||||
|
|
||||||
# 主服务
|
# 主服务
|
||||||
VITE_API_PREFIX = '/api'
|
VITE_API_PREFIX = '/api'
|
||||||
VITE_API_BASE_URL = 'http://test.xueai.art/api' # http://huanda.xueai.art http://106.54.11.219/api 43.248.131.153:8003
|
VITE_API_BASE_URL = 'http://test.xueai.art/newapi/api' # http://huanda.xueai.art http://106.54.11.219/api 43.248.131.153:8003
|
||||||
VITE_API_WS_URL = 'ws://test.xueai.art/api'
|
|
||||||
|
|
||||||
# 支付服务
|
# 支付服务
|
||||||
VITE_API_PAY_PREFIX = '/pay'
|
VITE_API_PAY_PREFIX = '/pay'
|
||||||
@ -12,7 +11,8 @@ VITE_API_PAY_TARGET = 'http://test.xueai.art' # http://43.248.133.202 test.xue
|
|||||||
|
|
||||||
# 任务处理模块
|
# 任务处理模块
|
||||||
VITE_API_WORKFLOW_UPLOAD = 'http://43.248.97.19:4000/aigc/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
|
VITE_API_WORKFLOW_UPLOAD = 'http://43.248.97.19:4000/aigc/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
|
||||||
VITE_API_WORKFLOW_WS = 'ws://43.248.97.19:4000/testworkflow'
|
VITE_API_TASK_PREFIX = '/suanli'
|
||||||
|
VITE_API_TASK_TARGET = 'http://test.xueai.art'
|
||||||
|
|
||||||
# 是否开启开发者工具
|
# 是否开启开发者工具
|
||||||
VITE_OPEN_DEVTOOLS = false
|
VITE_OPEN_DEVTOOLS = false
|
||||||
|
|||||||
@ -7,7 +7,6 @@ VITE_BUILD_MOCK = false
|
|||||||
# 主服务
|
# 主服务
|
||||||
VITE_API_PREFIX = '/api'
|
VITE_API_PREFIX = '/api'
|
||||||
VITE_API_BASE_URL = 'https://sxwz.xueai.art/api'
|
VITE_API_BASE_URL = 'https://sxwz.xueai.art/api'
|
||||||
VITE_API_WS_URL = 'wss://sxwz.xueai.art/api'
|
|
||||||
|
|
||||||
# 支付服务
|
# 支付服务
|
||||||
VITE_API_PAY_PREFIX = '/pay'
|
VITE_API_PAY_PREFIX = '/pay'
|
||||||
@ -15,7 +14,8 @@ VITE_API_PAY_TARGET = 'https://sxwz.xueai.art' # http://43.248.133.202
|
|||||||
|
|
||||||
# 任务处理模块
|
# 任务处理模块
|
||||||
VITE_API_WORKFLOW_UPLOAD = 'https://designtools.xueai.art/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
|
VITE_API_WORKFLOW_UPLOAD = 'https://designtools.xueai.art/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
|
||||||
VITE_API_WORKFLOW_WS = 'wss://talkingdraw.xueai.art/testworkflow'
|
VITE_API_TASK_PREFIX = '/suanli'
|
||||||
|
VITE_API_TASK_TARGET = 'http://test.xueai.art'
|
||||||
|
|
||||||
# 模型资源
|
# 模型资源
|
||||||
VITE_API_MODEL_RESOURCE = 'https://resources.xueai.art/AIGC'
|
VITE_API_MODEL_RESOURCE = 'https://resources.xueai.art/AIGC'
|
||||||
|
|||||||
72
CLAUDE.md
72
CLAUDE.md
@ -16,7 +16,7 @@ Vue 3 (Composition API) + Vite 7 + Pinia + Vue Router + Element Plus + Less + pn
|
|||||||
|
|
||||||
## 架构概览
|
## 架构概览
|
||||||
|
|
||||||
AI 绘画/视频生成前端操作平台,通过 HTTP 接口对接后端和第三方 AI 平台(RunningHub),提交生成任务并轮询结果。
|
AI 绘画/视频生成前端操作平台,通过 HTTP 接口对接算力调度后端(suanli)和第三方 AI 平台(RunningHub),提交生成任务并轮询结果。
|
||||||
|
|
||||||
### 关键目录
|
### 关键目录
|
||||||
|
|
||||||
@ -25,36 +25,70 @@ src/
|
|||||||
├── main.js # 入口:创建 Vue 应用,安装 Pinia/Router/VueVirtualScroller
|
├── main.js # 入口:创建 Vue 应用,安装 Pinia/Router/VueVirtualScroller
|
||||||
├── router/index.js # 路由定义 + token 验证守卫
|
├── router/index.js # 路由定义 + token 验证守卫
|
||||||
├── stores/ # Pinia 状态管理
|
├── stores/ # Pinia 状态管理
|
||||||
│ ├── user.js # 用户认证、信息、免费次数
|
│ ├── user.js # 用户认证、信息
|
||||||
│ └── display.js # 生成历史列表、UI 状态(滚动、画布等)
|
│ └── display.js # 生成历史列表、UI 状态(滚动、画布等)
|
||||||
├── apis/ # HTTP API 模块(auth/display),通过 axios 实例调用
|
├── apis/ # HTTP API 层:纯请求封装,不含业务逻辑
|
||||||
|
│ ├── auth/ # 认证相关(登录、token 校验、用户信息)
|
||||||
|
│ └── display/ # 任务创建/轮询/历史、平台模型、收藏/删除
|
||||||
├── components/ # 通用组件
|
├── components/ # 通用组件
|
||||||
│ ├── dialogBox/ # 生成参数输入面板(核心交互入口),含模型选择、比例、上传等子组件
|
│ ├── dialogBox/ # 生成参数输入面板(核心交互入口),含模型选择、比例、上传等子组件
|
||||||
│ ├── virtual-scroller/# 虚拟滚动列表组件(自定义实现,reverse 模式)
|
│ ├── virtual-scroller/# 虚拟滚动列表组件(自定义实现,reverse 模式)
|
||||||
│ └── canvas/ # 图片画布编辑(圆/矩形选区,局部重绘)
|
│ └── canvas/ # 图片画布编辑(圆/矩形选区,局部重绘)
|
||||||
├── views/ # 页面(home、login)
|
├── views/ # 页面(home、login)
|
||||||
├── utils/
|
├── utils/
|
||||||
│ ├── request.js # Axios 实例,拦截器处理 token 和不同服务的 baseURL 路由
|
│ ├── request.js # Axios 实例 + 拦截器:统一 Auth(不带 Bearer)+ 按前缀路由 baseURL
|
||||||
│ ├── websocket.js # 任务生成入口:HTTP POST 创建任务 + 20s 轮询获取结果
|
│ ├── websocket.js # 任务生成入口:组装参数 → 调用 API → 20s 轮询直至完成/失败
|
||||||
|
│ ├── modelApi.js # 模型业务层:localStorage 每日缓存 + 模型名称→UUID 查找
|
||||||
│ ├── createTask.js # 调用平台适配器 Playload() 构造任务 body
|
│ ├── createTask.js # 调用平台适配器 Playload() 构造任务 body
|
||||||
│ ├── modelConfig.js # 从远程 JSON 加载 workflow 配置,localStorage 每日缓存
|
│ ├── modelConfig.js # 从远程 JSON 加载 workflow 配置,localStorage 每日缓存
|
||||||
│ ├── modelApi.js # 从 /suanli/v1/platforms/:code/models 获取模型列表及 UUID
|
|
||||||
│ └── auth.ts # token 存取工具(localStorage)
|
│ └── auth.ts # token 存取工具(localStorage)
|
||||||
├── config/
|
├── config/
|
||||||
│ ├── index.js # 平台配置入口,导出 runninghub 适配器
|
│ ├── index.js # 平台配置入口,导出 runninghub 适配器
|
||||||
│ └── runninghub/ # RunningHub 平台适配器:Playload() 构造和 result() 解析
|
│ └── runninghub/ # RunningHub 平台适配器:Playload() 构造和 result() 解析
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### API 层设计原则
|
||||||
|
|
||||||
|
- `src/apis/` 中的函数只做**纯 HTTP 调用**(`service.get/post/delete` 等),不包含缓存、localStorage、业务判断等逻辑
|
||||||
|
- 缓存、数据转换等业务逻辑放在 `src/utils/` 中,调用 apis 层的原始函数
|
||||||
|
|
||||||
|
示例:`utils/modelApi.js` 导入 `apis/display` 的原始 `fetchPlatformModels`,在其上叠加 localStorage 每日缓存和 `getModelId` 查找逻辑。
|
||||||
|
|
||||||
### 核心数据流
|
### 核心数据流
|
||||||
|
|
||||||
1. 用户在 `dialogBox` 中设置参数(模型、提示词、比例、上传图片等)
|
1. 用户在 `dialogBox` 中设置参数(模型、提示词、比例、上传图片等)
|
||||||
2. 点击生成 → `dialogBox:handleStart()` 组装 data(含 modelId、params、imgs、request)
|
2. 点击生成 → `dialogBox:handleStart()` 组装 data(含 modelId、params、imgs、request)
|
||||||
3. 调用 `websocket.js:generate(data, generateData)`
|
3. 调用 `websocket.js:generate(data, generateData)`
|
||||||
4. `generate()` 内部先通过 `createTask(data)` → `runninghub.Playload()` 构造 RunningHub workflow payload 作为 body
|
4. `generate()` 内部先通过 `createTask(data)` → `runninghub.Playload()` 构造 RunningHub workflow payload 作为 body
|
||||||
5. 调用 `modelApi.getModelId(type, modelName)` 从 `/suanli/v1/platforms/:code/models` 查找模型 UUID(带 localStorage 每日缓存)
|
5. 调用 `modelApi.getModelId(type, modelName)` 查找模型 UUID(带 localStorage 每日缓存)
|
||||||
6. POST `/api/v1/tasks` 提交任务(`{ model_id, body, request }`),携带 `X-Session-Id` 用于预扣费
|
6. 调用 `requestCreateTask(body, sessionId)` → POST `/suanli/v1/tasks`(`{ model_id, body, request }`,携带 `X-Session-Id` 用于预扣费)
|
||||||
7. 返回 task_id → `displayStore.addGeneratingItem()` 在前端列表插入"生成中"条目
|
7. 返回 task_id → `displayStore.addGeneratingItem()` 在前端列表插入"生成中"条目
|
||||||
8. 每 20 秒轮询 GET `/api/v1/tasks/{task_id}`,completed 时调用 `updateItemToSuccess()` 更新列表
|
8. 每 20 秒轮询 `requestTaskStatus(taskId)` → GET `/suanli/v1/tasks/{task_id}`,completed 时调用 `updateItemToSuccess()` 更新列表
|
||||||
|
|
||||||
|
### 接口速查
|
||||||
|
|
||||||
|
| 函数 | 端点 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `requestCreateTask` | POST `/suanli/v1/tasks` | 创建生成任务 |
|
||||||
|
| `requestTaskStatus` | GET `/suanli/v1/tasks/:id` | 查询单个任务状态 |
|
||||||
|
| `requestTaskHistory` | GET `/suanli/v1/tasks/history` | 历史任务列表(支持 `user_id`/`platform_code`/`page`/`pageSize`) |
|
||||||
|
| `fetchPlatformModels` | GET `/suanli/v1/platforms/:code/models` | 获取平台模型列表 |
|
||||||
|
| `cancelOrCollect` | POST `/collect/toggle` | 收藏/取消收藏 |
|
||||||
|
| `deleteGenerateHistory` | DELETE `/taskRecordHistory/delete` | 删除历史记录 |
|
||||||
|
|
||||||
|
### 请求拦截器路由
|
||||||
|
|
||||||
|
拦截器统一设置 `Authorization: <token>`(不带 Bearer 前缀),根据 URL 前缀切换后端:
|
||||||
|
|
||||||
|
| URL 前缀 | 环境变量 | 示例 target |
|
||||||
|
|----------|----------|-------------|
|
||||||
|
| `/suanli` | `VITE_API_TASK_TARGET` | `http://test.xueai.art` |
|
||||||
|
| `/pay` | `VITE_API_PAY_TARGET` | `http://test.xueai.art` |
|
||||||
|
| 其他 | `VITE_API_BASE_URL`(默认) | `http://test.xueai.art/newapi/api` |
|
||||||
|
|
||||||
|
### 响应格式兼容
|
||||||
|
|
||||||
|
响应拦截器同时识别 `code === 0` 和 `status === 0` 两种成功状态。用户信息接口兼容新旧两种格式:`data.userInfo` 嵌套(新)和 `data` 扁平(旧),在 store 的 `getInfo()` 中做了 `const u = res.data.userInfo || res.data` 的兼容处理。
|
||||||
|
|
||||||
### 平台编码映射
|
### 平台编码映射
|
||||||
|
|
||||||
@ -63,28 +97,14 @@ src/
|
|||||||
| Painting | `ai_painting_talk` |
|
| Painting | `ai_painting_talk` |
|
||||||
| Video | `ai_video_talk` |
|
| Video | `ai_video_talk` |
|
||||||
|
|
||||||
|
映射函数 `getPlatformCode()` 位于 `utils/modelApi.js`。
|
||||||
|
|
||||||
### 自动导入
|
### 自动导入
|
||||||
|
|
||||||
- `unplugin-auto-import`:自动导入 Vue/Router/Pinia API,`.vue` 中无需手动 import
|
- `unplugin-auto-import`:自动导入 Vue/Router/Pinia API,`.vue` 中无需手动 import
|
||||||
- `unplugin-vue-components`:自动注册 `src/components/` 下的组件和 Element Plus 组件
|
- `unplugin-vue-components`:自动注册 `src/components/` 下的组件和 Element Plus 组件
|
||||||
- Element Plus 图标通过 `unplugin-icons` 按需加载
|
- Element Plus 图标通过 `unplugin-icons` 按需加载
|
||||||
|
|
||||||
### 环境变量
|
|
||||||
|
|
||||||
`VITE_API_BASE_URL` 定义主 API 地址(含 `/api` 后缀)。请求拦截器根据 URL 前缀自动切换后端服务:
|
|
||||||
|
|
||||||
| 前缀 | 用途 |
|
|
||||||
|------|------|
|
|
||||||
| `/pay` | 支付服务 |
|
|
||||||
| `/api`(默认) | 主服务(含任务创建 `/api/v1/tasks`) |
|
|
||||||
|
|
||||||
新增的 `/suanli` 接口使用 `VITE_API_BASE_URL` 去掉 `/api` 后缀作为基础 URL。
|
|
||||||
|
|
||||||
### 路由守卫
|
### 路由守卫
|
||||||
|
|
||||||
`src/router/index.js` 的 `beforeEach` 守卫检查 token 存在性和有效性(调用 `/auth/check/token`),无效则跳转 `/login`。支持通过 URL query `?token=xxx` 传入 token。
|
`src/router/index.js` 的 `beforeEach` 守卫检查 token 存在性和有效性(调用 `POST /login/validateToken`),无效则跳转 `/login`。支持通过 URL query `?token=xxx` 传入 token。
|
||||||
|
|
||||||
### Authorization 头
|
|
||||||
|
|
||||||
- 通过 axios 的请求(auth/display 等):拦截器自动加 `Bearer` 前缀
|
|
||||||
- 通过 fetch 的请求(任务创建/轮询/模型列表):直接传 token,**不加** `Bearer` 前缀
|
|
||||||
|
|||||||
3
components.d.ts
vendored
3
components.d.ts
vendored
@ -11,7 +11,10 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
2: typeof import('./src/components/virtual-scroller/VirtualScroller copy 2.vue')['default']
|
||||||
|
3: typeof import('./src/components/virtual-scroller/VirtualScroller copy 3.vue')['default']
|
||||||
Canvas: typeof import('./src/components/canvas/index.vue')['default']
|
Canvas: typeof import('./src/components/canvas/index.vue')['default']
|
||||||
|
copy: typeof import('./src/components/virtual-scroller/VirtualScroller copy.vue')['default']
|
||||||
DialogBox: typeof import('./src/components/dialogBox/index.vue')['default']
|
DialogBox: typeof import('./src/components/dialogBox/index.vue')['default']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
|
|||||||
@ -38,14 +38,10 @@ export function logout() {
|
|||||||
|
|
||||||
/** @desc 获取用户信息 */
|
/** @desc 获取用户信息 */
|
||||||
export const getUserInfo = () => {
|
export const getUserInfo = () => {
|
||||||
return service.get(`${BASE_URL}/user/info`)
|
return service.get(`/sysUser/currentUser
|
||||||
}
|
`)
|
||||||
|
|
||||||
/** @desc 获取路由信息 */
|
|
||||||
export const getUserRoute = () => {
|
|
||||||
return service.get(`${BASE_URL}/route`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkUsertoken = () => {
|
export const checkUsertoken = () => {
|
||||||
return service.get(`${BASE_URL}/check/token`)
|
return service.post(`/login/validateToken`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import service from '@/utils/request'
|
import service from '@/utils/request'
|
||||||
|
|
||||||
// 获取生成历史列表
|
// ==================== 历史记录 API(axios) ====================
|
||||||
export function getGenerateHistoryList(query) {
|
|
||||||
return service.get('/taskRecordHistory', { params: query })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消或收藏
|
// 取消或收藏
|
||||||
export function cancelOrCollect(query) {
|
export function cancelOrCollect(query) {
|
||||||
@ -15,7 +12,28 @@ export function deleteGenerateHistory(query) {
|
|||||||
return service.delete('/taskRecordHistory/delete', { params: query })
|
return service.delete('/taskRecordHistory/delete', { params: query })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取免费次数
|
// ==================== 任务 API(axios,经由 /suanli 前缀路由到算力调度后端) ====================
|
||||||
export function getFreeTimes(id) {
|
|
||||||
return service.get('/plantformBalance/userBalances', { params: { id } })
|
// 创建生成任务(HTTP POST /suanli/v1/tasks)
|
||||||
}
|
export function requestCreateTask(body, sessionId) {
|
||||||
|
return service.post('/suanli/v1/tasks', body, {
|
||||||
|
headers: { 'X-Session-Id': sessionId }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询任务状态 / 获取历史任务结果(HTTP GET /suanli/v1/tasks/:id)
|
||||||
|
export function requestTaskStatus(taskId) {
|
||||||
|
return service.get(`/suanli/v1/tasks/${taskId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取历史任务列表(HTTP GET /suanli/v1/tasks/history,支持平台筛选和分页)
|
||||||
|
export function requestTaskHistory(params) {
|
||||||
|
return service.get('/suanli/v1/tasks/history', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 平台模型 API ====================
|
||||||
|
|
||||||
|
// 获取平台模型列表(原始 HTTP 调用,不含缓存逻辑)
|
||||||
|
export function fetchPlatformModels(code) {
|
||||||
|
return service.get(`/suanli/v1/platforms/${code}/models`)
|
||||||
|
}
|
||||||
|
|||||||
@ -161,7 +161,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { generate } from '@/utils/websocket'
|
import { generate } from '@/utils/websocket'
|
||||||
import { useDisplayStore, useUserStore } from '@/stores'
|
import { useDisplayStore } from '@/stores'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
import { getModelId } from '@/utils/modelApi'
|
import { getModelId } from '@/utils/modelApi'
|
||||||
|
|
||||||
@ -742,7 +742,6 @@ const handleSend = async () => {
|
|||||||
modelName: 'GPT',
|
modelName: 'GPT',
|
||||||
modelId,
|
modelId,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
free: useUserStore().freeTimes,
|
|
||||||
params: [
|
params: [
|
||||||
{ name: 'prompt', data: inputText.value + '并且去除掉图1中的框' },
|
{ name: 'prompt', data: inputText.value + '并且去除掉图1中的框' },
|
||||||
{ name: 'index', data: 1 },
|
{ name: 'index', data: 1 },
|
||||||
|
|||||||
@ -83,7 +83,7 @@ import ImageUploader from './imageUploader/index.vue'
|
|||||||
import VideoImageUploader from './videoImageUploader/index.vue'
|
import VideoImageUploader from './videoImageUploader/index.vue'
|
||||||
import Time from './Time/index.vue'
|
import Time from './Time/index.vue'
|
||||||
import { Sender } from 'vue-element-plus-x'
|
import { Sender } from 'vue-element-plus-x'
|
||||||
import { useDisplayStore, useUserStore } from '@/stores'
|
import { useDisplayStore } from '@/stores'
|
||||||
import { generate } from '@/utils/websocket'
|
import { generate } from '@/utils/websocket'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { fetchModelConfig } from '@/utils/modelConfig'
|
import { fetchModelConfig } from '@/utils/modelConfig'
|
||||||
@ -106,8 +106,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const useDisplay = useDisplayStore()
|
const useDisplay = useDisplayStore()
|
||||||
const useUser = useUserStore()
|
|
||||||
|
|
||||||
const isgerenate = ref(false)
|
const isgerenate = ref(false)
|
||||||
|
|
||||||
const model = ref() // 模型
|
const model = ref() // 模型
|
||||||
@ -234,7 +232,6 @@ const handleStart = async () => {
|
|||||||
modelName: model.value,
|
modelName: model.value,
|
||||||
modelId: modelId || modelDisplayConfig.value?.modelId || '',
|
modelId: modelId || modelDisplayConfig.value?.modelId || '',
|
||||||
quantity: quantity.value,
|
quantity: quantity.value,
|
||||||
free: useUser.freeTimes,
|
|
||||||
params: [
|
params: [
|
||||||
{ name: 'prompt', data: prompt.value},
|
{ name: 'prompt', data: prompt.value},
|
||||||
{ name: 'quantity', data: quantity.value},
|
{ name: 'quantity', data: quantity.value},
|
||||||
@ -301,16 +298,8 @@ watch(() => props.type, (newType) => {
|
|||||||
} else {
|
} else {
|
||||||
model.value = 'flux'
|
model.value = 'flux'
|
||||||
}
|
}
|
||||||
const chargeType = newType === 'Painting' ? 1 : 4
|
|
||||||
useUser.fetchFreeTimes(chargeType)
|
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
// 组件挂载时获取免费次数
|
|
||||||
onMounted(async () => {
|
|
||||||
const chargeType = props.type === 'Painting' ? 1 : 4
|
|
||||||
await useUser.fetchFreeTimes(chargeType)
|
|
||||||
console.log('免费次数', useUser.freeTimes)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import {
|
|||||||
getUserInfo as getUserInfoApi,
|
getUserInfo as getUserInfoApi,
|
||||||
logout as logoutApi
|
logout as logoutApi
|
||||||
} from '@/apis/auth'
|
} from '@/apis/auth'
|
||||||
import { getFreeTimes } from '@/apis/display'
|
|
||||||
import { clearToken, getToken, setToken } from '@/utils/auth'
|
import { clearToken, getToken, setToken } from '@/utils/auth'
|
||||||
|
|
||||||
const storeSetup = () => {
|
const storeSetup = () => {
|
||||||
@ -34,7 +33,6 @@ const storeSetup = () => {
|
|||||||
|
|
||||||
const dept = ref({}) // 当前用户所在部门集合
|
const dept = ref({}) // 当前用户所在部门集合
|
||||||
const isLogin = ref(false)
|
const isLogin = ref(false)
|
||||||
const freeTimes = ref(0) // 免费次数
|
|
||||||
|
|
||||||
// 重置token
|
// 重置token
|
||||||
const resetToken = () => {
|
const resetToken = () => {
|
||||||
@ -45,49 +43,40 @@ const storeSetup = () => {
|
|||||||
// 检查token有效性
|
// 检查token有效性
|
||||||
const checkTokenValid = async () => {
|
const checkTokenValid = async () => {
|
||||||
const res = await checkUsertokenApi()
|
const res = await checkUsertokenApi()
|
||||||
console.log('checkTokenValid:', res) // 打印响应数据以进行调试
|
console.log('checkTokenValid:', res)
|
||||||
if (res.code === '401' || res.success === false) {
|
if (res.code === '401' || res.status === '401' || res.success === false) {
|
||||||
// 检查响应数据是否存在,以避免空响应导致的错误
|
console.error('Token is invalid:', res.message)
|
||||||
console.error('Token is invalid:', res.message)// 打印错误信息以进行调试
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
console.log('Token is valid') // 打印成功信息以进行调试
|
console.log('Token is valid')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
const getInfo = async () => {
|
const getInfo = async () => {
|
||||||
const res = await getUserInfoApi()
|
const res = await getUserInfoApi()
|
||||||
Object.assign(userInfo, res.data)
|
// 兼容新旧格式:新格式 data.userInfo 嵌套,旧格式 data 扁平
|
||||||
// userInfo.avatar = getAvatar(res.data.avatar, res.data.gender)
|
const u = res.data.userInfo || res.data
|
||||||
userInfo.username = res.data.username
|
Object.assign(userInfo, u)
|
||||||
if (typeof res.data.routers === 'string' && res.data.routers.trim() !== '') {
|
userInfo.id = u.userId || u.id
|
||||||
userInfo.routers = res.data.routers.split(',').map((item) => item.trim()) // 补充trim处理更完善
|
userInfo.username = u.userName || u.username
|
||||||
|
if (typeof u.routers === 'string' && u.routers.trim() !== '') {
|
||||||
|
userInfo.routers = u.routers.split(',').map((item) => item.trim())
|
||||||
} else {
|
} else {
|
||||||
userInfo.routers = []
|
userInfo.routers = []
|
||||||
}
|
}
|
||||||
if (res.data.roles && res.data.roles.length) {
|
// 角色和权限在 data 层级(非 userInfo 内)
|
||||||
roles.value = res.data.roles
|
const roleList = res.data.roles || u.roles
|
||||||
permissions.value = res.data.permissions
|
if (roleList?.length) {
|
||||||
|
roles.value = roleList
|
||||||
|
permissions.value = res.data.permissions || u.permissions || []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取免费次数
|
|
||||||
const fetchFreeTimes = async (chargeType = 1) => {
|
|
||||||
if (userInfo.id) {
|
|
||||||
const res = await getFreeTimes(userInfo.id)
|
|
||||||
const balanceList = res.data || []
|
|
||||||
const target = balanceList.find((item) => item.chargeType === chargeType)
|
|
||||||
freeTimes.value = target?.balance || 0
|
|
||||||
return freeTimes.value
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
const accountLogin = async (req) => {
|
const accountLogin = async (req) => {
|
||||||
const res = await accountLoginApi(req)
|
const res = await accountLoginApi(req)
|
||||||
if (res.data == null || res.code === '500' || res.success === false) {
|
if (res.data == null || res.code === '500' || res.status === 500 || res.success === false) {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
ElMessage({
|
ElMessage({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
@ -134,14 +123,12 @@ const storeSetup = () => {
|
|||||||
dept,
|
dept,
|
||||||
username,
|
username,
|
||||||
isLogin,
|
isLogin,
|
||||||
freeTimes,
|
|
||||||
accountLogin,
|
accountLogin,
|
||||||
logout,
|
logout,
|
||||||
logoutCallBack,
|
logoutCallBack,
|
||||||
getInfo,
|
getInfo,
|
||||||
resetToken,
|
resetToken,
|
||||||
checkTokenValid,
|
checkTokenValid
|
||||||
fetchFreeTimes
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { getToken } from '@/utils/auth'
|
import { fetchPlatformModels as fetchModelsRaw } from '@/apis/display'
|
||||||
|
|
||||||
const CACHE_PREFIX = 'platform_models_'
|
const CACHE_PREFIX = 'platform_models_'
|
||||||
|
|
||||||
@ -53,13 +53,7 @@ export function getPlatformCode(type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// suanli 接口的基础 URL(不带 /api 后缀)
|
// 获取平台模型列表(带 localStorage 每日缓存)
|
||||||
function getSuanliBaseUrl() {
|
|
||||||
const apiBase = import.meta.env.VITE_API_BASE_URL || ''
|
|
||||||
return apiBase.replace(/\/api$/, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取平台模型列表
|
|
||||||
export async function fetchPlatformModels(code) {
|
export async function fetchPlatformModels(code) {
|
||||||
const cached = getFromCache(code)
|
const cached = getFromCache(code)
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@ -68,19 +62,7 @@ export async function fetchPlatformModels(code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const token = getToken()
|
const result = await fetchModelsRaw(code)
|
||||||
const baseUrl = getSuanliBaseUrl()
|
|
||||||
const url = `${baseUrl}/suanli/v1/platforms/${code}/models`
|
|
||||||
|
|
||||||
console.log(`从远程获取平台模型列表: ${url}`)
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': token
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await response.json()
|
|
||||||
|
|
||||||
if (result.code === 0 && result.data?.models) {
|
if (result.code === 0 && result.data?.models) {
|
||||||
saveToCache(code, result.data.models)
|
saveToCache(code, result.data.models)
|
||||||
|
|||||||
@ -29,21 +29,19 @@ const StatusCodeMessage = {
|
|||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const token = getToken()
|
const token = getToken()
|
||||||
if (token) {
|
if (!config.headers) {
|
||||||
if (!config.headers) {
|
config.headers = {}
|
||||||
config.headers = {}
|
|
||||||
}
|
|
||||||
config.headers.Authorization = `Bearer ${token}`
|
|
||||||
}
|
}
|
||||||
// console.log(config.baseURL)
|
|
||||||
if (config.url?.startsWith(import.meta.env.VITE_API_PAY_PREFIX)) { // 支付服务路由
|
// 统一 Auth 头不带 Bearer 前缀
|
||||||
|
if (token) config.headers.Authorization = token
|
||||||
|
|
||||||
|
if (config.url?.startsWith(import.meta.env.VITE_API_TASK_PREFIX)) { // 算力调度后端
|
||||||
|
config.baseURL = import.meta.env.VITE_API_TASK_TARGET
|
||||||
|
} else if (config.url?.startsWith(import.meta.env.VITE_API_PAY_PREFIX)) { // 支付服务路由
|
||||||
config.baseURL = import.meta.env.VITE_API_PAY_TARGET
|
config.baseURL = import.meta.env.VITE_API_PAY_TARGET
|
||||||
} else if (config.url?.startsWith(import.meta.env.VITE_API_AIGC_PREFIX)) { // 资源服务路由
|
} else if (config.url?.startsWith(import.meta.env.VITE_API_AIGC_PREFIX)) { // 资源服务路由
|
||||||
// config.url = config.url.replace(import.meta.env.VITE_API_AIGC_PREFIX, '')
|
|
||||||
config.baseURL = import.meta.env.VITE_API_AIGC_TARGET
|
config.baseURL = import.meta.env.VITE_API_AIGC_TARGET
|
||||||
} else if (config.url?.startsWith(import.meta.env.VITE_API_MUSIC_WORKFLOW_PREFIX)) { // 音频生成平台工作流服务路由
|
|
||||||
config.url = config.url.replace(import.meta.env.VITE_API_MUSIC_WORKFLOW_PREFIX, '')
|
|
||||||
config.baseURL = import.meta.env.VITE_API_MUSIC_WORKFLOW_TARGET
|
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
@ -57,11 +55,11 @@ service.interceptors.request.use(
|
|||||||
service.interceptors.response.use(
|
service.interceptors.response.use(
|
||||||
(response) => {
|
(response) => {
|
||||||
const { data } = response
|
const { data } = response
|
||||||
const { success, code, msg } = data
|
const { success, code, status, msg, message } = data
|
||||||
if (success || code === 0) {
|
if (success || code === 0 || status === 0) {
|
||||||
console.log('msg: \n', msg)
|
console.log('msg: \n', msg)
|
||||||
return response.data
|
return response.data
|
||||||
} else if (code === 401 && response.config.url !== '/auth/check/token`') { // 判断code=401时进行页面刷新,但是不对检验token这个路由的请求判断,防止出现死循环
|
} else if (code === 401 && response.config.url !== '/login/validateToken`') { // 判断code=401时进行页面刷新,但是不对检验token这个路由的请求判断,防止出现死循环
|
||||||
userError()
|
userError()
|
||||||
}
|
}
|
||||||
console.log('CodeMessage: \n', StatusCodeMessage[code])
|
console.log('CodeMessage: \n', StatusCodeMessage[code])
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { ElNotification } from 'element-plus'
|
import { ElNotification } from 'element-plus'
|
||||||
import { h } from 'vue'
|
import { h } from 'vue'
|
||||||
import { useDisplayStore, useUserStore } from '@/stores'
|
import { useDisplayStore } from '@/stores'
|
||||||
import { getToken } from '@/utils/auth'
|
|
||||||
import { createTask } from '@/utils/createTask'
|
import { createTask } from '@/utils/createTask'
|
||||||
import { userError } from '@/utils/tokenError'
|
import { userError } from '@/utils/tokenError'
|
||||||
|
import { requestCreateTask, requestTaskStatus } from '@/apis/display'
|
||||||
|
|
||||||
export function getChargeType(chargeType) {
|
export function getChargeType(chargeType) {
|
||||||
switch (chargeType) {
|
switch (chargeType) {
|
||||||
@ -63,8 +63,6 @@ const activePollIntervals = new Set()
|
|||||||
|
|
||||||
export async function generate(data, generateData) {
|
export async function generate(data, generateData) {
|
||||||
const useDisplay = useDisplayStore()
|
const useDisplay = useDisplayStore()
|
||||||
const token = getToken()
|
|
||||||
const baseUrl = import.meta.env.VITE_API_BASE_URL
|
|
||||||
let taskId = null
|
let taskId = null
|
||||||
let pollInterval = null
|
let pollInterval = null
|
||||||
|
|
||||||
@ -94,17 +92,7 @@ export async function generate(data, generateData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// POST 创建任务
|
// POST 创建任务
|
||||||
const createResponse = await fetch(`${baseUrl}/v1/tasks`, {
|
const createResult = await requestCreateTask(requestBody, sessionId)
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': token,
|
|
||||||
'X-Session-Id': sessionId
|
|
||||||
},
|
|
||||||
body: JSON.stringify(requestBody)
|
|
||||||
})
|
|
||||||
|
|
||||||
const createResult = await createResponse.json()
|
|
||||||
|
|
||||||
if (createResult.code !== 0) {
|
if (createResult.code !== 0) {
|
||||||
ElNotification({
|
ElNotification({
|
||||||
@ -131,14 +119,7 @@ export async function generate(data, generateData) {
|
|||||||
// 轮询任务状态
|
// 轮询任务状态
|
||||||
const pollTask = async () => {
|
const pollTask = async () => {
|
||||||
try {
|
try {
|
||||||
const pollResponse = await fetch(`${baseUrl}/v1/tasks/${taskId}`, {
|
const pollResult = await requestTaskStatus(taskId)
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': token
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const pollResult = await pollResponse.json()
|
|
||||||
|
|
||||||
if (pollResult.code !== 0) return
|
if (pollResult.code !== 0) return
|
||||||
|
|
||||||
@ -153,7 +134,6 @@ export async function generate(data, generateData) {
|
|||||||
const urls = taskData.outputs?.images?.map(img => img.url) || []
|
const urls = taskData.outputs?.images?.map(img => img.url) || []
|
||||||
if (urls.length > 0) {
|
if (urls.length > 0) {
|
||||||
useDisplay.updateItemToSuccess(taskId, urls)
|
useDisplay.updateItemToSuccess(taskId, urls)
|
||||||
if (useUserStore().freeTimes) await useUserStore().fetchFreeTimes()
|
|
||||||
websocketSuccess()
|
websocketSuccess()
|
||||||
} else {
|
} else {
|
||||||
websocketError(4403, '未获取到生成结果')
|
websocketError(4403, '未获取到生成结果')
|
||||||
|
|||||||
@ -73,9 +73,10 @@ import RefreshOverlay from './components/RefreshOverlay.vue'
|
|||||||
import Select from '@/components/Select/index.vue'
|
import Select from '@/components/Select/index.vue'
|
||||||
import { VirtualScroller } from '@/components/virtual-scroller'
|
import { VirtualScroller } from '@/components/virtual-scroller'
|
||||||
import Canvas from '@/components/canvas/index.vue'
|
import Canvas from '@/components/canvas/index.vue'
|
||||||
import { getGenerateHistoryList } from '@/apis/display'
|
import { requestTaskHistory } from '@/apis/display'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { getChargeType } from '@/utils/websocket'
|
import { getChargeType } from '@/utils/websocket'
|
||||||
|
import { getPlatformCode } from '@/utils/modelApi'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
if: {
|
if: {
|
||||||
@ -163,12 +164,11 @@ const fetchHistory = async (isLoadMore = false) => {
|
|||||||
try {
|
try {
|
||||||
const pageToFetch = isLoadMore ? currentPage.value + 1 : 1
|
const pageToFetch = isLoadMore ? currentPage.value + 1 : 1
|
||||||
|
|
||||||
const result = await getGenerateHistoryList({
|
const result = await requestTaskHistory({
|
||||||
userId: userStore.userInfo.id,
|
user_id: userStore.userInfo.id,
|
||||||
chargeType: chargeType.value,
|
platform_code: getPlatformCode(props.type),
|
||||||
page: pageToFetch,
|
page: pageToFetch,
|
||||||
size: 10,
|
pageSize: 10
|
||||||
sort: 'createTime,desc'
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const dataList = result.data?.list || result.data || []
|
const dataList = result.data?.list || result.data || []
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user