diff --git a/.vscode/settings.json b/.vscode/settings.json index 1e3e2cde..da749f93 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,10 @@ ], "url": "https://json.schemastore.org/electron-builder" } + ], + "eslint.validate": [ + "javascript", + "javascriptreact", + "vue" ] } diff --git a/electron/global.ts b/electron/global.ts index 0cedfbc1..4d0437bb 100644 --- a/electron/global.ts +++ b/electron/global.ts @@ -1,2 +1,2 @@ import { ref } from 'vue'; -export let isLoaded = ref(false); \ No newline at end of file +export const isLoaded = ref(false); \ No newline at end of file diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index 1c4f6970..09887094 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -1,6 +1,5 @@ import { ipcMain, WebContents } from 'electron'; import { spawn, ChildProcess, exec } from 'node:child_process'; -import readline from 'node:readline'; import { promisify } from 'node:util'; import pino from 'pino'; @@ -122,7 +121,7 @@ const parseUpgradableList = (output: string) => { // Listen for download requests from renderer process ipcMain.on('queue-install', async (event, download_json) => { const download = JSON.parse(download_json); - const { id, pkgname, upgradeOnly } = download || {}; + const { id, pkgname } = download || {}; if (!id || !pkgname) { logger.warn('passed arguments missing id or pkgname'); @@ -151,9 +150,9 @@ ipcMain.on('queue-install', async (event, download_json) => { const webContents = event.sender; // 开始组装安装命令 - let superUserCmd = await checkSuperUserCommand(); + const superUserCmd = await checkSuperUserCommand(); let execCommand = ''; - let execParams = []; + const execParams = []; if (superUserCmd.length > 0) { execCommand = superUserCmd; execParams.push(SHELL_CALLER_PATH); @@ -258,7 +257,7 @@ ipcMain.handle('check-installed', async (_event, pkgname: string) => { logger.info(`检查应用是否已安装: ${pkgname}`); - let child = spawn(SHELL_CALLER_PATH, ['apm', 'list', '--installed', pkgname], { + const child = spawn(SHELL_CALLER_PATH, ['apm', 'list', '--installed', pkgname], { shell: true, env: process.env }); @@ -291,16 +290,16 @@ ipcMain.on('remove-installed', async (_event, pkgname: string) => { } logger.info(`卸载已安装应用: ${pkgname}`); - let superUserCmd = await checkSuperUserCommand(); + const superUserCmd = await checkSuperUserCommand(); let execCommand = ''; - let execParams = []; + const execParams = []; if (superUserCmd.length > 0) { execCommand = superUserCmd; execParams.push(SHELL_CALLER_PATH); } else { execCommand = SHELL_CALLER_PATH; } - let child = spawn(execCommand, [...execParams, 'apm', 'remove', '-y', pkgname], { + const child = spawn(execCommand, [...execParams, 'apm', 'remove', '-y', pkgname], { shell: true, env: process.env }); diff --git a/electron/main/deeplink.ts b/electron/main/deeplink.ts index 2f941ba1..11d02718 100644 --- a/electron/main/deeplink.ts +++ b/electron/main/deeplink.ts @@ -7,7 +7,7 @@ import pino from "pino"; const logger = pino({ 'name': 'deeplink.ts' }); type Query = Record; -export type Listener = (query: Query) => any; +export type Listener = (query: Query) => void; class ListenersMap { private map: Map> = new Map(); diff --git a/electron/main/index.ts b/electron/main/index.ts index ae9ae08f..ad831982 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1,5 +1,4 @@ import { app, BrowserWindow, ipcMain, Menu, shell, Tray } from 'electron' -import { createRequire } from 'node:module' import { fileURLToPath } from 'node:url' import path from 'node:path' import os from 'node:os' @@ -19,7 +18,6 @@ import './backend/install-manager.js' import './handle-url-scheme.js' const logger = pino({ 'name': 'index.ts' }); -const require = createRequire(import.meta.url) const __dirname = path.dirname(fileURLToPath(import.meta.url)) // The built directory structure @@ -187,8 +185,13 @@ app.whenReady().then(() => { // 双击触发 tray.on('click', () => { // 双击通知区图标实现应用的显示或隐藏 - win.isVisible() ? win.hide() : win.show() - win.isVisible() ? win.setSkipTaskbar(false) : win.setSkipTaskbar(true); + if (win.isVisible()) { + win.hide(); + win.setSkipTaskbar(true); + } else { + win.show(); + win.setSkipTaskbar(false); + } }); }) diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 1bc7e0cf..f2f77cd1 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -123,7 +123,8 @@ const { appendLoading, removeLoading } = useLoading() domReady().then(appendLoading) window.onmessage = (ev) => { - ev.data.payload === 'removeLoading' && removeLoading() + if (ev.data.payload === 'removeLoading') + removeLoading() } setTimeout(removeLoading, 4999) diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 00000000..69064c25 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,17 @@ +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; +import pluginVue from "eslint-plugin-vue"; +import { defineConfig, globalIgnores } from "eslint/config"; +import eslintConfigPrettier from "eslint-config-prettier/flat"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; + +export default defineConfig([ + globalIgnores(["**/3rdparty/**", "**/node_modules/**", "**/dist/**", "**/dist-electron/**"]), + { files: ["**/*.{js,mjs,cjs,ts,mts,cts,vue}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: { ...globals.browser, ...globals.node } } }, + tseslint.configs.recommended, + pluginVue.configs["flat/essential"], + { files: ["**/*.vue"], languageOptions: { parserOptions: { parser: tseslint.parser } } }, + eslintConfigPrettier, + eslintPluginPrettierRecommended, +]); diff --git a/package.json b/package.json index c4e95aec..f7607349 100644 --- a/package.json +++ b/package.json @@ -26,15 +26,26 @@ "build:vite": "vue-tsc --noEmit && vite build --mode production", "build:rpm": "vue-tsc --noEmit && vite build --mode production && electron-builder --config electron-builder.yml --linux rpm", "build:deb": "vue-tsc --noEmit && vite build --mode production && electron-builder --config electron-builder.yml --linux deb", - "preview": "vite preview --mode debug" + "preview": "vite preview --mode debug", + "lint": "eslint --ext .ts,.vue src electron" }, "devDependencies": { "@dotenvx/dotenvx": "^1.51.4", + "@eslint/create-config": "^1.11.0", + "@eslint/js": "^9.39.2", "@loongdotjs/electron-builder": "^26.0.12-1", "@vitejs/plugin-vue": "^6.0.3", "electron": "^40.0.0", + "eslint": "^9.39.2", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-vue": "^10.7.0", + "globals": "^17.3.0", + "jiti": "^2.6.1", "pino-pretty": "^13.1.3", + "prettier": "3.8.1", "typescript": "^5.4.2", + "typescript-eslint": "^8.55.0", "vite": "^6.4.1", "vite-plugin-electron": "^0.29.0", "vite-plugin-electron-renderer": "^0.14.5", diff --git a/src/App.vue b/src/App.vue index 77d989c8..b8df9995 100644 --- a/src/App.vue +++ b/src/App.vue @@ -59,8 +59,9 @@ import UninstallConfirmModal from './components/UninstallConfirmModal.vue'; import { APM_STORE_BASE_URL, currentApp, currentAppIsInstalled } from './global/storeConfig'; import { downloads, removeDownloadItem, watchDownloadsChange } from './global/downloadStatus'; import { handleInstall, handleRetry, handleUpgrade } from './modeuls/processInstall'; -import type { App, AppJson, DownloadItem, UpdateAppItem, InstalledAppInfo, ChannelPayload } from './global/typedefinition'; +import type { App, AppJson, DownloadItem, UpdateAppItem, ChannelPayload } from './global/typedefinition'; import type { Ref } from 'vue'; +import type { IpcRendererEvent } from 'electron'; const logger = pino(); // Axios 全局配置 @@ -250,9 +251,9 @@ const refreshUpgradableApps = async () => { selected: false, upgrading: false })); - } catch (error: any) { + } catch (error: unknown) { upgradableApps.value = []; - updateError.value = error?.message || '检查更新失败'; + updateError.value = (error as Error)?.message || '检查更新失败'; } finally { updateLoading.value = false; } @@ -322,7 +323,7 @@ const refreshInstalledApps = async () => { installedError.value = result?.message || '读取已安装应用失败'; return; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any + installedApps.value = [] for (const app of result.apps) { let appInfo = apps.value.find(a => a.pkgname === app.pkgname); @@ -355,9 +356,9 @@ const refreshInstalledApps = async () => { } installedApps.value.push(appInfo); } - } catch (error: any) { + } catch (error: unknown) { installedApps.value = []; - installedError.value = error?.message || '读取已安装应用失败'; + installedError.value = (error as Error)?.message || '读取已安装应用失败'; } finally { installedLoading.value = false; } @@ -409,70 +410,6 @@ const uninstallInstalledApp = (app: App) => { requestUninstall(app); }; -const openApmStoreUrl = (url: string, { fallbackText }: { fallbackText: string }) => { - try { - window.location.href = url; - } catch (e) { - showProtocolFallback(fallbackText); - } -}; - -const showProtocolFallback = (cmd: string) => { - const existing = document.getElementById('protocolFallbackBox'); - if (existing) existing.remove(); - - const box = document.createElement('div'); - box.id = 'protocolFallbackBox'; - box.style.position = 'fixed'; - box.style.right = '18px'; - box.style.bottom = '18px'; - box.style.zIndex = '2000'; - box.style.boxShadow = 'var(--shadow)'; - box.style.background = 'var(--card)'; - box.style.borderRadius = '12px'; - box.style.padding = '12px'; - box.style.maxWidth = '420px'; - box.style.fontSize = '13px'; - box.innerHTML = ` -
无法直接启动本地应用?
-
请在终端执行下列命令,或检查系统是否已将 apmstore:// 协议关联到 APM 处理程序。
-
- ${escapeHtml(cmd)} - - -
- `; - document.body.appendChild(box); - - document.getElementById('copyApmCmd')?.addEventListener('click', () => { - navigator.clipboard?.writeText(cmd).then(() => { - alert('命令已复制到剪贴板'); - }).catch(() => { - prompt('请手动复制命令:', cmd); - }); - }); - - document.getElementById('dismissApmCmd')?.addEventListener('click', () => { - box.remove(); - }); - - // 自动消失 - setTimeout(() => { - try { box.remove(); } catch (e) { } - }, 30000); -}; - -const escapeHtml = (s: string) => { - if (!s) return ''; - return s.replace(/[&<>"']/g, (c) => ({ - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - })[c as '&' | '<' | '>' | '"' | "'"]); -}; - // 目前 APM 商店不能暂停下载(因为 APM 本身不支持),但保留这些方法以备将来使用 const pauseDownload = (id: DownloadItem) => { const download = downloads.value.find(d => d.id === id.id); @@ -660,7 +597,7 @@ onMounted(async () => { } }); - window.ipcRenderer.on('deep-link-install', (_event: Electron.IpcRendererEvent, pkgname: string) => { + window.ipcRenderer.on('deep-link-install', (_event: IpcRendererEvent, pkgname: string) => { const tryOpen = () => { const target = apps.value.find(a => a.pkgname === pkgname); if (target) { @@ -682,7 +619,7 @@ onMounted(async () => { } }); - window.ipcRenderer.on('remove-complete', (_event: Electron.IpcRendererEvent, payload: ChannelPayload) => { + window.ipcRenderer.on('remove-complete', (_event: IpcRendererEvent, payload: ChannelPayload) => { const pkgname = currentApp.value?.pkgname if(payload.success && pkgname){ removeDownloadItem(pkgname); diff --git a/src/components/DownloadDetail.vue b/src/components/DownloadDetail.vue index 715c97c2..a6d26f60 100644 --- a/src/components/DownloadDetail.vue +++ b/src/components/DownloadDetail.vue @@ -146,18 +146,6 @@ const handleOverlayClick = () => { close(); }; -const pause = () => { - // emit('pause', props.download.id); -}; - -const resume = () => { - // emit('resume', props.download.id); -}; - -const cancel = () => { - //emit('cancel', props.download.id); -}; - const retry = () => { if (props.download) { emit('retry', props.download); diff --git a/src/components/DownloadQueue.vue b/src/components/DownloadQueue.vue index 5bd3719e..10ce3313 100644 --- a/src/components/DownloadQueue.vue +++ b/src/components/DownloadQueue.vue @@ -100,18 +100,6 @@ const toggleExpand = () => { isExpanded.value = !isExpanded.value; }; -const pauseDownload = (id: string) => { - // emit('pause', id); -}; - -const resumeDownload = (id: string) => { - // emit('resume', id); -}; - -const cancelDownload = (id: string) => { - // emit('cancel', id); -}; - const retryDownload = (download: DownloadItem) => { emit('retry', download); }; diff --git a/src/components/UpdateAppsModal.vue b/src/components/UpdateAppsModal.vue index 6c8a5349..fa18d539 100644 --- a/src/components/UpdateAppsModal.vue +++ b/src/components/UpdateAppsModal.vue @@ -90,6 +90,7 @@ defineProps<{ hasSelected: boolean; }>(); +// eslint-disable-next-line @typescript-eslint/no-unused-vars const emit = defineEmits<{ (e: 'close'): void; (e: 'refresh'): void; diff --git a/src/modeuls/processInstall.ts b/src/modeuls/processInstall.ts index f2e3a16e..94d98f1b 100644 --- a/src/modeuls/processInstall.ts +++ b/src/modeuls/processInstall.ts @@ -7,7 +7,7 @@ import { currentApp, currentAppIsInstalled } from "../global/storeConfig"; import { APM_STORE_BASE_URL } from "../global/storeConfig"; import { downloads } from "../global/downloadStatus"; -import { InstallLog, DownloadItem, DownloadResult, App } from '../global/typedefinition'; +import { InstallLog, DownloadItem, DownloadResult, App, DownloadItemStatus } from '../global/typedefinition'; let downloadIdCounter = 0; const logger = pino({ name: 'processInstall.ts' }); @@ -110,7 +110,7 @@ window.ipcRenderer.on('remove-complete', (_event, log: DownloadResult) => { window.ipcRenderer.on('install-status', (_event, log: InstallLog) => { const downloadObj = downloads.value.find(d => d.id === log.id); - if(downloadObj) downloadObj.status = log.message as any; + if(downloadObj) downloadObj.status = log.message as DownloadItemStatus; }); window.ipcRenderer.on('install-log', (_event, log: InstallLog) => { const downloadObj = downloads.value.find(d => d.id === log.id); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 5bab384c..88cf6479 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ /// declare module '*.vue' {