1
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { amazonApi } from '../../api/amazon'
|
||||
import { handlePlatformFileExport } from '../../utils/settings'
|
||||
|
||||
// 接收VIP状态
|
||||
const props = defineProps<{
|
||||
isVip: boolean
|
||||
}>()
|
||||
|
||||
// 响应式状态
|
||||
const loading = ref(false) // 主加载状态
|
||||
const tableLoading = ref(false) // 表格加载状态
|
||||
@@ -85,6 +90,22 @@ async function onDrop(e: DragEvent) {
|
||||
|
||||
// 批量获取产品信息 - 核心数据处理逻辑
|
||||
async function batchGetProductInfo(asinList: string[]) {
|
||||
// VIP检查
|
||||
if (!props.isVip) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'VIP已过期,数据采集功能受限。请联系管理员续费后继续使用。',
|
||||
'VIP功能限制',
|
||||
{
|
||||
confirmButtonText: '我知道了',
|
||||
showCancelButton: false,
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
} catch {}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
currentAsin.value = '正在处理...'
|
||||
progressPercentage.value = 0
|
||||
@@ -165,8 +186,6 @@ async function startQueuedFetch() {
|
||||
|
||||
// 导出Excel数据
|
||||
const exportLoading = ref(false)
|
||||
const exportProgress = ref(0)
|
||||
const showExportProgress = ref(false)
|
||||
|
||||
async function exportToExcel() {
|
||||
if (!localProductData.value.length) {
|
||||
@@ -175,12 +194,6 @@ async function exportToExcel() {
|
||||
}
|
||||
|
||||
exportLoading.value = true
|
||||
showExportProgress.value = true
|
||||
exportProgress.value = 0
|
||||
|
||||
const progressInterval = setInterval(() => {
|
||||
if (exportProgress.value < 90) exportProgress.value += Math.random() * 20
|
||||
}, 100)
|
||||
|
||||
// 生成Excel HTML格式
|
||||
let html = `<table>
|
||||
@@ -198,17 +211,12 @@ async function exportToExcel() {
|
||||
const blob = new Blob([html], { type: 'application/vnd.ms-excel' })
|
||||
const fileName = `Amazon产品数据_${new Date().toISOString().slice(0, 10)}.xls`
|
||||
|
||||
await handlePlatformFileExport('amazon', blob, fileName)
|
||||
const success = await handlePlatformFileExport('amazon', blob, fileName)
|
||||
|
||||
clearInterval(progressInterval)
|
||||
exportProgress.value = 100
|
||||
showMessage('Excel文件导出成功!', 'success')
|
||||
|
||||
setTimeout(() => {
|
||||
showExportProgress.value = false
|
||||
exportLoading.value = false
|
||||
exportProgress.value = 0
|
||||
}, 2000)
|
||||
if (success) {
|
||||
showMessage('Excel文件导出成功!', 'success')
|
||||
}
|
||||
exportLoading.value = false
|
||||
}
|
||||
|
||||
// 获取卖家/配送方信息 - 数据处理辅助函数
|
||||
@@ -360,13 +368,6 @@ onMounted(async () => {
|
||||
<div class="step-header"><div class="title">导出数据</div></div>
|
||||
<div class="action-buttons column">
|
||||
<el-button size="small" class="w100 btn-blue" :disabled="!localProductData.length || loading || exportLoading" :loading="exportLoading" @click="exportToExcel">{{ exportLoading ? '导出中...' : '导出Excel' }}</el-button>
|
||||
<!-- 导出进度条 -->
|
||||
<div v-if="showExportProgress" class="export-progress">
|
||||
<div class="export-progress-bar">
|
||||
<div class="export-progress-fill" :style="{ width: exportProgress + '%' }"></div>
|
||||
</div>
|
||||
<div class="export-progress-text">{{ Math.round(exportProgress) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -529,10 +530,6 @@ onMounted(async () => {
|
||||
.progress-fill { height: 100%; background: #1677FF; border-radius: 999px; transition: width 0.3s ease; }
|
||||
.progress-text { font-size: 13px; color: #1677FF; font-weight: 500; min-width: 44px; text-align: right; }
|
||||
.current-status { font-size: 12px; color: #606266; padding-left: 2px; }
|
||||
.export-progress { display: flex; align-items: center; gap: 8px; margin-top: 6px; padding: 0 4px; }
|
||||
.export-progress-bar { flex: 1; height: 4px; background: #e3eeff; border-radius: 2px; overflow: hidden; }
|
||||
.export-progress-fill { height: 100%; background: #1677FF; border-radius: 2px; transition: width 0.3s ease; }
|
||||
.export-progress-text { font-size: 11px; color: #1677FF; font-weight: 500; min-width: 32px; text-align: right; }
|
||||
.table-container { display: flex; flex-direction: column; flex: 1; min-height: 400px; overflow: hidden; }
|
||||
.table-section { flex: 1; overflow: hidden; position: relative; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; display: flex; flex-direction: column; }
|
||||
.table-wrapper { flex: 1; overflow: auto; }
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ElMessage } from 'element-plus'
|
||||
import { User } from '@element-plus/icons-vue'
|
||||
import { authApi } from '../../api/auth'
|
||||
import { deviceApi } from '../../api/device'
|
||||
import { getOrCreateDeviceId } from '../../utils/deviceId'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
@@ -11,7 +12,7 @@ interface Props {
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'loginSuccess', data: { token: string; user: any }): void
|
||||
(e: 'loginSuccess', data: { token: string; permissions?: string; expireTime?: string }): void
|
||||
(e: 'showRegister'): void
|
||||
}
|
||||
|
||||
@@ -31,16 +32,26 @@ async function handleAuth() {
|
||||
|
||||
authLoading.value = true
|
||||
try {
|
||||
await deviceApi.register({ username: authForm.value.username })
|
||||
const loginRes: any = await authApi.login(authForm.value)
|
||||
const data = loginRes?.data || loginRes
|
||||
// 获取或生成设备ID
|
||||
const deviceId = await getOrCreateDeviceId()
|
||||
|
||||
// 注册设备
|
||||
await deviceApi.register({
|
||||
username: authForm.value.username,
|
||||
deviceId: deviceId,
|
||||
os: navigator.platform
|
||||
})
|
||||
|
||||
// 登录
|
||||
const loginRes: any = await authApi.login({
|
||||
...authForm.value,
|
||||
clientId: deviceId
|
||||
})
|
||||
|
||||
emit('loginSuccess', {
|
||||
token: data.token,
|
||||
user: {
|
||||
username: data.username,
|
||||
permissions: data.permissions
|
||||
}
|
||||
token: loginRes.data.accessToken || loginRes.data.token,
|
||||
permissions: loginRes.data.permissions,
|
||||
expireTime: loginRes.data.expireTime
|
||||
})
|
||||
ElMessage.success('登录成功')
|
||||
resetForm()
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ref, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { User } from '@element-plus/icons-vue'
|
||||
import { authApi } from '../../api/auth'
|
||||
import { getOrCreateDeviceId } from '../../utils/deviceId'
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean
|
||||
@@ -10,7 +11,7 @@ interface Props {
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'loginSuccess', data: { token: string; user: any }): void
|
||||
(e: 'loginSuccess', data: { token: string; permissions?: string; expireTime?: string }): void
|
||||
(e: 'backToLogin'): void
|
||||
}
|
||||
|
||||
@@ -42,8 +43,8 @@ async function checkUsernameAvailability() {
|
||||
|
||||
try {
|
||||
const res: any = await authApi.checkUsername(registerForm.value.username)
|
||||
const data = res?.data || res
|
||||
usernameCheckResult.value = data?.available || false
|
||||
// 后端返回 {code: 200, data: true/false},data 直接是布尔值
|
||||
usernameCheckResult.value = res.data
|
||||
} catch {
|
||||
usernameCheckResult.value = null
|
||||
}
|
||||
@@ -54,23 +55,34 @@ async function handleRegister() {
|
||||
|
||||
registerLoading.value = true
|
||||
try {
|
||||
await authApi.register({
|
||||
// 获取设备ID
|
||||
const deviceId = await getOrCreateDeviceId()
|
||||
|
||||
// 注册账号(传递设备ID用于判断是否赠送VIP)
|
||||
const registerRes: any = await authApi.register({
|
||||
username: registerForm.value.username,
|
||||
password: registerForm.value.password
|
||||
password: registerForm.value.password,
|
||||
deviceId: deviceId
|
||||
})
|
||||
|
||||
const loginRes: any = await authApi.login({
|
||||
username: registerForm.value.username,
|
||||
password: registerForm.value.password
|
||||
})
|
||||
const loginData = loginRes?.data || loginRes
|
||||
|
||||
emit('loginSuccess', {
|
||||
token: loginData.token,
|
||||
user: {
|
||||
username: loginData.username,
|
||||
permissions: loginData.permissions
|
||||
|
||||
// 显示注册成功和VIP信息
|
||||
if (registerRes.data.expireTime) {
|
||||
const expireDate = new Date(registerRes.data.expireTime)
|
||||
const now = new Date()
|
||||
const daysLeft = Math.ceil((expireDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (daysLeft > 0) {
|
||||
ElMessage.success(`注册成功!您获得了 ${daysLeft} 天VIP体验`)
|
||||
} else {
|
||||
ElMessage.warning('注册成功!该设备已使用过新人福利,请联系管理员续费')
|
||||
}
|
||||
}
|
||||
|
||||
// 使用注册返回的token直接登录
|
||||
emit('loginSuccess', {
|
||||
token: registerRes.data.accessToken || registerRes.data.token,
|
||||
permissions: registerRes.data.permissions,
|
||||
expireTime: registerRes.data.expireTime
|
||||
})
|
||||
resetForm()
|
||||
} catch (err) {
|
||||
|
||||
@@ -117,14 +117,16 @@ const info = ref({
|
||||
const SKIP_VERSION_KEY = 'skipped_version'
|
||||
const REMIND_LATER_KEY = 'remind_later_time'
|
||||
|
||||
async function autoCheck() {
|
||||
async function autoCheck(silent = false) {
|
||||
try {
|
||||
version.value = await (window as any).electronAPI.getJarVersion()
|
||||
const checkRes: any = await updateApi.checkUpdate(version.value)
|
||||
const result = checkRes?.data || checkRes
|
||||
|
||||
if (!result.needUpdate) {
|
||||
ElMessage.info('当前已是最新版本')
|
||||
if (!silent) {
|
||||
ElMessage.info('当前已是最新版本')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -149,10 +151,14 @@ async function autoCheck() {
|
||||
}
|
||||
show.value = true
|
||||
stage.value = 'check'
|
||||
ElMessage.success('发现新版本')
|
||||
if (!silent) {
|
||||
ElMessage.success('发现新版本')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('检查更新失败:', error)
|
||||
ElMessage.error('检查更新失败')
|
||||
if (!silent) {
|
||||
ElMessage.error('检查更新失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +245,7 @@ async function installUpdate() {
|
||||
|
||||
onMounted(async () => {
|
||||
version.value = await (window as any).electronAPI.getJarVersion()
|
||||
await autoCheck(true)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, onMounted} from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {rakutenApi} from '../../api/rakuten'
|
||||
import { batchConvertImages } from '../../utils/imageProxy'
|
||||
import { handlePlatformFileExport } from '../../utils/settings'
|
||||
|
||||
// 接收VIP状态
|
||||
const props = defineProps<{
|
||||
isVip: boolean
|
||||
}>()
|
||||
|
||||
// UI 与加载状态
|
||||
const loading = ref(false)
|
||||
const tableLoading = ref(false)
|
||||
@@ -114,17 +119,22 @@ function needsSearch(product: any) {
|
||||
}
|
||||
|
||||
async function loadLatest() {
|
||||
const resp = await rakutenApi.getLatestProducts()
|
||||
allProducts.value = (resp.products || []).map(p => ({...p, skuPrices: parseSkuPrices(p)}))
|
||||
|
||||
const resp: any = await rakutenApi.getLatestProducts()
|
||||
const products = resp.data.products || []
|
||||
allProducts.value = products.map((p: any) => ({...p, skuPrices: parseSkuPrices(p)}))
|
||||
}
|
||||
|
||||
async function searchProductInternal(product: any) {
|
||||
if (!product || !product.imgUrl) return
|
||||
if (!needsSearch(product)) return
|
||||
const res = await rakutenApi.search1688(product.imgUrl, currentBatchId.value)
|
||||
const data = res
|
||||
const skuJson = (data as any)?.skuPriceJson ?? (data as any)?.skuPrice
|
||||
if (!props.isVip) {
|
||||
ElMessage.warning('VIP已过期,1688识图功能受限')
|
||||
return
|
||||
}
|
||||
|
||||
const res: any = await rakutenApi.search1688(product.imgUrl, currentBatchId.value)
|
||||
const data = res.data
|
||||
const skuJson = data.skuPriceJson || data.skuPrice
|
||||
Object.assign(product, {
|
||||
mapRecognitionLink: data.mapRecognitionLink,
|
||||
freight: data.freight,
|
||||
@@ -186,8 +196,24 @@ async function onDrop(e: DragEvent) {
|
||||
}
|
||||
|
||||
|
||||
// 点击“获取数据
|
||||
// 点击"获取数据
|
||||
async function handleStartSearch() {
|
||||
// VIP检查
|
||||
if (!props.isVip) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'VIP已过期,数据采集功能受限。请联系管理员续费后继续使用。',
|
||||
'VIP功能限制',
|
||||
{
|
||||
confirmButtonText: '我知道了',
|
||||
showCancelButton: false,
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
} catch {}
|
||||
return
|
||||
}
|
||||
|
||||
if (pendingFile.value) {
|
||||
try {
|
||||
loading.value = true
|
||||
@@ -199,8 +225,8 @@ async function handleStartSearch() {
|
||||
progressPercentage.value = 0
|
||||
totalProducts.value = 0
|
||||
processedProducts.value = 0
|
||||
const resp = await rakutenApi.getProducts({file: pendingFile.value, batchId: currentBatchId.value})
|
||||
const products = (resp.products || []).map(p => ({...p, skuPrices: parseSkuPrices(p)}))
|
||||
const resp: any = await rakutenApi.getProducts({file: pendingFile.value, batchId: currentBatchId.value})
|
||||
const products = (resp.data.products || []).map((p: any) => ({...p, skuPrices: parseSkuPrices(p)}))
|
||||
|
||||
if (products.length === 0) {
|
||||
showMessage('未采集到数据,请检查代理或店铺是否存在', 'warning')
|
||||
@@ -373,9 +399,11 @@ async function exportToExcel() {
|
||||
})
|
||||
const fileName = `乐天商品数据_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
|
||||
await handlePlatformFileExport('rakuten', blob, fileName)
|
||||
const success = await handlePlatformFileExport('rakuten', blob, fileName)
|
||||
|
||||
showMessage('Excel文件导出成功!', 'success')
|
||||
if (success) {
|
||||
showMessage('Excel文件导出成功!', 'success')
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('导出失败', 'error')
|
||||
} finally {
|
||||
|
||||
@@ -6,6 +6,11 @@ import AccountManager from '../common/AccountManager.vue'
|
||||
import { batchConvertImages } from '../../utils/imageProxy'
|
||||
import { handlePlatformFileExport } from '../../utils/settings'
|
||||
|
||||
// 接收VIP状态
|
||||
const props = defineProps<{
|
||||
isVip: boolean
|
||||
}>()
|
||||
|
||||
type Shop = { id: string; shopName: string }
|
||||
|
||||
const accounts = ref<BanmaAccount[]>([])
|
||||
@@ -86,6 +91,22 @@ function handleCurrentChange(page: number) {
|
||||
async function fetchData() {
|
||||
if (isFetching.value) return
|
||||
|
||||
// VIP检查
|
||||
if (!props.isVip) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'VIP已过期,数据采集功能受限。请联系管理员续费后继续使用。',
|
||||
'VIP功能限制',
|
||||
{
|
||||
confirmButtonText: '我知道了',
|
||||
showCancelButton: false,
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
} catch {}
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
isFetching.value = true
|
||||
showProgress.value = true
|
||||
@@ -237,9 +258,11 @@ async function exportToExcel() {
|
||||
})
|
||||
const fileName = `斑马订单数据_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
|
||||
await handlePlatformFileExport('zebra', blob, fileName)
|
||||
const success = await handlePlatformFileExport('zebra', blob, fileName)
|
||||
|
||||
showMessage('Excel文件导出成功!', 'success')
|
||||
if (success) {
|
||||
showMessage('Excel文件导出成功!', 'success')
|
||||
}
|
||||
} catch (error) {
|
||||
showMessage('导出失败', 'error')
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user