feat: 功能

This commit is contained in:
2025-11-12 22:45:29 +08:00
parent 94c114a44d
commit fc7d2ccea5
41 changed files with 2406 additions and 343 deletions

View File

@@ -0,0 +1,176 @@
# 公共 Hooks 模块
此目录包含可在 monorepo 各个应用中复用的 Vue Composition API Hooks。
## 📁 目录结构
```
hooks/web/
├── useCache.js # 缓存管理 Hook
├── useUserInfo.js # 用户信息获取 Hook
└── useVoiceText.ts # 语音文本处理 Hook已抽离为公共模块
```
## 🚀 使用方式
### useUserInfo Hook
获取用户信息的公共 Hook可在各个应用中复用。
#### 基础用法
```javascript
import { useUserInfo } from '@gold/hooks/web/useUserInfo'
import { getToken } from '@/utils/token-manager'
// 在组件中使用
const { fetchUserInfo, loading, error, userInfo } = useUserInfo({
getToken, // 传入获取 token 的函数
})
// 获取用户信息
await fetchUserInfo()
```
#### 便捷函数
如果只需要获取一次用户信息,可以使用便捷函数:
```javascript
import { getUserInfo } from '@gold/hooks/web/useUserInfo'
import { getToken } from '@/utils/token-manager'
// 直接获取用户信息
const userInfo = await getUserInfo({
getToken,
})
```
#### 自定义配置
```javascript
const { fetchUserInfo } = useUserInfo({
baseUrl: '/custom-api/member', // 自定义 API 基础 URL
getToken: () => {
// 自定义获取 token 的逻辑
return localStorage.getItem('token')
},
})
```
## 📝 API 说明
### useUserInfo(options)
**参数:**
- `options.baseUrl` (string, 可选): API 基础 URL默认使用 `API_BASE.APP_MEMBER`
- `options.getToken` (Function, 可选): 获取 token 的函数
**返回值:**
- `fetchUserInfo()` (Function): 获取用户信息的异步函数
- `loading` (Ref<boolean>): 加载状态
- `error` (Ref<Error | null>): 错误信息
- `userInfo` (Ref<Object | null>): 用户信息数据
### getUserInfo(options)
便捷函数,直接返回用户信息对象。
**参数:**`useUserInfo`
**返回值:** `Promise<Object>` 用户信息对象
## 🔄 在 Store 中使用
```javascript
import { defineStore } from 'pinia'
import { getUserInfo } from '@gold/hooks/web/useUserInfo'
import { getToken } from '@/utils/token-manager'
export const useUserStore = defineStore('user', () => {
const userInfo = ref(null)
async function fetchUserInfo() {
try {
const data = await getUserInfo({ getToken })
userInfo.value = data
} catch (error) {
console.error('获取用户信息失败:', error)
}
}
return { userInfo, fetchUserInfo }
})
```
### useVoiceText Hook
语音文本转换 Hook将音频文件转换为文本转录。
#### 初始化(在应用启动时)
```javascript
// 在应用的 API 服务文件中(如 common.js
import { createApiService } from '@gold/config/api/services'
import { setApiService } from '@gold/hooks/web/useVoiceText'
import http from '@/api/http'
import { getAuthHeader } from '@/utils/token-manager'
import { API_BASE } from '@gold/config/api'
// 创建 API 服务实例
const apiService = createApiService({
http,
getAuthHeader,
baseUrl: API_BASE.TIKHUB_APP,
})
// 设置全局 API 服务(供 useVoiceText hook 使用)
setApiService(apiService)
```
#### 使用方式
```javascript
import useVoiceText from '@gold/hooks/web/useVoiceText'
import type { AudioItem } from '@gold/config/types'
// 在组件中使用
const { getVoiceText } = useVoiceText()
const audioList: AudioItem[] = [
{ audio_url: 'https://example.com/audio.mp3' }
]
const transcriptions = await getVoiceText(audioList)
// transcriptions: [{ key: 'url', value: 'transcribed text', audio_url: '...' }]
```
#### 类型定义
```typescript
import type {
AudioItem,
TranscriptionResult
} from '@gold/config/types'
```
## 📦 依赖
- `vue`: Vue 3 Composition API
- `axios`: HTTP 请求库(用于 useUserInfo
- `@gold/config/api`: 公共 API 配置
- `@gold/config/api/services`: 公共 API 服务创建器
- `@gold/config/types`: 公共类型定义
## 🔧 配置要求
确保在应用的 `vite.config.js` 中配置了 `@gold` 别名:
```javascript
resolve: {
alias: {
'@gold': fileURLToPath(new URL('../../', import.meta.url))
}
}
```

View File

@@ -9,6 +9,9 @@ export const CACHE_KEY = {
ROLE_ROUTERS: 'roleRouters',
USER: 'user',
VisitTenantId: 'visitTenantId',
// Token 相关
ACCESS_TOKEN: 'ACCESS_TOKEN',
REFRESH_TOKEN: 'REFRESH_TOKEN',
// 系统设置
IS_DARK: 'isDark',
LANG: 'lang',
@@ -37,3 +40,29 @@ export const deleteUserCache = () => {
wsCache.delete(CACHE_KEY.VisitTenantId)
// 注意,不要清理 LoginForm 登录表单
}
/**
* 删除所有 token 缓存
* 清空 wsCache 中所有可能的 token 键名变体
*/
export const deleteTokenCache = () => {
try {
const { wsCache } = useCache()
// 删除所有可能的 token 键名变体(大小写不同)
const tokenKeys = [
CACHE_KEY.ACCESS_TOKEN,
'access_token',
CACHE_KEY.REFRESH_TOKEN,
'refresh_token'
]
tokenKeys.forEach(key => {
try {
wsCache.delete(key)
} catch (e) {
// 忽略单个键删除失败
}
})
} catch (e) {
console.warn('删除 token 缓存失败:', e)
}
}

View File

@@ -0,0 +1,150 @@
/**
* 用户信息 Hook
* 封装获取用户信息的逻辑,可在各个应用中复用
*
* 使用方式:
* import { useUserInfo } from '@gold/hooks/web/useUserInfo'
*
* const { fetchUserInfo, loading, error } = useUserInfo()
* await fetchUserInfo()
*/
import { ref } from 'vue'
import axios from 'axios'
import { API_BASE } from '@gold/config/api'
// 获取 token 的工具函数(需要从应用层传入或使用全局配置)
let getTokenFn = null
/**
* 设置获取 token 的函数
* @param {Function} fn - 获取 token 的函数
*/
export function setTokenGetter(fn) {
getTokenFn = fn
}
/**
* 获取 Authorization Header
* @returns {string}
*/
function getAuthHeader() {
if (getTokenFn) {
const token = getTokenFn()
return token ? `Bearer ${token}` : ''
}
// 如果没有设置 token getter尝试从常见位置获取
try {
// 尝试从 sessionStorage 获取
const manualToken = sessionStorage.getItem('DEV_MANUAL_TOKEN')
if (manualToken) {
return `Bearer ${manualToken}`
}
} catch (e) {
// 忽略错误
}
return ''
}
/**
* 用户信息 Hook
* @param {Object} options - 配置选项
* @param {string} options.baseUrl - API 基础 URL可选默认使用 APP_MEMBER
* @param {Function} options.getToken - 获取 token 的函数(可选)
* @returns {Object} { fetchUserInfo, loading, error, userInfo }
*/
export function useUserInfo(options = {}) {
const loading = ref(false)
const error = ref(null)
const userInfo = ref(null)
// 如果传入了 getToken 函数,设置它
if (options.getToken) {
setTokenGetter(options.getToken)
}
// 确定 API 基础路径
const baseUrl = options.baseUrl || API_BASE.APP_MEMBER
const apiUrl = `${baseUrl}/user/get`
/**
* 获取用户信息
* @returns {Promise<Object>} 用户信息对象
*/
async function fetchUserInfo() {
loading.value = true
error.value = null
try {
const authHeader = getAuthHeader()
const headers = {
'Content-Type': 'application/json',
}
if (authHeader) {
headers.Authorization = authHeader
}
// 获取 tenant-id从环境变量或默认值
const tenantId =
(typeof import.meta !== 'undefined' && import.meta.env?.VITE_TENANT_ID) ||
(typeof process !== 'undefined' && process.env?.VITE_TENANT_ID) ||
'1'
if (tenantId) {
headers['tenant-id'] = tenantId
}
const response = await axios.get(apiUrl, { headers })
// 处理响应数据(根据后端返回格式调整)
// 后端通常返回 { code: 0, data: {...}, msg: '...' } 格式
let data = null
if (response.data) {
// 如果响应有 code 字段,说明是标准格式
if (typeof response.data.code === 'number') {
// code 为 0 或 200 表示成功
if (response.data.code === 0 || response.data.code === 200) {
data = response.data.data || response.data
} else {
throw new Error(response.data.msg || response.data.message || '获取用户信息失败')
}
} else {
// 没有 code 字段,直接使用 data
data = response.data.data || response.data
}
}
if (data) {
userInfo.value = data
return data
} else {
throw new Error('获取用户信息失败:响应数据为空')
}
} catch (err) {
error.value = err
console.error('获取用户信息失败:', err)
throw err
} finally {
loading.value = false
}
}
return {
fetchUserInfo,
loading,
error,
userInfo,
}
}
/**
* 便捷函数:直接获取用户信息(不返回响应式状态)
* @param {Object} options - 配置选项
* @returns {Promise<Object>} 用户信息对象
*/
export async function getUserInfo(options = {}) {
const { fetchUserInfo } = useUserInfo(options)
return await fetchUserInfo()
}

View File

@@ -1,26 +1,33 @@
import CommonService from '@/api/common'
import type { AudioItem, TranscriptionResult } from '@/src/types/global'
// 使用公共类型定义
import type {
AudioItem,
TranscriptionResult,
TranscriptionResponse,
TranscriptionData
} from '@gold/config/types'
/**
* 转录数据接口
* API 服务接口(需要从应用层注入)
*/
interface TranscriptionData {
file_url?: string
transcripts?: Array<{ text: string }>
interface ApiService {
videoToCharacters: (data: { fileLinkList: string[] }) => Promise<{ data: string }>
}
// 全局 API 服务实例(由应用层设置)
let apiServiceInstance: ApiService | null = null
/**
* 响应结果接口
* 设置 API 服务实例
* @param service - API 服务对象
*/
interface TranscriptionResponse {
results: Array<{
transcription_url: string
}>
export function setApiService(service: ApiService) {
apiServiceInstance = service
}
/**
* 将音频列表转换为文本转录
* @param list - 音频项列表
* @param apiService - API 服务实例(可选,如果已通过 setApiService 设置则不需要)
* @returns 转录结果数组
* @throws 当转录过程出错时抛出错误
*
@@ -29,9 +36,19 @@ interface TranscriptionResponse {
* const transcriptions = await getVoiceText(audioList)
* console.log(transcriptions) // [{ key: 'url', value: 'transcribed text' }]
*/
export async function getVoiceText(list: AudioItem[]): Promise<TranscriptionResult[]> {
export async function getVoiceText(
list: AudioItem[],
apiService?: ApiService
): Promise<TranscriptionResult[]> {
// 使用传入的 apiService 或全局实例
const service = apiService || apiServiceInstance
if (!service) {
throw new Error('getVoiceText: 需要提供 API 服务实例。请使用 setApiService() 设置或传入 apiService 参数')
}
// 调用API将视频转换为文本
const ret = await CommonService.videoToCharacters({
const ret = await service.videoToCharacters({
fileLinkList: list.map(item => item.audio_url),
})
@@ -68,19 +85,29 @@ export async function getVoiceText(list: AudioItem[]): Promise<TranscriptionResu
* Hook 返回值接口
*/
interface UseVoiceTextReturn {
getVoiceText: (list: AudioItem[]) => Promise<TranscriptionResult[]>
getVoiceText: (list: AudioItem[], apiService?: ApiService) => Promise<TranscriptionResult[]>
}
/**
* 语音文本转换 Hook
* @param apiService - API 服务实例(可选,如果已通过 setApiService 设置则不需要)
* @returns 包含 getVoiceText 方法的对象
*
* @example
* // 方式一:使用全局设置的 API 服务
* setApiService(myApiService)
* const { getVoiceText } = useVoiceText()
* const result = await getVoiceText(audioList)
*
* @example
* // 方式二:传入 API 服务实例
* const { getVoiceText } = useVoiceText()
* const result = await getVoiceText(audioList, myApiService)
*/
export default function useVoiceText(): UseVoiceTextReturn {
return { getVoiceText }
export default function useVoiceText(apiService?: ApiService): UseVoiceTextReturn {
return {
getVoiceText: (list: AudioItem[]) => getVoiceText(list, apiService)
}
}