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,116 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
v-model:show="visible"
|
||||||
|
preset="dialog"
|
||||||
|
:title="t('settings.system.cache')"
|
||||||
|
:positive-text="t('common.confirm')"
|
||||||
|
:negative-text="t('common.cancel')"
|
||||||
|
@positive-click="handleConfirm"
|
||||||
|
@negative-click="handleCancel"
|
||||||
|
>
|
||||||
|
<n-space vertical>
|
||||||
|
<p>{{ t('settings.system.cacheClearTitle') }}</p>
|
||||||
|
<n-checkbox-group v-model:value="selectedTypes">
|
||||||
|
<n-space vertical>
|
||||||
|
<n-checkbox
|
||||||
|
v-for="option in clearCacheOptions"
|
||||||
|
:key="option.key"
|
||||||
|
:value="option.key"
|
||||||
|
:label="option.label"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<div>
|
||||||
|
<div>{{ t(`settings.system.cacheTypes.${option.key}.label`) }}</div>
|
||||||
|
<div class="text-gray-400 text-sm">
|
||||||
|
{{ t(`settings.system.cacheTypes.${option.key}.description`) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-checkbox>
|
||||||
|
</n-space>
|
||||||
|
</n-checkbox-group>
|
||||||
|
</n-space>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, defineProps, defineEmits } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:show', 'confirm']);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const visible = ref(props.show);
|
||||||
|
const selectedTypes = ref<string[]>([]);
|
||||||
|
|
||||||
|
const clearCacheOptions = ref([
|
||||||
|
{
|
||||||
|
label: t('settings.system.cacheTypes.history.label'),
|
||||||
|
key: 'history',
|
||||||
|
description: t('settings.system.cacheTypes.history.description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.system.cacheTypes.favorite.label'),
|
||||||
|
key: 'favorite',
|
||||||
|
description: t('settings.system.cacheTypes.favorite.description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.system.cacheTypes.user.label'),
|
||||||
|
key: 'user',
|
||||||
|
description: t('settings.system.cacheTypes.user.description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.system.cacheTypes.settings.label'),
|
||||||
|
key: 'settings',
|
||||||
|
description: t('settings.system.cacheTypes.settings.description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.system.cacheTypes.downloads.label'),
|
||||||
|
key: 'downloads',
|
||||||
|
description: t('settings.system.cacheTypes.downloads.description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.system.cacheTypes.resources.label'),
|
||||||
|
key: 'resources',
|
||||||
|
description: t('settings.system.cacheTypes.resources.description')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.system.cacheTypes.lyrics.label'),
|
||||||
|
key: 'lyrics',
|
||||||
|
description: t('settings.system.cacheTypes.lyrics.description')
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 同步外部show属性变化
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(newVal) => {
|
||||||
|
visible.value = newVal;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 同步内部visible变化
|
||||||
|
watch(
|
||||||
|
() => visible.value,
|
||||||
|
(newVal) => {
|
||||||
|
emit('update:show', newVal);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
emit('confirm', selectedTypes.value);
|
||||||
|
selectedTypes.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
selectedTypes.value = [];
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
v-model:show="visible"
|
||||||
|
preset="dialog"
|
||||||
|
:title="t('settings.playback.musicSources')"
|
||||||
|
:positive-text="t('common.confirm')"
|
||||||
|
:negative-text="t('common.cancel')"
|
||||||
|
@positive-click="handleConfirm"
|
||||||
|
@negative-click="handleCancel"
|
||||||
|
>
|
||||||
|
<n-space vertical>
|
||||||
|
<p>{{ t('settings.playback.musicSourcesDesc') }}</p>
|
||||||
|
<n-checkbox-group v-model:value="selectedSources">
|
||||||
|
<n-grid :cols="2" :x-gap="12" :y-gap="8">
|
||||||
|
<n-grid-item v-for="source in musicSourceOptions" :key="source.value">
|
||||||
|
<n-checkbox :value="source.value">
|
||||||
|
{{ source.label }}
|
||||||
|
<template v-if="source.value === 'gdmusic'">
|
||||||
|
<n-tooltip>
|
||||||
|
<template #trigger>
|
||||||
|
<n-icon size="16" class="ml-1 text-blue-500 cursor-help">
|
||||||
|
<i class="ri-information-line"></i>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ t('settings.playback.gdmusicInfo') }}
|
||||||
|
</n-tooltip>
|
||||||
|
</template>
|
||||||
|
</n-checkbox>
|
||||||
|
</n-grid-item>
|
||||||
|
</n-grid>
|
||||||
|
</n-checkbox-group>
|
||||||
|
<div v-if="selectedSources.length === 0" class="text-red-500 text-sm">
|
||||||
|
{{ t('settings.playback.musicSourcesWarning') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- GD音乐台设置 -->
|
||||||
|
<div v-if="selectedSources.includes('gdmusic')" class="mt-4 border-t pt-4 border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 class="text-base font-medium mb-2">GD音乐台(music.gdstudio.xyz)设置</h3>
|
||||||
|
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
||||||
|
GD音乐台将自动尝试多个音乐平台进行解析,无需额外配置。优先级高于其他解析方式,但是请求可能较慢。感谢(music.gdstudio.xyz)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</n-space>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, defineProps, defineEmits } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { type Platform } from '@/types/music';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
sources: {
|
||||||
|
type: Array as () => Platform[],
|
||||||
|
default: () => ['migu', 'kugou', 'pyncmd', 'bilibili', 'youtube']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:show', 'update:sources']);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const visible = ref(props.show);
|
||||||
|
const selectedSources = ref<Platform[]>(props.sources);
|
||||||
|
|
||||||
|
const musicSourceOptions = ref([
|
||||||
|
{ label: 'MiGu音乐', value: 'migu' },
|
||||||
|
{ label: '酷狗音乐', value: 'kugou' },
|
||||||
|
{ label: 'pyncmd', value: 'pyncmd' },
|
||||||
|
{ label: '酷我音乐', value: 'kuwo' },
|
||||||
|
{ label: 'Bilibili音乐', value: 'bilibili' },
|
||||||
|
{ label: 'YouTube', value: 'youtube' },
|
||||||
|
{ label: 'GD音乐台', value: 'gdmusic' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 同步外部show属性变化
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(newVal) => {
|
||||||
|
visible.value = newVal;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 同步内部visible变化
|
||||||
|
watch(
|
||||||
|
() => visible.value,
|
||||||
|
(newVal) => {
|
||||||
|
emit('update:show', newVal);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 同步外部sources属性变化
|
||||||
|
watch(
|
||||||
|
() => props.sources,
|
||||||
|
(newVal) => {
|
||||||
|
selectedSources.value = [...newVal];
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
// 确保至少选择一个音源
|
||||||
|
const defaultPlatforms = ['migu', 'kugou', 'pyncmd', 'bilibili', 'youtube'];
|
||||||
|
const valuesToEmit = selectedSources.value.length > 0
|
||||||
|
? [...new Set(selectedSources.value)]
|
||||||
|
: defaultPlatforms;
|
||||||
|
|
||||||
|
emit('update:sources', valuesToEmit);
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
// 取消时还原为props传入的初始值
|
||||||
|
selectedSources.value = [...props.sources];
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
v-model:show="visible"
|
||||||
|
preset="dialog"
|
||||||
|
:title="t('settings.network.proxy')"
|
||||||
|
:positive-text="t('common.confirm')"
|
||||||
|
:negative-text="t('common.cancel')"
|
||||||
|
:show-icon="false"
|
||||||
|
@positive-click="handleProxyConfirm"
|
||||||
|
@negative-click="handleCancel"
|
||||||
|
>
|
||||||
|
<n-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="proxyForm"
|
||||||
|
:rules="proxyRules"
|
||||||
|
label-placement="left"
|
||||||
|
label-width="80"
|
||||||
|
require-mark-placement="right-hanging"
|
||||||
|
>
|
||||||
|
<n-form-item :label="t('settings.network.proxy')" path="protocol">
|
||||||
|
<n-select
|
||||||
|
v-model:value="proxyForm.protocol"
|
||||||
|
:options="[
|
||||||
|
{ label: 'HTTP', value: 'http' },
|
||||||
|
{ label: 'HTTPS', value: 'https' },
|
||||||
|
{ label: 'SOCKS5', value: 'socks5' }
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="t('settings.network.proxyHost')" path="host">
|
||||||
|
<n-input
|
||||||
|
v-model:value="proxyForm.host"
|
||||||
|
:placeholder="t('settings.network.proxyHostPlaceholder')"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="t('settings.network.proxyPort')" path="port">
|
||||||
|
<n-input-number
|
||||||
|
v-model:value="proxyForm.port"
|
||||||
|
:placeholder="t('settings.network.proxyPortPlaceholder')"
|
||||||
|
:min="1"
|
||||||
|
:max="65535"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, defineProps, defineEmits } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useMessage } from 'naive-ui';
|
||||||
|
import type { FormRules } from 'naive-ui';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
protocol: 'http',
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 7890
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:show', 'confirm']);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const message = useMessage();
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
const visible = ref(props.show);
|
||||||
|
const proxyForm = ref({
|
||||||
|
protocol: props.config.protocol || 'http',
|
||||||
|
host: props.config.host || '127.0.0.1',
|
||||||
|
port: props.config.port || 7890
|
||||||
|
});
|
||||||
|
|
||||||
|
const proxyRules: FormRules = {
|
||||||
|
protocol: {
|
||||||
|
required: true,
|
||||||
|
message: t('settings.validation.selectProxyProtocol'),
|
||||||
|
trigger: ['blur', 'change']
|
||||||
|
},
|
||||||
|
host: {
|
||||||
|
required: true,
|
||||||
|
message: t('settings.validation.proxyHost'),
|
||||||
|
trigger: ['blur', 'change'],
|
||||||
|
validator: (_rule, value) => {
|
||||||
|
if (!value) return false;
|
||||||
|
// 简单的IP或域名验证
|
||||||
|
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$|^localhost$|^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
|
||||||
|
return ipRegex.test(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
port: {
|
||||||
|
required: true,
|
||||||
|
message: t('settings.validation.portNumber'),
|
||||||
|
trigger: ['blur', 'change'],
|
||||||
|
validator: (_rule, value) => {
|
||||||
|
return value >= 1 && value <= 65535;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 同步外部show属性变化
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(newVal) => {
|
||||||
|
visible.value = newVal;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 同步内部visible变化
|
||||||
|
watch(
|
||||||
|
() => visible.value,
|
||||||
|
(newVal) => {
|
||||||
|
emit('update:show', newVal);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 同步外部config变化
|
||||||
|
watch(
|
||||||
|
() => props.config,
|
||||||
|
(newVal) => {
|
||||||
|
proxyForm.value = {
|
||||||
|
protocol: newVal.protocol || 'http',
|
||||||
|
host: newVal.host || '127.0.0.1',
|
||||||
|
port: newVal.port || 7890
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleProxyConfirm = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
emit('confirm', { ...proxyForm.value });
|
||||||
|
visible.value = false;
|
||||||
|
message.success(t('settings.network.messages.proxySuccess'));
|
||||||
|
} catch (err) {
|
||||||
|
message.error(t('settings.network.messages.proxyError'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// 音乐平台类型
|
||||||
|
export type Platform = 'qq' | 'migu' | 'kugou' | 'pyncmd' | 'joox' | 'kuwo' | 'bilibili' | 'youtube' | 'gdmusic';
|
||||||
|
|
||||||
|
// 默认平台列表
|
||||||
|
export const DEFAULT_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'bilibili', 'youtube'];
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="set-item">
|
<div class="set-item" v-if="isElectron">
|
||||||
<div>
|
<div>
|
||||||
<div class="set-item-title">{{ t('settings.playback.musicSources') }}</div>
|
<div class="set-item-title">{{ t('settings.playback.musicSources') }}</div>
|
||||||
<div class="set-item-content">
|
<div class="set-item-content">
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span class="text-gray-500">{{ t('settings.playback.selectedMusicSources') }}</span>
|
<span class="text-gray-500">{{ t('settings.playback.selectedMusicSources') }}</span>
|
||||||
<span v-if="musicSources.length > 0" class="text-gray-400">
|
<span v-if="musicSources.length > 0" class="text-gray-400">
|
||||||
{{ musicSources.map((source) => getSourceLabel(source)).join(', ') }}
|
{{ musicSources.join(', ') }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="text-red-500 text-xs">
|
<span v-else class="text-red-500 text-xs">
|
||||||
{{ t('settings.playback.noMusicSources') }}
|
{{ t('settings.playback.noMusicSources') }}
|
||||||
@@ -428,145 +428,40 @@
|
|||||||
<play-bottom />
|
<play-bottom />
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
|
|
||||||
<!-- 快捷键设置弹窗 -->
|
<template v-if="isElectron">
|
||||||
<shortcut-settings v-model:show="showShortcutModal" @change="handleShortcutsChange" />
|
<!-- 快捷键设置弹窗 -->
|
||||||
|
<shortcut-settings v-model:show="showShortcutModal" @change="handleShortcutsChange" />
|
||||||
|
|
||||||
|
<!-- 代理设置弹窗 -->
|
||||||
|
<proxy-settings
|
||||||
|
v-model:show="showProxyModal"
|
||||||
|
:config="proxyForm"
|
||||||
|
@confirm="handleProxyConfirm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 音源设置弹窗 -->
|
||||||
|
<music-source-settings
|
||||||
|
v-model:show="showMusicSourcesModal"
|
||||||
|
v-model:sources="musicSources"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 远程控制设置弹窗 -->
|
||||||
|
<remote-control-setting v-model:visible="showRemoteControlModal" />
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 代理设置弹窗 -->
|
|
||||||
<n-modal
|
|
||||||
v-model:show="showProxyModal"
|
|
||||||
preset="dialog"
|
|
||||||
:title="t('settings.network.proxy')"
|
|
||||||
:positive-text="t('common.confirm')"
|
|
||||||
:negative-text="t('common.cancel')"
|
|
||||||
:show-icon="false"
|
|
||||||
@positive-click="handleProxyConfirm"
|
|
||||||
@negative-click="showProxyModal = false"
|
|
||||||
>
|
|
||||||
<n-form
|
|
||||||
ref="formRef"
|
|
||||||
:model="proxyForm"
|
|
||||||
:rules="proxyRules"
|
|
||||||
label-placement="left"
|
|
||||||
label-width="80"
|
|
||||||
require-mark-placement="right-hanging"
|
|
||||||
>
|
|
||||||
<n-form-item :label="t('settings.network.proxy')" path="protocol">
|
|
||||||
<n-select
|
|
||||||
v-model:value="proxyForm.protocol"
|
|
||||||
:options="[
|
|
||||||
{ label: 'HTTP', value: 'http' },
|
|
||||||
{ label: 'HTTPS', value: 'https' },
|
|
||||||
{ label: 'SOCKS5', value: 'socks5' }
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item :label="t('settings.network.proxyHost')" path="host">
|
|
||||||
<n-input
|
|
||||||
v-model:value="proxyForm.host"
|
|
||||||
:placeholder="t('settings.network.proxyHostPlaceholder')"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
<n-form-item :label="t('settings.network.proxyPort')" path="port">
|
|
||||||
<n-input-number
|
|
||||||
v-model:value="proxyForm.port"
|
|
||||||
:placeholder="t('settings.network.proxyPortPlaceholder')"
|
|
||||||
:min="1"
|
|
||||||
:max="65535"
|
|
||||||
/>
|
|
||||||
</n-form-item>
|
|
||||||
</n-form>
|
|
||||||
</n-modal>
|
|
||||||
<!-- 清除缓存弹窗 -->
|
<!-- 清除缓存弹窗 -->
|
||||||
<n-modal
|
<clear-cache-settings
|
||||||
v-model:show="showClearCacheModal"
|
v-model:show="showClearCacheModal"
|
||||||
preset="dialog"
|
@confirm="clearCache"
|
||||||
:title="t('settings.system.cache')"
|
/>
|
||||||
:positive-text="t('common.confirm')"
|
|
||||||
:negative-text="t('common.cancel')"
|
|
||||||
@positive-click="clearCache"
|
|
||||||
@negative-click="
|
|
||||||
() => {
|
|
||||||
selectedCacheTypes = [];
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<n-space vertical>
|
|
||||||
<p>{{ t('settings.system.cacheClearTitle') }}</p>
|
|
||||||
<n-checkbox-group v-model:value="selectedCacheTypes">
|
|
||||||
<n-space vertical>
|
|
||||||
<n-checkbox
|
|
||||||
v-for="option in clearCacheOptions"
|
|
||||||
:key="option.key"
|
|
||||||
:value="option.key"
|
|
||||||
:label="option.label"
|
|
||||||
>
|
|
||||||
<template #default>
|
|
||||||
<div>
|
|
||||||
<div>{{ t(`settings.system.cacheTypes.${option.key}.label`) }}</div>
|
|
||||||
<div class="text-gray-400 text-sm">
|
|
||||||
{{ t(`settings.system.cacheTypes.${option.key}.description`) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</n-checkbox>
|
|
||||||
</n-space>
|
|
||||||
</n-checkbox-group>
|
|
||||||
</n-space>
|
|
||||||
</n-modal>
|
|
||||||
|
|
||||||
<!-- 音源设置弹窗 -->
|
|
||||||
<n-modal
|
|
||||||
v-model:show="showMusicSourcesModal"
|
|
||||||
preset="dialog"
|
|
||||||
:title="t('settings.playback.musicSources')"
|
|
||||||
:positive-text="t('common.confirm')"
|
|
||||||
:negative-text="t('common.cancel')"
|
|
||||||
@positive-click="showMusicSourcesModal = false"
|
|
||||||
@negative-click="showMusicSourcesModal = false"
|
|
||||||
>
|
|
||||||
<n-space vertical>
|
|
||||||
<p>{{ t('settings.playback.musicSourcesDesc') }}</p>
|
|
||||||
<n-checkbox-group v-model:value="musicSources">
|
|
||||||
<n-grid :cols="2" :x-gap="12" :y-gap="8">
|
|
||||||
<n-grid-item v-for="source in musicSourceOptions" :key="source.value">
|
|
||||||
<n-checkbox :value="source.value">
|
|
||||||
{{ source.label }}
|
|
||||||
<template v-if="source.value === 'gdmusic'">
|
|
||||||
<n-tooltip>
|
|
||||||
<template #trigger>
|
|
||||||
<n-icon size="16" class="ml-1 text-blue-500 cursor-help">
|
|
||||||
<i class="ri-information-line"></i>
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
{{ t('settings.playback.gdmusicInfo') }}
|
|
||||||
</n-tooltip>
|
|
||||||
</template>
|
|
||||||
</n-checkbox>
|
|
||||||
</n-grid-item>
|
|
||||||
</n-grid>
|
|
||||||
</n-checkbox-group>
|
|
||||||
<div v-if="musicSources.length === 0" class="text-red-500 text-sm">
|
|
||||||
{{ t('settings.playback.musicSourcesWarning') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- GD音乐台设置 -->
|
|
||||||
<div v-if="musicSources.includes('gdmusic')" class="mt-4 border-t pt-4 border-gray-200 dark:border-gray-700">
|
|
||||||
<h3 class="text-base font-medium mb-2">GD音乐台(music.gdstudio.xyz)设置</h3>
|
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
|
||||||
GD音乐台将自动尝试多个音乐平台进行解析,无需额外配置。优先级高于其他解析方式,但是请求可能较慢。感谢(music.gdstudio.xyz)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</n-space>
|
|
||||||
</n-modal>
|
|
||||||
|
|
||||||
<!-- 远程控制设置弹窗 -->
|
|
||||||
<remote-control-setting v-model:visible="showRemoteControlModal" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
import type { FormRules } from 'naive-ui';
|
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import { computed, h, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
import { computed, h, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
@@ -577,18 +472,20 @@ import DonationList from '@/components/common/DonationList.vue';
|
|||||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||||
import LanguageSwitcher from '@/components/LanguageSwitcher.vue';
|
import LanguageSwitcher from '@/components/LanguageSwitcher.vue';
|
||||||
import ShortcutSettings from '@/components/settings/ShortcutSettings.vue';
|
import ShortcutSettings from '@/components/settings/ShortcutSettings.vue';
|
||||||
|
import ProxySettings from '@/components/settings/ProxySettings.vue';
|
||||||
|
import ClearCacheSettings from '@/components/settings/ClearCacheSettings.vue';
|
||||||
|
import MusicSourceSettings from '@/components/settings/MusicSourceSettings.vue';
|
||||||
|
import RemoteControlSetting from '@/components/settings/ServerSetting.vue';
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { isElectron, isMobile } from '@/utils';
|
import { isElectron, isMobile } from '@/utils';
|
||||||
import { openDirectory, selectDirectory } from '@/utils/fileOperation';
|
import { openDirectory, selectDirectory } from '@/utils/fileOperation';
|
||||||
import { checkUpdate, UpdateResult } from '@/utils/update';
|
import { checkUpdate, UpdateResult } from '@/utils/update';
|
||||||
import RemoteControlSetting from '@/views/setting/ServerSetting.vue';
|
import { type Platform } from '@/types/music';
|
||||||
|
|
||||||
import config from '../../../../package.json';
|
import config from '../../../../package.json';
|
||||||
|
|
||||||
// 手动定义Platform类型,避免从主进程导入的问题
|
// 所有平台默认值
|
||||||
type Platform = 'qq' | 'migu' | 'kugou' | 'pyncmd' | 'joox' | 'kuwo' | 'bilibili' | 'youtube' | 'gdmusic';
|
|
||||||
// 所有平台
|
|
||||||
const ALL_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'bilibili', 'youtube'];
|
const ALL_PLATFORMS: Platform[] = ['migu', 'kugou', 'pyncmd', 'bilibili', 'youtube'];
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
@@ -705,41 +602,12 @@ const openDownloadPath = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showProxyModal = ref(false);
|
const showProxyModal = ref(false);
|
||||||
const formRef = ref();
|
|
||||||
const proxyForm = ref({
|
const proxyForm = ref({
|
||||||
protocol: 'http',
|
protocol: 'http',
|
||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: 7890
|
port: 7890
|
||||||
});
|
});
|
||||||
|
|
||||||
const proxyRules: FormRules = {
|
|
||||||
protocol: {
|
|
||||||
required: true,
|
|
||||||
message: t('settings.validation.selectProxyProtocol'),
|
|
||||||
trigger: ['blur', 'change']
|
|
||||||
},
|
|
||||||
host: {
|
|
||||||
required: true,
|
|
||||||
message: t('settings.validation.proxyHost'),
|
|
||||||
trigger: ['blur', 'change'],
|
|
||||||
validator: (_rule, value) => {
|
|
||||||
if (!value) return false;
|
|
||||||
// 简单的IP或域名验证
|
|
||||||
const ipRegex =
|
|
||||||
/^(\d{1,3}\.){3}\d{1,3}$|^localhost$|^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
|
|
||||||
return ipRegex.test(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
port: {
|
|
||||||
required: true,
|
|
||||||
message: t('settings.validation.portNumber'),
|
|
||||||
trigger: ['blur', 'change'],
|
|
||||||
validator: (_rule, value) => {
|
|
||||||
return value >= 1 && value <= 65535;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 使用 store 中的字体列表
|
// 使用 store 中的字体列表
|
||||||
const systemFonts = computed(() => settingsStore.systemFonts);
|
const systemFonts = computed(() => settingsStore.systemFonts);
|
||||||
|
|
||||||
@@ -817,24 +685,16 @@ watch(
|
|||||||
{ immediate: true, deep: true }
|
{ immediate: true, deep: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleProxyConfirm = async () => {
|
const handleProxyConfirm = async (proxyConfig) => {
|
||||||
try {
|
// 保存代理配置时保留enable状态
|
||||||
await formRef.value?.validate();
|
setData.value = {
|
||||||
// 保存代理配置时保留enable状态
|
...setData.value,
|
||||||
setData.value = {
|
proxyConfig: {
|
||||||
...setData.value,
|
enable: setData.value.proxyConfig?.enable || false,
|
||||||
proxyConfig: {
|
...proxyConfig
|
||||||
enable: setData.value.proxyConfig?.enable || false,
|
}
|
||||||
protocol: proxyForm.value.protocol,
|
};
|
||||||
host: proxyForm.value.host,
|
message.success(t('settings.network.messages.proxySuccess'));
|
||||||
port: proxyForm.value.port
|
|
||||||
}
|
|
||||||
};
|
|
||||||
showProxyModal.value = false;
|
|
||||||
message.success(t('settings.network.messages.proxySuccess'));
|
|
||||||
} catch (err) {
|
|
||||||
message.error(t('settings.network.messages.proxyError'));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateAndSaveRealIP = () => {
|
const validateAndSaveRealIP = () => {
|
||||||
@@ -880,48 +740,9 @@ const toggleDonationList = () => {
|
|||||||
|
|
||||||
// 清除缓存相关
|
// 清除缓存相关
|
||||||
const showClearCacheModal = ref(false);
|
const showClearCacheModal = ref(false);
|
||||||
const clearCacheOptions = ref([
|
|
||||||
{
|
|
||||||
label: t('settings.system.cacheTypes.history.label'),
|
|
||||||
key: 'history',
|
|
||||||
description: t('settings.system.cacheTypes.history.description')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('settings.system.cacheTypes.favorite.label'),
|
|
||||||
key: 'favorite',
|
|
||||||
description: t('settings.system.cacheTypes.favorite.description')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('settings.system.cacheTypes.user.label'),
|
|
||||||
key: 'user',
|
|
||||||
description: t('settings.system.cacheTypes.user.description')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('settings.system.cacheTypes.settings.label'),
|
|
||||||
key: 'settings',
|
|
||||||
description: t('settings.system.cacheTypes.settings.description')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('settings.system.cacheTypes.downloads.label'),
|
|
||||||
key: 'downloads',
|
|
||||||
description: t('settings.system.cacheTypes.downloads.description')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('settings.system.cacheTypes.resources.label'),
|
|
||||||
key: 'resources',
|
|
||||||
description: t('settings.system.cacheTypes.resources.description')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('settings.system.cacheTypes.lyrics.label'),
|
|
||||||
key: 'lyrics',
|
|
||||||
description: t('settings.system.cacheTypes.lyrics.description')
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const selectedCacheTypes = ref<string[]>([]);
|
const clearCache = async (selectedCacheTypes) => {
|
||||||
|
const clearTasks = selectedCacheTypes.map(async (type) => {
|
||||||
const clearCache = async () => {
|
|
||||||
const clearTasks = selectedCacheTypes.value.map(async (type) => {
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'history':
|
case 'history':
|
||||||
localStorage.removeItem('musicHistory');
|
localStorage.removeItem('musicHistory');
|
||||||
@@ -980,8 +801,6 @@ const clearCache = async () => {
|
|||||||
|
|
||||||
await Promise.all(clearTasks);
|
await Promise.all(clearTasks);
|
||||||
message.success(t('settings.system.messages.clearSuccess'));
|
message.success(t('settings.system.messages.clearSuccess'));
|
||||||
showClearCacheModal.value = false;
|
|
||||||
selectedCacheTypes.value = [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const showShortcutModal = ref(false);
|
const showShortcutModal = ref(false);
|
||||||
@@ -1076,17 +895,6 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 音源设置相关
|
// 音源设置相关
|
||||||
const musicSourceOptions = ref([
|
|
||||||
{ label: 'MiGu音乐', value: 'migu' },
|
|
||||||
{ label: '酷狗音乐', value: 'kugou' },
|
|
||||||
{ label: 'pyncmd', value: 'pyncmd' },
|
|
||||||
{ label: '酷我音乐', value: 'kuwo' },
|
|
||||||
{ label: 'Bilibili音乐', value: 'bilibili' },
|
|
||||||
{ label: 'YouTube', value: 'youtube' },
|
|
||||||
{ label: 'GD音乐台', value: 'gdmusic' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 已选择的音源列表
|
|
||||||
const musicSources = computed({
|
const musicSources = computed({
|
||||||
get: () => {
|
get: () => {
|
||||||
if (!setData.value.enabledMusicSources) {
|
if (!setData.value.enabledMusicSources) {
|
||||||
@@ -1106,11 +914,6 @@ const musicSources = computed({
|
|||||||
|
|
||||||
const showMusicSourcesModal = ref(false);
|
const showMusicSourcesModal = ref(false);
|
||||||
|
|
||||||
const getSourceLabel = (source: Platform) => {
|
|
||||||
const sourceLabel = musicSourceOptions.value.find(s => s.value === source)?.label;
|
|
||||||
return sourceLabel || source;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 远程控制设置弹窗
|
// 远程控制设置弹窗
|
||||||
const showRemoteControlModal = ref(false);
|
const showRemoteControlModal = ref(false);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user