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>
|
||||
<div class="settings-popover">
|
||||
<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>
|
||||
|
||||
<DisplayModeSettings />
|
||||
<el-divider />
|
||||
|
||||
<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>
|
||||
|
||||
<ThemeSettings />
|
||||
<el-divider />
|
||||
|
||||
<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>
|
||||
-->
|
||||
<InterfaceSettings />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useSettingsStore } from '@/store/settings';
|
||||
// Import themes object and omit 'dark' theme from selection
|
||||
import { themes as availableThemes } from '@/styles/theme';
|
||||
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;
|
||||
// });
|
||||
|
||||
import { ElDivider } from 'element-plus';
|
||||
import DisplayModeSettings from './DisplayModeSettings.vue';
|
||||
import ThemeSettings from './ThemeSettings.vue';
|
||||
import InterfaceSettings from './InterfaceSettings.vue';
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.settings-popover {
|
||||
padding: 15px;
|
||||
min-width: 240px; // Increased width for better layout
|
||||
min-width: 240px;
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 600; // Slightly bolder heading
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.display-mode-group {
|
||||
display: flex; // Use flex for equal spacing
|
||||
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;
|
||||
}
|
||||
.el-divider {
|
||||
margin: 18px 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>
|
|
@ -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