mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-03 14:20:50 +08:00
feat: 优化设置页面
This commit is contained in:
79
src/renderer/views/set/SettingItem.vue
Normal file
79
src/renderer/views/set/SettingItem.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center justify-between p-4 rounded-lg transition-all bg-light dark:bg-dark text-gray-900 dark:text-white border border-gray-200 dark:border-gray-700 hover:bg-gray-50 hover:dark:bg-gray-800"
|
||||
:class="[
|
||||
// 移动端垂直布局
|
||||
{ 'max-md:flex-col max-md:items-start max-md:gap-3 max-md:p-3': !inline },
|
||||
// 可点击样式
|
||||
{
|
||||
'cursor-pointer hover:text-green-500 hover:!bg-green-50 hover:dark:!bg-green-900/30':
|
||||
clickable
|
||||
},
|
||||
customClass
|
||||
]"
|
||||
@click="handleClick"
|
||||
>
|
||||
<!-- 左侧:标题和描述 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-base font-medium mb-1">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</div>
|
||||
<div
|
||||
v-if="description || $slots.description"
|
||||
class="text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<slot name="description">{{ description }}</slot>
|
||||
</div>
|
||||
<!-- 额外内容插槽 -->
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:操作区 -->
|
||||
<div
|
||||
v-if="$slots.action || $slots.default"
|
||||
class="flex items-center gap-2 flex-shrink-0"
|
||||
:class="{ 'max-md:w-full max-md:justify-end': !inline }"
|
||||
>
|
||||
<slot name="action">
|
||||
<slot></slot>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'SettingItem'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** 设置项标题 */
|
||||
title?: string;
|
||||
/** 设置项描述 */
|
||||
description?: string;
|
||||
/** 是否可点击 */
|
||||
clickable?: boolean;
|
||||
/** 是否保持水平布局(不响应移动端) */
|
||||
inline?: boolean;
|
||||
/** 自定义类名 */
|
||||
customClass?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '',
|
||||
description: '',
|
||||
clickable: false,
|
||||
inline: false,
|
||||
customClass: ''
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
click: [event: MouseEvent];
|
||||
}>();
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (props.clickable) {
|
||||
emit('click', event);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
47
src/renderer/views/set/SettingNav.vue
Normal file
47
src/renderer/views/set/SettingNav.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div
|
||||
class="w-32 h-full flex-shrink-0 border-r border-gray-200 dark:border-gray-700 bg-light dark:bg-dark"
|
||||
>
|
||||
<div
|
||||
v-for="section in sections"
|
||||
:key="section.id"
|
||||
class="px-4 py-2.5 cursor-pointer text-sm transition-colors duration-200 border-l-2"
|
||||
:class="[
|
||||
currentSection === section.id
|
||||
? 'text-primary dark:text-white bg-gray-50 dark:bg-dark-100 !border-primary font-medium'
|
||||
: 'text-gray-600 dark:text-gray-400 border-transparent hover:text-primary hover:dark:text-white hover:bg-gray-50 hover:dark:bg-dark-100 hover:border-gray-300'
|
||||
]"
|
||||
@click="handleClick(section.id)"
|
||||
>
|
||||
{{ section.title }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'SettingNav'
|
||||
});
|
||||
|
||||
export interface NavSection {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/** 导航项列表 */
|
||||
sections: NavSection[];
|
||||
/** 当前激活的分组 ID */
|
||||
currentSection: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
navigate: [sectionId: string];
|
||||
}>();
|
||||
|
||||
const handleClick = (sectionId: string) => {
|
||||
emit('navigate', sectionId);
|
||||
};
|
||||
</script>
|
||||
42
src/renderer/views/set/SettingSection.vue
Normal file
42
src/renderer/views/set/SettingSection.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div :id="id" :ref="setRef" class="mb-6 scroll-mt-4">
|
||||
<!-- 分组标题 -->
|
||||
<div class="text-base font-medium mb-4 text-gray-600 dark:text-white">
|
||||
<slot name="title">{{ title }}</slot>
|
||||
</div>
|
||||
|
||||
<!-- 设置项列表 -->
|
||||
<div class="space-y-4 max-md:space-y-3">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ComponentPublicInstance } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'SettingSection'
|
||||
});
|
||||
|
||||
interface Props {
|
||||
/** 分组 ID,用于导航定位 */
|
||||
id?: string;
|
||||
/** 分组标题 */
|
||||
title?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
id: '',
|
||||
title: ''
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
ref: [el: Element | null];
|
||||
}>();
|
||||
|
||||
// 暴露 ref 给父组件
|
||||
const setRef = (el: Element | ComponentPublicInstance | null) => {
|
||||
emit('ref', el as Element | null);
|
||||
};
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user