refactor(品牌工具页): 抽取 formatDateTime 与 uploadPathsToJava 公共工具
- 新增 shared/utils/datetime.ts:13 个品牌页逐字重复的 formatDateTime 收敛为一处; 语义不同的 3 个变体(toLocaleString / 字符串切片 / 支持时间戳)保留不动 - 新增 shared/utils/upload-to-java.ts:10 个品牌页的上传循环收敛,api 依赖注入便于单测; 保留 uploadOss(品牌Tab)与 returnEmptyWhenUnavailable(跟价)两处行为差异 - 补 datetime / upload-to-java 单测 10 例 净减约 327 行;vue-tsc 构建与 690 个前端单测全通过
This commit is contained in:
@@ -97,7 +97,7 @@ import {
|
||||
type UploadedFileRef,
|
||||
type UploadFileVo,
|
||||
} from '@/shared/api/java-modules'
|
||||
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import { expandBrandFolderRecursive } from '@/shared/api/brand'
|
||||
import { getPywebviewApi } from '@/shared/bridges/pywebview'
|
||||
import { getTaskPollIntervalMs } from '@/shared/task-progress-config'
|
||||
import { getStoredApiSecret } from '@/shared/utils/api-secret-store'
|
||||
@@ -110,6 +110,8 @@ import {
|
||||
checkSelectedFiles,
|
||||
} from '@/shared/dispatch-guard'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const selectedFileNames = ref<string[]>([])
|
||||
const uploadedFiles = ref<UploadFileVo[]>([])
|
||||
@@ -293,37 +295,6 @@ function loadPollingIds() {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
async function uploadAppearancePathsToJava(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() {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.select_brand_xlsx_files || !api.upload_file_to_java) {
|
||||
@@ -334,7 +305,7 @@ async function selectFiles() {
|
||||
if (!paths?.length) return
|
||||
if (!(await passGuard(checkSelectedFiles(paths, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
try {
|
||||
const files = await uploadAppearancePathsToJava(paths)
|
||||
const files = await uploadPathsToJava(getPywebviewApi(), paths)
|
||||
uploadedFiles.value = files
|
||||
selectedFileNames.value = files.map((f) => f.relativePath || f.originalFilename || f.fileKey)
|
||||
parseResult.value = null
|
||||
@@ -366,7 +337,7 @@ async function selectFolder() {
|
||||
return
|
||||
}
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
const files = await uploadAppearancePathsToJava(result.items)
|
||||
const files = await uploadPathsToJava(getPywebviewApi(), result.items)
|
||||
uploadedFiles.value = files
|
||||
selectedFileNames.value = result.items.map((item) => item.relativePath || item.absolutePath)
|
||||
parseResult.value = null
|
||||
|
||||
@@ -125,7 +125,8 @@ import { getTaskPollIntervalMs } from '@/shared/task-progress-config'
|
||||
import { createCategorizedTimers } from '@/shared/utils/categorized-timers'
|
||||
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'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const uploadedFiles = ref<UploadFileVo[]>([])
|
||||
const strategy = ref<'Terms' | 'Simple'>('Simple')
|
||||
@@ -342,38 +343,7 @@ function toBrandTaskView(item: BrandTaskItem): TaskItemView {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
/** 上传本地 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, true)
|
||||
if (!uploaded?.success || !uploaded.data) {
|
||||
throw new Error(uploaded?.error || uploaded?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
files.push(uploaded.data)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
async function selectFiles() {
|
||||
const bridge = getPywebviewApi()
|
||||
if (!bridge?.select_brand_xlsx_files) {
|
||||
@@ -384,7 +354,7 @@ async function selectFiles() {
|
||||
if (!paths?.length) return
|
||||
if (!(await passGuard(checkSelectedFiles(paths, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
try {
|
||||
const files = await uploadBrandPathsToJava(paths)
|
||||
const files = await uploadPathsToJava(getPywebviewApi(), paths, { uploadOss: true })
|
||||
uploadedFiles.value = files
|
||||
ElMessage.success(`已选择并上传 ${files.length} 个 Excel 文件`)
|
||||
} catch (error) {
|
||||
@@ -412,7 +382,7 @@ async function selectFolder() {
|
||||
return
|
||||
}
|
||||
if (!(await passGuard(checkSelectedFiles(res.items.map((i) => i.absolutePath), { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
const files = await uploadBrandPathsToJava(res.items)
|
||||
const files = await uploadPathsToJava(getPywebviewApi(), res.items, { uploadOss: true })
|
||||
uploadedFiles.value = files
|
||||
ElMessage.success(`已选择并上传文件夹内 ${files.length} 个 Excel 文件`)
|
||||
} catch (error) {
|
||||
|
||||
@@ -150,7 +150,7 @@ import AmazonToolPageShell from '@/pages/amazon/components/AmazonToolPageShell.v
|
||||
import ModuleTemplateDownload from '@/shared/components/ModuleTemplateDownload.vue'
|
||||
import TaskCenterPanel from '@/shared/components/tasks/TaskCenterPanel.vue'
|
||||
import type { TaskItemView, TaskStatCard } from '@/shared/components/tasks/types'
|
||||
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import { expandBrandFolderRecursive } from '@/shared/api/brand'
|
||||
import { getPywebviewApi } from '@/shared/bridges/pywebview'
|
||||
import { useTaskProgressLoop } from '@/shared/composables/useTaskProgressLoop'
|
||||
import { createCategorizedTimers } from '@/shared/utils/categorized-timers'
|
||||
@@ -179,6 +179,8 @@ import {
|
||||
type CollectDataTaskDetailVo,
|
||||
type UploadFileVo,
|
||||
} from '@/shared/api/java-modules'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const COUNTRY_OPTIONS = [
|
||||
{ code: 'DE', label: '德国' },
|
||||
@@ -307,24 +309,6 @@ async function loadCountryPreference() {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadPathsToJava(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() {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.select_brand_xlsx_files || !api.upload_file_to_java) {
|
||||
@@ -335,7 +319,7 @@ async function selectFiles() {
|
||||
const paths = await api.select_brand_xlsx_files()
|
||||
if (!paths?.length) return
|
||||
if (!(await passGuard(checkSelectedFiles(paths, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
const files = await uploadPathsToJava(paths)
|
||||
const files = await uploadPathsToJava(getPywebviewApi(), paths)
|
||||
uploadedFiles.value = files
|
||||
selectedFileNames.value = files.map((f) => f.relativePath || f.originalFilename || f.fileKey)
|
||||
lastTaskId.value = null
|
||||
@@ -365,7 +349,7 @@ async function selectFolder() {
|
||||
return
|
||||
}
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
const files = await uploadPathsToJava(result.items)
|
||||
const files = await uploadPathsToJava(getPywebviewApi(), result.items)
|
||||
uploadedFiles.value = files
|
||||
selectedFileNames.value = result.items.map((item) => item.relativePath || item.absolutePath)
|
||||
lastTaskId.value = null
|
||||
@@ -501,19 +485,6 @@ async function pushToPythonQueue() {
|
||||
}
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
function normalizeTaskStatus(item: CollectDataHistoryItem) {
|
||||
return (item.taskStatus || '').toUpperCase()
|
||||
}
|
||||
|
||||
@@ -155,7 +155,6 @@ import AmazonToolPageShell from '@/pages/amazon/components/AmazonToolPageShell.v
|
||||
import TaskCenterPanel from '@/shared/components/tasks/TaskCenterPanel.vue'
|
||||
import type { TaskItemView, TaskStatCard } from '@/shared/components/tasks/types'
|
||||
import { expandBrandFolderRecursive } from '@/shared/api/brand'
|
||||
import type { BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import {
|
||||
deleteConvertHistory,
|
||||
getConvertHistory,
|
||||
@@ -169,6 +168,8 @@ import { getPywebviewApi, type UploadedJavaFile } from '@/shared/bridges/pywebvi
|
||||
import { saveUrlWithProgress } from '@/shared/utils/download-progress'
|
||||
import { EXCEL_EXTENSIONS, checkSelectedFiles, guardBlocked } from '@/shared/dispatch-guard'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const convertSelectedPaths = ref<string[]>([])
|
||||
const convertArchiveName = ref('')
|
||||
@@ -210,19 +211,6 @@ function itemSource(item: TaskItemView): ConvertResultItem {
|
||||
return item.source as ConvertResultItem
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
function convertTaskStatusText(item: ConvertResultItem) {
|
||||
const status = (item.taskStatus || '').toUpperCase()
|
||||
if (status === 'RUNNING') return '执行中'
|
||||
@@ -254,25 +242,6 @@ function toConvertTaskView(item: ConvertResultItem): TaskItemView {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadPathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.upload_file_to_java) {
|
||||
throw new Error('当前桌面端未提供文件上传桥接能力')
|
||||
}
|
||||
|
||||
const uploaded: UploadedJavaFile[] = []
|
||||
for (const item of paths) {
|
||||
const filePath = typeof item === 'string' ? item : item.absolutePath
|
||||
const relativePath = typeof item === 'string' ? undefined : item.relativePath
|
||||
const result = await api.upload_file_to_java(filePath, relativePath)
|
||||
if (!result?.success || !result.data) {
|
||||
throw new Error(result?.error || result?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
uploaded.push(result.data)
|
||||
}
|
||||
return uploaded
|
||||
}
|
||||
|
||||
async function loadConvertTemplates(preferredTemplateId?: string) {
|
||||
const templates = await getConvertTemplates()
|
||||
convertTemplates.value = templates || []
|
||||
@@ -300,7 +269,7 @@ async function loadConvertTemplates(preferredTemplateId?: string) {
|
||||
async function handleSelectedPaths(paths: string[], successMessage: string) {
|
||||
convertSelectedPaths.value = paths
|
||||
convertArchiveName.value = ''
|
||||
convertUploadedFiles.value = await uploadPathsToJava(paths)
|
||||
convertUploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), paths)
|
||||
ElMessage.success(successMessage)
|
||||
}
|
||||
|
||||
@@ -339,7 +308,7 @@ async function selectConvertFolder() {
|
||||
if (result.success && result.items?.length) {
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
convertSelectedPaths.value = result.items.map((item) => item.relativePath)
|
||||
convertUploadedFiles.value = await uploadPathsToJava(result.items)
|
||||
convertUploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), result.items)
|
||||
ElMessage.success(`已选择文件夹内 ${result.items.length} 个 xlsx 文件`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -120,12 +120,14 @@ import { ElMessage } from 'element-plus'
|
||||
import AmazonToolPageShell from '@/pages/amazon/components/AmazonToolPageShell.vue';
|
||||
import TaskCenterPanel from '@/shared/components/tasks/TaskCenterPanel.vue'
|
||||
import type { TaskItemView, TaskStatCard } from '@/shared/components/tasks/types'
|
||||
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import { expandBrandFolderRecursive } from '@/shared/api/brand'
|
||||
import { deleteDedupeHistory, getDedupeHistory, getDedupeResultDownloadUrl, getDedupeRunProgress, getExcelInfo, runDedupe, type DedupeResultItem, type DedupeRunProgressVo, type DedupeRunVo } from '@/shared/api/java-modules'
|
||||
import { getPywebviewApi, type UploadedJavaFile } from '@/shared/bridges/pywebview'
|
||||
import { saveUrlWithProgress } from '@/shared/utils/download-progress'
|
||||
import { EXCEL_EXTENSIONS, checkSelectedFiles, guardBlocked } from '@/shared/dispatch-guard'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const cleanAvailableColumns = ref<string[]>([])
|
||||
const cleanSelectedColumns = ref<string[]>([])
|
||||
@@ -144,19 +146,6 @@ const cleanDisplayPaths = computed(() => cleanSelectedPaths.value.slice(0, 8))
|
||||
const latestRunId = ref('')
|
||||
const cleanRunStartedAt = ref('')
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
function itemSource(item: TaskItemView): DedupeResultItem {
|
||||
return item.source as DedupeResultItem
|
||||
}
|
||||
@@ -231,24 +220,6 @@ function clearAllCleanColumns() {
|
||||
cleanSelectedColumns.value = []
|
||||
}
|
||||
|
||||
async function uploadPathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.upload_file_to_java) {
|
||||
throw new Error('当前桌面端未提供文件上传桥接能力')
|
||||
}
|
||||
const uploaded: UploadedJavaFile[] = []
|
||||
for (const item of paths) {
|
||||
const filePath = typeof item === 'string' ? item : item.absolutePath
|
||||
const relativePath = typeof item === 'string' ? undefined : item.relativePath
|
||||
const result = await api.upload_file_to_java(filePath, relativePath)
|
||||
if (!result?.success || !result.data) {
|
||||
throw new Error(result?.error || result?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
uploaded.push(result.data)
|
||||
}
|
||||
return uploaded
|
||||
}
|
||||
|
||||
async function loadCleanHeaders(fileKey: string) {
|
||||
const result = await getExcelInfo(fileKey)
|
||||
if (!result.headers?.length) {
|
||||
@@ -272,7 +243,7 @@ async function loadCleanHeaders(fileKey: string) {
|
||||
async function handleSelectedPaths(paths: string[], successMessage: string) {
|
||||
cleanSelectedPaths.value = paths
|
||||
cleanArchiveName.value = ''
|
||||
cleanUploadedFiles.value = await uploadPathsToJava(paths)
|
||||
cleanUploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), paths)
|
||||
if (cleanUploadedFiles.value.length > 0) {
|
||||
await loadCleanHeaders(cleanUploadedFiles.value[0].fileKey)
|
||||
}
|
||||
@@ -314,7 +285,7 @@ async function selectCleanFolder() {
|
||||
if (result.success && result.items?.length) {
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
cleanSelectedPaths.value = result.items.map((item) => item.relativePath)
|
||||
cleanUploadedFiles.value = await uploadPathsToJava(result.items)
|
||||
cleanUploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), result.items)
|
||||
if (cleanUploadedFiles.value.length > 0) {
|
||||
await loadCleanHeaders(cleanUploadedFiles.value[0].fileKey)
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ import ModuleTemplateDownload from '@/shared/components/ModuleTemplateDownload.v
|
||||
import TaskCenterPanel from '@/shared/components/tasks/TaskCenterPanel.vue'
|
||||
import type { TaskItemView, TaskStatCard } from '@/shared/components/tasks/types'
|
||||
import ZiniaoVersionSetting from '@/shared/components/ZiniaoVersionSetting.vue'
|
||||
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import { expandBrandFolderRecursive } from '@/shared/api/brand'
|
||||
import {
|
||||
deleteDeleteBrandHistory,
|
||||
getDeleteBrandHistory,
|
||||
@@ -139,6 +139,8 @@ import {
|
||||
} from '@/shared/dispatch-guard'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import { useZiniaoVersion } from '@/shared/utils/ziniao-version'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
interface SessionDeleteBrandItem extends DeleteBrandResultItem {
|
||||
_pushed?: boolean
|
||||
@@ -534,19 +536,6 @@ function taskFinishedAt(item: DeleteBrandResultItem) {
|
||||
return taskDetails.value[item.taskId]?.task?.finishedAt ?? ''
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
|
||||
}
|
||||
|
||||
|
||||
function formatMatchResult(item: DeleteBrandResultItem) {
|
||||
if (item.matchStatus === 'INDEX_STALE' && isUsableMatchedItem(item)) {
|
||||
@@ -1023,24 +1012,6 @@ function stopPolling() {
|
||||
clearAutoRetryTimer()
|
||||
}
|
||||
|
||||
async function uploadPathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.upload_file_to_java) {
|
||||
throw new Error('当前桌面端未提供文件上传桥接能力')
|
||||
}
|
||||
const uploaded: UploadedJavaFile[] = []
|
||||
for (const item of paths) {
|
||||
const filePath = typeof item === 'string' ? item : item.absolutePath
|
||||
const relativePath = typeof item === 'string' ? undefined : item.relativePath
|
||||
const result = await api.upload_file_to_java(filePath, relativePath)
|
||||
if (!result?.success || !result.data) {
|
||||
throw new Error(result?.error || result?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
uploaded.push(result.data)
|
||||
}
|
||||
return uploaded
|
||||
}
|
||||
|
||||
async function selectFiles() {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.select_brand_xlsx_files) {
|
||||
@@ -1052,7 +1023,7 @@ async function selectFiles() {
|
||||
if (!paths?.length) return
|
||||
if (!(await passGuard(checkSelectedFiles(paths, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
selectedPaths.value = paths
|
||||
uploadedFiles.value = await uploadPathsToJava(paths)
|
||||
uploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), paths)
|
||||
ElMessage.success(`已选择 ${paths.length} 个删除品牌文件`)
|
||||
} catch (error) {
|
||||
// 上传失败时清空选择,避免残留上一批文件让用户误以为新文件已就绪
|
||||
@@ -1079,7 +1050,7 @@ async function selectFolder() {
|
||||
}
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
selectedPaths.value = result.items.map((item) => item.relativePath || item.absolutePath)
|
||||
uploadedFiles.value = await uploadPathsToJava(result.items)
|
||||
uploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), result.items)
|
||||
ElMessage.success(`已选择文件夹内 ${result.items.length} 个 xlsx 文件`)
|
||||
} catch (error) {
|
||||
selectedPaths.value = []
|
||||
|
||||
@@ -272,6 +272,7 @@ import {
|
||||
countryLabel,
|
||||
sanitizeCountryCodes,
|
||||
} from "@/shared/country-options";
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
|
||||
const MAX_TRANSIENT_ERRORS = 30;
|
||||
/** 任务终态后等待结果文件(Java 侧异步生成)的最大轮次,12 × 10s ≈ 2 分钟 */
|
||||
@@ -447,19 +448,6 @@ function createSubmissionId(taskId: number, shopName?: string) {
|
||||
return `patrol-delete:${taskId}:${shopName || "shop"}:${Date.now()}`;
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return "-";
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
function formatMatchStatus(status?: string) {
|
||||
const value = (status || "").trim();
|
||||
const map: Record<string, string> = {
|
||||
|
||||
@@ -257,6 +257,8 @@ import {
|
||||
type PriceTrackShopQueueItem,
|
||||
type PriceTrackTaskDetailVo,
|
||||
} from '@/shared/api/java-modules'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const ziniaoVersion = useZiniaoVersion()
|
||||
|
||||
@@ -336,19 +338,6 @@ function snapTask(item: PriceTrackHistoryItem) {
|
||||
return item.taskId ? taskSnapshots.value[item.taskId]?.task : undefined
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
|
||||
}
|
||||
|
||||
const pollTimer = ref<number | null>(null)
|
||||
const pollingInFlight = ref(false)
|
||||
let disposed = false
|
||||
@@ -511,7 +500,7 @@ async function selectAsinFile() {
|
||||
}
|
||||
try {
|
||||
asinFiles.value = paths
|
||||
asinUploadedFiles.value = await uploadAsinPathsToJava(paths)
|
||||
asinUploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), paths, { returnEmptyWhenUnavailable: true })
|
||||
ElMessage.success(`已选择 ${paths.length} 个ASIN文件`)
|
||||
} catch (error) {
|
||||
// 上传失败时清空选择,避免本地路径残留被当作服务器路径提交给 Java
|
||||
@@ -537,7 +526,7 @@ async function selectAsinFolder() {
|
||||
}
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_CSV_EXTENSIONS })))) return
|
||||
asinFiles.value = result.items.map((item) => item.relativePath || item.absolutePath)
|
||||
asinUploadedFiles.value = await uploadAsinPathsToJava(result.items)
|
||||
asinUploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), result.items, { returnEmptyWhenUnavailable: true })
|
||||
ElMessage.success(`已选择文件夹内 ${result.items.length} 个ASIN文件`)
|
||||
} catch (error) {
|
||||
// 上传失败时清空选择,避免残留上一批文件让用户误以为新文件已就绪
|
||||
@@ -550,24 +539,6 @@ async function selectAsinFolder() {
|
||||
|
||||
|
||||
// ========== 国家顺序相关 ==========
|
||||
async function uploadAsinPathsToJava(paths: Array<string | { absolutePath: string; relativePath?: string }>) {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.upload_file_to_java) {
|
||||
return []
|
||||
}
|
||||
const uploaded: UploadedJavaFile[] = []
|
||||
for (const item of paths) {
|
||||
const filePath = typeof item === 'string' ? item : item.absolutePath
|
||||
const relativePath = typeof item === 'string' ? undefined : item.relativePath
|
||||
const result = await api.upload_file_to_java(filePath, relativePath)
|
||||
if (!result?.success || !result.data) {
|
||||
throw new Error(result?.error || result?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
uploaded.push(result.data)
|
||||
}
|
||||
return uploaded
|
||||
}
|
||||
|
||||
function resolveAsinRequestPaths() {
|
||||
// 提交上传返回的 fileKey(与去重/转换/采集等模块一致):服务端按 key 反查上传临时目录,
|
||||
// 不依赖上传返回的 localPath 绝对路径形态——安全加固后服务端按拼接解析绝对路径会失败。
|
||||
|
||||
@@ -174,6 +174,7 @@ import { checkQueuePayload, guardBlocked } from '@/shared/dispatch-guard'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import ZiniaoVersionSetting from '@/shared/components/ZiniaoVersionSetting.vue'
|
||||
import { useZiniaoVersion } from '@/shared/utils/ziniao-version'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
|
||||
const shopInput = ref('')
|
||||
const candidates = ref<ProductRiskCandidateVo[]>([])
|
||||
@@ -338,19 +339,6 @@ function snapTask(item: ProductRiskHistoryItem) {
|
||||
return item.taskId ? taskSnapshots.value[item.taskId]?.task : undefined
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
|
||||
}
|
||||
|
||||
const pollTimer = ref<number | null>(null)
|
||||
const pollingInFlight = ref(false)
|
||||
let disposed = false
|
||||
|
||||
@@ -191,7 +191,7 @@ import ModuleTemplateDownload from '@/shared/components/ModuleTemplateDownload.v
|
||||
import TaskCenterPanel from '@/shared/components/tasks/TaskCenterPanel.vue'
|
||||
import type { TaskItemView, TaskStatCard } from '@/shared/components/tasks/types'
|
||||
import ZiniaoVersionSetting from '@/shared/components/ZiniaoVersionSetting.vue'
|
||||
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import { expandBrandFolderRecursive } from '@/shared/api/brand'
|
||||
import {
|
||||
activatePublishFile,
|
||||
activatePublishTask,
|
||||
@@ -219,6 +219,7 @@ import {
|
||||
} from '@/shared/dispatch-guard'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import { useZiniaoVersion, type ZiniaoVersion } from '@/shared/utils/ziniao-version'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const COUNTRY_OPTIONS = [
|
||||
{ code: 'DE', label: '德国' },
|
||||
@@ -476,22 +477,6 @@ function onSyncCountryChange(code: string, event: Event) {
|
||||
syncCountries.value = COUNTRY_OPTIONS.map((item) => item.code).filter((item) => selected.has(item))
|
||||
}
|
||||
|
||||
async function uploadPathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.upload_file_to_java) throw new Error('当前桌面端未提供文件上传能力')
|
||||
const uploaded: UploadedJavaFile[] = []
|
||||
for (const item of paths) {
|
||||
const filePath = typeof item === 'string' ? item : item.absolutePath
|
||||
const relativePath = typeof item === 'string' ? undefined : item.relativePath
|
||||
const result = await api.upload_file_to_java(filePath, relativePath)
|
||||
if (!result?.success || !result.data) {
|
||||
throw new Error(result?.error || result?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
uploaded.push(result.data)
|
||||
}
|
||||
return uploaded
|
||||
}
|
||||
|
||||
async function selectFiles() {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.select_brand_xlsx_files || !api.upload_file_to_java) {
|
||||
@@ -503,7 +488,7 @@ async function selectFiles() {
|
||||
if (!paths?.length) return
|
||||
if (!(await passGuard(checkSelectedFiles(paths, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
uploading.value = true
|
||||
const uploaded = await uploadPathsToJava(paths)
|
||||
const uploaded = await uploadPathsToJava(getPywebviewApi(), paths)
|
||||
selectedPaths.value = paths
|
||||
uploadedFiles.value = uploaded
|
||||
ElMessage.success(`已上传 ${uploaded.length} 个待上架文件`)
|
||||
@@ -533,7 +518,7 @@ async function selectFolder() {
|
||||
}
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
uploading.value = true
|
||||
const uploaded = await uploadPathsToJava(result.items)
|
||||
const uploaded = await uploadPathsToJava(getPywebviewApi(), result.items)
|
||||
selectedPaths.value = result.items.map((item) => item.relativePath || item.absolutePath)
|
||||
uploadedFiles.value = uploaded
|
||||
ElMessage.success(`已上传文件夹内 ${uploaded.length} 个 xlsx 文件`)
|
||||
|
||||
@@ -214,6 +214,7 @@ import { checkQueuePayload } from "@/shared/dispatch-guard";
|
||||
import { passGuard } from "@/shared/dispatch-guard-ui";
|
||||
import ZiniaoVersionSetting from "@/shared/components/ZiniaoVersionSetting.vue";
|
||||
import { useZiniaoVersion } from "@/shared/utils/ziniao-version";
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
|
||||
const MAX_TRANSIENT_ERRORS = 30;
|
||||
const ziniaoVersion = useZiniaoVersion();
|
||||
@@ -375,19 +376,6 @@ function createSubmissionId(taskId: number, shopName?: string) {
|
||||
return `query-asin:${taskId}:${shopName || "shop"}:${Date.now()}`;
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return "-";
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
function formatMatchStatus(status?: string) {
|
||||
const value = (status || "").trim();
|
||||
const map: Record<string, string> = {
|
||||
|
||||
@@ -135,7 +135,7 @@ import {
|
||||
type UploadedFileRef,
|
||||
type UploadFileVo,
|
||||
} from '@/shared/api/java-modules'
|
||||
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import { expandBrandFolderRecursive } from '@/shared/api/brand'
|
||||
import { getPywebviewApi, type ProxyMode } from '@/shared/bridges/pywebview'
|
||||
import { getTaskPollIntervalMs } from '@/shared/task-progress-config'
|
||||
import { getStoredApiSecret } from '@/shared/utils/api-secret-store'
|
||||
@@ -150,6 +150,8 @@ import {
|
||||
checkSelectedFiles,
|
||||
} from '@/shared/dispatch-guard'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const selectedFileNames = ref<string[]>([])
|
||||
const uploadedFiles = ref<UploadFileVo[]>([])
|
||||
@@ -300,19 +302,6 @@ function getRequiredAlipriceCredentials() {
|
||||
return { username, password }
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
function maskSecret(secret: string) {
|
||||
if (!secret) return ''
|
||||
if (secret.length <= 10) return '***'
|
||||
@@ -374,24 +363,6 @@ function loadPollingIds() {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadAppearancePathsToJava(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() {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.select_brand_xlsx_files || !api.upload_file_to_java) {
|
||||
@@ -402,7 +373,7 @@ async function selectFiles() {
|
||||
if (!paths?.length) return
|
||||
if (!(await passGuard(checkSelectedFiles(paths, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
try {
|
||||
const files = await uploadAppearancePathsToJava(paths)
|
||||
const files = await uploadPathsToJava(getPywebviewApi(), paths)
|
||||
uploadedFiles.value = files
|
||||
selectedFileNames.value = files.map((f) => f.relativePath || f.originalFilename || f.fileKey)
|
||||
parseResult.value = null
|
||||
@@ -434,7 +405,7 @@ async function selectFolder() {
|
||||
return
|
||||
}
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
const files = await uploadAppearancePathsToJava(result.items)
|
||||
const files = await uploadPathsToJava(getPywebviewApi(), result.items)
|
||||
uploadedFiles.value = files
|
||||
selectedFileNames.value = result.items.map((item) => item.relativePath || item.absolutePath)
|
||||
parseResult.value = null
|
||||
|
||||
@@ -141,12 +141,14 @@ import { ElMessage } from 'element-plus'
|
||||
import AmazonToolPageShell from '@/pages/amazon/components/AmazonToolPageShell.vue';
|
||||
import TaskCenterPanel from '@/shared/components/tasks/TaskCenterPanel.vue'
|
||||
import type { TaskItemView, TaskStatCard } from '@/shared/components/tasks/types'
|
||||
import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared/api/brand'
|
||||
import { expandBrandFolderRecursive } from '@/shared/api/brand'
|
||||
import { deleteSplitHistory, getExcelInfo, getSplitHistory, getSplitResultDownloadUrl, runSplit, type SplitResultItem } from '@/shared/api/java-modules'
|
||||
import { getPywebviewApi, type UploadedJavaFile } from '@/shared/bridges/pywebview'
|
||||
import { saveUrlWithProgress } from '@/shared/utils/download-progress'
|
||||
import { EXCEL_EXTENSIONS, checkSelectedFiles, guardBlocked } from '@/shared/dispatch-guard'
|
||||
import { passGuard } from '@/shared/dispatch-guard-ui'
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
import { uploadPathsToJava } from '@/shared/utils/upload-to-java'
|
||||
|
||||
const splitSelectedPaths = ref<string[]>([])
|
||||
const splitArchiveName = ref('')
|
||||
@@ -188,19 +190,6 @@ function itemSource(item: TaskItemView): SplitResultItem {
|
||||
return item.source as SplitResultItem
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
function splitTaskStatusText(item: SplitResultItem) {
|
||||
const status = (item.taskStatus || '').toUpperCase()
|
||||
if (status === 'RUNNING') return '执行中'
|
||||
@@ -235,24 +224,6 @@ function toSplitTaskView(item: SplitResultItem): TaskItemView {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadPathsToJava(paths: Array<string | BrandExpandFolderItem>) {
|
||||
const api = getPywebviewApi()
|
||||
if (!api?.upload_file_to_java) {
|
||||
throw new Error('当前桌面端未提供文件上传桥接能力')
|
||||
}
|
||||
const uploaded: UploadedJavaFile[] = []
|
||||
for (const item of paths) {
|
||||
const filePath = typeof item === 'string' ? item : item.absolutePath
|
||||
const relativePath = typeof item === 'string' ? undefined : item.relativePath
|
||||
const result = await api.upload_file_to_java(filePath, relativePath)
|
||||
if (!result?.success || !result.data) {
|
||||
throw new Error(result?.error || result?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
uploaded.push(result.data)
|
||||
}
|
||||
return uploaded
|
||||
}
|
||||
|
||||
async function loadSplitInfo(fileKey: string) {
|
||||
const result = await getExcelInfo(fileKey)
|
||||
splitAvailableColumns.value = result.headers || []
|
||||
@@ -263,7 +234,7 @@ async function loadSplitInfo(fileKey: string) {
|
||||
async function handleSelectedPaths(paths: string[], successMessage: string) {
|
||||
splitSelectedPaths.value = paths
|
||||
splitArchiveName.value = ''
|
||||
splitUploadedFiles.value = await uploadPathsToJava(paths)
|
||||
splitUploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), paths)
|
||||
if (splitUploadedFiles.value.length > 0) {
|
||||
await loadSplitInfo(splitUploadedFiles.value[0].fileKey)
|
||||
}
|
||||
@@ -305,7 +276,7 @@ async function selectSplitFolder() {
|
||||
if (result.success && result.items?.length) {
|
||||
if (!(await passGuard(checkSelectedFiles(result.items, { allowedExtensions: EXCEL_EXTENSIONS })))) return
|
||||
splitSelectedPaths.value = result.items.map((item) => item.relativePath)
|
||||
splitUploadedFiles.value = await uploadPathsToJava(result.items)
|
||||
splitUploadedFiles.value = await uploadPathsToJava(getPywebviewApi(), result.items)
|
||||
if (splitUploadedFiles.value.length > 0) {
|
||||
await loadSplitInfo(splitUploadedFiles.value[0].fileKey)
|
||||
}
|
||||
|
||||
@@ -254,6 +254,7 @@ import { checkQueuePayload } from "@/shared/dispatch-guard";
|
||||
import { passGuard } from "@/shared/dispatch-guard-ui";
|
||||
import ZiniaoVersionSetting from "@/shared/components/ZiniaoVersionSetting.vue";
|
||||
import { useZiniaoVersion } from "@/shared/utils/ziniao-version";
|
||||
import { formatDateTime } from '@/shared/utils/datetime'
|
||||
|
||||
const MAX_TRANSIENT_ERRORS = 30;
|
||||
const ziniaoVersion = useZiniaoVersion();
|
||||
@@ -506,19 +507,6 @@ function createSubmissionId(taskId: number, shopName?: string) {
|
||||
return `withdraw:${taskId}:${shopName || "shop"}:${Date.now()}`;
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string) {
|
||||
if (!value) return "-";
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
function formatMatchStatus(status?: string) {
|
||||
const value = (status || "").trim();
|
||||
const map: Record<string, string> = {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 时间展示工具(前端各工具页统一使用)。
|
||||
*/
|
||||
|
||||
/**
|
||||
* 把后端时间串格式化为 `YYYY-MM-DD HH:mm:ss`(本地时区)。
|
||||
* 空值返回 '-';无法解析时原样返回,避免把后端原始串吞掉。
|
||||
*/
|
||||
export function formatDateTime(value?: string): string {
|
||||
if (!value) return '-'
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) return value
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { PywebviewApi, UploadedJavaFile } from '../bridges/pywebview.ts'
|
||||
|
||||
/** 待上传条目:本机绝对路径字符串,或展开文件夹得到的 { absolutePath, relativePath }。 */
|
||||
export interface UploadPathItem {
|
||||
absolutePath: string
|
||||
relativePath?: string
|
||||
}
|
||||
|
||||
export interface UploadPathsToJavaOptions {
|
||||
/** 透传给桥的 uploadOss 参数;需要把文件落到 OSS 的模块传 true。 */
|
||||
uploadOss?: boolean
|
||||
/** 缺少上传桥时返回空数组而非抛错(个别模块的降级语义)。 */
|
||||
returnEmptyWhenUnavailable?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 逐个把本机文件上传给 Java,返回上传结果列表。
|
||||
*
|
||||
* 任一个失败即整体抛错(不返回半成品),由调用方统一提示并清空已选文件,
|
||||
* 避免残留上一批让用户误以为新文件已就绪。
|
||||
*
|
||||
* api 由调用方传入(通常是 `getPywebviewApi()`),便于脱离桌面桥单测。
|
||||
*/
|
||||
export async function uploadPathsToJava(
|
||||
api: PywebviewApi | undefined,
|
||||
paths: ReadonlyArray<string | UploadPathItem>,
|
||||
options: UploadPathsToJavaOptions = {},
|
||||
): Promise<UploadedJavaFile[]> {
|
||||
const upload = api?.upload_file_to_java
|
||||
if (!upload) {
|
||||
if (options.returnEmptyWhenUnavailable) {
|
||||
return []
|
||||
}
|
||||
throw new Error('当前桌面端未提供文件上传能力')
|
||||
}
|
||||
const uploaded: UploadedJavaFile[] = []
|
||||
for (const item of paths) {
|
||||
const filePath = typeof item === 'string' ? item : item.absolutePath
|
||||
const relativePath = typeof item === 'string' ? undefined : item.relativePath
|
||||
const result = await upload(filePath, relativePath, options.uploadOss)
|
||||
if (!result?.success || !result.data) {
|
||||
throw new Error(result?.error || result?.message || `上传失败:${filePath}`)
|
||||
}
|
||||
uploaded.push(result.data)
|
||||
}
|
||||
return uploaded
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { formatDateTime } from '../src/shared/utils/datetime.ts'
|
||||
|
||||
// 用本地时间构造再往返 ISO,期望值由同一本地时区还原,避免依赖运行环境的时区。
|
||||
test('formatDateTime 空值返回 -', () => {
|
||||
assert.equal(formatDateTime(undefined), '-')
|
||||
assert.equal(formatDateTime(''), '-')
|
||||
})
|
||||
|
||||
test('formatDateTime 无法解析时原样返回', () => {
|
||||
assert.equal(formatDateTime('not-a-date'), 'not-a-date')
|
||||
})
|
||||
|
||||
test('formatDateTime 按 YYYY-MM-DD HH:mm:ss 补零(个位月/日/时/分/秒)', () => {
|
||||
const local = new Date(2026, 0, 5, 9, 7, 3)
|
||||
assert.equal(formatDateTime(local.toISOString()), '2026-01-05 09:07:03')
|
||||
})
|
||||
|
||||
test('formatDateTime 年末边界', () => {
|
||||
const local = new Date(2026, 11, 31, 23, 59, 59)
|
||||
assert.equal(formatDateTime(local.toISOString()), '2026-12-31 23:59:59')
|
||||
})
|
||||
@@ -0,0 +1,70 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { uploadPathsToJava } from '../src/shared/utils/upload-to-java.ts'
|
||||
import type { PywebviewApi } from '../src/shared/bridges/pywebview.ts'
|
||||
|
||||
function fakeApi(
|
||||
upload: (
|
||||
filePath: string,
|
||||
relativePath?: string,
|
||||
uploadOss?: boolean,
|
||||
) => Promise<{ success: boolean; data?: never; error?: string; message?: string }>,
|
||||
) {
|
||||
return { upload_file_to_java: upload } as unknown as PywebviewApi
|
||||
}
|
||||
|
||||
function okData(filePath: string) {
|
||||
return { fileKey: `k:${filePath}`, originalFilename: filePath, localPath: '', size: 1 }
|
||||
}
|
||||
|
||||
test('uploadPathsToJava 逐个上传并返回结果列表', async () => {
|
||||
const calls: Array<[string, string | undefined, boolean | undefined]> = []
|
||||
const api = fakeApi(async (filePath, relativePath, uploadOss) => {
|
||||
calls.push([filePath, relativePath, uploadOss])
|
||||
return { success: true, data: okData(filePath) as never }
|
||||
})
|
||||
|
||||
const result = await uploadPathsToJava(api, ['/a.xlsx', { absolutePath: '/b.xlsx', relativePath: 'sub/b.xlsx' }])
|
||||
|
||||
assert.equal(result.length, 2)
|
||||
assert.deepEqual(calls, [
|
||||
['/a.xlsx', undefined, undefined],
|
||||
['/b.xlsx', 'sub/b.xlsx', undefined],
|
||||
])
|
||||
})
|
||||
|
||||
test('uploadPathsToJava 透传 uploadOss 参数', async () => {
|
||||
let seen: boolean | undefined
|
||||
const api = fakeApi(async (filePath, _relativePath, uploadOss) => {
|
||||
seen = uploadOss
|
||||
return { success: true, data: okData(filePath) as never }
|
||||
})
|
||||
|
||||
await uploadPathsToJava(api, ['/a.xlsx'], { uploadOss: true })
|
||||
|
||||
assert.equal(seen, true)
|
||||
})
|
||||
|
||||
test('uploadPathsToJava 任一失败即整体抛错并带上桥返回的文案', async () => {
|
||||
const api = fakeApi(async (filePath) =>
|
||||
filePath === '/bad.xlsx'
|
||||
? { success: false, error: '磁盘读取失败' }
|
||||
: { success: true, data: okData(filePath) as never },
|
||||
)
|
||||
|
||||
await assert.rejects(() => uploadPathsToJava(api, ['/ok.xlsx', '/bad.xlsx']), /磁盘读取失败/)
|
||||
})
|
||||
|
||||
test('uploadPathsToJava 失败无文案时回落到「上传失败:路径」', async () => {
|
||||
const api = fakeApi(async () => ({ success: false }))
|
||||
|
||||
await assert.rejects(() => uploadPathsToJava(api, ['/x.xlsx']), /上传失败:\/x\.xlsx/)
|
||||
})
|
||||
|
||||
test('uploadPathsToJava 缺上传桥默认抛错', async () => {
|
||||
await assert.rejects(() => uploadPathsToJava(undefined, ['/a.xlsx']), /未提供文件上传能力/)
|
||||
})
|
||||
|
||||
test('uploadPathsToJava 缺上传桥时 returnEmptyWhenUnavailable 返回空数组', async () => {
|
||||
assert.deepEqual(await uploadPathsToJava(undefined, ['/a.xlsx'], { returnEmptyWhenUnavailable: true }), [])
|
||||
})
|
||||
Reference in New Issue
Block a user