refactor(settings): 将设置面板拆分为多个组件
- 新增 DisplayModeSettings、ThemeSettings 和 InterfaceSettings 组件 - 重构 SettingsPanel 组件,使用新组件替换原有的设置项 - 删除 ThemeSwitcher 组件 - 更新样式和布局,优化用户体验
This commit is contained in:
parent
f65dd67087
commit
995eb85166
|
@ -0,0 +1,82 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h4>整体外观</h4>
|
||||||
|
<el-radio-group
|
||||||
|
v-model="currentDisplayMode"
|
||||||
|
@change="changeDisplayMode"
|
||||||
|
size="small"
|
||||||
|
class="display-mode-group"
|
||||||
|
>
|
||||||
|
<el-radio-button label="system">
|
||||||
|
<span class="icon system"></span> 默认
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button label="light">
|
||||||
|
<span class="icon light"></span> 浅色
|
||||||
|
</el-radio-button>
|
||||||
|
<el-radio-button label="dark">
|
||||||
|
<span class="icon dark"></span> 深色
|
||||||
|
</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useSettingsStore } from '@/store/settings';
|
||||||
|
import { ElRadioGroup, ElRadioButton } from 'element-plus';
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const currentDisplayMode = ref(settingsStore.displayMode);
|
||||||
|
|
||||||
|
const changeDisplayMode = (newMode) => {
|
||||||
|
settingsStore.updateDisplayMode(newMode);
|
||||||
|
currentDisplayMode.value = newMode;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.display-mode-group {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
gap: 8px;
|
||||||
|
.el-radio-button {
|
||||||
|
flex: 1;
|
||||||
|
:deep(.el-radio-button__inner) {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 8px 5px;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid var(--el-border-color);
|
||||||
|
|
||||||
|
&.system { background: linear-gradient(135deg, #fff 50%, #222 50%); border-color: #888; }
|
||||||
|
&.light { background-color: #fff; }
|
||||||
|
&.dark { background-color: #222; border-color: #555; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active :deep(.el-radio-button__inner) {
|
||||||
|
background-color: var(--el-color-primary-light-9);
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-radio-button + .el-radio-button {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h4>界面设置</h4>
|
||||||
|
<div class="setting-item">
|
||||||
|
<span>启用动画效果</span>
|
||||||
|
<el-switch
|
||||||
|
v-model="animationsEnabled"
|
||||||
|
@change="toggleAnimationsSetting"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useSettingsStore } from '@/store/settings';
|
||||||
|
import { ElSwitch } from 'element-plus';
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const animationsEnabled = ref(settingsStore.animationsEnabled);
|
||||||
|
|
||||||
|
const toggleAnimationsSetting = (value) => {
|
||||||
|
settingsStore.toggleAnimations(value);
|
||||||
|
animationsEnabled.value = value;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.setting-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,243 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="settings-popover">
|
<div class="settings-popover">
|
||||||
<h4>整体外观</h4>
|
<DisplayModeSettings />
|
||||||
<el-radio-group
|
|
||||||
v-model="currentDisplayMode"
|
|
||||||
@change="changeDisplayMode"
|
|
||||||
size="small"
|
|
||||||
class="display-mode-group"
|
|
||||||
>
|
|
||||||
<el-radio-button label="system">
|
|
||||||
<span class="icon system"></span> 系统默认
|
|
||||||
</el-radio-button>
|
|
||||||
<el-radio-button label="light">
|
|
||||||
<span class="icon light"></span> 浅色
|
|
||||||
</el-radio-button>
|
|
||||||
<el-radio-button label="dark">
|
|
||||||
<span class="icon dark"></span> 深色
|
|
||||||
</el-radio-button>
|
|
||||||
</el-radio-group>
|
|
||||||
|
|
||||||
<el-divider />
|
<el-divider />
|
||||||
|
<ThemeSettings />
|
||||||
<h4>主题</h4>
|
|
||||||
<div class="theme-grid">
|
|
||||||
<div
|
|
||||||
v-for="(theme, name) in filteredThemes"
|
|
||||||
:key="name"
|
|
||||||
class="theme-option"
|
|
||||||
:class="{ 'active': currentTheme === name }"
|
|
||||||
@click="changeTheme(name)"
|
|
||||||
>
|
|
||||||
<div class="theme-preview" :style="{ backgroundColor: theme.primary }"></div>
|
|
||||||
<span class="theme-label">{{ getThemeLabel(name) }}</span>
|
|
||||||
</div>
|
|
||||||
<!-- 可以添加 '选择一种颜色...' 的选项 -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-divider />
|
<el-divider />
|
||||||
|
<InterfaceSettings />
|
||||||
<h4>界面设置</h4>
|
|
||||||
<div class="setting-item">
|
|
||||||
<span>启用动画效果</span>
|
|
||||||
<el-switch
|
|
||||||
v-model="animationsEnabled"
|
|
||||||
@change="toggleAnimationsSetting"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 可以添加更多设置项 -->
|
|
||||||
<!--
|
|
||||||
<el-divider />
|
|
||||||
<h4>其他设置</h4>
|
|
||||||
<div class="setting-item">
|
|
||||||
<span>收起侧边栏</span>
|
|
||||||
<el-switch v-model="sidebarCollapsed" @change="toggleSidebarSetting" />
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ElDivider } from 'element-plus';
|
||||||
import { useSettingsStore } from '@/store/settings';
|
import DisplayModeSettings from './DisplayModeSettings.vue';
|
||||||
// Import themes object and omit 'dark' theme from selection
|
import ThemeSettings from './ThemeSettings.vue';
|
||||||
import { themes as availableThemes } from '@/styles/theme';
|
import InterfaceSettings from './InterfaceSettings.vue';
|
||||||
import {
|
|
||||||
ElButton, ElDropdown, ElDropdownMenu, ElDropdownItem,
|
|
||||||
ElDivider, ElSwitch, ElIcon, ElRadioGroup, ElRadioButton
|
|
||||||
} from 'element-plus';
|
|
||||||
import { ArrowDown } from '@element-plus/icons-vue'; // Keep if needed elsewhere, remove if not
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
// --- Display Mode ---
|
|
||||||
const currentDisplayMode = ref(settingsStore.displayMode);
|
|
||||||
|
|
||||||
const changeDisplayMode = (newMode) => {
|
|
||||||
settingsStore.updateDisplayMode(newMode);
|
|
||||||
// currentDisplayMode will update reactively from the store if using computed,
|
|
||||||
// but direct update here ensures immediate visual feedback if needed
|
|
||||||
currentDisplayMode.value = newMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Theme Settings ---
|
|
||||||
const currentTheme = ref(settingsStore.theme);
|
|
||||||
|
|
||||||
// Filter out the 'dark' theme as it's handled by displayMode
|
|
||||||
const filteredThemes = computed(() => {
|
|
||||||
const { dark, ...rest } = availableThemes;
|
|
||||||
return rest;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const themeLabels = {
|
|
||||||
default: '默认',
|
|
||||||
// dark: '深色', // No longer selectable as a base theme color palette
|
|
||||||
fresh: '清新',
|
|
||||||
warm: '暖色',
|
|
||||||
business: '商务',
|
|
||||||
// Add labels for other themes from your image if they exist in styles/theme.js
|
|
||||||
// e.g., cool: '凉风', pink: '柔和的粉色', bubblegum: '泡泡糖', sunny: '晴天', etc.
|
|
||||||
// Make sure the keys match the keys in your `themes` object in theme.js
|
|
||||||
};
|
|
||||||
|
|
||||||
const getThemeLabel = (themeName) => {
|
|
||||||
return themeLabels[themeName] || themeName;
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeTheme = (newTheme) => {
|
|
||||||
settingsStore.updateTheme(newTheme); // Use the action
|
|
||||||
currentTheme.value = newTheme; // Update local ref for immediate feedback
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Animation Settings ---
|
|
||||||
const animationsEnabled = ref(settingsStore.animationsEnabled);
|
|
||||||
|
|
||||||
const toggleAnimationsSetting = (value) => {
|
|
||||||
settingsStore.toggleAnimations(value); // Use the action
|
|
||||||
animationsEnabled.value = value; // Update local ref
|
|
||||||
};
|
|
||||||
|
|
||||||
// Watch for external store changes (e.g., reset button) if necessary
|
|
||||||
// settingsStore.$subscribe((mutation, state) => {
|
|
||||||
// currentDisplayMode.value = state.displayMode;
|
|
||||||
// currentTheme.value = state.theme;
|
|
||||||
// animationsEnabled.value = state.animationsEnabled;
|
|
||||||
// });
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.settings-popover {
|
.settings-popover {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
min-width: 240px; // Increased width for better layout
|
min-width: 240px;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600; // Slightly bolder heading
|
font-weight: 600;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-mode-group {
|
.el-divider {
|
||||||
display: flex; // Use flex for equal spacing
|
margin: 18px 0;
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 5px; // Add some space below
|
|
||||||
|
|
||||||
.el-radio-button {
|
|
||||||
flex: 1; // Make buttons fill the space equally
|
|
||||||
:deep(.el-radio-button__inner) {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center; // Center content
|
|
||||||
border-radius: 4px !important; // Slightly round corners
|
|
||||||
border: 1px solid var(--el-border-color-lighter); // Use variable for border
|
|
||||||
box-shadow: none; // Remove default shadow
|
|
||||||
padding: 8px 5px; // Adjust padding
|
|
||||||
font-size: 12px; // Smaller font size
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: inline-block;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
margin-right: 5px;
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--el-border-color);
|
|
||||||
|
|
||||||
&.system { background: linear-gradient(135deg, #fff 50%, #222 50%); border-color: #888; }
|
|
||||||
&.light { background-color: #fff; }
|
|
||||||
&.dark { background-color: #222; border-color: #555; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Style for selected button
|
|
||||||
&.is-active :deep(.el-radio-button__inner) {
|
|
||||||
background-color: var(--el-color-primary-light-9);
|
|
||||||
border-color: var(--el-color-primary);
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove spacing between buttons created by inline-block nature
|
|
||||||
.el-radio-button + .el-radio-button {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); // Responsive grid
|
|
||||||
gap: 10px; // Gap between items
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-option {
|
|
||||||
cursor: pointer;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-align: center;
|
|
||||||
transition: border-color 0.2s ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--el-border-color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-preview {
|
|
||||||
width: 100%;
|
|
||||||
height: 30px; // Adjust height as needed
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
border: 1px solid var(--el-border-color-lighter); // Add subtle border
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-label {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
display: block; // Ensure label is on new line
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.setting-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 12px; // Consistent spacing
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--el-text-color-regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-divider {
|
|
||||||
margin: 18px 0; // Increase divider margin
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h4>主题</h4>
|
||||||
|
<div class="theme-grid">
|
||||||
|
<div
|
||||||
|
v-for="(theme, name) in filteredThemes"
|
||||||
|
:key="name"
|
||||||
|
class="theme-option"
|
||||||
|
:class="{ 'active': currentTheme === name }"
|
||||||
|
@click="changeTheme(name)"
|
||||||
|
>
|
||||||
|
<div class="theme-preview" :style="{ backgroundColor: theme.primary }"></div>
|
||||||
|
<span class="theme-label">{{ getThemeLabel(name) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useSettingsStore } from '@/store/settings';
|
||||||
|
import { themes as availableThemes } from '@/styles/theme';
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const currentTheme = ref(settingsStore.theme);
|
||||||
|
|
||||||
|
const filteredThemes = computed(() => {
|
||||||
|
const { dark, ...rest } = availableThemes;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
|
||||||
|
const themeLabels = {
|
||||||
|
default: '默认',
|
||||||
|
fresh: '清新',
|
||||||
|
warm: '暖色',
|
||||||
|
business: '商务',
|
||||||
|
};
|
||||||
|
|
||||||
|
const getThemeLabel = (themeName) => {
|
||||||
|
return themeLabels[themeName] || themeName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeTheme = (newTheme) => {
|
||||||
|
settingsStore.updateTheme(newTheme);
|
||||||
|
currentTheme.value = newTheme;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.theme-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-option {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-align: center;
|
||||||
|
transition: border-color 0.2s ease-in-out;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--el-border-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview {
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,56 +0,0 @@
|
||||||
<template>
|
|
||||||
<el-dropdown trigger="click" @command="handleCommand">
|
|
||||||
<el-button type="primary">
|
|
||||||
主题切换
|
|
||||||
<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
|
||||||
</el-button>
|
|
||||||
<template #dropdown>
|
|
||||||
<el-dropdown-menu>
|
|
||||||
<el-dropdown-item v-for="(theme, name) in themes" :key="name" :command="name">
|
|
||||||
<div class="theme-item">
|
|
||||||
<span class="theme-name">{{ name }}</span>
|
|
||||||
<div class="theme-colors">
|
|
||||||
<span v-for="(color, type) in theme" :key="type"
|
|
||||||
class="color-dot"
|
|
||||||
:style="{ backgroundColor: color }">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dropdown-item>
|
|
||||||
</el-dropdown-menu>
|
|
||||||
</template>
|
|
||||||
</el-dropdown>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ArrowDown } from '@element-plus/icons-vue'
|
|
||||||
import { themes, setTheme } from '@/styles/theme'
|
|
||||||
|
|
||||||
const handleCommand = (themeName) => {
|
|
||||||
setTheme(themeName)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.theme-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-name {
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-colors {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-dot {
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
</style>
|
|
Loading…
Reference in New Issue