feat: 添加远程控制功能,支持远程控制音乐播放操作

This commit is contained in:
alger
2025-04-29 23:21:16 +08:00
parent 0128662ed2
commit c82ffd0c7d
14 changed files with 995 additions and 10 deletions
+3
View File
@@ -19,8 +19,10 @@ declare module 'vue' {
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NCollapse: typeof import('naive-ui')['NCollapse']
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer']
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
@@ -50,6 +52,7 @@ declare module 'vue' {
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NTag: typeof import('naive-ui')['NTag']
NText: typeof import('naive-ui')['NText']
NTooltip: typeof import('naive-ui')['NTooltip']
NVirtualList: typeof import('naive-ui')['NVirtualList']
RouterLink: typeof import('vue-router')['RouterLink']
@@ -168,7 +168,6 @@ import {
setBackgroundImg
} from '@/utils';
import { getArtistDetail } from '@/api/artist';
import { cloneDeep } from 'lodash';
const userStore = useUserStore();
const playerStore = usePlayerStore();
+1 -1
View File
@@ -72,7 +72,7 @@
</template>
<script setup lang="ts">
import { useDateFormat, useThrottleFn } from '@vueuse/core';
import { useDateFormat } from '@vueuse/core';
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
+17
View File
@@ -271,6 +271,16 @@
}}</n-button>
</div>
</div>
<div class="set-item">
<div>
<div class="set-item-title">{{ t('settings.application.remoteControl') }}</div>
<div class="set-item-content">{{ t('settings.application.remoteControlDesc') }}</div>
</div>
<n-button size="small" @click="showRemoteControlModal = true">{{
t('common.configure')
}}</n-button>
</div>
</div>
</div>
@@ -548,6 +558,9 @@
</div>
</n-space>
</n-modal>
<!-- 远程控制设置弹窗 -->
<remote-control-setting v-model:visible="showRemoteControlModal" />
</div>
</template>
@@ -569,6 +582,7 @@ import { useUserStore } from '@/store/modules/user';
import { isElectron, isMobile } from '@/utils';
import { openDirectory, selectDirectory } from '@/utils/fileOperation';
import { checkUpdate, UpdateResult } from '@/utils/update';
import RemoteControlSetting from '@/views/setting/ServerSetting.vue';
import config from '../../../../package.json';
@@ -1096,6 +1110,9 @@ const getSourceLabel = (source: Platform) => {
const sourceLabel = musicSourceOptions.value.find(s => s.value === source)?.label;
return sourceLabel || source;
};
// 远程控制设置弹窗
const showRemoteControlModal = ref(false);
</script>
<style lang="scss" scoped>
@@ -0,0 +1,226 @@
<template>
<n-modal
v-model:show="visible"
preset="card"
:title="t('settings.remoteControl.title')"
class="remote-control-modal"
style="max-width: 650px; width: 100%"
>
<n-scrollbar>
<div class="remote-control-setting">
<n-form label-placement="left" label-width="auto" :style="{ maxWidth: '640px' }">
<n-form-item :label="t('settings.remoteControl.enable')">
<n-switch v-model:value="remoteControlConfig.enabled" />
</n-form-item>
<n-form-item :label="t('settings.remoteControl.port')">
<n-input-number
v-model:value="remoteControlConfig.port"
:min="1024"
:max="65535"
:disabled="!remoteControlConfig.enabled"
/>
</n-form-item>
<n-form-item :label="t('settings.remoteControl.allowedIps')">
<div class="allowed-ips-container">
<div v-for="(_, index) in remoteControlConfig.allowedIps" :key="index" class="ip-item">
<n-input v-model:value="remoteControlConfig.allowedIps[index]" :disabled="!remoteControlConfig.enabled" />
<n-button
quaternary
circle
type="error"
:disabled="!remoteControlConfig.enabled"
@click="removeIp(index)"
>
<template #icon>
<n-icon><i class="ri-delete-bin-line"></i></n-icon>
</template>
</n-button>
</div>
<n-button
secondary
size="small"
:disabled="!remoteControlConfig.enabled"
@click="addIp"
>
<template #icon>
<n-icon><i class="ri-add-line"></i></n-icon>
</template>
{{ t('settings.remoteControl.addIp') }}
</n-button>
<n-text depth="3" size="small" class="allow-all-hint">
{{ t('settings.remoteControl.emptyListHint') }}
</n-text>
</div>
</n-form-item>
<n-form-item>
<n-space>
<n-button
type="primary"
:disabled="!remoteControlConfig.enabled"
@click="saveConfig"
>
{{ t('common.save') }}
</n-button>
<n-button @click="resetConfig">
{{ t('common.reset') }}
</n-button>
</n-space>
</n-form-item>
<n-collapse-transition :show="remoteControlConfig.enabled">
<div class="remote-info">
<n-alert type="info">
<template #icon>
<n-icon><i class="ri-information-line"></i></n-icon>
</template>
<p>{{ t('settings.remoteControl.accessInfo') }}</p>
<div class="access-url">
<n-tag type="success">
http://localhost:{{ remoteControlConfig.port }}/
</n-tag>
</div>
<div v-if="localIpAddresses.length" class="local-ips">
<div v-for="ip in localIpAddresses" :key="ip" class="ip-address">
<n-tag type="info">
http://{{ ip }}:{{ remoteControlConfig.port }}/
</n-tag>
</div>
</div>
</n-alert>
</div>
</n-collapse-transition>
</n-form>
</div>
</n-scrollbar>
</n-modal>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useMessage } from 'naive-ui';
import { cloneDeep } from 'lodash';
const { t } = useI18n();
const message = useMessage();
// 控制弹窗显示的属性
const visible = defineModel('visible', { default: false });
// 默认配置
const defaultConfig:{
enabled: boolean,
port: number,
allowedIps: string[]
} = {
enabled: false,
port: 31888,
allowedIps: []
};
// 远程控制配置
const remoteControlConfig = ref({...defaultConfig});
// 本地IP地址列表
const localIpAddresses = ref<string[]>([]);
// 获取本地IP地址
const getLocalIpAddresses = () => {
if (window.electron) {
window.electron.ipcRenderer.invoke('get-local-ip-addresses').then((ips: string[]) => {
localIpAddresses.value = ips;
});
}
};
// 添加IP地址
const addIp = () => {
remoteControlConfig.value.allowedIps.push('');
};
// 删除IP地址
const removeIp = (index: number) => {
remoteControlConfig.value.allowedIps.splice(index, 1);
};
// 保存配置
const saveConfig = () => {
// 过滤空IP
remoteControlConfig.value.allowedIps = remoteControlConfig.value.allowedIps.filter(ip => ip.trim() !== '');
if (window.electron) {
window.electron.ipcRenderer.send('update-remote-control-config', cloneDeep(remoteControlConfig.value));
message.success(t('settings.remoteControl.saveSuccess'));
}
};
// 重置配置
const resetConfig = () => {
if (window.electron) {
window.electron.ipcRenderer.invoke('get-remote-control-config').then((config) => {
if (config) {
remoteControlConfig.value = config;
} else {
remoteControlConfig.value = { ...defaultConfig };
}
});
}
};
// 组件挂载时,获取当前配置
onMounted(async () => {
if (window.electron) {
try {
const config = await window.electron.ipcRenderer.invoke('get-remote-control-config');
if (config) {
remoteControlConfig.value = config;
}
// 获取本地IP地址
getLocalIpAddresses();
} catch (error) {
console.error('获取远程控制配置失败:', error);
}
}
});
</script>
<style lang="scss" scoped>
.remote-control-setting {
padding: 0 20px;
}
.allowed-ips-container {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
.ip-item {
display: flex;
align-items: center;
gap: 10px;
}
.allow-all-hint {
margin-top: 5px;
}
}
.remote-info {
margin-top: 16px;
.access-url {
margin-top: 10px;
}
.local-ips {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 5px;
}
}
</style>