This commit is contained in:
2025-09-30 17:16:11 +08:00
parent e650a7c7f3
commit 52ce0e1969
25 changed files with 689 additions and 989 deletions

View File

@@ -295,6 +295,17 @@ onMounted(async () => {
<div class="body-layout">
<!-- 左侧步骤栏 -->
<aside class="steps-sidebar">
<!-- 顶部标签栏 -->
<div class="top-tabs">
<div class="tab-item active">
<span class="tab-icon">📦</span>
<span class="tab-text">ASIN查询</span>
</div>
<div class="tab-item" @click="openGenmaiSpirit">
<span class="tab-icon">🔍</span>
<span class="tab-text">跟卖精灵</span>
</div>
</div>
<div class="steps-title">操作流程</div>
<div class="steps-flow">
<!-- 1 -->
@@ -356,7 +367,6 @@ onMounted(async () => {
</div>
<div class="export-progress-text">{{ Math.round(exportProgress) }}%</div>
</div>
<el-button size="small" class="w100 btn-blue" :loading="genmaiLoading" @click="openGenmaiSpirit">{{ genmaiLoading ? '启动中...' : '跟卖精灵' }}</el-button>
</div>
</div>
</div>
@@ -453,14 +463,25 @@ onMounted(async () => {
<style scoped>
.amazon-root { position: absolute; inset: 0; background: #f5f5f5; padding: 12px; box-sizing: border-box; }
.main-container { background: #fff; border-radius: 4px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); height: 100%; display: flex; flex-direction: column; }
.body-layout { display: flex; gap: 12px; height: 100%; }
/* 顶部标签栏 */
.top-tabs { display: flex; margin-bottom: 8px; }
.tab-item { flex: 1; display: flex; align-items: center; justify-content: center; gap: 3px; padding: 4px 6px; cursor: pointer; transition: all 0.2s ease; background: #f5f7fa; color: #606266; font-size: 11px; font-weight: 500; border: 1px solid #ebeef5; }
.tab-item:first-child { border-radius: 3px 0 0 3px; }
.tab-item:last-child { border-radius: 0 3px 3px 0; border-left: none; }
.tab-item:hover { background: #e8f4ff; color: #409EFF; }
.tab-item.active { background: #1677FF; color: #fff; border-color: #1677FF; cursor: default; }
.tab-icon { font-size: 12px; }
.tab-text { line-height: 1; }
.body-layout { display: flex; gap: 12px; flex: 1; overflow: hidden; }
.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; margin-bottom: 8px; text-align: left; }
.steps-title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
.steps-flow { position: relative; }
.steps-flow:before { content: ''; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: #e5e7eb; }
.flow-item { position: relative; display: grid; grid-template-columns: 24px 1fr; gap: 10px; padding: 8px 0; }
.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 + .flow-item { border-top: 1px dashed #ebeef5; }
.flow-item .step-index { position: static; width: 24px; height: 24px; line-height: 24px; 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; }
.step-card { border: none; border-radius: 0; padding: 0; background: transparent; }
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
@@ -521,14 +542,14 @@ onMounted(async () => {
.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: left; }
.table td { padding: 10px 8px; border-bottom: 1px solid #f0f0f0; vertical-align: middle; }
.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; }
.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; }

View File

@@ -31,11 +31,10 @@ async function handleAuth() {
authLoading.value = true
try {
// 1. 先检查设备限制
await deviceApi.register({ username: authForm.value.username })
// 2. 设备检查通过,进行登录
const data = await authApi.login(authForm.value)
const loginRes: any = await authApi.login(authForm.value)
const data = loginRes?.data || loginRes
emit('loginSuccess', {
token: data.token,
user: {

View File

@@ -41,8 +41,9 @@ async function checkUsernameAvailability() {
}
try {
const data = await authApi.checkUsername(registerForm.value.username)
usernameCheckResult.value = data.available
const res: any = await authApi.checkUsername(registerForm.value.username)
const data = res?.data || res
usernameCheckResult.value = data?.available || false
} catch {
usernameCheckResult.value = null
}
@@ -53,18 +54,17 @@ async function handleRegister() {
registerLoading.value = true
try {
// 1. 注册
await authApi.register({
username: registerForm.value.username,
password: registerForm.value.password
})
// 2. 注册成功后直接登录
const loginData = await authApi.login({
const loginRes: any = await authApi.login({
username: registerForm.value.username,
password: registerForm.value.password
})
const loginData = loginRes?.data || loginRes
emit('loginSuccess', {
token: loginData.token,
user: {

View File

@@ -0,0 +1,332 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import {
getSettings,
saveSettings,
savePlatformSettings,
type Platform,
type PlatformExportSettings
} from '../../utils/settings'
interface Props {
modelValue: boolean
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
// 平台配置
const platforms = [
{ key: 'amazon' as Platform, name: 'Amazon', icon: '🛒', color: '#FF9900' },
{ key: 'rakuten' as Platform, name: 'Rakuten', icon: '🛍️', color: '#BF0000' },
{ key: 'zebra' as Platform, name: 'Zebra', icon: '🦓', color: '#34495E' }
]
// 设置项
const platformSettings = ref<Record<Platform, PlatformExportSettings>>({
amazon: { exportPath: '' },
rakuten: { exportPath: '' },
zebra: { exportPath: '' }
})
const activeTab = ref<Platform>('amazon')
const show = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 选择导出路径
async function selectExportPath(platform: Platform) {
const result = await (window as any).electronAPI.showOpenDialog({
title: `选择${platforms.find(p => p.key === platform)?.name}默认导出路径`,
properties: ['openDirectory'],
defaultPath: platformSettings.value[platform].exportPath
})
if (!result.canceled && result.filePaths?.length > 0) {
platformSettings.value[platform].exportPath = result.filePaths[0]
}
}
// 保存设置
function saveAllSettings() {
Object.keys(platformSettings.value).forEach(platformKey => {
const platform = platformKey as Platform
const platformConfig = platformSettings.value[platform]
savePlatformSettings(platform, platformConfig)
})
ElMessage({ message: '设置已保存', type: 'success' })
show.value = false
}
// 加载设置
function loadAllSettings() {
const settings = getSettings()
platformSettings.value = {
amazon: { ...settings.platforms.amazon },
rakuten: { ...settings.platforms.rakuten },
zebra: { ...settings.platforms.zebra }
}
}
// 重置单个平台设置
function resetPlatformSettings(platform: Platform) {
platformSettings.value[platform] = {
exportPath: ''
}
}
// 重置所有设置
function resetAllSettings() {
platforms.forEach(platform => {
resetPlatformSettings(platform.key)
})
}
onMounted(() => {
loadAllSettings()
})
</script>
<template>
<el-dialog
v-model="show"
title="应用设置"
width="480px"
:close-on-click-modal="false"
class="settings-dialog">
<div class="settings-content">
<!-- 平台选择标签 -->
<div class="platform-tabs">
<div
v-for="platform in platforms"
:key="platform.key"
:class="['platform-tab', { active: activeTab === platform.key }]"
@click="activeTab = platform.key"
:style="{ '--platform-color': platform.color }">
<span class="platform-icon">{{ platform.icon }}</span>
<span class="platform-name">{{ platform.name }}</span>
</div>
</div>
<!-- 当前平台设置 -->
<div class="setting-section">
<div class="section-title">
<span class="title-icon">📁</span>
<span>{{ platforms.find(p => p.key === activeTab)?.name }} 导出设置</span>
</div>
<div class="setting-item">
<div class="setting-label">默认导出路径</div>
<div class="setting-desc">设置 {{ platforms.find(p => p.key === activeTab)?.name }} Excel文件的默认保存位置</div>
<div class="path-input-group">
<el-input
v-model="platformSettings[activeTab].exportPath"
placeholder="留空时自动弹出保存对话框"
readonly
class="path-input">
<template #suffix>
<el-button
size="small"
type="primary"
@click="selectExportPath(activeTab)"
class="select-btn">
浏览
</el-button>
</template>
</el-input>
</div>
</div>
<div class="setting-actions">
<el-button
size="small"
@click="resetPlatformSettings(activeTab)">
重置此平台
</el-button>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="resetAllSettings">重置全部</el-button>
<el-button @click="show = false">取消</el-button>
<el-button type="primary" @click="saveAllSettings">保存设置</el-button>
</div>
</template>
</el-dialog>
</template>
<style scoped>
.settings-dialog :deep(.el-dialog__body) {
padding: 0 20px 20px 20px;
}
.settings-content {
max-height: 500px;
overflow-y: auto;
}
.platform-tabs {
display: flex;
gap: 8px;
margin-bottom: 20px;
padding: 4px;
background: #F8F9FA;
border-radius: 8px;
}
.platform-tab {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 8px 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
background: transparent;
color: #606266;
font-size: 13px;
}
.platform-tab:hover {
background: rgba(255, 255, 255, 0.8);
color: var(--platform-color);
}
.platform-tab.active {
background: #fff;
color: var(--platform-color);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-weight: 500;
}
.platform-icon {
font-size: 16px;
}
.platform-name {
font-size: 12px;
}
.setting-section {
margin-bottom: 24px;
}
.section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #EBEEF5;
}
.title-icon {
font-size: 18px;
}
.setting-item {
margin-bottom: 20px;
}
.setting-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.setting-info {
flex: 1;
}
.setting-label {
font-size: 14px;
font-weight: 500;
color: #303133;
margin-bottom: 4px;
}
.setting-desc {
font-size: 12px;
color: #909399;
line-height: 1.4;
}
.path-input-group {
margin-top: 8px;
}
.path-input {
width: 100%;
}
.path-input :deep(.el-input__wrapper) {
padding-right: 80px;
}
.select-btn {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
height: 24px;
padding: 0 12px;
font-size: 12px;
}
.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;
}
.setting-actions {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #EBEEF5;
}
.settings-dialog :deep(.el-dialog__header) {
text-align: center;
padding-right: 40px; /* 为右侧关闭按钮留出空间 */
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
}
</style>
<script lang="ts">
export default {
name: 'SettingsDialog'
}
</script>

View File

@@ -2,15 +2,18 @@
<div>
<div class="version-info" @click="autoCheck">v{{ version || '-' }}</div>
<el-dialog v-model="show" width="522px" :close-on-click-modal="false" align-center class="update-dialog" :title="stage === 'downloading' ? `正在更新 ${appName}` : '软件更新'">
<el-dialog v-model="show" width="522px" :close-on-click-modal="false" align-center class="update-dialog"
:title="stage === 'downloading' ? `正在更新 ${appName}` : '软件更新'">
<div v-if="stage === 'check'" class="update-content">
<div class="update-layout">
<div class="left-pane">
<img src="/icon/icon.png" class="app-icon app-icon-large" alt="App Icon" />
<img src="/icon/icon.png" class="app-icon app-icon-large" alt="App Icon"/>
</div>
<div class="right-pane">
<p class="announce">新版本的"{{ appName }}"已经发布</p>
<p class="desc">{{ appName }} {{ info.latestVersion }} 可供安装您现在的版本是 {{ version }}要现在安装吗</p>
<p class="desc">{{ appName }} {{ info.latestVersion }} 可供安装您现在的版本是 {{
version
}}要现在安装吗</p>
<div class="update-details form">
<h4>更新信息</h4>
@@ -20,7 +23,7 @@
class="notes-box"
:rows="6"
readonly
resize="none" />
resize="none"/>
</div>
<div class="update-actions row">
@@ -41,7 +44,7 @@
<div v-else-if="stage === 'downloading'" class="update-content">
<div class="download-main">
<div class="download-icon">
<img src="/icon/icon.png" class="app-icon" alt="App Icon" />
<img src="/icon/icon.png" class="app-icon" alt="App Icon"/>
</div>
<div class="download-content">
<div class="download-info">
@@ -52,7 +55,7 @@
:percentage="prog.percentage"
:show-text="false"
:stroke-width="6"
color="#409EFF" />
color="#409EFF"/>
<div class="progress-details">
<span style="font-weight: 500">{{ prog.current }} / {{ prog.total }}</span>
<el-button size="small" @click="cancelDownload">取消</el-button>
@@ -64,7 +67,7 @@
<div v-else-if="stage === 'completed'" class="update-content">
<div class="update-header text-center">
<img src="/icon/icon.png" class="app-icon" alt="App Icon" />
<img src="/icon/icon.png" class="app-icon" alt="App Icon"/>
<h3>更新完成</h3>
<p>更新文件已下载将在重启后自动应用</p>
</div>
@@ -77,7 +80,7 @@
:percentage="100"
:show-text="false"
:stroke-width="6"
color="#67C23A" />
color="#67C23A"/>
</div>
<div class="update-buttons">
@@ -90,9 +93,9 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { updateApi } from '../../api/update'
import {ref, computed, onMounted, onUnmounted} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import {updateApi} from '../../api/update'
const props = defineProps<{ modelValue: boolean }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
@@ -105,8 +108,8 @@ const show = computed({
type Stage = 'check' | 'downloading' | 'completed'
const stage = ref<Stage>('check')
const appName = ref('我了个电商')
const version = ref('2.0.0')
const prog = ref({ percentage: 0, current: '0 MB', total: '0 MB', speed: '' })
const version = ref('')
const prog = ref({percentage: 0, current: '0 MB', total: '0 MB', speed: ''})
const info = ref({
latestVersion: '2.4.8',
downloadUrl: '',
@@ -117,49 +120,48 @@ const info = ref({
async function autoCheck() {
try {
ElMessage({ message: '正在检查更新...', type: 'info' })
version.value = await (window as any).electronAPI.getJarVersion()
const checkRes: any = await updateApi.checkUpdate(version.value)
const result = checkRes?.data || checkRes
try {
version.value = await updateApi.getVersion()
} catch (error) {
console.error('获取版本失败:', error)
version.value = '2.0.0'
if (!result.needUpdate) {
ElMessage.info('当前已是最新版本')
return
}
info.value = {
currentVersion: version.value,
latestVersion: '2.4.9',
downloadUrl: 'https://qiniu.pxdj.tashowz.com/2025/09/becac13811214c909d11162d2ff2c863.asar',
currentVersion: result.currentVersion,
latestVersion: result.latestVersion,
downloadUrl: result.downloadUrl || '',
updateNotes: '• 优化了用户界面体验\n• 修复了已知问题\n• 提升了系统稳定性\n• 轻量级更新仅替换app.asar',
hasUpdate: true
}
show.value = true
stage.value = 'check'
ElMessage({ message: '发现新版本', type: 'success' })
ElMessage.success('发现新版本')
} catch (error) {
console.error('检查更新失败:', error)
ElMessage({ message: '检查更新失败', type: 'error' })
ElMessage.error('检查更新失败')
}
}
async function start() {
if (!info.value.downloadUrl) {
ElMessage({ message: '下载链接不可用', type: 'error' });
return;
ElMessage.error('下载链接不可用')
return
}
stage.value = 'downloading';
prog.value = { percentage: 0, current: '0 MB', total: '0 MB', speed: '' };
stage.value = 'downloading'
prog.value = {percentage: 0, current: '0 MB', total: '0 MB', speed: ''}
(window as any).electronAPI.onDownloadProgress((progress: any) => {
;(window as any).electronAPI.onDownloadProgress((progress: any) => {
prog.value = {
percentage: progress.percentage || 0,
current: progress.current || '0 MB',
total: progress.total || '0 MB',
speed: progress.speed || ''
};
});
}
})
try {
const response = await (window as any).electronAPI.downloadUpdate(info.value.downloadUrl)
@@ -167,14 +169,14 @@ async function start() {
if (response.success) {
stage.value = 'completed'
prog.value.percentage = 100
ElMessage({ message: '下载完成', type: 'success' })
ElMessage.success('下载完成')
} else {
ElMessage({ message: '下载失败: ' + (response.error || '未知错误'), type: 'error' })
ElMessage.error('下载失败: ' + (response.error || '未知错误'))
stage.value = 'check'
}
} catch (error) {
console.error('下载失败:', error)
ElMessage({ message: '下载失败', type: 'error' })
ElMessage.error('下载失败')
stage.value = 'check'
}
}
@@ -195,37 +197,26 @@ async function cancelDownload() {
async function installUpdate() {
try {
await ElMessageBox.confirm(
'安装过程中程序将自动重启,请确保已保存所有工作。确定要立即安装更新吗?',
'确认安装',
{
confirmButtonText: '立即安装',
cancelButtonText: '取消',
type: 'warning'
}
'安装过程中程序将自动重启,请确保已保存所有工作。确定要立即安装更新吗?',
'确认安装',
{
confirmButtonText: '立即安装',
cancelButtonText: '取消',
type: 'warning'
}
)
const response = await (window as any).electronAPI.installUpdate()
if (response.success) {
ElMessage({ message: '应用即将重启', type: 'success' })
ElMessage.success('应用即将重启')
setTimeout(() => show.value = false, 1000)
} else {
ElMessage({ message: '重启失败: ' + (response.error || '未知错误'), type: 'error' })
stage.value = 'check'
}
} catch (error) {
if (error !== 'cancel') {
console.error('安装失败:', error)
ElMessage({ message: '安装失败', type: 'error' })
}
if (error !== 'cancel') ElMessage.error('安装失败')
}
}
onMounted(async () => {
try {
version.value = await updateApi.getVersion()
} catch (error) {
console.error('获取版本失败:', error)
}
version.value = await (window as any).electronAPI.getJarVersion()
})
onUnmounted(() => {
@@ -238,7 +229,7 @@ onUnmounted(() => {
position: fixed;
right: 10px;
bottom: 10px;
background: rgba(255,255,255,0.9);
background: rgba(255, 255, 255, 0.9);
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
@@ -273,11 +264,38 @@ onUnmounted(() => {
margin-bottom: 5px;
}
.left-pane { display: flex; flex-direction: column; align-items: flex-start; }
.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; }
.right-pane .desc { font-size: 13px; color: #6b7280; line-height: 1.6; margin: 0; word-break: break-word; }
.left-pane {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.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;
}
.right-pane .desc {
font-size: 13px;
color: #6b7280;
line-height: 1.6;
margin: 0;
word-break: break-word;
}
.update-header {
display: flex;
@@ -324,8 +342,13 @@ onUnmounted(() => {
margin: 12px 0 8px 0;
}
.update-details.form { max-height: none; }
.notes-box :deep(textarea.el-textarea__inner) { white-space: pre-wrap; }
.update-details.form {
max-height: none;
}
.notes-box :deep(textarea.el-textarea__inner) {
white-space: pre-wrap;
}
.update-details h4 {
font-size: 14px;
@@ -347,10 +370,24 @@ onUnmounted(() => {
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; }
.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;

View File

@@ -49,9 +49,7 @@ const selectedFileName = ref('')
const pendingFile = ref<File | null>(null)
const region = ref('JP')
const regionOptions = [
{ label: '日本 (Japan)', value: 'JP', flag: '🇯🇵' },
{ label: '美国 (USA)', value: 'US', flag: '🇺🇸' },
{ label: '中国 (China)', value: 'CN', flag: '🇨🇳' },
{ label: '日本 (Japan)', value: 'JP', flag: '🇯🇵' }
]
// 获取数据筛选:查询日期
const dateRange = ref<string[] | null>(null)
@@ -432,7 +430,7 @@ onMounted(loadLatest)
<div class="title">网站地区</div>
</div>
<div class="desc">请选择目标网站地区日本区</div>
<el-select v-model="region" placeholder="选择地区" size="small" style="width: 100%">
<el-select v-model="region" placeholder="选择地区" size="small" style="width: 100%" disabled>
<el-option v-for="opt in regionOptions" :key="opt.value" :label="opt.label" :value="opt.value">
<span style="margin-right:6px">{{ opt.flag }}</span>{{ opt.label }}
</el-option>
@@ -602,14 +600,14 @@ onMounted(loadLatest)
.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; margin-bottom: 8px; text-align: left; }
.steps-title { font-size: 14px; font-weight: 600; color: #303133; text-align: left; }
/* 卡片式步骤,与示例一致 */
.steps-flow { position: relative; }
.steps-flow:before { content: ''; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: #e5e7eb; }
.flow-item { position: relative; display: grid; grid-template-columns: 24px 1fr; gap: 10px; padding: 8px 0; }
.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 + .flow-item { border-top: 1px dashed #ebeef5; }
.flow-item .step-index { position: static; width: 24px; height: 24px; line-height: 24px; 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; }
.step-card { border: none; border-radius: 0; padding: 0; background: transparent; }
.step-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }

View File

@@ -538,12 +538,12 @@ export default {
.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-steps { position: relative; }
.step { display: grid; grid-template-columns: 24px 1fr; gap: 10px; position: relative; padding: 8px 0; }
.step { display: grid; grid-template-columns: 22px 1fr; gap: 10px; position: relative; padding: 8px 0; }
.step + .step { border-top: 1px dashed #ebeef5; }
.step-index { width: 24px; height: 24px; 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-title { font-size: 13px; color: #606266; margin-bottom: 6px; font-weight: 600; text-align: left; }
.aside-steps:before { content: ''; position: absolute; left: 12px; top: 0; bottom: 0; width: 2px; background: #e5e7eb; }
.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; }
.step-actions { margin-top: 8px; display: flex; gap: 8px; }
.step-accounts { position: relative; }