feat: mac 添加权限

This commit is contained in:
alger
2025-12-20 18:32:14 +08:00
parent 7e59cfee05
commit 00a251b5b6
13 changed files with 197 additions and 34 deletions

View File

@@ -16,5 +16,7 @@
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
</dict>
</plist>

View File

@@ -24,6 +24,7 @@ mac:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
- NSMicrophoneUsageDescription: Application requests access to the microphone for audio visualization.
notarize: false
dmg:
artifactName: ${name}-${version}.${ext}

View File

@@ -52,6 +52,31 @@ export default {
copyFailed: 'Copy failed',
backgroundDownload: 'Background Download'
},
disclaimer: {
title: 'Terms of Use',
warning:
'This application is a development test version. Functions are not yet perfect, and there may be many problems and bugs. It is for learning and exchange only.',
item1:
'This application is for personal learning, research and technical exchange only. Please do not use it for any commercial purposes.',
item2:
'Please delete it within 24 hours after downloading. If you need to use it for a long time, please support the genuine music service.',
item3:
'By using this application, you understand and assume the relevant risks. The developer is not responsible for any loss.',
agree: 'I have read and agree',
disagree: 'Disagree and Exit'
},
donate: {
title: 'Support Developer',
subtitle: 'Your support is my motivation',
tip: 'Donation is completely voluntary. All functions can be used normally without donation. Thank you for your understanding and support!',
wechat: 'WeChat',
alipay: 'Alipay',
wechatQR: 'WeChat QR Code',
alipayQR: 'Alipay QR Code',
scanTip: 'Please use your phone to scan the QR code above to donate',
enterApp: 'Enter App',
noForce: 'No forced donation, click to enter'
},
coffee: {
title: 'Buy me a coffee',
alipay: 'Alipay',

View File

@@ -52,6 +52,31 @@ export default {
copyFailed: 'コピーに失敗しました',
backgroundDownload: 'バックグラウンドダウンロード'
},
disclaimer: {
title: '使用上の注意',
warning:
'このアプリは開発テスト版であり、機能が不完全で、多くの問題やバグが存在する可能性があります。学習と交流のみを目的としています。',
item1:
'このアプリは個人の学習、研究、技術交流のみを目的としています。商業目的で使用しないでください。',
item2:
'ダウンロード後24時間以内に削除してください。長期使用を希望される場合は、正規の音楽サービスをサポートしてください。',
item3:
'このアプリを使用することで、関連するリスクを理解し、負担するものとします。開発者は一切の損失に対して責任を負いません。',
agree: '以上の内容を読み、同意します',
disagree: '同意せずに終了'
},
donate: {
title: '開発者を支援',
subtitle: '皆様のサポートが私の原動力です',
tip: '寄付は完全に任意です。寄付しなくてもすべての機能を通常通り使用できます。ご理解とご支援に感謝します!',
wechat: 'WeChat',
alipay: 'Alipay',
wechatQR: 'WeChat 受取コード',
alipayQR: 'Alipay 受取コード',
scanTip: 'スマートフォンのアプリで上記のQRコードをスキャンして寄付してください',
enterApp: 'アプリに入る',
noForce: '寄付は強制ではありません。クリックして入れます'
},
coffee: {
title: 'コーヒーをおごる',
alipay: 'Alipay',

View File

@@ -51,6 +51,31 @@ export default {
copyFailed: '복사 실패',
backgroundDownload: '백그라운드 다운로드'
},
disclaimer: {
title: '이용 안내',
warning:
'본 앱은 개발 테스트 버전으로 기능이 아직 미흡하며, 다수의 문제와 버그가 존재할 수 있습니다. 학습 및 교류 목적으로만 사용하십시오.',
item1:
'본 앱은 개인의 학습, 연구 및 기술 교류 목적으로만 사용되며, 상업적 용도로 사용하지 마십시오.',
item2:
'다운로드 후 24시간 이내에 삭제해 주십시오. 장기 사용을 원하시면 정품 음악 서비스를 이용해 주십시오.',
item3:
'본 앱을 사용함으로써 관련 위험을 이해하고 감수하는 것으로 간주합니다. 개발자는 어떠한 손실에 대해서도 책임을 지지 않습니다.',
agree: '숙지하였으며 이에 동의합니다',
disagree: '동의하지 않음 및 정지'
},
donate: {
title: '개발자 지원',
subtitle: '여러분의 지원이 저의 원동력입니다',
tip: '후원은 완전히 자율적입니다. 후원하지 않더라도 모든 기능을 정상적으로 사용할 수 있습니다. 이해와 지원에 감사드립니다!',
wechat: 'WeChat',
alipay: 'Alipay',
wechatQR: 'WeChat 결제 코드',
alipayQR: 'Alipay 결제 코드',
scanTip: '휴대전화로 위 QR 코드를 스캔하여 후원해 주세요',
enterApp: '앱 시작하기',
noForce: '후원은 강제가 아닙니다. 클릭하여 시작할 수 있습니다'
},
coffee: {
title: '커피 한 잔 사주세요',
alipay: '알리페이',

View File

@@ -50,6 +50,27 @@ export default {
copyFailed: '复制失败',
backgroundDownload: '后台下载'
},
disclaimer: {
title: '使用须知',
warning: '本应用为开发测试版本,功能尚不完善,可能存在较多问题和 Bug仅供学习交流使用。',
item1: '本应用仅供个人学习、研究和技术交流使用,请勿用于任何商业用途。',
item2: '请在下载后 24 小时内删除,如需长期使用请支持正版音乐服务。',
item3: '使用本应用即表示您理解并承担相关风险,开发者不对任何损失负责。',
agree: '我已阅读并同意',
disagree: '不同意并退出'
},
donate: {
title: '支持开发者',
subtitle: '您的支持是我前进的动力',
tip: '捐赠完全自愿,不捐赠也可以正常使用所有功能,感谢您的理解与支持!',
wechat: '微信',
alipay: '支付宝',
wechatQR: '微信收款码',
alipayQR: '支付宝收款码',
scanTip: '请使用手机扫描上方二维码进行捐赠',
enterApp: '进入应用',
noForce: '不强制捐赠,点击即可进入'
},
coffee: {
title: '请我喝咖啡',
alipay: '支付宝',

View File

@@ -50,6 +50,27 @@ export default {
copyFailed: '複製失敗',
backgroundDownload: '背景下載'
},
disclaimer: {
title: '使用說明',
warning: '本程式為開發測試版本,功能尚未完善,可能存在諸多問題及臭蟲,僅供學習交流使用。',
item1: '本程式僅供個人學習、研究及技術交流之目的,不得用於任何商業用途。',
item2: '請在下載後 24 小時內刪除,若對您有所幫助,請支持正版音樂。',
item3: '使用本程式即代表您已了解並同意相關風險,開發者對任何損失概不負責。',
agree: '我已了解並同意',
disagree: '不同意並退出'
},
donate: {
title: '支援開發者',
subtitle: '您的支援是我持續更新的動力',
tip: '捐贈完全採自願原則。即使不捐贈,您依然可以正常使用所有功能。感謝您的理解與支援!',
wechat: '微信支付',
alipay: '支付寶',
wechatQR: '微信收款碼',
alipayQR: '支付寶收款碼',
scanTip: '請使用手機 App 掃描 QR Code 進行捐贈',
enterApp: '進入程式',
noForce: '捐贈並非強制,您可以點擊按鈕直接進入'
},
coffee: {
title: '請我喝杯咖啡',
alipay: '支付寶',

View File

@@ -143,6 +143,12 @@ export function initializeWindowManager() {
}
});
// 强制退出应用(用于免责声明拒绝等场景)
ipcMain.on('quit-app', () => {
setAppQuitting(true);
app.quit();
});
ipcMain.on('mini-tray', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {

View File

@@ -23,14 +23,57 @@ ipcMain.handle('unblock-music', async (_event, id, songData, enabledSources) =>
}
});
async function startMusicApi(): Promise<void> {
console.log('MUSIC API STARTED');
const port = (store.get('set') as any).musicApiPort || 30488;
await server.serveNcmApi({
port
/**
* 检查端口是否可用
*/
function checkPortAvailable(port: number): Promise<boolean> {
return new Promise((resolve) => {
const net = require('net');
const tester = net
.createServer()
.once('error', () => {
resolve(false);
})
.once('listening', () => {
tester.close(() => resolve(true));
})
.listen(port);
});
}
async function startMusicApi(): Promise<void> {
console.log('MUSIC API STARTING...');
const settings = store.get('set') as any;
let port = settings?.musicApiPort || 30488;
const maxRetries = 10;
// 检查端口是否可用,如果不可用则尝试下一个端口
for (let i = 0; i < maxRetries; i++) {
const isAvailable = await checkPortAvailable(port);
if (isAvailable) {
break;
}
console.log(`端口 ${port} 被占用,尝试切换到端口 ${port + 1}`);
port++;
}
// 如果端口发生变化,保存新端口到配置
const originalPort = settings?.musicApiPort || 30488;
if (port !== originalPort) {
console.log(`端口从 ${originalPort} 切换到 ${port}`);
store.set('set', { ...settings, musicApiPort: port });
}
try {
await server.serveNcmApi({
port
});
console.log(`MUSIC API STARTED on port ${port}`);
} catch (error) {
console.error(`MUSIC API 启动失败:`, error);
throw error;
}
}
export { startMusicApi };

View File

@@ -4,6 +4,7 @@ interface API {
minimize: () => void;
maximize: () => void;
close: () => void;
quitApp: () => void;
dragStart: (data: any) => void;
miniTray: () => void;
miniWindow: () => void;
@@ -25,11 +26,7 @@ interface API {
importLxMusicScript: () => Promise<{ name: string; content: string } | null>;
invoke: (channel: string, ...args: any[]) => Promise<any>;
getSearchSuggestions: (keyword: string) => Promise<any>;
lxMusicHttpRequest: (request: {
url: string;
options: any;
requestId: string;
}) => Promise<any>;
lxMusicHttpRequest: (request: { url: string; options: any; requestId: string }) => Promise<any>;
lxMusicHttpCancel: (requestId: string) => Promise<void>;
}

View File

@@ -6,6 +6,7 @@ const api = {
minimize: () => ipcRenderer.send('minimize-window'),
maximize: () => ipcRenderer.send('maximize-window'),
close: () => ipcRenderer.send('close-window'),
quitApp: () => ipcRenderer.send('quit-app'),
dragStart: (data) => ipcRenderer.send('drag-start', data),
miniTray: () => ipcRenderer.send('mini-tray'),
miniWindow: () => ipcRenderer.send('mini-window'),
@@ -61,11 +62,8 @@ const api = {
getSearchSuggestions: (keyword: string) => ipcRenderer.invoke('get-search-suggestions', keyword),
// 落雪音乐 HTTP 请求(绕过 CORS
lxMusicHttpRequest: (request: {
url: string;
options: any;
requestId: string;
}) => ipcRenderer.invoke('lx-music-http-request', request),
lxMusicHttpRequest: (request: { url: string; options: any; requestId: string }) =>
ipcRenderer.invoke('lx-music-http-request', request),
lxMusicHttpCancel: (requestId: string) => ipcRenderer.invoke('lx-music-http-cancel', requestId)
};

View File

@@ -5,6 +5,7 @@
<n-message-provider>
<router-view></router-view>
<traffic-warning-drawer v-if="!isElectron"></traffic-warning-drawer>
<disclaimer-modal></disclaimer-modal>
</n-message-provider>
</n-dialog-provider>
</n-config-provider>
@@ -18,6 +19,7 @@ import { computed, nextTick, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import DisclaimerModal from '@/components/common/DisclaimerModal.vue';
import TrafficWarningDrawer from '@/components/TrafficWarningDrawer.vue';
import { usePlayerStore } from '@/store/modules/player';
import { useSettingsStore } from '@/store/modules/settings';

View File

@@ -237,6 +237,7 @@ import { useI18n } from 'vue-i18n';
// 导入收款码图片
import alipayQRCode from '@/assets/alipay.png';
import wechatQRCode from '@/assets/wechat.png';
import { isElectron, isLyricWindow } from '@/utils';
const { t } = useI18n();
@@ -250,20 +251,8 @@ const qrcodeType = ref<'wechat' | 'alipay'>('wechat');
const isTransitioning = ref(false); // 防止用户点击过快
// 检查是否需要显示免责声明
const shouldShowDisclaimer = (): boolean => {
const agreedTime = localStorage.getItem(DISCLAIMER_AGREED_KEY);
// 从未同意过
if (!agreedTime) return true;
const savedTime = parseInt(agreedTime, 10);
const now = Date.now();
// 随机 3-10 天后再次显示
const randomDays = Math.floor(Math.random() * 8) + 3; // 3-10 天
const intervalMs = randomDays * 24 * 60 * 60 * 1000;
return now - savedTime >= intervalMs;
const shouldShowDisclaimer = () => {
return !localStorage.getItem(DISCLAIMER_AGREED_KEY);
};
// 处理同意
@@ -278,13 +267,18 @@ const handleAgree = () => {
}, 300);
};
// 处理不同意 - 关闭窗口
// 处理不同意 - 退出应用
const handleDisagree = () => {
if (isTransitioning.value) return;
isTransitioning.value = true;
if (isElectron) {
// Electron 环境下强制退出应用
window.api?.quitApp?.();
} else {
// Web 环境下尝试关闭窗口
window.close();
}
isTransitioning.value = false;
};
@@ -316,6 +310,9 @@ const handleEnterApp = () => {
};
onMounted(() => {
// 歌词窗口不显示免责声明
if (isLyricWindow.value) return;
// 检查是否需要显示免责声明
if (shouldShowDisclaimer()) {
showDisclaimer.value = true;