mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
feat: 优化设置页面
This commit is contained in:
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
+656
-936
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user