mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 19:47:49 +08:00
refactor(api): 渠道与存储管理调用适配后端通用 manage 接口
- 新增 src/api/manage.ts 通用封装:ManageRequest(target+action+params) 与 manageNotificationChannel/manageStorage 两个调用入口 - 存储类调用全部改走 POST /storage/manage:用量、整理方式、 配置保存/重置、二维码与 OAuth 登录确认(smb/alist/rclone/u115/alipan) - ClawBot 状态查询、二维码刷新、登出与缓存迁移改走 POST /notification/manage,原 query 参数转入 body params - 同步更新 AccountSettingNotification 单测断言,prune 失效 lint 抑制项
This commit is contained in:
@@ -101,7 +101,7 @@
|
||||
},
|
||||
"src/components/cards/DirectoryCard.vue": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 3
|
||||
"count": 1
|
||||
},
|
||||
"sonarjs/super-linear-regex": {
|
||||
"count": 1
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { AxiosRequestConfig } from 'axios'
|
||||
import api from './index'
|
||||
|
||||
/** 通用管理请求:目标标识 + 管理动作 + 透传参数,与后端 ManageRequest 一致。 */
|
||||
export interface ManageRequest {
|
||||
target: string
|
||||
action: string
|
||||
params?: Record<string, unknown>
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用统一管理端点(通知渠道 / 网盘存储)
|
||||
*
|
||||
* 端点层不定义任何目标特定的名称与参数,
|
||||
* 目标标识、管理动作与表单参数原样透传给后端模块
|
||||
*/
|
||||
function manageTarget<T = Record<string, unknown>>(
|
||||
endpoint: 'notification/manage' | 'storage/manage',
|
||||
request: ManageRequest,
|
||||
config?: AxiosRequestConfig,
|
||||
): Promise<T> {
|
||||
return api.post<T>(endpoint, { params: {}, ...request }, config)
|
||||
}
|
||||
|
||||
/** 对指定通知渠道执行管理动作,返回响应中的业务数据。 */
|
||||
export function manageNotificationChannel<T = Record<string, unknown>>(
|
||||
channel: string,
|
||||
action: string,
|
||||
params: Record<string, unknown> = {},
|
||||
config?: AxiosRequestConfig,
|
||||
): Promise<T> {
|
||||
return manageTarget<T>('notification/manage', { target: channel, action, params }, config)
|
||||
}
|
||||
|
||||
/** 对指定网盘存储执行管理动作,返回响应中的业务数据。 */
|
||||
export function manageStorage<T = Record<string, unknown>>(
|
||||
storage: string,
|
||||
action: string,
|
||||
params: Record<string, unknown> = {},
|
||||
config?: AxiosRequestConfig,
|
||||
): Promise<T> {
|
||||
return manageTarget<T>('storage/manage', { target: storage, action, params }, config)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { StorageConf, TransferDirectoryConf } from '@/api/types'
|
||||
import api from '@/api'
|
||||
import { manageStorage } from '@/api/manage'
|
||||
import { nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { storageRemoteDict } from '@/api/constants'
|
||||
@@ -138,11 +138,17 @@ async function loadTransferTypeItems() {
|
||||
if (!props.directory.library_storage || !props.directory.storage) return
|
||||
try {
|
||||
// 下载器储存整理方法
|
||||
const storage_res = await api.get(`storage/transtype/${props.directory.storage}`)
|
||||
const storage_transtype = (storage_res as any).transtype
|
||||
const storage_res = await manageStorage<{ transtype?: Record<string, string> }>(
|
||||
props.directory.storage,
|
||||
'support_transtype',
|
||||
)
|
||||
const storage_transtype = storage_res?.transtype
|
||||
// 媒体库储存整理方法
|
||||
const library_storage_res = await api.get(`storage/transtype/${props.directory.library_storage}`)
|
||||
const library_storage_transtype = (library_storage_res as any).transtype
|
||||
const library_storage_res = await manageStorage<{ transtype?: Record<string, string> }>(
|
||||
props.directory.library_storage,
|
||||
'support_transtype',
|
||||
)
|
||||
const library_storage_transtype = library_storage_res?.transtype
|
||||
// 为空终止
|
||||
if (!library_storage_transtype || !storage_transtype) return
|
||||
// 取并集
|
||||
|
||||
@@ -9,7 +9,7 @@ import alist_png from '@images/misc/openlist.svg'
|
||||
import alistgo_png from '@images/misc/alist.svg'
|
||||
import custom_png from '@images/misc/database.png'
|
||||
import smb_png from '@images/misc/smb.png'
|
||||
import api from '@/api'
|
||||
import { manageStorage } from '@/api/manage'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -127,7 +127,7 @@ const usage = computed(() => {
|
||||
/** 查询存储空间使用信息。 */
|
||||
async function queryStorage() {
|
||||
try {
|
||||
const data: { total: number; available: number } = await api.get(`storage/usage/${props.storage.type}`)
|
||||
const data = await manageStorage<{ total: number; available: number }>(props.storage.type, 'usage')
|
||||
total.value = data.total
|
||||
available.value = data.available
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import { manageStorage } from '@/api/manage'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
@@ -33,7 +33,7 @@ async function handleDone() {
|
||||
// 重置配置
|
||||
async function handleReset() {
|
||||
try {
|
||||
await api.get(`/storage/reset/${props.type}`)
|
||||
await manageStorage(props.type, 'reset_config')
|
||||
// 重置成功
|
||||
handleDone()
|
||||
} catch (e) {
|
||||
@@ -64,7 +64,7 @@ const sourceItems = [
|
||||
// 保存alist设置
|
||||
async function savaAlistConfig() {
|
||||
try {
|
||||
await api.post(`storage/save/${props.type}`, props.conf)
|
||||
await manageStorage(props.type, 'save_config', { conf: props.conf })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import { manageStorage } from '@/api/manage'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
@@ -38,10 +38,15 @@ async function handleDone() {
|
||||
emit('done')
|
||||
}
|
||||
|
||||
// 调用/aliyun/qrcode api生成二维码
|
||||
// 调用存储统一管理接口生成二维码
|
||||
async function getQrcode() {
|
||||
try {
|
||||
const result = await api.get<{ codeUrl: string }>('/storage/qrcode/alipan', { feedback: 'silent' })
|
||||
const result = await manageStorage<{ codeUrl: string }>(
|
||||
'alipan',
|
||||
'generate_qrcode',
|
||||
{},
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
qrCodeUrl.value = result.codeUrl
|
||||
timeoutTimer = setTimeout(checkQrcode, 3000)
|
||||
} catch (e) {
|
||||
@@ -50,10 +55,15 @@ async function getQrcode() {
|
||||
}
|
||||
}
|
||||
|
||||
// 调用/aliyun/check api验证二维码
|
||||
// 调用存储统一管理接口验证二维码
|
||||
async function checkQrcode() {
|
||||
try {
|
||||
const result = await api.get<{ status: string; tip: string }>('/storage/check/alipan', { feedback: 'silent' })
|
||||
const result = await manageStorage<{ status: string; tip: string }>(
|
||||
'alipan',
|
||||
'check_login',
|
||||
{},
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
const qrCodeStatus = result.status
|
||||
text.value = result.tip
|
||||
if (qrCodeStatus == 'LoginSuccess') {
|
||||
@@ -79,7 +89,7 @@ async function checkQrcode() {
|
||||
// 重置配置
|
||||
async function handleReset() {
|
||||
try {
|
||||
await api.get<null>('/storage/reset/alipan', { feedback: 'silent' })
|
||||
await manageStorage('alipan', 'reset_config', {}, { feedback: 'silent' })
|
||||
// 重置成功
|
||||
alertType.value = 'success'
|
||||
handleDone()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import api from '@/api'
|
||||
import { manageNotificationChannel } from '@/api/manage'
|
||||
import { NotificationConf } from '@/api/types'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
@@ -280,10 +280,12 @@ async function fetchWechatClawBotStatus(options: WechatClawBotStatusFetchOptions
|
||||
wechatClawBotLoading.value = true
|
||||
}
|
||||
try {
|
||||
const result = await api.get<WechatClawBotStatus>('notification/wechatclawbot/status', {
|
||||
params: getWechatClawBotRequestParams({ auto_generate_qrcode: autoGenerateQrcode }),
|
||||
feedback: 'silent',
|
||||
})
|
||||
const result = await manageNotificationChannel<WechatClawBotStatus>(
|
||||
'WechatClawBot',
|
||||
'status',
|
||||
getWechatClawBotRequestParams({ auto_generate_qrcode: autoGenerateQrcode }),
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
wechatClawBotStatus.value = result
|
||||
await updateWechatClawBotQrImage(result)
|
||||
const status = (result.qrcode_status || '').toLowerCase()
|
||||
@@ -329,10 +331,12 @@ async function refreshWechatClawBotQrcode(options: WechatClawBotRefreshOptions =
|
||||
wechatClawBotActionLoading.value = true
|
||||
}
|
||||
try {
|
||||
const result = await api.post<WechatClawBotStatus>('notification/wechatclawbot/refresh', null, {
|
||||
params: getWechatClawBotRequestParams(),
|
||||
feedback: 'silent',
|
||||
})
|
||||
const result = await manageNotificationChannel<WechatClawBotStatus>(
|
||||
'WechatClawBot',
|
||||
'refresh_qrcode',
|
||||
getWechatClawBotRequestParams(),
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
wechatClawBotStatus.value = result
|
||||
await updateWechatClawBotQrImage(result)
|
||||
wechatClawBotExpiredRefreshAttempted.value = false
|
||||
@@ -359,10 +363,12 @@ async function logoutWechatClawBot() {
|
||||
}
|
||||
wechatClawBotActionLoading.value = true
|
||||
try {
|
||||
await api.post<null>('notification/wechatclawbot/logout', null, {
|
||||
params: getWechatClawBotRequestParams(),
|
||||
feedback: 'silent',
|
||||
})
|
||||
await manageNotificationChannel(
|
||||
'WechatClawBot',
|
||||
'logout',
|
||||
getWechatClawBotRequestParams(),
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
$toast.success(t('notification.wechatclawbot.logoutSuccess'))
|
||||
await fetchWechatClawBotStatus({
|
||||
autoGenerateQrcode: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import { manageStorage } from '@/api/manage'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
import { configureAceEditorPadding } from '@/utils/aceEditor'
|
||||
@@ -38,7 +38,7 @@ async function handleDone() {
|
||||
// 保存rclone设置
|
||||
async function savaRcloneConfig() {
|
||||
try {
|
||||
await api.post(`storage/save/rclone`, props.conf)
|
||||
await manageStorage('rclone', 'save_config', { conf: props.conf })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -47,7 +47,7 @@ async function savaRcloneConfig() {
|
||||
// 重置配置
|
||||
async function handleReset() {
|
||||
try {
|
||||
await api.get('/storage/reset/rclone')
|
||||
await manageStorage('rclone', 'reset_config')
|
||||
handleDone()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import { manageStorage } from '@/api/manage'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
@@ -29,7 +29,7 @@ async function handleDone() {
|
||||
// 重置配置
|
||||
async function handleReset() {
|
||||
try {
|
||||
await api.get('/storage/reset/smb')
|
||||
await manageStorage('smb', 'reset_config')
|
||||
// 重置成功
|
||||
handleDone()
|
||||
} catch (e) {
|
||||
@@ -40,7 +40,7 @@ async function handleReset() {
|
||||
// 保存 SMB 设置
|
||||
async function saveSmbConfig() {
|
||||
try {
|
||||
await api.post(`storage/save/smb`, props.conf)
|
||||
await manageStorage('smb', 'save_config', { conf: props.conf })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import api from '@/api'
|
||||
import { manageStorage } from '@/api/manage'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useDisplay } from 'vuetify'
|
||||
|
||||
@@ -64,7 +64,7 @@ function handleDone() {
|
||||
// 重置配置
|
||||
async function handleReset() {
|
||||
try {
|
||||
await api.get<null>('/storage/reset/u115', { feedback: 'silent' })
|
||||
await manageStorage('u115', 'reset_config', {}, { feedback: 'silent' })
|
||||
setMessage('success', t('dialog.u115Auth.authSuccess'))
|
||||
handleDone()
|
||||
} catch (error) {
|
||||
@@ -76,7 +76,12 @@ async function handleReset() {
|
||||
// 获取授权URL
|
||||
async function fetchAuthUrl() {
|
||||
try {
|
||||
const result = await api.get<{ authUrl: string; state: string }>('/storage/auth_url/u115', { feedback: 'silent' })
|
||||
const result = await manageStorage<{ authUrl: string; state: string }>(
|
||||
'u115',
|
||||
'generate_auth_url',
|
||||
{},
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
authUrl.value = result.authUrl
|
||||
authState.value = result.state
|
||||
} catch (error) {
|
||||
@@ -120,7 +125,12 @@ function openAuthWindow() {
|
||||
// 检查授权状态
|
||||
async function checkAuthStatus() {
|
||||
try {
|
||||
const result = await api.get<{ status: number; tip?: string }>('/storage/check/u115', { feedback: 'silent' })
|
||||
const result = await manageStorage<{ status: number; tip?: string }>(
|
||||
'u115',
|
||||
'check_login',
|
||||
{},
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
const { status, tip } = result
|
||||
|
||||
if (status === AUTH_STATUS_SUCCESS) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from 'vue-toastification'
|
||||
import api from '@/api'
|
||||
import { manageNotificationChannel } from '@/api/manage'
|
||||
import type { NotificationConf, NotificationSwitchConf } from '@/api/types'
|
||||
import NotificationChannelCard from '@/components/cards/NotificationChannelCard.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
@@ -230,13 +231,15 @@ async function migrateWechatClawBotRenames() {
|
||||
([oldName, newName]) => oldName && newName && oldName !== newName && activeWechatClawBotNames.has(newName),
|
||||
)
|
||||
for (const [oldName, newName] of renameEntries) {
|
||||
await api.post('notification/wechatclawbot/migrate', null, {
|
||||
feedback: 'silent',
|
||||
params: {
|
||||
old_source: oldName,
|
||||
new_source: newName,
|
||||
await manageNotificationChannel(
|
||||
'WechatClawBot',
|
||||
'migrate_cache',
|
||||
{
|
||||
old_name: oldName,
|
||||
new_name: newName,
|
||||
},
|
||||
})
|
||||
{ feedback: 'silent' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,8 +190,10 @@ describe('AccountSettingNotification', () => {
|
||||
await user.click(getCard('通知渠道').getByRole('button', { name: '保存' }))
|
||||
|
||||
await waitFor(() => expect(mocks.apiPost).toHaveBeenCalledTimes(2))
|
||||
expect(mocks.apiPost).toHaveBeenNthCalledWith(1, 'notification/wechatclawbot/migrate', null, {
|
||||
params: { old_source: 'Alpha', new_source: 'Gamma' },
|
||||
expect(mocks.apiPost).toHaveBeenNthCalledWith(1, 'notification/manage', {
|
||||
target: 'WechatClawBot',
|
||||
action: 'migrate_cache',
|
||||
params: { old_name: 'Alpha', new_name: 'Gamma' },
|
||||
})
|
||||
expect(mocks.apiPost).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
@@ -216,8 +218,10 @@ describe('AccountSettingNotification', () => {
|
||||
mocks.apiPost.mockResolvedValue({ success: true })
|
||||
await user.click(save)
|
||||
await waitFor(() => expect(mocks.apiPost).toHaveBeenCalledTimes(2))
|
||||
expect(mocks.apiPost).toHaveBeenNthCalledWith(1, 'notification/wechatclawbot/migrate', null, {
|
||||
params: { old_source: 'Alpha', new_source: 'Beta' },
|
||||
expect(mocks.apiPost).toHaveBeenNthCalledWith(1, 'notification/manage', {
|
||||
target: 'WechatClawBot',
|
||||
action: 'migrate_cache',
|
||||
params: { old_name: 'Alpha', new_name: 'Beta' },
|
||||
})
|
||||
expect(mocks.apiPost).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
|
||||
Reference in New Issue
Block a user