fix(brand): 品牌检测补上传链+新增 /api/brand/run 端点+文件夹展开桥
- BrandBrandTab 对齐其它工具页:选文件/文件夹后先 upload_file_to_java 再提交(服务器可读路径),修复本地路径直传导致的「请先上传待处理文件/没有有效的xlsx文件路径」 - runBrandNow/createBrandTask 改为 files 契约(fileUrl=临时路径 localPath)+ 传 userId - Java 新增 POST /api/brand/run(等价 /tasks 并返回待爬取数据),修复前端「立即运行」No static resource - 选择文件夹改走新版桌面客户端桥 expand_brand_folder(本机展开);旧客户端提示改用文件多选 - 生产 .env 已另配 AIIMAGE_BRAND_CHECK_TOKEN(上游 16890 鉴权),非代码变更
This commit is contained in:
@@ -123,21 +123,18 @@ import {
|
||||
cancelBrandTask,
|
||||
createBrandTask,
|
||||
deleteBrandTask,
|
||||
expandBrandFolder,
|
||||
getBrandTaskDownloadUrl,
|
||||
getBrandTasks,
|
||||
runBrandNow,
|
||||
type BrandTaskItem,
|
||||
} from '@/shared/api/brand'
|
||||
import { checkSelectedFiles, EXCEL_EXTENSIONS } from '@/shared/dispatch-guard.ts'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import type { UploadFileVo } from '@/shared/api/upload.ts'
|
||||
import type { UploadedFileRef } from '@/shared/api/types/upload.ts'
|
||||
import type { BrandExpandFolderItem } from '@/shared/api/types/modules/brand'
|
||||
|
||||
/** 扩展文件夹接口响应(向后端真实字段 paths 兼容,shared 类型仅声明 items 时以本接口为准) */
|
||||
interface BrandExpandFolderPathsResponse {
|
||||
success: boolean
|
||||
paths?: string[]
|
||||
error?: string
|
||||
}
|
||||
|
||||
const selectedPaths = ref<string[]>([])
|
||||
const uploadedFiles = ref<UploadFileVo[]>([])
|
||||
const strategy = ref<'Terms' | 'Simple'>('Terms')
|
||||
const runMode = ref<'immediate' | 'queue'>('immediate')
|
||||
const submitting = ref(false)
|
||||
@@ -151,6 +148,10 @@ const hasUid = computed(() => {
|
||||
const numeric = Number(raw.trim())
|
||||
return Number.isFinite(numeric) && numeric > 0
|
||||
})
|
||||
// 展示用:已上传文件列表的展示名(优先相对路径,其次原文件名)
|
||||
const selectedPaths = computed(() =>
|
||||
uploadedFiles.value.map((f) => f.relativePath || f.originalFilename || f.fileKey),
|
||||
)
|
||||
const displayPaths = computed(() => selectedPaths.value.slice(0, 8))
|
||||
|
||||
function baseName(path: string) {
|
||||
@@ -275,10 +276,23 @@ function formatDateTime(value?: string) {
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
function normalizeSelected(paths: string[]) {
|
||||
selectedPaths.value = paths
|
||||
.filter((path) => /\.xlsx$/i.test(path))
|
||||
.filter((path, index, array) => array.indexOf(path) === index)
|
||||
/** 上传本地 xlsx 到 Java 临时目录,成功后回填 fileUrl(服务器本地路径,Java 直接读取) */
|
||||
async function uploadBrandPathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.upload_file_to_java) {
|
||||
throw new Error('当前环境未提供文件上传能力')
|
||||
}
|
||||
const files: UploadFileVo[] = []
|
||||
for (const item of paths) {
|
||||
const filePath = typeof item === 'string' ? item : item.absolutePath
|
||||
const relativePath = typeof item === 'string' ? undefined : item.relativePath
|
||||
const uploaded = await api.upload_file_to_java(filePath, relativePath)
|
||||
if (!uploaded?.success || !uploaded.data) {
|
||||
throw new Error(uploaded?.error || uploaded?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
files.push(uploaded.data)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
async function selectFiles() {
|
||||
@@ -287,13 +301,15 @@ async function selectFiles() {
|
||||
ElMessage.warning('当前环境不支持文件选择,请在本机客户端中打开')
|
||||
return
|
||||
}
|
||||
const paths = await bridge.select_brand_xlsx_files()
|
||||
if (!paths?.length) return
|
||||
if (!(await passGuard(checkSelectedFiles(paths, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
try {
|
||||
const paths = await bridge.select_brand_xlsx_files()
|
||||
if (!paths?.length) return
|
||||
normalizeSelected(paths)
|
||||
ElMessage.success(`已选择 ${selectedPaths.value.length} 个 Excel 文件`)
|
||||
const files = await uploadBrandPathsToJava(paths)
|
||||
uploadedFiles.value = files
|
||||
ElMessage.success(`已选择并上传 ${files.length} 个 Excel 文件`)
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '文件选择失败')
|
||||
ElMessage.error(error instanceof Error ? error.message : '文件上传失败')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,16 +319,23 @@ async function selectFolder() {
|
||||
ElMessage.warning('当前环境不支持文件夹选择,请在本机客户端中打开')
|
||||
return
|
||||
}
|
||||
const folder = await bridge.select_brand_folder()
|
||||
if (!folder) return
|
||||
// 新版桌面客户端桥在本机展开文件夹(服务器无法访问用户本机目录)
|
||||
if (!bridge.expand_brand_folder) {
|
||||
ElMessage.warning('当前桌面客户端版本不支持文件夹展开,请使用"选择文件"(可多选)')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const folder = await bridge.select_brand_folder()
|
||||
if (!folder) return
|
||||
const res = (await expandBrandFolder(folder)) as BrandExpandFolderPathsResponse
|
||||
if (!res.success || !res.paths?.length) {
|
||||
const res = await bridge.expand_brand_folder(folder)
|
||||
if (!res.success || !res.items?.length) {
|
||||
ElMessage.warning(res.error || '该文件夹下没有 xlsx 文件')
|
||||
return
|
||||
}
|
||||
normalizeSelected(res.paths)
|
||||
ElMessage.success(`已选择文件夹内 ${selectedPaths.value.length} 个 Excel 文件`)
|
||||
if (!(await passGuard(checkSelectedFiles(res.items.map((i) => i.absolutePath), { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
const files = await uploadBrandPathsToJava(res.items)
|
||||
uploadedFiles.value = files
|
||||
ElMessage.success(`已选择并上传文件夹内 ${files.length} 个 Excel 文件`)
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '文件夹选择失败')
|
||||
}
|
||||
@@ -327,28 +350,35 @@ async function submitRun() {
|
||||
ElMessage.warning('未获取到登录用户,请先在本机客户端登录后再试')
|
||||
return
|
||||
}
|
||||
if (!selectedPaths.value.length) {
|
||||
if (!uploadedFiles.value.length) {
|
||||
ElMessage.warning('请先选择待检 Excel 文件')
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
// 上传后以服务器可读的 localPath 作为 fileUrl(Java resolveSourceFile 直接读临时文件)
|
||||
const files: UploadedFileRef[] = uploadedFiles.value.map((f) => ({
|
||||
fileKey: f.fileKey,
|
||||
originalFilename: f.originalFilename,
|
||||
relativePath: f.relativePath,
|
||||
fileUrl: f.localPath,
|
||||
}))
|
||||
if (runMode.value === 'immediate') {
|
||||
const res = await runBrandNow(selectedPaths.value, strategy.value)
|
||||
const res = await runBrandNow(files, strategy.value)
|
||||
if (res.success && res.task_id) {
|
||||
ElMessage.success(`已创建任务 ${res.task_id},客户端开始检测`)
|
||||
} else {
|
||||
throw new Error((res as { error?: string }).error || '运行失败')
|
||||
}
|
||||
} else {
|
||||
const res = await createBrandTask(selectedPaths.value, strategy.value)
|
||||
const res = await createBrandTask(files, strategy.value)
|
||||
if (res.success && res.task_id) {
|
||||
ElMessage.success(`任务 ${res.task_id} 已添加到队列`)
|
||||
} else {
|
||||
throw new Error((res as { error?: string }).error || '添加失败')
|
||||
}
|
||||
}
|
||||
selectedPaths.value = []
|
||||
uploadedFiles.value = []
|
||||
await loadTasks()
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '提交失败')
|
||||
|
||||
Reference in New Issue
Block a user