1
This commit is contained in:
@@ -6,7 +6,10 @@
|
||||
},
|
||||
"compression": "maximum",
|
||||
"asarUnpack": [
|
||||
"public/**/*"
|
||||
"public/jre/**/*",
|
||||
"public/icon/**/*",
|
||||
"public/image/**/*",
|
||||
"public/splash.html"
|
||||
],
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
@@ -40,15 +43,28 @@
|
||||
"filter": ["**/*"]
|
||||
},
|
||||
{
|
||||
|
||||
"from": "src/main/static",
|
||||
"to": "static",
|
||||
"filter": ["**/*"]
|
||||
},
|
||||
{
|
||||
"from": "public",
|
||||
"to": "assets",
|
||||
"filter": [
|
||||
"erp_client_sb-*.jar"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "public",
|
||||
"to": "public",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"jre/**/*",
|
||||
"icon/**/*",
|
||||
"image/**/*",
|
||||
"splash.html",
|
||||
"!erp_client_sb-*.jar",
|
||||
"!data/**/*",
|
||||
"!jre/bin/jabswitch.exe",
|
||||
"!jre/bin/jaccessinspector.exe",
|
||||
"!jre/bin/jaccesswalker.exe",
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
import {app, BrowserWindow, ipcMain, Menu, screen, dialog} from 'electron';
|
||||
import {existsSync, createWriteStream, promises as fs} from 'fs';
|
||||
import {existsSync, createWriteStream, promises as fs, mkdirSync, copyFileSync} from 'fs';
|
||||
import {join, dirname} from 'path';
|
||||
import {spawn, ChildProcess} from 'child_process';
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
|
||||
let springProcess: ChildProcess | null = null;
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let splashWindow: BrowserWindow | null = null;
|
||||
let appOpened = false;
|
||||
|
||||
let downloadProgress = {percentage: 0, current: '0 MB', total: '0 MB', speed: ''};
|
||||
let isDownloading = false;
|
||||
let downloadedFilePath: string | null = null;
|
||||
|
||||
function openAppIfNotOpened() {
|
||||
if (appOpened) return;
|
||||
appOpened = true;
|
||||
if (mainWindow) {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.once('did-finish-load', () => {
|
||||
setTimeout(() => {
|
||||
mainWindow?.show();
|
||||
mainWindow?.focus();
|
||||
if (splashWindow) {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
}
|
||||
|
||||
// 安全关闭启动画面
|
||||
if (splashWindow && !splashWindow.isDestroyed()) {
|
||||
splashWindow.close();
|
||||
splashWindow = null;
|
||||
}
|
||||
@@ -61,12 +62,24 @@ function getJarFilePath(): string {
|
||||
return join(__dirname, '../../public/erp_client_sb-2.4.7.jar');
|
||||
}
|
||||
|
||||
const bundledJarPath = join(process.resourcesPath, 'app.asar.unpacked/public/erp_client_sb-2.4.7.jar');
|
||||
if (existsSync(bundledJarPath)) {
|
||||
return bundledJarPath;
|
||||
// 生产环境:需要将JAR包从asar提取到临时位置
|
||||
const tempDir = join(app.getPath('temp'), 'erp-client');
|
||||
const tempJarPath = join(tempDir, 'erp_client_sb-2.4.7.jar');
|
||||
|
||||
// 确保临时目录存在
|
||||
if (!existsSync(tempDir)) {
|
||||
mkdirSync(tempDir, { recursive: true });
|
||||
}
|
||||
|
||||
return join(__dirname, '../../public/erp_client_sb-2.4.7.jar');
|
||||
// 如果临时JAR不存在,从asar中复制
|
||||
if (!existsSync(tempJarPath)) {
|
||||
const asarJarPath = join(__dirname, '../assets/erp_client_sb-2.4.7.jar');
|
||||
if (existsSync(asarJarPath)) {
|
||||
copyFileSync(asarJarPath, tempJarPath);
|
||||
}
|
||||
}
|
||||
|
||||
return tempJarPath;
|
||||
}
|
||||
|
||||
function getSplashPath(): string {
|
||||
@@ -95,9 +108,48 @@ function getIconPath(): string {
|
||||
return join(__dirname, '../renderer/icon/icon.png');
|
||||
}
|
||||
|
||||
function getDataDirectoryPath(): string {
|
||||
// 将用户数据目录放在可写的应用数据目录下
|
||||
const userDataPath = app.getPath('userData');
|
||||
const dataDir = join(userDataPath, 'data');
|
||||
|
||||
// 确保数据目录存在
|
||||
if (!existsSync(dataDir)) {
|
||||
mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
return dataDir;
|
||||
}
|
||||
|
||||
function migrateDataFromPublic(): void {
|
||||
// 如果是首次运行,尝试从public/data迁移数据
|
||||
const oldDataPath = join(__dirname, '../../public/data');
|
||||
const newDataPath = getDataDirectoryPath();
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && existsSync(oldDataPath)) {
|
||||
try {
|
||||
const files = require('fs').readdirSync(oldDataPath);
|
||||
for (const file of files) {
|
||||
const srcFile = join(oldDataPath, file);
|
||||
const destFile = join(newDataPath, file);
|
||||
|
||||
if (!existsSync(destFile)) {
|
||||
require('fs').copyFileSync(srcFile, destFile);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('数据迁移失败,使用默认配置');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startSpringBoot() {
|
||||
// 首先迁移数据(如果需要)
|
||||
migrateDataFromPublic();
|
||||
|
||||
const jarPath = getJarFilePath();
|
||||
const javaPath = getJavaExecutablePath();
|
||||
const dataDir = getDataDirectoryPath();
|
||||
|
||||
if (!existsSync(jarPath)) {
|
||||
dialog.showErrorBox('启动失败', `JAR 文件不存在:\n${jarPath}`);
|
||||
@@ -106,15 +158,31 @@ function startSpringBoot() {
|
||||
}
|
||||
|
||||
try {
|
||||
springProcess = spawn(javaPath, ['-jar', jarPath], {
|
||||
cwd: dirname(jarPath),
|
||||
detached: false
|
||||
// Spring Boot启动参数配置
|
||||
const springArgs = [
|
||||
'-jar', jarPath,
|
||||
`--spring.datasource.url=jdbc:sqlite:${dataDir}/erp-cache.db`,
|
||||
`--logging.file.path=${dataDir}`,
|
||||
`--server.port=8081`
|
||||
];
|
||||
|
||||
// 工作目录设为数据目录,这样Spring Boot会在数据目录下创建临时文件
|
||||
springProcess = spawn(javaPath, springArgs, {
|
||||
cwd: dataDir,
|
||||
detached: false,
|
||||
env: {
|
||||
...process.env,
|
||||
'ERP_DATA_DIR': dataDir,
|
||||
'USER_DATA_DIR': dataDir
|
||||
}
|
||||
});
|
||||
|
||||
let startupCompleted = false;
|
||||
|
||||
springProcess.stdout?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
console.log('[Spring Boot]', output.trim());
|
||||
|
||||
if (!startupCompleted && (output.includes('Started Success') || output.includes('Started ErpClientSbApplication'))) {
|
||||
startupCompleted = true;
|
||||
openAppIfNotOpened();
|
||||
@@ -185,6 +253,16 @@ function createWindow() {
|
||||
Menu.setApplicationMenu(null);
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
|
||||
// 打开开发者工具
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
// 监听窗口关闭事件,确保正确清理引用
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
mainWindow.webContents.once('did-finish-load', () => {
|
||||
setTimeout(() => checkPendingUpdate(), 500);
|
||||
});
|
||||
@@ -212,6 +290,11 @@ app.whenReady().then(() => {
|
||||
}
|
||||
});
|
||||
|
||||
// 监听启动窗口关闭事件
|
||||
splashWindow.on('closed', () => {
|
||||
splashWindow = null;
|
||||
});
|
||||
|
||||
const splashPath = getSplashPath();
|
||||
if (existsSync(splashPath)) {
|
||||
splashWindow.loadFile(splashPath);
|
||||
@@ -278,31 +361,6 @@ ipcMain.handle('download-update', async (event, downloadUrl: string) => {
|
||||
isDownloading = true;
|
||||
|
||||
try {
|
||||
const isRealDev = process.env.NODE_ENV === 'development' && !app.isPackaged;
|
||||
|
||||
if (isRealDev) {
|
||||
for (let i = 0; i <= 100; i += 10) {
|
||||
setTimeout(() => {
|
||||
downloadProgress = {
|
||||
percentage: i,
|
||||
current: (i * 0.5).toFixed(1) + ' MB',
|
||||
total: '50.0 MB',
|
||||
speed: '2.5 MB/s'
|
||||
};
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('download-progress', downloadProgress);
|
||||
}
|
||||
}, i * 100);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
downloadedFilePath = 'completed';
|
||||
isDownloading = false;
|
||||
}, 1100);
|
||||
|
||||
return {success: true, filePath: 'dev-mode-simulated', dev: true};
|
||||
}
|
||||
|
||||
const tempPath = join(app.getPath('temp'), 'app.asar.new');
|
||||
|
||||
await downloadFile(downloadUrl, tempPath, (progress) => {
|
||||
@@ -353,13 +411,6 @@ ipcMain.handle('get-download-progress', () => {
|
||||
|
||||
ipcMain.handle('install-update', async () => {
|
||||
try {
|
||||
const isRealDev = process.env.NODE_ENV === 'development' && !app.isPackaged;
|
||||
|
||||
if (isRealDev) {
|
||||
downloadedFilePath = null;
|
||||
return {success: true, message: '开发环境模拟重启'};
|
||||
}
|
||||
|
||||
const updateFilePath = join(process.resourcesPath, 'app.asar.update');
|
||||
const hasUpdateFile = existsSync(updateFilePath);
|
||||
|
||||
@@ -406,38 +457,23 @@ ipcMain.handle('cancel-download', () => {
|
||||
});
|
||||
|
||||
ipcMain.handle('get-update-status', () => {
|
||||
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged || process.defaultApp;
|
||||
return {downloadedFilePath, isDownloading, downloadProgress, isPackaged: app.isPackaged, isDev};
|
||||
return {downloadedFilePath, isDownloading, downloadProgress, isPackaged: app.isPackaged};
|
||||
});
|
||||
|
||||
// 添加文件保存对话框处理器
|
||||
ipcMain.handle('show-save-dialog', async (event, options) => {
|
||||
if (!mainWindow) {
|
||||
return {canceled: true};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await dialog.showSaveDialog(mainWindow, options);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('文件保存对话框错误:', error);
|
||||
return {canceled: true, error: error instanceof Error ? error.message : '对话框打开失败'};
|
||||
}
|
||||
return await dialog.showSaveDialog(mainWindow!, options);
|
||||
});
|
||||
|
||||
// 添加文件夹选择对话框处理器
|
||||
ipcMain.handle('show-open-dialog', async (event, options) => {
|
||||
if (!mainWindow) {
|
||||
return {canceled: true};
|
||||
}
|
||||
return await dialog.showOpenDialog(mainWindow!, options);
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await dialog.showOpenDialog(mainWindow, options);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('文件夹选择对话框错误:', error);
|
||||
return {canceled: true, error: error instanceof Error ? error.message : '对话框打开失败'};
|
||||
}
|
||||
// 添加文件写入处理器
|
||||
ipcMain.handle('write-file', async (event, filePath: string, data: Uint8Array) => {
|
||||
await fs.writeFile(filePath, Buffer.from(data));
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ const electronAPI = {
|
||||
showSaveDialog: (options: any) => ipcRenderer.invoke('show-save-dialog', options),
|
||||
// 添加文件夹选择对话框 API
|
||||
showOpenDialog: (options: any) => ipcRenderer.invoke('show-open-dialog', options),
|
||||
// 添加文件写入 API
|
||||
writeFile: (filePath: string, data: Uint8Array) => ipcRenderer.invoke('write-file', filePath, data),
|
||||
|
||||
onDownloadProgress: (callback: (progress: any) => void) => {
|
||||
ipcRenderer.on('download-progress', (event, progress) => callback(progress))
|
||||
|
||||
@@ -12,6 +12,7 @@ const RakutenDashboard = defineAsyncComponent(() => import('./components/rakuten
|
||||
const AmazonDashboard = defineAsyncComponent(() => import('./components/amazon/AmazonDashboard.vue'))
|
||||
const ZebraDashboard = defineAsyncComponent(() => import('./components/zebra/ZebraDashboard.vue'))
|
||||
const UpdateDialog = defineAsyncComponent(() => import('./components/common/UpdateDialog.vue'))
|
||||
const SettingsDialog = defineAsyncComponent(() => import('./components/common/SettingsDialog.vue'))
|
||||
|
||||
const dashboardsMap: Record<string, Component> = {
|
||||
rakuten: RakutenDashboard,
|
||||
@@ -48,6 +49,9 @@ const userPermissions = ref<string>('')
|
||||
// 更新对话框状态
|
||||
const showUpdateDialog = ref(false)
|
||||
|
||||
// 设置对话框状态
|
||||
const showSettingsDialog = ref(false)
|
||||
|
||||
// 菜单配置 - 复刻ERP客户端格式
|
||||
const menuConfig = [
|
||||
{key: 'rakuten', name: 'Rakuten', index: 'rakuten', icon: 'R'},
|
||||
@@ -69,8 +73,6 @@ function hasPermission(module: string) {
|
||||
if (!permissions) {
|
||||
return defaultModules.includes(module) // 没有权限信息时显示默认菜单
|
||||
}
|
||||
|
||||
// 简化权限检查:直接检查模块名是否在权限字符串中
|
||||
return permissions.includes(module)
|
||||
}
|
||||
|
||||
@@ -132,6 +134,7 @@ function handleMenuSelect(key: string) {
|
||||
async function handleLoginSuccess(data: { token: string; permissions?: string }) {
|
||||
isAuthenticated.value = true
|
||||
showAuthDialog.value = false
|
||||
showRegDialog.value = false // 确保注册对话框也关闭
|
||||
|
||||
try {
|
||||
// 保存token到本地数据库
|
||||
@@ -209,11 +212,6 @@ function showRegisterDialog() {
|
||||
showRegDialog.value = true
|
||||
}
|
||||
|
||||
function handleRegisterSuccess() {
|
||||
showRegDialog.value = false
|
||||
showAuthDialog.value = true
|
||||
}
|
||||
|
||||
function backToLogin() {
|
||||
showRegDialog.value = false
|
||||
showAuthDialog.value = true
|
||||
@@ -362,6 +360,10 @@ async function openDeviceManager() {
|
||||
await fetchDeviceData()
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
showSettingsDialog.value = true
|
||||
}
|
||||
|
||||
async function fetchDeviceData() {
|
||||
if (!currentUsername.value) {
|
||||
ElMessage({
|
||||
@@ -472,7 +474,8 @@ onUnmounted(() => {
|
||||
@go-forward="goForward"
|
||||
@reload="reloadPage"
|
||||
@user-click="handleUserClick"
|
||||
@open-device="openDeviceManager"/>
|
||||
@open-device="openDeviceManager"
|
||||
@open-settings="openSettings"/>
|
||||
<div class="content-body">
|
||||
<div
|
||||
class="dashboard-home"
|
||||
@@ -500,12 +503,15 @@ onUnmounted(() => {
|
||||
|
||||
<RegisterDialog
|
||||
v-model="showRegDialog"
|
||||
@register-success="handleRegisterSuccess"
|
||||
@login-success="handleLoginSuccess"
|
||||
@back-to-login="backToLogin"/>
|
||||
|
||||
<!-- 更新对话框 -->
|
||||
<UpdateDialog v-model="showUpdateDialog" />
|
||||
|
||||
<!-- 设置对话框 -->
|
||||
<SettingsDialog v-model="showSettingsDialog" />
|
||||
|
||||
<!-- 设备管理弹框 -->
|
||||
<el-dialog
|
||||
v-model="showDeviceDialog"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
export type HttpMethod = 'GET' | 'POST';
|
||||
|
||||
const BASE_CLIENT = 'http://localhost:8081'; // erp_client_sb
|
||||
const BASE_RUOYI = 'http://localhost:8080';
|
||||
const BASE_RUOYI = 'http://192.168.1.89:8080';
|
||||
|
||||
function resolveBase(path: string): string {
|
||||
// 走 ruoyi-admin 的路径:鉴权与版本、平台工具路由
|
||||
|
||||
@@ -53,7 +53,7 @@ export const zebraApi = {
|
||||
return http.delete<void>(`/tool/banma/accounts/${id}`);
|
||||
},
|
||||
|
||||
// 业务采集(仍走客户端微服务 8081)
|
||||
// 业务采集
|
||||
getShops(params?: { accountId?: number }) {
|
||||
return http.get<{ data?: { list?: Array<{ id: string; shopName: string }> } }>(
|
||||
'/api/banma/shops', params as unknown as Record<string, unknown>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { amazonApi } from '../../api/amazon'
|
||||
import { handlePlatformFileExport } from '../../utils/settings'
|
||||
|
||||
// 响应式状态
|
||||
const loading = ref(false) // 主加载状态
|
||||
@@ -196,11 +197,9 @@ async function exportToExcel() {
|
||||
html += '</table>'
|
||||
|
||||
const blob = new Blob([html], { type: 'application/vnd.ms-excel' })
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `Amazon产品数据_${new Date().toISOString().slice(0, 10)}.xls`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
const fileName = `Amazon产品数据_${new Date().toISOString().slice(0, 10)}.xls`
|
||||
|
||||
await handlePlatformFileExport('amazon', blob, fileName)
|
||||
|
||||
clearInterval(progressInterval)
|
||||
exportProgress.value = 100
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
(e: 'registerSuccess'): void
|
||||
(e: 'loginSuccess', data: { token: string; user: any }): void
|
||||
(e: 'backToLogin'): void
|
||||
}
|
||||
|
||||
@@ -53,12 +53,25 @@ async function handleRegister() {
|
||||
|
||||
registerLoading.value = true
|
||||
try {
|
||||
const result = await authApi.register({
|
||||
// 1. 注册
|
||||
await authApi.register({
|
||||
username: registerForm.value.username,
|
||||
password: registerForm.value.password
|
||||
})
|
||||
ElMessage.success(result.message || '注册成功,请登录')
|
||||
emit('registerSuccess')
|
||||
|
||||
// 2. 注册成功后直接登录
|
||||
const loginData = await authApi.login({
|
||||
username: registerForm.value.username,
|
||||
password: registerForm.value.password
|
||||
})
|
||||
|
||||
emit('loginSuccess', {
|
||||
token: loginData.token,
|
||||
user: {
|
||||
username: loginData.username,
|
||||
permissions: loginData.permissions
|
||||
}
|
||||
})
|
||||
resetForm()
|
||||
} catch (err) {
|
||||
ElMessage.error((err as Error).message)
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
type PlatformKey = 'zebra' | 'shopee' | 'rakuten' | 'amazon'
|
||||
|
||||
const props = defineProps<{ modelValue: boolean; platform?: PlatformKey }>()
|
||||
const emit = defineEmits(['update:modelValue', 'add'])
|
||||
const emit = defineEmits(['update:modelValue', 'add', 'refresh'])
|
||||
const visible = computed({ get: () => props.modelValue, set: v => emit('update:modelValue', v) })
|
||||
const curPlatform = ref<PlatformKey>(props.platform || 'zebra')
|
||||
const PLATFORM_LABEL: Record<PlatformKey, string> = {
|
||||
@@ -23,6 +23,9 @@ async function load() {
|
||||
const list = (res as any)?.data ?? res
|
||||
accounts.value = Array.isArray(list) ? list : []
|
||||
}
|
||||
|
||||
// 暴露方法供父组件调用
|
||||
defineExpose({ load })
|
||||
onMounted(load)
|
||||
|
||||
function switchPlatform(p: PlatformKey) {
|
||||
@@ -38,11 +41,12 @@ function formatDate(a: any) {
|
||||
async function onDelete(a: any) {
|
||||
const id = a?.id
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除账号 “${a?.name || a?.username || id}” 吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除账号 "${a?.name || a?.username || id}" 吗?`, '提示', { type: 'warning' })
|
||||
} catch { return }
|
||||
await zebraApi.removeAccount(id)
|
||||
ElMessage({ message: '删除成功', type: 'success' })
|
||||
await load()
|
||||
emit('refresh') // 通知外层组件刷新账号列表
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -106,11 +106,13 @@ type Stage = 'check' | 'downloading' | 'completed'
|
||||
const stage = ref<Stage>('check')
|
||||
const appName = ref('我了个电商')
|
||||
const version = ref('2.0.0')
|
||||
const prog = ref({ percentage: 0, current: '0 MB', total: '0 MB', speed: '' as string | undefined })
|
||||
const prog = ref({ percentage: 0, current: '0 MB', total: '0 MB', speed: '' })
|
||||
const info = ref({
|
||||
latestVersion: '2.4.8',
|
||||
downloadUrl: '',
|
||||
updateNotes: '• 优化了用户界面体验\n• 修复了已知问题\n• 提升了系统稳定性\n• 增加了新的功能模块\n• 优化了数据处理性能'
|
||||
updateNotes: '• 优化了用户界面体验\n• 修复了已知问题\n• 提升了系统稳定性\n• 增加了新的功能模块\n• 优化了数据处理性能',
|
||||
currentVersion: '',
|
||||
hasUpdate: false
|
||||
})
|
||||
|
||||
async function autoCheck() {
|
||||
@@ -143,26 +145,24 @@ async function autoCheck() {
|
||||
|
||||
async function start() {
|
||||
if (!info.value.downloadUrl) {
|
||||
ElMessage({ message: '下载链接不可用', type: 'error' })
|
||||
return
|
||||
ElMessage({ message: '下载链接不可用', type: 'error' });
|
||||
return;
|
||||
}
|
||||
|
||||
stage.value = 'downloading'
|
||||
prog.value = { percentage: 0, current: '0 MB', total: '0 MB', speed: '' }
|
||||
stage.value = 'downloading';
|
||||
prog.value = { percentage: 0, current: '0 MB', total: '0 MB', speed: '' };
|
||||
|
||||
|
||||
|
||||
window.electronAPI.onDownloadProgress((progress) => {
|
||||
(window as any).electronAPI.onDownloadProgress((progress: any) => {
|
||||
prog.value = {
|
||||
percentage: progress.percentage || 0,
|
||||
current: progress.current || '0 MB',
|
||||
total: progress.total || '0 MB',
|
||||
speed: progress.speed || ''
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await window.electronAPI.downloadUpdate(info.value.downloadUrl)
|
||||
const response = await (window as any).electronAPI.downloadUpdate(info.value.downloadUrl)
|
||||
|
||||
if (response.success) {
|
||||
stage.value = 'completed'
|
||||
@@ -181,10 +181,8 @@ async function start() {
|
||||
|
||||
async function cancelDownload() {
|
||||
try {
|
||||
if (window.electronAPI) {
|
||||
window.electronAPI.removeDownloadProgressListener()
|
||||
await window.electronAPI.cancelDownload()
|
||||
}
|
||||
(window as any).electronAPI.removeDownloadProgressListener()
|
||||
await (window as any).electronAPI.cancelDownload()
|
||||
show.value = false
|
||||
stage.value = 'check'
|
||||
} catch (error) {
|
||||
@@ -205,7 +203,7 @@ async function installUpdate() {
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
const response = await window.electronAPI.installUpdate()
|
||||
const response = await (window as any).electronAPI.installUpdate()
|
||||
|
||||
if (response.success) {
|
||||
ElMessage({ message: '应用即将重启', type: 'success' })
|
||||
@@ -231,9 +229,7 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (window.electronAPI) {
|
||||
window.electronAPI.removeDownloadProgressListener()
|
||||
}
|
||||
(window as any).electronAPI.removeDownloadProgressListener()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ interface Emits {
|
||||
(e: 'reload'): void
|
||||
(e: 'user-click'): void
|
||||
(e: 'open-device'): void
|
||||
(e: 'open-settings'): void
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
@@ -45,7 +46,7 @@ defineEmits<Emits>()
|
||||
<button class="nav-btn-round" title="设备管理" @click="$emit('open-device')">
|
||||
<el-icon><Monitor /></el-icon>
|
||||
</button>
|
||||
<button class="nav-btn-round" title="设置">
|
||||
<button class="nav-btn-round" title="设置" @click="$emit('open-settings')">
|
||||
<el-icon><Setting /></el-icon>
|
||||
</button>
|
||||
<button class="nav-btn-round" title="用户" @click="$emit('user-click')">
|
||||
|
||||
@@ -3,6 +3,7 @@ import {ref, computed, onMounted} from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {rakutenApi} from '../../api/rakuten'
|
||||
import { batchConvertImages } from '../../utils/imageProxy'
|
||||
import { handlePlatformFileExport } from '../../utils/settings'
|
||||
|
||||
// UI 与加载状态
|
||||
const loading = ref(false)
|
||||
@@ -200,10 +201,15 @@ 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)}))
|
||||
allProducts.value = products
|
||||
pendingFile.value = null
|
||||
const resp = await rakutenApi.getProducts({file: pendingFile.value, batchId: currentBatchId.value})
|
||||
const products = (resp.products || []).map(p => ({...p, skuPrices: parseSkuPrices(p)}))
|
||||
|
||||
if (products.length === 0) {
|
||||
showMessage('未采集到数据,请检查代理或店铺是否存在', 'warning')
|
||||
}
|
||||
|
||||
allProducts.value = products
|
||||
pendingFile.value = null
|
||||
} catch (e) {
|
||||
statusType.value = 'error'
|
||||
statusMessage.value = '解析失败,请重试'
|
||||
@@ -367,12 +373,9 @@ async function exportToExcel() {
|
||||
const blob = new Blob([buffer], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const fileName = `乐天商品数据_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `乐天商品数据_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
await handlePlatformFileExport('rakuten', blob, fileName)
|
||||
|
||||
showMessage('Excel文件导出成功!', 'success')
|
||||
} catch (error) {
|
||||
@@ -412,8 +415,7 @@ onMounted(loadLatest)
|
||||
<div class="dropzone" @dragover.prevent="onDragOver" @dragleave="onDragLeave" @drop="onDrop" @click="openRakutenUpload" :class="{ disabled: loading }">
|
||||
<div class="dz-el-icon">📤</div>
|
||||
<div class="dz-text">点击或将文件拖拽到这里上传</div>
|
||||
<div class="dz-sub">支持扩展名:.xls .xlsx</div>
|
||||
<div class="dz-sub">文件单列:1/1</div>
|
||||
<div class="dz-sub">支持 .xls .xlsx</div>
|
||||
</div>
|
||||
<input ref="uploadInputRef" style="display:none" type="file" accept=".xls,.xlsx" @change="handleExcelUpload" :disabled="loading"/>
|
||||
<div v-if="selectedFileName" class="file-chip">
|
||||
@@ -621,10 +623,10 @@ onMounted(loadLatest)
|
||||
.content-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
||||
|
||||
.left-controls { margin-top: 10px; display: flex; flex-direction: column; gap: 10px; }
|
||||
.dropzone { border: 1px dashed #c0c4cc; border-radius: 6px; padding: 16px; text-align: center; cursor: pointer; background: #fafafa; }
|
||||
.dropzone { border: 1px dashed #c0c4cc; border-radius: 6px; padding: 12px; text-align: center; cursor: pointer; background: #fafafa; }
|
||||
.dropzone:hover { background: #f6fbff; border-color: #409EFF; }
|
||||
.dropzone.disabled { opacity: .6; cursor: not-allowed; }
|
||||
.dz-el-icon { font-size: 20px; margin-bottom: 6px; color: #909399; }
|
||||
.dz-el-icon { font-size: 18px; margin-bottom: 4px; color: #909399; }
|
||||
.dz-text { color: #303133; font-size: 13px; }
|
||||
.dz-sub { color: #909399; font-size: 12px; }
|
||||
.single-input.left { display: flex; gap: 8px; }
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { zebraApi, type ZebraOrder, type BanmaAccount } from '../../api/zebra'
|
||||
import AccountManager from '../common/AccountManager.vue'
|
||||
import { batchConvertImages } from '../../utils/imageProxy'
|
||||
import { handlePlatformFileExport } from '../../utils/settings'
|
||||
|
||||
type Shop = { id: string; shopName: string }
|
||||
|
||||
@@ -234,12 +235,9 @@ async function exportToExcel() {
|
||||
const blob = new Blob([buffer], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
})
|
||||
const fileName = `斑马订单数据_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = `斑马订单数据_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
link.click()
|
||||
URL.revokeObjectURL(link.href)
|
||||
await handlePlatformFileExport('zebra', blob, fileName)
|
||||
|
||||
showMessage('Excel文件导出成功!', 'success')
|
||||
} catch (error) {
|
||||
@@ -266,6 +264,7 @@ const formUsername = ref('')
|
||||
const formPassword = ref('')
|
||||
const rememberPwd = ref(true)
|
||||
const managerVisible = ref(false)
|
||||
const accountManagerRef = ref()
|
||||
|
||||
function openAddAccount() {
|
||||
isEditMode.value = false
|
||||
@@ -302,6 +301,9 @@ async function submitAccount() {
|
||||
accountDialogVisible.value = false
|
||||
await loadAccounts()
|
||||
if (id) accountId.value = id
|
||||
if (managerVisible.value && accountManagerRef.value?.load) {
|
||||
accountManagerRef.value.load()
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage({ message: e?.message || '账号或密码错误,无法获取Token', type: 'error' })
|
||||
}
|
||||
@@ -519,7 +521,7 @@ async function removeCurrentAccount() {
|
||||
<el-button type="primary" class="btn-blue" style="width: 100%" @click="submitAccount">登录</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<AccountManager v-model="managerVisible" platform="zebra" @add="openAddAccount" />
|
||||
<AccountManager ref="accountManagerRef" v-model="managerVisible" platform="zebra" @add="openAddAccount" @refresh="loadAccounts" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -114,9 +114,8 @@ public class AmazonScrapingServiceImpl implements IAmazonScrapingService, PagePr
|
||||
if (asin == null || asin.trim().isEmpty()) continue;
|
||||
String cleanAsin = asin.replaceAll("[^a-zA-Z0-9]", "");
|
||||
|
||||
// 查找缓存,有缓存就用缓存,没缓存就爬取
|
||||
Optional<AmazonProductEntity> cached = amazonProductRepository.findByAsin(cleanAsin);
|
||||
if (cached.isPresent()) {
|
||||
if (cached.isPresent() && !isEmpty(cached.get().getPrice()) && !isEmpty(cached.get().getSeller())) {
|
||||
AmazonProductEntity entity = cached.get();
|
||||
entity.setSessionId(sessionId);
|
||||
entity.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
@@ -36,10 +36,17 @@ public class BanmaOrderController extends BaseController {
|
||||
*/
|
||||
@PostMapping("/accounts")
|
||||
public R<?> saveAccount(@RequestBody BanmaAccount body) {
|
||||
// 先验证Token
|
||||
String token = ((com.ruoyi.system.service.impl.BanmaAccountServiceImpl) accountService)
|
||||
.validateAndGetToken(body.getUsername(), body.getPassword());
|
||||
if (token == null) {
|
||||
return R.fail("账号或密码错误,无法获取Token");
|
||||
}
|
||||
// 验证成功后保存账号
|
||||
Long id = accountService.saveOrUpdate(body);
|
||||
boolean ok = false;
|
||||
try { ok = accountService.refreshToken(id); } catch (Exception ignore) {}
|
||||
return ok ? R.ok(Map.of("id", id)) : R.fail("账号或密码错误,无法获取Token");
|
||||
// 刷新Token到数据库
|
||||
accountService.refreshToken(id);
|
||||
return R.ok(Map.of("id", id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user