Files
erp_sb/electron-vue-template/src/renderer/components/common/SettingsDialog.vue
zhangzijienbplus 7e065c1a0b feat(erp): 实现按用户地区隔离的最新产品查询逻辑
- 修改AmazonController以支持按用户和区域筛选最新会话数据
- 更新AmazonProductRepository中的findLatestProducts方法,增加region参数实现数据隔离
-优化AmazonScrapingServiceImpl的数据处理流程,增强缓存清理机制
- 调整GenmaiServiceImpl的token验证逻辑并改进Chrome启动配置- 升级系统版本至2.5.5并完善相关依赖管理
- 改进前端设置对话框中关于缓存清理描述的信息准确性
-重构SystemController接口,移除不必要的用户名参数传递- 强化GenmaiAccountController和服务层的安全校验逻辑
2025-10-27 13:34:25 +08:00

1189 lines
30 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
getSettings,
saveSettings,
savePlatformSettings,
type Platform,
type PlatformExportSettings
} from '../../utils/settings'
import { feedbackApi } from '../../api/feedback'
import { getToken, getUsernameFromToken } from '../../utils/token'
import { getOrCreateDeviceId } from '../../utils/deviceId'
import { updateApi } from '../../api/update'
interface Props {
modelValue: boolean
}
interface Emits {
(e: 'update:modelValue', value: boolean): void
(e: 'autoUpdateChanged', value: boolean): void
(e: 'openUpdateDialog'): 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<string>('export')
const settingsMainRef = ref<HTMLElement | null>(null)
const isScrolling = ref(false)
// 反馈表单
const feedbackContent = ref('')
const selectedLogDate = ref('')
const feedbackSubmitting = ref(false)
const logDates = ref<string[]>([])
// 关闭行为配置
const closeAction = ref<'quit' | 'minimize' | 'tray'>('quit')
// 缓存相关
const clearingCache = ref(false)
// 启动配置
const autoLaunch = ref(false)
const launchMinimized = ref(false)
// 更新相关
const currentVersion = ref('')
const latestVersion = ref('')
const updateNotes = ref('')
const checkingUpdate = ref(false)
const hasUpdate = ref(false)
const autoUpdate = ref(false)
const show = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
// 监听对话框打开,重新加载当前用户的设置
watch(() => props.modelValue, (newVal) => {
if (newVal) {
loadAllSettings()
loadCurrentVersion()
}
})
// 选择导出路径
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]
}
}
// 保存设置
async function saveAllSettings() {
const username = getUsernameFromToken()
const oldSettings = getSettings(username)
const autoUpdateChanged = oldSettings.autoUpdate !== autoUpdate.value
// 1. 保存到 localStorage按账号隔离
saveSettings({
platforms: platformSettings.value,
autoUpdate: autoUpdate.value,
closeAction: closeAction.value,
autoLaunch: autoLaunch.value,
launchMinimized: launchMinimized.value
}, username)
// 2. 同步到 Electron 主进程(控制应用行为)
await (window as any).electronAPI.setCloseAction(closeAction.value)
await (window as any).electronAPI.setLaunchConfig({
autoLaunch: autoLaunch.value,
launchMinimized: launchMinimized.value
})
ElMessage({ message: '设置已保存', type: 'success' })
show.value = false
if (autoUpdateChanged) {
emit('autoUpdateChanged', autoUpdate.value)
}
}
// 加载设置
function loadAllSettings() {
const username = getUsernameFromToken()
const settings = getSettings(username)
platformSettings.value = {
amazon: { ...settings.platforms.amazon },
rakuten: { ...settings.platforms.rakuten },
zebra: { ...settings.platforms.zebra }
}
autoUpdate.value = settings.autoUpdate ?? false
closeAction.value = settings.closeAction ?? 'quit'
autoLaunch.value = settings.autoLaunch ?? false
launchMinimized.value = settings.launchMinimized ?? false
}
// 重置单个平台设置
function resetPlatformSettings(platform: Platform) {
platformSettings.value[platform] = {
exportPath: ''
}
}
// 重置所有设置
async function resetAllSettings() {
try {
await ElMessageBox.confirm(
'确定要重置所有设置吗?此操作不可恢复。',
'确认重置',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
platforms.forEach(platform => {
resetPlatformSettings(platform.key)
})
autoUpdate.value = false
closeAction.value = 'quit'
autoLaunch.value = false
launchMinimized.value = false
ElMessage.success('所有设置已重置')
} catch {
// 用户取消操作
}
}
// 清理缓存
async function handleClearCache() {
try {
await ElMessageBox.confirm(
'确定要清理客户端缓存吗?将清除本地设备的所有缓存数据、更新文件及相关状态(不影响登录状态)',
'确认清理',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
clearingCache.value = true
// 1. 清理后端数据库缓存
const result = await (window as any).electronAPI.clearCache()
if (!result || result.code !== 0) {
ElMessage.error('缓存清理失败')
return
}
// 2. 清除更新相关的 localStorage 状态
localStorage.removeItem('skipped_version')
localStorage.removeItem('remind_later_time')
// 3. 清除已下载的更新文件
await (window as any).electronAPI.clearUpdateFiles()
ElMessage.success('缓存清理成功')
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error('缓存清理失败')
}
} finally {
clearingCache.value = false
}
}
// 滚动到指定区域
function scrollToSection(sectionKey: string) {
if (isScrolling.value) return
const element = document.getElementById(`section-${sectionKey}`)
if (element && settingsMainRef.value) {
isScrolling.value = true
activeTab.value = sectionKey
const container = settingsMainRef.value
// 计算元素相对于容器的位置
const containerRect = container.getBoundingClientRect()
const elementRect = element.getBoundingClientRect()
const relativeTop = elementRect.top - containerRect.top
const targetTop = container.scrollTop + relativeTop - 14 // 14是容器的padding
const startTop = container.scrollTop
const distance = targetTop - startTop
const duration = 800 // 增加持续时间,使滚动更慢更平滑
let start: number | null = null
function animation(currentTime: number) {
if (start === null) start = currentTime
const timeElapsed = currentTime - start
const progress = Math.min(timeElapsed / duration, 1)
// 使用 easeInOutCubic 缓动函数
const easing = progress < 0.5
? 4 * progress * progress * progress
: 1 - Math.pow(-2 * progress + 2, 3) / 2
container.scrollTop = startTop + distance * easing
if (progress < 1) {
requestAnimationFrame(animation)
} else {
isScrolling.value = false
}
}
requestAnimationFrame(animation)
}
}
// 获取可用的日志日期列表
async function loadLogDates() {
try {
const result = await (window as any).electronAPI.getLogDates()
if (result && result.dates) {
logDates.value = result.dates
}
} catch (error) {
console.warn('获取日志日期列表失败:', error)
}
}
// 检查更新
async function checkForUpdates() {
try {
checkingUpdate.value = true
currentVersion.value = await (window as any).electronAPI.getJarVersion()
const checkRes: any = await updateApi.checkUpdate(currentVersion.value)
const result = checkRes?.data || checkRes
if (!result.needUpdate) {
hasUpdate.value = false
updateNotes.value = ''
ElMessage.success('当前已是最新版本')
} else {
hasUpdate.value = true
latestVersion.value = result.latestVersion
updateNotes.value = result.updateNotes || ''
ElMessage.success('发现新版本:' + result.latestVersion)
}
} catch (error: any) {
ElMessage.error('检查更新失败:' + (error?.message || '网络错误'))
} finally {
checkingUpdate.value = false
}
}
// 处理立即升级按钮点击
function handleUpgradeClick() {
show.value = false
emit('openUpdateDialog')
}
// 加载当前版本
async function loadCurrentVersion() {
try {
currentVersion.value = await (window as any).electronAPI.getJarVersion()
} catch (error) {
console.warn('获取当前版本失败:', error)
}
}
// 提交反馈
async function submitFeedback() {
if (!feedbackContent.value.trim()) {
ElMessage.warning('请输入反馈内容')
return
}
const username = getUsernameFromToken()
if (!username) {
ElMessage.warning('请先登录')
return
}
try {
feedbackSubmitting.value = true
const deviceId = await getOrCreateDeviceId()
let logFile: File | undefined = undefined
// 如果选择了日志日期,读取日志文件
if (selectedLogDate.value) {
try {
const result = await (window as any).electronAPI.readLogFile(selectedLogDate.value)
if (result && result.content) {
// 将日志内容转换为File对象
const blob = new Blob([result.content], { type: 'text/plain' })
logFile = new File([blob], `spring-boot-${selectedLogDate.value}.log`, { type: 'text/plain' })
}
} catch (error) {
console.warn('读取日志文件失败:', error)
}
}
await feedbackApi.submit({
username,
deviceId,
feedbackContent: feedbackContent.value,
logDate: selectedLogDate.value || undefined,
logFile
})
ElMessage.success('反馈提交成功,感谢您的反馈!')
feedbackContent.value = ''
selectedLogDate.value = ''
show.value = false
} catch (error: any) {
ElMessage.error(error?.message || '提交失败,请稍后重试')
} finally {
feedbackSubmitting.value = false
}
}
onMounted(() => {
loadAllSettings()
loadLogDates()
loadCurrentVersion()
})
</script>
<template>
<Teleport to="body">
<Transition name="dialog-fade">
<div v-if="show" class="dialog-mask" @click="show = false">
<div class="dialog-container" @click.stop>
<div class="settings-layout">
<!-- 左侧导航 -->
<div class="settings-sidebar">
<div class="sidebar-header">应用设置</div>
<div
:class="['sidebar-item', { active: activeTab === 'export' }]"
@click="scrollToSection('export')">
<span class="sidebar-icon">📤</span>
<span class="sidebar-text">导出</span>
</div>
<div
:class="['sidebar-item', { active: activeTab === 'update' }]"
@click="scrollToSection('update')">
<span class="sidebar-icon">🔄</span>
<span class="sidebar-text">更新</span>
</div>
<div
:class="['sidebar-item', { active: activeTab === 'cache' }]"
@click="scrollToSection('cache')">
<span class="sidebar-icon">💾</span>
<span class="sidebar-text">缓存</span>
</div>
<div
:class="['sidebar-item', { active: activeTab === 'startup' }]"
@click="scrollToSection('startup')">
<span class="sidebar-icon">🚀</span>
<span class="sidebar-text">启动</span>
</div>
<div
:class="['sidebar-item', { active: activeTab === 'feedback' }]"
@click="scrollToSection('feedback')">
<span class="sidebar-icon">💬</span>
<span class="sidebar-text">反馈</span>
</div>
<div
:class="['sidebar-item', { active: activeTab === 'general' }]"
@click="scrollToSection('general')">
<span class="sidebar-icon"></span>
<span class="sidebar-text">通用</span>
</div>
</div>
<!-- 右侧内容区域 -->
<div class="settings-content">
<!-- 滚动内容 -->
<div class="settings-main" ref="settingsMainRef">
<!-- 导出设置 -->
<div id="section-export" class="setting-section export-section" @mouseenter="activeTab = 'export'">
<div class="section-title">导出设置</div>
<div class="section-subtitle-text">配置各平台默认导出路径</div>
<!-- 遍历显示所有平台 -->
<div
v-for="platform in platforms"
:key="platform.key"
class="platform-item">
<div class="platform-header">
<span class="platform-icon">{{ platform.icon }}</span>
<span class="platform-name">{{ platform.name }}</span>
</div>
<div class="input-button-row">
<el-input
v-model="platformSettings[platform.key].exportPath"
placeholder="留空时自动弹出保存对话框"
readonly
class="path-input">
</el-input>
<el-button
size="small"
type="primary"
@click="selectExportPath(platform.key)">
浏览
</el-button>
<el-button
size="small"
style="margin-left: 3px"
@click="resetPlatformSettings(platform.key)">
重置
</el-button>
</div>
</div>
</div>
<!-- 更新设置 -->
<div id="section-update" class="setting-section" @mouseenter="activeTab = 'update'">
<div class="section-header">
<div class="section-title">软件更新</div>
</div>
<div class="setting-item">
<div class="update-info-row">
<div class="version-display">
<span class="version-label">{{ hasUpdate ? '发现新版本:' : '当前版本:' }}</span>
<span class="version-value">{{ hasUpdate ? latestVersion : (currentVersion || '-') }}</span>
<el-button
type="primary"
size="small"
:loading="checkingUpdate"
@click="hasUpdate ? handleUpgradeClick() : checkForUpdates()">
{{ checkingUpdate ? '检查中...' : (hasUpdate ? '立即升级' : '检查更新') }}
</el-button>
</div>
</div>
</div>
<div class="setting-item" style="margin-top: 10px;">
<div class="checkbox-row">
<el-checkbox v-model="autoUpdate" size="default">
自动安装更新
</el-checkbox>
</div>
<div class="setting-desc" style="margin-top: 4px;">
如果自动安装将仅会安装到新版本后但需手动点击"重启升级"后才可升级
</div>
</div>
<div v-if="hasUpdate" class="setting-item" style="margin-top: 10px;">
<div class="update-notes-section">
<h4 class="notes-title">更新详情</h4>
<div class="notes-content">{{ updateNotes }}</div>
</div>
</div>
</div>
<!-- 缓存设置 -->
<div id="section-cache" class="setting-section" @mouseenter="activeTab = 'cache'">
<div class="section-title">缓存设置</div>
<div class="setting-item">
<div class="setting-row">
<span class="cache-desc">清理本地设备的所有缓存数据不影响登录状态</span>
<el-button
type="primary"
size="small"
:loading="clearingCache"
:disabled="clearingCache"
@click="handleClearCache">
{{ clearingCache ? '清理中' : '清理缓存' }}
</el-button>
</div>
</div>
</div>
<!-- 启动设置 -->
<div id="section-startup" class="setting-section" @mouseenter="activeTab = 'startup'">
<div class="section-title">启动</div>
<div class="setting-item">
<div class="checkbox-row">
<el-checkbox v-model="autoLaunch" size="default">
开机时自动启动
</el-checkbox>
</div>
</div>
<div class="setting-item" style="margin-top: 10px;">
<div class="checkbox-row">
<el-checkbox v-model="launchMinimized" size="default">
启动后最小化到程序坞
</el-checkbox>
</div>
</div>
</div>
<!-- 反馈页面 -->
<div id="section-feedback" class="setting-section" @mouseenter="activeTab = 'feedback'">
<div class="section-title">反馈</div>
<div class="feedback-form">
<div class="setting-item">
<el-input
v-model="feedbackContent"
type="textarea"
:rows="5"
placeholder="描述问题或建议..."
maxlength="1000"
show-word-limit
/>
</div>
<div class="setting-item">
<div class="input-button-row">
<el-select
v-model="selectedLogDate"
placeholder="附带日志(可选)"
clearable
class="path-input">
<el-option
v-for="date in logDates"
:key="date"
:label="date"
:value="date"
/>
</el-select>
<el-button
type="primary"
:loading="feedbackSubmitting"
@click="submitFeedback">
{{ feedbackSubmitting ? '提交中' : '提交' }}
</el-button>
</div>
</div>
</div>
</div>
<!-- 通用设置 -->
<div id="section-general" class="setting-section" @mouseenter="activeTab = 'general'">
<div class="section-header">
<div class="section-title">关闭</div>
<div class="section-subtitle">当关闭应用程序时</div>
</div>
<div class="setting-item">
<el-radio-group v-model="closeAction" class="compact-radio-group">
<el-radio value="tray" size="default">在后台运行此程序</el-radio>
<el-radio value="quit" size="default">完全退出此程序</el-radio>
</el-radio-group>
</div>
</div>
</div>
<!-- 底部按钮 -->
<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>
</div>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
/* 对话框遮罩 */
.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;
}
/* 对话框容器 */
.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;
}
/* 对话框动画 */
.dialog-fade-enter-active,
.dialog-fade-leave-active {
transition: opacity 0.2s ease;
}
.dialog-fade-enter-active .dialog-container,
.dialog-fade-leave-active .dialog-container {
transition: transform 0.2s ease;
}
.dialog-fade-enter-from,
.dialog-fade-leave-to {
opacity: 0;
}
.dialog-fade-enter-from .dialog-container,
.dialog-fade-leave-to .dialog-container {
transform: scale(0.95);
}
.settings-layout {
display: flex;
height: 100%;
flex: 1;
overflow: hidden;
}
/* 左侧导航 */
.settings-sidebar {
width: 130px;
flex-shrink: 0;
background: #FFFFFF;
border-right: 1px solid #E5E6EB;
padding: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.sidebar-header {
padding: 12px 12px;
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 {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 滚动内容区 */
.settings-main {
flex: 1;
padding: 12px;
overflow-y: auto;
background: #F9FAFB;
}
.settings-main::-webkit-scrollbar {
width: 6px;
}
.settings-main::-webkit-scrollbar-track {
background: transparent;
}
.settings-main::-webkit-scrollbar-thumb {
background: #C9CDD4;
border-radius: 3px;
}
.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;
}
.section-header {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 10px;
}
.section-title {
font-size: 14px;
font-weight: 600;
color: #1F2937;
text-align: left;
margin-bottom: 8px;
}
.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;
}
.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 {
display: flex;
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
.compact-radio-group :deep(.el-radio) {
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 {
display: flex;
flex-direction: column;
gap: 8px;
}
.version-display {
display: flex;
align-items: center;
gap: 10px;
}
.version-label {
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 {
margin-bottom: 8px;
padding-bottom: 8px;
border-bottom: 1px solid #E5E6EB;
}
.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;
}
</style>
<script lang="ts">
export default {
name: 'SettingsDialog'
}
</script>