mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-08 10:00:50 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d3992154a | ||
|
|
81e7b67c7f | ||
|
|
d7e94a342b | ||
|
|
46f8067577 | ||
|
|
1dc7d0ceca | ||
|
|
ba64631a17 | ||
|
|
cdb9524f04 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,8 +1,13 @@
|
||||
# 更新日志
|
||||
|
||||
## [v3.1.0] - 2024-01-02
|
||||
## [v3.2.0] - 2024-01-04
|
||||
|
||||
### ✨ 新功能
|
||||
- 优化主入口代码,添加歌曲下载功能 (018218a)
|
||||
- 完善网页版安装应用功能 (38a9d6e)
|
||||
- 修改更新检查功能 (8dab799)
|
||||
- 添加代理功能和 realIP配置功能 解决内网访问问题 和 国外访问问题 (d7e94a3)
|
||||
- 关闭应用的提示修改,可存储配置最小化或关闭 (46f8067)
|
||||
- 解决检查更新请求失败问题 (cdb9524)
|
||||
|
||||
### 🐞 问题修复
|
||||
- 修复歌词页面与底栏冲突问题(#26) (1dc7d0c)
|
||||
- 修复搜索歌曲列表页面显示错误问题(#33) (1dc7d0c)
|
||||
- 修复搜索类型切换没有重新加载搜索的问题(#25) (ba64631)
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "AlgerMusicPlayer",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"description": "Alger Music Player",
|
||||
"author": "Alger <algerkc@qq.com>",
|
||||
"main": "./out/main/index.js",
|
||||
|
||||
@@ -1,11 +1,43 @@
|
||||
import { BrowserWindow, shell, ipcMain } from 'electron';
|
||||
import { BrowserWindow, shell, ipcMain, app, session } from 'electron';
|
||||
import { is } from '@electron-toolkit/utils';
|
||||
import { join } from 'path';
|
||||
import Store from 'electron-store';
|
||||
|
||||
const store = new Store();
|
||||
|
||||
/**
|
||||
* 初始化代理设置
|
||||
*/
|
||||
function initializeProxy() {
|
||||
const defaultConfig = {
|
||||
enable: false,
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: 7890
|
||||
};
|
||||
|
||||
const proxyConfig = store.get('set.proxyConfig', defaultConfig) as {
|
||||
enable: boolean;
|
||||
protocol: string;
|
||||
host: string;
|
||||
port: number;
|
||||
};
|
||||
|
||||
if (proxyConfig?.enable) {
|
||||
const proxyRules = `${proxyConfig.protocol}://${proxyConfig.host}:${proxyConfig.port}`;
|
||||
session.defaultSession.setProxy({ proxyRules });
|
||||
} else {
|
||||
session.defaultSession.setProxy({ proxyRules: '' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化窗口管理相关的IPC监听
|
||||
*/
|
||||
export function initializeWindowManager() {
|
||||
// 初始化代理设置
|
||||
initializeProxy();
|
||||
|
||||
ipcMain.on('minimize-window', (event) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win) {
|
||||
@@ -28,6 +60,7 @@ export function initializeWindowManager() {
|
||||
const win = BrowserWindow.fromWebContents(event.sender);
|
||||
if (win) {
|
||||
win.destroy();
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -37,6 +70,11 @@ export function initializeWindowManager() {
|
||||
win.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听代理设置变化
|
||||
store.onDidChange('set.proxyConfig', () => {
|
||||
initializeProxy();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
{
|
||||
"isProxy": false,
|
||||
"proxyConfig": {
|
||||
"enable": false,
|
||||
"protocol": "http",
|
||||
"host": "127.0.0.1",
|
||||
"port": 7890
|
||||
},
|
||||
"enableRealIP":false,
|
||||
"realIP":"",
|
||||
"noAnimate": false,
|
||||
"animationSpeed": 1,
|
||||
"author": "Alger",
|
||||
"authorUrl": "https://github.com/algerkong",
|
||||
"musicApiPort": 30488
|
||||
"musicApiPort": 30488,
|
||||
"closeAction": "ask"
|
||||
}
|
||||
|
||||
3
src/renderer/components.d.ts
vendored
3
src/renderer/components.d.ts
vendored
@@ -17,6 +17,8 @@ declare module 'vue' {
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NImage: typeof import('naive-ui')['NImage']
|
||||
NInput: typeof import('naive-ui')['NInput']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
@@ -25,6 +27,7 @@ declare module 'vue' {
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSelect: typeof import('naive-ui')['NSelect']
|
||||
NSlider: typeof import('naive-ui')['NSlider']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
|
||||
@@ -313,4 +313,13 @@ watch(
|
||||
.double-item {
|
||||
@apply mb-2 bg-light-100 bg-opacity-20 dark:bg-dark-100 dark:bg-opacity-20 rounded-3xl;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.music-info {
|
||||
@apply hidden;
|
||||
}
|
||||
.music-list-content {
|
||||
@apply pb-[100px];
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -129,7 +129,7 @@ const handleUpdate = async () => {
|
||||
}
|
||||
|
||||
if (downloadUrl) {
|
||||
window.open(downloadUrl, '_blank');
|
||||
window.open(`https://ghproxy.cn/${downloadUrl}`, '_blank');
|
||||
} else {
|
||||
// 如果没有找到对应的安装包,跳转到 release 页面
|
||||
window.open('https://github.com/algerkong/AlgerMusicPlayer/releases/latest', '_blank');
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
</router-view>
|
||||
</div>
|
||||
<play-bottom height="5rem" />
|
||||
<app-menu v-if="isMobile" class="menu" :menus="menus" />
|
||||
<app-menu v-if="isMobile && !store.state.musicFull" class="menu" :menus="menus" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部音乐播放 -->
|
||||
<play-bar v-if="isPlay" />
|
||||
<play-bar v-if="isPlay" :style="isMobile && store.state.musicFull ? 'bottom: 0;' : ''" />
|
||||
</div>
|
||||
<install-app-modal v-if="!isElectron"></install-app-modal>
|
||||
<update-modal />
|
||||
|
||||
@@ -79,7 +79,7 @@ import {
|
||||
textColors,
|
||||
useLyricProgress
|
||||
} from '@/hooks/MusicHook';
|
||||
import { getImgUrl } from '@/utils';
|
||||
import { getImgUrl, isMobile } from '@/utils';
|
||||
import { animateGradient, getHoverBackgroundColor, getTextColors } from '@/utils/linearColor';
|
||||
|
||||
// 定义 refs
|
||||
@@ -116,9 +116,11 @@ const lrcScroll = (behavior = 'smooth') => {
|
||||
const debouncedLrcScroll = useDebounceFn(lrcScroll, 200);
|
||||
|
||||
const mouseOverLayout = () => {
|
||||
if(isMobile.value) {return}
|
||||
isMouse.value = true;
|
||||
};
|
||||
const mouseLeaveLayout = () => {
|
||||
if(isMobile.value) {return}
|
||||
setTimeout(() => {
|
||||
isMouse.value = false;
|
||||
lrcScroll();
|
||||
@@ -321,6 +323,9 @@ defineExpose({
|
||||
.music-lrc-text {
|
||||
@apply text-xl text-center;
|
||||
}
|
||||
.music-content {
|
||||
@apply h-[calc(100vh-120px)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<!-- 展开全屏 -->
|
||||
<music-full ref="MusicFullRef" v-model:music-full="musicFullVisible" :background="background" />
|
||||
<!-- 底部播放栏 -->
|
||||
|
||||
<div
|
||||
@@ -141,6 +140,7 @@
|
||||
</n-popover>
|
||||
</div>
|
||||
<!-- 播放音乐 -->
|
||||
<music-full ref="MusicFullRef" v-model:music-full="musicFullVisible" :background="background" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -294,6 +294,7 @@ const musicFullVisible = ref(false);
|
||||
// 设置musicFull
|
||||
const setMusicFull = () => {
|
||||
musicFullVisible.value = !musicFullVisible.value;
|
||||
store.commit('setMusicFull', musicFullVisible.value);
|
||||
};
|
||||
|
||||
const palyListRef = useTemplateRef('palyListRef');
|
||||
@@ -432,8 +433,7 @@ const openLyricWindow = () => {
|
||||
|
||||
.mobile {
|
||||
.music-play-bar {
|
||||
@apply px-4;
|
||||
bottom: 70px;
|
||||
@apply px-4 bottom-[70px] transition-all duration-300;
|
||||
}
|
||||
.music-time {
|
||||
display: none;
|
||||
|
||||
@@ -175,13 +175,17 @@ const search = () => {
|
||||
router.push({
|
||||
path: '/search',
|
||||
query: {
|
||||
keyword: value
|
||||
keyword: value,
|
||||
type: store.state.searchType
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const selectSearchType = (key: number) => {
|
||||
store.state.searchType = key;
|
||||
if (searchValue.value) {
|
||||
search();
|
||||
}
|
||||
};
|
||||
|
||||
const searchTypeOptions = ref(SEARCH_TYPES);
|
||||
|
||||
@@ -10,14 +10,37 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showCloseModal"
|
||||
preset="dialog"
|
||||
title="关闭应用"
|
||||
:style="{ width: '400px' }"
|
||||
:mask-closable="true"
|
||||
>
|
||||
<div class="close-dialog-content">
|
||||
<p>请选择关闭方式</p>
|
||||
<div class="remember-choice">
|
||||
<n-checkbox v-model:checked="rememberChoice">记住我的选择</n-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<template #action>
|
||||
<div class="dialog-footer">
|
||||
<n-button type="primary" @click="handleAction('minimize')">最小化到托盘</n-button>
|
||||
<n-button @click="handleAction('close')">退出应用</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDialog } from 'naive-ui';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { isElectron } from '@/utils';
|
||||
|
||||
const dialog = useDialog();
|
||||
const store = useStore();
|
||||
const showCloseModal = ref(false);
|
||||
const rememberChoice = ref(false);
|
||||
|
||||
const minimize = () => {
|
||||
if (!isElectron) {
|
||||
@@ -26,22 +49,40 @@ const minimize = () => {
|
||||
window.api.minimize();
|
||||
};
|
||||
|
||||
const handleAction = (action: 'minimize' | 'close') => {
|
||||
if (rememberChoice.value) {
|
||||
store.commit('setSetData', {
|
||||
...store.state.setData,
|
||||
closeAction: action
|
||||
});
|
||||
}
|
||||
|
||||
if (action === 'minimize') {
|
||||
window.api.miniTray();
|
||||
} else {
|
||||
window.api.close();
|
||||
}
|
||||
showCloseModal.value = false;
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
if (!isElectron) {
|
||||
return;
|
||||
}
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '确定要退出吗?',
|
||||
positiveText: '最小化',
|
||||
negativeText: '关闭',
|
||||
onPositiveClick: () => {
|
||||
window.api.minimize();
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
window.api.close();
|
||||
}
|
||||
});
|
||||
|
||||
const closeAction = store.state.setData.closeAction;
|
||||
|
||||
if (closeAction === 'minimize') {
|
||||
window.api.miniTray();
|
||||
return;
|
||||
}
|
||||
|
||||
if (closeAction === 'close') {
|
||||
window.api.close();
|
||||
return;
|
||||
}
|
||||
|
||||
showCloseModal.value = true;
|
||||
};
|
||||
|
||||
const drag = (event: MouseEvent) => {
|
||||
@@ -68,4 +109,12 @@ const drag = (event: MouseEvent) => {
|
||||
button {
|
||||
@apply text-gray-600 dark:text-gray-400 hover:text-green-500;
|
||||
}
|
||||
|
||||
.close-dialog-content {
|
||||
@apply flex flex-col gap-4;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
@apply flex gap-4 justify-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -37,6 +37,7 @@ interface State {
|
||||
favoriteList: number[];
|
||||
playMode: number;
|
||||
theme: ThemeType;
|
||||
musicFull: boolean;
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
@@ -55,7 +56,8 @@ const state: State = {
|
||||
searchType: 1,
|
||||
favoriteList: getLocalStorageItem('favoriteList', []),
|
||||
playMode: getLocalStorageItem('playMode', 0),
|
||||
theme: getCurrentTheme()
|
||||
theme: getCurrentTheme(),
|
||||
musicFull: false
|
||||
};
|
||||
|
||||
const { handlePlayMusic, nextPlay, prevPlay } = useMusicListHook();
|
||||
@@ -73,6 +75,9 @@ const mutations = {
|
||||
setPlayMusic(state: State, play: boolean) {
|
||||
state.play = play;
|
||||
},
|
||||
setMusicFull(state: State, musicFull: boolean) {
|
||||
state.musicFull = musicFull;
|
||||
},
|
||||
setPlayList(state: State, playList: SongResult[]) {
|
||||
state.playListIndex = playList.findIndex((item) => item.id === state.playMusic.id);
|
||||
state.playList = playList;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import axios, { InternalAxiosRequestConfig } from 'axios';
|
||||
import { isElectron } from '.';
|
||||
|
||||
let setData: any = null;
|
||||
|
||||
if (window.electron) {
|
||||
setData = window.electron.ipcRenderer.sendSync('get-store-value', 'set');
|
||||
const getSetData = ()=>{
|
||||
if (window.electron) {
|
||||
setData = window.electron.ipcRenderer.sendSync('get-store-value', 'set');
|
||||
}
|
||||
}
|
||||
|
||||
getSetData()
|
||||
// 扩展请求配置接口
|
||||
interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
|
||||
retryCount?: number;
|
||||
@@ -26,15 +29,18 @@ const RETRY_DELAY = 500;
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config: CustomAxiosRequestConfig) => {
|
||||
// 初始化重试次数
|
||||
config.retryCount = 0;
|
||||
getSetData();
|
||||
// 只在retryCount未定义时初始化为0
|
||||
if (config.retryCount === undefined) {
|
||||
config.retryCount = 0;
|
||||
}
|
||||
|
||||
// 在请求发送之前做一些处理
|
||||
// 在get请求params中添加timestamp
|
||||
if (config.method === 'get') {
|
||||
config.params = {
|
||||
...config.params,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
@@ -42,6 +48,16 @@ request.interceptors.request.use(
|
||||
}
|
||||
}
|
||||
|
||||
if(isElectron){
|
||||
const proxyConfig = setData?.proxyConfig
|
||||
if (proxyConfig?.enable && ['http', 'https'].includes(proxyConfig?.protocol)) {
|
||||
config.params.proxy = `${proxyConfig.protocol}://${proxyConfig.host}:${proxyConfig.port}`
|
||||
}
|
||||
if(setData.enableRealIP && setData.realIP){
|
||||
config.params.realIP = setData.realIP
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
@@ -58,14 +74,15 @@ request.interceptors.response.use(
|
||||
async (error) => {
|
||||
const config = error.config as CustomAxiosRequestConfig;
|
||||
|
||||
// 如果没有配置重试次数,则初始化为0
|
||||
if (!config || !config.retryCount) {
|
||||
config.retryCount = 0;
|
||||
// 如果没有配置,直接返回错误
|
||||
if (!config) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
// 检查是否还可以重试
|
||||
if (config.retryCount < MAX_RETRIES) {
|
||||
if (config.retryCount !== undefined && config.retryCount < MAX_RETRIES) {
|
||||
config.retryCount++;
|
||||
console.log(`请求重试第 ${config.retryCount} 次`);
|
||||
|
||||
// 延迟重试
|
||||
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
|
||||
@@ -74,6 +91,7 @@ request.interceptors.response.use(
|
||||
return request(config);
|
||||
}
|
||||
|
||||
console.log(`重试${MAX_RETRIES}次后仍然失败`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -34,8 +34,16 @@ export interface UpdateResult {
|
||||
*/
|
||||
export const getLatestReleaseInfo = async (): Promise<GithubReleaseInfo | null> => {
|
||||
try {
|
||||
const token = import.meta.env.VITE_GITHUB_TOKEN;
|
||||
const headers = {};
|
||||
if (token) {
|
||||
headers['Authorization'] = `token ${token}`;
|
||||
}
|
||||
const response = await axios.get(
|
||||
'https://api.github.com/repos/algerkong/AlgerMusicPlayer/releases/latest'
|
||||
'https://api.github.com/repos/algerkong/AlgerMusicPlayer/releases/latest',
|
||||
{
|
||||
headers
|
||||
}
|
||||
);
|
||||
|
||||
if (response.data) {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
<!-- 歌单列表 -->
|
||||
<n-scrollbar class="recommend" :size="100" @scroll="handleScroll">
|
||||
<n-scrollbar class="recommend" style="height: calc(100% - 55px)" :size="100" @scroll="handleScroll">
|
||||
<div v-loading="loading" class="recommend-list">
|
||||
<div
|
||||
v-for="(item, index) in recommendList"
|
||||
@@ -218,8 +218,6 @@ watch(
|
||||
}
|
||||
|
||||
.recommend {
|
||||
@apply w-full h-full;
|
||||
|
||||
&-title {
|
||||
@apply text-lg font-bold pb-2;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
@@ -325,5 +323,8 @@ watch(
|
||||
.play-list-type {
|
||||
@apply mx-0 w-full;
|
||||
}
|
||||
.categories-wrapper {
|
||||
@apply pl-4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -292,4 +292,13 @@ const isPrevDisabled = computed(() => currentIndex.value === 0);
|
||||
@apply text-center py-4 col-span-full;
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.mv-list-content {
|
||||
@apply pl-4 pr-4;
|
||||
}
|
||||
.categories-wrapper {
|
||||
@apply pl-4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -104,6 +104,15 @@ watch(
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => searchType.value,
|
||||
() => {
|
||||
if (store.state.searchValue) {
|
||||
loadSearch(store.state.searchValue);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const dateFormat = (time: any) => useDateFormat(time, 'YYYY.MM.DD').value;
|
||||
const loadSearch = async (keywords: any, type: any = null) => {
|
||||
hotKeyword.value = keywords;
|
||||
|
||||
@@ -112,6 +112,23 @@
|
||||
<n-button size="small" type="primary" @click="openAuthor"><i class="ri-github-line"></i>前往github</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-item" v-if="isElectron">
|
||||
<div>
|
||||
<div class="set-item-title">关闭行为</div>
|
||||
<div class="set-item-content">
|
||||
{{ closeActionLabels[setData.closeAction] || '每次询问' }}
|
||||
</div>
|
||||
</div>
|
||||
<n-select
|
||||
v-model:value="setData.closeAction"
|
||||
:options="[
|
||||
{ label: '每次询问', value: 'ask' },
|
||||
{ label: '最小化到托盘', value: 'minimize' },
|
||||
{ label: '直接退出', value: 'close' }
|
||||
]"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</div>
|
||||
<div class="set-item" v-if="isElectron">
|
||||
<div>
|
||||
<div class="set-item-title">重启</div>
|
||||
@@ -119,15 +136,84 @@
|
||||
</div>
|
||||
<n-button type="primary" @click="restartApp">重启</n-button>
|
||||
</div>
|
||||
<div class="set-item" v-if="isElectron">
|
||||
<div>
|
||||
<div class="set-item-title">代理设置</div>
|
||||
<div class="set-item-content">无法访问音乐时可以开启代理</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<n-switch v-model:value="setData.proxyConfig.enable">
|
||||
<template #checked>开启</template>
|
||||
<template #unchecked>关闭</template>
|
||||
</n-switch>
|
||||
<n-button size="small" @click="showProxyModal = true">配置</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="set-item" v-if="isElectron">
|
||||
<div>
|
||||
<div class="set-item-title">realIP</div>
|
||||
<div class="set-item-content">由于限制,此项目在国外使用会受到限制可使用realIP参数,传进国内IP解决,如:116.25.146.177 即可解决</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<n-switch v-model:value="setData.enableRealIP">
|
||||
<template #checked>开启</template>
|
||||
<template #unchecked>关闭</template>
|
||||
</n-switch>
|
||||
<n-input
|
||||
v-if="setData.enableRealIP"
|
||||
v-model:value="setData.realIP"
|
||||
placeholder="realIP"
|
||||
@blur="validateAndSaveRealIP"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PlayBottom/>
|
||||
<n-modal
|
||||
v-model:show="showProxyModal"
|
||||
preset="dialog"
|
||||
title="代理设置"
|
||||
positive-text="确认"
|
||||
negative-text="取消"
|
||||
@positive-click="handleProxyConfirm"
|
||||
@negative-click="showProxyModal = false"
|
||||
:show-icon="false"
|
||||
>
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="proxyForm"
|
||||
:rules="proxyRules"
|
||||
label-placement="left"
|
||||
label-width="80"
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<n-form-item label="代理协议" 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="代理地址" path="host">
|
||||
<n-input v-model:value="proxyForm.host" placeholder="请输入代理地址" />
|
||||
</n-form-item>
|
||||
<n-form-item label="代理端口" path="port">
|
||||
<n-input-number v-model:value="proxyForm.port" placeholder="请输入代理端口" :min="1" :max="65535" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-modal>
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue';
|
||||
import { computed, ref, onMounted, watch } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import type { FormRules } from 'naive-ui';
|
||||
import { isElectron } from '@/utils';
|
||||
import { checkUpdate, UpdateResult } from '@/utils/update';
|
||||
import { selectDirectory, openDirectory } from '@/utils/fileOperation';
|
||||
@@ -144,7 +230,25 @@ const updateInfo = ref<UpdateResult>({
|
||||
releaseInfo: null
|
||||
});
|
||||
|
||||
const setData = computed(() => store.state.setData);
|
||||
const closeActionLabels = {
|
||||
ask: '每次询问',
|
||||
minimize: '最小化到托盘',
|
||||
close: '直接退出'
|
||||
} as const;
|
||||
|
||||
const setData = computed(() => {
|
||||
const data = store.state.setData;
|
||||
// 确保代理配置存在
|
||||
if (!data.proxyConfig) {
|
||||
data.proxyConfig = {
|
||||
enable: false,
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: 7890
|
||||
};
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
watch(() => setData.value, (newVal) => {
|
||||
store.commit('setSetData', newVal)
|
||||
@@ -203,8 +307,116 @@ const openDownloadPath = () => {
|
||||
openDirectory(setData.value.downloadPath, message);
|
||||
};
|
||||
|
||||
const showProxyModal = ref(false);
|
||||
const formRef = ref();
|
||||
const proxyForm = ref({
|
||||
protocol: 'http',
|
||||
host: '127.0.0.1',
|
||||
port: 7890
|
||||
});
|
||||
|
||||
const proxyRules: FormRules = {
|
||||
protocol: {
|
||||
required: true,
|
||||
message: '请选择代理协议',
|
||||
trigger: ['blur', 'change']
|
||||
},
|
||||
host: {
|
||||
required: true,
|
||||
message: '请输入代理地址',
|
||||
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: '请输入有效的端口号(1-65535)',
|
||||
trigger: ['blur', 'change'],
|
||||
validator: (_rule, value) => {
|
||||
return value >= 1 && value <= 65535;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化时从store获取代理配置
|
||||
onMounted(() => {
|
||||
checkForUpdates();
|
||||
if (setData.value.proxyConfig) {
|
||||
proxyForm.value = { ...setData.value.proxyConfig };
|
||||
}
|
||||
// 确保enableRealIP有默认值
|
||||
if (setData.value.enableRealIP === undefined) {
|
||||
store.commit('setSetData', {
|
||||
...setData.value,
|
||||
enableRealIP: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 监听代理配置变化
|
||||
watch(() => setData.value.proxyConfig, (newVal) => {
|
||||
if (newVal) {
|
||||
proxyForm.value = {
|
||||
protocol: newVal.protocol || 'http',
|
||||
host: newVal.host || '127.0.0.1',
|
||||
port: newVal.port || 7890
|
||||
};
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
const handleProxyConfirm = async () => {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
// 保存代理配置时保留enable状态
|
||||
store.commit('setSetData', {
|
||||
...setData.value,
|
||||
proxyConfig: {
|
||||
enable: setData.value.proxyConfig?.enable || false,
|
||||
protocol: proxyForm.value.protocol,
|
||||
host: proxyForm.value.host,
|
||||
port: proxyForm.value.port
|
||||
}
|
||||
});
|
||||
showProxyModal.value = false;
|
||||
message.success('代理设置已保存,重启应用后生效');
|
||||
} catch (err) {
|
||||
message.error('请检查输入是否正确');
|
||||
}
|
||||
};
|
||||
|
||||
const validateAndSaveRealIP = () => {
|
||||
const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||
if (!setData.value.realIP || ipRegex.test(setData.value.realIP)) {
|
||||
store.commit('setSetData', {
|
||||
...setData.value,
|
||||
realIP: setData.value.realIP,
|
||||
enableRealIP: true
|
||||
});
|
||||
if (setData.value.realIP) {
|
||||
message.success('真实IP设置已保存');
|
||||
}
|
||||
} else {
|
||||
message.error('请输入有效的IP地址');
|
||||
store.commit('setSetData', {
|
||||
...setData.value,
|
||||
realIP: ''
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 监听enableRealIP变化,当关闭时清空realIP
|
||||
watch(() => setData.value.enableRealIP, (newVal) => {
|
||||
if (!newVal) {
|
||||
store.commit('setSetData', {
|
||||
...setData.value,
|
||||
realIP: '',
|
||||
enableRealIP: false
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user