refactor(auth):重构认证服务并移除冗余代码

- 移除了 AuthServiceImpl 中的登录、注册、token 验证等方法,仅保留错误上报和客户端信息功能
- 删除了设备注册和离线通知相关逻辑
- 移除了 IAuthService 接口中的登录、注册、验证 token 等方法定义
- 清理了 AccountManager.vue 中的无关注释文字-优化了阿里巴巴1688 服务中的图片上传处理逻辑- 移除了 AmazonScrapingServiceImpl 中未使用的日志导入和空行
- 统一了 Vue 组件中的同步导入方式,替换异步组件定义
- 更新了应用配置文件中的服务器地址和懒加载设置
- 新增缓存管理服务用于统一清理各类缓存数据
- 优化了设备 IP 地址获取逻辑并在注册时传递给后端- 调整了构建配置以减小安装包体积并支持多语言
- 修改了主进程窗口加载逻辑以适配开发与生产环境- 添加了全局样式限制图片预览器尺寸
- 移除了设备 ID 测试类和部分无用的正则表达式导入
This commit is contained in:
2025-10-20 18:01:40 +08:00
parent 0c85aa5677
commit 17f03c3ade
27 changed files with 517 additions and 535 deletions

View File

@@ -15,6 +15,7 @@
"directories": {
"output": "dist"
},
"electronLanguages": ["zh-CN", "en-US"],
"nsis": {
"oneClick": false,
"perMachine": false,
@@ -22,7 +23,7 @@
"shortcutName": "erpClient"
},
"win": {
"target": "nsis",
"target": "dir",
"icon": "public/icon/icon.png"
},
"files": [
@@ -53,23 +54,10 @@
"config/**/*",
"!erp_client_sb-*.jar",
"!data/**/*",
"!jre/bin/jab*.exe",
"!jre/bin/jac*.exe",
"!jre/bin/jar*.exe",
"!jre/bin/jc*.exe",
"!jre/bin/jd*.exe",
"!jre/bin/jf*.exe",
"!jre/bin/jh*.exe",
"!jre/bin/ji*.exe",
"!jre/bin/jl*.exe",
"!jre/bin/jm*.exe",
"!jre/bin/jp*.exe",
"!jre/bin/jr*.exe",
"!jre/bin/jsh*.exe",
"!jre/bin/jst*.exe",
"!jre/bin/k*.exe",
"!jre/bin/rmi*.exe",
"!jre/bin/serial*.exe",
"!jre/bin/*.exe",
"jre/bin/java.exe",
"jre/bin/javaw.exe",
"jre/bin/keytool.exe",
"!jre/include/**",
"!jre/lib/src.zip",
"!jre/lib/ct.sym",

View File

@@ -5,10 +5,10 @@
"main": "main/main.js",
"scripts": {
"dev": "node scripts/dev-server.js",
"build": "node scripts/build.js && electron-builder",
"build:win": "node scripts/build.js && electron-builder --win",
"build:mac": "node scripts/build.js && electron-builder --mac",
"build:linux": "node scripts/build.js && electron-builder --linux"
"build": "node scripts/build.js && electron-builder --dir",
"build:win": "node scripts/build.js && electron-builder --win --dir",
"build:mac": "node scripts/build.js && electron-builder --mac --dir",
"build:linux": "node scripts/build.js && electron-builder --linux --dir"
},
"repository": "https://github.com/deluze/electron-vue-template",
"author": {

View File

@@ -23,23 +23,27 @@ function openAppIfNotOpened() {
if (appOpened) return;
appOpened = true;
if (mainWindow && !mainWindow.isDestroyed()) {
isDev
? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`)
: mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
mainWindow.webContents.once('did-finish-load', () => {
setTimeout(() => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.show();
mainWindow.focus();
const config = loadConfig();
const shouldMinimize = config.launchMinimized || false;
if (!shouldMinimize) {
mainWindow.show();
mainWindow.focus();
}
if (isDev) mainWindow.webContents.openDevTools();
}
if (splashWindow && !splashWindow.isDestroyed()) {
splashWindow.close();
splashWindow = null;
}
}, 1000);
}, 500);
});
isDev
? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`)
: mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
}
}
@@ -78,11 +82,17 @@ function getDataDirectoryPath(): string {
return dataDir;
}
interface AppConfig {
closeAction?: 'quit' | 'minimize' | 'tray';
autoLaunch?: boolean;
launchMinimized?: boolean;
}
function getConfigPath(): string {
return join(app.getPath('userData'), 'config.json');
}
function loadConfig(): { closeAction?: 'quit' | 'minimize' | 'tray' } {
function loadConfig(): AppConfig {
try {
const configPath = getConfigPath();
if (existsSync(configPath)) {
@@ -94,7 +104,7 @@ function loadConfig(): { closeAction?: 'quit' | 'minimize' | 'tray' } {
return {};
}
function saveConfig(config: any) {
function saveConfig(config: AppConfig) {
try {
const configPath = getConfigPath();
require('fs').writeFileSync(configPath, JSON.stringify(config, null, 2));
@@ -138,7 +148,6 @@ function startSpringBoot() {
}
try {
// Spring Boot启动参数配置
const springArgs = [
'-jar', jarPath,
`--spring.datasource.url=jdbc:sqlite:${dataDir}/erp-cache.db`,
@@ -146,32 +155,20 @@ function startSpringBoot() {
`--logging.config=file:${logbackConfigPath}`
];
// 工作目录设为数据目录这样Spring Boot会在数据目录下创建临时文件
springProcess = spawn(javaPath, springArgs, {
cwd: dataDir,
detached: false
detached: false,
stdio: ['ignore', 'pipe', 'pipe']
});
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();
}
console.log('[Spring Boot]', data.toString().trim());
});
springProcess.stderr?.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();
}
console.log('[Spring Boot]', data.toString().trim());
});
springProcess.on('close', (code) => {
@@ -187,8 +184,25 @@ function startSpringBoot() {
app.quit();
});
const checkHealth = () => {
if (startupCompleted) return;
http.get('http://127.0.0.1:8081', (res) => {
if (!startupCompleted) {
startupCompleted = true;
console.log('[Spring Boot] 服务已就绪');
openAppIfNotOpened();
}
}).on('error', () => {
setTimeout(checkHealth, 200);
});
};
setTimeout(checkHealth, 1000);
setTimeout(() => {
if (!startupCompleted) {
console.log('[Spring Boot] 启动超时,强制打开窗口');
openAppIfNotOpened();
}
}, 15000);
@@ -199,7 +213,7 @@ function startSpringBoot() {
}
}
startSpringBoot();
// startSpringBoot();
function stopSpringBoot() {
if (!springProcess) return;
@@ -225,9 +239,10 @@ function createWindow() {
mainWindow = new BrowserWindow({
width: 1280,
height: 800,
show: false,
show: false, //
autoHideMenuBar: true,
icon: getIconPath(),
backgroundColor: '#f5f5f5',
webPreferences: {
preload: join(__dirname, 'preload.js'),
nodeIntegration: false,
@@ -266,38 +281,52 @@ function createWindow() {
}
app.whenReady().then(() => {
// 应用开机自启动配置
const config = loadConfig();
const shouldMinimize = config.launchMinimized || false;
if (config.autoLaunch !== undefined) {
app.setLoginItemSettings({
openAtLogin: config.autoLaunch,
openAsHidden: shouldMinimize
});
}
createWindow();
createTray(mainWindow);
splashWindow = new BrowserWindow({
width: 1200,
height: 675,
frame: false,
transparent: false,
resizable: false,
alwaysOnTop: false,
show: true,
center: true,
icon: getIconPath(),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
// 只有在不需要最小化启动时才显示 splash 窗口
if (!shouldMinimize) {
splashWindow = new BrowserWindow({
width: 1200,
height: 675,
frame: false,
transparent: false,
resizable: false,
alwaysOnTop: false,
show: true,
center: true,
icon: getIconPath(),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
}
});
// 监听启动窗口关闭事件
splashWindow.on('closed', () => {
splashWindow = null;
});
const splashPath = getSplashPath();
if (existsSync(splashPath)) {
splashWindow.loadFile(splashPath);
}
});
// 监听启动窗口关闭事件
splashWindow.on('closed', () => {
splashWindow = null;
});
const splashPath = getSplashPath();
if (existsSync(splashPath)) {
splashWindow.loadFile(splashPath);
}
// setTimeout(() => {
// openAppIfNotOpened();
// }, 2000);
setTimeout(() => {
openAppIfNotOpened();
}, 2000);
app.on('activate', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
@@ -709,6 +738,46 @@ ipcMain.handle('set-close-action', (event, action: 'quit' | 'minimize' | 'tray')
return { success: true };
});
// 清理缓存
ipcMain.handle('clear-cache', async () => {
try {
const response = await fetch('http://127.0.0.1:8081/api/system/cache/clear', {
method: 'POST'
});
const data = await response.json();
return data;
} catch (error: any) {
console.error('清理缓存失败:', error);
return { success: false, error: error.message };
}
});
// 获取启动配置
ipcMain.handle('get-launch-config', () => {
const config = loadConfig();
const loginSettings = app.getLoginItemSettings();
return {
autoLaunch: config.autoLaunch !== undefined ? config.autoLaunch : loginSettings.openAtLogin,
launchMinimized: config.launchMinimized || false
};
});
// 设置启动配置
ipcMain.handle('set-launch-config', (event, launchConfig: { autoLaunch: boolean; launchMinimized: boolean }) => {
const config = loadConfig();
config.autoLaunch = launchConfig.autoLaunch;
config.launchMinimized = launchConfig.launchMinimized;
saveConfig(config);
// 立即应用开机自启动设置
app.setLoginItemSettings({
openAtLogin: launchConfig.autoLaunch,
openAsHidden: launchConfig.launchMinimized
});
return { success: true };
});
async function getFileSize(url: string): Promise<number> {
return new Promise((resolve) => {

View File

@@ -27,6 +27,13 @@ const electronAPI = {
getCloseAction: () => ipcRenderer.invoke('get-close-action'),
setCloseAction: (action: 'quit' | 'minimize' | 'tray') => ipcRenderer.invoke('set-close-action', action),
// 缓存管理 API
clearCache: () => ipcRenderer.invoke('clear-cache'),
// 启动配置 API
getLaunchConfig: () => ipcRenderer.invoke('get-launch-config'),
setLaunchConfig: (config: { autoLaunch: boolean; launchMinimized: boolean }) => ipcRenderer.invoke('set-launch-config', config),
onDownloadProgress: (callback: (progress: any) => void) => {
ipcRenderer.removeAllListeners('download-progress')
ipcRenderer.on('download-progress', (event, progress) => callback(progress))

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import {onMounted, ref, computed, defineAsyncComponent, type Component, onUnmounted, provide} from 'vue'
import {onMounted, ref, computed, type Component, onUnmounted, provide} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import 'element-plus/dist/index.css'
@@ -9,15 +9,15 @@ import {getOrCreateDeviceId} from './utils/deviceId'
import {getToken, setToken, removeToken, getUsernameFromToken, getClientIdFromToken} from './utils/token'
import {CONFIG} from './api/http'
import {getSettings} from './utils/settings'
const LoginDialog = defineAsyncComponent(() => import('./components/auth/LoginDialog.vue'))
const RegisterDialog = defineAsyncComponent(() => import('./components/auth/RegisterDialog.vue'))
const NavigationBar = defineAsyncComponent(() => import('./components/layout/NavigationBar.vue'))
const RakutenDashboard = defineAsyncComponent(() => import('./components/rakuten/RakutenDashboard.vue'))
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 TrialExpiredDialog = defineAsyncComponent(() => import('./components/common/TrialExpiredDialog.vue'))
import LoginDialog from './components/auth/LoginDialog.vue'
import RegisterDialog from './components/auth/RegisterDialog.vue'
import NavigationBar from './components/layout/NavigationBar.vue'
import RakutenDashboard from './components/rakuten/RakutenDashboard.vue'
import AmazonDashboard from './components/amazon/AmazonDashboard.vue'
import ZebraDashboard from './components/zebra/ZebraDashboard.vue'
import UpdateDialog from './components/common/UpdateDialog.vue'
import SettingsDialog from './components/common/SettingsDialog.vue'
import TrialExpiredDialog from './components/common/TrialExpiredDialog.vue'
const dashboardsMap: Record<string, Component> = {
rakuten: RakutenDashboard,
@@ -480,12 +480,14 @@ function handleAutoUpdateChanged(enabled: boolean) {
}
}
// 处理打开更新对话框
function handleOpenUpdateDialog() {
showUpdateDialog.value = true
}
onUnmounted(() => {
SSEManager.disconnect()
})
</script>
<template>
@@ -582,7 +584,7 @@ onUnmounted(() => {
<UpdateDialog ref="updateDialogRef" v-model="showUpdateDialog" />
<!-- 设置对话框 -->
<SettingsDialog v-model="showSettingsDialog" @auto-update-changed="handleAutoUpdateChanged" />
<SettingsDialog v-model="showSettingsDialog" @auto-update-changed="handleAutoUpdateChanged" @open-update-dialog="handleOpenUpdateDialog" />
<!-- 试用期过期弹框 -->
<TrialExpiredDialog v-model="showTrialExpiredDialog" :expired-type="trialExpiredType" />
@@ -1006,4 +1008,14 @@ onUnmounted(() => {
.vip-status-card.vip-expired .vip-expire-date {
color: #B0B0B0;
}
</style>
<style>
/* 全局样式:限制图片预览器大小 */
.el-image-viewer__img {
max-width: 50vw !important;
max-height: 50vh !important;
width: auto !important;
height: auto !important;
}
</style>

View File

@@ -14,6 +14,18 @@ export interface DeviceQuota {
used: number
}
/**
* 获取本机内网IP地址
*/
async function getLocalIP(): Promise<string> {
try {
const res = await http.get<{ data: string }>('/api/system/local-ip')
return res.data
} catch {
return '127.0.0.1'
}
}
export const deviceApi = {
getQuota(username: string) {
return http.get<{ data: DeviceQuota }>('/monitor/device/quota', { username })
@@ -23,8 +35,9 @@ export const deviceApi = {
return http.get<{ data: DeviceItem[] }>('/monitor/device/list', { username })
},
register(payload: { username: string; deviceId: string; os?: string }) {
return http.post('/monitor/device/register', payload)
async register(payload: { username: string; deviceId: string; os?: string }) {
const ip = await getLocalIP()
return http.post('/monitor/device/register', { ...payload, ip })
},
remove(payload: { deviceId: string }) {

View File

@@ -4,8 +4,9 @@ export type HttpMethod = 'GET' | 'POST' | 'DELETE';
// 集中管理所有后端服务配置
export const CONFIG = {
CLIENT_BASE: 'http://localhost:8081',
RUOYI_BASE: 'http://192.168.1.89:8085',
SSE_URL: 'http://192.168.1.89:8085/monitor/account/events'
// RUOYI_BASE: 'http://192.168.1.89:8085',
RUOYI_BASE: 'http://8.138.23.49:8085',
SSE_URL: 'http://8.138.23.49:8085/monitor/account/events'
} as const;
function resolveBase(path: string): string {
@@ -13,7 +14,6 @@ function resolveBase(path: string): string {
if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma')) {
return CONFIG.RUOYI_BASE;
}
// 其他走客户端服务
return CONFIG.CLIENT_BASE;
}

View File

@@ -247,13 +247,22 @@ function stopFetch() {
// 打开Genmai Spirit工具
async function openGenmaiSpirit() {
genmaiLoading.value = true
try {
await amazonApi.openGenmaiSpirit()
} catch (error: any) {
showMessage(error.message || '打开跟卖精灵失败', 'error')
} finally {
genmaiLoading.value = false
await ElMessageBox.confirm('打开跟卖精灵会关闭所有谷歌浏览器进程,是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
genmaiLoading.value = true
try {
await amazonApi.openGenmaiSpirit()
} catch (error: any) {
showMessage(error.message || '打开跟卖精灵失败', 'error')
} finally {
genmaiLoading.value = false
}
} catch {
// 用户取消
}
}

View File

@@ -3,7 +3,6 @@ import { ref, computed } from 'vue'
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 {
@@ -35,13 +34,6 @@ async function handleAuth() {
// 获取或生成设备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,

View File

@@ -74,7 +74,7 @@ export default defineComponent({ name: 'AccountManager' })
<div class="top">
<img src="/icon/image.png" class="hero" alt="logo" />
<div class="head-main">
<div class="main-title">在线账号管理3/3</div>
<div class="main-title">在线账号管理</div>
<div class="main-sub">
您当前订阅可同时托管3家 Shopee 店铺<br>
如需扩增同时托管店铺数 <span class="upgrade">升级订阅</span>

View File

@@ -20,6 +20,7 @@ interface Props {
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'autoUpdateChanged', value: boolean): void
(e: 'openUpdateDialog'): void
}
const props = defineProps<Props>()
@@ -50,7 +51,14 @@ const feedbackSubmitting = ref(false)
const logDates = ref<string[]>([])
// 关闭行为配置
const closeAction = ref<'quit' | 'minimize' | 'tray'>('tray')
const closeAction = ref<'quit' | 'minimize' | 'tray'>('quit')
// 缓存相关
const clearingCache = ref(false)
// 启动配置
const autoLaunch = ref(false)
const launchMinimized = ref(false)
// 更新相关
const currentVersion = ref('')
@@ -95,6 +103,12 @@ async function saveAllSettings() {
// 保存关闭行为配置
await (window as any).electronAPI.setCloseAction(closeAction.value)
// 保存启动配置
await (window as any).electronAPI.setLaunchConfig({
autoLaunch: autoLaunch.value,
launchMinimized: launchMinimized.value
})
ElMessage({ message: '设置已保存', type: 'success' })
show.value = false
@@ -123,10 +137,89 @@ function resetPlatformSettings(platform: Platform) {
}
// 重置所有设置
function resetAllSettings() {
platforms.forEach(platform => {
resetPlatformSettings(platform.key)
})
async function resetAllSettings() {
try {
await ElMessageBox.confirm(
'确定要重置所有设置吗?此操作不可恢复。',
'确认重置',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
// 重置所有平台设置
platforms.forEach(platform => {
resetPlatformSettings(platform.key)
})
// 重置自动更新配置
autoUpdate.value = false
// 重置关闭行为配置
closeAction.value = 'quit'
// 重置启动配置
autoLaunch.value = false
launchMinimized.value = false
ElMessage.success('所有设置已重置')
} catch {
// 用户取消操作
}
}
// 清理缓存
async function handleClearCache() {
try {
await ElMessageBox.confirm(
'确定要清理客户端缓存吗?将清除所有缓存数据、更新文件及相关状态(不影响登录状态)',
'确认清理',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
clearingCache.value = true
// 1. 清理后端数据库缓存
const result = await (window as any).electronAPI.clearCache()
if (!result || result.code !== 0) {
ElMessage.error('缓存清理失败')
return
}
// 2. 清除更新相关的 localStorage 状态
localStorage.removeItem('skipped_version')
localStorage.removeItem('remind_later_time')
// 3. 清除已下载的更新文件
await (window as any).electronAPI.clearUpdateFiles()
ElMessage.success('缓存清理成功')
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error('缓存清理失败')
}
} finally {
clearingCache.value = false
}
}
// 加载启动配置
async function loadLaunchConfig() {
try {
const config = await (window as any).electronAPI.getLaunchConfig()
if (config) {
autoLaunch.value = config.autoLaunch || false
launchMinimized.value = config.launchMinimized || false
}
} catch (error) {
console.warn('获取启动配置失败:', error)
}
}
// 滚动到指定区域
@@ -179,7 +272,7 @@ function handleScroll() {
const container = settingsMainRef.value
if (!container) return
const sections = ['export', 'update', 'feedback', 'general']
const sections = ['export', 'update', 'cache', 'startup', 'feedback', 'general']
const scrollTop = container.scrollTop
const containerHeight = container.clientHeight
const scrollHeight = container.scrollHeight
@@ -249,6 +342,12 @@ async function checkForUpdates() {
}
}
// 处理立即升级按钮点击
function handleUpgradeClick() {
show.value = false
emit('openUpdateDialog')
}
// 加载当前版本
async function loadCurrentVersion() {
try {
@@ -315,6 +414,7 @@ onMounted(() => {
loadLogDates()
loadCloseAction()
loadCurrentVersion()
loadLaunchConfig()
})
</script>
@@ -339,6 +439,18 @@ onMounted(() => {
<span class="sidebar-icon">🔄</span>
<span class="sidebar-text">更新</span>
</div>
<div
:class="['sidebar-item', { active: activeTab === 'cache' }]"
@click="scrollToSection('cache')">
<span class="sidebar-icon">💾</span>
<span class="sidebar-text">缓存</span>
</div>
<div
:class="['sidebar-item', { active: activeTab === 'startup' }]"
@click="scrollToSection('startup')">
<span class="sidebar-icon">🚀</span>
<span class="sidebar-text">启动</span>
</div>
<div
:class="['sidebar-item', { active: activeTab === 'feedback' }]"
@click="scrollToSection('feedback')">
@@ -409,7 +521,7 @@ onMounted(() => {
type="primary"
size="small"
:loading="checkingUpdate"
@click="checkForUpdates">
@click="hasUpdate ? handleUpgradeClick() : checkForUpdates()">
{{ checkingUpdate ? '检查中...' : (hasUpdate ? '立即升级' : '检查更新') }}
</el-button>
</div>
@@ -435,6 +547,46 @@ onMounted(() => {
</div>
</div>
<!-- 缓存设置 -->
<div id="section-cache" class="setting-section" @mouseenter="activeTab = 'cache'">
<div class="section-title">缓存设置</div>
<div class="setting-item">
<div class="setting-row">
<span class="cache-desc">清理应用缓存数据不影响登录状态</span>
<el-button
type="primary"
size="small"
:loading="clearingCache"
:disabled="clearingCache"
@click="handleClearCache">
{{ clearingCache ? '清理中' : '清理缓存' }}
</el-button>
</div>
</div>
</div>
<!-- 启动设置 -->
<div id="section-startup" class="setting-section" @mouseenter="activeTab = 'startup'">
<div class="section-title">启动</div>
<div class="setting-item">
<div class="checkbox-row">
<el-checkbox v-model="autoLaunch" size="default">
开机时自动启动
</el-checkbox>
</div>
</div>
<div class="setting-item" style="margin-top: 10px;">
<div class="checkbox-row">
<el-checkbox v-model="launchMinimized" size="default">
启动后最小化到程序坞
</el-checkbox>
</div>
</div>
</div>
<!-- 反馈页面 -->
<div id="section-feedback" class="setting-section" @mouseenter="activeTab = 'feedback'">
<div class="section-title">反馈</div>
@@ -726,6 +878,11 @@ onMounted(() => {
text-align: left;
}
.cache-desc {
font-size: 13px;
color: #86909C;
}
.setting-item {
margin-bottom: 0;
}

View File

@@ -134,17 +134,28 @@ async function loadLatest() {
allProducts.value = products.map((p: any) => ({...p, skuPrices: parseSkuPrices(p)}))
}
function hasValid1688Data(data: any) {
if (!data) return false
const skuJson = data.skuPriceJson || data.skuPrice
const prices = parseSkuPrices({ skuPriceJson: skuJson })
if (!data.mapRecognitionLink) return false
if (!Array.isArray(prices) || !prices.length) return false
if (!data.freight || data.freight <= 0) return false
if (!data.median || data.median <= 0) return false
return true
}
async function searchProductInternal(product: any) {
if (!product || !product.imgUrl) return
if (!needsSearch(product)) return
if (!product || !product.imgUrl) return false
if (!needsSearch(product)) return true
if (!props.isVip) {
if (checkExpiredType) trialExpiredType.value = checkExpiredType()
showTrialExpiredDialog.value = true
return
return false
}
const res: any = await rakutenApi.search1688(product.imgUrl, currentBatchId.value)
const data = res.data
if (!hasValid1688Data(data)) return false
const skuJson = data.skuPriceJson || data.skuPrice
Object.assign(product, {
mapRecognitionLink: data.mapRecognitionLink,
@@ -157,6 +168,20 @@ async function searchProductInternal(product: any) {
image1688Url: data.mapRecognitionLink,
detailUrl1688: data.mapRecognitionLink,
})
return true
}
async function searchProductWithRetry(product: any, maxRetry = 2) {
for (let attempt = 1; attempt <= maxRetry; attempt++) {
try {
const ok = await searchProductInternal(product)
if (ok) return true
} catch (e) {
console.warn('search1688 failed', e)
}
if (attempt < maxRetry) await delay(600)
}
return false
}
function beforeUpload(file: File) {
@@ -305,7 +330,7 @@ async function serialSearch1688(products: any[]) {
const product = products[i]
product.searching1688 = true
await nextTickSafe()
await searchProductInternal(product)
await searchProductWithRetry(product)
product.searching1688 = false
processedProducts.value++
progressPercentage.value = Math.floor((processedProducts.value / Math.max(1, totalProducts.value)) * 100)
@@ -560,7 +585,7 @@ onMounted(loadLatest)
</td>
<td>
<div class="image-container" v-if="row.imgUrl">
<img :src="row.imgUrl" class="thumb" alt="thumb"/>
<el-image :src="row.imgUrl" class="thumb" fit="contain" :preview-src-list="[row.imgUrl]" />
</div>
<span v-else>无图片</span>
</td>

View File

@@ -474,7 +474,7 @@ async function removeCurrentAccount() {
<td>{{ row.orderedAt || '-' }}</td>
<td>
<div class="image-container" v-if="row.productImage">
<img :src="row.productImage" class="thumb" alt="thumb" />
<el-image :src="row.productImage" class="thumb" fit="contain" :preview-src-list="[row.productImage]" />
</div>
<span v-else>无图片</span>
</td>

View File

@@ -5,6 +5,9 @@
<title>erpClient</title>
<link rel="icon" href="/icon/icon.png">
<meta name="theme-color" content="#ffffff">
<style>
body { margin: 0; background-color: #f5f5f5; }
</style>
</head>
<body>
<div id="app"></div>