diff --git a/web/package.json b/web/package.json
index c2c1ec67..a85657df 100644
--- a/web/package.json
+++ b/web/package.json
@@ -23,6 +23,7 @@
"gen-auto-import": "tsx gen-auto-import.ts"
},
"dependencies": {
+ "@alova/adapter-xhr": "^2.1.1",
"@eslint/eslintrc": "^3.2.0",
"@fontsource-variable/jetbrains-mono": "^5.1.1",
"@guolao/vue-monaco-editor": "^1.5.4",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 5ae2fefc..8e744f26 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@alova/adapter-xhr':
+ specifier: ^2.1.1
+ version: 2.1.1(alova@3.2.8)
'@eslint/eslintrc':
specifier: ^3.2.0
version: 3.2.0
@@ -207,6 +210,11 @@ importers:
packages:
+ '@alova/adapter-xhr@2.1.1':
+ resolution: {integrity: sha512-053tY4wVEL5PUX8HwvFrp/y1wTObGjMvwVBLUVSOcZKuZwcoUraWdQoWAdNRnSWfwJoPEd+ta9EdS4KPqTcz9g==}
+ peerDependencies:
+ alova: ^3.0.20
+
'@alova/shared@1.1.2':
resolution: {integrity: sha512-8q/gMHFpzm7XYcaUlsyTCMDRRhFnewwheTeObMjPl1+bFdr+wZuBEHEPYIyd8tyzLwfrqpBeonaMN2tlngM8EA==}
@@ -989,36 +997,42 @@ packages:
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.1':
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
+ libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
@@ -1096,51 +1110,61 @@ packages:
resolution: {integrity: sha512-MW6N3AoC61OfE1VgnN5O1OW0gt8VTbhx9s/ZEPLBM11wEdHjeilPzOxVmmsrx5YmejpGPvez8QwGGvMU+pGxpw==}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.34.3':
resolution: {integrity: sha512-2SQkhr5xvatYq0/+H6qyW0zvrQz9LM4lxGkpWURLoQX5+yP8MsERh4uWmxFohOvwCP6l/+wgiHZ1qVwLDc7Qmw==}
cpu: [arm]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.34.3':
resolution: {integrity: sha512-R3JLYt8YoRwKI5shJsovLpcR6pwIMui/MGG/MmxZ1DYI3iRSKI4qcYrvYgDf4Ss2oCR3RL3F3dYK7uAGQgMIuQ==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.34.3':
resolution: {integrity: sha512-4XQhG8v/t3S7Rxs7rmFUuM6j09hVrTArzONS3fUZ6oBRSN/ps9IPQjVhp62P0W3KhqJdQADo/MRlYRMdgxr/3w==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.34.3':
resolution: {integrity: sha512-QlW1jCUZ1LHUIYCAK2FciVw1ptHsxzApYVi05q7bz2A8oNE8QxQ85NhM4arLxkAlcnS42t4avJbSfzSQwbIaKg==}
cpu: [loong64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-powerpc64le-gnu@4.34.3':
resolution: {integrity: sha512-kMbLToizVeCcN69+nnm20Dh0hrRIAjgaaL+Wh0gWZcNt8e542d2FUGtsyuNsHVNNF3gqTJrpzUGIdwMGLEUM7g==}
cpu: [ppc64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.34.3':
resolution: {integrity: sha512-YgD0DnZ3CHtvXRH8rzjVSxwI0kMTr0RQt3o1N92RwxGdx7YejzbBO0ELlSU48DP96u1gYYVWfUhDRyaGNqJqJg==}
cpu: [riscv64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.34.3':
resolution: {integrity: sha512-dIOoOz8altjp6UjAi3U9EW99s8nta4gzi52FeI45GlPyrUH4QixUoBMH9VsVjt+9A2RiZBWyjYNHlJ/HmJOBCQ==}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.34.3':
resolution: {integrity: sha512-lOyG3aF4FTKrhpzXfMmBXgeKUUXdAWmP2zSNf8HTAXPqZay6QYT26l64hVizBjq+hJx3pl0DTEyvPi9sTA6VGA==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.34.3':
resolution: {integrity: sha512-usztyYLu2i+mYzzOjqHZTaRXbUOqw3P6laNUh1zcqxbPH1P2Tz/QdJJCQSnGxCtsRQeuU2bCyraGMtMumC46rw==}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.34.3':
resolution: {integrity: sha512-ojFOKaz/ZyalIrizdBq2vyc2f0kFbJahEznfZlxdB6pF9Do6++i1zS5Gy6QLf8D7/S57MHrmBLur6AeRYeQXSA==}
@@ -3732,6 +3756,11 @@ packages:
snapshots:
+ '@alova/adapter-xhr@2.1.1(alova@3.2.8)':
+ dependencies:
+ '@alova/shared': 1.1.2
+ alova: 3.2.8
+
'@alova/shared@1.1.2': {}
'@ampproject/remapping@2.3.0':
diff --git a/web/src/api/panel/file/index.ts b/web/src/api/panel/file/index.ts
index 63450e38..6a89dd57 100644
--- a/web/src/api/panel/file/index.ts
+++ b/web/src/api/panel/file/index.ts
@@ -1,47 +1,39 @@
-import { request } from '@/utils'
+import { http } from '@/utils'
export default {
// 创建文件/文件夹
- create: (path: string, dir: boolean): any => request.post('/file/create', { path, dir }),
+ create: (path: string, dir: boolean): any => http.Post('/file/create', { path, dir }),
// 获取文件内容
- content: (path: string): any => request.get('/file/content', { params: { path } }),
+ content: (path: string): any => http.Get('/file/content', { params: { path } }),
// 保存文件
- save: (path: string, content: string): any => request.post('/file/save', { path, content }),
+ save: (path: string, content: string): any => http.Post('/file/save', { path, content }),
// 删除文件
- delete: (path: string): any => request.post('/file/delete', { path }),
+ delete: (path: string): any => http.Post('/file/delete', { path }),
// 上传文件
- upload: (path: string, formData: FormData, onProgress: any): any => {
- formData.append('path', path)
- return request.post('/file/upload', formData, {
- headers: { 'Content-Type': 'multipart/form-data' },
- onUploadProgress: (progressEvent: any) => {
- onProgress({ percent: Math.ceil((progressEvent.loaded / progressEvent.total) * 100) })
- }
- })
- },
+ upload: (formData: FormData): any => http.Post('/file/upload', formData),
// 检查文件是否存在
- exist: (paths: string[]): any => request.post('/file/exist', paths),
+ exist: (paths: string[]): any => http.Post('/file/exist', paths),
// 移动文件
- move: (paths: any[]): any => request.post('/file/move', paths),
+ move: (paths: any[]): any => http.Post('/file/move', paths),
// 复制文件
- copy: (paths: any[]): any => request.post('/file/copy', paths),
+ copy: (paths: any[]): any => http.Post('/file/copy', paths),
// 远程下载
remoteDownload: (path: string, url: string): any =>
- request.post('/file/remoteDownload', { path, url }),
+ http.Post('/file/remoteDownload', { path, url }),
// 获取文件信息
- info: (path: string): any => request.get('/file/info', { params: { path } }),
+ info: (path: string): any => http.Get('/file/info', { params: { path } }),
// 修改文件权限
permission: (path: string, mode: string, owner: string, group: string): any =>
- request.post('/file/permission', { path, mode, owner, group }),
+ http.Post('/file/permission', { path, mode, owner, group }),
// 压缩文件
compress: (dir: string, paths: string[], file: string): any =>
- request.post('/file/compress', { dir, paths, file }),
+ http.Post('/file/compress', { dir, paths, file }),
// 解压文件
- unCompress: (file: string, path: string): any => request.post('/file/unCompress', { file, path }),
+ unCompress: (file: string, path: string): any => http.Post('/file/unCompress', { file, path }),
// 搜索文件
search: (path: string, keyword: string, sub: boolean, page: number, limit: number): any =>
- request.get('/file/search', { params: { path, keyword, sub, page, limit } }),
+ http.Get('/file/search', { params: { path, keyword, sub, page, limit } }),
// 获取文件列表
list: (path: string, page: number, limit: number, sort: string): any =>
- request.get('/file/list', { params: { path, page, limit, sort } })
+ http.Get('/file/list', { params: { path, page, limit, sort } })
}
diff --git a/web/src/components/common/CodeEditor.vue b/web/src/components/common/CodeEditor.vue
index 2ebad9ea..622e4c2b 100644
--- a/web/src/components/common/CodeEditor.vue
+++ b/web/src/components/common/CodeEditor.vue
@@ -18,25 +18,25 @@ const props = defineProps({
const disabled = ref(false) // 在出现错误的情况下禁用保存
const data = ref('')
-const get = async () => {
- await file
- .content(props.path)
- .then((res) => {
- data.value = decodeBase64(res.data.content)
+const get = () => {
+ useRequest(file.content(props.path))
+ .onSuccess(({ data }) => {
+ data.value = decodeBase64(data.content)
window.$message.success('获取成功')
})
- .catch(() => {
+ .onError(() => {
disabled.value = true
})
}
-const save = async () => {
+const save = () => {
if (disabled.value) {
window.$message.error('当前状态下不可保存')
return
}
- await file.save(props.path, data.value)
- window.$message.success('保存成功')
+ useRequest(file.save(props.path, data.value)).onSuccess(() => {
+ window.$message.success('保存成功')
+ })
}
onMounted(() => {
diff --git a/web/src/utils/http/index.ts b/web/src/utils/http/index.ts
index 85aa15d0..6c58cac8 100644
--- a/web/src/utils/http/index.ts
+++ b/web/src/utils/http/index.ts
@@ -1,6 +1,7 @@
import { resolveResError } from '@/utils/http/helpers'
+import type { AlovaXHRResponse } from '@alova/adapter-xhr'
+import { xhrRequestAdapter } from '@alova/adapter-xhr'
import { createAlova, Method } from 'alova'
-import adapterFetch from 'alova/fetch'
import VueHook from 'alova/vue'
import axios from 'axios'
import { reqReject, reqResolve, resReject, resResolve } from './interceptors'
@@ -27,15 +28,21 @@ export const http = createAlova({
id: 'panel',
cacheFor: null,
statesHook: VueHook,
- requestAdapter: adapterFetch(),
+ requestAdapter: xhrRequestAdapter(),
baseURL: import.meta.env.VITE_BASE_API,
responded: {
- onSuccess: async (response: Response, method: Method) => {
- const ct = response.headers.get('Content-Type')
- const json =
- ct && ct.includes('application/json')
- ? await response.json()
- : { code: response.status, message: await response.text() }
+ onSuccess: async (response: AlovaXHRResponse, method: Method) => {
+ const ct = response.headers['Content-Type'] || response.headers['content-type']
+ let json
+ try {
+ if (ct && ct.includes('application/json')) {
+ json = typeof response.data === 'string' ? JSON.parse(response.data) : response.data
+ } else {
+ json = { code: response.status, message: response.data }
+ }
+ } catch (error) {
+ json = { code: response.status, message: 'JSON 解析失败' }
+ }
const { status, statusText } = response
const { meta } = method
if (status !== 200) {
diff --git a/web/src/views/file/CompressModal.vue b/web/src/views/file/CompressModal.vue
index 3d833a83..af42280d 100644
--- a/web/src/views/file/CompressModal.vue
+++ b/web/src/views/file/CompressModal.vue
@@ -24,26 +24,24 @@ const ensureExtension = (extension: string) => {
}
}
-const handleArchive = async () => {
+const handleArchive = () => {
ensureExtension(format.value)
loading.value = true
const message = window.$message.loading('正在压缩中...', {
duration: 0
})
const paths = selected.value.map((item) => item.replace(path.value, '').replace(/^\//, ''))
- await api
- .compress(path.value, paths, file.value)
- .then(() => {
- window.$message.success('压缩成功')
+ useRequest(api.compress(path.value, paths, file.value))
+ .onSuccess(() => {
show.value = false
selected.value = []
+ window.$message.success('压缩成功')
})
- .catch(() => {
- window.$message.error('压缩失败')
+ .onComplete(() => {
+ message?.destroy()
+ loading.value = false
+ window.$bus.emit('file:refresh')
})
- message?.destroy()
- loading.value = false
- window.$bus.emit('file:refresh')
}
onMounted(() => {
diff --git a/web/src/views/file/ListTable.vue b/web/src/views/file/ListTable.vue
index 36720e7a..6af41400 100644
--- a/web/src/views/file/ListTable.vue
+++ b/web/src/views/file/ListTable.vue
@@ -1,5 +1,14 @@
@@ -34,7 +40,6 @@ const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequ
:segmented="false"
>
- 若上传报网络错误,请开启面板 HTTPS 后重试
点击或者拖动文件到该区域来上传
- 大文件建议使用 SFTP 上传
+ 大文件建议使用 SFTP 等方式上传
diff --git a/web/src/views/firewall/ForwardView.vue b/web/src/views/firewall/ForwardView.vue
index a7c501b8..75e383a4 100644
--- a/web/src/views/firewall/ForwardView.vue
+++ b/web/src/views/firewall/ForwardView.vue
@@ -131,21 +131,21 @@ const handleDelete = (row: any) => {
})
}
-const batchDelete = () => {
+const batchDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要删除的规则')
return
}
- for (const key of selectedRowKeys.value) {
- // 解析json
+ const promises = selectedRowKeys.value.map((key: any) => {
const rule = JSON.parse(key)
- useRequest(firewall.deleteForward(rule)).onSuccess(() => {
+ return useRequest(firewall.deleteForward(rule)).then(() => {
window.$message.success(`${rule.protocol} ${rule.target_ip}:${rule.target_port} 删除成功`)
})
- }
+ })
- refresh()
+ await Promise.all(promises)
+ await refresh()
}
const onChecked = (rowKeys: any) => {
diff --git a/web/src/views/firewall/IpRuleView.vue b/web/src/views/firewall/IpRuleView.vue
index 2513b409..ad5c8294 100644
--- a/web/src/views/firewall/IpRuleView.vue
+++ b/web/src/views/firewall/IpRuleView.vue
@@ -173,21 +173,21 @@ const handleDelete = (row: any) => {
})
}
-const batchDelete = () => {
+const batchDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要删除的规则')
return
}
- for (const key of selectedRowKeys.value) {
- // 解析json
+ const promises = selectedRowKeys.value.map((key: any) => {
const rule = JSON.parse(key)
- useRequest(firewall.deleteIpRule(rule)).onSuccess(() => {
+ return useRequest(firewall.deleteIpRule(rule)).then(() => {
window.$message.success(`${rule.address} 删除成功`)
})
- }
+ })
- refresh()
+ await Promise.all(promises)
+ await refresh()
}
const onChecked = (rowKeys: any) => {
diff --git a/web/src/views/firewall/RuleView.vue b/web/src/views/firewall/RuleView.vue
index e7856c99..5f7cc0bb 100644
--- a/web/src/views/firewall/RuleView.vue
+++ b/web/src/views/firewall/RuleView.vue
@@ -210,23 +210,23 @@ const handleDelete = async (row: any) => {
})
}
-const batchDelete = () => {
+const batchDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要删除的规则')
return
}
- for (const key of selectedRowKeys.value) {
- // 解析json
+ const promises = selectedRowKeys.value.map((key: any) => {
const rule = JSON.parse(key)
- useRequest(firewall.deleteRule(rule)).onSuccess(() => {
+ return useRequest(firewall.deleteRule(rule)).then(() => {
const port =
rule.port_start == rule.port_end ? rule.port_start : `${rule.port_start}-${rule.port_end}`
window.$message.success(`${rule.family} 规则 ${port}/${rule.protocol} 删除成功`)
})
- }
+ })
- refresh()
+ await Promise.all(promises)
+ await refresh()
}
const onChecked = (rowKeys: any) => {
diff --git a/web/src/views/website/IndexView.vue b/web/src/views/website/IndexView.vue
index ed9b8d80..2a87977a 100644
--- a/web/src/views/website/IndexView.vue
+++ b/web/src/views/website/IndexView.vue
@@ -307,19 +307,21 @@ const handleCreate = async () => {
})
}
-const batchDelete = async () => {
+const bulkDelete = async () => {
if (selectedRowKeys.value.length === 0) {
window.$message.info('请选择要删除的网站')
return
}
- for (const id of selectedRowKeys.value) {
- useRequest(website.delete(id, true, false)).onSuccess(() => {
- refresh()
- const site = data.value.find((item: any) => item.id === id)
+ const promises = selectedRowKeys.value.map((id: any) => {
+ const site = data.value.find((item: any) => item.id === id)
+ return useRequest(website.delete(id, true, false)).then(() => {
window.$message.success('网站 ' + site?.name + ' 删除成功')
})
- }
+ })
+
+ await Promise.all(promises)
+ await refresh()
}
const formatDbValue = (value: string) => {
@@ -346,7 +348,7 @@ onMounted(() => {
{{ $t('websiteIndex.create.trigger') }}
-
+
批量删除