feat(amazon): 更新商标筛查界面图标与状态展示- 将商标筛查状态图标从图片替换为 SVG 图标
- 添加了进行中、取消、完成/失败状态的 SVG 图标 -优化任务进度指示器,使用 SVG 并支持旋转动画 - 禁用跟卖许可筛查功能并更新提示文案 - 调整标签页样式和间距,适配不同屏幕尺寸 -修复状态横幅图标显示问题,并统一图标尺寸 - 更新全局样式以提升视觉一致性和用户体验
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 913 B |
Binary file not shown.
|
Before Width: | Height: | Size: 894 B |
Binary file not shown.
|
Before Width: | Height: | Size: 870 B |
Binary file not shown.
|
Before Width: | Height: | Size: 533 B |
BIN
electron-vue-template/public/image/excel-format-example.png
Normal file
BIN
electron-vue-template/public/image/excel-format-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
@@ -1,31 +1,78 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>正在启动...</title>
|
||||
<style>
|
||||
html, body { height: 100%; margin: 0; }
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body { height: 100%; overflow: hidden; }
|
||||
body {
|
||||
background: #fff; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;
|
||||
background-image: var(--splash-image, url('./image/splash_screen.png'));
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
.image {
|
||||
flex: 1;
|
||||
background-image: __SPLASH_IMAGE__;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
.box {
|
||||
height: 64px;
|
||||
padding: 0 30px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
.text {
|
||||
font-size: 14px;
|
||||
color: rgba(0,0,0,0.85);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.progress {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: rgba(0,0,0,0.06);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.bar {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, #1677ff 0%, #4096ff 100%);
|
||||
border-radius: 10px;
|
||||
animation: load 3s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
}
|
||||
.btn {
|
||||
padding: 4px 15px;
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
color: rgba(0,0,0,0.65);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn:hover {
|
||||
color: #1677ff;
|
||||
border-color: #1677ff;
|
||||
background: #f0f7ff;
|
||||
}
|
||||
@keyframes load {
|
||||
to { width: 90%; }
|
||||
}
|
||||
.box { position: fixed; left: 0; right: 0; bottom: 28px; padding: 0 0; }
|
||||
.progress { position: relative; width: 100vw; height: 6px; background: rgba(0,0,0,0.08); }
|
||||
.bar { position: absolute; left: 0; top: 0; height: 100%; width: 20vw; min-width: 120px; background: linear-gradient(90deg, #67C23A, #409EFF); animation: slide 1s ease-in-out infinite alternate; }
|
||||
@keyframes slide { 0% { left: 0; } 100% { left: calc(100vw - 20vw); } }
|
||||
</style>
|
||||
<link rel="icon" href="icon/icon.png">
|
||||
<link rel="apple-touch-icon" href="icon/icon.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data: file: https:; style-src 'self' 'unsafe-inline';">
|
||||
</head>
|
||||
<body>
|
||||
<div class="image"></div>
|
||||
<div class="box">
|
||||
<span class="text">正在启动</span>
|
||||
<div class="progress"><div class="bar"></div></div>
|
||||
<button class="btn" onclick="require('electron').ipcRenderer.send('quit-app')">退出</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -30,6 +30,11 @@ function openAppIfNotOpened() {
|
||||
: mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
|
||||
|
||||
mainWindow.webContents.once('did-finish-load', () => {
|
||||
if (splashWindow && !splashWindow.isDestroyed()) {
|
||||
splashWindow.webContents.send('splash-complete');
|
||||
}
|
||||
|
||||
// 先显示主窗口,再关闭splash,避免白屏
|
||||
setTimeout(() => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
const shouldMinimize = loadConfig().launchMinimized || false;
|
||||
@@ -39,14 +44,19 @@ function openAppIfNotOpened() {
|
||||
}
|
||||
if (isDev) mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
// 延迟关闭splash,确保主窗口已显示
|
||||
setTimeout(() => {
|
||||
if (splashWindow && !splashWindow.isDestroyed()) {
|
||||
splashWindow.close();
|
||||
splashWindow = null;
|
||||
}
|
||||
}, 100);
|
||||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 通用资源路径获取函数
|
||||
function getResourcePath(devPath: string, prodPath: string, fallbackPath?: string): string {
|
||||
if (isDev) return join(__dirname, devPath);
|
||||
@@ -76,6 +86,74 @@ const getSplashPath = () => getResourcePath('../../public/splash.html', 'public/
|
||||
const getIconPath = () => getResourcePath('../../public/icon/icon1.png', 'public/icon/icon1.png');
|
||||
const getLogbackConfigPath = () => getResourcePath('../../public/config/logback.xml', 'public/config/logback.xml');
|
||||
|
||||
// 图片缓存目录
|
||||
const getImageCacheDir = () => {
|
||||
const cacheDir = join(app.getPath('userData'), 'image-cache');
|
||||
if (!existsSync(cacheDir)) mkdirSync(cacheDir, { recursive: true });
|
||||
return cacheDir;
|
||||
};
|
||||
|
||||
// 下载图片到本地
|
||||
async function downloadImageToLocal(imageUrl: string, username: string, type: 'splash' | 'logo'): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const protocol = imageUrl.startsWith('https') ? https : http;
|
||||
protocol.get(imageUrl, (res) => {
|
||||
if (res.statusCode !== 200) return resolve();
|
||||
const chunks: Buffer[] = [];
|
||||
res.on('data', (chunk) => chunks.push(chunk));
|
||||
res.on('end', () => {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
const ext = imageUrl.match(/\.(jpg|jpeg|png|gif|webp)$/i)?.[1] || 'png';
|
||||
const filepath = join(getImageCacheDir(), `${username}_${type}.${ext}`);
|
||||
writeFileSync(filepath, buffer);
|
||||
console.log(`[图片缓存] 已保存: ${username}_${type}.${ext}`);
|
||||
resolve();
|
||||
});
|
||||
res.on('error', () => resolve());
|
||||
}).on('error', () => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
// 加载本地缓存图片
|
||||
function loadCachedImage(username: string, type: 'splash' | 'logo'): string | null {
|
||||
try {
|
||||
const files = readdirSync(getImageCacheDir());
|
||||
const file = files.find(f => f.startsWith(`${username}_${type}.`));
|
||||
if (file) {
|
||||
const buffer = readFileSync(join(getImageCacheDir(), file));
|
||||
const ext = extname(file).slice(1);
|
||||
const mime = { jpg: 'jpeg', jpeg: 'jpeg', png: 'png', gif: 'gif', webp: 'webp' }[ext] || 'png';
|
||||
return `url('data:image/${mime};base64,${buffer.toString('base64')}')`;
|
||||
}
|
||||
} catch (err) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 删除本地缓存图片
|
||||
function deleteCachedImage(username: string, type: 'splash' | 'logo'): void {
|
||||
try {
|
||||
const files = readdirSync(getImageCacheDir());
|
||||
const file = files.find(f => f.startsWith(`${username}_${type}.`));
|
||||
if (file) {
|
||||
const filepath = join(getImageCacheDir(), file);
|
||||
if (existsSync(filepath)) {
|
||||
require('fs').unlinkSync(filepath);
|
||||
console.log(`[图片缓存] 已删除: ${file}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
// 获取默认开屏图片
|
||||
function getDefaultSplashImage(): string {
|
||||
const path = getResourcePath('../../public/image/splash_screen.png', 'public/image/splash_screen.png');
|
||||
if (existsSync(path)) {
|
||||
const base64 = readFileSync(path).toString('base64');
|
||||
return `url('data:image/png;base64,${base64}')`;
|
||||
}
|
||||
return 'none';
|
||||
}
|
||||
|
||||
function getDataDirectoryPath(): string {
|
||||
const dataDir = join(app.getPath('userData'), 'data');
|
||||
if (!existsSync(dataDir)) mkdirSync(dataDir, {recursive: true});
|
||||
@@ -101,6 +179,7 @@ interface AppConfig {
|
||||
launchMinimized?: boolean;
|
||||
lastUsername?: string;
|
||||
splashImageUrl?: string;
|
||||
brandLogoUrl?: string;
|
||||
}
|
||||
|
||||
function getConfigPath(): string {
|
||||
@@ -143,12 +222,15 @@ function migrateDataFromPublic(): void {
|
||||
|
||||
|
||||
function startSpringBoot() {
|
||||
console.log('[Spring Boot] 开始启动...');
|
||||
migrateDataFromPublic();
|
||||
const jarPath = getJarFilePath();
|
||||
const javaPath = getJavaExecutablePath();
|
||||
const dataDir = getDataDirectoryPath();
|
||||
const logDir = getLogDirectoryPath();
|
||||
const logbackConfigPath = getLogbackConfigPath();
|
||||
console.log('[Spring Boot] JAR路径:', jarPath);
|
||||
console.log('[Spring Boot] Java路径:', javaPath);
|
||||
if (!existsSync(jarPath)) {
|
||||
dialog.showErrorBox('启动失败', `JAR 文件不存在:\n${jarPath}`);
|
||||
app.quit();
|
||||
@@ -180,8 +262,10 @@ function startSpringBoot() {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
let checkCount = 0;
|
||||
const checkHealth = () => {
|
||||
if (startupCompleted) return;
|
||||
|
||||
http.get('http://127.0.0.1:8081/api/system/version', (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
setTimeout(checkHealth, 100);
|
||||
@@ -213,7 +297,6 @@ function startSpringBoot() {
|
||||
}
|
||||
}
|
||||
|
||||
// startSpringBoot();
|
||||
function stopSpringBoot() {
|
||||
if (!springProcess) return;
|
||||
try {
|
||||
@@ -236,8 +319,10 @@ function stopSpringBoot() {
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1280,
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 1200,
|
||||
minHeight: 800,
|
||||
show: false, //
|
||||
frame: false,
|
||||
autoHideMenuBar: true,
|
||||
@@ -352,49 +437,48 @@ app.whenReady().then(() => {
|
||||
|
||||
// 只有在不需要最小化启动时才显示 splash 窗口
|
||||
if (!shouldMinimize) {
|
||||
const config = loadConfig();
|
||||
const username = config.lastUsername || '';
|
||||
const imageUrl = config.splashImageUrl || '';
|
||||
|
||||
// 图片加载:本地缓存 > 默认图片
|
||||
let splashImage = (imageUrl && username && loadCachedImage(username, 'splash')) || getDefaultSplashImage();
|
||||
|
||||
// 如果有URL但缓存不存在,后台下载
|
||||
if (imageUrl && username && !loadCachedImage(username, 'splash')) {
|
||||
downloadImageToLocal(imageUrl, username, 'splash');
|
||||
}
|
||||
|
||||
const splashHtml = readFileSync(getSplashPath(), 'utf-8').replace('__SPLASH_IMAGE__', splashImage);
|
||||
|
||||
splashWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 675,
|
||||
height: 800,
|
||||
frame: false,
|
||||
transparent: false,
|
||||
resizable: false,
|
||||
alwaysOnTop: false,
|
||||
show: true,
|
||||
show: false,
|
||||
center: true,
|
||||
icon: getIconPath(),
|
||||
backgroundColor: '#ffffff',
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
}
|
||||
});
|
||||
|
||||
// 监听启动窗口关闭事件
|
||||
splashWindow.on('closed', () => {
|
||||
splashWindow = null;
|
||||
});
|
||||
splashWindow.on('closed', () => splashWindow = null);
|
||||
|
||||
const splashPath = getSplashPath();
|
||||
if (existsSync(splashPath)) {
|
||||
const config = loadConfig();
|
||||
const imageUrl = config.splashImageUrl || '';
|
||||
console.log('[开屏图片] 启动配置:', { username: config.lastUsername, imageUrl, configPath: getConfigPath() });
|
||||
// 加载预注入的 HTML(图片已base64内联,无跨域问题)
|
||||
splashWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(splashHtml)}`);
|
||||
splashWindow.once('ready-to-show', () => splashWindow?.show());
|
||||
}
|
||||
|
||||
splashWindow.loadFile(splashPath);
|
||||
|
||||
if (imageUrl) {
|
||||
splashWindow.webContents.once('did-finish-load', () => {
|
||||
splashWindow?.webContents.executeJavaScript(`
|
||||
document.body.style.setProperty('--splash-image', "url('${imageUrl}')");
|
||||
`).then(() => console.log('[开屏图片] 注入成功:', imageUrl))
|
||||
.catch(err => console.error('[开屏图片] 注入失败:', err));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
//666
|
||||
console.log('[启动流程] 准备启动 Spring Boot...');
|
||||
setTimeout(() => {
|
||||
openAppIfNotOpened();
|
||||
}, 100);
|
||||
startSpringBoot();
|
||||
}, 200);
|
||||
|
||||
app.on('activate', () => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
@@ -424,6 +508,11 @@ ipcMain.on('message', (event, message) => {
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
ipcMain.on('quit-app', () => {
|
||||
isQuitting = true;
|
||||
app.quit();
|
||||
});
|
||||
|
||||
ipcMain.handle('get-jar-version', () => {
|
||||
const jarPath = getJarFilePath();
|
||||
const match = jarPath ? basename(jarPath).match(/erp_client_sb-(\d+\.\d+\.\d+)\.jar/) : null;
|
||||
@@ -785,13 +874,21 @@ ipcMain.handle('window-is-maximized', () => {
|
||||
return mainWindow && !mainWindow.isDestroyed() ? mainWindow.isMaximized() : false;
|
||||
});
|
||||
|
||||
// 保存开屏图片配置(用户名 + URL)
|
||||
ipcMain.handle('save-splash-config', (event, username: string, imageUrl: string) => {
|
||||
// 保存开屏图片配置(用户名 + URL)并下载到本地
|
||||
ipcMain.handle('save-splash-config', async (event, username: string, imageUrl: string) => {
|
||||
const config = loadConfig();
|
||||
config.lastUsername = username;
|
||||
config.splashImageUrl = imageUrl;
|
||||
saveConfig(config);
|
||||
console.log('[开屏图片] 已保存配置:', { username, imageUrl, path: getConfigPath() });
|
||||
|
||||
// 如果有图片URL,立即下载到本地缓存
|
||||
if (imageUrl && username) {
|
||||
await downloadImageToLocal(imageUrl, username, 'splash');
|
||||
} else if (username) {
|
||||
// 如果图片URL为空,删除本地缓存
|
||||
deleteCachedImage(username, 'splash');
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
@@ -801,6 +898,48 @@ ipcMain.handle('get-splash-config', () => {
|
||||
return { username: config.lastUsername || '', imageUrl: config.splashImageUrl || '' };
|
||||
});
|
||||
|
||||
// 保存品牌logo配置
|
||||
ipcMain.handle('save-brand-logo-config', async (event, username: string, logoUrl: string) => {
|
||||
const config = loadConfig();
|
||||
config.brandLogoUrl = logoUrl;
|
||||
saveConfig(config);
|
||||
|
||||
// 如果有logo URL,立即下载到本地缓存
|
||||
if (logoUrl && username) {
|
||||
await downloadImageToLocal(logoUrl, username, 'logo');
|
||||
} else if (username) {
|
||||
// 如果logo URL为空,删除本地缓存
|
||||
deleteCachedImage(username, 'logo');
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// 清除用户配置(退出登录时调用)
|
||||
ipcMain.handle('clear-user-config', async () => {
|
||||
const config = loadConfig();
|
||||
const username = config.lastUsername;
|
||||
|
||||
// 清除配置
|
||||
config.lastUsername = '';
|
||||
config.splashImageUrl = '';
|
||||
config.brandLogoUrl = '';
|
||||
saveConfig(config);
|
||||
|
||||
// 删除本地缓存的图片
|
||||
if (username) {
|
||||
deleteCachedImage(username, 'splash');
|
||||
deleteCachedImage(username, 'logo');
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
// 加载完整配置
|
||||
ipcMain.handle('load-config', () => {
|
||||
return loadConfig();
|
||||
});
|
||||
|
||||
|
||||
async function getFileSize(url: string): Promise<number> {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -47,6 +47,11 @@ const electronAPI = {
|
||||
saveSplashConfig: (username: string, imageUrl: string) => ipcRenderer.invoke('save-splash-config', username, imageUrl),
|
||||
getSplashConfig: () => ipcRenderer.invoke('get-splash-config'),
|
||||
|
||||
// 品牌logo相关 API
|
||||
saveBrandLogoConfig: (username: string, logoUrl: string) => ipcRenderer.invoke('save-brand-logo-config', username, logoUrl),
|
||||
loadConfig: () => ipcRenderer.invoke('load-config'),
|
||||
clearUserConfig: () => ipcRenderer.invoke('clear-user-config'),
|
||||
|
||||
onDownloadProgress: (callback: (progress: any) => void) => {
|
||||
ipcRenderer.removeAllListeners('download-progress')
|
||||
ipcRenderer.on('download-progress', (event, progress) => callback(progress))
|
||||
|
||||
@@ -32,10 +32,8 @@ export function createTray(mainWindow: BrowserWindow | null) {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 右键菜单
|
||||
updateTrayMenu(mainWindow)
|
||||
|
||||
return tray
|
||||
}
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ async function handleLoginSuccess(data: {
|
||||
}
|
||||
}
|
||||
|
||||
function clearLocalAuth() {
|
||||
async function clearLocalAuth() {
|
||||
removeToken()
|
||||
isAuthenticated.value = false
|
||||
currentUsername.value = ''
|
||||
@@ -253,9 +253,17 @@ function clearLocalAuth() {
|
||||
vipExpireTime.value = null
|
||||
deviceTrialExpired.value = false
|
||||
accountType.value = 'trial'
|
||||
brandLogoUrl.value = '' // 清除品牌logo
|
||||
showAuthDialog.value = true
|
||||
showDeviceDialog.value = false
|
||||
SSEManager.disconnect()
|
||||
|
||||
// 清除主进程中的用户配置和缓存
|
||||
try {
|
||||
await (window as any).electronAPI.clearUserConfig()
|
||||
} catch (error) {
|
||||
console.warn('清除用户配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
@@ -265,7 +273,7 @@ async function logout() {
|
||||
} catch (error) {
|
||||
console.warn('离线通知失败:', error)
|
||||
}
|
||||
clearLocalAuth()
|
||||
await clearLocalAuth()
|
||||
}
|
||||
|
||||
async function handleUserClick() {
|
||||
@@ -374,11 +382,22 @@ async function syncSettingsToElectron() {
|
||||
// 加载品牌logo
|
||||
async function loadBrandLogo() {
|
||||
try {
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return
|
||||
// 1. 优先从本地缓存读取(秒开)
|
||||
const config = await (window as any).electronAPI.loadConfig()
|
||||
if (config.brandLogoUrl) {
|
||||
brandLogoUrl.value = config.brandLogoUrl
|
||||
}
|
||||
|
||||
// 2. 后台异步更新(不阻塞UI)
|
||||
const username = getUsernameFromToken()
|
||||
if (username) {
|
||||
const res = await splashApi.getBrandLogo(username)
|
||||
brandLogoUrl.value = res.data.url
|
||||
const newUrl = res.data.url
|
||||
if (newUrl !== brandLogoUrl.value) {
|
||||
brandLogoUrl.value = newUrl
|
||||
await (window as any).electronAPI.saveBrandLogoConfig(username, newUrl)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
brandLogoUrl.value = ''
|
||||
}
|
||||
@@ -411,7 +430,7 @@ const SSEManager = {
|
||||
}
|
||||
},
|
||||
|
||||
handleMessage(e: MessageEvent) {
|
||||
async handleMessage(e: MessageEvent) {
|
||||
try {
|
||||
if (e.type === 'ping') return
|
||||
|
||||
@@ -422,11 +441,11 @@ const SSEManager = {
|
||||
console.log('SSE连接已就绪')
|
||||
break
|
||||
case 'DEVICE_REMOVED':
|
||||
clearLocalAuth()
|
||||
await clearLocalAuth()
|
||||
ElMessage.warning('会话已失效,请重新登录')
|
||||
break
|
||||
case 'FORCE_LOGOUT':
|
||||
logout()
|
||||
await logout()
|
||||
ElMessage.warning('会话已失效,请重新登录')
|
||||
break
|
||||
case 'PERMISSIONS_UPDATED':
|
||||
@@ -529,7 +548,7 @@ async function confirmRemoveDevice(row: DeviceItem) {
|
||||
deviceQuota.value.used = Math.max(0, deviceQuota.value.used - 1)
|
||||
|
||||
if (row.deviceId === getClientIdFromToken()) {
|
||||
clearLocalAuth()
|
||||
await clearLocalAuth()
|
||||
}
|
||||
|
||||
ElMessage.success('已移除设备')
|
||||
@@ -613,13 +632,37 @@ onUnmounted(() => {
|
||||
</div>
|
||||
<div class="erp-container">
|
||||
<div class="sidebar">
|
||||
<div class="user-avatar">
|
||||
<div class="main-logo">
|
||||
<img src="/icon/icon.png" alt="logo"/>
|
||||
</div>
|
||||
|
||||
<!-- 品牌logo区域(有logo时显示) -->
|
||||
<!-- 用户头像区域 -->
|
||||
<div class="user-avatar-section" @click="handleUserClick">
|
||||
<div class="avatar-wrapper">
|
||||
<img
|
||||
v-if="isAuthenticated"
|
||||
src="/image/img_v3_02qd_052605f0-4be3-44db-9691-35ee5ff6201g.jpg"
|
||||
alt="用户头像"
|
||||
class="user-avatar-img"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
src="/image/user.png"
|
||||
alt="默认头像"
|
||||
class="user-avatar-img"
|
||||
/>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="user-name-wrapper">
|
||||
<span class="user-name">{{ isAuthenticated ? currentUsername : '登录/注册' }}</span>
|
||||
<span v-if="isAuthenticated && vipStatus.isVip" class="vip-badge">VIP {{ vipStatus.daysLeft }}天</span>
|
||||
</div>
|
||||
<div class="user-action">{{ isAuthenticated ? '18659156151' : '登录账号体验完整功能' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="brandLogoUrl" class="brand-logo-section">
|
||||
<img :src="brandLogoUrl" alt="品牌logo" class="brand-logo"/>
|
||||
<img :src="brandLogoUrl" alt="品牌 Banner" class="brand-logo"/>
|
||||
</div>
|
||||
|
||||
<div class="menu-group-title">电商平台</div>
|
||||
@@ -683,8 +726,7 @@ onUnmounted(() => {
|
||||
@open-device="openDeviceManager"
|
||||
@open-settings="openSettings"
|
||||
@open-account-manager="openAccountManager"
|
||||
@check-update="handleCheckUpdate"
|
||||
@show-login="showAuthDialog = true"/>
|
||||
@check-update="handleCheckUpdate"/>
|
||||
<div class="content-body">
|
||||
<div
|
||||
class="dashboard-home"
|
||||
@@ -835,8 +877,8 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 180px;
|
||||
min-width: 180px;
|
||||
width: 220px;
|
||||
min-width: 220px;
|
||||
flex-shrink: 0;
|
||||
background: #F0F0F0;
|
||||
border-right: 1px solid #e8eaec;
|
||||
@@ -859,20 +901,104 @@ onUnmounted(() => {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
/* 主Logo */
|
||||
.main-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px 0;
|
||||
margin: 0 0 12px 0;
|
||||
padding: 8px 0;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 90px;
|
||||
|
||||
.main-logo img {
|
||||
width: 120px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* 用户头像区域 */
|
||||
.user-avatar-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 8px 10px;
|
||||
margin: 0 0 16px 0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
flex-shrink: 0;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.user-avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.user-name-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.vip-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0px 5px;
|
||||
height: 16px;
|
||||
background: #BAE0FF;
|
||||
border: 1px solid rgba(22, 119, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: rgba(0, 29, 102, 1);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
line-height: 100%;
|
||||
text-align: center;
|
||||
letter-spacing: 0%;
|
||||
}
|
||||
|
||||
.user-action {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.menu-group-title {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
@@ -886,8 +1012,8 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
margin-bottom: 12px;
|
||||
height: 60px;
|
||||
margin-bottom: 16px;
|
||||
padding: 0 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type HttpMethod = 'GET' | 'POST' | 'DELETE';
|
||||
//const RUOYI_BASE = 'http://8.138.23.49:8085';
|
||||
const RUOYI_BASE = 'http://192.168.1.89:8085';
|
||||
const RUOYI_BASE = 'http://8.138.23.49:8085';
|
||||
// const RUOYI_BASE = 'http://192.168.1.89:8085';
|
||||
export const CONFIG = {
|
||||
CLIENT_BASE: 'http://localhost:8081',
|
||||
RUOYI_BASE,
|
||||
|
||||
@@ -131,7 +131,7 @@ function handleExportData() {
|
||||
<div class="top-tabs">
|
||||
<div :class="['tab-item', { active: currentTab === 'asin' }]" @click="currentTab = 'asin'">
|
||||
<img src="/icon/asin.png" alt="ASIN查询" class="tab-icon" />
|
||||
<span class="tab-text">ASIN查询</span>
|
||||
<span class="tab-text">ASIN监控</span>
|
||||
</div>
|
||||
<div :class="['tab-item', { active: currentTab === 'genmai' }]" @click="currentTab = 'genmai'">
|
||||
<img src="/icon/anjldk.png" alt="跟卖精灵" class="tab-icon" />
|
||||
@@ -245,7 +245,17 @@ function handleExportData() {
|
||||
<!-- 进行中状态横幅 -->
|
||||
<div v-if="trademarkPanelRef?.queryStatus === 'inProgress'" class="status-banner progress-banner">
|
||||
<div class="banner-icon">
|
||||
<img src="/icon/wait.png" alt="筛查中" class="icon-image" />
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 4H40" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 44H40" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 4V16L21 26" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 44V29.5L27 21" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 44V30L18.5 23.5" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 4V16L29.5 23.5" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18 33H19" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M29.1484 32.6465L29.8555 33.3536" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 38H25" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="banner-content">
|
||||
<div class="banner-title">数据筛查中...</div>
|
||||
@@ -259,7 +269,11 @@ function handleExportData() {
|
||||
<!-- 取消状态横幅 -->
|
||||
<div v-if="trademarkPanelRef?.queryStatus === 'cancel'" class="status-banner cancel-banner">
|
||||
<div class="banner-icon">
|
||||
<img src="/icon/cancel.png" alt="已取消" class="icon-image" />
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#FAAD14" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M29.6574 18.3428L18.3438 29.6565" stroke="#FAAD14" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.3438 18.3428L29.6574 29.6565" stroke="#FAAD14" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="banner-content">
|
||||
<div class="banner-title">已取消查询</div>
|
||||
@@ -274,7 +288,15 @@ function handleExportData() {
|
||||
<!-- 完成/失败状态横幅 -->
|
||||
<div v-if="trademarkPanelRef?.queryStatus === 'done' || trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError'" class="status-banner done-banner">
|
||||
<div class="banner-icon">
|
||||
<img :src="trademarkPanelRef.queryStatus === 'done' ? '/icon/done1.png' : '/icon/error.png'" alt="完成" class="icon-image" />
|
||||
<svg v-if="trademarkPanelRef.queryStatus === 'done'" width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z" stroke="#52C41A" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M16 24L22 30L34 18" stroke="#52C41A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#FF4D4F" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M29.6574 18.3428L18.3438 29.6565" stroke="#FF4D4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.3438 18.3428L29.6574 29.6565" stroke="#FF4D4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="banner-content">
|
||||
<div class="banner-title">{{ trademarkPanelRef.queryStatus === 'done' ? '筛查已完成' : '数据筛查失败' }}</div>
|
||||
@@ -296,29 +318,101 @@ function handleExportData() {
|
||||
<div class="status-column">
|
||||
<!-- 任务1:产品商标筛查 -->
|
||||
<div class="status-item">
|
||||
<img v-if="(trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total" src="/icon/done.png" alt="采集完成" class="status-indicator-icon" />
|
||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" src="/icon/error.png" alt="筛查失败" class="status-indicator-icon" />
|
||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" src="/icon/cancel.png" alt="已取消" class="status-indicator-icon" />
|
||||
<img v-else-if="(trademarkPanelRef?.taskProgress?.product?.current || 0) > 0" src="/icon/inProgress.png" alt="采集中" class="status-indicator-icon" />
|
||||
<img v-else src="/icon/acquisition.png" alt="待采集" class="status-indicator-icon" />
|
||||
<svg v-if="(trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.product?.current || 0) >= trademarkPanelRef.taskProgress.product.total" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z" stroke="#52C41A" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M16 24L22 30L34 18" stroke="#52C41A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#FF4D4F" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M29.6574 18.3428L18.3438 29.6565" stroke="#FF4D4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.3438 18.3428L29.6574 29.6565" stroke="#FF4D4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="((trademarkPanelRef?.taskProgress?.product?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.product?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#FAAD14" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M29.6574 18.3428L18.3438 29.6565" stroke="#FAAD14" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.3438 18.3428L29.6574 29.6565" stroke="#FAAD14" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="(trademarkPanelRef?.taskProgress?.product?.current || 0) > 0" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon spinning">
|
||||
<path d="M8 4H40" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 44H40" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 4V16L21 26" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 44V29.5L27 21" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 44V30L18.5 23.5" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 4V16L29.5 23.5" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18 33H19" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M29.1484 32.6465L29.8555 33.3536" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 38H25" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<circle cx="24" cy="24" r="20" stroke="#D9D9D9" stroke-width="2" stroke-linejoin="round"/>
|
||||
<circle cx="24" cy="24" r="4" fill="#D9D9D9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="status-connector"></div>
|
||||
<!-- 任务2:品牌商标筛查 -->
|
||||
<div class="status-item">
|
||||
<img v-if="(trademarkPanelRef?.taskProgress?.brand?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.brand?.current || 0) >= trademarkPanelRef.taskProgress.brand.total" src="/icon/done.png" alt="采集完成" class="status-indicator-icon" />
|
||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.brand?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.brand?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" src="/icon/error.png" alt="筛查失败" class="status-indicator-icon" />
|
||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.brand?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.brand?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" src="/icon/cancel.png" alt="已取消" class="status-indicator-icon" />
|
||||
<img v-else-if="(trademarkPanelRef?.taskProgress?.brand?.current || 0) > 0" src="/icon/inProgress.png" alt="采集中" class="status-indicator-icon" />
|
||||
<img v-else src="/icon/acquisition.png" alt="待采集" class="status-indicator-icon" />
|
||||
<svg v-if="(trademarkPanelRef?.taskProgress?.brand?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.brand?.current || 0) >= trademarkPanelRef.taskProgress.brand.total" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z" stroke="#52C41A" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M16 24L22 30L34 18" stroke="#52C41A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="((trademarkPanelRef?.taskProgress?.brand?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.brand?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#FF4D4F" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M29.6574 18.3428L18.3438 29.6565" stroke="#FF4D4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.3438 18.3428L29.6574 29.6565" stroke="#FF4D4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="((trademarkPanelRef?.taskProgress?.brand?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.brand?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#FAAD14" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M29.6574 18.3428L18.3438 29.6565" stroke="#FAAD14" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.3438 18.3428L29.6574 29.6565" stroke="#FAAD14" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="(trademarkPanelRef?.taskProgress?.brand?.current || 0) > 0" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon spinning">
|
||||
<path d="M8 4H40" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 44H40" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 4V16L21 26" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 44V29.5L27 21" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 44V30L18.5 23.5" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 4V16L29.5 23.5" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18 33H19" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M29.1484 32.6465L29.8555 33.3536" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 38H25" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<circle cx="24" cy="24" r="20" stroke="#D9D9D9" stroke-width="2" stroke-linejoin="round"/>
|
||||
<circle cx="24" cy="24" r="4" fill="#D9D9D9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="status-connector"></div>
|
||||
<!-- 任务3:跟卖许可筛查 -->
|
||||
<div class="status-item">
|
||||
<img v-if="(trademarkPanelRef?.taskProgress?.platform?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.platform?.current || 0) >= trademarkPanelRef.taskProgress.platform.total" src="/icon/done.png" alt="采集完成" class="status-indicator-icon" />
|
||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.platform?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.platform?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" src="/icon/error.png" alt="筛查失败" class="status-indicator-icon" />
|
||||
<img v-else-if="((trademarkPanelRef?.taskProgress?.platform?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.platform?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" src="/icon/cancel.png" alt="已取消" class="status-indicator-icon" />
|
||||
<img v-else-if="(trademarkPanelRef?.taskProgress?.platform?.current || 0) > 0" src="/icon/inProgress.png" alt="采集中" class="status-indicator-icon" />
|
||||
<img v-else src="/icon/acquisition.png" alt="待采集" class="status-indicator-icon" />
|
||||
<svg v-if="(trademarkPanelRef?.taskProgress?.platform?.total || 0) > 0 && (trademarkPanelRef?.taskProgress?.platform?.current || 0) >= trademarkPanelRef.taskProgress.platform.total" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C29.5228 44 34.5228 41.7614 38.1421 38.1421C41.7614 34.5228 44 29.5228 44 24C44 18.4772 41.7614 13.4772 38.1421 9.85786C34.5228 6.23858 29.5228 4 24 4C18.4772 4 13.4772 6.23858 9.85786 9.85786C6.23858 13.4772 4 18.4772 4 24C4 29.5228 6.23858 34.5228 9.85786 38.1421C13.4772 41.7614 18.4772 44 24 44Z" stroke="#52C41A" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M16 24L22 30L34 18" stroke="#52C41A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="((trademarkPanelRef?.taskProgress?.platform?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.platform?.current || 0) > 0) && (trademarkPanelRef?.queryStatus === 'error' || trademarkPanelRef?.queryStatus === 'networkError')" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#FF4D4F" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M29.6574 18.3428L18.3438 29.6565" stroke="#FF4D4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.3438 18.3428L29.6574 29.6565" stroke="#FF4D4F" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="((trademarkPanelRef?.taskProgress?.platform?.total || 0) > 0 || (trademarkPanelRef?.taskProgress?.platform?.current || 0) > 0) && trademarkPanelRef?.queryStatus === 'cancel'" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<path d="M24 44C35.0457 44 44 35.0457 44 24C44 12.9543 35.0457 4 24 4C12.9543 4 4 12.9543 4 24C4 35.0457 12.9543 44 24 44Z" stroke="#FAAD14" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M29.6574 18.3428L18.3438 29.6565" stroke="#FAAD14" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.3438 18.3428L29.6574 29.6565" stroke="#FAAD14" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else-if="(trademarkPanelRef?.taskProgress?.platform?.current || 0) > 0" width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon spinning">
|
||||
<path d="M8 4H40" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 44H40" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 4V16L21 26" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 44V29.5L27 21" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 44V30L18.5 23.5" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M36 4V16L29.5 23.5" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18 33H19" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M29.1484 32.6465L29.8555 33.3536" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M24 38H25" stroke="#1677FF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<svg v-else width="24" height="24" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" class="status-indicator-icon">
|
||||
<circle cx="24" cy="24" r="20" stroke="#D9D9D9" stroke-width="2" stroke-linejoin="round"/>
|
||||
<circle cx="24" cy="24" r="4" fill="#D9D9D9"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -386,7 +480,7 @@ function handleExportData() {
|
||||
<div class="task-item">
|
||||
<div class="task-title-row">
|
||||
<div class="task-info">
|
||||
<div class="task-name">{{ trademarkPanelRef?.taskProgress?.platform?.label || '亚马逊跟卖许可筛查' }}</div>
|
||||
<div class="task-name task-name-disabled">亚马逊跟卖许可筛查(即将上线,敬请期待)</div>
|
||||
<div class="task-desc">{{ trademarkPanelRef?.taskProgress?.platform?.desc || '筛查亚马逊许可跟卖的商品' }}<span v-if="trademarkPanelRef?.queryStatus !== 'inProgress'"> (未开始)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -507,7 +601,6 @@ function handleExportData() {
|
||||
align-items: center;
|
||||
padding: 0px 12px;
|
||||
gap: 8px;
|
||||
width: 140px;
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
max-height: 40px;
|
||||
@@ -518,19 +611,19 @@ function handleExportData() {
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: #606266;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.tab-item:hover {
|
||||
background: #f0f9ff;
|
||||
border-color: #1677FF;
|
||||
color: #1677FF;
|
||||
color: #303133;
|
||||
}
|
||||
.tab-item.active {
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #1677FF;
|
||||
color: #1677FF;
|
||||
color: #303133;
|
||||
cursor: default;
|
||||
}
|
||||
.tab-icon { width: 20px; height: 20px; flex-shrink: 0; object-fit: contain; }
|
||||
@@ -576,7 +669,6 @@ function handleExportData() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.steps-sidebar.narrow {
|
||||
width: 240px;
|
||||
@@ -793,7 +885,7 @@ function handleExportData() {
|
||||
gap: 10px;
|
||||
width: 90%;
|
||||
max-width: 872px;
|
||||
margin: 0 auto;
|
||||
margin: 16px auto;
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 8px;
|
||||
@@ -816,6 +908,7 @@ function handleExportData() {
|
||||
pointer-events: none;
|
||||
}
|
||||
.genmai-info-boxes {
|
||||
margin-bottom: 16px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
@@ -840,7 +933,7 @@ function handleExportData() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 0 16px;
|
||||
padding: 0px 15px;
|
||||
}
|
||||
.info-box:not(:last-child) {
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.06);
|
||||
@@ -859,6 +952,8 @@ function handleExportData() {
|
||||
.info-link {
|
||||
color: #1677FF;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
.info-link:hover {
|
||||
text-decoration: underline;
|
||||
@@ -909,13 +1004,12 @@ function handleExportData() {
|
||||
background: transparent;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.done-banner .banner-icon {
|
||||
background: transparent;
|
||||
}
|
||||
.icon-image {
|
||||
.banner-icon svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
filter: none;
|
||||
}
|
||||
.done-banner .banner-icon {
|
||||
background: transparent;
|
||||
}
|
||||
.banner-content {
|
||||
flex: 1;
|
||||
@@ -946,10 +1040,6 @@ function handleExportData() {
|
||||
.unified-task-card {
|
||||
width: 70%;
|
||||
max-width: 900px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -982,7 +1072,7 @@ function handleExportData() {
|
||||
height: 24px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.status-indicator-icon[src*="inProgress"] {
|
||||
.status-indicator-icon.spinning {
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
.status-connector {
|
||||
@@ -1038,6 +1128,9 @@ function handleExportData() {
|
||||
margin-bottom: 2px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.task-name-disabled {
|
||||
color: #909399;
|
||||
}
|
||||
.task-desc {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
@@ -1115,8 +1208,7 @@ function handleExportData() {
|
||||
}
|
||||
/* wide保持280px不变 */
|
||||
.tab-item {
|
||||
width: 120px;
|
||||
padding: 0px 8px;
|
||||
padding: 0px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1146,8 +1238,13 @@ function handleExportData() {
|
||||
max-height: 300px;
|
||||
}
|
||||
.tab-item {
|
||||
width: 100px;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
padding: 0px 6px;
|
||||
gap: 6px;
|
||||
}
|
||||
.tab-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.status-banner,
|
||||
.unified-task-card {
|
||||
@@ -1161,9 +1258,9 @@ function handleExportData() {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.icon-image {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
.banner-icon svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.banner-title {
|
||||
font-size: 14px;
|
||||
|
||||
@@ -106,6 +106,7 @@ const completedSteps = computed(() => {
|
||||
|
||||
const showTrialExpiredDialog = ref(false)
|
||||
const trialExpiredType = ref<'device' | 'account' | 'both' | 'subscribe'>('account')
|
||||
const showExcelExample = ref(false)
|
||||
const vipStatus = inject<any>('vipStatus')
|
||||
|
||||
function showMessage(message: string, type: 'success' | 'warning' | 'error' | 'info' = 'info') {
|
||||
@@ -199,8 +200,6 @@ async function restoreSession() {
|
||||
if (!saved) return
|
||||
|
||||
const { sessionId, timestamp } = JSON.parse(saved)
|
||||
|
||||
// 检查是否在7天内
|
||||
if (Date.now() - timestamp > 7 * 24 * 60 * 60 * 1000) {
|
||||
localStorage.removeItem(`trademark_session_${username}`)
|
||||
return
|
||||
@@ -215,9 +214,31 @@ async function restoreSession() {
|
||||
queryStatus.value = result.data.queryStatus || 'idle'
|
||||
|
||||
if (result.data.taskProgress) {
|
||||
Object.assign(taskProgress.value, result.data.taskProgress)
|
||||
const saved = result.data.taskProgress
|
||||
|
||||
if (saved.product) {
|
||||
taskProgress.value.product.total = saved.product.total || 0
|
||||
taskProgress.value.product.current = saved.product.current || 0
|
||||
taskProgress.value.product.completed = saved.product.completed || 0
|
||||
}
|
||||
|
||||
if (saved.brand) {
|
||||
taskProgress.value.brand.total = saved.brand.total || 0
|
||||
taskProgress.value.brand.current = saved.brand.current || 0
|
||||
taskProgress.value.brand.completed = saved.brand.completed || 0
|
||||
}
|
||||
|
||||
if (saved.platform) {
|
||||
taskProgress.value.platform.total = saved.platform.total || 0
|
||||
taskProgress.value.platform.current = saved.platform.current || 0
|
||||
taskProgress.value.platform.completed = saved.platform.completed || 0
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复真实数据标记
|
||||
if (taskProgress.value.product.total > 0) isProductTaskRealData.value = true
|
||||
if (taskProgress.value.brand.total > 0) isBrandTaskRealData.value = true
|
||||
|
||||
emit('updateData', trademarkData.value)
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -742,7 +763,8 @@ defineExpose({
|
||||
<div class="step-index">1</div>
|
||||
<div class="step-card">
|
||||
<div class="step-header"><div class="title">导入Excel表格</div></div>
|
||||
<div class="desc">产品筛查:需导入卖家精灵选品表格,并勾选"导出主图";品牌筛查:Excel需包含"品牌"列</div>
|
||||
<div class="desc">在卖家精灵导出文档时,必须要勾选“导出主图”。导入的表格必须具备“品牌”,“商品主图”两个表头,商品主图必须为超链接。
|
||||
<span class="example-link" @click="showExcelExample = true">点击查看示例</span></div>
|
||||
<div
|
||||
class="dropzone"
|
||||
:class="{ uploading: uploadLoading, 'drag-active': dragActive }"
|
||||
@@ -767,8 +789,8 @@ defineExpose({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 选择亚马逊商标地区 -->
|
||||
<div class="flow-item">
|
||||
<!-- 2. 选择亚马逊商标地区 - 已隐藏 -->
|
||||
<div v-if="false" class="flow-item">
|
||||
<div class="step-index">2</div>
|
||||
<div class="step-card">
|
||||
<div class="step-header"><div class="title">选择亚马逊商家账号</div></div>
|
||||
@@ -798,9 +820,9 @@ defineExpose({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 选择产品地理区域 -->
|
||||
<!-- 2. 选择产品地理区域 -->
|
||||
<div class="flow-item">
|
||||
<div class="step-index">3</div>
|
||||
<div class="step-index">2</div>
|
||||
<div class="step-card">
|
||||
<div class="step-header"><div class="title">选择产品品牌地区</div></div>
|
||||
<div class="desc">暂时仅支持美国品牌商标筛查,后续将开放更多地区,敬请期待。</div>
|
||||
@@ -812,15 +834,15 @@ defineExpose({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. 选择需查询的(可多选) -->
|
||||
<!-- 3. 选择需查询的(可多选) -->
|
||||
<div class="flow-item">
|
||||
<div class="step-index">4</div>
|
||||
<div class="step-index">3</div>
|
||||
<div class="step-card">
|
||||
<div class="step-header"><div class="title">选择需查询的(可多选)</div></div>
|
||||
<div class="desc">默认查询违规产品,可多选</div>
|
||||
|
||||
<div class="query-options">
|
||||
<div :class="['query-card', { active: queryTypes.includes('product') }]" @click="toggleQueryType('product')">
|
||||
<div :class="['query-card', 'query-disabled', { active: queryTypes.includes('product') }]">
|
||||
<div class="query-check">
|
||||
<div class="check-icon" v-if="queryTypes.includes('product')">✓</div>
|
||||
</div>
|
||||
@@ -830,7 +852,7 @@ defineExpose({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="['query-card', { active: queryTypes.includes('brand') }]" @click="toggleQueryType('brand')">
|
||||
<div :class="['query-card', 'query-disabled', { active: queryTypes.includes('brand') }]">
|
||||
<div class="query-check">
|
||||
<div class="check-icon" v-if="queryTypes.includes('brand')">✓</div>
|
||||
</div>
|
||||
@@ -846,7 +868,7 @@ defineExpose({
|
||||
</div>
|
||||
<div class="query-content">
|
||||
<div class="query-title">跟卖许可筛查</div>
|
||||
<div class="query-desc">筛查亚马逊许可跟卖的商品</div>
|
||||
<div class="query-desc">筛查亚马逊许可跟卖的商品(即将上线,敬请期待)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -857,7 +879,7 @@ defineExpose({
|
||||
<!-- 底部开始查询按钮 -->
|
||||
<div class="bottom-action">
|
||||
<div class="action-header">
|
||||
<span class="step-indicator">{{ completedSteps }}/4</span>
|
||||
<span class="step-indicator">{{ completedSteps }}/3</span>
|
||||
<el-button v-if="!trademarkLoading" class="start-btn" type="primary" :disabled="!trademarkFileName || queryTypes.length === 0" @click="startTrademarkQuery">
|
||||
开始筛查
|
||||
</el-button>
|
||||
@@ -868,6 +890,11 @@ defineExpose({
|
||||
</div>
|
||||
|
||||
<TrialExpiredDialog v-model="showTrialExpiredDialog" :expired-type="trialExpiredType" />
|
||||
|
||||
<!-- Excel示例弹框 -->
|
||||
<el-dialog v-model="showExcelExample" title="表格要求" width="600px" class="excel-dialog">
|
||||
<img src="/image/excel-format-example.png" alt="Excel格式示例" style="width: 100%; height: auto; border-radius: 4px;" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -896,6 +923,24 @@ defineExpose({
|
||||
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
||||
.title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
||||
.desc { font-size: 12px; color: #909399; margin-bottom: 10px; text-align: left; line-height: 1.5; }
|
||||
.example-link {
|
||||
color: #1677FF;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.example-link:hover {
|
||||
color: #0958d9;
|
||||
}
|
||||
|
||||
/* 弹框标题左对齐 */
|
||||
:deep(.excel-dialog .el-dialog__header) {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.links { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
||||
.link { color: #909399; cursor: pointer; font-size: 12px; }
|
||||
|
||||
@@ -954,7 +999,6 @@ defineExpose({
|
||||
}
|
||||
.query-card.query-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.query-check {
|
||||
@@ -998,14 +1042,14 @@ defineExpose({
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
.query-title-disabled {
|
||||
color: #909399;
|
||||
}
|
||||
.query-desc {
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
line-height: 1.3;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,6 +97,8 @@ async function saveBrandLogoInBackground(username: string) {
|
||||
try {
|
||||
const res = await splashApi.getBrandLogo(username)
|
||||
const url = res?.data?.url || ''
|
||||
// 保存到本地配置
|
||||
await (window as any).electronAPI.saveBrandLogoConfig(username, url)
|
||||
// 触发App.vue加载品牌logo
|
||||
window.dispatchEvent(new CustomEvent('brandLogoChanged', { detail: url }))
|
||||
} catch (error) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { getToken, getUsernameFromToken } from '../../utils/token'
|
||||
import { getOrCreateDeviceId } from '../../utils/deviceId'
|
||||
import { updateApi } from '../../api/update'
|
||||
import { splashApi } from '../../api/splash'
|
||||
import { compressImage, formatFileSize, COMPRESS_PRESETS } from '../../utils/imageCompressor'
|
||||
|
||||
const TrialExpiredDialog = defineAsyncComponent(() => import('./TrialExpiredDialog.vue'))
|
||||
const refreshVipStatus = inject<() => Promise<boolean>>('refreshVipStatus')
|
||||
@@ -394,10 +395,13 @@ function triggerFileSelect() {
|
||||
async function loadSplashImage() {
|
||||
try {
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return
|
||||
|
||||
if (!username) {
|
||||
splashImageUrl.value = ''
|
||||
return
|
||||
}
|
||||
const res = await splashApi.getSplashImage(username)
|
||||
splashImageUrl.value = res.data.url
|
||||
const url = res?.data?.url || res?.data?.data?.url || ''
|
||||
splashImageUrl.value = url ? `${url}?t=${Date.now()}` : ''
|
||||
} catch (error) {
|
||||
splashImageUrl.value = ''
|
||||
}
|
||||
@@ -406,31 +410,36 @@ async function loadSplashImage() {
|
||||
// 上传开屏图片
|
||||
async function handleSplashImageUpload(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
if (!input.files || input.files.length === 0) return
|
||||
if (!input.files?.length) return
|
||||
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) {
|
||||
ElMessage.warning('请先登录')
|
||||
input.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// VIP验证
|
||||
if (refreshVipStatus) await refreshVipStatus()
|
||||
if (!props.isVip) {
|
||||
if (vipStatus) trialExpiredType.value = vipStatus.value.expiredType
|
||||
trialExpiredType.value = vipStatus?.value?.expiredType || 'account'
|
||||
showTrialExpiredDialog.value = true
|
||||
input.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
const file = input.files[0]
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return ElMessage.warning('请先登录')
|
||||
if (!file.type.startsWith('image/')) return ElMessage.error('只支持图片文件')
|
||||
if (file.size > 5 * 1024 * 1024) return ElMessage.error('图片大小不能超过5MB')
|
||||
if (file.size > 10 * 1024 * 1024) return ElMessage.error('图片大小不能超过10MB')
|
||||
|
||||
try {
|
||||
uploadingSplashImage.value = true
|
||||
const res = await splashApi.uploadSplashImage(file, username)
|
||||
const compressed = await compressImage(file, COMPRESS_PRESETS.HIGH)
|
||||
const res = await splashApi.uploadSplashImage(compressed.file, username)
|
||||
splashImageUrl.value = res.url
|
||||
await (window as any).electronAPI.saveSplashConfig(username, res.url)
|
||||
ElMessage.success('开屏图片设置成功,重启应用后生效')
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '上传失败')
|
||||
} catch (error) {
|
||||
splashImageUrl.value = ''
|
||||
} finally {
|
||||
uploadingSplashImage.value = false
|
||||
input.value = ''
|
||||
@@ -450,9 +459,11 @@ async function handleDeleteSplashImage() {
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return ElMessage.warning('请先登录')
|
||||
|
||||
// 先清空UI显示,立即生效
|
||||
splashImageUrl.value = ''
|
||||
|
||||
await splashApi.deleteSplashImage(username)
|
||||
await (window as any).electronAPI.saveSplashConfig(username, '')
|
||||
splashImageUrl.value = ''
|
||||
ElMessage.success('开屏图片已删除,重启应用后恢复默认')
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') ElMessage.error(error?.message || '删除失败')
|
||||
@@ -465,10 +476,13 @@ async function handleDeleteSplashImage() {
|
||||
async function loadBrandLogo() {
|
||||
try {
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return
|
||||
|
||||
if (!username) {
|
||||
brandLogoUrl.value = ''
|
||||
return
|
||||
}
|
||||
const res = await splashApi.getBrandLogo(username)
|
||||
brandLogoUrl.value = res.data.url
|
||||
const url = res?.data?.url || ''
|
||||
brandLogoUrl.value = url ? `${url}?t=${Date.now()}` : ''
|
||||
} catch (error) {
|
||||
brandLogoUrl.value = ''
|
||||
}
|
||||
@@ -477,31 +491,36 @@ async function loadBrandLogo() {
|
||||
// 上传品牌logo
|
||||
async function handleBrandLogoUpload(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
if (!input.files || input.files.length === 0) return
|
||||
if (!input.files?.length) return
|
||||
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) {
|
||||
ElMessage.warning('请先登录')
|
||||
input.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// VIP验证
|
||||
if (refreshVipStatus) await refreshVipStatus()
|
||||
if (!props.isVip) {
|
||||
if (vipStatus) trialExpiredType.value = vipStatus.value.expiredType
|
||||
trialExpiredType.value = vipStatus?.value?.expiredType || 'account'
|
||||
showTrialExpiredDialog.value = true
|
||||
input.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
const file = input.files[0]
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return ElMessage.warning('请先登录')
|
||||
if (!file.type.startsWith('image/')) return ElMessage.error('只支持图片文件')
|
||||
if (file.size > 5 * 1024 * 1024) return ElMessage.error('图片大小不能超过5MB')
|
||||
if (file.size > 10 * 1024 * 1024) return ElMessage.error('图片大小不能超过10MB')
|
||||
|
||||
try {
|
||||
uploadingBrandLogo.value = true
|
||||
const res = await splashApi.uploadBrandLogo(file, username)
|
||||
const compressed = await compressImage(file, COMPRESS_PRESETS.HIGH)
|
||||
const res = await splashApi.uploadBrandLogo(compressed.file, username)
|
||||
brandLogoUrl.value = res.url
|
||||
ElMessage.success('品牌logo设置成功')
|
||||
window.dispatchEvent(new CustomEvent('brandLogoChanged', { detail: res.url }))
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || '上传失败')
|
||||
} catch (error) {
|
||||
brandLogoUrl.value = ''
|
||||
} finally {
|
||||
uploadingBrandLogo.value = false
|
||||
input.value = ''
|
||||
@@ -512,7 +531,7 @@ async function handleBrandLogoUpload(event: Event) {
|
||||
async function handleDeleteBrandLogo() {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'确定要删除品牌logo吗?删除后将隐藏logo区域。',
|
||||
'确定要删除品牌 Banner吗?删除后将隐藏区域。',
|
||||
'确认删除',
|
||||
{ confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
||||
)
|
||||
@@ -521,11 +540,10 @@ async function handleDeleteBrandLogo() {
|
||||
const username = getUsernameFromToken()
|
||||
if (!username) return ElMessage.warning('请先登录')
|
||||
|
||||
await splashApi.deleteBrandLogo(username)
|
||||
|
||||
// 立即清空本地状态
|
||||
// 先清空UI显示,立即生效
|
||||
brandLogoUrl.value = ''
|
||||
ElMessage.success('品牌logo已删除')
|
||||
|
||||
await splashApi.deleteBrandLogo(username)
|
||||
|
||||
// 立即触发App.vue清空logo
|
||||
window.dispatchEvent(new CustomEvent('brandLogoChanged', { detail: '' }))
|
||||
@@ -542,6 +560,16 @@ onMounted(() => {
|
||||
loadCurrentVersion()
|
||||
loadSplashImage()
|
||||
loadBrandLogo()
|
||||
|
||||
// 监听品牌logo删除事件
|
||||
const handleBrandLogoChange = (e: any) => {
|
||||
const url = e.detail || ''
|
||||
brandLogoUrl.value = url
|
||||
}
|
||||
window.addEventListener('brandLogoChanged', handleBrandLogoChange)
|
||||
|
||||
// 组件卸载时移除监听
|
||||
return () => window.removeEventListener('brandLogoChanged', handleBrandLogoChange)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -736,7 +764,7 @@ onMounted(() => {
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="image-preview-wrapper" v-if="splashImageUrl">
|
||||
<img :src="splashImageUrl" alt="开屏图片预览" class="preview-image" />
|
||||
<img :src="splashImageUrl" alt="开屏图片" class="preview-image" />
|
||||
<div class="image-buttons">
|
||||
<input ref="fileInputRef" type="file" accept="image/*" @change="handleSplashImageUpload" style="display: none;" />
|
||||
<button class="img-btn" :disabled="uploadingSplashImage" @click="triggerFileSelect">
|
||||
@@ -747,29 +775,27 @@ onMounted(() => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="splash-placeholder" v-else>
|
||||
<span class="placeholder-icon">🖼️</span>
|
||||
<span class="placeholder-text">未设置自定义开屏图片</span>
|
||||
<div class="image-preview-wrapper splash-placeholder" v-else>
|
||||
<input ref="fileInputRef" type="file" accept="image/*" @change="handleSplashImageUpload" style="display: none;" />
|
||||
<el-button type="primary" size="small" style="margin-top: 8px;" :loading="uploadingSplashImage" @click="triggerFileSelect">
|
||||
{{ uploadingSplashImage ? '上传中' : '选择图片' }}
|
||||
</el-button>
|
||||
<div class="placeholder-content" @click="triggerFileSelect">
|
||||
<span class="placeholder-icon">🖼️</span>
|
||||
<span class="placeholder-text">{{ uploadingSplashImage ? '上传中...' : '点击选择开屏图片' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 品牌logo页面 -->
|
||||
<div id="section-brand" class="setting-section" @mouseenter="activeTab = 'brand'">
|
||||
<div class="section-title-row">
|
||||
<div class="section-title">品牌logo</div>
|
||||
<div class="section-title">品牌 Banner</div>
|
||||
<img src="/icon/vipExclusive.png" alt="VIP专享" class="vip-exclusive-logo" />
|
||||
</div>
|
||||
<div class="section-subtitle-text"> 支持 JPG、PNG 格式,最佳显示尺寸/比例 1200*736</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="image-preview-wrapper" v-if="brandLogoUrl">
|
||||
<img :src="brandLogoUrl" alt="品牌logo" class="preview-image" />
|
||||
<img :src="brandLogoUrl" alt="品牌 Banner" class="preview-image" />
|
||||
<div class="image-buttons">
|
||||
<input ref="brandLogoInputRef" type="file" accept="image/*" @change="handleBrandLogoUpload" style="display: none;" />
|
||||
<button class="img-btn" :disabled="uploadingBrandLogo" @click="brandLogoInputRef?.click()">
|
||||
@@ -780,15 +806,13 @@ onMounted(() => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="splash-placeholder" v-else>
|
||||
<span class="placeholder-icon">🏷️</span>
|
||||
<span class="placeholder-text">未设置品牌logo</span>
|
||||
<div class="image-preview-wrapper splash-placeholder" v-else>
|
||||
<input ref="brandLogoInputRef" type="file" accept="image/*" @change="handleBrandLogoUpload" style="display: none;" />
|
||||
<el-button type="primary" size="small" style="margin-top: 8px;" :loading="uploadingBrandLogo" @click="brandLogoInputRef?.click()">
|
||||
{{ uploadingBrandLogo ? '上传中' : '选择图片' }}
|
||||
</el-button>
|
||||
<div class="placeholder-content" @click="brandLogoInputRef?.click()">
|
||||
<span class="placeholder-icon">🏷️</span>
|
||||
<span class="placeholder-text">{{ uploadingBrandLogo ? '上传中...' : '点击选择品牌 Banner' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1246,8 +1270,43 @@ onMounted(() => {
|
||||
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* 占位符 */
|
||||
.splash-placeholder {
|
||||
width: 75% !important;
|
||||
height: auto !important;
|
||||
border: 2px dashed #d9d9d9 !important;
|
||||
border-radius: 8px !important;
|
||||
background: #fafafa !important;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.splash-placeholder:hover {
|
||||
border-color: #1677ff !important;
|
||||
background: #f0f7ff !important;
|
||||
}
|
||||
|
||||
.placeholder-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.image-buttons {
|
||||
|
||||
@@ -20,7 +20,6 @@ interface Emits {
|
||||
(e: 'open-settings'): void
|
||||
(e: 'open-account-manager'): void
|
||||
(e: 'check-update'): void
|
||||
(e: 'show-login'): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
@@ -113,10 +112,6 @@ onMounted(async () => {
|
||||
</template>
|
||||
</el-dropdown>
|
||||
|
||||
<!-- 登录/注册按钮 -->
|
||||
<button v-if="!isAuthenticated" class="login-btn" @click="$emit('show-login')">
|
||||
登录/注册
|
||||
</button>
|
||||
</div>
|
||||
<div class="navbar-center">
|
||||
<div class="breadcrumbs">
|
||||
@@ -324,29 +319,6 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
/* 登录/注册按钮 */
|
||||
.login-btn {
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: #1677FF;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
outline: none;
|
||||
white-space: nowrap;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.login-btn:hover {
|
||||
background: #0d5ed6;
|
||||
}
|
||||
|
||||
.login-btn:active {
|
||||
background: #0c54c2;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
|
||||
177
electron-vue-template/src/renderer/utils/imageCompressor.ts
Normal file
177
electron-vue-template/src/renderer/utils/imageCompressor.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* 图片压缩工具 - 在上传前压缩图片,减少传输和存储大小
|
||||
* 保持视觉效果的同时显著减小文件体积
|
||||
*/
|
||||
|
||||
export interface CompressOptions {
|
||||
/** 目标质量 0-1,默认0.8 */
|
||||
quality?: number
|
||||
/** 最大宽度,超过则等比缩放,默认1920 */
|
||||
maxWidth?: number
|
||||
/** 最大高度,超过则等比缩放,默认1080 */
|
||||
maxHeight?: number
|
||||
/** 输出格式,默认'image/jpeg' */
|
||||
mimeType?: string
|
||||
/** 是否转换为WebP格式(更小体积),默认false */
|
||||
useWebP?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩图片文件
|
||||
* @param file 原始图片文件
|
||||
* @param options 压缩选项
|
||||
* @returns 压缩后的Blob和压缩信息
|
||||
*/
|
||||
export async function compressImage(
|
||||
file: File,
|
||||
options: CompressOptions = {}
|
||||
): Promise<{
|
||||
blob: Blob
|
||||
file: File
|
||||
originalSize: number
|
||||
compressedSize: number
|
||||
compressionRatio: number
|
||||
}> {
|
||||
const {
|
||||
quality = 0.85,
|
||||
maxWidth = 1920,
|
||||
maxHeight = 1080,
|
||||
mimeType = 'image/jpeg',
|
||||
useWebP = false,
|
||||
} = options
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = (e) => {
|
||||
const img = new Image()
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
// 计算压缩后的尺寸(保持宽高比)
|
||||
let { width, height } = img
|
||||
const aspectRatio = width / height
|
||||
|
||||
if (width > maxWidth) {
|
||||
width = maxWidth
|
||||
height = width / aspectRatio
|
||||
}
|
||||
|
||||
if (height > maxHeight) {
|
||||
height = maxHeight
|
||||
width = height * aspectRatio
|
||||
}
|
||||
|
||||
// 创建canvas进行压缩
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) {
|
||||
reject(new Error('无法获取canvas上下文'))
|
||||
return
|
||||
}
|
||||
|
||||
// 绘制图片
|
||||
ctx.drawImage(img, 0, 0, width, height)
|
||||
|
||||
// 转换为Blob - 如果原图是PNG,保持PNG格式以保留透明度
|
||||
const isPNG = file.type === 'image/png'
|
||||
const outputMimeType = useWebP ? 'image/webp' : (isPNG ? 'image/png' : mimeType)
|
||||
|
||||
canvas.toBlob(
|
||||
(blob) => {
|
||||
if (!blob) {
|
||||
reject(new Error('图片压缩失败'))
|
||||
return
|
||||
}
|
||||
|
||||
const originalSize = file.size
|
||||
const compressedSize = blob.size
|
||||
const compressionRatio = ((1 - compressedSize / originalSize) * 100).toFixed(2)
|
||||
|
||||
// 转换为File对象
|
||||
const isPNG = file.type === 'image/png'
|
||||
let newFileName = file.name
|
||||
if (useWebP) {
|
||||
newFileName = file.name.replace(/\.[^.]+$/, '.webp')
|
||||
} else if (!isPNG) {
|
||||
newFileName = file.name.replace(/\.[^.]+$/, '.jpg')
|
||||
}
|
||||
// 如果是PNG,保持原文件名
|
||||
|
||||
const compressedFile = new File(
|
||||
[blob],
|
||||
newFileName,
|
||||
{ type: outputMimeType }
|
||||
)
|
||||
|
||||
resolve({
|
||||
blob,
|
||||
file: compressedFile,
|
||||
originalSize,
|
||||
compressedSize,
|
||||
compressionRatio: parseFloat(compressionRatio),
|
||||
})
|
||||
},
|
||||
outputMimeType,
|
||||
isPNG ? 1.0 : quality // PNG使用无损压缩(quality=1.0)
|
||||
)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
img.onerror = () => {
|
||||
reject(new Error('图片加载失败'))
|
||||
}
|
||||
|
||||
img.src = e.target?.result as string
|
||||
}
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error('文件读取失败'))
|
||||
}
|
||||
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
export function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
/**
|
||||
* 预设压缩配置
|
||||
*/
|
||||
export const COMPRESS_PRESETS = {
|
||||
/** 高质量(适合开屏图片、品牌Logo)- 1920x1080, 85%质量 */
|
||||
HIGH: {
|
||||
quality: 0.85,
|
||||
maxWidth: 1920,
|
||||
maxHeight: 1080,
|
||||
mimeType: 'image/jpeg',
|
||||
},
|
||||
/** 中等质量(适合一般图片)- 1280x720, 80%质量 */
|
||||
MEDIUM: {
|
||||
quality: 0.8,
|
||||
maxWidth: 1280,
|
||||
maxHeight: 720,
|
||||
mimeType: 'image/jpeg',
|
||||
},
|
||||
/** 缩略图(适合列表展示)- 400x400, 75%质量 */
|
||||
THUMBNAIL: {
|
||||
quality: 0.75,
|
||||
maxWidth: 400,
|
||||
maxHeight: 400,
|
||||
mimeType: 'image/jpeg',
|
||||
},
|
||||
} as const
|
||||
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
<groupId>com.tashow.erp</groupId>
|
||||
<artifactId>erp_client_sb</artifactId>
|
||||
<version>2.6.2</version>
|
||||
<version>2.6.3</version>
|
||||
<name>erp_client_sb</name>
|
||||
<description>erp客户端</description>
|
||||
<properties>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.tashow.erp.controller;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.tashow.erp.entity.TrademarkSessionEntity;
|
||||
@@ -17,9 +18,11 @@ import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 商标检查控制器
|
||||
*/
|
||||
@@ -56,9 +59,7 @@ public class TrademarkController {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> brands = (List<String>) request.get("brands");
|
||||
String taskId = (String) request.get("taskId");
|
||||
|
||||
try {
|
||||
// 保持与前端传入数量一致,不去重(允许重复品牌以匹配产品数量)
|
||||
List<String> list = brands.stream()
|
||||
.filter(b -> b != null && !b.trim().isEmpty())
|
||||
.map(String::trim)
|
||||
@@ -70,9 +71,6 @@ public class TrademarkController {
|
||||
List<String> toQuery = list.stream()
|
||||
.filter(b -> !cached.containsKey(b))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
logger.info("全局缓存命中: {}/{},需查询: {}", cached.size(), list.size(), toQuery.size());
|
||||
// 3. 查询未命中的品牌
|
||||
Map<String, Boolean> queried = new HashMap<>();
|
||||
if (!toQuery.isEmpty()) {
|
||||
for (int i = 0; i < toQuery.size(); i++) {
|
||||
@@ -138,7 +136,10 @@ public class TrademarkController {
|
||||
if (taskId != null) {
|
||||
String finalTaskId = taskId;
|
||||
new Thread(() -> {
|
||||
try { Thread.sleep(30000); } catch (InterruptedException ignored) {}
|
||||
try {
|
||||
Thread.sleep(30000);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
progressMap.remove(finalTaskId);
|
||||
cancelMap.remove(finalTaskId);
|
||||
}).start();
|
||||
|
||||
@@ -47,8 +47,8 @@ server:
|
||||
api:
|
||||
server:
|
||||
# 主服务器API配置
|
||||
#base-url: "http://8.138.23.49:8085"
|
||||
base-url: "http://192.168.1.89:8085"
|
||||
base-url: "http://8.138.23.49:8085"
|
||||
# base-url: "http://192.168.1.89:8085"
|
||||
paths:
|
||||
monitor: "/monitor/client/api"
|
||||
login: "/monitor/account/login"
|
||||
|
||||
@@ -99,8 +99,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="permissions != null">permissions = #{permissions},</if>
|
||||
<if test="deviceLimit != null">device_limit = #{deviceLimit},</if>
|
||||
<if test="accountType != null">account_type = #{accountType},</if>
|
||||
<if test="splashImage != null">splash_image = #{splashImage},</if>
|
||||
<if test="brandLogo != null">brand_logo = #{brandLogo},</if>
|
||||
splash_image = #{splashImage},
|
||||
brand_logo = #{brandLogo},
|
||||
<if test="updateBy != null">update_by = #{updateBy},</if>
|
||||
update_time = sysdate()
|
||||
</trim>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"Bandelt\",\"brand_type\":\"R\",\"status_code\":\"Live\",\"filing_date\":\"2021-06-27\",\"registration_date\":\"2022-07-12\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"Magic Ants\",\"brand_type\":\"R\",\"status_code\":\"Live\",\"filing_date\":\"2018-04-09\",\"registration_date\":\"2019-02-12\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"VWALK\",\"brand_type\":\"TM\",\"status_code\":\"Dead\",\"filing_date\":\"2020-08-18\",\"registration_date\":\"\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"TUPWEL\",\"brand_type\":\"TM\",\"status_code\":\"Dead\",\"filing_date\":\"\",\"registration_date\":\"\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"Muruseni\",\"brand_type\":\"多品类\",\"status_code\":\"\",\"filing_date\":\"\",\"registration_date\":\"\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"FLYPROFiber\",\"brand_type\":\"R\",\"status_code\":\"Live\",\"filing_date\":\"2020-07-29\",\"registration_date\":\"2021-03-09\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"LECPECON\",\"brand_type\":\"R\",\"status_code\":\"Live\",\"filing_date\":\"2020-12-28\",\"registration_date\":\"2021-10-19\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"SquEqu\",\"brand_type\":\"R\",\"status_code\":\"Live\",\"filing_date\":\"2023-01-18\",\"registration_date\":\"2024-02-06\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"pechpell\",\"brand_type\":\"TM\",\"status_code\":\"Live\",\"filing_date\":\"2024-09-27\",\"registration_date\":\"\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"Arefic\",\"brand_type\":\"R\",\"status_code\":\"Live\",\"filing_date\":\"2022-09-08\",\"registration_date\":\"2023-10-17\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"Tiga\",\"brand_type\":\"多品类\",\"status_code\":\"\",\"filing_date\":\"\",\"registration_date\":\"\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"uxcell\",\"brand_type\":\"多品类\",\"status_code\":\"\",\"filing_date\":\"\",\"registration_date\":\"\"}"
|
||||
},
|
||||
{
|
||||
"brand_info": "{\"brand_name\":\"CJRSLRB\",\"brand_type\":\"R\",\"status_code\":\"Live\",\"filing_date\":\"2014-11-28\",\"registration_date\":\"2015-07-21\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,412 +0,0 @@
|
||||
{
|
||||
"trademarks" : [ {
|
||||
"status" : {
|
||||
"staff" : {
|
||||
"examiner" : {
|
||||
"number" : null
|
||||
},
|
||||
"paralegal" : null,
|
||||
"ituParalegal" : null,
|
||||
"lie" : null,
|
||||
"chargeTo" : null
|
||||
},
|
||||
"correspondence" : {
|
||||
"freeFormAddress" : [ ],
|
||||
"address" : {
|
||||
"line1" : "625 SLATERS LANE, FOURTH FLOOR",
|
||||
"city" : "ALEXANDRIA",
|
||||
"region" : {
|
||||
"stateCountry" : {
|
||||
"code" : "VA",
|
||||
"name" : "VIRGINIA"
|
||||
},
|
||||
"isoRegion" : {
|
||||
"code" : "VA",
|
||||
"name" : "VIRGINIA"
|
||||
},
|
||||
"iso" : {
|
||||
"code" : "US",
|
||||
"name" : "UNITED STATES"
|
||||
},
|
||||
"wipo" : null
|
||||
},
|
||||
"postalCode" : "22314-1176",
|
||||
"countryCode" : "US",
|
||||
"countryName" : "UNITED STATES"
|
||||
},
|
||||
"attorneyName" : "Thomas J. Moore",
|
||||
"attorneyEmail" : {
|
||||
"authIndicator" : "Y",
|
||||
"addresses" : [ "mail@baconthomas.com" ]
|
||||
},
|
||||
"individualFullName" : "THOMAS J. MOORE",
|
||||
"firmName" : "BACON & THOMAS, PLLC",
|
||||
"correspondantPhone" : "703-683-0500",
|
||||
"correspondantFax" : "703-683-1080",
|
||||
"correspondantEmail" : {
|
||||
"authIndicator" : "Y",
|
||||
"addresses" : [ "mail@baconthomas.com" ]
|
||||
}
|
||||
},
|
||||
"serialNumber" : 88123456,
|
||||
"designSearchList" : [ ],
|
||||
"filingDate" : "2018-09-19",
|
||||
"usRegistrationNumber" : "",
|
||||
"filedAsTeasPlusApp" : true,
|
||||
"currentlyAsTeasPlusApp" : true,
|
||||
"filedAsBaseApp" : false,
|
||||
"currentlyAsBaseApp" : false,
|
||||
"filedAsTeasRfApp" : false,
|
||||
"currentlyAsTeasRfApp" : false,
|
||||
"supplementalRegister" : false,
|
||||
"amendPrincipal" : false,
|
||||
"amendSupplemental" : false,
|
||||
"trademark" : false,
|
||||
"certificationMark" : false,
|
||||
"serviceMark" : true,
|
||||
"collectiveMembershipMark" : false,
|
||||
"collectiveServiceMark" : false,
|
||||
"collectiveTradeMark" : false,
|
||||
"status" : 606,
|
||||
"statusDate" : "2019-10-14",
|
||||
"dateAbandoned" : "2019-10-14",
|
||||
"standardChar" : true,
|
||||
"markDrawingCd" : "4",
|
||||
"colorDrawingCurr" : false,
|
||||
"section2f" : false,
|
||||
"section2fPartial" : false,
|
||||
"others" : false,
|
||||
"publishedPrevRegMark" : false,
|
||||
"clsTotal" : 3,
|
||||
"filedUse" : false,
|
||||
"filedItu" : true,
|
||||
"filed44d" : false,
|
||||
"filed44e" : false,
|
||||
"filed66a" : false,
|
||||
"filedNoBasis" : false,
|
||||
"useCurr" : false,
|
||||
"ituCurr" : true,
|
||||
"sect44dCurr" : false,
|
||||
"sect44eCurr" : false,
|
||||
"sect66aCurr" : false,
|
||||
"noBasisCurr" : false,
|
||||
"useAmended" : false,
|
||||
"ituAmended" : false,
|
||||
"sect44dAmended" : false,
|
||||
"sect44eAmended" : false,
|
||||
"attrnyDktNumber" : "MYLI6005/TJM",
|
||||
"sect8Filed" : false,
|
||||
"sect8Acpt" : false,
|
||||
"sect8PartialAcpt" : false,
|
||||
"sect15Filed" : false,
|
||||
"sect15Ack" : false,
|
||||
"sect71Filed" : false,
|
||||
"sect71Acpt" : false,
|
||||
"sect71PartialAcpt" : false,
|
||||
"renewalFiled" : false,
|
||||
"changeInReg" : false,
|
||||
"lawOffAsgnCd" : "M40",
|
||||
"currLocationCd" : "700",
|
||||
"currLocationDt" : "2019-03-12",
|
||||
"chargeToLocation" : null,
|
||||
"phyLocation" : null,
|
||||
"phyLocationDt" : null,
|
||||
"extStatusDesc" : "Abandoned because no Statement of Use or Extension Request timely filed after Notice of Allowance was issued. To view all documents in this file, click on the Trademark Document Retrieval link at the top of this page. ",
|
||||
"intStatusDesc" : null,
|
||||
"markDrawDesc" : "STANDARD CHARACTER MARK",
|
||||
"currentLoc" : "INTENT TO USE SECTION",
|
||||
"correction" : "",
|
||||
"disclaimer" : "\"UL\"",
|
||||
"markElement" : "MYLIFE UL PROTECT",
|
||||
"parentOf" : [ ],
|
||||
"prevRegNumList" : [ ],
|
||||
"newLawOffAsgnCd" : "113",
|
||||
"lawOffAssigned" : "LAW OFFICE 113",
|
||||
"tm5Status" : 10,
|
||||
"tm5StatusDesc" : "DEAD/APPLICATION/Refused/Dismissed or Invalidated",
|
||||
"tm5StatusDef" : "This trademark application was refused, dismissed, or invalidated by the Office and this application is no longer active.",
|
||||
"physicalLocationHistory" : [ {
|
||||
"eventDate" : "2018-09-25",
|
||||
"physicalLocation" : "MADCD",
|
||||
"physicalLocationDescription" : "NO PHYSICAL FILE"
|
||||
}, {
|
||||
"eventDate" : "2018-09-22",
|
||||
"physicalLocation" : "OUT",
|
||||
"physicalLocationDescription" : "NO PHYSICAL FILE"
|
||||
} ],
|
||||
"pseudoMark" : null
|
||||
},
|
||||
"parties" : {
|
||||
"ownerGroups" : {
|
||||
"20" : [ {
|
||||
"serialNumber" : 88123456,
|
||||
"partyType" : 20,
|
||||
"partyTypeDescription" : "OWNER AT PUBLICATION",
|
||||
"reelFrame" : null,
|
||||
"entityNum" : 1,
|
||||
"entityType" : {
|
||||
"code" : 3,
|
||||
"description" : "CORPORATION"
|
||||
},
|
||||
"name" : "Modern Woodmen of America",
|
||||
"composedOf" : null,
|
||||
"dbaAkaFormerly" : null,
|
||||
"assignment" : null,
|
||||
"address1" : "1701 - 1st Avenue",
|
||||
"address2" : "",
|
||||
"city" : "Rock Island",
|
||||
"addressStateCountry" : {
|
||||
"stateCountry" : {
|
||||
"code" : "IL",
|
||||
"name" : "ILLINOIS"
|
||||
},
|
||||
"isoRegion" : {
|
||||
"code" : "IL",
|
||||
"name" : "ILLINOIS"
|
||||
},
|
||||
"iso" : {
|
||||
"code" : "US",
|
||||
"name" : "UNITED STATES"
|
||||
},
|
||||
"wipo" : null
|
||||
},
|
||||
"zip" : "61201",
|
||||
"citizenship" : {
|
||||
"stateCountry" : {
|
||||
"code" : "IL",
|
||||
"name" : "ILLINOIS"
|
||||
},
|
||||
"isoRegion" : {
|
||||
"code" : "IL",
|
||||
"name" : "ILLINOIS"
|
||||
},
|
||||
"iso" : {
|
||||
"code" : "US",
|
||||
"name" : "UNITED STATES"
|
||||
},
|
||||
"wipo" : null
|
||||
}
|
||||
} ],
|
||||
"10" : [ {
|
||||
"serialNumber" : 88123456,
|
||||
"partyType" : 10,
|
||||
"partyTypeDescription" : "ORIGINAL APPLICANT",
|
||||
"reelFrame" : null,
|
||||
"entityNum" : 1,
|
||||
"entityType" : {
|
||||
"code" : 3,
|
||||
"description" : "CORPORATION"
|
||||
},
|
||||
"name" : "Modern Woodmen of America",
|
||||
"composedOf" : null,
|
||||
"dbaAkaFormerly" : null,
|
||||
"assignment" : null,
|
||||
"address1" : "1701 - 1st Avenue",
|
||||
"address2" : "",
|
||||
"city" : "Rock Island",
|
||||
"addressStateCountry" : {
|
||||
"stateCountry" : {
|
||||
"code" : "IL",
|
||||
"name" : "ILLINOIS"
|
||||
},
|
||||
"isoRegion" : {
|
||||
"code" : "IL",
|
||||
"name" : "ILLINOIS"
|
||||
},
|
||||
"iso" : {
|
||||
"code" : "US",
|
||||
"name" : "UNITED STATES"
|
||||
},
|
||||
"wipo" : null
|
||||
},
|
||||
"zip" : "61201",
|
||||
"citizenship" : {
|
||||
"stateCountry" : {
|
||||
"code" : "IL",
|
||||
"name" : "ILLINOIS"
|
||||
},
|
||||
"isoRegion" : {
|
||||
"code" : "IL",
|
||||
"name" : "ILLINOIS"
|
||||
},
|
||||
"iso" : {
|
||||
"code" : "US",
|
||||
"name" : "UNITED STATES"
|
||||
},
|
||||
"wipo" : null
|
||||
}
|
||||
} ]
|
||||
}
|
||||
},
|
||||
"gsList" : [ {
|
||||
"serialNumber" : 88123456,
|
||||
"internationalClassPrime" : true,
|
||||
"usClasses" : [ {
|
||||
"code" : "100",
|
||||
"description" : "Miscellaneous"
|
||||
}, {
|
||||
"code" : "101",
|
||||
"description" : "Advertising and Business"
|
||||
}, {
|
||||
"code" : "102",
|
||||
"description" : "Insurance and Financial"
|
||||
} ],
|
||||
"internationalClasses" : [ {
|
||||
"code" : "036",
|
||||
"description" : "Insurance and financial"
|
||||
} ],
|
||||
"pseudoClasses" : [ ],
|
||||
"statusCode" : "6",
|
||||
"statusDescription" : "ACTIVE",
|
||||
"statusDate" : "2018-09-25",
|
||||
"firstUseDate" : null,
|
||||
"firstUseInCommerceDate" : null,
|
||||
"firstUseDateDescription" : null,
|
||||
"firstUseInCommerceDateDescription" : null,
|
||||
"description" : "Insurance administration in the field of life insurance; Insurance agencies in the field of life insurance; Insurance agency and brokerage; Insurance brokerage in the field of life insurance; Insurance services, namely, underwriting life insurance; Insurance services, namely, underwriting, issuance and administration of life insurance; Insurance underwriting in the field of life insurance; Issuance of life insurance",
|
||||
"classBasis" : null,
|
||||
"primeClassCode" : "036"
|
||||
} ],
|
||||
"foreignInfoList" : [ ],
|
||||
"prosecutionHistory" : [ {
|
||||
"entryNumber" : 18,
|
||||
"entryCode" : "MAB6",
|
||||
"entryType" : "E",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2019-10-15T04:00:00.000+0000",
|
||||
"entryDesc" : "ABANDONMENT NOTICE E-MAILED - NO USE STATEMENT FILED"
|
||||
}, {
|
||||
"entryNumber" : 17,
|
||||
"entryCode" : "ABN6",
|
||||
"entryType" : "S",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2019-10-14T04:00:00.000+0000",
|
||||
"entryDesc" : "ABANDONMENT - NO USE STATEMENT FILED"
|
||||
}, {
|
||||
"entryNumber" : 16,
|
||||
"entryCode" : "NOAM",
|
||||
"entryType" : "E",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2019-03-12T04:00:00.000+0000",
|
||||
"entryDesc" : "NOA E-MAILED - SOU REQUIRED FROM APPLICANT"
|
||||
}, {
|
||||
"entryNumber" : 15,
|
||||
"entryCode" : "NPUB",
|
||||
"entryType" : "E",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2019-01-15T05:00:00.000+0000",
|
||||
"entryDesc" : "OFFICIAL GAZETTE PUBLICATION CONFIRMATION E-MAILED"
|
||||
}, {
|
||||
"entryNumber" : 14,
|
||||
"entryCode" : "PUBO",
|
||||
"entryType" : "A",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2019-01-15T05:00:00.000+0000",
|
||||
"entryDesc" : "PUBLISHED FOR OPPOSITION"
|
||||
}, {
|
||||
"entryNumber" : 13,
|
||||
"entryCode" : "NONP",
|
||||
"entryType" : "E",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-12-26T05:00:00.000+0000",
|
||||
"entryDesc" : "NOTIFICATION OF NOTICE OF PUBLICATION E-MAILED"
|
||||
}, {
|
||||
"entryNumber" : 12,
|
||||
"entryCode" : "ALIE",
|
||||
"entryType" : "A",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-12-06T05:00:00.000+0000",
|
||||
"entryDesc" : "ASSIGNED TO LIE"
|
||||
}, {
|
||||
"entryNumber" : 11,
|
||||
"entryCode" : "CNSA",
|
||||
"entryType" : "P",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-11-15T05:00:00.000+0000",
|
||||
"entryDesc" : "APPROVED FOR PUB - PRINCIPAL REGISTER"
|
||||
}, {
|
||||
"entryNumber" : 10,
|
||||
"entryCode" : "XAEC",
|
||||
"entryType" : "I",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-11-09T05:00:00.000+0000",
|
||||
"entryDesc" : "EXAMINER'S AMENDMENT ENTERED"
|
||||
}, {
|
||||
"entryNumber" : 9,
|
||||
"entryCode" : "GNEN",
|
||||
"entryType" : "O",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-11-09T05:00:00.000+0000",
|
||||
"entryDesc" : "NOTIFICATION OF EXAMINERS AMENDMENT E-MAILED"
|
||||
}, {
|
||||
"entryNumber" : 8,
|
||||
"entryCode" : "GNEA",
|
||||
"entryType" : "O",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-11-09T05:00:00.000+0000",
|
||||
"entryDesc" : "EXAMINERS AMENDMENT E-MAILED"
|
||||
}, {
|
||||
"entryNumber" : 7,
|
||||
"entryCode" : "CNEA",
|
||||
"entryType" : "R",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-11-09T05:00:00.000+0000",
|
||||
"entryDesc" : "EXAMINERS AMENDMENT -WRITTEN"
|
||||
}, {
|
||||
"entryNumber" : 6,
|
||||
"entryCode" : "GNRN",
|
||||
"entryType" : "O",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-11-08T05:00:00.000+0000",
|
||||
"entryDesc" : "NOTIFICATION OF NON-FINAL ACTION E-MAILED"
|
||||
}, {
|
||||
"entryNumber" : 5,
|
||||
"entryCode" : "GNRT",
|
||||
"entryType" : "F",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-11-08T05:00:00.000+0000",
|
||||
"entryDesc" : "NON-FINAL ACTION E-MAILED"
|
||||
}, {
|
||||
"entryNumber" : 4,
|
||||
"entryCode" : "CNRT",
|
||||
"entryType" : "R",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-11-08T05:00:00.000+0000",
|
||||
"entryDesc" : "NON-FINAL ACTION WRITTEN"
|
||||
}, {
|
||||
"entryNumber" : 3,
|
||||
"entryCode" : "DOCK",
|
||||
"entryType" : "D",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-10-31T04:00:00.000+0000",
|
||||
"entryDesc" : "ASSIGNED TO EXAMINER"
|
||||
}, {
|
||||
"entryNumber" : 2,
|
||||
"entryCode" : "NWOS",
|
||||
"entryType" : "I",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-09-25T04:00:00.000+0000",
|
||||
"entryDesc" : "NEW APPLICATION OFFICE SUPPLIED DATA ENTERED"
|
||||
}, {
|
||||
"entryNumber" : 1,
|
||||
"entryCode" : "NWAP",
|
||||
"entryType" : "I",
|
||||
"proceedingNum" : 0,
|
||||
"entryDate" : "2018-09-22T04:00:00.000+0000",
|
||||
"entryDesc" : "NEW APPLICATION ENTERED"
|
||||
} ],
|
||||
"relationshipBundleList" : [ ],
|
||||
"internationalData" : false,
|
||||
"publication" : {
|
||||
"serialNumber" : 88123456,
|
||||
"datePublished" : "2019-01-15",
|
||||
"noticeOfAllowanceDate" : "2019-03-12",
|
||||
"officialGazettes" : [ ]
|
||||
},
|
||||
"divisional" : {
|
||||
"serialNumber" : 88123456,
|
||||
"childOf" : null,
|
||||
"parentOfList" : [ ]
|
||||
}
|
||||
} ]
|
||||
}
|
||||
Reference in New Issue
Block a user