style(components): format CSS styles in Vue components
- Remove extra spaces in CSS property declarations - Consolidate multi-line CSS rules into single lines - Maintain consistent formatting across component styles - Improve readability by removing unnecessary line breaks - Ensure uniform styling structure in scoped CSS blocks
This commit is contained in:
270
CLAUDE.md
Normal file
270
CLAUDE.md
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a **hybrid ERP system** consisting of:
|
||||||
|
1. **Backend**: RuoYi-Vue (Spring Boot 2.5.15 + MyBatis) - Java 17 management system
|
||||||
|
2. **Desktop Client**: Electron + Vue 3 + TypeScript application
|
||||||
|
3. **Embedded Spring Boot Service**: Java service that runs within the Electron app
|
||||||
|
|
||||||
|
The architecture uses a dual-service pattern where the Electron app communicates with both:
|
||||||
|
- Local embedded Spring Boot service (port 8081)
|
||||||
|
- Remote RuoYi admin backend (port 8085)
|
||||||
|
|
||||||
|
## Repository Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\wox\erp\
|
||||||
|
├── electron-vue-template/ # Electron + Vue 3 desktop client
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── main/ # Electron main process (TypeScript)
|
||||||
|
│ │ └── renderer/ # Vue 3 renderer process
|
||||||
|
│ │ ├── api/ # API client modules
|
||||||
|
│ │ ├── components/ # Vue components
|
||||||
|
│ │ └── utils/ # Utility functions
|
||||||
|
│ ├── scripts/ # Build scripts
|
||||||
|
│ └── package.json
|
||||||
|
├── ruoyi-admin/ # Main Spring Boot application entry
|
||||||
|
├── ruoyi-system/ # System management module
|
||||||
|
├── ruoyi-framework/ # Framework core (Security, Redis, etc.)
|
||||||
|
├── ruoyi-common/ # Common utilities
|
||||||
|
├── ruoyi-generator/ # Code generator
|
||||||
|
├── ruoyi-quartz/ # Scheduled tasks
|
||||||
|
├── erp_client_sb/ # Embedded Spring Boot service for client
|
||||||
|
├── sql/ # Database migration scripts
|
||||||
|
└── pom.xml # Root Maven configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Backend (Spring Boot)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the project (from root)
|
||||||
|
mvn clean package
|
||||||
|
|
||||||
|
# Run the RuoYi admin backend
|
||||||
|
cd ruoyi-admin
|
||||||
|
mvn spring-boot:run
|
||||||
|
# Runs on http://localhost:8085
|
||||||
|
|
||||||
|
# Build without tests
|
||||||
|
mvn clean package -DskipTests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend (Electron + Vue)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd electron-vue-template
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Development mode with hot reload
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build for distribution
|
||||||
|
npm run build # Cross-platform
|
||||||
|
npm run build:win # Windows
|
||||||
|
npm run build:mac # macOS
|
||||||
|
npm run build:linux # Linux
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Architecture Patterns
|
||||||
|
|
||||||
|
### 1. Dual-Backend Routing (http.ts)
|
||||||
|
|
||||||
|
The Electron client uses intelligent routing to determine which backend to call:
|
||||||
|
- Paths starting with `/monitor/`, `/system/`, `/tool/banma`, `/tool/genmai` → RuoYi backend (port 8085)
|
||||||
|
- All other paths → Embedded Spring Boot service (port 8081)
|
||||||
|
|
||||||
|
**Location**: `electron-vue-template/src/renderer/api/http.ts`
|
||||||
|
|
||||||
|
### 2. Account-Based Resource Isolation
|
||||||
|
|
||||||
|
User-specific resources (splash images, brand logos) are stored per account:
|
||||||
|
- Backend stores URLs in the `client_account` table (columns: `splash_image`, `brand_logo`)
|
||||||
|
- Files are uploaded to Qiniu Cloud (七牛云) configured in `application.yml`
|
||||||
|
- Each user sees only their own uploaded assets
|
||||||
|
|
||||||
|
**Key files**:
|
||||||
|
- Java entity: `ruoyi-system/src/main/java/com/ruoyi/system/domain/ClientAccount.java`
|
||||||
|
- MyBatis mapper: `ruoyi-system/src/main/resources/mapper/system/ClientAccountMapper.xml`
|
||||||
|
- API endpoints: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/monitor/ClientAccountController.java`
|
||||||
|
|
||||||
|
### 3. Event-Driven UI Updates
|
||||||
|
|
||||||
|
The Vue application uses custom browser events to propagate state changes between components:
|
||||||
|
- `window.dispatchEvent(new CustomEvent('brandLogoChanged'))` - notifies when brand logo changes
|
||||||
|
- `window.addEventListener('brandLogoChanged', handler)` - listens for changes in App.vue
|
||||||
|
|
||||||
|
This pattern ensures immediate UI updates after upload/delete operations without requiring page refreshes.
|
||||||
|
|
||||||
|
### 4. VIP Feature Gating
|
||||||
|
|
||||||
|
Certain features (e.g., custom splash images, brand logos) are gated by VIP status:
|
||||||
|
- Check `accountType` field in `ClientAccount` (values: `trial`, `paid`)
|
||||||
|
- Trial accounts show `TrialExpiredDialog` when attempting VIP features
|
||||||
|
- VIP validation happens in `SettingsDialog.vue` before allowing uploads
|
||||||
|
|
||||||
|
## Important Configuration
|
||||||
|
|
||||||
|
### Backend Configuration
|
||||||
|
|
||||||
|
**File**: `ruoyi-admin/src/main/resources/application.yml`
|
||||||
|
|
||||||
|
Key settings:
|
||||||
|
- Server port: `8085`
|
||||||
|
- Upload path: `ruoyi.profile: D:/ruoyi/uploadPath`
|
||||||
|
- Redis: `8.138.23.49:6379` (password: `123123`)
|
||||||
|
- Qiniu Cloud credentials for file storage
|
||||||
|
- Token expiration: 30 minutes
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
The system uses MySQL with MyBatis. When adding new fields:
|
||||||
|
1. Write SQL migration script in `sql/` directory
|
||||||
|
2. Update Java entity in `ruoyi-system/src/main/java/com/ruoyi/system/domain/`
|
||||||
|
3. Update MyBatis mapper XML in `ruoyi-system/src/main/resources/mapper/system/`
|
||||||
|
4. Include field in `<resultMap>`, `<sql id="select...">`, `<insert>`, and `<update>` sections
|
||||||
|
|
||||||
|
### Electron Main Process
|
||||||
|
|
||||||
|
**File**: `electron-vue-template/src/main/main.ts`
|
||||||
|
|
||||||
|
- Manages embedded Spring Boot process lifecycle
|
||||||
|
- Handles splash screen display
|
||||||
|
- Configures tray icon
|
||||||
|
- Manages auto-updates
|
||||||
|
- Uses app data directory: `app.getPath('userData')`
|
||||||
|
|
||||||
|
## Development Workflow (from .cursor/rules/guize.mdc)
|
||||||
|
|
||||||
|
When making code changes, follow this three-phase approach:
|
||||||
|
|
||||||
|
### Phase 1: Analyze Problem (【分析问题】)
|
||||||
|
- Understand user intent and ask clarifying questions
|
||||||
|
- Search all related code
|
||||||
|
- Identify root cause
|
||||||
|
- Look for code smells: duplication, poor naming, outdated patterns, inconsistent types
|
||||||
|
- Ask questions if multiple solutions exist
|
||||||
|
|
||||||
|
### Phase 2: Plan Solution (【制定方案】)
|
||||||
|
- List files to be created/modified/deleted
|
||||||
|
- Describe changes briefly for each file
|
||||||
|
- Eliminate code duplication through reuse/abstraction
|
||||||
|
- Ensure DRY principles and good architecture
|
||||||
|
- Ask questions if key decisions are unclear
|
||||||
|
|
||||||
|
### Phase 3: Execute (【执行方案】)
|
||||||
|
- Implement according to the approved plan
|
||||||
|
- Run type checking after modifications
|
||||||
|
- **DO NOT** commit code unless explicitly requested
|
||||||
|
- **DO NOT** start dev servers automatically
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Adding a New API Endpoint
|
||||||
|
|
||||||
|
1. **Backend** (Spring Boot):
|
||||||
|
```java
|
||||||
|
// In appropriate Controller (e.g., ClientAccountController.java)
|
||||||
|
@PostMapping("/your-endpoint")
|
||||||
|
public AjaxResult yourMethod(@RequestBody YourDTO dto) {
|
||||||
|
// Implementation
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Frontend** (Vue/TypeScript):
|
||||||
|
```typescript
|
||||||
|
// In electron-vue-template/src/renderer/api/your-module.ts
|
||||||
|
export const yourApi = {
|
||||||
|
async yourMethod(data: YourType) {
|
||||||
|
return http.post<ResponseType>('/your-endpoint', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Component usage**:
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { yourApi } from '@/api/your-module'
|
||||||
|
|
||||||
|
const handleAction = async () => {
|
||||||
|
try {
|
||||||
|
const res = await yourApi.yourMethod(data)
|
||||||
|
// Handle success, update local state immediately
|
||||||
|
localState.value = res.data
|
||||||
|
// Dispatch event if other components need to know
|
||||||
|
window.dispatchEvent(new CustomEvent('yourEventName'))
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Upload Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Frontend
|
||||||
|
const handleUpload = async (file: File) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
formData.append('username', currentUsername)
|
||||||
|
|
||||||
|
const res = await splashApi.uploadSomething(file, username)
|
||||||
|
if (res.url) {
|
||||||
|
localImageUrl.value = res.url // Update immediately
|
||||||
|
window.dispatchEvent(new CustomEvent('imageChanged'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
// Backend Controller
|
||||||
|
@PostMapping("/upload")
|
||||||
|
public AjaxResult upload(@RequestParam("file") MultipartFile file) {
|
||||||
|
String url = qiniuService.uploadFile(file);
|
||||||
|
// Save URL to database
|
||||||
|
return AjaxResult.success(url);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technology Stack Details
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **Framework**: Spring Boot 2.5.15
|
||||||
|
- **Security**: Spring Security 5.7.12 + JWT
|
||||||
|
- **ORM**: MyBatis with PageHelper
|
||||||
|
- **Database**: MySQL
|
||||||
|
- **Cache**: Redis (Lettuce client)
|
||||||
|
- **File Storage**: Qiniu Cloud (七牛云)
|
||||||
|
- **API Docs**: Swagger 3.0.0
|
||||||
|
- **Build**: Maven
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **Framework**: Vue 3.3.8 (Composition API with `<script setup>`)
|
||||||
|
- **Desktop**: Electron 32.1.2
|
||||||
|
- **Build**: Vite 4.5.0
|
||||||
|
- **UI Library**: Element Plus 2.11.3
|
||||||
|
- **Language**: TypeScript 5.2.2
|
||||||
|
- **Excel**: ExcelJS 4.4.0
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Currently, there is no explicit test framework configured. When adding tests:
|
||||||
|
- Backend: Use JUnit with Spring Boot Test
|
||||||
|
- Frontend: Consider Vitest (already compatible with Vite)
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- **Chinese Language**: All user-facing text should be in Chinese (simplified)
|
||||||
|
- **Code Style**: Follow existing patterns - keep code concise and avoid unnecessary abstractions
|
||||||
|
- **No Auto-commit**: Never commit changes unless explicitly requested by the user
|
||||||
|
- **Secrets**: Qiniu Cloud keys are in `application.yml` - never expose in client code
|
||||||
|
- **Token Management**: JWT tokens stored in Electron via `utils/token.ts`, sent in `Authorization` header
|
||||||
|
- **Image Proxy**: Custom protocol handler in Electron for loading images from backend
|
||||||
@@ -23,7 +23,6 @@ function openAppIfNotOpened() {
|
|||||||
!appOpened && setTimeout(openAppIfNotOpened, 50);
|
!appOpened && setTimeout(openAppIfNotOpened, 50);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
appOpened = true;
|
appOpened = true;
|
||||||
isDev
|
isDev
|
||||||
? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`)
|
? mainWindow.loadURL(`http://localhost:${process.argv[2] || 8083}`)
|
||||||
@@ -473,14 +472,14 @@ app.whenReady().then(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 已手动启动后端
|
// 已手动启动后端
|
||||||
setTimeout(() => {
|
|
||||||
startSpringBoot();
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
// openAppIfNotOpened();
|
// startSpringBoot();
|
||||||
// }, 200);
|
// }, 200);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
openAppIfNotOpened();
|
||||||
|
}, 200);
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ const SSEManager = {
|
|||||||
|
|
||||||
const src = new EventSource(`${CONFIG.SSE_URL}?clientId=${clientId}&token=${token}`)
|
const src = new EventSource(`${CONFIG.SSE_URL}?clientId=${clientId}&token=${token}`)
|
||||||
this.connection = src
|
this.connection = src
|
||||||
src.onopen = () => console.log('SSE连接已建立')
|
src.onopen = () => {}
|
||||||
src.onmessage = (e) => this.handleMessage(e)
|
src.onmessage = (e) => this.handleMessage(e)
|
||||||
src.onerror = () => this.handleError()
|
src.onerror = () => this.handleError()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -438,11 +438,9 @@ const SSEManager = {
|
|||||||
try {
|
try {
|
||||||
if (e.type === 'ping') return
|
if (e.type === 'ping') return
|
||||||
|
|
||||||
console.log('SSE消息:', e.data)
|
|
||||||
const payload = JSON.parse(e.data)
|
const payload = JSON.parse(e.data)
|
||||||
switch (payload.type) {
|
switch (payload.type) {
|
||||||
case 'ready':
|
case 'ready':
|
||||||
console.log('SSE连接已就绪')
|
|
||||||
break
|
break
|
||||||
case 'DEVICE_REMOVED':
|
case 'DEVICE_REMOVED':
|
||||||
await clearLocalAuth()
|
await clearLocalAuth()
|
||||||
@@ -825,468 +823,80 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.root {position: fixed; inset: 0; width: 100vw; height: 100vh; background-color: #f5f5f5; opacity: 0; transition: opacity 0.1s ease;}
|
||||||
.root {
|
.loading-container {display: flex; justify-content: center; align-items: center; height: 100vh; width: 100%; position: fixed; top: 0; left: 0; background-color: #f5f5f5; z-index: 9999; transition: opacity 0.1s ease;}
|
||||||
position: fixed;
|
.loading-spinner {width: 50px; height: 50px; border: 5px solid #e6e6e6; border-top: 5px solid #409EFF; border-radius: 50%; animation: spin 1s linear infinite;}
|
||||||
inset: 0;
|
@keyframes spin {0% {
|
||||||
width: 100vw;
|
transform: rotate(0deg);}
|
||||||
height: 100vh;
|
100% {transform: rotate(360deg);}
|
||||||
background-color: #f5f5f5;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
z-index: 9999;
|
|
||||||
transition: opacity 0.1s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border: 5px solid #e6e6e6;
|
|
||||||
border-top: 5px solid #409EFF;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.erp-container {
|
|
||||||
display: flex;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
width: 220px;
|
|
||||||
min-width: 220px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background: #F0F0F0;
|
|
||||||
border-right: 1px solid #e8eaec;
|
|
||||||
padding: 16px 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.platform-icons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picon {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.erp-container {display: flex; height: 100vh;}
|
||||||
|
.sidebar {width: 220px; min-width: 220px; flex-shrink: 0; background: #F0F0F0; border-right: 1px solid #e8eaec; padding: 16px 12px; box-sizing: border-box; display: flex; flex-direction: column;}
|
||||||
|
.platform-icons {display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px;}
|
||||||
|
.picon {width: 28px; height: 28px; object-fit: contain;}
|
||||||
/* 主Logo */
|
/* 主Logo */
|
||||||
.main-logo {
|
.main-logo {align-items: center; justify-content: center; padding: 8px 0; margin: 0 0 16px 0;}
|
||||||
align-items: center;
|
.main-logo img {width: 120px; object-fit: contain;}
|
||||||
justify-content: center;
|
|
||||||
padding: 8px 0;
|
|
||||||
margin: 0 0 16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-logo img {
|
|
||||||
width: 120px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 用户头像区域 */
|
/* 用户头像区域 */
|
||||||
.user-avatar-section {
|
.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;}
|
||||||
display: flex;
|
.avatar-wrapper {flex-shrink: 0; width: 40px; height: 40px; 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);}
|
||||||
align-items: center;
|
.user-avatar-img {width: 100%; height: 100%; object-fit: cover;}
|
||||||
gap: 7px;
|
.user-info {flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; text-align: left;}
|
||||||
padding: 8px 10px;
|
.user-name-wrapper {display: flex; align-items: center; gap: 4px; min-width: 0;}
|
||||||
margin: 0 0 16px 0;
|
.user-name {font-size: 14px; font-weight: 600; color: #303133; white-space: nowrap; line-height: 1.2; min-width: 0; overflow: hidden; text-overflow: ellipsis;}
|
||||||
border-radius: 8px;
|
.vip-badge {display: inline-flex; align-items: center; justify-content: center; padding: 0px 3px; height: 14px; 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%;}
|
||||||
cursor: pointer;
|
.user-action {font-size: 11px; color: #909399; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}
|
||||||
transition: all 0.2s ease;
|
.menu-group-title {font-size: 12px; color: #909399; margin: 8px 6px 10px; text-align: left;}
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-wrapper {
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
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: 4px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-name {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
white-space: nowrap;
|
|
||||||
line-height: 1.2;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0px 3px;
|
|
||||||
height: 14px;
|
|
||||||
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;
|
|
||||||
margin: 8px 6px 10px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 品牌logo区域 */
|
/* 品牌logo区域 */
|
||||||
.brand-logo-section {
|
.brand-logo-section {display: flex; justify-content: center; align-items: center; width: 100%; height: 60px; margin-bottom: 16px; box-sizing: border-box;}
|
||||||
display: flex;
|
.brand-logo {width: 100%; height: 100%; object-fit: cover; border-radius: 12px;}
|
||||||
justify-content: center;
|
.menu {list-style: none; padding: 0; margin: 0;}
|
||||||
align-items: center;
|
.menu-item {display: flex; align-items: center; padding: 12px 16px; border-radius: 8px; cursor: pointer; color: #333333; margin-bottom: 4px;}
|
||||||
width: 100%;
|
.menu-item:hover {background: #f5f7fa;}
|
||||||
height: 60px;
|
.menu-item.active {background: rgba(0, 0, 0, 0.1) !important; /* color: #409EFF !important; */;}
|
||||||
margin-bottom: 16px;
|
.menu-text {font-size: 14px;}
|
||||||
box-sizing: border-box;
|
.menu-text {display: inline-flex; align-items: center; gap: 6px;}
|
||||||
}
|
.menu-icon {display: inline-flex; width: 18px; height: 18px; border-radius: 4px; align-items: center; justify-content: center; font-size: 12px; color: #fff;}
|
||||||
|
.menu-icon-img {width: 100%; height: 100%; object-fit: contain;}
|
||||||
.brand-logo {
|
.menu-icon[data-k="shopee"] {background: #EE4D2D;}
|
||||||
width: 100%;
|
.main-content {flex: 1; min-width: 0; position: relative; display: flex; flex-direction: column;}
|
||||||
height: 100%;
|
.content-body {position: relative; flex: 1; background: #fff; min-height: 0; overflow: hidden;}
|
||||||
object-fit: cover;
|
.dashboard-home {position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: #ffffff; z-index: 100;}
|
||||||
border-radius: 12px;
|
.icon-container {display: flex; justify-content: center;}
|
||||||
}
|
.main-icon {width: 400px; height: 400px; border-radius: 20px; object-fit: contain;}
|
||||||
|
.placeholder {position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: #fff;}
|
||||||
.menu {
|
.placeholder-card {background: #ffffff; border: 1px solid #e8eaec; border-radius: 12px; padding: 24px 28px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); color: #2c3e50;}
|
||||||
list-style: none;
|
.placeholder-title {font-size: 18px; font-weight: 600; margin-bottom: 8px;}
|
||||||
padding: 0;
|
.placeholder-desc {font-size: 13px; color: #606266;}
|
||||||
margin: 0;
|
.device-dialog-header {display: flex; flex-direction: column; align-items: center; padding: 12px 0 4px 0; margin-left: 40px;}
|
||||||
}
|
.device-dialog :deep(.el-dialog__header) {text-align: center;}
|
||||||
|
.device-dialog :deep(.el-dialog__body) {padding-top: 0;}
|
||||||
.menu-item {
|
.device-illustration {width: 180px; height: auto; object-fit: contain; margin-bottom: 8px;}
|
||||||
display: flex;
|
.device-title {font-size: 18px; font-weight: 600; color: #303133; margin-bottom: 6px;}
|
||||||
align-items: center;
|
.device-count {color: #909399; font-weight: 500;}
|
||||||
padding: 12px 16px;
|
.device-subtitle {font-size: 12px; color: #909399;}
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #333333;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item:hover {
|
|
||||||
background: #f5f7fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item.active {
|
|
||||||
background: rgba(0, 0, 0, 0.1) !important;
|
|
||||||
/* color: #409EFF !important; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-text {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-text {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-icon {
|
|
||||||
display: inline-flex;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
border-radius: 4px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-icon-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-icon[data-k="shopee"] {
|
|
||||||
background: #EE4D2D;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.content-body {
|
|
||||||
position: relative;
|
|
||||||
flex: 1;
|
|
||||||
background: #fff;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard-home {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #ffffff;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-icon {
|
|
||||||
width: 400px;
|
|
||||||
height: 400px;
|
|
||||||
border-radius: 20px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-card {
|
|
||||||
background: #ffffff;
|
|
||||||
border: 1px solid #e8eaec;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 24px 28px;
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-desc {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #606266;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-dialog-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 12px 0 4px 0;
|
|
||||||
margin-left: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-dialog :deep(.el-dialog__header) {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-dialog :deep(.el-dialog__body) {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-illustration {
|
|
||||||
width: 180px;
|
|
||||||
height: auto;
|
|
||||||
object-fit: contain;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-count {
|
|
||||||
color: #909399;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-subtitle {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 浮动版本信息 */
|
/* 浮动版本信息 */
|
||||||
.version-info {
|
.version-info {position: fixed; right: 10px; bottom: 10px; background: rgba(255, 255, 255, 0.9); padding: 5px 10px; border-radius: 4px; font-size: 12px; color: #909399; z-index: 1000; cursor: pointer; user-select: none;}
|
||||||
position: fixed;
|
|
||||||
right: 10px;
|
|
||||||
bottom: 10px;
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
z-index: 1000;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* VIP状态卡片样式 */
|
/* VIP状态卡片样式 */
|
||||||
.vip-status-card {
|
.vip-status-card {margin-top: auto; width: 100%; border-radius: 8px; display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; box-sizing: border-box; background: #BAE0FF; box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15); transition: all 0.3s ease; position: relative; cursor: pointer; user-select: none;}
|
||||||
margin-top: auto;
|
.vip-status-card:hover {box-shadow: 0 3px 10px rgba(255, 215, 0, 0.25); transform: translateY(-1px);}
|
||||||
width: 100%;
|
.vip-status-card:active {transform: translateY(0);}
|
||||||
border-radius: 8px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 10px 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: #BAE0FF;
|
|
||||||
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-status-card:hover {
|
|
||||||
box-shadow: 0 3px 10px rgba(255, 215, 0, 0.25);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-status-card:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 正常状态和警告状态 - 统一温暖金色渐变 */
|
/* 正常状态和警告状态 - 统一温暖金色渐变 */
|
||||||
.vip-status-card.vip-active,
|
.vip-status-card.vip-active,
|
||||||
.vip-status-card.vip-normal,
|
.vip-status-card.vip-normal,
|
||||||
.vip-status-card.vip-warning {
|
.vip-status-card.vip-warning {background: #BAE0FF; box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);}
|
||||||
background: #BAE0FF;
|
|
||||||
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 过期状态 - 灰色,保持水平布局 */
|
/* 过期状态 - 灰色,保持水平布局 */
|
||||||
.vip-status-card.vip-expired {
|
.vip-status-card.vip-expired {background: linear-gradient(135deg, #FAFAFA 0%, #E8E8E8 100%); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);}
|
||||||
background: linear-gradient(135deg, #FAFAFA 0%, #E8E8E8 100%);
|
.vip-info {flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; text-align: left;}
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
.vip-status-text {font-size: 13px; font-weight: 600; color: #001D66; text-align: left; letter-spacing: 0.3px;}
|
||||||
}
|
.vip-expire-date {font-size: 10px; color: #001D66; line-height: 1.3; text-align: left; opacity: 0.9;}
|
||||||
|
.vip-status-card.vip-expired .vip-info {align-items: flex-start;}
|
||||||
.vip-info {
|
.vip-status-card.vip-expired .vip-status-text {color: #909399;}
|
||||||
flex: 1;
|
.vip-status-card.vip-expired .vip-expire-date {color: #B0B0B0;}
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 3px;
|
|
||||||
min-width: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-status-text {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #001D66;
|
|
||||||
text-align: left;
|
|
||||||
letter-spacing: 0.3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-expire-date {
|
|
||||||
font-size: 10px;
|
|
||||||
color: #001D66;
|
|
||||||
line-height: 1.3;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-status-card.vip-expired .vip-info {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-status-card.vip-expired .vip-status-text {
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-status-card.vip-expired .vip-expire-date {
|
|
||||||
color: #B0B0B0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* 全局样式:限制图片预览器大小 */
|
/* 全局样式:限制图片预览器大小 */
|
||||||
.el-image-viewer__img {
|
.el-image-viewer__img {max-width: 50vw !important; max-height: 50vh !important; width: auto !important; height: auto !important;}
|
||||||
max-width: 50vw !important;
|
|
||||||
max-height: 50vh !important;
|
|
||||||
width: auto !important;
|
|
||||||
height: auto !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
export type HttpMethod = 'GET' | 'POST' | 'DELETE';
|
import { AppConfig, isRuoyiPath } from '../config'
|
||||||
const RUOYI_BASE = 'http://8.138.23.49:8085';
|
|
||||||
//const RUOYI_BASE = 'http://192.168.1.89:8085';
|
export type HttpMethod = 'GET' | 'POST' | 'DELETE'
|
||||||
export const CONFIG = {
|
export const CONFIG = AppConfig
|
||||||
CLIENT_BASE: 'http://localhost:8081',
|
|
||||||
RUOYI_BASE,
|
|
||||||
SSE_URL: `${RUOYI_BASE}/monitor/account/events`
|
|
||||||
} as const;
|
|
||||||
function resolveBase(path: string): string {
|
function resolveBase(path: string): string {
|
||||||
// 路由到 ruoyi-admin (8085):仅系统管理和监控相关
|
return isRuoyiPath(path) ? CONFIG.RUOYI_BASE : CONFIG.CLIENT_BASE
|
||||||
if (path.startsWith('/monitor/') || path.startsWith('/system/') || path.startsWith('/tool/banma') || path.startsWith('/tool/genmai')) {
|
|
||||||
return CONFIG.RUOYI_BASE;
|
|
||||||
}
|
|
||||||
return CONFIG.CLIENT_BASE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildQuery(params?: Record<string, unknown>): string {
|
function buildQuery(params?: Record<string, unknown>): string {
|
||||||
|
|||||||
@@ -535,710 +535,240 @@ function handleExportData() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.amazon-root {
|
.amazon-root {position: absolute; inset: 0; background: #fff; box-sizing: border-box; overflow: hidden;}
|
||||||
position: absolute;
|
.main-container {height: 100%; display: flex; flex-direction: column; padding: 12px; box-sizing: border-box; overflow: hidden; min-height: 0;}
|
||||||
inset: 0;
|
|
||||||
background: #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.main-container {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 顶部标签栏 */
|
/* 顶部标签栏 */
|
||||||
.top-tabs {
|
.top-tabs {display: flex; gap: 8px; margin-bottom: 12px; flex-shrink: 0;}
|
||||||
display: flex;
|
.tab-item {box-sizing: border-box; display: flex; flex-direction: row; justify-content: center; align-items: center; padding: 0px 12px; gap: 8px; height: 40px; min-height: 40px; max-height: 40px; background: #FFFFFF; border: 1px solid #e5e7eb; border-radius: 8px; flex: none; flex-shrink: 0; cursor: pointer; transition: all 0.2s ease; color: #303133; font-size: 14px; font-weight: 400;}
|
||||||
gap: 8px;
|
.tab-item:hover {background: #f0f9ff; border-color: #1677FF; color: #303133;}
|
||||||
margin-bottom: 12px;
|
.tab-item.active {background: #FFFFFF; border: 1px solid #1677FF; color: #303133; cursor: default;}
|
||||||
flex-shrink: 0;
|
.tab-icon {width: 20px; height: 20px; flex-shrink: 0; object-fit: contain;}
|
||||||
}
|
.tab-text {line-height: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;}
|
||||||
.tab-item {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0px 12px;
|
|
||||||
gap: 8px;
|
|
||||||
height: 40px;
|
|
||||||
min-height: 40px;
|
|
||||||
max-height: 40px;
|
|
||||||
background: #FFFFFF;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
border-radius: 8px;
|
|
||||||
flex: none;
|
|
||||||
flex-shrink: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
color: #303133;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
.tab-item:hover {
|
|
||||||
background: #f0f9ff;
|
|
||||||
border-color: #1677FF;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
.tab-item.active {
|
|
||||||
background: #FFFFFF;
|
|
||||||
border: 1px solid #1677FF;
|
|
||||||
color: #303133;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
.tab-icon { width: 20px; height: 20px; flex-shrink: 0; object-fit: contain; }
|
|
||||||
.tab-text {
|
|
||||||
line-height: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 账号列表样式(和斑马一致) */
|
/* 账号列表样式(和斑马一致) */
|
||||||
.account-list { height: auto; }
|
.account-list {height: auto;}
|
||||||
.scroll-limit { max-height: 140px; }
|
.scroll-limit {max-height: 140px;}
|
||||||
.placeholder-box { display: flex; align-items: center; justify-content: center; flex-direction: column; height: 100px; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 8px; }
|
.placeholder-box {display: flex; align-items: center; justify-content: center; flex-direction: column; height: 100px; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 8px;}
|
||||||
.placeholder-img { width: 80px; opacity: 0.9; }
|
.placeholder-img {width: 80px; opacity: 0.9;}
|
||||||
.placeholder-tip { margin-top: 6px; font-size: 12px; color: #a8abb2; }
|
.placeholder-tip {margin-top: 6px; font-size: 12px; color: #a8abb2;}
|
||||||
.avatar { width: 18px; height: 18px; border-radius: 50%; }
|
.avatar {width: 18px; height: 18px; border-radius: 50%;}
|
||||||
.acct-row { display: grid; grid-template-columns: 6px 18px 1fr auto; align-items: center; gap: 6px; width: 100%; }
|
.acct-row {display: grid; grid-template-columns: 6px 18px 1fr auto; align-items: center; gap: 6px; width: 100%;}
|
||||||
.acct-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 12px; }
|
.acct-text {overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 12px;}
|
||||||
.status-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
|
.status-dot {width: 6px; height: 6px; border-radius: 50%; display: inline-block;}
|
||||||
.status-dot.on { background: #22c55e; }
|
.status-dot.on {background: #22c55e;}
|
||||||
.status-dot.off { background: #f87171; }
|
.status-dot.off {background: #f87171;}
|
||||||
.acct-item { padding: 6px 8px; border-radius: 6px; cursor: pointer; margin-bottom: 4px; }
|
.acct-item {padding: 6px 8px; border-radius: 6px; cursor: pointer; margin-bottom: 4px;}
|
||||||
.acct-item.selected { background: #eef5ff; box-shadow: inset 0 0 0 1px #d6e4ff; }
|
.acct-item.selected {background: #eef5ff; box-shadow: inset 0 0 0 1px #d6e4ff;}
|
||||||
.acct-check { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; background: transparent; color: #111; font-size: 12px; }
|
.acct-check {display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; background: transparent; color: #111; font-size: 12px;}
|
||||||
.account-list::-webkit-scrollbar { width: 0; height: 0; }
|
.account-list::-webkit-scrollbar {width: 0; height: 0;}
|
||||||
|
.body-layout {display: flex; gap: 12px; flex: 1; overflow: hidden; min-height: 0;}
|
||||||
.body-layout {
|
.steps-sidebar {background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 12px; height: 100%; flex-shrink: 0; display: flex; flex-direction: column; box-sizing: border-box;}
|
||||||
display: flex;
|
.steps-sidebar.narrow {width: 240px;}
|
||||||
gap: 12px;
|
.steps-sidebar.wide {width: 280px;}
|
||||||
flex: 1;
|
.steps-title {font-size: 14px; font-weight: 600; color: #303133; text-align: left; flex-shrink: 0; margin-bottom: 8px;}
|
||||||
overflow: hidden;
|
.steps-flow {position: relative;}
|
||||||
min-height: 0;
|
.steps-flow:before {content: ''; position: absolute; left: 13px; top: 26px; bottom: 0; width: 2px; background: rgba(229, 231, 235, 0.6);}
|
||||||
}
|
.flow-item {position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 12px; padding: 10px 0;}
|
||||||
.steps-sidebar {
|
.flow-item .step-index {position: static; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 14px; font-weight: 600; margin-top: 2px;}
|
||||||
background: #fff;
|
.flow-item:after {display: none;}
|
||||||
border: 1px solid #ebeef5;
|
.step-card {border: none; border-radius: 0; padding: 0; background: transparent;}
|
||||||
border-radius: 6px;
|
.step-header {display: flex; align-items: center; gap: 8px; margin-bottom: 8px;}
|
||||||
padding: 12px;
|
.title {font-size: 14px; font-weight: 600; color: #303133; text-align: left;}
|
||||||
height: 100%;
|
.desc {font-size: 12px; color: #909399; margin-bottom: 10px; text-align: left; line-height: 1.5;}
|
||||||
flex-shrink: 0;
|
.mini-hint {font-size: 12px; color: #909399; margin-top: 6px;}
|
||||||
display: flex;
|
.links {display: flex; align-items: center; gap: 6px; margin-bottom: 8px;}
|
||||||
flex-direction: column;
|
.link {color: #409EFF; cursor: pointer; font-size: 12px;}
|
||||||
box-sizing: border-box;
|
.sep {color: #dcdfe6;}
|
||||||
}
|
.content-panel {flex: 1; display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden;}
|
||||||
.steps-sidebar.narrow {
|
.left-controls {margin-top: 10px; display: flex; flex-direction: column; gap: 10px;}
|
||||||
width: 240px;
|
.dropzone {border: 1px dashed #c0c4cc; border-radius: 6px; padding: 16px; text-align: center; cursor: pointer; background: #fafafa;}
|
||||||
}
|
.dropzone:hover {background: #f6fbff; border-color: #409EFF;}
|
||||||
.steps-sidebar.wide {
|
.dz-icon {font-size: 20px; margin-bottom: 6px;}
|
||||||
width: 280px;
|
.dz-text {color: #303133; font-size: 13px;}
|
||||||
}
|
.dz-sub {color: #909399; font-size: 12px;}
|
||||||
.steps-title {
|
.file-chip {display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; color: #606266; margin-top: 6px;}
|
||||||
font-size: 14px;
|
.file-chip .dot {width: 6px; height: 6px; background: #409EFF; border-radius: 50%; display: inline-block;}
|
||||||
font-weight: 600;
|
.file-chip .name {overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
|
||||||
color: #303133;
|
.single-input.left {display: flex; gap: 8px;}
|
||||||
text-align: left;
|
.action-buttons.column {display: flex; flex-direction: column; gap: 8px;}
|
||||||
flex-shrink: 0;
|
.step-actions {margin-top: 8px; display: flex; gap: 8px;}
|
||||||
margin-bottom: 8px;
|
.btn-row {display: grid; grid-template-columns: 1fr 1fr; gap: 8px;}
|
||||||
}
|
.w50 {width: 100%;}
|
||||||
.steps-flow { position: relative; }
|
.form-row {margin-bottom: 10px;}
|
||||||
.steps-flow:before { content: ''; position: absolute; left: 13px; top: 26px; bottom: 0; width: 2px; background: rgba(229, 231, 235, 0.6); }
|
.label {display: block; font-size: 12px; color: #606266; margin-bottom: 6px;}
|
||||||
.flow-item { position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 12px; padding: 10px 0; }
|
|
||||||
.flow-item .step-index { position: static; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 14px; font-weight: 600; margin-top: 2px; }
|
|
||||||
.flow-item:after { display: none; }
|
|
||||||
.step-card { border: none; border-radius: 0; padding: 0; background: transparent; }
|
|
||||||
.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; }
|
|
||||||
.mini-hint { font-size: 12px; color: #909399; margin-top: 6px; }
|
|
||||||
.links { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
|
||||||
.link { color: #409EFF; cursor: pointer; font-size: 12px; }
|
|
||||||
.sep { color: #dcdfe6; }
|
|
||||||
.content-panel {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-controls { margin-top: 10px; display: flex; flex-direction: column; gap: 10px; }
|
|
||||||
.dropzone { border: 1px dashed #c0c4cc; border-radius: 6px; padding: 16px; text-align: center; cursor: pointer; background: #fafafa; }
|
|
||||||
.dropzone:hover { background: #f6fbff; border-color: #409EFF; }
|
|
||||||
.dz-icon { font-size: 20px; margin-bottom: 6px; }
|
|
||||||
.dz-text { color: #303133; font-size: 13px; }
|
|
||||||
.dz-sub { color: #909399; font-size: 12px; }
|
|
||||||
.file-chip { display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; color: #606266; margin-top: 6px; }
|
|
||||||
.file-chip .dot { width: 6px; height: 6px; background: #409EFF; border-radius: 50%; display: inline-block; }
|
|
||||||
.file-chip .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
||||||
.single-input.left { display: flex; gap: 8px; }
|
|
||||||
.action-buttons.column { display: flex; flex-direction: column; gap: 8px; }
|
|
||||||
.step-actions { margin-top: 8px; display: flex; gap: 8px; }
|
|
||||||
.btn-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
||||||
.w50 { width: 100%; }
|
|
||||||
.form-row { margin-bottom: 10px; }
|
|
||||||
.label { display: block; font-size: 12px; color: #606266; margin-bottom: 6px; }
|
|
||||||
|
|
||||||
/* 统一左侧控件宽度与主色 */
|
/* 统一左侧控件宽度与主色 */
|
||||||
.steps-sidebar :deep(.el-date-editor),
|
.steps-sidebar :deep(.el-date-editor),
|
||||||
.steps-sidebar :deep(.el-range-editor.el-input__wrapper),
|
.steps-sidebar :deep(.el-range-editor.el-input__wrapper),
|
||||||
.steps-sidebar :deep(.el-input),
|
.steps-sidebar :deep(.el-input),
|
||||||
.steps-sidebar :deep(.el-input__wrapper),
|
.steps-sidebar :deep(.el-input__wrapper),
|
||||||
.steps-sidebar :deep(.el-select) { width: 100%; box-sizing: border-box; }
|
.steps-sidebar :deep(.el-select) {width: 100%; box-sizing: border-box;}
|
||||||
.btn-blue { background: #1677FF; border-color: #1677FF; color: #fff; }
|
.btn-blue {background: #1677FF; border-color: #1677FF; color: #fff;}
|
||||||
.btn-blue:disabled { background: #a6c8ff; border-color: #a6c8ff; color: #fff; }
|
.btn-blue:disabled {background: #a6c8ff; border-color: #a6c8ff; color: #fff;}
|
||||||
.w100 { width: 100%; }
|
.w100 {width: 100%;}
|
||||||
.steps-sidebar :deep(.el-button + .el-button) { margin-left: 0; }
|
.steps-sidebar :deep(.el-button + .el-button) {margin-left: 0;}
|
||||||
.import-section { margin-bottom: 10px; flex-shrink: 0; }
|
.import-section {margin-bottom: 10px; flex-shrink: 0;}
|
||||||
.import-controls { display: flex; align-items: flex-end; gap: 20px; flex-wrap: wrap; margin-bottom: 8px; }
|
.import-controls {display: flex; align-items: flex-end; gap: 20px; flex-wrap: wrap; margin-bottom: 8px;}
|
||||||
.single-input { display: flex; align-items: center; gap: 8px; }
|
.single-input {display: flex; align-items: center; gap: 8px;}
|
||||||
.text { width: 180px; height: 32px; padding: 0 10px; border: 1px solid #dcdfe6; border-radius: 4px; font-size: 14px; outline: none; transition: border-color 0.2s ease; }
|
.text {width: 180px; height: 32px; padding: 0 10px; border: 1px solid #dcdfe6; border-radius: 4px; font-size: 14px; outline: none; transition: border-color 0.2s ease;}
|
||||||
.text:focus { border-color: #409EFF; }
|
.text:focus {border-color: #409EFF;}
|
||||||
.text:disabled { background: #f5f7fa; color: #c0c4cc; }
|
.text:disabled {background: #f5f7fa; color: #c0c4cc;}
|
||||||
.action-buttons { display: flex; gap: 10px; flex-wrap: wrap; }
|
.action-buttons {display: flex; gap: 10px; flex-wrap: wrap;}
|
||||||
.current-status { font-size: 12px; color: #606266; padding-left: 2px; }
|
.current-status {font-size: 12px; color: #606266; padding-left: 2px;}
|
||||||
.table-container {
|
.table-container {display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden;}
|
||||||
display: flex;
|
.table-section {flex: 1; overflow: hidden; position: relative; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; display: flex; flex-direction: column; min-height: 0;}
|
||||||
flex-direction: column;
|
.table-wrapper {flex: 1; min-height: 0; overflow: auto; scrollbar-width: thin; scrollbar-color: #c0c4cc transparent;}
|
||||||
flex: 1;
|
.table-wrapper::-webkit-scrollbar {width: 6px; height: 6px;}
|
||||||
min-height: 0;
|
.table-wrapper::-webkit-scrollbar-track {background: transparent;}
|
||||||
overflow: hidden;
|
.table-wrapper::-webkit-scrollbar-thumb {background: #c0c4cc; border-radius: 3px;}
|
||||||
|
.table-wrapper:hover::-webkit-scrollbar-thumb {background: #a8abb2;}
|
||||||
|
.table {width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed;}
|
||||||
|
.table th {background: #f5f7fa; color: #909399; font-weight: 600; padding: 12px 8px; border-bottom: 2px solid #ebeef5; text-align: center;}
|
||||||
|
.table td {padding: 10px 8px; border-bottom: 1px solid #f0f0f0; vertical-align: middle; text-align: center;}
|
||||||
|
.table tbody tr:hover {background: #f9f9f9;}
|
||||||
|
.table th:nth-child(1), .table td:nth-child(1) {width: 33.33%;}
|
||||||
|
.table th:nth-child(2), .table td:nth-child(2) {width: 33.33%;}
|
||||||
|
.table th:nth-child(3), .table td:nth-child(3) {width: 33.33%;}
|
||||||
|
.asin-out {color: #f56c6c; font-weight: 600;}
|
||||||
|
.seller-info {display: flex; align-items: center; gap: 4px; justify-content: center;}
|
||||||
|
.seller {color: #303133; font-weight: 500;}
|
||||||
|
.shipper {color: #909399; font-size: 12px;}
|
||||||
|
.price {color: #e6a23c; font-weight: 600;}
|
||||||
|
.table-loading {position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266;}
|
||||||
|
.spinner {font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px;}
|
||||||
|
.inline-spinner {display: inline-block; animation: spin 1s linear infinite;}
|
||||||
|
@keyframes spin {0% { transform: rotate(0deg);}
|
||||||
|
100% {transform: rotate(360deg);}
|
||||||
}
|
}
|
||||||
.table-section {
|
.pagination-fixed {flex-shrink: 0; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end;}
|
||||||
flex: 1;
|
.pagination-fixed :deep(.el-pager li.is-active) {border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff;}
|
||||||
overflow: hidden;
|
.empty-tip {text-align: center; color: #909399; padding: 16px 0;}
|
||||||
position: relative;
|
.import-section[draggable], .import-section.drag-active {border: 1px dashed #409EFF; border-radius: 6px;}
|
||||||
background: #fff;
|
.empty-container {text-align: center;}
|
||||||
border: 1px solid #ebeef5;
|
.empty-icon {font-size: 48px; margin-bottom: 12px; opacity: 0.6;}
|
||||||
border-radius: 4px;
|
.empty-image {max-width: 200px; width: 100%; height: auto; margin-bottom: 16px; opacity: 0.8;}
|
||||||
display: flex;
|
.empty-text {font-size: 14px; color: #909399;}
|
||||||
flex-direction: column;
|
.empty-abs {position: absolute; left: 0; right: 0; top: 48px; bottom: 0; display: flex; align-items: center; justify-content: center;}
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
.table-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: auto;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: #c0c4cc transparent;
|
|
||||||
}
|
|
||||||
.table-wrapper::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
||||||
.table-wrapper::-webkit-scrollbar-track { background: transparent; }
|
|
||||||
.table-wrapper::-webkit-scrollbar-thumb { background: #c0c4cc; border-radius: 3px; }
|
|
||||||
.table-wrapper:hover::-webkit-scrollbar-thumb { background: #a8abb2; }
|
|
||||||
.table { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; }
|
|
||||||
.table th { background: #f5f7fa; color: #909399; font-weight: 600; padding: 12px 8px; border-bottom: 2px solid #ebeef5; text-align: center; }
|
|
||||||
.table td { padding: 10px 8px; border-bottom: 1px solid #f0f0f0; vertical-align: middle; text-align: center; }
|
|
||||||
.table tbody tr:hover { background: #f9f9f9; }
|
|
||||||
.table th:nth-child(1), .table td:nth-child(1) { width: 33.33%; }
|
|
||||||
.table th:nth-child(2), .table td:nth-child(2) { width: 33.33%; }
|
|
||||||
.table th:nth-child(3), .table td:nth-child(3) { width: 33.33%; }
|
|
||||||
.asin-out { color: #f56c6c; font-weight: 600; }
|
|
||||||
.seller-info { display: flex; align-items: center; gap: 4px; justify-content: center; }
|
|
||||||
.seller { color: #303133; font-weight: 500; }
|
|
||||||
.shipper { color: #909399; font-size: 12px; }
|
|
||||||
.price { color: #e6a23c; font-weight: 600; }
|
|
||||||
.table-loading { position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266; }
|
|
||||||
.spinner { font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px; }
|
|
||||||
.inline-spinner { display: inline-block; animation: spin 1s linear infinite; }
|
|
||||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
||||||
.pagination-fixed { flex-shrink: 0; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end; }
|
|
||||||
.pagination-fixed :deep(.el-pager li.is-active) { border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff; }
|
|
||||||
.empty-tip { text-align: center; color: #909399; padding: 16px 0; }
|
|
||||||
.import-section[draggable], .import-section.drag-active { border: 1px dashed #409EFF; border-radius: 6px; }
|
|
||||||
.empty-container { text-align: center; }
|
|
||||||
.empty-icon { font-size: 48px; margin-bottom: 12px; opacity: 0.6; }
|
|
||||||
.empty-image { max-width: 200px; width: 100%; height: auto; margin-bottom: 16px; opacity: 0.8; }
|
|
||||||
.empty-text { font-size: 14px; color: #909399; }
|
|
||||||
.empty-abs { position: absolute; left: 0; right: 0; top: 48px; bottom: 0; display: flex; align-items: center; justify-content: center; }
|
|
||||||
|
|
||||||
/* 状态图标样式 */
|
/* 状态图标样式 */
|
||||||
.status-image {
|
.status-image {max-width: 160px; width: 100%; height: auto; margin-bottom: 20px; opacity: 0.9; user-select: none; -webkit-user-drag: none; pointer-events: none;}
|
||||||
max-width: 160px;
|
.status-loading {animation: pulse 2s ease-in-out infinite;}
|
||||||
width: 100%;
|
@keyframes pulse {0%, 100% { opacity: 0.6;}
|
||||||
height: auto;
|
50% {opacity: 0.9;}
|
||||||
margin-bottom: 20px;
|
|
||||||
opacity: 0.9;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
.status-loading {
|
.empty-title {font-size: 16px; font-weight: 600; color: #303133; margin-bottom: 8px;}
|
||||||
animation: pulse 2s ease-in-out infinite;
|
.empty-desc {font-size: 14px; color: #909399; line-height: 1.5;}
|
||||||
}
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { opacity: 0.6; }
|
|
||||||
50% { opacity: 0.9; }
|
|
||||||
}
|
|
||||||
.empty-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.empty-desc {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #909399;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 商标筛查相关样式 */
|
/* 商标筛查相关样式 */
|
||||||
.table-trademark th:nth-child(1), .table-trademark td:nth-child(1) { width: 15%; }
|
.table-trademark th:nth-child(1), .table-trademark td:nth-child(1) {width: 15%;}
|
||||||
.table-trademark th:nth-child(2), .table-trademark td:nth-child(2) { width: 12%; }
|
.table-trademark th:nth-child(2), .table-trademark td:nth-child(2) {width: 12%;}
|
||||||
.table-trademark th:nth-child(3), .table-trademark td:nth-child(3) { width: 18%; }
|
.table-trademark th:nth-child(3), .table-trademark td:nth-child(3) {width: 18%;}
|
||||||
.table-trademark th:nth-child(4), .table-trademark td:nth-child(4) { width: 25%; }
|
.table-trademark th:nth-child(4), .table-trademark td:nth-child(4) {width: 25%;}
|
||||||
.table-trademark th:nth-child(5), .table-trademark td:nth-child(5) { width: 15%; }
|
.table-trademark th:nth-child(5), .table-trademark td:nth-child(5) {width: 15%;}
|
||||||
.table-trademark th:nth-child(6), .table-trademark td:nth-child(6) { width: 15%; }
|
.table-trademark th:nth-child(6), .table-trademark td:nth-child(6) {width: 15%;}
|
||||||
|
.trademark-name {font-weight: 600; color: #303133;}
|
||||||
.trademark-name { font-weight: 600; color: #303133; }
|
.truncate-cell {max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
|
||||||
.truncate-cell { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.status-registered {color: #67c23a; font-weight: 500;}
|
||||||
|
.status-risk {color: #f56c6c; font-weight: 500;}
|
||||||
.status-registered { color: #67c23a; font-weight: 500; }
|
.status-available {color: #409EFF; font-weight: 500;}
|
||||||
.status-risk { color: #f56c6c; font-weight: 500; }
|
.similarity-high {color: #f56c6c; font-weight: 600;}
|
||||||
.status-available { color: #409EFF; font-weight: 500; }
|
.dz-el-icon {font-size: 18px; margin-bottom: 4px; color: #909399;}
|
||||||
|
|
||||||
.similarity-high { color: #f56c6c; font-weight: 600; }
|
|
||||||
|
|
||||||
.dz-el-icon { font-size: 18px; margin-bottom: 4px; color: #909399; }
|
|
||||||
|
|
||||||
/* 跟卖精灵内容样式 */
|
/* 跟卖精灵内容样式 */
|
||||||
.genmai-content {
|
.genmai-content {display: flex; flex-direction: column; align-items: center; justify-content: space-between; height: 100%;}
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 商标筛查空状态样式 */
|
/* 商标筛查空状态样式 */
|
||||||
.trademark-empty-wrapper {
|
.trademark-empty-wrapper {width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: space-between;}
|
||||||
width: 100%;
|
.trademark-empty-content {flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;}
|
||||||
height: 100%;
|
.trademark-info-boxes {display: flex; text-align: left; flex-direction: row; align-items: flex-start; padding: 8px 0; gap: 10px; width: 90%; max-width: 872px; margin: 16px auto; background: rgba(0, 0, 0, 0.02); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 8px;}
|
||||||
display: flex;
|
.genmai-image-wrapper {flex: 1; display: flex; align-items: center; justify-content: center; width: 100%;}
|
||||||
flex-direction: column;
|
.genmai-image {max-width: 100%; max-height: 370px; width: auto; height: auto; object-fit: contain; user-select: none; -webkit-user-drag: none; pointer-events: none;}
|
||||||
justify-content: space-between;
|
.genmai-info-boxes {margin-bottom: 16px; box-sizing: border-box; display: flex; text-align: left; flex-direction: row; align-items: flex-start; padding: 8px 0px; gap: 10px; width: 90%; max-width: 872px; height: 200rpx; background: rgba(0, 0, 0, 0.02); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 8px; flex: none; order: 0; flex-grow: 0;}
|
||||||
}
|
.info-box {flex: 1; border: none; background: transparent; display: flex; flex-direction: column; gap: 8px; padding: 0px 15px;}
|
||||||
.trademark-empty-content {
|
.info-box:not(:last-child) {border-right: 1px solid rgba(0, 0, 0, 0.06);}
|
||||||
flex: 1;
|
.info-title {font-size: 14px; font-weight: 600; color: #303133; line-height: 1.4;}
|
||||||
display: flex;
|
.info-text {font-size: 13px; color: #606266; line-height: 1.6;}
|
||||||
flex-direction: column;
|
.info-link {color: #1677FF; text-decoration: none; position: relative; z-index: 10;}
|
||||||
align-items: center;
|
.info-link:hover {text-decoration: underline;}
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.trademark-info-boxes {
|
|
||||||
display: flex;
|
|
||||||
text-align: left;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 8px 0;
|
|
||||||
gap: 10px;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 872px;
|
|
||||||
margin: 16px auto;
|
|
||||||
background: rgba(0, 0, 0, 0.02);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
.genmai-image-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.genmai-image {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 370px;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
object-fit: contain;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.genmai-info-boxes {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
text-align: left;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 8px 0px;
|
|
||||||
gap: 10px;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 872px;
|
|
||||||
height: 200rpx;
|
|
||||||
background: rgba(0, 0, 0, 0.02);
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
||||||
border-radius: 8px;
|
|
||||||
flex: none;
|
|
||||||
order: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
.info-box {
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 0px 15px;
|
|
||||||
}
|
|
||||||
.info-box:not(:last-child) {
|
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
.info-title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.info-text {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #606266;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
.info-link {
|
|
||||||
color: #1677FF;
|
|
||||||
text-decoration: none;
|
|
||||||
position: relative;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
.info-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 商标筛查进度样式 */
|
/* 商标筛查进度样式 */
|
||||||
.trademark-progress-container {
|
.trademark-progress-container {padding: 24px; width: 100%; height: 100%; box-sizing: border-box; overflow-y: auto; display: flex; flex-direction: column; align-items: center;}
|
||||||
padding: 24px;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow-y: auto;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 状态横幅 */
|
/* 状态横幅 */
|
||||||
.status-banner {
|
.status-banner {box-sizing: border-box; display: flex; flex-direction: row; align-items: center; padding: 16px; gap: 16px; width: 70%; max-width: 900px; min-height: 90px; background: #FFFFFF; border: 1px solid rgba(0, 0, 0, 0.1); border-radius: 16px; flex: none; margin-bottom: 16px;}
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
gap: 16px;
|
|
||||||
width: 70%;
|
|
||||||
max-width: 900px;
|
|
||||||
min-height: 90px;
|
|
||||||
background: #FFFFFF;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
border-radius: 16px;
|
|
||||||
flex: none;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
.status-banner.progress-banner,
|
.status-banner.progress-banner,
|
||||||
.status-banner.done-banner,
|
.status-banner.done-banner,
|
||||||
.status-banner.error-banner,
|
.status-banner.error-banner,
|
||||||
.status-banner.cancel-banner {
|
.status-banner.cancel-banner {box-shadow: 0px 8px 20px rgba(22, 119, 255, 0.2);}
|
||||||
box-shadow: 0px 8px 20px rgba(22, 119, 255, 0.2);
|
.banner-icon {flex-shrink: 0; width: 64px; height: 64px; display: flex; align-items: center; justify-content: center; background: transparent; border-radius: 50%;}
|
||||||
}
|
.banner-icon svg {width: 48px; height: 48px;}
|
||||||
.banner-icon {
|
.done-banner .banner-icon {background: transparent;}
|
||||||
flex-shrink: 0;
|
.banner-content {flex: 1; min-width: 0; text-align: left;}
|
||||||
width: 64px;
|
.banner-title {font-size: 16px; font-weight: 600; color: #303133; margin-bottom: 4px;}
|
||||||
height: 64px;
|
.banner-desc {font-size: 13px; color: #606266; line-height: 1.5;}
|
||||||
display: flex;
|
.banner-actions {flex-shrink: 0; display: flex; gap: 12px;}
|
||||||
align-items: center;
|
.banner-actions .el-button {min-width: 100px;}
|
||||||
justify-content: center;
|
|
||||||
background: transparent;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.banner-icon svg {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.done-banner .banner-icon {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.banner-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.banner-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.banner-desc {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #606266;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
.banner-actions {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.banner-actions .el-button {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 统一任务卡片容器 */
|
/* 统一任务卡片容器 */
|
||||||
.unified-task-card {
|
.unified-task-card {width: 70%; max-width: 900px; overflow: hidden;}
|
||||||
width: 70%;
|
|
||||||
max-width: 900px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 任务区块 */
|
/* 任务区块 */
|
||||||
.task-section {
|
.task-section {display: flex; flex-direction: row; padding: 20px; gap: 20px;}
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: 20px;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 左侧状态列 */
|
/* 左侧状态列 */
|
||||||
.status-column {
|
.status-column {display: flex; flex-direction: column; align-items: center; gap: 0; flex-shrink: 0;}
|
||||||
display: flex;
|
.status-item {display: flex; align-items: flex-start; justify-content: center; width: 24px; height: 24px; flex-shrink: 0;}
|
||||||
flex-direction: column;
|
.status-indicator-icon {width: 24px; height: 24px; object-fit: contain;}
|
||||||
align-items: center;
|
.status-indicator-icon.spinning {animation: spin 1.5s linear infinite;}
|
||||||
gap: 0;
|
@keyframes spin {from { transform: rotate(0deg);}
|
||||||
flex-shrink: 0;
|
to {transform: rotate(360deg);}
|
||||||
}
|
}
|
||||||
.status-item {
|
.status-connector {width: 1px; height: 114px; background: #e5e7eb; margin: 8px 0;}
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.status-indicator-icon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
.status-indicator-icon.spinning {
|
|
||||||
animation: spin 1.5s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.status-connector {
|
|
||||||
width: 1px;
|
|
||||||
height: 114px;
|
|
||||||
background: #e5e7eb;
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 任务内容列 */
|
/* 任务内容列 */
|
||||||
.tasks-content {
|
.tasks-content {flex: 1; display: flex; flex-direction: column; gap: 16px; min-width: 0;}
|
||||||
flex: 1;
|
.task-item {width: 100%; padding: 0; padding-bottom: 16px; border-bottom: 1px solid rgba(0, 0, 0, 0.06); min-height: 76px;}
|
||||||
display: flex;
|
.task-item:first-child {padding-top: 0;}
|
||||||
flex-direction: column;
|
.task-item:last-child {padding-bottom: 0; border-bottom: none;}
|
||||||
gap: 16px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.task-item {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
||||||
min-height: 76px;
|
|
||||||
}
|
|
||||||
.task-item:first-child {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
.task-item:last-child {
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 竖线分隔(换行后不需要) */
|
/* 竖线分隔(换行后不需要) */
|
||||||
.vertical-divider {
|
.vertical-divider {display: none;}
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 任务标题行 */
|
/* 任务标题行 */
|
||||||
.task-title-row {
|
.task-title-row {display: flex; align-items: flex-start; margin-bottom: 12px;}
|
||||||
display: flex;
|
.task-info {flex: 1; min-width: 0; text-align: left;}
|
||||||
align-items: flex-start;
|
.task-name {font-size: 13px; font-weight: 600; color: #303133; margin-bottom: 2px; line-height: 1.4;}
|
||||||
margin-bottom: 12px;
|
.task-name-disabled {color: #909399;}
|
||||||
}
|
.task-desc {font-size: 11px; color: #909399; line-height: 1.4;}
|
||||||
.task-info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.task-name {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
.task-name-disabled {
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
.task-desc {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #909399;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 进度条 */
|
/* 进度条 */
|
||||||
.task-progress-wrapper {
|
.task-progress-wrapper {display: flex; align-items: center; gap: 10px; margin-bottom: 10px;}
|
||||||
display: flex;
|
.task-progress-bar {flex: 1; height: 4px; background: rgba(22, 119, 255, 0.1); border-radius: 999px; overflow: hidden;}
|
||||||
align-items: center;
|
.task-progress-fill {height: 100%; background: linear-gradient(90deg, #1677FF 0%, #4096FF 100%); border-radius: 999px; transition: width 0.3s ease;}
|
||||||
gap: 10px;
|
.task-percentage {flex-shrink: 0; font-size: 13px; font-weight: 600; color: #1677FF; min-width: 40px; text-align: right;}
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.task-progress-bar {
|
|
||||||
flex: 1;
|
|
||||||
height: 4px;
|
|
||||||
background: rgba(22, 119, 255, 0.1);
|
|
||||||
border-radius: 999px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.task-progress-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, #1677FF 0%, #4096FF 100%);
|
|
||||||
border-radius: 999px;
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
.task-percentage {
|
|
||||||
flex-shrink: 0;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1677FF;
|
|
||||||
min-width: 40px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 统计数据 */
|
/* 统计数据 */
|
||||||
.task-stats {
|
.task-stats {display: flex; align-items: center; justify-content: space-between; gap: 8px; text-align: left;}
|
||||||
display: flex;
|
.task-stat {flex: 1; display: flex; flex-direction: column; gap: 2px;}
|
||||||
align-items: center;
|
.task-stat.highlight {border-left: 1px solid rgba(0, 0, 0, 0.06); border-right: 1px solid rgba(0, 0, 0, 0.06); padding: 0 8px;}
|
||||||
justify-content: space-between;
|
.stat-label {font-size: 10px; color: #909399;}
|
||||||
gap: 8px;
|
.stat-value {font-size: 16px; font-weight: 600; color: #303133;}
|
||||||
text-align: left;
|
.task-stat.highlight .stat-label {color: #67c23a;}
|
||||||
}
|
.task-stat.highlight .stat-value {color: #67c23a;}
|
||||||
.task-stat {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
.task-stat.highlight {
|
|
||||||
border-left: 1px solid rgba(0, 0, 0, 0.06);
|
|
||||||
border-right: 1px solid rgba(0, 0, 0, 0.06);
|
|
||||||
padding: 0 8px;
|
|
||||||
}
|
|
||||||
.stat-label {
|
|
||||||
font-size: 10px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
.stat-value {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
.task-stat.highlight .stat-label {
|
|
||||||
color: #67c23a;
|
|
||||||
}
|
|
||||||
.task-stat.highlight .stat-value {
|
|
||||||
color: #67c23a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式设计 */
|
/* 响应式设计 */
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {.steps-sidebar.narrow {
|
||||||
.steps-sidebar.narrow {
|
width: 220px;}
|
||||||
width: 220px;
|
/* wide保持280px不变 */
|
||||||
}
|
.tab-item {padding: 0px 15px;}
|
||||||
/* wide保持280px不变 */
|
|
||||||
.tab-item {
|
|
||||||
padding: 0px 15px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {.steps-sidebar.narrow {
|
||||||
.steps-sidebar.narrow {
|
width: 200px;}
|
||||||
width: 200px;
|
.steps-sidebar.wide {width: 260px;}
|
||||||
}
|
.genmai-info-boxes {width: 95%;}
|
||||||
.steps-sidebar.wide {
|
.status-banner,
|
||||||
width: 260px;
|
.unified-task-card {width: 85%;}
|
||||||
}
|
|
||||||
.genmai-info-boxes {
|
|
||||||
width: 95%;
|
|
||||||
}
|
|
||||||
.status-banner,
|
|
||||||
.unified-task-card {
|
|
||||||
width: 85%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {.body-layout {
|
||||||
.body-layout {
|
flex-direction: column;}
|
||||||
flex-direction: column;
|
.steps-sidebar.narrow,
|
||||||
}
|
.steps-sidebar.wide {width: 100%; max-height: 300px;}
|
||||||
.steps-sidebar.narrow,
|
.tab-item {font-size: 12px; padding: 0px 6px; gap: 6px;}
|
||||||
.steps-sidebar.wide {
|
.tab-icon {width: 16px; height: 16px;}
|
||||||
width: 100%;
|
.status-banner,
|
||||||
max-height: 300px;
|
.unified-task-card {width: 95%;}
|
||||||
}
|
.status-banner {min-height: auto; padding: 14px;}
|
||||||
.tab-item {
|
.banner-icon {width: 40px; height: 40px;}
|
||||||
font-size: 12px;
|
.banner-icon svg {width: 32px; height: 32px;}
|
||||||
padding: 0px 6px;
|
.banner-title {font-size: 14px;}
|
||||||
gap: 6px;
|
.banner-desc {font-size: 12px;}
|
||||||
}
|
.banner-actions {flex-direction: column; width: 100%;}
|
||||||
.tab-icon {
|
.banner-actions .el-button {width: 100%;}
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
.status-banner,
|
|
||||||
.unified-task-card {
|
|
||||||
width: 95%;
|
|
||||||
}
|
|
||||||
.status-banner {
|
|
||||||
min-height: auto;
|
|
||||||
padding: 14px;
|
|
||||||
}
|
|
||||||
.banner-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
.banner-icon svg {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
}
|
|
||||||
.banner-title {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.banner-desc {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.banner-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.banner-actions .el-button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -341,46 +341,32 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.asin-panel {
|
.asin-panel {flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden;}
|
||||||
flex: 1;
|
.steps-flow {position: relative; flex: 1; min-height: 0; overflow-y: auto; scrollbar-width: none;}
|
||||||
min-height: 0;
|
.asin-panel .steps-flow::-webkit-scrollbar {display: none;}
|
||||||
display: flex;
|
.steps-flow:before {content: ''; position: absolute; left: 13px; top: 26px; bottom: 0; width: 2px; background: rgba(229, 231, 235, 0.6);}
|
||||||
flex-direction: column;
|
.flow-item {position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 12px; padding: 10px 0;}
|
||||||
overflow: hidden;
|
.flow-item .step-index {position: static; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 14px; font-weight: 600; margin-top: 2px;}
|
||||||
}
|
.step-card {border: none; border-radius: 0; padding: 0; background: transparent; min-width: 0;}
|
||||||
.steps-flow {
|
.step-header {display: flex; align-items: center; gap: 8px; margin-bottom: 8px;}
|
||||||
position: relative;
|
.title {font-size: 14px; font-weight: 600; color: #303133; text-align: left;}
|
||||||
flex: 1;
|
.desc {font-size: 12px; color: #909399; margin-bottom: 10px; text-align: left; line-height: 1.5;}
|
||||||
min-height: 0;
|
.links {display: flex; align-items: center; gap: 2px; margin-bottom: 8px;}
|
||||||
overflow-y: auto;
|
.link {color: #409EFF; cursor: pointer; font-size: 12px;}
|
||||||
scrollbar-width: none;
|
.sep {color: #dcdfe6;}
|
||||||
}
|
.dropzone {border: 1px dashed #c0c4cc; border-radius: 6px; padding: 16px; text-align: center; cursor: pointer; background: #fafafa;}
|
||||||
.asin-panel .steps-flow::-webkit-scrollbar {
|
.dropzone:hover {background: #f6fbff; border-color: #409EFF;}
|
||||||
display: none;
|
.dz-el-icon {font-size: 18px; margin-bottom: 4px; color: #909399;}
|
||||||
}
|
.dz-text {color: #303133; font-size: 13px;}
|
||||||
.steps-flow:before { content: ''; position: absolute; left: 13px; top: 26px; bottom: 0; width: 2px; background: rgba(229, 231, 235, 0.6); }
|
.dz-sub {color: #909399; font-size: 12px;}
|
||||||
.flow-item { position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 12px; padding: 10px 0; }
|
.file-chip {display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; color: #606266; margin-top: 6px; width: 100%; box-sizing: border-box;}
|
||||||
.flow-item .step-index { position: static; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 14px; font-weight: 600; margin-top: 2px; }
|
.file-chip .dot {width: 6px; height: 6px; background: #409EFF; border-radius: 50%; flex-shrink: 0;}
|
||||||
.step-card { border: none; border-radius: 0; padding: 0; background: transparent; min-width: 0; }
|
.file-chip .name {flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
|
||||||
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
.file-chip .delete-btn {cursor: pointer; opacity: 0.6; flex-shrink: 0;}
|
||||||
.title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
.file-chip .delete-btn:hover {opacity: 1;}
|
||||||
.desc { font-size: 12px; color: #909399; margin-bottom: 10px; text-align: left; line-height: 1.5; }
|
.action-buttons.column {display: flex; flex-direction: column; gap: 8px;}
|
||||||
.links { display: flex; align-items: center; gap: 2px; margin-bottom: 8px; }
|
.btn-blue {background: #1677FF; border-color: #1677FF; color: #fff;}
|
||||||
.link { color: #409EFF; cursor: pointer; font-size: 12px; }
|
.btn-blue:disabled {background: #a6c8ff; border-color: #a6c8ff; color: #fff;}
|
||||||
.sep { color: #dcdfe6; }
|
.w100 {width: 100%;}
|
||||||
.dropzone { border: 1px dashed #c0c4cc; border-radius: 6px; padding: 16px; text-align: center; cursor: pointer; background: #fafafa; }
|
|
||||||
.dropzone:hover { background: #f6fbff; border-color: #409EFF; }
|
|
||||||
.dz-el-icon { font-size: 18px; margin-bottom: 4px; color: #909399; }
|
|
||||||
.dz-text { color: #303133; font-size: 13px; }
|
|
||||||
.dz-sub { color: #909399; font-size: 12px; }
|
|
||||||
.file-chip { display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; color: #606266; margin-top: 6px; width: 100%; box-sizing: border-box; }
|
|
||||||
.file-chip .dot { width: 6px; height: 6px; background: #409EFF; border-radius: 50%; flex-shrink: 0; }
|
|
||||||
.file-chip .name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
||||||
.file-chip .delete-btn { cursor: pointer; opacity: 0.6; flex-shrink: 0; }
|
|
||||||
.file-chip .delete-btn:hover { opacity: 1; }
|
|
||||||
.action-buttons.column { display: flex; flex-direction: column; gap: 8px; }
|
|
||||||
.btn-blue { background: #1677FF; border-color: #1677FF; color: #fff; }
|
|
||||||
.btn-blue:disabled { background: #a6c8ff; border-color: #a6c8ff; color: #fff; }
|
|
||||||
.w100 { width: 100%; }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -119,55 +119,41 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.genmai-panel {
|
.genmai-panel {flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden;}
|
||||||
flex: 1;
|
.steps-flow {position: relative; flex: 1; min-height: 0; overflow-y: auto; scrollbar-width: none;}
|
||||||
min-height: 0;
|
.genmai-panel .steps-flow::-webkit-scrollbar {display: none;}
|
||||||
display: flex;
|
.steps-flow:before {content: ''; position: absolute; left: 13px; top: 26px; bottom: 0; width: 2px; background: rgba(229, 231, 235, 0.6);}
|
||||||
flex-direction: column;
|
.flow-item {position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 12px; padding: 10px 0;}
|
||||||
overflow: hidden;
|
.flow-item .step-index {position: static; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 14px; font-weight: 600; margin-top: 2px;}
|
||||||
|
.step-card {border: none; border-radius: 0; padding: 0; background: transparent;}
|
||||||
|
.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;}
|
||||||
|
.account-list {height: auto;}
|
||||||
|
.scroll-limit {max-height: 140px;}
|
||||||
|
.placeholder-box {display: flex; align-items: center; justify-content: center; flex-direction: column; height: 100px; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 8px;}
|
||||||
|
.placeholder-img {width: 80px; opacity: 0.9;}
|
||||||
|
.placeholder-tip {margin-top: 6px; font-size: 12px; color: #a8abb2;}
|
||||||
|
.avatar {width: 18px; height: 18px; border-radius: 50%;}
|
||||||
|
.acct-row {display: grid; grid-template-columns: 6px 18px 1fr auto; align-items: center; gap: 6px; width: 100%;}
|
||||||
|
.acct-text {overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 12px;}
|
||||||
|
.status-dot {width: 6px; height: 6px; border-radius: 50%; display: inline-block;}
|
||||||
|
.status-dot.on {background: #22c55e;}
|
||||||
|
.status-dot.off {background: #f87171;}
|
||||||
|
.acct-item {padding: 6px 8px; border-radius: 6px; cursor: pointer; margin-bottom: 4px;}
|
||||||
|
.acct-item.selected {background: #eef5ff; box-shadow: inset 0 0 0 1px #d6e4ff;}
|
||||||
|
.acct-check {display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; background: transparent; color: #111; font-size: 12px;}
|
||||||
|
.account-list::-webkit-scrollbar {width: 0; height: 0;}
|
||||||
|
.step-actions {margin-top: 8px; display: flex; gap: 8px;}
|
||||||
|
.btn-row {display: grid; grid-template-columns: 1fr 1fr; gap: 8px;}
|
||||||
|
.w50 {width: 100%;}
|
||||||
|
.action-buttons.column {display: flex; flex-direction: column; gap: 8px;}
|
||||||
|
.btn-blue {background: #1677FF; border-color: #1677FF; color: #fff;}
|
||||||
|
.btn-blue:disabled {background: #a6c8ff; border-color: #a6c8ff; color: #fff;}
|
||||||
|
.w100 {width: 100%;}
|
||||||
|
.inline-spinner {display: inline-block; animation: spin 1s linear infinite;}
|
||||||
|
@keyframes spin {0% { transform: rotate(0deg);}
|
||||||
|
100% {transform: rotate(360deg);}
|
||||||
}
|
}
|
||||||
.steps-flow {
|
|
||||||
position: relative;
|
|
||||||
flex: 1;
|
|
||||||
min-height: 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
.genmai-panel .steps-flow::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.steps-flow:before { content: ''; position: absolute; left: 13px; top: 26px; bottom: 0; width: 2px; background: rgba(229, 231, 235, 0.6); }
|
|
||||||
.flow-item { position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 12px; padding: 10px 0; }
|
|
||||||
.flow-item .step-index { position: static; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 14px; font-weight: 600; margin-top: 2px; }
|
|
||||||
.step-card { border: none; border-radius: 0; padding: 0; background: transparent; }
|
|
||||||
.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; }
|
|
||||||
|
|
||||||
.account-list { height: auto; }
|
|
||||||
.scroll-limit { max-height: 140px; }
|
|
||||||
.placeholder-box { display: flex; align-items: center; justify-content: center; flex-direction: column; height: 100px; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 8px; }
|
|
||||||
.placeholder-img { width: 80px; opacity: 0.9; }
|
|
||||||
.placeholder-tip { margin-top: 6px; font-size: 12px; color: #a8abb2; }
|
|
||||||
.avatar { width: 18px; height: 18px; border-radius: 50%; }
|
|
||||||
.acct-row { display: grid; grid-template-columns: 6px 18px 1fr auto; align-items: center; gap: 6px; width: 100%; }
|
|
||||||
.acct-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 12px; }
|
|
||||||
.status-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
|
|
||||||
.status-dot.on { background: #22c55e; }
|
|
||||||
.status-dot.off { background: #f87171; }
|
|
||||||
.acct-item { padding: 6px 8px; border-radius: 6px; cursor: pointer; margin-bottom: 4px; }
|
|
||||||
.acct-item.selected { background: #eef5ff; box-shadow: inset 0 0 0 1px #d6e4ff; }
|
|
||||||
.acct-check { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; background: transparent; color: #111; font-size: 12px; }
|
|
||||||
.account-list::-webkit-scrollbar { width: 0; height: 0; }
|
|
||||||
|
|
||||||
.step-actions { margin-top: 8px; display: flex; gap: 8px; }
|
|
||||||
.btn-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
|
||||||
.w50 { width: 100%; }
|
|
||||||
.action-buttons.column { display: flex; flex-direction: column; gap: 8px; }
|
|
||||||
.btn-blue { background: #1677FF; border-color: #1677FF; color: #fff; }
|
|
||||||
.btn-blue:disabled { background: #a6c8ff; border-color: #a6c8ff; color: #fff; }
|
|
||||||
.w100 { width: 100%; }
|
|
||||||
.inline-spinner { display: inline-block; animation: spin 1s linear infinite; }
|
|
||||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1079,491 +1079,83 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.trademark-panel {
|
.trademark-panel {flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden;}
|
||||||
flex: 1;
|
.steps-flow {position: relative; flex: 1; min-height: 0; overflow-y: auto; scrollbar-width: none;}
|
||||||
min-height: 0;
|
.steps-flow::-webkit-scrollbar {display: none;}
|
||||||
display: flex;
|
.steps-flow:before {content: ''; position: absolute; left: 13px; top: 26px; bottom: 0; width: 2px; background: rgba(229, 231, 235, 0.6);}
|
||||||
flex-direction: column;
|
.flow-item {position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 12px; padding: 10px 0;}
|
||||||
overflow: hidden;
|
.flow-item .step-index {position: static; width: 28px; height: 28px; line-height: 28px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 14px; font-weight: 600; margin-top: 2px;}
|
||||||
}
|
.step-card {border: none; border-radius: 0; padding: 0; background: transparent; min-width: 0;}
|
||||||
|
.step-header {display: flex; align-items: center; gap: 8px; margin-bottom: 8px;}
|
||||||
.steps-flow {
|
.title {font-size: 14px; font-weight: 600; color: #303133; text-align: left;}
|
||||||
position: relative;
|
.desc {font-size: 12px; color: #909399; margin-bottom: 10px; text-align: left; line-height: 1.5;}
|
||||||
flex: 1;
|
.example-link {color: #1677FF; cursor: pointer; text-decoration: underline; margin-left: 4px;}
|
||||||
min-height: 0;
|
.example-link:hover {color: #0958d9;}
|
||||||
overflow-y: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.steps-flow::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.steps-flow:before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 13px;
|
|
||||||
top: 26px;
|
|
||||||
bottom: 0;
|
|
||||||
width: 2px;
|
|
||||||
background: rgba(229, 231, 235, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flow-item {
|
|
||||||
position: relative;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 28px 1fr;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flow-item .step-index {
|
|
||||||
position: static;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
line-height: 28px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #1677FF;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-card {
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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) {
|
:deep(.excel-dialog .el-dialog__header) {text-align: left; font-weight: bold; margin-bottom: 0; padding-bottom: 0;}
|
||||||
text-align: left;
|
.links {display: flex; align-items: center; gap: 6px; margin-bottom: 8px;}
|
||||||
font-weight: bold;
|
.link {color: #909399; cursor: pointer; font-size: 12px;}
|
||||||
margin-bottom: 0;
|
.file-chip {display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; color: #606266; margin-top: 6px; width: 100%; box-sizing: border-box;}
|
||||||
padding-bottom: 0;
|
.file-chip .dot {width: 6px; height: 6px; background: #409EFF; border-radius: 50%; flex-shrink: 0;}
|
||||||
}
|
.file-chip .name {flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
|
||||||
|
.file-chip .delete-btn {cursor: pointer; opacity: 0.6; flex-shrink: 0;}
|
||||||
.links {
|
.file-chip .delete-btn:hover {opacity: 1;}
|
||||||
display: flex;
|
.dropzone {border: 1px dashed #c0c4cc; border-radius: 8px; padding: 20px 16px; text-align: center; cursor: pointer; background: #fafafa; transition: all 0.2s ease;}
|
||||||
align-items: center;
|
.dropzone:hover {background: #f6fbff; border-color: #409EFF;}
|
||||||
gap: 6px;
|
.dropzone.uploading {cursor: not-allowed; opacity: 0.7;}
|
||||||
margin-bottom: 8px;
|
.dropzone.uploading:hover {background: #fafafa; border-color: #c0c4cc;}
|
||||||
}
|
.dropzone.drag-active {background: #e6f4ff; border-color: #1677FF;}
|
||||||
|
.dz-icon {font-size: 20px; margin-bottom: 6px; color: #909399;}
|
||||||
.link {
|
.dz-text {color: #303133; font-size: 13px; margin-bottom: 2px;}
|
||||||
color: #909399;
|
.dz-sub {color: #909399; font-size: 12px;}
|
||||||
cursor: pointer;
|
.spinner {animation: spin 1s linear infinite;}
|
||||||
font-size: 12px;
|
@keyframes spin {from {
|
||||||
}
|
transform: rotate(0deg);}
|
||||||
|
to {transform: rotate(360deg);}
|
||||||
.file-chip {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
padding: 6px 8px;
|
|
||||||
background: #f5f7fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #606266;
|
|
||||||
margin-top: 6px;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-chip .dot {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
background: #409EFF;
|
|
||||||
border-radius: 50%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-chip .name {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-chip .delete-btn {
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0.6;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-chip .delete-btn:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone {
|
|
||||||
border: 1px dashed #c0c4cc;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px 16px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
background: #fafafa;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone:hover {
|
|
||||||
background: #f6fbff;
|
|
||||||
border-color: #409EFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone.uploading {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone.uploading:hover {
|
|
||||||
background: #fafafa;
|
|
||||||
border-color: #c0c4cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropzone.drag-active {
|
|
||||||
background: #e6f4ff;
|
|
||||||
border-color: #1677FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dz-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dz-text {
|
|
||||||
color: #303133;
|
|
||||||
font-size: 13px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dz-sub {
|
|
||||||
color: #909399;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-options {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 12px;
|
|
||||||
gap: 10px;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 46px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
background: #ffffff;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-card:hover {
|
|
||||||
border-color: #1677FF;
|
|
||||||
background: #f7fbff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-card.active {
|
|
||||||
border-color: #1677FF;
|
|
||||||
background: #f0f9ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-card.query-disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-check {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1.5px solid #d9d9d9;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background: transparent;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-card.active .query-check {
|
|
||||||
border-color: #1677FF;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.check-icon {
|
|
||||||
color: #1677FF;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-content {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
align-items: flex-start;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-title {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
line-height: 1.3;
|
|
||||||
text-align: left;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-title-disabled {
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.query-desc {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #909399;
|
|
||||||
line-height: 1.3;
|
|
||||||
text-align: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-actions {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w50 {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-action {
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-indicator {
|
|
||||||
font-size: 15px;
|
|
||||||
color: #303133;
|
|
||||||
font-weight: 600;
|
|
||||||
min-width: 36px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.start-btn {
|
|
||||||
flex: 1;
|
|
||||||
height: 32px;
|
|
||||||
padding: 0px 16px;
|
|
||||||
background: #1677FF;
|
|
||||||
border-color: #1677FF;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.start-btn:disabled {
|
|
||||||
background: #d9d9d9;
|
|
||||||
border-color: #d9d9d9;
|
|
||||||
color: #ffffff;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop-btn {
|
|
||||||
background: #f56c6c;
|
|
||||||
border-color: #f56c6c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop-btn:hover {
|
|
||||||
background: #f78989;
|
|
||||||
border-color: #f78989;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-blue {
|
|
||||||
background: #1677FF;
|
|
||||||
border-color: #1677FF;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-blue:disabled {
|
|
||||||
background: #a6c8ff;
|
|
||||||
border-color: #a6c8ff;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-list {
|
|
||||||
height: auto;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #ebeef5;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-limit {
|
|
||||||
max-height: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acct-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 6px 18px 1fr auto;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acct-text {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
min-width: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-dot {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-dot.on {
|
|
||||||
background: #22c55e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-dot.off {
|
|
||||||
background: #f87171;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acct-item {
|
|
||||||
padding: 6px 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acct-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acct-item.selected {
|
|
||||||
background: #eef5ff;
|
|
||||||
box-shadow: inset 0 0 0 1px #d6e4ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acct-item.disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.acct-check {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: transparent;
|
|
||||||
color: #111;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-list :deep(.el-scrollbar__view) {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account-list::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.query-options {display: flex; flex-direction: column; gap: 8px;}
|
||||||
|
.query-card {display: flex; flex-direction: row; align-items: center; padding: 8px 12px; gap: 10px; width: 100%; min-height: 46px; border-radius: 6px; border: 1px solid #e5e7eb; background: #ffffff; cursor: pointer; transition: all 0.2s ease; box-sizing: border-box;}
|
||||||
|
.query-card:hover {border-color: #1677FF; background: #f7fbff;}
|
||||||
|
.query-card.active {border-color: #1677FF; background: #f0f9ff;}
|
||||||
|
.query-card.query-disabled {cursor: not-allowed; pointer-events: none;}
|
||||||
|
.query-check {width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid #d9d9d9; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: transparent; transition: all 0.2s ease;}
|
||||||
|
.query-card.active .query-check {border-color: #1677FF; background: transparent;}
|
||||||
|
.check-icon {color: #1677FF; font-size: 10px; font-weight: bold; line-height: 1;}
|
||||||
|
.query-content {flex: 1; display: flex; flex-direction: column; gap: 2px; align-items: flex-start; min-width: 0;}
|
||||||
|
.query-title {font-size: 13px; font-weight: 600; color: #303133; line-height: 1.3; text-align: left; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 100%;}
|
||||||
|
.query-title-disabled {color: #909399;}
|
||||||
|
.query-desc {font-size: 11px; color: #909399; line-height: 1.3; text-align: left; width: 100%;}
|
||||||
|
.step-actions {margin-top: 8px;}
|
||||||
|
.btn-row {display: grid; grid-template-columns: 1fr 1fr; gap: 8px;}
|
||||||
|
.w50 {width: 100%;}
|
||||||
|
.bottom-action {flex-shrink: 0;}
|
||||||
|
.action-header {display: flex; align-items: center; gap: 10px;}
|
||||||
|
.step-indicator {font-size: 15px; color: #303133; font-weight: 600; min-width: 36px; text-align: left;}
|
||||||
|
.start-btn {flex: 1; height: 32px; padding: 0px 16px; background: #1677FF; border-color: #1677FF; font-size: 14px; font-weight: 500; border-radius: 6px;}
|
||||||
|
.start-btn:disabled {background: #d9d9d9; border-color: #d9d9d9; color: #ffffff; cursor: not-allowed;}
|
||||||
|
.stop-btn {background: #f56c6c; border-color: #f56c6c;}
|
||||||
|
.stop-btn:hover {background: #f78989; border-color: #f78989;}
|
||||||
|
.btn-blue {background: #1677FF; border-color: #1677FF; color: #fff;}
|
||||||
|
.btn-blue:disabled {background: #a6c8ff; border-color: #a6c8ff; color: #fff;}
|
||||||
|
.account-list {height: auto; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 8px; padding: 4px;}
|
||||||
|
.scroll-limit {max-height: 140px;}
|
||||||
|
.avatar {width: 18px; height: 18px; border-radius: 50%;}
|
||||||
|
.acct-row {display: grid; grid-template-columns: 6px 18px 1fr auto; align-items: center; gap: 6px; width: 100%;}
|
||||||
|
.acct-text {overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 12px; color: #303133;}
|
||||||
|
.status-dot {width: 6px; height: 6px; border-radius: 50%; display: inline-block;}
|
||||||
|
.status-dot.on {background: #22c55e;}
|
||||||
|
.status-dot.off {background: #f87171;}
|
||||||
|
.acct-item {padding: 6px 8px; border-radius: 6px; margin-bottom: 4px;}
|
||||||
|
.acct-item:last-child {margin-bottom: 0;}
|
||||||
|
.acct-item.selected {background: #eef5ff; box-shadow: inset 0 0 0 1px #d6e4ff;}
|
||||||
|
.acct-item.disabled {cursor: not-allowed; opacity: 0.6;}
|
||||||
|
.acct-check {display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: 50%; background: transparent; color: #111; font-size: 12px;}
|
||||||
|
.account-list :deep(.el-scrollbar__view) {padding: 0;}
|
||||||
|
.account-list::-webkit-scrollbar {width: 0; height: 0;}
|
||||||
.trademark-panel :deep(.el-select),
|
.trademark-panel :deep(.el-select),
|
||||||
.trademark-panel :deep(.el-input__wrapper) {
|
.trademark-panel :deep(.el-input__wrapper) {width: 100%;}
|
||||||
width: 100%;
|
.trademark-panel :deep(.el-select .el-input__wrapper) {border-radius: 6px;}
|
||||||
}
|
.trademark-panel :deep(.el-button) {border-radius: 6px;}
|
||||||
|
|
||||||
.trademark-panel :deep(.el-select .el-input__wrapper) {
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trademark-panel :deep(.el-button) {
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -166,36 +166,10 @@ async function saveBrandLogoInBackground(username: string) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.auth-logo {
|
.auth-logo {width: 160px; height: auto;}
|
||||||
width: 160px;
|
.auth-dialog {--el-color-primary: #1677FF;}
|
||||||
height: auto;
|
.auth-dialog :deep(.el-button--primary) {background-color: #1677FF; border-color: #1677FF;}
|
||||||
}
|
.auth-title-wrap {margin-bottom: 12px;}
|
||||||
|
.auth-title {margin: 0; font-size: 18px; font-weight: 700; color: #1f1f1f; text-align: left;}
|
||||||
.auth-dialog {
|
.auth-subtitle {margin: 6px 0 0; font-size: 12px; color: #8c8c8c; text-align: left;}
|
||||||
--el-color-primary: #1677FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-dialog :deep(.el-button--primary) {
|
|
||||||
background-color: #1677FF;
|
|
||||||
border-color: #1677FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-title-wrap {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f1f1f;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-subtitle {
|
|
||||||
margin: 6px 0 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #8c8c8c;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -185,36 +185,10 @@ function backToLogin() {
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.auth-logo {
|
.auth-logo {width: 160px; height: auto;}
|
||||||
width: 160px;
|
.auth-dialog {--el-color-primary: #1677FF;}
|
||||||
height: auto;
|
.auth-dialog :deep(.el-button--primary) {background-color: #1677FF; border-color: #1677FF;}
|
||||||
}
|
.auth-title-wrap {margin-bottom: 12px;}
|
||||||
|
.auth-title {margin: 0; font-size: 18px; font-weight: 700; color: #1f1f1f; text-align: left;}
|
||||||
.auth-dialog {
|
.auth-subtitle {margin: 6px 0 0; font-size: 12px; color: #8c8c8c; text-align: left;}
|
||||||
--el-color-primary: #1677FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-dialog :deep(.el-button--primary) {
|
|
||||||
background-color: #1677FF;
|
|
||||||
border-color: #1677FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-title-wrap {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f1f1f;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.auth-subtitle {
|
|
||||||
margin: 6px 0 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #8c8c8c;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -174,46 +174,45 @@ export default defineComponent({ name: 'AccountManager' })
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.acc-manager :deep(.el-dialog__header) { text-align:center; }
|
.acc-manager :deep(.el-dialog__header) {text-align:center;}
|
||||||
.layout { display:grid; grid-template-columns: 160px 1fr; gap: 12px; min-height: 340px; }
|
.layout {display:grid; grid-template-columns: 160px 1fr; gap: 12px; min-height: 340px;}
|
||||||
.sider { border-right: 1px solid #ebeef5; padding-right: 10px; }
|
.sider {border-right: 1px solid #ebeef5; padding-right: 10px;}
|
||||||
.sider-title { color:#303133; font-size:13px; font-weight: 600; margin-bottom: 10px; text-align: left; }
|
.sider-title {color:#303133; font-size:13px; font-weight: 600; margin-bottom: 10px; text-align: left;}
|
||||||
.nav { display:flex; flex-direction: column; gap: 4px; }
|
.nav {display:flex; flex-direction: column; gap: 4px;}
|
||||||
.nav-item { padding: 6px 8px; border-radius: 4px; cursor: pointer; color:#606266; font-size: 12px; transition: all 0.2s; text-align: left; }
|
.nav-item {padding: 6px 8px; border-radius: 4px; cursor: pointer; color:#606266; font-size: 12px; transition: all 0.2s; text-align: left;}
|
||||||
.nav-item:hover { background:#f0f2f5; }
|
.nav-item:hover {background:#f0f2f5;}
|
||||||
.nav-item.active { background:#e6f4ff; color:#409EFF; font-weight: 600; }
|
.nav-item.active {background:#e6f4ff; color:#409EFF; font-weight: 600;}
|
||||||
.platform-bar { font-weight: 600; color:#303133; margin: 0 0 12px 0; text-align: left; font-size: 14px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5; }
|
.platform-bar {font-weight: 600; color:#303133; margin: 0 0 12px 0; text-align: left; font-size: 14px; padding-bottom: 8px; border-bottom: 1px solid #ebeef5;}
|
||||||
.content { display:flex; flex-direction: column; min-width: 0; }
|
.content {display:flex; flex-direction: column; min-width: 0;}
|
||||||
.top { display:flex; flex-direction: column; align-items:center; gap: 6px; margin-bottom: 12px; }
|
.top {display:flex; flex-direction: column; align-items:center; gap: 6px; margin-bottom: 12px;}
|
||||||
.hero { width: 160px; height: auto; }
|
.hero {width: 160px; height: auto;}
|
||||||
.head-main { text-align:center; }
|
.head-main {text-align:center;}
|
||||||
.main-title { font-size: 16px; font-weight: 600; color:#303133; margin-bottom: 4px; }
|
.main-title {font-size: 16px; font-weight: 600; color:#303133; margin-bottom: 4px;}
|
||||||
.main-sub { color:#909399; font-size: 11px; line-height: 1.4; }
|
.main-sub {color:#909399; font-size: 11px; line-height: 1.4;}
|
||||||
.upgrade { color:#409EFF; cursor: pointer; font-weight: 600; transition: all 0.2s ease; }
|
.upgrade {color:#409EFF; cursor: pointer; font-weight: 600; transition: all 0.2s ease;}
|
||||||
.upgrade:hover { color:#0d5ed6; text-decoration: underline; }
|
.upgrade:hover {color:#0d5ed6; text-decoration: underline;}
|
||||||
.list { border:1px solid #ebeef5; border-radius: 6px; background: #fff; flex: 0 0 auto; width: 100%; max-height: 160px; overflow-y: auto; }
|
.list {border:1px solid #ebeef5; border-radius: 6px; background: #fff; flex: 0 0 auto; width: 100%; max-height: 160px; overflow-y: auto;}
|
||||||
.list.compact { max-height: 48px; }
|
.list.compact {max-height: 48px;}
|
||||||
|
|
||||||
/* 添加账号对话框样式 */
|
/* 添加账号对话框样式 */
|
||||||
.add-account-dialog .aad-header { display:flex; flex-direction: column; align-items:center; gap:8px; padding-top: 8px; width: 100%; }
|
.add-account-dialog .aad-header {display:flex; flex-direction: column; align-items:center; gap:8px; padding-top: 8px; width: 100%;}
|
||||||
.add-account-dialog .aad-icon { width: 120px; height: auto; }
|
.add-account-dialog .aad-icon {width: 120px; height: auto;}
|
||||||
.add-account-dialog .aad-title { font-weight: 600; font-size: 18px; text-align: center; }
|
.add-account-dialog .aad-title {font-weight: 600; font-size: 18px; text-align: center;}
|
||||||
.add-account-dialog .aad-row { margin-top: 12px; }
|
.add-account-dialog .aad-row {margin-top: 12px;}
|
||||||
:deep(.add-account-dialog .el-dialog__header) { text-align: center; padding-right: 0; display: block; }
|
:deep(.add-account-dialog .el-dialog__header) {text-align: center; padding-right: 0; display: block;}
|
||||||
.btn-blue { background: #1677FF; border-color: #1677FF; color: #fff; }
|
.btn-blue {background: #1677FF; border-color: #1677FF; color: #fff;}
|
||||||
.btn-blue:hover { background: #0d5ed6; border-color: #0d5ed6; }
|
.btn-blue:hover {background: #0d5ed6; border-color: #0d5ed6;}
|
||||||
.row { display:grid; grid-template-columns: 8px 1fr 120px 60px; gap: 8px; align-items:center; padding: 4px 8px; border-bottom: 1px solid #f5f5f5; height: 28px; }
|
.row {display:grid; grid-template-columns: 8px 1fr 120px 60px; gap: 8px; align-items:center; padding: 4px 8px; border-bottom: 1px solid #f5f5f5; height: 28px;}
|
||||||
.row:last-child { border-bottom:none; }
|
.row:last-child {border-bottom:none;}
|
||||||
.row:hover { background:#fafafa; }
|
.row:hover {background:#fafafa;}
|
||||||
.dot { width:6px; height:6px; border-radius:50%; justify-self: center; }
|
.dot {width:6px; height:6px; border-radius:50%; justify-self: center;}
|
||||||
.dot.on { background:#52c41a; }
|
.dot.on {background:#52c41a;}
|
||||||
.dot.off { background:#ff4d4f; }
|
.dot.off {background:#ff4d4f;}
|
||||||
.user-info { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
.user-info {display: flex; align-items: center; gap: 8px; min-width: 0;}
|
||||||
.avatar { width:22px; height:22px; border-radius:50%; object-fit: cover; }
|
.avatar {width:22px; height:22px; border-radius:50%; object-fit: cover;}
|
||||||
.name { font-weight:500; font-size: 13px; color:#303133; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.name {font-weight:500; font-size: 13px; color:#303133; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
|
||||||
.date { color:#999; font-size:11px; text-align: center; }
|
.date {color:#999; font-size:11px; text-align: center;}
|
||||||
.footer { display:flex; justify-content:center; padding-top: 10px; }
|
.footer {display:flex; justify-content:center; padding-top: 10px;}
|
||||||
.btn { width: 180px; height: 32px; font-size: 13px; }
|
.btn {width: 180px; height: 32px; font-size: 13px;}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -890,694 +890,135 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 对话框遮罩 */
|
/* 对话框遮罩 */
|
||||||
.dialog-mask {
|
.dialog-mask {position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 2000;}
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 对话框容器 */
|
/* 对话框容器 */
|
||||||
.dialog-container {
|
.dialog-container {width: 540px; background: #FFFFFF; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; max-height: 80vh; overflow: hidden;}
|
||||||
width: 540px;
|
|
||||||
background: #FFFFFF;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-height: 80vh;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 对话框动画 */
|
/* 对话框动画 */
|
||||||
.dialog-fade-enter-active,
|
.dialog-fade-enter-active,
|
||||||
.dialog-fade-leave-active {
|
.dialog-fade-leave-active {transition: opacity 0.2s ease;}
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-fade-enter-active .dialog-container,
|
.dialog-fade-enter-active .dialog-container,
|
||||||
.dialog-fade-leave-active .dialog-container {
|
.dialog-fade-leave-active .dialog-container {transition: transform 0.2s ease;}
|
||||||
transition: transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-fade-enter-from,
|
.dialog-fade-enter-from,
|
||||||
.dialog-fade-leave-to {
|
.dialog-fade-leave-to {opacity: 0;}
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-fade-enter-from .dialog-container,
|
.dialog-fade-enter-from .dialog-container,
|
||||||
.dialog-fade-leave-to .dialog-container {
|
.dialog-fade-leave-to .dialog-container {transform: scale(0.95);}
|
||||||
transform: scale(0.95);
|
.settings-layout {display: flex; height: 100%; flex: 1; overflow: hidden;}
|
||||||
}
|
|
||||||
|
|
||||||
.settings-layout {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 左侧导航 */
|
/* 左侧导航 */
|
||||||
.settings-sidebar {
|
.settings-sidebar {width: 130px; flex-shrink: 0; background: #FFFFFF; border-right: 1px solid #E5E6EB; padding: 0; overflow-y: auto; display: flex; flex-direction: column;}
|
||||||
width: 130px;
|
.sidebar-header {padding: 12px 12px; font-size: 14px; font-weight: 600; color: #1F2937; border-bottom: 1px solid #E5E6EB; text-align: left; flex-shrink: 0;}
|
||||||
flex-shrink: 0;
|
.settings-sidebar::-webkit-scrollbar {width: 6px;}
|
||||||
background: #FFFFFF;
|
.settings-sidebar::-webkit-scrollbar-track {background: transparent;}
|
||||||
border-right: 1px solid #E5E6EB;
|
.settings-sidebar::-webkit-scrollbar-thumb {background: #C9CDD4; border-radius: 3px;}
|
||||||
padding: 0;
|
.settings-sidebar::-webkit-scrollbar-thumb:hover {background: #86909C;}
|
||||||
overflow-y: auto;
|
.sidebar-item {display: flex; align-items: center; gap: 8px; padding: 10px 12px; margin: 0; cursor: pointer; transition: all 0.2s ease; color: #6B7280; font-size: 13px; user-select: none; position: relative;}
|
||||||
display: flex;
|
.sidebar-item:first-of-type {margin-top: 0;}
|
||||||
flex-direction: column;
|
.sidebar-item:hover {background: #F3F6FF; color: #165DFF;}
|
||||||
}
|
.sidebar-item.active {background: #F3F6FF; color: #165DFF; font-weight: 500;}
|
||||||
|
.sidebar-item.active::before {content: ''; position: absolute; left: 0; top: 0; width: 3px; height: 100%; background: #165DFF;}
|
||||||
.sidebar-header {
|
.sidebar-icon {font-size: 16px; flex-shrink: 0;}
|
||||||
padding: 12px 12px;
|
.sidebar-text {font-size: 13px;}
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1F2937;
|
|
||||||
border-bottom: 1px solid #E5E6EB;
|
|
||||||
text-align: left;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-sidebar::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-sidebar::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-sidebar::-webkit-scrollbar-thumb {
|
|
||||||
background: #C9CDD4;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-sidebar::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #86909C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
margin: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
color: #6B7280;
|
|
||||||
font-size: 13px;
|
|
||||||
user-select: none;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-item:first-of-type {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-item:hover {
|
|
||||||
background: #F3F6FF;
|
|
||||||
color: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-item.active {
|
|
||||||
background: #F3F6FF;
|
|
||||||
color: #165DFF;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-item.active::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 3px;
|
|
||||||
height: 100%;
|
|
||||||
background: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-text {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 右侧内容区域 */
|
/* 右侧内容区域 */
|
||||||
.settings-content {
|
.settings-content {flex: 1; display: flex; flex-direction: column; overflow: hidden;}
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 滚动内容区 */
|
/* 滚动内容区 */
|
||||||
.settings-main {
|
.settings-main {flex: 1; padding: 12px; overflow-y: auto; background: #F9FAFB;}
|
||||||
flex: 1;
|
.settings-main::-webkit-scrollbar {width: 6px;}
|
||||||
padding: 12px;
|
.settings-main::-webkit-scrollbar-track {background: transparent;}
|
||||||
overflow-y: auto;
|
.settings-main::-webkit-scrollbar-thumb {background: #C9CDD4; border-radius: 3px;}
|
||||||
background: #F9FAFB;
|
.settings-main::-webkit-scrollbar-thumb:hover {background: #86909C;}
|
||||||
}
|
.setting-section {background: #FFFFFF; border-radius: 6px; padding: 12px; margin-bottom: 10px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); border: 1px solid #E5E6EB;}
|
||||||
|
.export-section {padding: 10px 12px;}
|
||||||
.settings-main::-webkit-scrollbar {
|
.section-header {display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px;}
|
||||||
width: 6px;
|
.section-title {font-size: 14px; font-weight: 600; color: #1F2937; text-align: left;}
|
||||||
}
|
.section-title-row {display: flex; align-items: center; gap: 8px; margin-bottom: 8px;}
|
||||||
|
.vip-exclusive-logo {width: 60px; height: auto; vertical-align: middle;}
|
||||||
.settings-main::-webkit-scrollbar-track {
|
.section-header .section-title {margin-bottom: 0;}
|
||||||
background: transparent;
|
.section-subtitle {font-size: 11px; color: #86909C; font-weight: normal;}
|
||||||
}
|
.section-subtitle-text {font-size: 12px; color: #86909C; margin-bottom: 8px; text-align: left;}
|
||||||
|
.cache-desc {font-size: 13px; color: #86909C;}
|
||||||
.settings-main::-webkit-scrollbar-thumb {
|
.setting-item {margin-bottom: 0;}
|
||||||
background: #C9CDD4;
|
.setting-item:last-child {margin-bottom: 0;}
|
||||||
border-radius: 3px;
|
.setting-row {display: flex; align-items: center; justify-content: space-between;}
|
||||||
}
|
.setting-info {flex: 1;}
|
||||||
|
.setting-label {font-size: 13px; font-weight: 500; color: #1F2937; margin-bottom: 6px; display: block; text-align: left;}
|
||||||
.settings-main::-webkit-scrollbar-thumb:hover {
|
.setting-desc {font-size: 12px; color: #86909C; line-height: 1.5; margin-bottom: 8px; text-align: left;}
|
||||||
background: #86909C;
|
.input-button-row {display: flex; align-items: center;}
|
||||||
}
|
.path-input {flex: 1; margin-right: 8px;}
|
||||||
|
.path-input :deep(.el-input__wrapper) {border-color: #E5E6EB; box-shadow: none; transition: all 0.2s;}
|
||||||
.setting-section {
|
.path-input :deep(.el-input__inner) {font-size: 13px;}
|
||||||
background: #FFFFFF;
|
.path-input :deep(.el-input__wrapper:hover) {border-color: #165DFF;}
|
||||||
border-radius: 6px;
|
.path-input :deep(.el-input__wrapper.is-focus) {border-color: #165DFF; box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);}
|
||||||
padding: 12px;
|
.input-button-row :deep(.el-button) {padding: 6px 16px; font-size: 13px; flex-shrink: 0;}
|
||||||
margin-bottom: 10px;
|
.info-content {background: #F8F9FA; border-radius: 6px; padding: 12px; font-size: 13px; color: #606266; line-height: 1.5;}
|
||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
.info-content p {margin: 0 0 6px 0;}
|
||||||
border: 1px solid #E5E6EB;
|
.info-content p:last-child {margin-bottom: 0;}
|
||||||
}
|
.dialog-footer {display: flex; justify-content: flex-end; gap: 10px; padding: 5px 14px; border-top: 1px solid #E5E6EB; background: #FFFFFF; flex-shrink: 0;}
|
||||||
|
.dialog-footer :deep(.el-button) {padding: 5px 14px; border-radius: 6px; font-size: 12px;}
|
||||||
.export-section {
|
.dialog-footer :deep(.el-button--primary) {background: #165DFF; border-color: #165DFF;}
|
||||||
padding: 10px 12px;
|
.dialog-footer :deep(.el-button--primary:hover) {background: #4080FF; border-color: #4080FF;}
|
||||||
}
|
.dialog-footer :deep(.el-button--default) {border-color: #E5E6EB; color: #6B7280;}
|
||||||
|
.dialog-footer :deep(.el-button--default:hover) {border-color: #165DFF; color: #165DFF; background: #F3F6FF;}
|
||||||
.section-header {
|
.feedback-form {padding-top: 0;}
|
||||||
display: flex;
|
.feedback-form .setting-item + .setting-item {margin-top: 8px;}
|
||||||
align-items: baseline;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1F2937;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-exclusive-logo {
|
|
||||||
width: 60px;
|
|
||||||
height: auto;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-header .section-title {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-subtitle {
|
|
||||||
font-size: 11px;
|
|
||||||
color: #86909C;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-subtitle-text {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #86909C;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cache-desc {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #86909C;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-item {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-label {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1F2937;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
display: block;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-desc {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #86909C;
|
|
||||||
line-height: 1.5;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-button-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-input {
|
|
||||||
flex: 1;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-input :deep(.el-input__wrapper) {
|
|
||||||
border-color: #E5E6EB;
|
|
||||||
box-shadow: none;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-input :deep(.el-input__inner) {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-input :deep(.el-input__wrapper:hover) {
|
|
||||||
border-color: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path-input :deep(.el-input__wrapper.is-focus) {
|
|
||||||
border-color: #165DFF;
|
|
||||||
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-button-row :deep(.el-button) {
|
|
||||||
padding: 6px 16px;
|
|
||||||
font-size: 13px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.info-content {
|
|
||||||
background: #F8F9FA;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #606266;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-content p {
|
|
||||||
margin: 0 0 6px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-content p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.dialog-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 5px 14px;
|
|
||||||
border-top: 1px solid #E5E6EB;
|
|
||||||
background: #FFFFFF;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer :deep(.el-button) {
|
|
||||||
padding: 5px 14px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer :deep(.el-button--primary) {
|
|
||||||
background: #165DFF;
|
|
||||||
border-color: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer :deep(.el-button--primary:hover) {
|
|
||||||
background: #4080FF;
|
|
||||||
border-color: #4080FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer :deep(.el-button--default) {
|
|
||||||
border-color: #E5E6EB;
|
|
||||||
color: #6B7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-footer :deep(.el-button--default:hover) {
|
|
||||||
border-color: #165DFF;
|
|
||||||
color: #165DFF;
|
|
||||||
background: #F3F6FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form .setting-item + .setting-item {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 图片预览容器 */
|
/* 图片预览容器 */
|
||||||
.image-preview-wrapper {
|
.image-preview-wrapper {position: relative; width: 75%;}
|
||||||
position: relative;
|
.preview-image {width: 100%; display: block; object-fit: contain; border-radius: 8px;}
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-image {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 占位符 */
|
/* 占位符 */
|
||||||
.splash-placeholder {
|
.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;}
|
||||||
width: 75% !important;
|
.splash-placeholder:hover {border-color: #1677ff !important; background: #f0f7ff !important;}
|
||||||
height: auto !important;
|
.placeholder-content {display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; padding: 30px 20px;}
|
||||||
border: 2px dashed #d9d9d9 !important;
|
.placeholder-icon {font-size: 28px;}
|
||||||
border-radius: 8px !important;
|
.placeholder-text {font-size: 13px; color: rgba(0, 0, 0, 0.45);}
|
||||||
background: #fafafa !important;
|
.image-buttons {position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); display: flex; gap: 8px; opacity: 0; transition: opacity 0.2s ease;}
|
||||||
cursor: pointer;
|
.image-preview-wrapper:hover .image-buttons {opacity: 1;}
|
||||||
transition: all 0.3s;
|
.img-btn {padding: 6px 14px; background: rgba(0, 0, 0, 0.6); border: none; border-radius: 4px; color: #fff; font-size: 12px; cursor: pointer; transition: background 0.2s ease;}
|
||||||
}
|
.img-btn:hover:not(:disabled) {background: rgba(0, 0, 0, 0.8);}
|
||||||
|
.img-btn:disabled {opacity: 0.5; cursor: not-allowed;}
|
||||||
.splash-placeholder:hover {
|
.feedback-form :deep(.el-textarea__inner) {border-color: #E5E6EB; border-radius: 6px; font-size: 13px; padding: 8px 12px;}
|
||||||
border-color: #1677ff !important;
|
.feedback-form :deep(.el-textarea__inner:hover) {border-color: #165DFF;}
|
||||||
background: #f0f7ff !important;
|
.feedback-form :deep(.el-textarea__inner:focus) {border-color: #165DFF; box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);}
|
||||||
}
|
.feedback-form :deep(.el-select) {width: 100%;}
|
||||||
|
.feedback-form :deep(.el-select .el-input__inner) {font-size: 13px;}
|
||||||
.placeholder-content {
|
.feedback-form :deep(.el-select .el-input__wrapper) {border-color: #E5E6EB;}
|
||||||
display: flex;
|
.feedback-form :deep(.el-select .el-input__wrapper:hover) {border-color: #165DFF;}
|
||||||
flex-direction: column;
|
.feedback-form :deep(.el-select .el-input__wrapper.is-focus) {border-color: #165DFF; box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);}
|
||||||
align-items: center;
|
.feedback-form .input-button-row :deep(.el-button) {height: 32px; padding: 0 16px; font-size: 13px;}
|
||||||
justify-content: center;
|
.feedback-form .input-button-row :deep(.el-button--primary) {background: #165DFF; border-color: #165DFF;}
|
||||||
gap: 6px;
|
.feedback-form .input-button-row :deep(.el-button--primary:hover) {background: #4080FF; border-color: #4080FF;}
|
||||||
padding: 30px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-icon {
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-text {
|
|
||||||
font-size: 13px;
|
|
||||||
color: rgba(0, 0, 0, 0.45);
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-buttons {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-preview-wrapper:hover .image-buttons {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-btn {
|
|
||||||
padding: 6px 14px;
|
|
||||||
background: rgba(0, 0, 0, 0.6);
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-btn:hover:not(:disabled) {
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.img-btn:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form :deep(.el-textarea__inner) {
|
|
||||||
border-color: #E5E6EB;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form :deep(.el-textarea__inner:hover) {
|
|
||||||
border-color: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form :deep(.el-textarea__inner:focus) {
|
|
||||||
border-color: #165DFF;
|
|
||||||
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form :deep(.el-select) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form :deep(.el-select .el-input__inner) {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form :deep(.el-select .el-input__wrapper) {
|
|
||||||
border-color: #E5E6EB;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form :deep(.el-select .el-input__wrapper:hover) {
|
|
||||||
border-color: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form :deep(.el-select .el-input__wrapper.is-focus) {
|
|
||||||
border-color: #165DFF;
|
|
||||||
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form .input-button-row :deep(.el-button) {
|
|
||||||
height: 32px;
|
|
||||||
padding: 0 16px;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form .input-button-row :deep(.el-button--primary) {
|
|
||||||
background: #165DFF;
|
|
||||||
border-color: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-form .input-button-row :deep(.el-button--primary:hover) {
|
|
||||||
background: #4080FF;
|
|
||||||
border-color: #4080FF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 单选框组 */
|
/* 单选框组 */
|
||||||
.compact-radio-group {
|
.compact-radio-group {display: flex; flex-direction: column; gap: 10px; align-items: flex-start;}
|
||||||
display: flex;
|
.compact-radio-group :deep(.el-radio) {margin-right: 0; height: auto; display: flex; align-items: center;}
|
||||||
flex-direction: column;
|
.compact-radio-group :deep(.el-radio__label) {font-size: 13px; color: #1F2937; padding-left: 8px; line-height: 1.4;}
|
||||||
gap: 10px;
|
.compact-radio-group :deep(.el-radio__inner) {width: 16px; height: 16px; border-width: 1px;}
|
||||||
align-items: flex-start;
|
.compact-radio-group :deep(.el-radio__input) {flex-shrink: 0;}
|
||||||
}
|
.compact-radio-group :deep(.el-radio__input.is-checked .el-radio__inner) {background: #165DFF; border-color: #165DFF; border-width: 1px;}
|
||||||
|
.compact-radio-group :deep(.el-radio__inner::after) {width: 6px; height: 6px;}
|
||||||
.compact-radio-group :deep(.el-radio) {
|
.compact-radio-group :deep(.el-radio__input.is-checked + .el-radio__label) {color: #165DFF;}
|
||||||
margin-right: 0;
|
|
||||||
height: auto;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-radio-group :deep(.el-radio__label) {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #1F2937;
|
|
||||||
padding-left: 8px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-radio-group :deep(.el-radio__inner) {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-radio-group :deep(.el-radio__input) {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-radio-group :deep(.el-radio__input.is-checked .el-radio__inner) {
|
|
||||||
background: #165DFF;
|
|
||||||
border-color: #165DFF;
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-radio-group :deep(.el-radio__inner::after) {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.compact-radio-group :deep(.el-radio__input.is-checked + .el-radio__label) {
|
|
||||||
color: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 更新相关样式 */
|
/* 更新相关样式 */
|
||||||
.update-info-row {
|
.update-info-row {display: flex; flex-direction: column; gap: 8px;}
|
||||||
display: flex;
|
.version-display {display: flex; align-items: center; gap: 10px;}
|
||||||
flex-direction: column;
|
.version-label {font-size: 13px; color: #6B7280; flex-shrink: 0;}
|
||||||
gap: 8px;
|
.version-value {font-size: 13px; font-weight: 600; color: #165DFF; flex: 1;}
|
||||||
}
|
.version-display :deep(.el-button) {padding: 6px 16px; font-size: 13px; flex-shrink: 0;}
|
||||||
|
.checkbox-row {display: flex; align-items: center;}
|
||||||
.version-display {
|
.checkbox-row :deep(.el-checkbox) {height: auto;}
|
||||||
display: flex;
|
.checkbox-row :deep(.el-checkbox__label) {font-size: 13px; color: #1F2937; padding-left: 8px;}
|
||||||
align-items: center;
|
.checkbox-row :deep(.el-checkbox__inner) {width: 16px; height: 16px;}
|
||||||
gap: 10px;
|
.checkbox-row :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {background: #165DFF; border-color: #165DFF;}
|
||||||
}
|
.update-notes-section {background: #F8F9FA; border-radius: 6px; padding: 10px;}
|
||||||
|
.notes-title {font-size: 13px; font-weight: 600; color: #374151; margin: 0 0 6px 0;}
|
||||||
.version-label {
|
.notes-content {font-size: 12px; color: #6B7280; line-height: 1.6; white-space: pre-wrap;}
|
||||||
font-size: 13px;
|
|
||||||
color: #6B7280;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.version-value {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #165DFF;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.version-display :deep(.el-button) {
|
|
||||||
padding: 6px 16px;
|
|
||||||
font-size: 13px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-row :deep(.el-checkbox) {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-row :deep(.el-checkbox__label) {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #1F2937;
|
|
||||||
padding-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-row :deep(.el-checkbox__inner) {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox-row :deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
|
|
||||||
background: #165DFF;
|
|
||||||
border-color: #165DFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-notes-section {
|
|
||||||
background: #F8F9FA;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes-title {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #374151;
|
|
||||||
margin: 0 0 6px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes-content {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6B7280;
|
|
||||||
line-height: 1.6;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 平台项样式 */
|
/* 平台项样式 */
|
||||||
.platform-item {
|
.platform-item {margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid #E5E6EB;}
|
||||||
margin-bottom: 8px;
|
.platform-item:last-child {margin-bottom: 0; padding-bottom: 0; border-bottom: none;}
|
||||||
padding-bottom: 8px;
|
.platform-item:first-child {margin-top: 2px;}
|
||||||
border-bottom: 1px solid #E5E6EB;
|
.platform-header {display: flex; align-items: center; gap: 6px; margin-bottom: 6px;}
|
||||||
}
|
.platform-icon {font-size: 16px; line-height: 1;}
|
||||||
|
.platform-name {font-size: 13px; font-weight: 600; color: #1F2937;}
|
||||||
.platform-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.platform-item:first-child {
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.platform-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.platform-icon {
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.platform-name {
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1F2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 占位符 */
|
/* 占位符 */
|
||||||
.splash-placeholder {
|
.splash-placeholder {width: 100%; height: 180px; border-radius: 6px; border: 2px dashed #E5E6EB; background: #F8F9FA; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;}
|
||||||
width: 100%;
|
.placeholder-icon {font-size: 32px; opacity: 0.5;}
|
||||||
height: 180px;
|
.placeholder-text {font-size: 13px; color: #86909C;}
|
||||||
border-radius: 6px;
|
|
||||||
border: 2px dashed #E5E6EB;
|
|
||||||
background: #F8F9FA;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-icon {
|
|
||||||
font-size: 32px;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-text {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #86909C;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -93,116 +93,23 @@ function copyWechat() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.trial-expired-dialog :deep(.el-dialog) {
|
.trial-expired-dialog :deep(.el-dialog) {border-radius: 16px;}
|
||||||
border-radius: 16px;
|
.trial-expired-dialog :deep(.el-dialog__header) {padding: 0; margin: 0;}
|
||||||
}
|
.trial-expired-dialog :deep(.el-dialog__body) {padding: 20px;}
|
||||||
|
.expired-content {display: flex; flex-direction: column; align-items: center; padding: 10px 0;}
|
||||||
.trial-expired-dialog :deep(.el-dialog__header) {
|
.expired-logo {width: 160px; height: auto;}
|
||||||
padding: 0;
|
.expired-title {font-size: 18px; font-weight: 700; color: #1f1f1f; margin: 0 0 8px 0; text-align: center;}
|
||||||
margin: 0;
|
.expired-subtitle {font-size: 12px; color: #8c8c8c; margin: 0 0 20px 0; text-align: center; line-height: 1.5;}
|
||||||
}
|
.wechat-card {display: flex; align-items: center; gap: 12px; padding: 10px 16px; background: #f5f5f5; border-radius: 6px; margin-bottom: 20px; width: 90%; cursor: pointer; transition: all 0.3s; position: relative;}
|
||||||
|
.wechat-card:hover {background: #e8f5e9; box-shadow: 0 2px 8px rgba(9, 187, 7, 0.15);}
|
||||||
.trial-expired-dialog :deep(.el-dialog__body) {
|
.wechat-icon {flex-shrink: 0;}
|
||||||
padding: 20px;
|
.wechat-icon svg {width: 36px; height: 36px;}
|
||||||
}
|
.wechat-info {flex: 1; text-align: left;}
|
||||||
|
.wechat-label {font-size: 12px; color: #666; margin-bottom: 2px;}
|
||||||
.expired-content {
|
.wechat-id {font-size: 15px; font-weight: 500; color: #1f1f1f;}
|
||||||
display: flex;
|
.copy-icon {margin-left: auto; font-size: 16px; opacity: 0.5; transition: all 0.3s;}
|
||||||
flex-direction: column;
|
.wechat-card:hover .copy-icon {opacity: 1; transform: scale(1.1);}
|
||||||
align-items: center;
|
.confirm-btn {height: 40px; font-size: 14px; font-weight: 500; background: #1677FF; border-color: #1677FF; border-radius: 6px;}
|
||||||
padding: 10px 0;
|
.confirm-btn:hover {background: #4096ff; border-color: #4096ff;}
|
||||||
}
|
|
||||||
|
|
||||||
.expired-logo {
|
|
||||||
width: 160px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expired-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #1f1f1f;
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.expired-subtitle {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #8c8c8c;
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
width: 90%;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-card:hover {
|
|
||||||
background: #e8f5e9;
|
|
||||||
box-shadow: 0 2px 8px rgba(9, 187, 7, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-icon {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-icon svg {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-info {
|
|
||||||
flex: 1;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-id {
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1f1f1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.copy-icon {
|
|
||||||
margin-left: auto;
|
|
||||||
font-size: 16px;
|
|
||||||
opacity: 0.5;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wechat-card:hover .copy-icon {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-btn {
|
|
||||||
height: 40px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
background: #1677FF;
|
|
||||||
border-color: #1677FF;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-btn:hover {
|
|
||||||
background: #4096ff;
|
|
||||||
border-color: #4096ff;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -298,292 +298,58 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
:deep(.update-dialog .el-dialog) {
|
:deep(.update-dialog .el-dialog) {border-radius: 16px; box-shadow: 0 24px 48px rgba(0, 0, 0, 0.15);}
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 24px 48px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 通用标题样式 */
|
/* 通用标题样式 */
|
||||||
:deep(.update-dialog .el-dialog__title) {
|
:deep(.update-dialog .el-dialog__title) {font-size: 14px; font-weight: 500; margin-left: 8px;}
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 默认标题样式(第一阶段 - 检查阶段) */
|
/* 默认标题样式(第一阶段 - 检查阶段) */
|
||||||
:deep(.update-dialog.stage-check .el-dialog__header) {
|
:deep(.update-dialog.stage-check .el-dialog__header) {display: block; text-align: left;}
|
||||||
display: block;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 第二阶段 - 下载中,标题居中 */
|
/* 第二阶段 - 下载中,标题居中 */
|
||||||
:deep(.update-dialog.stage-downloading .el-dialog__header) {
|
:deep(.update-dialog.stage-downloading .el-dialog__header) {display: block; text-align: center;}
|
||||||
display: block;
|
:deep(.update-dialog.stage-downloading .el-dialog__title) {margin-left: 20px;}
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.update-dialog.stage-downloading .el-dialog__title) {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 第三阶段 - 下载完成,标题居中 */
|
/* 第三阶段 - 下载完成,标题居中 */
|
||||||
:deep(.update-dialog.stage-completed .el-dialog__header) {
|
:deep(.update-dialog.stage-completed .el-dialog__header) {display: block; text-align: center;}
|
||||||
display: block;
|
:deep(.update-dialog.stage-completed .el-dialog__title) {margin-left: 20px;}
|
||||||
text-align: center;
|
:deep(.update-dialog .el-dialog__body) {padding: 0;}
|
||||||
}
|
.update-content {text-align: left;}
|
||||||
|
.update-layout {display: grid; grid-template-columns: 88px 1fr; align-items: start; margin-bottom: 5px;}
|
||||||
:deep(.update-dialog.stage-completed .el-dialog__title) {
|
.left-pane {display: flex; flex-direction: column; align-items: flex-start;}
|
||||||
margin-left: 20px;
|
.app-icon-large {width: 70px; height: 70px; border-radius: 12px; margin: 4px 0 0 0;}
|
||||||
}
|
.right-pane {min-width: 0;}
|
||||||
|
.right-pane .announce {font-size: 16px; font-weight: 600; color: #1f2937; margin: 4px 0 6px; word-break: break-word;}
|
||||||
:deep(.update-dialog .el-dialog__body) {
|
.right-pane .desc {font-size: 13px; color: #6b7280; line-height: 1.6; margin: 0; word-break: break-word;}
|
||||||
padding: 0;
|
.update-header {display: flex; align-items: flex-start; margin-bottom: 24px;}
|
||||||
}
|
.update-header.text-center {text-align: center; flex-direction: column; align-items: center;}
|
||||||
|
.app-icon {width: 70px; height: 70px; border-radius: 12px; margin-right: 16px; flex-shrink: 0;}
|
||||||
.update-content {
|
.update-header.text-center .app-icon {margin-right: 0; margin-bottom: 16px;}
|
||||||
text-align: left;
|
.update-header h3 {font-size: 20px; font-weight: 600; margin: 16px 0 8px 0; color: #1f2937;}
|
||||||
}
|
.update-header p {font-size: 14px; color: #6b7280; margin: 0; line-height: 1.5;}
|
||||||
|
.update-details {border-radius: 8px; padding: 0; margin: 12px 0 8px 0;}
|
||||||
.update-layout {
|
.update-details.form {max-height: none;}
|
||||||
display: grid;
|
.notes-box :deep(textarea.el-textarea__inner) {white-space: pre-wrap;}
|
||||||
grid-template-columns: 88px 1fr;
|
.update-details h4 {font-size: 14px; font-weight: 600; color: #374151; margin: 0 0 8px 0;}
|
||||||
align-items: start;
|
.update-actions.row {display: flex; flex-direction: column; align-items: stretch; gap: 12px;}
|
||||||
margin-bottom: 5px;
|
.update-buttons {display: flex; justify-content: space-between; gap: 12px;}
|
||||||
}
|
.update-actions.row .update-buttons {justify-content: space-between;}
|
||||||
|
:deep(.update-actions.row .update-buttons .el-button) {flex: none; min-width: 100px;}
|
||||||
.left-pane {
|
.left-actions {display: flex; gap: 12px;}
|
||||||
display: flex;
|
.right-actions {display: flex; gap: 8px;}
|
||||||
flex-direction: column;
|
:deep(.update-buttons .el-button) {flex: 1; height: 32px; font-size: 13px; border-radius: 8px;}
|
||||||
align-items: flex-start;
|
.download-header h3 {font-size: 14px; font-weight: 500; margin: 0; color: #1f2937;}
|
||||||
}
|
.download-main {display: grid; grid-template-columns: 80px 1fr; align-items: start;}
|
||||||
|
.download-icon {display: flex; justify-content: center;}
|
||||||
.app-icon-large {
|
.download-icon .app-icon {width: 64px; height: 64px; border-radius: 12px;}
|
||||||
width: 70px;
|
.download-content {min-width: 0;}
|
||||||
height: 70px;
|
.download-info {margin-bottom: 12px;}
|
||||||
border-radius: 12px;
|
.download-info p {font-size: 14px; font-weight: 600; color: #6b7280; margin: 0;}
|
||||||
margin: 4px 0 0 0;
|
.download-progress {margin: 0;}
|
||||||
}
|
.progress-info {display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 14px; color: #6b7280;}
|
||||||
|
.progress-details {margin-top: 12px; display: flex; justify-content: space-between; align-items: center;}
|
||||||
.right-pane {
|
.progress-details span {font-size: 12px; color: #909399;}
|
||||||
min-width: 0;
|
.action-buttons {display: flex; gap: 8px;}
|
||||||
}
|
:deep(.el-progress-bar__outer) {border-radius: 4px; background-color: #e5e7eb;}
|
||||||
|
:deep(.el-progress-bar__inner) {border-radius: 4px; transition: width 0.3s ease;}
|
||||||
.right-pane .announce {
|
:deep(.update-buttons .el-button--primary) {background-color: #2563eb; border-color: #2563eb; font-weight: 500;}
|
||||||
font-size: 16px;
|
:deep(.update-buttons .el-button--primary:hover) {background-color: #1d4ed8; border-color: #1d4ed8;}
|
||||||
font-weight: 600;
|
:deep(.update-buttons .el-button:not(.el-button--primary)) {background-color: #f3f4f6; border-color: #d1d5db; color: #374151; font-weight: 500;}
|
||||||
color: #1f2937;
|
:deep(.update-buttons .el-button:not(.el-button--primary):hover) {background-color: #e5e7eb; border-color: #9ca3af;}
|
||||||
margin: 4px 0 6px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-pane .desc {
|
|
||||||
font-size: 13px;
|
|
||||||
color: #6b7280;
|
|
||||||
line-height: 1.6;
|
|
||||||
margin: 0;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-header.text-center {
|
|
||||||
text-align: center;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-icon {
|
|
||||||
width: 70px;
|
|
||||||
height: 70px;
|
|
||||||
border-radius: 12px;
|
|
||||||
margin-right: 16px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-header.text-center .app-icon {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-header h3 {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 16px 0 8px 0;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-header p {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-details {
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 12px 0 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-details.form {
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes-box :deep(textarea.el-textarea__inner) {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-details h4 {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #374151;
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-actions.row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-actions.row .update-buttons {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.update-actions.row .update-buttons .el-button) {
|
|
||||||
flex: none;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.update-buttons .el-button) {
|
|
||||||
flex: 1;
|
|
||||||
height: 32px;
|
|
||||||
font-size: 13px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.download-header h3 {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin: 0;
|
|
||||||
color: #1f2937;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-main {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 80px 1fr;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-icon .app-icon {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-content {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-info {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-info p {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #6b7280;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-progress {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-details {
|
|
||||||
margin-top: 12px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-details span {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-progress-bar__outer) {
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #e5e7eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-progress-bar__inner) {
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.update-buttons .el-button--primary) {
|
|
||||||
background-color: #2563eb;
|
|
||||||
border-color: #2563eb;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.update-buttons .el-button--primary:hover) {
|
|
||||||
background-color: #1d4ed8;
|
|
||||||
border-color: #1d4ed8;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.update-buttons .el-button:not(.el-button--primary)) {
|
|
||||||
background-color: #f3f4f6;
|
|
||||||
border-color: #d1d5db;
|
|
||||||
color: #374151;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.update-buttons .el-button:not(.el-button--primary):hover) {
|
|
||||||
background-color: #e5e7eb;
|
|
||||||
border-color: #9ca3af;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -140,227 +140,44 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.top-navbar {
|
.top-navbar {height: 40px; display: flex; align-items: center; justify-content: space-between; padding: 0 16px; background: #ffffff; border-bottom: 1px solid #e8eaec; box-shadow: 0 1px 3px rgba(0,0,0,0.03); -webkit-app-region: drag; user-select: none;}
|
||||||
height: 40px;
|
.navbar-left {display: flex; align-items: center; gap: 8px; flex: 0 0 auto; -webkit-app-region: no-drag;}
|
||||||
display: flex;
|
.navbar-center {display: flex; justify-content: center; flex: 1;}
|
||||||
align-items: center;
|
.navbar-right {display: flex; align-items: center; gap: 8px; flex: 0 0 auto; -webkit-app-region: no-drag;}
|
||||||
justify-content: space-between;
|
.nav-controls {display: flex; gap: 4px;}
|
||||||
padding: 0 16px;
|
.nav-btn {width: 28px; height: 28px; border: none; background: transparent; cursor: pointer; font-size: 16px; color: #606266; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; outline: none; border-radius: 4px;}
|
||||||
background: #ffffff;
|
.arrow-icon {width: 18px; height: 18px; flex-shrink: 0;}
|
||||||
border-bottom: 1px solid #e8eaec;
|
.arrow-icon path {stroke: currentColor;}
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
|
.nav-btn:hover:not(:disabled) {background: rgba(0, 0, 0, 0.05); color: #409EFF;}
|
||||||
-webkit-app-region: drag;
|
.nav-btn:hover:not(:disabled) .arrow-icon path {stroke: #409EFF;}
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-center {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #606266;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
outline: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-icon {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-icon path {
|
|
||||||
stroke: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn:hover:not(:disabled) {
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
color: #409EFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn:hover:not(:disabled) .arrow-icon path {
|
|
||||||
stroke: #409EFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn:focus,
|
.nav-btn:focus,
|
||||||
.nav-btn:active {
|
.nav-btn:active {outline: none; border: none;}
|
||||||
outline: none;
|
.nav-btn:disabled {cursor: not-allowed; background: transparent; color: #d0d0d0;}
|
||||||
border: none;
|
.nav-btn:disabled .arrow-icon path {stroke: #d0d0d0;}
|
||||||
}
|
.nav-btn-round {width: 28px; height: 28px; border: none; border-radius: 4px; background: transparent; cursor: pointer; font-size: 16px; color: #606266; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; outline: none;}
|
||||||
|
.nav-btn-round:hover {background: rgba(0, 0, 0, 0.05); color: #409EFF;}
|
||||||
.nav-btn:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
background: transparent;
|
|
||||||
color: #d0d0d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn:disabled .arrow-icon path {
|
|
||||||
stroke: #d0d0d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn-round {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #606266;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn-round:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
color: #409EFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn-round:focus,
|
.nav-btn-round:focus,
|
||||||
.nav-btn-round:active {
|
.nav-btn-round:active {outline: none;}
|
||||||
outline: none;
|
.breadcrumbs {display: flex; align-items: center; color: #606266; font-size: 14px;}
|
||||||
}
|
.separator {margin: 0 6px; color: #c0c4cc; font-size: 14px;}
|
||||||
|
|
||||||
.breadcrumbs {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: #606266;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
margin: 0 6px;
|
|
||||||
color: #c0c4cc;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 窗口控制按钮 */
|
/* 窗口控制按钮 */
|
||||||
.window-controls {
|
.window-controls {display: flex; align-items: center; gap: 4px; margin-left: 8px;}
|
||||||
display: flex;
|
.window-btn {width: 32px; height: 28px; border: none; background: transparent; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; color: #606266; transition: all 0.2s ease; outline: none; padding: 0; margin: 0; border-radius: 4px;}
|
||||||
align-items: center;
|
.window-btn:hover {background: rgba(0, 0, 0, 0.05);}
|
||||||
gap: 4px;
|
.window-btn:active {background: rgba(0, 0, 0, 0.1);}
|
||||||
margin-left: 8px;
|
.window-btn-close:hover {background: #e81123; color: #ffffff;}
|
||||||
}
|
.window-btn-close:active {background: #f1707a;}
|
||||||
|
.maximize-icon {width: 12px; height: 12px;}
|
||||||
.window-btn {
|
|
||||||
width: 32px;
|
|
||||||
height: 28px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
outline: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-btn:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-btn:active {
|
|
||||||
background: rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-btn-close:hover {
|
|
||||||
background: #e81123;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window-btn-close:active {
|
|
||||||
background: #f1707a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maximize-icon {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 登录/注册按钮 */
|
/* 登录/注册按钮 */
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* 设置下拉菜单样式 */
|
/* 设置下拉菜单样式 */
|
||||||
.settings-dropdown {
|
.settings-dropdown {min-width: 180px !important; padding: 4px 0 !important; border-radius: 12px !important; margin-top: 4px !important;}
|
||||||
min-width: 180px !important;
|
.settings-dropdown .username-item {font-weight: 600 !important; color: #000000 !important; cursor: default !important; padding: 8px 16px !important; font-size: 14px !important;}
|
||||||
padding: 4px 0 !important;
|
.settings-dropdown .menu-item {padding: 8px 16px !important; font-size: 13px !important; color: #000000 !important; transition: all 0.2s ease !important;}
|
||||||
border-radius: 12px !important;
|
.settings-dropdown .menu-item:hover {background: #f5f7fa !important; color: #409EFF !important;}
|
||||||
margin-top: 4px !important;
|
.settings-dropdown .logout-item {color: #000000 !important;}
|
||||||
}
|
.settings-dropdown .logout-item:hover {background: #f5f7fa !important; color: #409EFF !important;}
|
||||||
|
.settings-dropdown .el-dropdown-menu__item.is-disabled {cursor: default !important; opacity: 1 !important;}
|
||||||
.settings-dropdown .username-item {
|
|
||||||
font-weight: 600 !important;
|
|
||||||
color: #000000 !important;
|
|
||||||
cursor: default !important;
|
|
||||||
padding: 8px 16px !important;
|
|
||||||
font-size: 14px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-dropdown .menu-item {
|
|
||||||
padding: 8px 16px !important;
|
|
||||||
font-size: 13px !important;
|
|
||||||
color: #000000 !important;
|
|
||||||
transition: all 0.2s ease !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-dropdown .menu-item:hover {
|
|
||||||
background: #f5f7fa !important;
|
|
||||||
color: #409EFF !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-dropdown .logout-item {
|
|
||||||
color: #000000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-dropdown .logout-item:hover {
|
|
||||||
background: #f5f7fa !important;
|
|
||||||
color: #409EFF !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-dropdown .el-dropdown-menu__item.is-disabled {
|
|
||||||
cursor: default !important;
|
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -649,229 +649,101 @@ onMounted(loadLatest)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.rakuten-root {
|
.rakuten-root {position: absolute; inset: 0; background: #fff; box-sizing: border-box;}
|
||||||
position: absolute;
|
.main-container {height: 100%; display: flex; flex-direction: column; padding: 12px; box-sizing: border-box;}
|
||||||
inset: 0;
|
.body-layout {display: flex; gap: 12px; height: 100%;}
|
||||||
background: #fff;
|
.steps-sidebar {width: 220px; background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 10px; height: 100%; flex-shrink: 0;}
|
||||||
box-sizing: border-box;
|
.steps-title {font-size: 14px; font-weight: 600; color: #303133; text-align: left;}
|
||||||
}
|
|
||||||
|
|
||||||
.main-container {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 12px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.body-layout { display: flex; gap: 12px; height: 100%; }
|
|
||||||
.steps-sidebar { width: 220px; background: #fff; border: 1px solid #ebeef5; border-radius: 6px; padding: 10px; height: 100%; flex-shrink: 0; }
|
|
||||||
.steps-title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
|
|
||||||
|
|
||||||
/* 卡片式步骤,与示例一致 */
|
/* 卡片式步骤,与示例一致 */
|
||||||
.steps-flow { position: relative; }
|
.steps-flow {position: relative;}
|
||||||
.steps-flow:before { content: ''; position: absolute; left: 11px; top: 20px; bottom: 0; width: 1px; background: rgba(229, 231, 235, 0.6); }
|
.steps-flow:before {content: ''; position: absolute; left: 11px; top: 20px; bottom: 0; width: 1px; background: rgba(229, 231, 235, 0.6);}
|
||||||
.flow-item { position: relative; display: grid; grid-template-columns: 22px 1fr; gap: 10px; padding: 8px 0; }
|
.flow-item {position: relative; display: grid; grid-template-columns: 22px 1fr; gap: 10px; padding: 8px 0;}
|
||||||
.flow-item .step-index { position: static; width: 22px; height: 22px; line-height: 22px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 12px; font-weight: 600; margin-top: 2px; }
|
.flow-item .step-index {position: static; width: 22px; height: 22px; line-height: 22px; text-align: center; border-radius: 50%; background: #1677FF; color: #fff; font-size: 12px; font-weight: 600; margin-top: 2px;}
|
||||||
.flow-item:after { display: none; }
|
.flow-item:after {display: none;}
|
||||||
.step-card { border: none; border-radius: 0; padding: 0; background: transparent; min-width: 0; }
|
.step-card {border: none; border-radius: 0; padding: 0; background: transparent; min-width: 0;}
|
||||||
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
.step-header {display: flex; align-items: center; gap: 8px; margin-bottom: 6px;}
|
||||||
.title { font-size: 13px; font-weight: 600; color: #303133; text-align: left; }
|
.title {font-size: 13px; font-weight: 600; color: #303133; text-align: left;}
|
||||||
.desc { font-size: 12px; color: #909399; margin-bottom: 8px; text-align: left; }
|
.desc {font-size: 12px; color: #909399; margin-bottom: 8px; text-align: left;}
|
||||||
.mini-hint { font-size: 12px; color: #909399; margin-top: 8px; text-align: left; }
|
.mini-hint {font-size: 12px; color: #909399; margin-top: 8px; text-align: left;}
|
||||||
.links { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
|
.links {display: flex; align-items: center; gap: 6px; margin-bottom: 8px;}
|
||||||
.link { color: #409EFF; cursor: pointer; font-size: 12px; }
|
.link {color: #409EFF; cursor: pointer; font-size: 12px;}
|
||||||
.sep { color: #dcdfe6; }
|
.sep {color: #dcdfe6;}
|
||||||
|
.content-panel {flex: 1; display: flex; flex-direction: column; min-width: 0;}
|
||||||
.content-panel { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
.left-controls {margin-top: 10px; display: flex; flex-direction: column; gap: 10px;}
|
||||||
|
.dropzone {border: 1px dashed #c0c4cc; border-radius: 6px; padding: 12px; text-align: center; cursor: pointer; background: #fafafa;}
|
||||||
.left-controls { margin-top: 10px; display: flex; flex-direction: column; gap: 10px; }
|
.dropzone:hover {background: #f6fbff; border-color: #409EFF;}
|
||||||
.dropzone { border: 1px dashed #c0c4cc; border-radius: 6px; padding: 12px; text-align: center; cursor: pointer; background: #fafafa; }
|
.dropzone.disabled {opacity: .6; cursor: not-allowed;}
|
||||||
.dropzone:hover { background: #f6fbff; border-color: #409EFF; }
|
.dz-el-icon {font-size: 18px; margin-bottom: 4px; color: #909399;}
|
||||||
.dropzone.disabled { opacity: .6; cursor: not-allowed; }
|
.dz-text {color: #303133; font-size: 13px;}
|
||||||
.dz-el-icon { font-size: 18px; margin-bottom: 4px; color: #909399; }
|
.dz-sub {color: #909399; font-size: 12px;}
|
||||||
.dz-text { color: #303133; font-size: 13px; }
|
.single-input.left {display: flex; gap: 8px;}
|
||||||
.dz-sub { color: #909399; font-size: 12px; }
|
.action-buttons.column {display: flex; flex-direction: column; gap: 8px;}
|
||||||
.single-input.left { display: flex; gap: 8px; }
|
.file-chip {display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; color: #606266; margin-top: 6px; width: 100%; box-sizing: border-box;}
|
||||||
.action-buttons.column { display: flex; flex-direction: column; gap: 8px; }
|
.file-chip .dot {width: 6px; height: 6px; background: #409EFF; border-radius: 50%; flex-shrink: 0;}
|
||||||
|
.file-chip .name {flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}
|
||||||
.file-chip { display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #f5f7fa; border-radius: 4px; font-size: 12px; color: #606266; margin-top: 6px; width: 100%; box-sizing: border-box; }
|
.file-chip .delete-btn {cursor: pointer; opacity: 0.6; flex-shrink: 0;}
|
||||||
.file-chip .dot { width: 6px; height: 6px; background: #409EFF; border-radius: 50%; flex-shrink: 0; }
|
.file-chip .delete-btn:hover {opacity: 1;}
|
||||||
.file-chip .name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.progress-section.left {margin-top: 10px;}
|
||||||
.file-chip .delete-btn { cursor: pointer; opacity: 0.6; flex-shrink: 0; }
|
.full {width: 100%;}
|
||||||
.file-chip .delete-btn:hover { opacity: 1; }
|
.form-row {margin-bottom: 10px;}
|
||||||
|
.label {display: block; font-size: 12px; color: #606266; margin-bottom: 6px;}
|
||||||
.progress-section.left { margin-top: 10px; }
|
|
||||||
.full { width: 100%; }
|
|
||||||
.form-row { margin-bottom: 10px; }
|
|
||||||
.label { display: block; font-size: 12px; color: #606266; margin-bottom: 6px; }
|
|
||||||
|
|
||||||
/* 统一左侧控件宽度与主色 */
|
/* 统一左侧控件宽度与主色 */
|
||||||
.steps-sidebar :deep(.el-date-editor),
|
.steps-sidebar :deep(.el-date-editor),
|
||||||
.steps-sidebar :deep(.el-range-editor.el-input__wrapper),
|
.steps-sidebar :deep(.el-range-editor.el-input__wrapper),
|
||||||
.steps-sidebar :deep(.el-input),
|
.steps-sidebar :deep(.el-input),
|
||||||
.steps-sidebar :deep(.el-input__wrapper),
|
.steps-sidebar :deep(.el-input__wrapper),
|
||||||
.steps-sidebar :deep(.el-select) { width: 100%; box-sizing: border-box; }
|
.steps-sidebar :deep(.el-select) {width: 100%; box-sizing: border-box;}
|
||||||
.btn-blue { background: #1677FF; border-color: #1677FF; color: #fff; }
|
.btn-blue {background: #1677FF; border-color: #1677FF; color: #fff;}
|
||||||
.btn-blue:disabled { background: #a6c8ff; border-color: #a6c8ff; color: #fff; }
|
.btn-blue:disabled {background: #a6c8ff; border-color: #a6c8ff; color: #fff;}
|
||||||
.w100 { width: 100%; }
|
.w100 {width: 100%;}
|
||||||
.steps-sidebar :deep(.el-button + .el-button) { margin-left: 0; }
|
.steps-sidebar :deep(.el-button + .el-button) {margin-left: 0;}
|
||||||
.progress-section { margin: 0px 12px 0px 12px; }
|
.progress-section {margin: 0px 12px 0px 12px;}
|
||||||
.progress-box { padding: 4px 0; }
|
.progress-box {padding: 4px 0;}
|
||||||
.progress-container { display: flex; align-items: center; gap: 8px; }
|
.progress-container {display: flex; align-items: center; gap: 8px;}
|
||||||
.progress-bar { flex: 1; height: 6px; background: #e3eeff; border-radius: 999px; overflow: hidden; }
|
.progress-bar {flex: 1; height: 6px; background: #e3eeff; border-radius: 999px; overflow: hidden;}
|
||||||
.progress-fill { height: 100%; background: #1677FF; border-radius: 999px; transition: width 0.3s ease; }
|
.progress-fill {height: 100%; background: #1677FF; border-radius: 999px; transition: width 0.3s ease;}
|
||||||
.progress-text { font-size: 13px; color: #1677FF; font-weight: 500; min-width: 44px; text-align: right; }
|
.progress-text {font-size: 13px; color: #1677FF; font-weight: 500; min-width: 44px; text-align: right;}
|
||||||
|
.current-status {font-size: 12px; color: #606266; padding-left: 2px;}
|
||||||
.current-status {
|
.export-progress {display: flex; align-items: center; gap: 8px; margin-top: 6px; padding: 0 4px;}
|
||||||
font-size: 12px;
|
.export-progress-bar {flex: 1; height: 4px; background: #e3eeff; border-radius: 2px; overflow: hidden;}
|
||||||
color: #606266;
|
.export-progress-fill {height: 100%; background: #1677FF; border-radius: 2px; transition: width 0.3s ease;}
|
||||||
padding-left: 2px;
|
.export-progress-text {font-size: 11px; color: #1677FF; font-weight: 500; min-width: 32px; text-align: right;}
|
||||||
}
|
.table-container {display: flex; flex-direction: column; flex: 1; min-height: 400px; overflow: hidden;}
|
||||||
.export-progress { display: flex; align-items: center; gap: 8px; margin-top: 6px; padding: 0 4px; }
|
.empty-section {flex: 1; display: flex; justify-content: center; align-items: center; background: #fff; border: 1px solid #ebeef5; border-radius: 6px;}
|
||||||
.export-progress-bar { flex: 1; height: 4px; background: #e3eeff; border-radius: 2px; overflow: hidden; }
|
.empty-container {text-align: center;}
|
||||||
.export-progress-fill { height: 100%; background: #1677FF; border-radius: 2px; transition: width 0.3s ease; }
|
.empty-icon {font-size: 48px; margin-bottom: 16px; opacity: 0.6;}
|
||||||
.export-progress-text { font-size: 11px; color: #1677FF; font-weight: 500; min-width: 32px; text-align: right; }
|
.empty-text {font-size: 14px; color: #909399;}
|
||||||
|
.table-section {flex: 1; overflow: hidden; position: relative; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; display: flex; flex-direction: column;}
|
||||||
.table-container {
|
.table-wrapper {flex: 1; overflow: auto;}
|
||||||
display: flex;
|
.table-wrapper {scrollbar-width: thin; scrollbar-color: #c0c4cc transparent;}
|
||||||
flex-direction: column;
|
.table-wrapper::-webkit-scrollbar {width: 6px; height: 6px;}
|
||||||
flex: 1;
|
.table-wrapper::-webkit-scrollbar-track {background: transparent;}
|
||||||
min-height: 400px;
|
.table-wrapper::-webkit-scrollbar-thumb {background: #c0c4cc; border-radius: 3px;}
|
||||||
overflow: hidden;
|
.table-wrapper:hover::-webkit-scrollbar-thumb {background: #a8abb2;}
|
||||||
}
|
.table {width: max-content; min-width: 100%; border-collapse: collapse; font-size: 13px;}
|
||||||
|
.table th {background: #f5f7fa; color: #909399; font-weight: 600; padding: 8px 6px; border-bottom: 2px solid #ebeef5; text-align: left; font-size: 12px; white-space: nowrap;}
|
||||||
.empty-section {
|
.table td {padding: 10px 8px; border-bottom: 1px solid #f0f0f0; vertical-align: middle;}
|
||||||
flex: 1;
|
.table tbody tr:hover {background: #f9f9f9;}
|
||||||
display: flex;
|
.truncate {max-width: 260px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}
|
||||||
justify-content: center;
|
.shop-col {max-width: 160px;}
|
||||||
align-items: center;
|
.url-col {max-width: 220px;}
|
||||||
background: #fff;
|
.empty-tip {text-align: center; color: #909399; padding: 16px 0;}
|
||||||
border: 1px solid #ebeef5;
|
.empty-container {text-align: center;}
|
||||||
border-radius: 6px;
|
.empty-icon {font-size: 48px; margin-bottom: 12px; opacity: 0.6;}
|
||||||
}
|
.empty-text {font-size: 14px; color: #909399;}
|
||||||
|
.import-section.drag-active {border: 1px dashed #409EFF; border-radius: 6px;}
|
||||||
.empty-container {
|
.empty-abs {position: absolute; left: 0; right: 0; top: 48px; bottom: 0; display: flex; align-items: center; justify-content: center; pointer-events: none;}
|
||||||
text-align: center;
|
.image-container {display: flex; justify-content: center; align-items: center; width: 40px; height: 40px; margin: 0 auto; background: #f8f9fa; border-radius: 2px;}
|
||||||
}
|
.thumb {width: 32px; height: 32px; object-fit: contain; border-radius: 2px;}
|
||||||
|
.table-loading {position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266; pointer-events: none;}
|
||||||
.empty-icon {
|
.spinner {font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px;}
|
||||||
font-size: 48px;
|
@keyframes spin {0% {
|
||||||
margin-bottom: 16px;
|
transform: rotate(0deg);}
|
||||||
opacity: 0.6;
|
100% {transform: rotate(360deg);}
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #909399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-section { flex: 1; overflow: hidden; position: relative; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; display: flex; flex-direction: column; }
|
|
||||||
.table-wrapper { flex: 1; overflow: auto; }
|
|
||||||
.table-wrapper { scrollbar-width: thin; scrollbar-color: #c0c4cc transparent; }
|
|
||||||
.table-wrapper::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
||||||
.table-wrapper::-webkit-scrollbar-track { background: transparent; }
|
|
||||||
.table-wrapper::-webkit-scrollbar-thumb { background: #c0c4cc; border-radius: 3px; }
|
|
||||||
.table-wrapper:hover::-webkit-scrollbar-thumb { background: #a8abb2; }
|
|
||||||
.table { width: max-content; min-width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
||||||
|
|
||||||
.table th {
|
|
||||||
background: #f5f7fa;
|
|
||||||
color: #909399;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 8px 6px;
|
|
||||||
border-bottom: 2px solid #ebeef5;
|
|
||||||
text-align: left;
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table td {
|
|
||||||
padding: 10px 8px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table tbody tr:hover {
|
|
||||||
background: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.truncate {
|
|
||||||
max-width: 260px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
.shop-col { max-width: 160px; }
|
|
||||||
.url-col { max-width: 220px; }
|
|
||||||
.empty-tip { text-align: center; color: #909399; padding: 16px 0; }
|
|
||||||
.empty-container { text-align: center; }
|
|
||||||
.empty-icon { font-size: 48px; margin-bottom: 12px; opacity: 0.6; }
|
|
||||||
.empty-text { font-size: 14px; color: #909399; }
|
|
||||||
.import-section.drag-active { border: 1px dashed #409EFF; border-radius: 6px; }
|
|
||||||
.empty-abs { position: absolute; left: 0; right: 0; top: 48px; bottom: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
|
|
||||||
|
|
||||||
.image-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumb {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-loading {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: rgba(255, 255, 255, 0.95);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #606266;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
font-size: 24px;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-fixed {
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 8px 12px 0 12px;
|
|
||||||
background: #fff;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-fixed :deep(.el-pager li.is-active) {
|
|
||||||
border: 1px solid #1677FF;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #1677FF;
|
|
||||||
background: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagination-fixed {flex-shrink: 0; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end;}
|
||||||
|
.pagination-fixed :deep(.el-pager li.is-active) {border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff;}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|||||||
@@ -575,93 +575,94 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.zebra-root { position: absolute; inset: 0; background: #fff; box-sizing: border-box; }
|
.zebra-root {position: absolute; inset: 0; background: #fff; box-sizing: border-box;}
|
||||||
.layout { height: 100%; display: grid; grid-template-columns: 220px 1fr; gap: 12px; padding: 12px; box-sizing: border-box; }
|
.layout {height: 100%; display: grid; grid-template-columns: 220px 1fr; gap: 12px; padding: 12px; box-sizing: border-box;}
|
||||||
.aside { border: 1px solid #ebeef5; border-radius: 4px; padding: 10px; display: flex; flex-direction: column; transition: width 0.2s ease; }
|
.aside {border: 1px solid #ebeef5; border-radius: 4px; padding: 10px; display: flex; flex-direction: column; transition: width 0.2s ease;}
|
||||||
.aside.collapsed { width: 56px; overflow: hidden; }
|
.aside.collapsed {width: 56px; overflow: hidden;}
|
||||||
.aside-header { display: flex; justify-content: flex-start; align-items: center; font-weight: 600; color: #606266; margin-bottom: 8px; }
|
.aside-header {display: flex; justify-content: flex-start; align-items: center; font-weight: 600; color: #606266; margin-bottom: 8px;}
|
||||||
.aside-steps { position: relative; }
|
.aside-steps {position: relative;}
|
||||||
.step { display: grid; grid-template-columns: 22px 1fr; gap: 10px; position: relative; padding: 8px 0; }
|
.step {display: grid; grid-template-columns: 22px 1fr; gap: 10px; position: relative; padding: 8px 0;}
|
||||||
.step-index { width: 22px; height: 22px; background: #1677FF; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; margin-top: 2px; }
|
.step-index {width: 22px; height: 22px; background: #1677FF; color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; margin-top: 2px;}
|
||||||
.step-body { min-width: 0; text-align: left; }
|
.step-body {min-width: 0; text-align: left;}
|
||||||
.step-title { font-size: 13px; color: #606266; margin-bottom: 6px; font-weight: 600; text-align: left; }
|
.step-title {font-size: 13px; color: #606266; margin-bottom: 6px; font-weight: 600; text-align: left;}
|
||||||
.aside-steps:before { content: ''; position: absolute; left: 11px; top: 20px; bottom: 0; width: 1px; background: rgba(229, 231, 235, 0.6); }
|
.aside-steps:before {content: ''; position: absolute; left: 11px; top: 20px; bottom: 0; width: 1px; background: rgba(229, 231, 235, 0.6);}
|
||||||
.account-list {height: auto; }
|
.account-list {height: auto;}
|
||||||
.step-actions { margin-top: 8px; display: flex; gap: 8px; }
|
.step-actions {margin-top: 8px; display: flex; gap: 8px;}
|
||||||
.step-accounts { position: relative; }
|
.step-accounts {position: relative;}
|
||||||
.sticky-actions { position: sticky; bottom: 0; background: #fafafa; padding-top: 8px; }
|
.sticky-actions {position: sticky; bottom: 0; background: #fafafa; padding-top: 8px;}
|
||||||
.scroll-limit { max-height: 160px; }
|
.scroll-limit {max-height: 160px;}
|
||||||
.btn-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
.btn-row {display: grid; grid-template-columns: 1fr 1fr; gap: 8px;}
|
||||||
.btn-col { display: flex; flex-direction: column; gap: 6px; }
|
.btn-col {display: flex; flex-direction: column; gap: 6px;}
|
||||||
.w50 { width: 48%; }
|
.w50 {width: 48%;}
|
||||||
.w100 { width: 100%; }
|
.w100 {width: 100%;}
|
||||||
.placeholder-box { display:flex; align-items:center; justify-content:center; flex-direction:column; height: 140px; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; }
|
.placeholder-box {display:flex; align-items:center; justify-content:center; flex-direction:column; height: 140px; background: #fff; border: 1px solid #ebeef5; border-radius: 4px;}
|
||||||
.placeholder-img { width: 120px; opacity: 0.9; }
|
.placeholder-img {width: 120px; opacity: 0.9;}
|
||||||
.placeholder-tip { margin-top: 6px; font-size: 12px; color: #a8abb2; }
|
.placeholder-tip {margin-top: 6px; font-size: 12px; color: #a8abb2;}
|
||||||
.aside :deep(.el-date-editor) { width: 100%; }
|
.aside :deep(.el-date-editor) {width: 100%;}
|
||||||
.aside :deep(.el-range-editor.el-input__wrapper) { width: 100%; box-sizing: border-box; }
|
.aside :deep(.el-range-editor.el-input__wrapper) {width: 100%; box-sizing: border-box;}
|
||||||
.aside :deep(.el-input),
|
.aside :deep(.el-input),
|
||||||
.aside :deep(.el-input__wrapper),
|
.aside :deep(.el-input__wrapper),
|
||||||
.aside :deep(.el-select) { width: 100%; box-sizing: border-box; }
|
.aside :deep(.el-select) {width: 100%; box-sizing: border-box;}
|
||||||
.aside :deep(.el-button + .el-button) { margin-left: 0 !important; }
|
.aside :deep(.el-button + .el-button) {margin-left: 0 !important;}
|
||||||
.btn-row :deep(.el-button) { width: 100%; }
|
.btn-row :deep(.el-button) {width: 100%;}
|
||||||
.btn-col :deep(.el-button) { width: 100%; }
|
.btn-col :deep(.el-button) {width: 100%;}
|
||||||
.btn-blue { background: #1677FF; border-color: #1677FF; color: #fff; }
|
.btn-blue {background: #1677FF; border-color: #1677FF; color: #fff;}
|
||||||
.btn-blue:disabled { background: #a6c8ff; border-color: #a6c8ff; color: #fff; }
|
.btn-blue:disabled {background: #a6c8ff; border-color: #a6c8ff; color: #fff;}
|
||||||
.tip { color: #909399; font-size: 12px; margin-bottom: 8px; text-align: left; }
|
.tip {color: #909399; font-size: 12px; margin-bottom: 8px; text-align: left;}
|
||||||
.avatar { width: 22px; height: 22px; border-radius: 50%; margin-right: 6px; vertical-align: -2px; }
|
.avatar {width: 22px; height: 22px; border-radius: 50%; margin-right: 6px; vertical-align: -2px;}
|
||||||
.acct-text { vertical-align: middle; }
|
.acct-text {vertical-align: middle;}
|
||||||
.acct-row { display: grid; grid-template-columns: 8px 18px 1fr auto; align-items: center; gap: 6px; width: 100%; }
|
.acct-row {display: grid; grid-template-columns: 8px 18px 1fr auto; align-items: center; gap: 6px; width: 100%;}
|
||||||
.acct-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 12px; }
|
.acct-text {overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-size: 12px;}
|
||||||
.status-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
|
.status-dot {width: 6px; height: 6px; border-radius: 50%; display: inline-block;}
|
||||||
.status-dot.on { background: #22c55e; }
|
.status-dot.on {background: #22c55e;}
|
||||||
.status-dot.off { background: #f87171; }
|
.status-dot.off {background: #f87171;}
|
||||||
.acct-item { padding: 6px 8px; border-radius: 8px; cursor: pointer; }
|
.acct-item {padding: 6px 8px; border-radius: 8px; cursor: pointer;}
|
||||||
.acct-item.selected { background: #eef5ff; box-shadow: inset 0 0 0 1px #d6e4ff; }
|
.acct-item.selected {background: #eef5ff; box-shadow: inset 0 0 0 1px #d6e4ff;}
|
||||||
.acct-check { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; background: transparent; color: #111; font-size: 14px; }
|
.acct-check {display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 50%; background: transparent; color: #111; font-size: 14px;}
|
||||||
.account-list::-webkit-scrollbar { width: 0; height: 0; }
|
.account-list::-webkit-scrollbar {width: 0; height: 0;}
|
||||||
.add-account-dialog .aad-header { display:flex; flex-direction: column; align-items:center; gap:8px; padding-top: 8px; width: 100%; }
|
.add-account-dialog .aad-header {display:flex; flex-direction: column; align-items:center; gap:8px; padding-top: 8px; width: 100%;}
|
||||||
.add-account-dialog .aad-icon { width: 120px; height: auto; }
|
.add-account-dialog .aad-icon {width: 120px; height: auto;}
|
||||||
.add-account-dialog .aad-title { font-weight: 600; font-size: 18px; text-align: center; }
|
.add-account-dialog .aad-title {font-weight: 600; font-size: 18px; text-align: center;}
|
||||||
.add-account-dialog .aad-row { margin-top: 12px; }
|
.add-account-dialog .aad-row {margin-top: 12px;}
|
||||||
.add-account-dialog .aad-opts { display:flex; align-items:center; }
|
.add-account-dialog .aad-opts {display:flex; align-items:center;}
|
||||||
|
|
||||||
/* 居中 header,避免右上角关闭按钮影响视觉中心 */
|
/* 居中 header,避免右上角关闭按钮影响视觉中心 */
|
||||||
:deep(.add-account-dialog .el-dialog__header) { text-align: center; padding-right: 0; display: block; }
|
:deep(.add-account-dialog .el-dialog__header) {text-align: center; padding-right: 0; display: block;}
|
||||||
.content { display: grid; grid-template-rows: 1fr auto; min-height: 0; }
|
.content {display: grid; grid-template-rows: 1fr auto; min-height: 0;}
|
||||||
.table-section { min-height: 0; overflow: hidden; position: relative; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; display: flex; flex-direction: column; }
|
.table-section {min-height: 0; overflow: hidden; position: relative; background: #fff; border: 1px solid #ebeef5; border-radius: 4px; display: flex; flex-direction: column;}
|
||||||
.table-wrapper { flex: 1; overflow: auto; overflow-x: auto; }
|
.table-wrapper {flex: 1; overflow: auto; overflow-x: auto;}
|
||||||
.table-wrapper { scrollbar-width: thin; scrollbar-color: #c0c4cc transparent; }
|
.table-wrapper {scrollbar-width: thin; scrollbar-color: #c0c4cc transparent;}
|
||||||
.table-wrapper::-webkit-scrollbar { width: 6px; height: 6px; }
|
.table-wrapper::-webkit-scrollbar {width: 6px; height: 6px;}
|
||||||
.table-wrapper::-webkit-scrollbar-track { background: transparent; }
|
.table-wrapper::-webkit-scrollbar-track {background: transparent;}
|
||||||
.table-wrapper::-webkit-scrollbar-thumb { background: #c0c4cc; border-radius: 3px; }
|
.table-wrapper::-webkit-scrollbar-thumb {background: #c0c4cc; border-radius: 3px;}
|
||||||
.table-wrapper:hover::-webkit-scrollbar-thumb { background: #a8abb2; }
|
.table-wrapper:hover::-webkit-scrollbar-thumb {background: #a8abb2;}
|
||||||
.table { width: max-content; min-width: 100%; border-collapse: collapse; font-size: 13px; }
|
.table {width: max-content; min-width: 100%; border-collapse: collapse; font-size: 13px;}
|
||||||
.table th { background: #f5f7fa; color: #909399; font-weight: 600; padding: 12px 8px; border-bottom: 2px solid #ebeef5; text-align: left; white-space: nowrap; }
|
.table th {background: #f5f7fa; color: #909399; font-weight: 600; padding: 12px 8px; border-bottom: 2px solid #ebeef5; text-align: left; white-space: nowrap;}
|
||||||
.table td { padding: 10px 8px; border-bottom: 1px solid #f0f0f0; vertical-align: middle; }
|
.table td {padding: 10px 8px; border-bottom: 1px solid #f0f0f0; vertical-align: middle;}
|
||||||
.table tbody tr:hover { background: #f9f9f9; }
|
.table tbody tr:hover {background: #f9f9f9;}
|
||||||
.truncate { max-width: 180px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.truncate {max-width: 180px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}
|
||||||
.image-container { display: flex; justify-content: center; align-items: center; width: 28px; height: 24px; margin: 0 auto; background: #f8f9fa; border-radius: 2px; }
|
.image-container {display: flex; justify-content: center; align-items: center; width: 28px; height: 24px; margin: 0 auto; background: #f8f9fa; border-radius: 2px;}
|
||||||
.thumb { width: 22px; height: 22px; object-fit: contain; border-radius: 2px; }
|
.thumb {width: 22px; height: 22px; object-fit: contain; border-radius: 2px;}
|
||||||
.price-tag { color: #e6a23c; font-weight: bold; }
|
.price-tag {color: #e6a23c; font-weight: bold;}
|
||||||
.fee-tag { color: #909399; font-weight: 500; }
|
.fee-tag {color: #909399; font-weight: 500;}
|
||||||
.table-loading { position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266; }
|
.table-loading {position: absolute; inset: 0; background: rgba(255, 255, 255, 0.95); display: flex; flex-direction: column; justify-content: center; align-items: center; font-size: 14px; color: #606266;}
|
||||||
.spinner { font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px; }
|
.spinner {font-size: 24px; animation: spin 1s linear infinite; margin-bottom: 8px;}
|
||||||
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
@keyframes spin {0% { transform: rotate(0deg);}
|
||||||
.pagination-fixed { position: sticky; bottom: 0; z-index: 2; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end; }
|
100% {transform: rotate(360deg);}
|
||||||
.pagination-fixed :deep(.el-pager li.is-active) { border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff; }
|
}
|
||||||
.tag { display: inline-block; padding: 0 6px; margin-left: 6px; font-size: 12px; background: #ecf5ff; color: #409EFF; border-radius: 3px; }
|
.pagination-fixed {position: sticky; bottom: 0; z-index: 2; padding: 8px 12px 0 12px; background: #fff; display: flex; justify-content: flex-end;}
|
||||||
.empty-abs { position: absolute; left: 0; right: 0; top: 48px; bottom: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
|
.pagination-fixed :deep(.el-pager li.is-active) {border: 1px solid #1677FF; border-radius: 4px; color: #1677FF; background: #fff;}
|
||||||
.progress-section { margin: 0px 12px 0px 12px; }
|
.tag {display: inline-block; padding: 0 6px; margin-left: 6px; font-size: 12px; background: #ecf5ff; color: #409EFF; border-radius: 3px;}
|
||||||
.progress-box { padding: 4px 0; }
|
.empty-abs {position: absolute; left: 0; right: 0; top: 48px; bottom: 0; display: flex; align-items: center; justify-content: center; pointer-events: none;}
|
||||||
.progress-container { display: flex; align-items: center; gap: 8px; }
|
.progress-section {margin: 0px 12px 0px 12px;}
|
||||||
.progress-bar { flex: 1; height: 6px; background: #e3eeff; border-radius: 999px; overflow: hidden; }
|
.progress-box {padding: 4px 0;}
|
||||||
.progress-fill { height: 100%; background: #1677FF; border-radius: 999px; transition: width 0.3s ease; }
|
.progress-container {display: flex; align-items: center; gap: 8px;}
|
||||||
.progress-text { font-size: 13px; color: #1677FF; font-weight: 500; min-width: 44px; text-align: right; }
|
.progress-bar {flex: 1; height: 6px; background: #e3eeff; border-radius: 999px; overflow: hidden;}
|
||||||
.export-progress { display: flex; align-items: center; gap: 8px; margin-top: 6px; padding: 0 4px; }
|
.progress-fill {height: 100%; background: #1677FF; border-radius: 999px; transition: width 0.3s ease;}
|
||||||
.export-progress-bar { flex: 1; height: 4px; background: #e3eeff; border-radius: 2px; overflow: hidden; }
|
.progress-text {font-size: 13px; color: #1677FF; font-weight: 500; min-width: 44px; text-align: right;}
|
||||||
.export-progress-fill { height: 100%; background: #67c23a; border-radius: 2px; transition: width 0.3s ease; }
|
.export-progress {display: flex; align-items: center; gap: 8px; margin-top: 6px; padding: 0 4px;}
|
||||||
.export-progress-text { font-size: 11px; color: #67c23a; font-weight: 500; min-width: 32px; text-align: right; }
|
.export-progress-bar {flex: 1; height: 4px; background: #e3eeff; border-radius: 2px; overflow: hidden;}
|
||||||
|
.export-progress-fill {height: 100%; background: #67c23a; border-radius: 2px; transition: width 0.3s ease;}
|
||||||
|
.export-progress-text {font-size: 11px; color: #67c23a; font-weight: 500; min-width: 32px; text-align: right;}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
20
electron-vue-template/src/renderer/config/index.ts
Normal file
20
electron-vue-template/src/renderer/config/index.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* 应用配置
|
||||||
|
*/
|
||||||
|
export const AppConfig = {
|
||||||
|
CLIENT_BASE: 'http://localhost:8081',
|
||||||
|
RUOYI_BASE: 'http://192.168.1.89:8085',
|
||||||
|
get SSE_URL() {
|
||||||
|
return `${this.RUOYI_BASE}/monitor/account/events`
|
||||||
|
}
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断路径是否路由到ruoyi-admin服务
|
||||||
|
*/
|
||||||
|
export function isRuoyiPath(path: string): boolean {
|
||||||
|
return path.startsWith('/monitor/') ||
|
||||||
|
path.startsWith('/system/') ||
|
||||||
|
path.startsWith('/tool/banma') ||
|
||||||
|
path.startsWith('/tool/genmai')
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.tashow.erp.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1688业务常量
|
||||||
|
*/
|
||||||
|
public class Alibaba1688Constants {
|
||||||
|
public static final String APP_ID = "32517";
|
||||||
|
public static final String INTERFACE_NAME = "imageOfferSearchService";
|
||||||
|
public static final String APP_NAME = "ios";
|
||||||
|
public static final String SEARCH_SCENE = "image";
|
||||||
|
public static final String SEO_SCENE = "seoSearch";
|
||||||
|
public static final int PAGE_SIZE = 40;
|
||||||
|
public static final String JSV_VERSION = "2.6.1";
|
||||||
|
public static final String API_VERSION = "2.0";
|
||||||
|
public static final String DATA_TYPE = "json";
|
||||||
|
public static final int TIMEOUT_MS = 10000;
|
||||||
|
public static final String API_BASE = "https://h5api.m.1688.com/h5";
|
||||||
|
public static final String API_METHOD = "mtop.relationrecommend.WirelessRecommend.recommend";
|
||||||
|
|
||||||
|
private Alibaba1688Constants() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.tashow.erp.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 亚马逊业务常量
|
||||||
|
*/
|
||||||
|
public class AmazonConstants {
|
||||||
|
public static final String REGION_JP = "JP";
|
||||||
|
public static final String REGION_US = "US";
|
||||||
|
public static final String DOMAIN_JP = "https://www.amazon.co.jp";
|
||||||
|
public static final String DOMAIN_US = "https://www.amazon.com";
|
||||||
|
public static final String URL_PRODUCT_PATH = "/dp/";
|
||||||
|
public static final String SESSION_PREFIX = "SINGLE_";
|
||||||
|
public static final String DATA_TYPE = "AMAZON";
|
||||||
|
public static final int RETRY_TIMES = 3;
|
||||||
|
public static final int SLEEP_TIME_BASE = 2000;
|
||||||
|
public static final int SLEEP_TIME_RANDOM = 2000;
|
||||||
|
public static final int TIMEOUT_MS = 20000;
|
||||||
|
public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36";
|
||||||
|
public static final String HEADER_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8";
|
||||||
|
public static final String HEADER_ACCEPT_ENCODING = "gzip, deflate, br";
|
||||||
|
public static final String HEADER_ACCEPT_LANGUAGE_US = "zh-CN,zh;q=0.9,en;q=0.8";
|
||||||
|
public static final String HEADER_ACCEPT_LANGUAGE_JP = "ja,en;q=0.9,zh-CN;q=0.8";
|
||||||
|
public static final String HEADER_CACHE_CONTROL = "max-age=0";
|
||||||
|
public static final String COOKIE_I18N_PREFS_US = "USD";
|
||||||
|
public static final String COOKIE_I18N_PREFS_JP = "JPY";
|
||||||
|
public static final String COOKIE_LC_US = "en_US";
|
||||||
|
public static final String COOKIE_LC_JP = "zh_CN";
|
||||||
|
public static final String COOKIE_SESSION_ID_US = "134-6097934-2082600";
|
||||||
|
public static final String COOKIE_SESSION_ID_JP = "358-1261309-0483141";
|
||||||
|
public static final String COOKIE_SESSION_ID_TIME = "2082787201l";
|
||||||
|
public static final String COOKIE_UBID_US = "132-7547587-3056927";
|
||||||
|
public static final String COOKIE_UBID_JP = "357-8224002-9668932";
|
||||||
|
public static final String COOKIE_SKIN = "noskin";
|
||||||
|
|
||||||
|
private AmazonConstants() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.tashow.erp.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 斑马业务常量
|
||||||
|
*/
|
||||||
|
public class BanmaConstants {
|
||||||
|
public static final String API_BASE = "https://banma365.cn";
|
||||||
|
public static final String API_ORDER_LIST = API_BASE + "/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
|
||||||
|
public static final String API_ORDER_LIST_WITH_TIME = API_BASE + "/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&orderedAtStart=%s&orderedAtEnd=%s&_t=%d";
|
||||||
|
public static final String API_TRACKING = API_BASE + "/zebraExpressHub/web/tracking/getByExpressNumber/%s";
|
||||||
|
public static final String API_SHOP_LIST = API_BASE + "/api/shop/list?_t=%d";
|
||||||
|
public static final int CONNECT_TIMEOUT_SECONDS = 5;
|
||||||
|
public static final int READ_TIMEOUT_SECONDS = 10;
|
||||||
|
public static final String DATA_TYPE = "BANMA";
|
||||||
|
public static final String DATA_TYPE_CACHE = "BANMA_CACHE";
|
||||||
|
public static final String TRACKING_PREFIX_ORDER = "ORDER_";
|
||||||
|
public static final String TRACKING_PREFIX_PRODUCT = "PRODUCT_";
|
||||||
|
public static final String TRACKING_PREFIX_UNKNOWN = "UNKNOWN_";
|
||||||
|
public static final String SESSION_PREFIX = "SESSION_";
|
||||||
|
public static final int CACHE_HOURS = 1;
|
||||||
|
|
||||||
|
private BanmaConstants() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.tashow.erp.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存相关常量
|
||||||
|
*/
|
||||||
|
public class CacheConstants {
|
||||||
|
public static final int DATA_RETENTION_HOURS = 1;
|
||||||
|
public static final int TRADEMARK_CACHE_DAYS = 1;
|
||||||
|
public static final int SESSION_LIMIT = 1;
|
||||||
|
public static final int RAKUTEN_CACHE_HOURS = 1;
|
||||||
|
|
||||||
|
private CacheConstants() {}
|
||||||
|
}
|
||||||
@@ -1,169 +1,16 @@
|
|||||||
package com.tashow.erp.common;
|
package com.tashow.erp.common;
|
||||||
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用常量信息
|
* 通用常量(保留兼容性)
|
||||||
*
|
* 新代码请使用具体业务常量类:AmazonConstants、RakutenConstants、HttpConstants等
|
||||||
* @author ruoyi
|
|
||||||
*/
|
|
||||||
public class Constants
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* UTF-8 字符集
|
|
||||||
*/
|
|
||||||
public static final String UTF8 = "UTF-8";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GBK 字符集
|
|
||||||
*/
|
|
||||||
public static final String GBK = "GBK";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统语言
|
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public class Constants {
|
||||||
|
public static final String HTTP = HttpConstants.HTTP;
|
||||||
|
public static final String HTTPS = HttpConstants.HTTPS;
|
||||||
public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
|
public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE;
|
||||||
|
|
||||||
/**
|
private Constants() {}
|
||||||
* www主域
|
|
||||||
*/
|
|
||||||
public static final String WWW = "www.";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* http请求
|
|
||||||
*/
|
|
||||||
public static final String HTTP = "http://";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https请求
|
|
||||||
*/
|
|
||||||
public static final String HTTPS = "https://";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用成功标识
|
|
||||||
*/
|
|
||||||
public static final String SUCCESS = "0";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用失败标识
|
|
||||||
*/
|
|
||||||
public static final String FAIL = "1";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录成功
|
|
||||||
*/
|
|
||||||
public static final String LOGIN_SUCCESS = "Success";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注销
|
|
||||||
*/
|
|
||||||
public static final String LOGOUT = "Logout";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册
|
|
||||||
*/
|
|
||||||
public static final String REGISTER = "Register";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录失败
|
|
||||||
*/
|
|
||||||
public static final String LOGIN_FAIL = "Error";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所有权限标识
|
|
||||||
*/
|
|
||||||
public static final String ALL_PERMISSION = "*:*:*";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 管理员角色权限标识
|
|
||||||
*/
|
|
||||||
public static final String SUPER_ADMIN = "admin";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 角色权限分隔符
|
|
||||||
*/
|
|
||||||
public static final String ROLE_DELIMETER = ",";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权限标识分隔符
|
|
||||||
*/
|
|
||||||
public static final String PERMISSION_DELIMETER = ",";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证码有效期(分钟)
|
|
||||||
*/
|
|
||||||
public static final Integer CAPTCHA_EXPIRATION = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌
|
|
||||||
*/
|
|
||||||
public static final String TOKEN = "token";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌前缀
|
|
||||||
*/
|
|
||||||
public static final String TOKEN_PREFIX = "Bearer ";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌前缀
|
|
||||||
*/
|
|
||||||
public static final String LOGIN_USER_KEY = "login_user_key";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户ID
|
|
||||||
*/
|
|
||||||
public static final String JWT_USERID = "userid";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户头像
|
|
||||||
*/
|
|
||||||
public static final String JWT_AVATAR = "avatar";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建时间
|
|
||||||
*/
|
|
||||||
public static final String JWT_CREATED = "created";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户权限
|
|
||||||
*/
|
|
||||||
public static final String JWT_AUTHORITIES = "authorities";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 资源映射路径 前缀
|
|
||||||
*/
|
|
||||||
public static final String RESOURCE_PREFIX = "/profile";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RMI 远程方法调用
|
|
||||||
*/
|
|
||||||
public static final String LOOKUP_RMI = "rmi:";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LDAP 远程方法调用
|
|
||||||
*/
|
|
||||||
public static final String LOOKUP_LDAP = "ldap:";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LDAPS 远程方法调用
|
|
||||||
*/
|
|
||||||
public static final String LOOKUP_LDAPS = "ldaps:";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
|
|
||||||
*/
|
|
||||||
public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
|
|
||||||
*/
|
|
||||||
public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 定时任务违规的字符
|
|
||||||
*/
|
|
||||||
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
|
|
||||||
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.tashow.erp.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 方舟精选业务常量
|
||||||
|
*/
|
||||||
|
public class FangzhouConstants {
|
||||||
|
public static final String API_URL = "https://api.fangzhoujingxuan.com/Task";
|
||||||
|
public static final String API_SECRET = "e10adc3949ba59abbe56e057f20f883e";
|
||||||
|
public static final int TOKEN_EXPIRED_CODE = -1006;
|
||||||
|
public static final String WEBSITE_CODE = "1";
|
||||||
|
public static final int SUCCESS_CODE = 1;
|
||||||
|
|
||||||
|
private FangzhouConstants() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.tashow.erp.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP协议常量
|
||||||
|
*/
|
||||||
|
public class HttpConstants {
|
||||||
|
public static final String HTTP = "http://";
|
||||||
|
public static final String HTTPS = "https://";
|
||||||
|
public static final String CHARSET_UTF8 = "UTF-8";
|
||||||
|
public static final String CHARSET_GBK = "GBK";
|
||||||
|
|
||||||
|
private HttpConstants() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.tashow.erp.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 乐天业务常量
|
||||||
|
*/
|
||||||
|
public class RakutenConstants {
|
||||||
|
public static final String DATA_TYPE = "RAKUTEN";
|
||||||
|
public static final String DOMAIN = "https://item.rakuten.co.jp";
|
||||||
|
public static final int RETRY_TIMES = 3;
|
||||||
|
public static final int SLEEP_TIME_BASE = 2000;
|
||||||
|
public static final int SLEEP_TIME_RANDOM = 2000;
|
||||||
|
public static final int TIMEOUT_MS = 20000;
|
||||||
|
public static final String DATA_TYPE_CACHE = "RAKUTEN_CACHE";
|
||||||
|
|
||||||
|
private RakutenConstants() {}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
package com.tashow.erp.controller;
|
package com.tashow.erp.controller;
|
||||||
|
|
||||||
|
import com.tashow.erp.common.RakutenConstants;
|
||||||
import com.tashow.erp.model.RakutenProduct;
|
import com.tashow.erp.model.RakutenProduct;
|
||||||
import com.tashow.erp.model.SearchResult;
|
import com.tashow.erp.model.SearchResult;
|
||||||
import com.tashow.erp.service.Alibaba1688Service;
|
import com.tashow.erp.service.Alibaba1688Service;
|
||||||
@@ -14,6 +16,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,7 +84,7 @@ public class RakutenController {
|
|||||||
}
|
}
|
||||||
int cachedCount = allProducts.size() - newProducts.size();
|
int cachedCount = allProducts.size() - newProducts.size();
|
||||||
if (cachedCount > 0) {
|
if (cachedCount > 0) {
|
||||||
dataReportUtil.reportDataCollection("RAKUTEN_CACHE", cachedCount, "0");
|
dataReportUtil.reportDataCollection(RakutenConstants.DATA_TYPE_CACHE, cachedCount, "0");
|
||||||
}
|
}
|
||||||
return JsonData.buildSuccess(Map.of(
|
return JsonData.buildSuccess(Map.of(
|
||||||
"products", allProducts,
|
"products", allProducts,
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ public class Alibaba1688ServiceImpl implements Alibaba1688Service {
|
|||||||
private final RestTemplate noSslRestTemplate = createNoSslRestTemplate();
|
private final RestTemplate noSslRestTemplate = createNoSslRestTemplate();
|
||||||
@Autowired
|
@Autowired
|
||||||
private ErrorReporter errorReporter;
|
private ErrorReporter errorReporter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建忽略SSL证书的RestTemplate
|
||||||
|
*/
|
||||||
private RestTemplate createNoSslRestTemplate() {
|
private RestTemplate createNoSslRestTemplate() {
|
||||||
try {
|
try {
|
||||||
TrustManager[] trustManagers = new TrustManager[] {
|
TrustManager[] trustManagers = new TrustManager[] {
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package com.tashow.erp.service.impl;
|
package com.tashow.erp.service.impl;
|
||||||
|
|
||||||
|
import com.tashow.erp.common.AmazonConstants;
|
||||||
|
import com.tashow.erp.common.CacheConstants;
|
||||||
import com.tashow.erp.entity.AmazonProductEntity;
|
import com.tashow.erp.entity.AmazonProductEntity;
|
||||||
import com.tashow.erp.repository.AmazonProductRepository;
|
import com.tashow.erp.repository.AmazonProductRepository;
|
||||||
import com.tashow.erp.service.AmazonScrapingService;
|
import com.tashow.erp.service.AmazonScrapingService;
|
||||||
import com.tashow.erp.utils.DataReportUtil;
|
import com.tashow.erp.utils.DataReportUtil;
|
||||||
import com.tashow.erp.utils.ErrorReporter;
|
import com.tashow.erp.utils.ErrorReporter;
|
||||||
import com.tashow.erp.utils.RakutenProxyUtil;
|
import com.tashow.erp.utils.RakutenProxyUtil;
|
||||||
|
import com.tashow.erp.utils.UrlBuilder;
|
||||||
|
import com.tashow.erp.utils.ValidationUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import us.codecraft.webmagic.Page;
|
import us.codecraft.webmagic.Page;
|
||||||
@@ -12,13 +17,13 @@ import us.codecraft.webmagic.Site;
|
|||||||
import us.codecraft.webmagic.Spider;
|
import us.codecraft.webmagic.Spider;
|
||||||
import us.codecraft.webmagic.processor.PageProcessor;
|
import us.codecraft.webmagic.processor.PageProcessor;
|
||||||
import us.codecraft.webmagic.selector.Html;
|
import us.codecraft.webmagic.selector.Html;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
/**
|
/**
|
||||||
* 亚马逊数据采集服务实现类
|
* 亚马逊数据采集服务实现
|
||||||
*
|
* 负责批量采集亚马逊商品信息并缓存
|
||||||
* @author ruoyi
|
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class AmazonScrapingServiceImpl implements AmazonScrapingService, PageProcessor {
|
public class AmazonScrapingServiceImpl implements AmazonScrapingService, PageProcessor {
|
||||||
@@ -42,7 +47,7 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
|||||||
String url = page.getUrl().toString();
|
String url = page.getUrl().toString();
|
||||||
// 提取ASIN
|
// 提取ASIN
|
||||||
String asin = html.xpath("//input[@id='ASIN']/@value").toString();
|
String asin = html.xpath("//input[@id='ASIN']/@value").toString();
|
||||||
if (isEmpty(asin)) {
|
if (ValidationUtils.isEmpty(asin)) {
|
||||||
String[] parts = url.split("/dp/");
|
String[] parts = url.split("/dp/");
|
||||||
if (parts.length > 1) asin = parts[1].split("/")[0].split("\\?")[0];
|
if (parts.length > 1) asin = parts[1].split("/")[0].split("\\?")[0];
|
||||||
}
|
}
|
||||||
@@ -50,33 +55,33 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
|||||||
String priceSymbol = html.xpath("//span[@class='a-price-symbol']/text()").toString();
|
String priceSymbol = html.xpath("//span[@class='a-price-symbol']/text()").toString();
|
||||||
String priceWhole = html.xpath("//span[@class='a-price-whole']/text()").toString();
|
String priceWhole = html.xpath("//span[@class='a-price-whole']/text()").toString();
|
||||||
String price = null;
|
String price = null;
|
||||||
if (!isEmpty(priceSymbol) && !isEmpty(priceWhole)) {
|
if (ValidationUtils.isNotEmpty(priceSymbol) && ValidationUtils.isNotEmpty(priceWhole)) {
|
||||||
price = priceSymbol + priceWhole;
|
price = priceSymbol + priceWhole;
|
||||||
}
|
}
|
||||||
if (isEmpty(price)) {
|
if (ValidationUtils.isEmpty(price)) {
|
||||||
price = html.xpath("//span[@class='a-price-range']/text()").toString();
|
price = html.xpath("//span[@class='a-price-range']/text()").toString();
|
||||||
}
|
}
|
||||||
// 提取卖家
|
// 提取卖家
|
||||||
String seller = html.xpath("//a[@id='sellerProfileTriggerId']/text()").toString();
|
String seller = html.xpath("//a[@id='sellerProfileTriggerId']/text()").toString();
|
||||||
if (isEmpty(seller)) {
|
if (ValidationUtils.isEmpty(seller)) {
|
||||||
seller = html.xpath("//span[@class='a-size-small offer-display-feature-text-message']/text()").toString();
|
seller = html.xpath("//span[@class='a-size-small offer-display-feature-text-message']/text()").toString();
|
||||||
}
|
}
|
||||||
// 关键数据为空时重试
|
// 关键数据为空时重试
|
||||||
if (isEmpty(price) && isEmpty(seller)) {
|
if (ValidationUtils.isEmpty(price) && ValidationUtils.isEmpty(seller)) {
|
||||||
throw new RuntimeException("Retry this page");
|
throw new RuntimeException("Retry this page");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEmpty(price)) errorReporter.reportDataEmpty("amazon", asin, price);
|
if (ValidationUtils.isEmpty(price)) errorReporter.reportDataEmpty(AmazonConstants.DATA_TYPE.toLowerCase(), asin, price);
|
||||||
if (isEmpty(seller)) errorReporter.reportDataEmpty("amazon", asin, seller);
|
if (ValidationUtils.isEmpty(seller)) errorReporter.reportDataEmpty(AmazonConstants.DATA_TYPE.toLowerCase(), asin, seller);
|
||||||
|
|
||||||
AmazonProductEntity entity = new AmazonProductEntity();
|
AmazonProductEntity entity = new AmazonProductEntity();
|
||||||
entity.setAsin(asin == null ? "" : asin);
|
entity.setAsin(asin == null ? "" : asin);
|
||||||
|
|
||||||
entity.setPrice(price);
|
entity.setPrice(price);
|
||||||
entity.setSeller(seller);
|
entity.setSeller(seller);
|
||||||
resultCache.put(asin, entity);
|
resultCache.put(asin, entity);
|
||||||
page.putField("entity", entity);
|
page.putField("entity", entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取WebMagic站点配置
|
* 获取WebMagic站点配置
|
||||||
*/
|
*/
|
||||||
@@ -86,31 +91,30 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量获取产品信息
|
* 批量获取亚马逊商品信息
|
||||||
|
* 优先从缓存读取,缓存未命中时实时采集
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<AmazonProductEntity> batchGetProductInfo(List<String> asinList, String batchId, String region) {
|
public List<AmazonProductEntity> batchGetProductInfo(List<String> asinList, String batchId, String region) {
|
||||||
String sessionId = (batchId != null) ? batchId : "SINGLE_" + UUID.randomUUID();
|
String sessionId = (batchId != null) ? batchId : AmazonConstants.SESSION_PREFIX + UUID.randomUUID();
|
||||||
LocalDateTime batchTime = LocalDateTime.now();
|
|
||||||
|
|
||||||
resultCache.clear();
|
resultCache.clear();
|
||||||
|
|
||||||
// 第一步:清理1小时前的所有旧数据
|
// 清理过期缓存数据
|
||||||
amazonProductRepository.deleteAllDataBefore(LocalDateTime.now().minusHours(1));
|
amazonProductRepository.deleteAllDataBefore(LocalDateTime.now().minusHours(CacheConstants.DATA_RETENTION_HOURS));
|
||||||
|
|
||||||
// 优化:批次内复用代理检测和工具实例
|
// 批次内复用代理检测和下载器实例
|
||||||
RakutenProxyUtil proxyUtil = new RakutenProxyUtil();
|
RakutenProxyUtil proxyUtil = new RakutenProxyUtil();
|
||||||
String sampleUrl = buildAmazonUrl(region, "B00000000");
|
String sampleUrl = UrlBuilder.buildAmazonUrl(region, "B00000000");
|
||||||
var proxyDownloader = proxyUtil.createProxyDownloader(proxyUtil.detectSystemProxy(sampleUrl));
|
var proxyDownloader = proxyUtil.createProxyDownloader(proxyUtil.detectSystemProxy(sampleUrl));
|
||||||
|
|
||||||
// 第二步:处理每个ASIN
|
// 处理每个ASIN
|
||||||
Map<String, AmazonProductEntity> allProducts = new HashMap<>();
|
Map<String, AmazonProductEntity> allProducts = new HashMap<>();
|
||||||
for (String asin : asinList.stream().distinct().toList()) {
|
for (String asin : asinList.stream().distinct().toList()) {
|
||||||
if (asin == null || asin.trim().isEmpty()) continue;
|
if (asin == null || asin.trim().isEmpty()) continue;
|
||||||
String cleanAsin = asin.replaceAll("[^a-zA-Z0-9]", "");
|
String cleanAsin = asin.replaceAll("[^a-zA-Z0-9]", "");
|
||||||
|
|
||||||
Optional<AmazonProductEntity> cached = amazonProductRepository.findByAsinAndRegion(cleanAsin, region);
|
Optional<AmazonProductEntity> cached = amazonProductRepository.findByAsinAndRegion(cleanAsin, region);
|
||||||
if (cached.isPresent() && !isEmpty(cached.get().getPrice()) && !isEmpty(cached.get().getSeller())) {
|
if (cached.isPresent() && ValidationUtils.isNotEmpty(cached.get().getPrice()) && ValidationUtils.isNotEmpty(cached.get().getSeller())) {
|
||||||
AmazonProductEntity cachedEntity = cached.get();
|
AmazonProductEntity cachedEntity = cached.get();
|
||||||
AmazonProductEntity entity = new AmazonProductEntity();
|
AmazonProductEntity entity = new AmazonProductEntity();
|
||||||
entity.setAsin(cachedEntity.getAsin());
|
entity.setAsin(cachedEntity.getAsin());
|
||||||
@@ -122,7 +126,7 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
|||||||
amazonProductRepository.save(entity);
|
amazonProductRepository.save(entity);
|
||||||
allProducts.put(cleanAsin, entity);
|
allProducts.put(cleanAsin, entity);
|
||||||
} else {
|
} else {
|
||||||
String url = buildAmazonUrl(region, cleanAsin);
|
String url = UrlBuilder.buildAmazonUrl(region, cleanAsin);
|
||||||
this.site = configureSiteForRegion(region);
|
this.site = configureSiteForRegion(region);
|
||||||
synchronized (spiderLock) {
|
synchronized (spiderLock) {
|
||||||
activeSpider = Spider.create(this).addUrl(url).setDownloader(proxyDownloader).thread(1);
|
activeSpider = Spider.create(this).addUrl(url).setDownloader(proxyDownloader).thread(1);
|
||||||
@@ -135,51 +139,38 @@ public class AmazonScrapingServiceImpl implements AmazonScrapingService, PagePro
|
|||||||
entity.setSessionId(sessionId);
|
entity.setSessionId(sessionId);
|
||||||
entity.setUpdatedAt(LocalDateTime.now());
|
entity.setUpdatedAt(LocalDateTime.now());
|
||||||
amazonProductRepository.save(entity);
|
amazonProductRepository.save(entity);
|
||||||
dataReportUtil.reportDataCollection("AMAZON", 1, "0");
|
dataReportUtil.reportDataCollection(AmazonConstants.DATA_TYPE, 1, "0");
|
||||||
allProducts.put(cleanAsin, entity);
|
allProducts.put(cleanAsin, entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ArrayList<>(allProducts.values());
|
return new ArrayList<>(allProducts.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEmpty(String str) {
|
|
||||||
return str == null || str.trim().isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据地区构建Amazon URL
|
* 根据地区配置WebMagic站点
|
||||||
*/
|
|
||||||
private String buildAmazonUrl(String region, String asin) {
|
|
||||||
if ("US".equals(region)) {
|
|
||||||
return "https://www.amazon.com/dp/" + asin;
|
|
||||||
}
|
|
||||||
return "https://www.amazon.co.jp/dp/" + asin;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 根据地区配置Site
|
|
||||||
*/
|
*/
|
||||||
private Site configureSiteForRegion(String region) {
|
private Site configureSiteForRegion(String region) {
|
||||||
boolean isUS = "US".equals(region);
|
boolean isUS = AmazonConstants.REGION_US.equals(region);
|
||||||
return Site.me()
|
return Site.me()
|
||||||
.setRetryTimes(3)
|
.setRetryTimes(AmazonConstants.RETRY_TIMES)
|
||||||
.setSleepTime(2000 + random.nextInt(2000))
|
.setSleepTime(AmazonConstants.SLEEP_TIME_BASE + random.nextInt(AmazonConstants.SLEEP_TIME_RANDOM))
|
||||||
.setTimeOut(20000)
|
.setTimeOut(AmazonConstants.TIMEOUT_MS)
|
||||||
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
|
.setUserAgent(AmazonConstants.USER_AGENT)
|
||||||
.addHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8")
|
.addHeader("accept", AmazonConstants.HEADER_ACCEPT)
|
||||||
.addHeader("accept-encoding", "gzip, deflate, br")
|
.addHeader("accept-encoding", AmazonConstants.HEADER_ACCEPT_ENCODING)
|
||||||
.addHeader("accept-language", isUS ? "zh-CN,zh;q=0.9,en;q=0.8" : "ja,en;q=0.9,zh-CN;q=0.8")
|
.addHeader("accept-language", isUS ? AmazonConstants.HEADER_ACCEPT_LANGUAGE_US : AmazonConstants.HEADER_ACCEPT_LANGUAGE_JP)
|
||||||
.addHeader("cache-control", "max-age=0")
|
.addHeader("cache-control", AmazonConstants.HEADER_CACHE_CONTROL)
|
||||||
.addHeader("referer", isUS ? "https://www.amazon.com/" : "https://www.amazon.co.jp/")
|
.addHeader("referer", isUS ? AmazonConstants.DOMAIN_US + "/" : AmazonConstants.DOMAIN_JP + "/")
|
||||||
.addHeader("sec-fetch-site", "none")
|
.addHeader("sec-fetch-site", "none")
|
||||||
.addHeader("sec-fetch-mode", "navigate")
|
.addHeader("sec-fetch-mode", "navigate")
|
||||||
.addHeader("sec-fetch-user", "?1")
|
.addHeader("sec-fetch-user", "?1")
|
||||||
.addHeader("sec-fetch-dest", "document")
|
.addHeader("sec-fetch-dest", "document")
|
||||||
.addCookie("i18n-prefs", isUS ? "USD" : "JPY")
|
.addCookie("i18n-prefs", isUS ? AmazonConstants.COOKIE_I18N_PREFS_US : AmazonConstants.COOKIE_I18N_PREFS_JP)
|
||||||
.addCookie(isUS ? "lc-main" : "lc-acbjp", isUS ? "en_US" : "zh_CN")
|
.addCookie(isUS ? "lc-main" : "lc-acbjp", isUS ? AmazonConstants.COOKIE_LC_US : AmazonConstants.COOKIE_LC_JP)
|
||||||
.addCookie("session-id", isUS ? "134-6097934-2082600" : "358-1261309-0483141")
|
.addCookie("session-id", isUS ? AmazonConstants.COOKIE_SESSION_ID_US : AmazonConstants.COOKIE_SESSION_ID_JP)
|
||||||
.addCookie("session-id-time", "2082787201l")
|
.addCookie("session-id-time", AmazonConstants.COOKIE_SESSION_ID_TIME)
|
||||||
.addCookie(isUS ? "ubid-main" : "ubid-acbjp", isUS ? "132-7547587-3056927" : "357-8224002-9668932")
|
.addCookie(isUS ? "ubid-main" : "ubid-acbjp", isUS ? AmazonConstants.COOKIE_UBID_US : AmazonConstants.COOKIE_UBID_JP)
|
||||||
.addCookie("skin", "noskin");
|
.addCookie("skin", AmazonConstants.COOKIE_SKIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,10 @@ import org.springframework.stereotype.Service;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证服务实现
|
||||||
|
* 负责客户端信息管理和错误上报
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class AuthServiceImpl {
|
public class AuthServiceImpl {
|
||||||
|
|
||||||
@@ -22,6 +26,9 @@ public class AuthServiceImpl {
|
|||||||
@Getter
|
@Getter
|
||||||
private String clientId = DeviceUtils.generateDeviceId();
|
private String clientId = DeviceUtils.generateDeviceId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端信息
|
||||||
|
*/
|
||||||
public Map<String, Object> getClientInfo() {
|
public Map<String, Object> getClientInfo() {
|
||||||
Map<String, Object> info = new HashMap<>();
|
Map<String, Object> info = new HashMap<>();
|
||||||
info.put("clientId", clientId);
|
info.put("clientId", clientId);
|
||||||
@@ -30,6 +37,9 @@ public class AuthServiceImpl {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上报错误信息
|
||||||
|
*/
|
||||||
public void reportError(String errorType, String errorMessage, Exception e) {
|
public void reportError(String errorType, String errorMessage, Exception e) {
|
||||||
try {
|
try {
|
||||||
Map<String, Object> errorData = new HashMap<>();
|
Map<String, Object> errorData = new HashMap<>();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.tashow.erp.service.impl;
|
package com.tashow.erp.service.impl;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.tashow.erp.common.BanmaConstants;
|
||||||
import com.tashow.erp.entity.BanmaOrderEntity;
|
import com.tashow.erp.entity.BanmaOrderEntity;
|
||||||
import com.tashow.erp.repository.BanmaOrderRepository;
|
import com.tashow.erp.repository.BanmaOrderRepository;
|
||||||
import com.tashow.erp.service.BanmaOrderService;
|
import com.tashow.erp.service.BanmaOrderService;
|
||||||
@@ -15,6 +17,7 @@ import org.springframework.boot.web.client.RestTemplateBuilder;
|
|||||||
import org.springframework.http.*;
|
import org.springframework.http.*;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -25,12 +28,14 @@ import java.util.stream.Collectors;
|
|||||||
*
|
*
|
||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 斑马订单服务实现
|
||||||
|
* 负责订单采集、物流查询和数据管理
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class BanmaOrderServiceImpl implements BanmaOrderService {
|
public class BanmaOrderServiceImpl implements BanmaOrderService {
|
||||||
private static final Logger logger = LoggerUtil.getLogger(BanmaOrderServiceImpl.class);
|
private static final Logger logger = LoggerUtil.getLogger(BanmaOrderServiceImpl.class);
|
||||||
private static final String API_URL = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
|
|
||||||
private static final String API_URL_WITH_TIME = "https://banma365.cn/api/order/list?%srecipientName=&page=%d&size=%d&markFlag=0&state=4&orderedAtStart=%s&orderedAtEnd=%s&_t=%d";
|
|
||||||
private static final String TRACKING_URL = "https://banma365.cn/zebraExpressHub/web/tracking/getByExpressNumber/%s";
|
|
||||||
|
|
||||||
@Value("${api.server.base-url}")
|
@Value("${api.server.base-url}")
|
||||||
private String ruoyiAdminBase;
|
private String ruoyiAdminBase;
|
||||||
@@ -46,16 +51,21 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
|||||||
private String currentBatchSessionId = null;
|
private String currentBatchSessionId = null;
|
||||||
// 物流信息缓存,避免重复查询
|
// 物流信息缓存,避免重复查询
|
||||||
private final Map<String, String> trackingInfoCache = new ConcurrentHashMap<>();
|
private final Map<String, String> trackingInfoCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public BanmaOrderServiceImpl(BanmaOrderRepository banmaOrderRepository, CacheService cacheService, DataReportUtil dataReportUtil, ErrorReporter errorReporter) {
|
public BanmaOrderServiceImpl(BanmaOrderRepository banmaOrderRepository, CacheService cacheService, DataReportUtil dataReportUtil, ErrorReporter errorReporter) {
|
||||||
this.banmaOrderRepository = banmaOrderRepository;
|
this.banmaOrderRepository = banmaOrderRepository;
|
||||||
this.cacheService = cacheService;
|
this.cacheService = cacheService;
|
||||||
this.dataReportUtil = dataReportUtil;
|
this.dataReportUtil = dataReportUtil;
|
||||||
this.errorReporter = errorReporter;
|
this.errorReporter = errorReporter;
|
||||||
RestTemplateBuilder builder = new RestTemplateBuilder();
|
RestTemplateBuilder builder = new RestTemplateBuilder();
|
||||||
builder.connectTimeout(Duration.ofSeconds(5));
|
builder.connectTimeout(Duration.ofSeconds(BanmaConstants.CONNECT_TIMEOUT_SECONDS));
|
||||||
builder.readTimeout(Duration.ofSeconds(10));
|
builder.readTimeout(Duration.ofSeconds(BanmaConstants.READ_TIMEOUT_SECONDS));
|
||||||
restTemplate = builder.build();
|
restTemplate = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从服务器获取认证Token
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void fetchTokenFromServer(Long accountId) {
|
private void fetchTokenFromServer(Long accountId) {
|
||||||
Map<String, Object> resp = restTemplate.getForObject(ruoyiAdminBase + "/tool/banma/accounts", Map.class);
|
Map<String, Object> resp = restTemplate.getForObject(ruoyiAdminBase + "/tool/banma/accounts", Map.class);
|
||||||
@@ -82,7 +92,7 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
|||||||
if (currentAuthToken != null) headers.set("Authorization", currentAuthToken);
|
if (currentAuthToken != null) headers.set("Authorization", currentAuthToken);
|
||||||
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
|
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
|
||||||
|
|
||||||
String url = "https://banma365.cn/api/shop/list?_t=" + System.currentTimeMillis();
|
String url = String.format(BanmaConstants.API_SHOP_LIST, System.currentTimeMillis());
|
||||||
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
|
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
|
||||||
|
|
||||||
return response.getBody() != null ? response.getBody() : new HashMap<>();
|
return response.getBody() != null ? response.getBody() : new HashMap<>();
|
||||||
@@ -113,8 +123,8 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String url = (StringUtils.isEmpty(startDate) || StringUtils.isEmpty(endDate))
|
String url = (StringUtils.isEmpty(startDate) || StringUtils.isEmpty(endDate))
|
||||||
? String.format(API_URL, shopIdsParam, page, pageSize, System.currentTimeMillis())
|
? String.format(BanmaConstants.API_ORDER_LIST, shopIdsParam, page, pageSize, System.currentTimeMillis())
|
||||||
: String.format(API_URL_WITH_TIME, shopIdsParam, page, pageSize, startDate, endDate, System.currentTimeMillis());
|
: String.format(BanmaConstants.API_ORDER_LIST_WITH_TIME, shopIdsParam, page, pageSize, startDate, endDate, System.currentTimeMillis());
|
||||||
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
|
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, Map.class);
|
||||||
if (response.getBody() == null || !Integer.valueOf(0).equals(response.getBody().get("code"))) {
|
if (response.getBody() == null || !Integer.valueOf(0).equals(response.getBody().get("code"))) {
|
||||||
Map<String, Object> errorResult = new HashMap<>();
|
Map<String, Object> errorResult = new HashMap<>();
|
||||||
@@ -134,7 +144,7 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
|||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (!orders.isEmpty()) dataReportUtil.reportDataCollection("BANMA", orders.size(), "0");
|
if (!orders.isEmpty()) dataReportUtil.reportDataCollection(BanmaConstants.DATA_TYPE, orders.size(), "0");
|
||||||
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("orders", orders);
|
result.put("orders", orders);
|
||||||
@@ -152,31 +162,32 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
|||||||
*/
|
*/
|
||||||
private Map processOrderData(Map<String, Object> order) {
|
private Map processOrderData(Map<String, Object> order) {
|
||||||
String trackingNumber = (String) order.get("internationalTrackingNumber");
|
String trackingNumber = (String) order.get("internationalTrackingNumber");
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
if (StringUtils.isNotEmpty(trackingNumber)) {
|
if (StringUtils.isNotEmpty(trackingNumber)) {
|
||||||
LocalDateTime cutoffTime = LocalDateTime.now().minusHours(1);
|
LocalDateTime cutoffTime = LocalDateTime.now().minusHours(BanmaConstants.CACHE_HOURS);
|
||||||
if (banmaOrderRepository.existsByTrackingNumberAndCreatedAtAfter(trackingNumber, cutoffTime)) {
|
if (banmaOrderRepository.existsByTrackingNumberAndCreatedAtAfter(trackingNumber, cutoffTime)) {
|
||||||
return banmaOrderRepository.findLatestByTrackingNumber(trackingNumber)
|
return banmaOrderRepository.findLatestByTrackingNumber(trackingNumber)
|
||||||
.map(entity -> {
|
.map(entity -> {
|
||||||
if (currentBatchSessionId != null && !currentBatchSessionId.equals(entity.getSessionId())) {
|
if (currentBatchSessionId != null && !currentBatchSessionId.equals(entity.getSessionId())) {
|
||||||
entity.setSessionId(currentBatchSessionId);
|
entity.setSessionId(currentBatchSessionId);
|
||||||
entity.setCreatedAt(LocalDateTime.now());
|
|
||||||
entity.setUpdatedAt(LocalDateTime.now());
|
entity.setUpdatedAt(LocalDateTime.now());
|
||||||
banmaOrderRepository.save(entity);
|
banmaOrderRepository.save(entity);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return objectMapper.readValue(entity.getOrderData(), Map.class);
|
return objectMapper.readValue(entity.getOrderData(), Map.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.warn("解析缓存订单数据失败: {}", trackingNumber);
|
||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
} else {
|
} else {
|
||||||
banmaOrderRepository.findByTrackingNumber(trackingNumber)
|
banmaOrderRepository.findByTrackingNumber(trackingNumber).ifPresent(banmaOrderRepository::delete);
|
||||||
.ifPresent(banmaOrderRepository::delete);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建新订单数据
|
// 构建新订单
|
||||||
Map<String, Object> result = new HashMap<>();
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("internationalTrackingNumber", trackingNumber);
|
result.put("internationalTrackingNumber", trackingNumber);
|
||||||
result.put("internationalShippingFee", order.get("internationalShippingFee"));
|
result.put("internationalShippingFee", order.get("internationalShippingFee"));
|
||||||
@@ -188,53 +199,55 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
|||||||
.map(list -> list.get(0))
|
.map(list -> list.get(0))
|
||||||
.ifPresent(subOrder -> extractSubOrderFields(result, subOrder));
|
.ifPresent(subOrder -> extractSubOrderFields(result, subOrder));
|
||||||
|
|
||||||
BanmaOrderEntity entity = new BanmaOrderEntity();
|
|
||||||
String entityTrackingNumber = (String) result.get("internationalTrackingNumber");
|
String entityTrackingNumber = (String) result.get("internationalTrackingNumber");
|
||||||
String shopOrderNumber = (String) result.get("shopOrderNumber");
|
String shopOrderNumber = (String) result.get("shopOrderNumber");
|
||||||
String productTitle = (String) result.get("productTitle");
|
String productTitle = (String) result.get("productTitle");
|
||||||
|
String orderId = String.valueOf(result.get("id"));
|
||||||
|
|
||||||
// 检查并上报空数据
|
// 上报空数据
|
||||||
if (StringUtils.isEmpty(entityTrackingNumber)) errorReporter.reportDataEmpty("banma", String.valueOf(result.get("id")), entityTrackingNumber);
|
if (StringUtils.isEmpty(entityTrackingNumber))
|
||||||
if (StringUtils.isEmpty(shopOrderNumber)) errorReporter.reportDataEmpty("banma", String.valueOf(result.get("id")), shopOrderNumber);
|
errorReporter.reportDataEmpty(BanmaConstants.DATA_TYPE.toLowerCase(), orderId, entityTrackingNumber);
|
||||||
if (StringUtils.isEmpty(productTitle)) errorReporter.reportDataEmpty("banma", String.valueOf(result.get("id")), productTitle);
|
if (StringUtils.isEmpty(shopOrderNumber))
|
||||||
|
errorReporter.reportDataEmpty(BanmaConstants.DATA_TYPE.toLowerCase(), orderId, shopOrderNumber);
|
||||||
|
if (StringUtils.isEmpty(productTitle))
|
||||||
|
errorReporter.reportDataEmpty(BanmaConstants.DATA_TYPE.toLowerCase(), orderId, productTitle);
|
||||||
|
|
||||||
|
// 生成跟踪号
|
||||||
if (StringUtils.isEmpty(entityTrackingNumber)) {
|
if (StringUtils.isEmpty(entityTrackingNumber)) {
|
||||||
if (StringUtils.isNotEmpty(shopOrderNumber)) {
|
if (StringUtils.isNotEmpty(shopOrderNumber)) {
|
||||||
entityTrackingNumber = "ORDER_" + shopOrderNumber;
|
entityTrackingNumber = BanmaConstants.TRACKING_PREFIX_ORDER + shopOrderNumber;
|
||||||
} else if (StringUtils.isNotEmpty(productTitle)) {
|
} else if (StringUtils.isNotEmpty(productTitle)) {
|
||||||
entityTrackingNumber = "PRODUCT_" + Math.abs(productTitle.hashCode());
|
entityTrackingNumber = BanmaConstants.TRACKING_PREFIX_PRODUCT + Math.abs(productTitle.hashCode());
|
||||||
} else {
|
} else {
|
||||||
entityTrackingNumber = "UNKNOWN_" + System.currentTimeMillis();
|
entityTrackingNumber = BanmaConstants.TRACKING_PREFIX_UNKNOWN + System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.setTrackingNumber(entityTrackingNumber);
|
|
||||||
try {
|
|
||||||
entity.setOrderData(objectMapper.writeValueAsString(result));
|
|
||||||
} catch (Exception e) {
|
|
||||||
entity.setOrderData("{}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成会话ID
|
// 生成会话ID
|
||||||
String sessionId = currentBatchSessionId != null ? currentBatchSessionId :
|
String sessionId = currentBatchSessionId != null ? currentBatchSessionId :
|
||||||
Optional.ofNullable((String) result.get("orderedAt"))
|
Optional.ofNullable((String) result.get("orderedAt"))
|
||||||
.filter(orderedAt -> orderedAt.length() >= 10)
|
.filter(orderedAt -> orderedAt.length() >= 10)
|
||||||
.map(orderedAt -> "SESSION_" + orderedAt.substring(0, 10))
|
.map(orderedAt -> BanmaConstants.SESSION_PREFIX + orderedAt.substring(0, 10))
|
||||||
.orElse("SESSION_" + java.time.LocalDate.now().toString());
|
.orElse(BanmaConstants.SESSION_PREFIX + java.time.LocalDate.now());
|
||||||
|
|
||||||
|
// 保存实体
|
||||||
|
BanmaOrderEntity entity = new BanmaOrderEntity();
|
||||||
|
entity.setTrackingNumber(entityTrackingNumber);
|
||||||
entity.setSessionId(sessionId);
|
entity.setSessionId(sessionId);
|
||||||
entity.setCreatedAt(LocalDateTime.now());
|
entity.setCreatedAt(LocalDateTime.now());
|
||||||
entity.setUpdatedAt(LocalDateTime.now());
|
entity.setUpdatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
entity.setOrderData(objectMapper.writeValueAsString(result));
|
||||||
banmaOrderRepository.save(entity);
|
banmaOrderRepository.save(entity);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("保存订单数据失败,跳过: {}", entityTrackingNumber);
|
logger.warn("保存订单失败: {}", entityTrackingNumber, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取子订单字段
|
||||||
|
*/
|
||||||
private void extractSubOrderFields(Map<String, Object> simplifiedOrder, Map<String, Object> subOrder) {
|
private void extractSubOrderFields(Map<String, Object> simplifiedOrder, Map<String, Object> subOrder) {
|
||||||
String[] basicFields = {"orderedAt", "timeSinceOrder", "createdAt", "poTrackingNumber"};
|
String[] basicFields = {"orderedAt", "timeSinceOrder", "createdAt", "poTrackingNumber"};
|
||||||
String[] productFields = {"productTitle", "shopOrderNumber", "priceJpy", "productQuantity", "shippingFeeJpy", "productNumber", "serviceFee", "productImage"};
|
String[] productFields = {"productTitle", "shopOrderNumber", "priceJpy", "productQuantity", "shippingFeeJpy", "productNumber", "serviceFee", "productImage"};
|
||||||
@@ -244,12 +257,16 @@ public class BanmaOrderServiceImpl implements BanmaOrderService {
|
|||||||
Arrays.stream(purchaseFields).forEach(field -> simplifiedOrder.put(field, subOrder.get(field)));
|
Arrays.stream(purchaseFields).forEach(field -> simplifiedOrder.put(field, subOrder.get(field)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取物流信息
|
||||||
|
*/
|
||||||
private String fetchTrackingInfo(String trackingNumber) {
|
private String fetchTrackingInfo(String trackingNumber) {
|
||||||
Map<String, Object> trackInfoMap = (Map<String, Object>) new SagawaExpressSdk().getTrackingInfo(trackingNumber).get("trackInfo");
|
Map<String, Object> trackInfoMap = (Map<String, Object>) new SagawaExpressSdk().getTrackingInfo(trackingNumber).get("trackInfo");
|
||||||
if (trackInfoMap != null) {
|
if (trackInfoMap != null) {
|
||||||
return trackInfoMap.get("dateTime") + " " + trackInfoMap.get("office") + " " + trackInfoMap.get("status");
|
return trackInfoMap.get("dateTime") + " " + trackInfoMap.get("office") + " " + trackInfoMap.get("status");
|
||||||
}
|
}
|
||||||
ResponseEntity<Map> response = restTemplate.getForEntity(String.format(TRACKING_URL, trackingNumber), Map.class);
|
ResponseEntity<Map> response = restTemplate.getForEntity(String.format(BanmaConstants.API_TRACKING, trackingNumber), Map.class);
|
||||||
return Optional.ofNullable(response.getBody())
|
return Optional.ofNullable(response.getBody())
|
||||||
.filter(body -> Integer.valueOf(0).equals(body.get("code")))
|
.filter(body -> Integer.valueOf(0).equals(body.get("code")))
|
||||||
.map(body -> (List<Map<String, Object>>) body.get("data"))
|
.map(body -> (List<Map<String, Object>>) body.get("data"))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.tashow.erp.service.impl;
|
package com.tashow.erp.service.impl;
|
||||||
|
|
||||||
|
import com.tashow.erp.common.CacheConstants;
|
||||||
import com.tashow.erp.entity.BrandTrademarkCacheEntity;
|
import com.tashow.erp.entity.BrandTrademarkCacheEntity;
|
||||||
import com.tashow.erp.repository.BrandTrademarkCacheRepository;
|
import com.tashow.erp.repository.BrandTrademarkCacheRepository;
|
||||||
import com.tashow.erp.service.BrandTrademarkCacheService;
|
import com.tashow.erp.service.BrandTrademarkCacheService;
|
||||||
@@ -7,30 +8,40 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 品牌商标缓存服务实现
|
||||||
|
* 提供商标查询结果的缓存管理
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheService {
|
public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheService {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private BrandTrademarkCacheRepository repository;
|
private BrandTrademarkCacheRepository repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从缓存获取品牌商标注册状态
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Boolean> getCached(List<String> brands) {
|
public Map<String, Boolean> getCached(List<String> brands) {
|
||||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(CacheConstants.TRADEMARK_CACHE_DAYS);
|
||||||
List<BrandTrademarkCacheEntity> cached = repository.findByBrandInAndCreatedAtAfter(brands, cutoffTime);
|
List<BrandTrademarkCacheEntity> cached = repository.findByBrandInAndCreatedAtAfter(brands, cutoffTime);
|
||||||
Map<String, Boolean> result = new HashMap<>();
|
Map<String, Boolean> result = new HashMap<>();
|
||||||
cached.forEach(e -> result.put(e.getBrand(), e.getRegistered()));
|
cached.forEach(e -> result.put(e.getBrand(), e.getRegistered()));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存商标查询结果到缓存
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void saveResults(Map<String, Boolean> results) {
|
public void saveResults(Map<String, Boolean> results) {
|
||||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(CacheConstants.TRADEMARK_CACHE_DAYS);
|
||||||
results.forEach((brand, registered) -> {
|
results.forEach((brand, registered) -> {
|
||||||
repository.findByBrandAndCreatedAtAfter(brand, cutoffTime)
|
repository.findByBrandAndCreatedAtAfter(brand, cutoffTime)
|
||||||
.ifPresentOrElse(
|
.ifPresentOrElse(
|
||||||
@@ -49,10 +60,13 @@ public class BrandTrademarkCacheServiceImpl implements BrandTrademarkCacheServic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理过期缓存数据
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void cleanExpired() {
|
public void cleanExpired() {
|
||||||
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(1);
|
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(CacheConstants.TRADEMARK_CACHE_DAYS);
|
||||||
repository.deleteByCreatedAtBefore(cutoffTime);
|
repository.deleteByCreatedAtBefore(cutoffTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.tashow.erp.service.impl;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.tashow.erp.common.FangzhouConstants;
|
||||||
import com.tashow.erp.service.IFangzhouApiService;
|
import com.tashow.erp.service.IFangzhouApiService;
|
||||||
import com.tashow.erp.utils.ApiForwarder;
|
import com.tashow.erp.utils.ApiForwarder;
|
||||||
import com.tashow.erp.utils.LoggerUtil;
|
import com.tashow.erp.utils.LoggerUtil;
|
||||||
@@ -23,14 +24,12 @@ import java.security.MessageDigest;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 方舟精选 API 服务实现
|
* 方舟精选API服务实现
|
||||||
|
* 负责与方舟精选平台的API交互
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
||||||
private static final Logger logger = LoggerUtil.getLogger(FangzhouApiServiceImpl.class);
|
private static final Logger logger = LoggerUtil.getLogger(FangzhouApiServiceImpl.class);
|
||||||
private static final String API_SECRET = "e10adc3949ba59abbe56e057f20f883e";
|
|
||||||
private static final String FANGZHOU_API_URL = "https://api.fangzhoujingxuan.com/Task";
|
|
||||||
private static final int TOKEN_EXPIRED_CODE = -1006;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RestTemplate restTemplate;
|
private RestTemplate restTemplate;
|
||||||
@@ -41,6 +40,9 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ApiForwarder apiForwarder;
|
private ApiForwarder apiForwarder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取API Token
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String getToken() {
|
public String getToken() {
|
||||||
try {
|
try {
|
||||||
@@ -60,6 +62,9 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新API Token
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String refreshToken() {
|
public String refreshToken() {
|
||||||
try {
|
try {
|
||||||
@@ -78,6 +83,9 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用方舟精选API
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public JsonNode callApi(String command, String data, String token) {
|
public JsonNode callApi(String command, String data, String token) {
|
||||||
try {
|
try {
|
||||||
@@ -87,26 +95,26 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
|||||||
formData.add("c", command);
|
formData.add("c", command);
|
||||||
formData.add("d", data);
|
formData.add("d", data);
|
||||||
formData.add("t", token);
|
formData.add("t", token);
|
||||||
formData.add("s", md5(ts + data + API_SECRET));
|
formData.add("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||||
formData.add("ts", String.valueOf(ts));
|
formData.add("ts", String.valueOf(ts));
|
||||||
formData.add("website", "1");
|
formData.add("website", FangzhouConstants.WEBSITE_CODE);
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
||||||
|
|
||||||
String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
String result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||||
JsonNode json = objectMapper.readTree(result);
|
JsonNode json = objectMapper.readTree(result);
|
||||||
|
|
||||||
// 处理 Token 失效
|
// 处理 Token 失效
|
||||||
int statusCode = json.get("S").asInt();
|
int statusCode = json.get("S").asInt();
|
||||||
if (statusCode == TOKEN_EXPIRED_CODE || statusCode == -1002) {
|
if (statusCode == FangzhouConstants.TOKEN_EXPIRED_CODE || statusCode == -1002) {
|
||||||
String newToken = statusCode == -1002 ? getToken() : refreshToken();
|
String newToken = statusCode == -1002 ? getToken() : refreshToken();
|
||||||
logger.info("Token 失效({}), {}后重试", statusCode, statusCode == -1002 ? "重新注册" : "刷新");
|
logger.info("Token 失效({}), {}后重试", statusCode, statusCode == -1002 ? "重新注册" : "刷新");
|
||||||
formData.set("t", newToken);
|
formData.set("t", newToken);
|
||||||
formData.set("s", md5(ts + data + API_SECRET));
|
formData.set("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||||
requestEntity = new HttpEntity<>(formData, headers);
|
requestEntity = new HttpEntity<>(formData, headers);
|
||||||
result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||||
json = objectMapper.readTree(result);
|
json = objectMapper.readTree(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,14 +127,14 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
|||||||
formData.add("c", command);
|
formData.add("c", command);
|
||||||
formData.add("d", data);
|
formData.add("d", data);
|
||||||
formData.add("t", newToken);
|
formData.add("t", newToken);
|
||||||
formData.add("s", md5(ts + data + API_SECRET));
|
formData.add("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||||
formData.add("ts", String.valueOf(ts));
|
formData.add("ts", String.valueOf(ts));
|
||||||
formData.add("website", "1");
|
formData.add("website", FangzhouConstants.WEBSITE_CODE);
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
||||||
try {
|
try {
|
||||||
String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
String result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||||
return objectMapper.readTree(result);
|
return objectMapper.readTree(result);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("重试失败", ex);
|
logger.error("重试失败", ex);
|
||||||
@@ -138,6 +146,9 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件到方舟精选
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public JsonNode uploadFile(MultipartFile file, String token) {
|
public JsonNode uploadFile(MultipartFile file, String token) {
|
||||||
try {
|
try {
|
||||||
@@ -149,26 +160,26 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
|||||||
formData.add("t", token);
|
formData.add("t", token);
|
||||||
formData.add("ts", ts);
|
formData.add("ts", ts);
|
||||||
formData.add("d", data);
|
formData.add("d", data);
|
||||||
formData.add("s", md5(ts + data + API_SECRET));
|
formData.add("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||||
formData.add("website", "1");
|
formData.add("website", FangzhouConstants.WEBSITE_CODE);
|
||||||
formData.add("files", file.getResource());
|
formData.add("files", file.getResource());
|
||||||
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
|
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
|
||||||
|
|
||||||
String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
String result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||||
JsonNode json = objectMapper.readTree(result);
|
JsonNode json = objectMapper.readTree(result);
|
||||||
|
|
||||||
// 处理 Token 失效
|
// 处理 Token 失效
|
||||||
int statusCode = json.get("S").asInt();
|
int statusCode = json.get("S").asInt();
|
||||||
if (statusCode == TOKEN_EXPIRED_CODE || statusCode == -1002) {
|
if (statusCode == FangzhouConstants.TOKEN_EXPIRED_CODE || statusCode == -1002) {
|
||||||
String newToken = statusCode == -1002 ? getToken() : refreshToken();
|
String newToken = statusCode == -1002 ? getToken() : refreshToken();
|
||||||
logger.info("Token 失效({}), {}后重试", statusCode, statusCode == -1002 ? "重新注册" : "刷新");
|
logger.info("Token 失效({}), {}后重试", statusCode, statusCode == -1002 ? "重新注册" : "刷新");
|
||||||
formData.set("t", newToken);
|
formData.set("t", newToken);
|
||||||
formData.set("s", md5(ts + data + API_SECRET));
|
formData.set("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||||
requestEntity = new HttpEntity<>(formData, headers);
|
requestEntity = new HttpEntity<>(formData, headers);
|
||||||
result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||||
json = objectMapper.readTree(result);
|
json = objectMapper.readTree(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,14 +194,14 @@ public class FangzhouApiServiceImpl implements IFangzhouApiService {
|
|||||||
formData.add("t", newToken);
|
formData.add("t", newToken);
|
||||||
formData.add("ts", ts);
|
formData.add("ts", ts);
|
||||||
formData.add("d", data);
|
formData.add("d", data);
|
||||||
formData.add("s", md5(ts + data + API_SECRET));
|
formData.add("s", md5(ts + data + FangzhouConstants.API_SECRET));
|
||||||
formData.add("website", "1");
|
formData.add("website", FangzhouConstants.WEBSITE_CODE);
|
||||||
formData.add("files", file.getResource());
|
formData.add("files", file.getResource());
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
|
||||||
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
|
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(formData, headers);
|
||||||
try {
|
try {
|
||||||
String result = restTemplate.postForObject(FANGZHOU_API_URL, requestEntity, String.class);
|
String result = restTemplate.postForObject(FangzhouConstants.API_URL, requestEntity, String.class);
|
||||||
return objectMapper.readTree(result);
|
return objectMapper.readTree(result);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error("重试失败", ex);
|
logger.error("重试失败", ex);
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import java.net.URL;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟卖精灵服务实现
|
||||||
|
* 负责打开和操作跟卖精灵网站
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class GenmaiServiceImpl {
|
public class GenmaiServiceImpl {
|
||||||
@Value("${api.server.base-url}")
|
@Value("${api.server.base-url}")
|
||||||
@@ -21,6 +25,9 @@ public class GenmaiServiceImpl {
|
|||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开跟卖精灵网站
|
||||||
|
*/
|
||||||
public void openGenmaiWebsite(Long accountId, String username) throws Exception {
|
public void openGenmaiWebsite(Long accountId, String username) throws Exception {
|
||||||
WebDriverManager.chromedriver()
|
WebDriverManager.chromedriver()
|
||||||
.driverRepositoryUrl(new URL("https://registry.npmmirror.com/-/binary/chromedriver/"))
|
.driverRepositoryUrl(new URL("https://registry.npmmirror.com/-/binary/chromedriver/"))
|
||||||
@@ -46,6 +53,9 @@ public class GenmaiServiceImpl {
|
|||||||
driver.navigate().refresh();
|
driver.navigate().refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取并验证Token
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private String getAndValidateToken(Long accountId, String username) {
|
private String getAndValidateToken(Long accountId, String username) {
|
||||||
String url = serverApiUrl + "/tool/genmai/accounts?name=" + username;
|
String url = serverApiUrl + "/tool/genmai/accounts?name=" + username;
|
||||||
@@ -68,6 +78,9 @@ public class GenmaiServiceImpl {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证Token是否有效
|
||||||
|
*/
|
||||||
private boolean validateToken(String token) {
|
private boolean validateToken(String token) {
|
||||||
try {
|
try {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
package com.tashow.erp.service.impl;
|
package com.tashow.erp.service.impl;
|
||||||
|
|
||||||
|
import com.tashow.erp.common.CacheConstants;
|
||||||
import com.tashow.erp.entity.RakutenProductEntity;
|
import com.tashow.erp.entity.RakutenProductEntity;
|
||||||
import com.tashow.erp.model.RakutenProduct;
|
import com.tashow.erp.model.RakutenProduct;
|
||||||
import com.tashow.erp.repository.RakutenProductRepository;
|
import com.tashow.erp.repository.RakutenProductRepository;
|
||||||
@@ -61,10 +63,10 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
|||||||
.filter(name -> name != null && !name.trim().isEmpty())
|
.filter(name -> name != null && !name.trim().isEmpty())
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
// 清理所有1小时前的旧数据,不分店铺全部清掉
|
// 清理所有过期的旧数据,不分店铺全部清掉
|
||||||
LocalDateTime cutoffTime = LocalDateTime.now().minusHours(1);
|
LocalDateTime cutoffTime = LocalDateTime.now().minusHours(CacheConstants.RAKUTEN_CACHE_HOURS);
|
||||||
repository.deleteAllDataBefore(cutoffTime);
|
repository.deleteAllDataBefore(cutoffTime);
|
||||||
log.info("清理1小时前的所有旧数据");
|
log.info("清理{}小时前的所有旧数据", CacheConstants.RAKUTEN_CACHE_HOURS);
|
||||||
|
|
||||||
List<RakutenProductEntity> entities = products.stream()
|
List<RakutenProductEntity> entities = products.stream()
|
||||||
.map(product -> {
|
.map(product -> {
|
||||||
@@ -80,7 +82,7 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查店铺是否有1小时内的缓存数据(按用户隔离)
|
* 检查店铺是否有缓存时间内的缓存数据(按用户隔离)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean hasRecentData(String shopName, String username) {
|
public boolean hasRecentData(String shopName, String username) {
|
||||||
@@ -88,9 +90,9 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
boolean hasRecent = repository.existsByOriginalShopNameAndSessionIdStartingWithAndCreatedAtAfter(
|
boolean hasRecent = repository.existsByOriginalShopNameAndSessionIdStartingWithAndCreatedAtAfter(
|
||||||
shopName, username + "#", LocalDateTime.now().minusHours(1));
|
shopName, username + "#", LocalDateTime.now().minusHours(CacheConstants.RAKUTEN_CACHE_HOURS));
|
||||||
if (hasRecent) {
|
if (hasRecent) {
|
||||||
log.info("店铺 {} 存在1小时内缓存数据(用户: {}),将使用缓存", shopName, username);
|
log.info("店铺 {} 存在缓存时间内缓存数据(用户: {}),将使用缓存", shopName, username);
|
||||||
}
|
}
|
||||||
return hasRecent;
|
return hasRecent;
|
||||||
}
|
}
|
||||||
@@ -114,6 +116,9 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新指定店铺的所有产品的会话ID
|
* 更新指定店铺的所有产品的会话ID
|
||||||
|
*
|
||||||
|
* @param shopName 店铺名
|
||||||
|
* @param newSessionId 新的会话ID
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -131,6 +136,9 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新指定产品列表的会话ID,只更新这些具体的产品
|
* 更新指定产品列表的会话ID,只更新这些具体的产品
|
||||||
|
*
|
||||||
|
* @param products 产品列表
|
||||||
|
* @param newSessionId 新的会话ID
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -167,7 +175,9 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清理指定店铺1小时之前的旧数据,保留1小时内的缓存
|
* 清理指定店铺的旧数据,保留1小时内的缓存
|
||||||
|
*
|
||||||
|
* @param shopName 店铺名
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -178,5 +188,4 @@ public class RakutenCacheServiceImpl implements RakutenCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,9 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
|||||||
return products;
|
return products;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 乐天页面解析器
|
||||||
|
*/
|
||||||
private class RakutenPageProcessor implements PageProcessor {
|
private class RakutenPageProcessor implements PageProcessor {
|
||||||
private final List<RakutenProduct> products;
|
private final List<RakutenProduct> products;
|
||||||
private final ErrorReporter errorReporter;
|
private final ErrorReporter errorReporter;
|
||||||
@@ -60,6 +63,9 @@ public class RakutenScrapingServiceImpl implements RakutenScrapingService {
|
|||||||
this.errorReporter = errorReporter;
|
this.errorReporter = errorReporter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析乐天页面数据
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void process(Page page) {
|
public void process(Page page) {
|
||||||
List<String> rankings = page.getHtml().xpath("//div[@class='srhRnk']/span[@class='icon']/text()").all();
|
List<String> rankings = page.getHtml().xpath("//div[@class='srhRnk']/span[@class='icon']/text()").all();
|
||||||
|
|||||||
@@ -35,10 +35,9 @@ public class DataReportUtil {
|
|||||||
reportData.put("clientId", generateClientId(dataType));
|
reportData.put("clientId", generateClientId(dataType));
|
||||||
reportData.put("dataType", dataType);
|
reportData.put("dataType", dataType);
|
||||||
reportData.put("dataCount", dataCount);
|
reportData.put("dataCount", dataCount);
|
||||||
reportData.put("status", status);
|
reportData.put("status", status != null ? status : "0");
|
||||||
|
|
||||||
sendReportData(reportData);
|
sendReportData(reportData);
|
||||||
logger.debug("数据上报成功: {} - {} 条", dataType, dataCount);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.warn("数据上报失败: {}", e.getMessage());
|
logger.warn("数据上报失败: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,17 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import com.tashow.erp.service.impl.AuthServiceImpl;
|
import com.tashow.erp.service.impl.AuthServiceImpl;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class ErrorReporter {
|
public class ErrorReporter {
|
||||||
|
|
||||||
@Value("${api.server.base-url}")
|
|
||||||
private String serverUrl;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AuthServiceImpl authService;
|
private AuthServiceImpl authService;
|
||||||
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
@Autowired
|
||||||
|
private ApiForwarder apiForwarder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上报启动失败错误
|
* 上报启动失败错误
|
||||||
@@ -67,17 +63,14 @@ public class ErrorReporter {
|
|||||||
errorData.put("errorType", errorType);
|
errorData.put("errorType", errorType);
|
||||||
errorData.put("errorMessage", errorMessage);
|
errorData.put("errorMessage", errorMessage);
|
||||||
errorData.put("stackTrace", getStackTrace(ex));
|
errorData.put("stackTrace", getStackTrace(ex));
|
||||||
|
|
||||||
// 添加系统信息
|
|
||||||
errorData.put("osName", System.getProperty("os.name"));
|
errorData.put("osName", System.getProperty("os.name"));
|
||||||
errorData.put("osVersion", System.getProperty("os.version"));
|
errorData.put("osVersion", System.getProperty("os.version"));
|
||||||
errorData.put("appVersion", System.getProperty("project.version", "unknown"));
|
errorData.put("appVersion", System.getProperty("project.version", "unknown"));
|
||||||
|
|
||||||
String url = serverUrl + "/monitor/client/api/error";
|
apiForwarder.post("/monitor/error", errorData, null);
|
||||||
restTemplate.postForObject(url, errorData, Map.class);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("错误上报失败: " + e.getMessage());
|
// 静默失败,不影响主业务
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.tashow.erp.utils;
|
package com.tashow.erp.utils;
|
||||||
|
|
||||||
|
import com.tashow.erp.common.HttpConstants;
|
||||||
import com.tashow.erp.common.Constants;
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -362,7 +361,7 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
|
|||||||
*/
|
*/
|
||||||
public static boolean ishttp(String link)
|
public static boolean ishttp(String link)
|
||||||
{
|
{
|
||||||
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
|
return StringUtils.startsWithAny(link, HttpConstants.HTTP, HttpConstants.HTTPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -635,9 +634,10 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
AntPathMatcher matcher = new AntPathMatcher();
|
||||||
for (String pattern : strs)
|
for (String pattern : strs)
|
||||||
{
|
{
|
||||||
if (isMatch(pattern, str))
|
if (matcher.match(pattern, str))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -645,28 +645,6 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断url是否与规则配置:
|
|
||||||
* ? 表示单个字符;
|
|
||||||
* * 表示一层路径内的任意字符串,不可跨层级;
|
|
||||||
* ** 表示任意层路径;
|
|
||||||
*
|
|
||||||
* @param pattern 匹配规则
|
|
||||||
* @param url 需要匹配的url
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public static boolean isMatch(String pattern, String url)
|
|
||||||
{
|
|
||||||
AntPathMatcher matcher = new AntPathMatcher();
|
|
||||||
return matcher.match(pattern, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T> T cast(Object obj)
|
|
||||||
{
|
|
||||||
return (T) obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
|
* 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.tashow.erp.utils;
|
||||||
|
|
||||||
|
import com.tashow.erp.common.AmazonConstants;
|
||||||
|
import com.tashow.erp.common.RakutenConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL构建工具类
|
||||||
|
*/
|
||||||
|
public class UrlBuilder {
|
||||||
|
/**
|
||||||
|
* 构建亚马逊商品URL
|
||||||
|
*/
|
||||||
|
public static String buildAmazonUrl(String region, String asin) {
|
||||||
|
String domain = AmazonConstants.REGION_US.equals(region)
|
||||||
|
? AmazonConstants.DOMAIN_US
|
||||||
|
: AmazonConstants.DOMAIN_JP;
|
||||||
|
return domain + AmazonConstants.URL_PRODUCT_PATH + asin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建乐天商品URL
|
||||||
|
*/
|
||||||
|
public static String buildRakutenUrl(String itemCode) {
|
||||||
|
return RakutenConstants.DOMAIN + "/" + itemCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UrlBuilder() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.tashow.erp.utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据验证工具类
|
||||||
|
*/
|
||||||
|
public class ValidationUtils {
|
||||||
|
/**
|
||||||
|
* 判断字符串是否为空
|
||||||
|
*/
|
||||||
|
public static boolean isEmpty(String str) {
|
||||||
|
return str == null || str.trim().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字符串是否非空
|
||||||
|
*/
|
||||||
|
public static boolean isNotEmpty(String str) {
|
||||||
|
return !isEmpty(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValidationUtils() {}
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ javafx:
|
|||||||
height: 800
|
height: 800
|
||||||
# style: DECORATED # javafx.stage.StageStyle [DECORATED, UNDECORATED, TRANSPARENT, UTILITY, UNIFIED]
|
# style: DECORATED # javafx.stage.StageStyle [DECORATED, UNDECORATED, TRANSPARENT, UTILITY, UNIFIED]
|
||||||
# resizable: false
|
# resizable: false
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
main:
|
main:
|
||||||
lazy-initialization: true
|
lazy-initialization: true
|
||||||
@@ -47,8 +46,9 @@ server:
|
|||||||
api:
|
api:
|
||||||
server:
|
server:
|
||||||
# 主服务器API配置
|
# 主服务器API配置
|
||||||
base-url: "http://8.138.23.49:8085"
|
#base-url: "http://8.138.23.49:8085"
|
||||||
#base-url: "http://192.168.1.89:8085"
|
#base-url: "http://192.168.1.89:8085"
|
||||||
|
base-url: "http://127.0.0.1:8085"
|
||||||
paths:
|
paths:
|
||||||
monitor: "/monitor/client/api"
|
monitor: "/monitor/client/api"
|
||||||
login: "/monitor/account/login"
|
login: "/monitor/account/login"
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ import cn.hutool.core.date.DateUtil;
|
|||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 客户端账号控制器
|
* 客户端账号控制器
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ import com.ruoyi.common.utils.StringUtils;
|
|||||||
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.annotation.Log;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
|
import com.ruoyi.web.service.IClientAccountService;
|
||||||
|
import com.ruoyi.web.security.JwtRsaKeyService;
|
||||||
|
import com.ruoyi.system.domain.ClientAccount;
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -25,26 +32,43 @@ public class VersionController extends BaseController {
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisTemplate<String, String> redisTemplate;
|
private RedisTemplate<String, String> redisTemplate;
|
||||||
|
@Autowired
|
||||||
|
private IClientAccountService clientAccountService;
|
||||||
|
@Autowired
|
||||||
|
private JwtRsaKeyService jwtRsaKeyService;
|
||||||
|
|
||||||
private static final String VERSION_REDIS_KEY = "erp:client:version";
|
private static final String VERSION_REDIS_KEY = "erp:client:version";
|
||||||
private static final String ASAR_URL_REDIS_KEY = "erp:client:asar_url";
|
private static final String ASAR_URL_REDIS_KEY = "erp:client:asar_url";
|
||||||
private static final String JAR_URL_REDIS_KEY = "erp:client:jar_url";
|
private static final String JAR_URL_REDIS_KEY = "erp:client:jar_url";
|
||||||
private static final String UPDATE_NOTES_REDIS_KEY = "erp:client:update_notes";
|
private static final String UPDATE_NOTES_REDIS_KEY = "erp:client:update_notes";
|
||||||
|
|
||||||
|
private static final String VERSION_BETA_REDIS_KEY = "erp:client:version:beta";
|
||||||
|
private static final String ASAR_URL_BETA_REDIS_KEY = "erp:client:asar_url:beta";
|
||||||
|
private static final String JAR_URL_BETA_REDIS_KEY = "erp:client:jar_url:beta";
|
||||||
|
private static final String UPDATE_NOTES_BETA_REDIS_KEY = "erp:client:update_notes:beta";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查版本更新
|
* 检查版本更新
|
||||||
*/
|
*/
|
||||||
@GetMapping("/check")
|
@GetMapping("/check")
|
||||||
public AjaxResult checkVersion(@RequestParam String currentVersion) {
|
public AjaxResult checkVersion(@RequestParam String currentVersion, HttpServletRequest request) {
|
||||||
String latestVersion = redisTemplate.opsForValue().get(VERSION_REDIS_KEY);
|
boolean isBeta = canUseBetaVersion(request);
|
||||||
|
|
||||||
|
String versionKey = isBeta ? VERSION_BETA_REDIS_KEY : VERSION_REDIS_KEY;
|
||||||
|
String asarKey = isBeta ? ASAR_URL_BETA_REDIS_KEY : ASAR_URL_REDIS_KEY;
|
||||||
|
String jarKey = isBeta ? JAR_URL_BETA_REDIS_KEY : JAR_URL_REDIS_KEY;
|
||||||
|
String notesKey = isBeta ? UPDATE_NOTES_BETA_REDIS_KEY : UPDATE_NOTES_REDIS_KEY;
|
||||||
|
|
||||||
|
String latestVersion = redisTemplate.opsForValue().get(versionKey);
|
||||||
boolean needUpdate = compareVersions(currentVersion, latestVersion) < 0;
|
boolean needUpdate = compareVersions(currentVersion, latestVersion) < 0;
|
||||||
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("currentVersion", currentVersion);
|
data.put("currentVersion", currentVersion);
|
||||||
data.put("latestVersion", latestVersion);
|
data.put("latestVersion", latestVersion);
|
||||||
data.put("needUpdate", needUpdate);
|
data.put("needUpdate", needUpdate);
|
||||||
data.put("asarUrl", redisTemplate.opsForValue().get(ASAR_URL_REDIS_KEY));
|
data.put("asarUrl", redisTemplate.opsForValue().get(asarKey));
|
||||||
data.put("jarUrl", redisTemplate.opsForValue().get(JAR_URL_REDIS_KEY));
|
data.put("jarUrl", redisTemplate.opsForValue().get(jarKey));
|
||||||
data.put("updateNotes", redisTemplate.opsForValue().get(UPDATE_NOTES_REDIS_KEY));
|
data.put("updateNotes", redisTemplate.opsForValue().get(notesKey));
|
||||||
|
|
||||||
return AjaxResult.success(data);
|
return AjaxResult.success(data);
|
||||||
}
|
}
|
||||||
@@ -54,16 +78,22 @@ public class VersionController extends BaseController {
|
|||||||
@PreAuthorize("@ss.hasPermi('system:version:query')")
|
@PreAuthorize("@ss.hasPermi('system:version:query')")
|
||||||
@GetMapping("/info")
|
@GetMapping("/info")
|
||||||
public AjaxResult getVersionInfo() {
|
public AjaxResult getVersionInfo() {
|
||||||
String currentVersion = redisTemplate.opsForValue().get(VERSION_REDIS_KEY);
|
|
||||||
if (StringUtils.isEmpty(currentVersion)) {
|
|
||||||
currentVersion = "2.0.0";
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("currentVersion", currentVersion);
|
|
||||||
data.put("asarUrl", redisTemplate.opsForValue().get(ASAR_URL_REDIS_KEY));
|
Map<String, Object> release = new HashMap<>();
|
||||||
data.put("jarUrl", redisTemplate.opsForValue().get(JAR_URL_REDIS_KEY));
|
release.put("version", redisTemplate.opsForValue().get(VERSION_REDIS_KEY));
|
||||||
data.put("updateNotes", redisTemplate.opsForValue().get(UPDATE_NOTES_REDIS_KEY));
|
release.put("asarUrl", redisTemplate.opsForValue().get(ASAR_URL_REDIS_KEY));
|
||||||
|
release.put("jarUrl", redisTemplate.opsForValue().get(JAR_URL_REDIS_KEY));
|
||||||
|
release.put("updateNotes", redisTemplate.opsForValue().get(UPDATE_NOTES_REDIS_KEY));
|
||||||
|
|
||||||
|
Map<String, Object> beta = new HashMap<>();
|
||||||
|
beta.put("version", redisTemplate.opsForValue().get(VERSION_BETA_REDIS_KEY));
|
||||||
|
beta.put("asarUrl", redisTemplate.opsForValue().get(ASAR_URL_BETA_REDIS_KEY));
|
||||||
|
beta.put("jarUrl", redisTemplate.opsForValue().get(JAR_URL_BETA_REDIS_KEY));
|
||||||
|
beta.put("updateNotes", redisTemplate.opsForValue().get(UPDATE_NOTES_BETA_REDIS_KEY));
|
||||||
|
|
||||||
|
data.put("release", release);
|
||||||
|
data.put("beta", beta);
|
||||||
data.put("updateTime", System.currentTimeMillis());
|
data.put("updateTime", System.currentTimeMillis());
|
||||||
|
|
||||||
return AjaxResult.success(data);
|
return AjaxResult.success(data);
|
||||||
@@ -78,25 +108,52 @@ public class VersionController extends BaseController {
|
|||||||
public AjaxResult updateVersionInfo(@RequestParam("version") String version,
|
public AjaxResult updateVersionInfo(@RequestParam("version") String version,
|
||||||
@RequestParam(value = "asarUrl", required = false) String asarUrl,
|
@RequestParam(value = "asarUrl", required = false) String asarUrl,
|
||||||
@RequestParam(value = "jarUrl", required = false) String jarUrl,
|
@RequestParam(value = "jarUrl", required = false) String jarUrl,
|
||||||
@RequestParam("updateNotes") String updateNotes) {
|
@RequestParam("updateNotes") String updateNotes,
|
||||||
redisTemplate.opsForValue().set(VERSION_REDIS_KEY, version);
|
@RequestParam(value = "isBeta", defaultValue = "false") Boolean isBeta) {
|
||||||
|
String versionKey = isBeta ? VERSION_BETA_REDIS_KEY : VERSION_REDIS_KEY;
|
||||||
|
String asarKey = isBeta ? ASAR_URL_BETA_REDIS_KEY : ASAR_URL_REDIS_KEY;
|
||||||
|
String jarKey = isBeta ? JAR_URL_BETA_REDIS_KEY : JAR_URL_REDIS_KEY;
|
||||||
|
String notesKey = isBeta ? UPDATE_NOTES_BETA_REDIS_KEY : UPDATE_NOTES_REDIS_KEY;
|
||||||
|
|
||||||
|
redisTemplate.opsForValue().set(versionKey, version);
|
||||||
if (StringUtils.isNotEmpty(asarUrl)) {
|
if (StringUtils.isNotEmpty(asarUrl)) {
|
||||||
redisTemplate.opsForValue().set(ASAR_URL_REDIS_KEY, asarUrl);
|
redisTemplate.opsForValue().set(asarKey, asarUrl);
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(jarUrl)) {
|
if (StringUtils.isNotEmpty(jarUrl)) {
|
||||||
redisTemplate.opsForValue().set(JAR_URL_REDIS_KEY, jarUrl);
|
redisTemplate.opsForValue().set(jarKey, jarUrl);
|
||||||
}
|
}
|
||||||
redisTemplate.opsForValue().set(UPDATE_NOTES_REDIS_KEY, updateNotes);
|
redisTemplate.opsForValue().set(notesKey, updateNotes);
|
||||||
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("version", version);
|
data.put("version", version);
|
||||||
data.put("asarUrl", asarUrl);
|
data.put("asarUrl", asarUrl);
|
||||||
data.put("jarUrl", jarUrl);
|
data.put("jarUrl", jarUrl);
|
||||||
data.put("updateNotes", updateNotes);
|
data.put("updateNotes", updateNotes);
|
||||||
|
data.put("isBeta", isBeta);
|
||||||
data.put("updateTime", System.currentTimeMillis());
|
data.put("updateTime", System.currentTimeMillis());
|
||||||
return AjaxResult.success(data);
|
return AjaxResult.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean canUseBetaVersion(HttpServletRequest request) {
|
||||||
|
try {
|
||||||
|
String token = request.getHeader("Authorization");
|
||||||
|
if (StringUtils.isEmpty(token)) return false;
|
||||||
|
if (token.startsWith("Bearer ")) token = token.substring(7);
|
||||||
|
|
||||||
|
String username = (String) Jwts.parser()
|
||||||
|
.setSigningKey(jwtRsaKeyService.getPublicKey())
|
||||||
|
.parseClaimsJws(token).getBody().get("sub");
|
||||||
|
|
||||||
|
ClientAccount account = clientAccountService.selectClientAccountByUsername(username);
|
||||||
|
if (account == null) return false;
|
||||||
|
|
||||||
|
JSONObject perms = JSON.parseObject(account.getPermissions());
|
||||||
|
return perms != null && perms.getBooleanValue("beta_version");
|
||||||
|
} catch (Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 比较版本号
|
* 比较版本号
|
||||||
* @param version1 版本1
|
* @param version1 版本1
|
||||||
@@ -107,8 +164,12 @@ public class VersionController extends BaseController {
|
|||||||
if (StringUtils.isEmpty(version1) || StringUtils.isEmpty(version2)) {
|
if (StringUtils.isEmpty(version1) || StringUtils.isEmpty(version2)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
String[] v1Parts = version1.split("\\.");
|
|
||||||
String[] v2Parts = version2.split("\\.");
|
String v1 = version1.replace("-beta", "");
|
||||||
|
String v2 = version2.replace("-beta", "");
|
||||||
|
|
||||||
|
String[] v1Parts = v1.split("\\.");
|
||||||
|
String[] v2Parts = v2.split("\\.");
|
||||||
|
|
||||||
int maxLength = Math.max(v1Parts.length, v2Parts.length);
|
int maxLength = Math.max(v1Parts.length, v2Parts.length);
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,6 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> authenticateClient(String authKey, Map<String, Object> clientInfo) {
|
public Map<String, Object> authenticateClient(String authKey, Map<String, Object> clientInfo) {
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
try {
|
try {
|
||||||
String accessToken = UUID.randomUUID().toString().replace("-", "");
|
String accessToken = UUID.randomUUID().toString().replace("-", "");
|
||||||
String clientId = (String) clientInfo.get("clientId");
|
String clientId = (String) clientInfo.get("clientId");
|
||||||
@@ -194,16 +193,18 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
|||||||
clientMonitorMapper.updateClientOnlineStatus(clientId, "1");
|
clientMonitorMapper.updateClientOnlineStatus(clientId, "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
result.put("success", true);
|
result.put("success", true);
|
||||||
result.put("accessToken", accessToken);
|
result.put("accessToken", accessToken);
|
||||||
result.put("tokenType", "Bearer");
|
result.put("tokenType", "Bearer");
|
||||||
result.put("expiresIn", 7200);
|
result.put("expiresIn", 7200);
|
||||||
result.put("clientId", clientId);
|
result.put("clientId", clientId);
|
||||||
result.put("permissions", null);
|
result.put("permissions", null);
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("认证失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("认证失败: {}", e.getMessage(), e);
|
||||||
|
throw new RuntimeException("认证失败", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientInfo findClientByClientId(String clientId) {
|
private ClientInfo findClientByClientId(String clientId) {
|
||||||
@@ -225,6 +226,7 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void recordErrorReport(Map<String, Object> errorData) {
|
public void recordErrorReport(Map<String, Object> errorData) {
|
||||||
|
try {
|
||||||
ClientErrorReport errorReport = new ClientErrorReport();
|
ClientErrorReport errorReport = new ClientErrorReport();
|
||||||
errorReport.setClientId((String) errorData.get("clientId"));
|
errorReport.setClientId((String) errorData.get("clientId"));
|
||||||
errorReport.setErrorType((String) errorData.get("errorType"));
|
errorReport.setErrorType((String) errorData.get("errorType"));
|
||||||
@@ -236,6 +238,10 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
|||||||
errorReport.setOsVersion((String) errorData.get("osVersion"));
|
errorReport.setOsVersion((String) errorData.get("osVersion"));
|
||||||
errorReport.setAppVersion((String) errorData.get("appVersion"));
|
errorReport.setAppVersion((String) errorData.get("appVersion"));
|
||||||
clientMonitorMapper.insertClientError(errorReport);
|
clientMonitorMapper.insertClientError(errorReport);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("记录错误报告失败: {}", e.getMessage(), e);
|
||||||
|
throw new RuntimeException("记录错误报告失败", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,7 +252,8 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
|||||||
try {
|
try {
|
||||||
String clientId = (String) dataReport.get("clientId");
|
String clientId = (String) dataReport.get("clientId");
|
||||||
String dataType = normalizeDataType((String) dataReport.get("dataType"));
|
String dataType = normalizeDataType((String) dataReport.get("dataType"));
|
||||||
String status = (String) dataReport.get("status");
|
Object statusObj = dataReport.get("status");
|
||||||
|
String status = statusObj != null ? String.valueOf(statusObj) : "0";
|
||||||
int dataCount = parseInteger(dataReport.get("dataCount"), 1);
|
int dataCount = parseInteger(dataReport.get("dataCount"), 1);
|
||||||
|
|
||||||
ClientDataReport existingReport = clientMonitorMapper.findRecentDataReport(clientId, dataType, status);
|
ClientDataReport existingReport = clientMonitorMapper.findRecentDataReport(clientId, dataType, status);
|
||||||
@@ -264,9 +271,13 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (clientId != null && !clientId.isEmpty()) {
|
if (clientId != null && !clientId.isEmpty()) {
|
||||||
|
try {
|
||||||
clientMonitorMapper.updateClientOnlineStatus(clientId, "1");
|
clientMonitorMapper.updateClientOnlineStatus(clientId, "1");
|
||||||
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.error("记录数据采集报告失败: {}", e.getMessage(), e);
|
||||||
|
throw new RuntimeException("记录数据采集报告失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function uploadFile(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新版本信息和下载链接
|
// 更新版本信息和下载链接
|
||||||
// data: { version, asarUrl, jarUrl }
|
// data: { version, asarUrl, jarUrl, updateNotes, isBeta }
|
||||||
export function updateVersion(data) {
|
export function updateVersion(data) {
|
||||||
return request({
|
return request({
|
||||||
url: '/system/version/update',
|
url: '/system/version/update',
|
||||||
|
|||||||
@@ -205,6 +205,7 @@
|
|||||||
<el-checkbox label="toolbox"><i class="el-icon-box"></i> 工具箱功能</el-checkbox>
|
<el-checkbox label="toolbox"><i class="el-icon-box"></i> 工具箱功能</el-checkbox>
|
||||||
<el-checkbox label="dataCollection"><i class="el-icon-document-copy"></i> 数据采集功能</el-checkbox>
|
<el-checkbox label="dataCollection"><i class="el-icon-document-copy"></i> 数据采集功能</el-checkbox>
|
||||||
<el-checkbox label="priceCompare"><i class="el-icon-price-tag"></i> 1688比价功能</el-checkbox>
|
<el-checkbox label="priceCompare"><i class="el-icon-price-tag"></i> 1688比价功能</el-checkbox>
|
||||||
|
<el-checkbox label="beta_version"><i class="el-icon-warning-outline"></i> 测试版更新权限</el-checkbox>
|
||||||
</el-checkbox-group>
|
</el-checkbox-group>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|||||||
@@ -7,29 +7,42 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<!-- 版本信息卡片 -->
|
<!-- 版本信息卡片 -->
|
||||||
<el-row class="mb8">
|
<el-row :gutter="20" class="mb8">
|
||||||
<el-col s>
|
<el-col :span="12">
|
||||||
<el-card class="box-card">
|
<el-card class="box-card">
|
||||||
<div slot="header" class="clearfix">
|
<div slot="header" class="clearfix">
|
||||||
<span>当前版本信息</span>
|
<span>正式版</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="version-info">
|
<div class="version-info">
|
||||||
<el-descriptions :column="1" border>
|
<el-descriptions :column="1" border>
|
||||||
<el-descriptions-item label="当前版本">
|
<el-descriptions-item label="版本号">
|
||||||
<el-tag type="primary" size="medium">{{ versionInfo.currentVersion }}</el-tag>
|
<el-tag type="success" size="medium">{{ versionInfo.release.version || '未设置' }}</el-tag>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="更新时间">
|
<el-descriptions-item label="ASAR链接" v-if="versionInfo.release.asarUrl">
|
||||||
{{ parseTime(versionInfo.updateTime) }}
|
<el-link :href="versionInfo.release.asarUrl" target="_blank" type="primary">{{ versionInfo.release.asarUrl }}</el-link>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="ASAR下载链接" v-if="versionInfo.asarUrl">
|
<el-descriptions-item label="JAR链接" v-if="versionInfo.release.jarUrl">
|
||||||
<el-link :href="versionInfo.asarUrl" target="_blank" type="primary">
|
<el-link :href="versionInfo.release.jarUrl" target="_blank" type="success">{{ versionInfo.release.jarUrl }}</el-link>
|
||||||
{{ versionInfo.asarUrl }}
|
|
||||||
</el-link>
|
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="JAR下载链接" v-if="versionInfo.jarUrl">
|
</el-descriptions>
|
||||||
<el-link :href="versionInfo.jarUrl" target="_blank" type="success">
|
</div>
|
||||||
{{ versionInfo.jarUrl }}
|
</el-card>
|
||||||
</el-link>
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-card class="box-card">
|
||||||
|
<div slot="header" class="clearfix">
|
||||||
|
<span>测试版</span>
|
||||||
|
</div>
|
||||||
|
<div class="version-info">
|
||||||
|
<el-descriptions :column="1" border>
|
||||||
|
<el-descriptions-item label="版本号">
|
||||||
|
<el-tag type="warning" size="medium">{{ versionInfo.beta.version || '未设置' }}</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="ASAR链接" v-if="versionInfo.beta.asarUrl">
|
||||||
|
<el-link :href="versionInfo.beta.asarUrl" target="_blank" type="primary">{{ versionInfo.beta.asarUrl }}</el-link>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="JAR链接" v-if="versionInfo.beta.jarUrl">
|
||||||
|
<el-link :href="versionInfo.beta.jarUrl" target="_blank" type="success">{{ versionInfo.beta.jarUrl }}</el-link>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,8 +53,14 @@
|
|||||||
<!-- 上传对话框 -->
|
<!-- 上传对话框 -->
|
||||||
<el-dialog title="上传新版本" :visible.sync="uploadVisible" width="500px" append-to-body>
|
<el-dialog title="上传新版本" :visible.sync="uploadVisible" width="500px" append-to-body>
|
||||||
<el-form ref="uploadForm" :model="uploadForm" :rules="uploadRules" label-width="100px">
|
<el-form ref="uploadForm" :model="uploadForm" :rules="uploadRules" label-width="100px">
|
||||||
|
<el-form-item label="版本类型" prop="isBeta">
|
||||||
|
<el-radio-group v-model="uploadForm.isBeta">
|
||||||
|
<el-radio :label="false">正式版</el-radio>
|
||||||
|
<el-radio :label="true">测试版</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="版本号" prop="version">
|
<el-form-item label="版本号" prop="version">
|
||||||
<el-input v-model="uploadForm.version" placeholder="请输入版本号,如:2.4.7"></el-input>
|
<el-input v-model="uploadForm.version" :placeholder="uploadForm.isBeta ? '请输入版本号,如:2.4.9-beta' : '请输入版本号,如:2.4.7'"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="更新内容" prop="updateNotes">
|
<el-form-item label="更新内容" prop="updateNotes">
|
||||||
<el-input v-model="uploadForm.updateNotes" type="textarea" :rows="4" placeholder="请输入更新内容"></el-input>
|
<el-input v-model="uploadForm.updateNotes" type="textarea" :rows="4" placeholder="请输入更新内容"></el-input>
|
||||||
@@ -98,10 +117,9 @@ export default {
|
|||||||
showSearch: true,
|
showSearch: true,
|
||||||
// 版本信息
|
// 版本信息
|
||||||
versionInfo: {
|
versionInfo: {
|
||||||
currentVersion: '2.0.0',
|
release: { version: null, asarUrl: null, jarUrl: null, updateNotes: null },
|
||||||
updateTime: null,
|
beta: { version: null, asarUrl: null, jarUrl: null, updateNotes: null },
|
||||||
asarUrl: null,
|
updateTime: null
|
||||||
jarUrl: null
|
|
||||||
},
|
},
|
||||||
// 版本检查表单
|
// 版本检查表单
|
||||||
checkForm: {
|
checkForm: {
|
||||||
@@ -115,6 +133,7 @@ export default {
|
|||||||
uploadVisible: false,
|
uploadVisible: false,
|
||||||
// 上传表单
|
// 上传表单
|
||||||
uploadForm: {
|
uploadForm: {
|
||||||
|
isBeta: false,
|
||||||
version: '',
|
version: '',
|
||||||
updateNotes: '',
|
updateNotes: '',
|
||||||
asarFile: null,
|
asarFile: null,
|
||||||
@@ -124,7 +143,16 @@ export default {
|
|||||||
uploadRules: {
|
uploadRules: {
|
||||||
version: [
|
version: [
|
||||||
{ required: true, message: "版本号不能为空", trigger: "blur" },
|
{ required: true, message: "版本号不能为空", trigger: "blur" },
|
||||||
{ pattern: /^\d+\.\d+\.\d+$/, message: "版本号格式不正确,应为x.y.z格式", trigger: "blur" }
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (!/^\d+\.\d+\.\d+(-beta)?$/.test(value)) {
|
||||||
|
callback(new Error('版本号格式不正确,应为x.y.z或x.y.z-beta格式'));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
updateNotes: [
|
updateNotes: [
|
||||||
{ required: true, message: "更新内容不能为空", trigger: "blur" }
|
{ required: true, message: "更新内容不能为空", trigger: "blur" }
|
||||||
@@ -158,6 +186,7 @@ export default {
|
|||||||
/** 重置上传表单 */
|
/** 重置上传表单 */
|
||||||
resetUploadForm() {
|
resetUploadForm() {
|
||||||
this.uploadForm = {
|
this.uploadForm = {
|
||||||
|
isBeta: false,
|
||||||
version: '',
|
version: '',
|
||||||
updateNotes: '',
|
updateNotes: '',
|
||||||
asarFile: null,
|
asarFile: null,
|
||||||
@@ -260,7 +289,8 @@ export default {
|
|||||||
version: this.uploadForm.version,
|
version: this.uploadForm.version,
|
||||||
asarUrl: asarUrl,
|
asarUrl: asarUrl,
|
||||||
jarUrl: jarUrl,
|
jarUrl: jarUrl,
|
||||||
updateNotes: this.uploadForm.updateNotes
|
updateNotes: this.uploadForm.updateNotes,
|
||||||
|
isBeta: this.uploadForm.isBeta
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.$modal.msgSuccess("版本文件上传成功");
|
this.$modal.msgSuccess("版本文件上传成功");
|
||||||
this.uploadVisible = false;
|
this.uploadVisible = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user