fix format and lint

This commit is contained in:
xishang0128 2025-12-25 15:32:32 +08:00
parent b7d6ea8e7a
commit 54bb819e28
No known key found for this signature in database
GPG Key ID: 02DAB1BEA331D06C
39 changed files with 387 additions and 352 deletions

View File

@ -1,4 +0,0 @@
node_modules
dist
out
.gitignore

View File

@ -1,9 +0,0 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'@electron-toolkit/eslint-config-ts/recommended',
'@electron-toolkit/eslint-config-prettier'
]
}

48
eslint.config.cjs Normal file
View File

@ -0,0 +1,48 @@
const js = require('@eslint/js')
const react = require('eslint-plugin-react')
const { configs } = require('@electron-toolkit/eslint-config-ts')
module.exports = [
{
ignores: ['**/node_modules/**', '**/dist/**', '**/out/**', '**/extra/**']
},
js.configs.recommended,
...configs.recommended,
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: {
react: react
},
rules: {
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules
},
settings: {
react: {
version: 'detect'
}
},
languageOptions: {
...react.configs.recommended.languageOptions
}
},
{
files: ['**/*.cjs', '**/*.mjs', '**/tailwind.config.js', '**/postcss.config.js'],
rules: {
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/explicit-function-return-type': 'off'
}
},
{
files: ['**/*.{ts,tsx}'],
rules: {
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'warn'
}
}
]

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import fs from 'fs' import fs from 'fs'
import AdmZip from 'adm-zip' import AdmZip from 'adm-zip'
import path from 'path' import path from 'path'

View File

@ -9,9 +9,7 @@ import {
} from './version-utils.mjs' } from './version-utils.mjs'
const chat_id = '@MihomoPartyChannel' const chat_id = '@MihomoPartyChannel'
const pkg = readFileSync('package.json', 'utf-8')
const changelog = readFileSync('changelog.md', 'utf-8') const changelog = readFileSync('changelog.md', 'utf-8')
const { version: packageVersion } = JSON.parse(pkg)
// 获取处理后的版本号 // 获取处理后的版本号
const version = getProcessedVersion() const version = getProcessedVersion()

View File

@ -7,9 +7,7 @@ import {
generateDownloadLinksMarkdown generateDownloadLinksMarkdown
} from './version-utils.mjs' } from './version-utils.mjs'
const pkg = readFileSync('package.json', 'utf-8')
let changelog = readFileSync('changelog.md', 'utf-8') let changelog = readFileSync('changelog.md', 'utf-8')
const { version: packageVersion } = JSON.parse(pkg)
// 获取处理后的版本号 // 获取处理后的版本号
const version = getProcessedVersion() const version = getProcessedVersion()

View File

@ -78,7 +78,7 @@ export async function createOverride(item: Partial<IOverrideItem>): Promise<IOve
}, },
responseType: 'text' responseType: 'text'
}) })
const data = res.data const data = res.data as string
await setOverride(id, newItem.ext, data) await setOverride(id, newItem.ext, data)
break break
} }

View File

@ -418,7 +418,9 @@ export async function convertMrsRuleset(filePath: string, behavior: string): Pro
} catch (error) { } catch (error) {
try { try {
await unlink(tempFilePath) await unlink(tempFilePath)
} catch {} } catch {
// ignore
}
throw error throw error
} }
} }

View File

@ -28,7 +28,7 @@ let runtimeConfig: IMihomoConfig
// 辅助函数:处理带偏移量的规则 // 辅助函数:处理带偏移量的规则
function processRulesWithOffset(ruleStrings: string[], currentRules: string[], isAppend = false) { function processRulesWithOffset(ruleStrings: string[], currentRules: string[], isAppend = false) {
const normalRules: string[] = [] const normalRules: string[] = []
let rules = [...currentRules] const rules = [...currentRules]
ruleStrings.forEach((ruleStr) => { ruleStrings.forEach((ruleStr) => {
const parts = ruleStr.split(',') const parts = ruleStr.split(',')
@ -65,7 +65,7 @@ export async function generateProfile(): Promise<void> {
controlSniff = true, controlSniff = true,
useNameserverPolicy useNameserverPolicy
} = await getAppConfig() } = await getAppConfig()
let currentProfile = await overrideProfile(current, await getProfile(current)) const currentProfile = await overrideProfile(current, await getProfile(current))
let controledMihomoConfig = await getControledMihomoConfig() let controledMihomoConfig = await getControledMihomoConfig()
// 根据开关状态过滤控制配置 // 根据开关状态过滤控制配置
@ -132,7 +132,7 @@ export async function generateProfile(): Promise<void> {
} }
} }
} catch (error) { } catch (error) {
console.error('读取或应用规则文件时出错:', error) console.error('读取或应用规则文件时出错', error)
} }
const profile = deepMerge(currentProfile, controledMihomoConfig) const profile = deepMerge(currentProfile, controledMihomoConfig)

View File

@ -330,8 +330,8 @@ async function cleanupWindowsNamedPipes(): Promise<void> {
process.kill(pid, 0) process.kill(pid, 0)
process.kill(pid, 'SIGTERM') process.kill(pid, 'SIGTERM')
await managerLogger.info(`Terminated process ${pid} to free pipe`) await managerLogger.info(`Terminated process ${pid} to free pipe`)
} catch (error: any) { } catch (error: unknown) {
if (error.code !== 'ESRCH') { if ((error as { code?: string })?.code !== 'ESRCH') {
await managerLogger.warn(`Failed to terminate process ${pid}:`, error) await managerLogger.warn(`Failed to terminate process ${pid}:`, error)
} }
} }
@ -351,8 +351,8 @@ async function cleanupWindowsNamedPipes(): Promise<void> {
process.kill(pid, 0) process.kill(pid, 0)
process.kill(pid, 'SIGTERM') process.kill(pid, 'SIGTERM')
await managerLogger.info(`Terminated process ${pid} to free pipe`) await managerLogger.info(`Terminated process ${pid} to free pipe`)
} catch (error: any) { } catch (error: unknown) {
if (error.code !== 'ESRCH') { if ((error as { code?: string })?.code !== 'ESRCH') {
await managerLogger.warn(`Failed to terminate process ${pid}:`, error) await managerLogger.warn(`Failed to terminate process ${pid}:`, error)
} }
} }
@ -589,8 +589,8 @@ export async function checkAdminPrivileges(): Promise<boolean> {
await execPromise('chcp 65001 >nul 2>&1 && fltmc', { encoding: 'utf8' }) await execPromise('chcp 65001 >nul 2>&1 && fltmc', { encoding: 'utf8' })
await managerLogger.info('Admin privileges confirmed via fltmc') await managerLogger.info('Admin privileges confirmed via fltmc')
return true return true
} catch (fltmcError: any) { } catch (fltmcError: unknown) {
const errorCode = fltmcError?.code || 0 const errorCode = (fltmcError as { code?: number })?.code || 0
await managerLogger.debug(`fltmc failed with code ${errorCode}, trying net session as fallback`) await managerLogger.debug(`fltmc failed with code ${errorCode}, trying net session as fallback`)
try { try {
@ -598,8 +598,8 @@ export async function checkAdminPrivileges(): Promise<boolean> {
await execPromise('chcp 65001 >nul 2>&1 && net session', { encoding: 'utf8' }) await execPromise('chcp 65001 >nul 2>&1 && net session', { encoding: 'utf8' })
await managerLogger.info('Admin privileges confirmed via net session') await managerLogger.info('Admin privileges confirmed via net session')
return true return true
} catch (netSessionError: any) { } catch (netSessionError: unknown) {
const netErrorCode = netSessionError?.code || 0 const netErrorCode = (netSessionError as { code?: number })?.code || 0
await managerLogger.debug( await managerLogger.debug(
`Both fltmc and net session failed, no admin privileges. Error codes: fltmc=${errorCode}, net=${netErrorCode}` `Both fltmc and net session failed, no admin privileges. Error codes: fltmc=${errorCode}, net=${netErrorCode}`
) )
@ -618,7 +618,8 @@ export async function showTunPermissionDialog(): Promise<boolean> {
const title = i18next.t('tun.permissions.title') || '需要管理员权限' const title = i18next.t('tun.permissions.title') || '需要管理员权限'
const message = const message =
i18next.t('tun.permissions.message') || '启用TUN模式需要管理员权限是否现在重启应用获取权限' i18next.t('tun.permissions.message') ||
'启用 TUN 模式需要管理员权限,是否现在重启应用获取权限?'
const confirmText = i18next.t('common.confirm') || '确认' const confirmText = i18next.t('common.confirm') || '确认'
const cancelText = i18next.t('common.cancel') || '取消' const cancelText = i18next.t('common.cancel') || '取消'
@ -834,7 +835,9 @@ async function checkHighPrivilegeMihomoProcess(): Promise<boolean> {
} }
} }
} }
} catch (error) {} } catch (error) {
// ignore
}
} }
if (!foundProcesses) { if (!foundProcesses) {

View File

@ -5,13 +5,13 @@ import { getAppConfig } from '../config'
export async function subStoreSubs(): Promise<ISubStoreSub[]> { export async function subStoreSubs(): Promise<ISubStoreSub[]> {
const { useCustomSubStore = false, customSubStoreUrl = '' } = await getAppConfig() const { useCustomSubStore = false, customSubStoreUrl = '' } = await getAppConfig()
const baseUrl = useCustomSubStore ? customSubStoreUrl : `http://127.0.0.1:${subStorePort}` const baseUrl = useCustomSubStore ? customSubStoreUrl : `http://127.0.0.1:${subStorePort}`
const res = await chromeRequest.get(`${baseUrl}/api/subs`, { responseType: 'json' }) const res = await chromeRequest.get<{ data: ISubStoreSub[] }>(`${baseUrl}/api/subs`, { responseType: 'json' })
return res.data.data as ISubStoreSub[] return res.data.data
} }
export async function subStoreCollections(): Promise<ISubStoreSub[]> { export async function subStoreCollections(): Promise<ISubStoreSub[]> {
const { useCustomSubStore = false, customSubStoreUrl = '' } = await getAppConfig() const { useCustomSubStore = false, customSubStoreUrl = '' } = await getAppConfig()
const baseUrl = useCustomSubStore ? customSubStoreUrl : `http://127.0.0.1:${subStorePort}` const baseUrl = useCustomSubStore ? customSubStoreUrl : `http://127.0.0.1:${subStorePort}`
const res = await chromeRequest.get(`${baseUrl}/api/collections`, { responseType: 'json' }) const res = await chromeRequest.get<{ data: ISubStoreSub[] }>(`${baseUrl}/api/collections`, { responseType: 'json' })
return res.data.data as ISubStoreSub[] return res.data.data
} }

View File

@ -26,7 +26,7 @@ export async function checkUpdate(): Promise<IAppVersion | undefined> {
responseType: 'text' responseType: 'text'
} }
) )
const latest = parse(res.data) as IAppVersion const latest = parse(res.data as string) as IAppVersion
const currentVersion = app.getVersion() const currentVersion = app.getVersion()
if (compareVersions(latest.version, currentVersion) > 0) { if (compareVersions(latest.version, currentVersion) > 0) {
return latest return latest
@ -94,7 +94,7 @@ export async function downloadAndInstallUpdate(version: string): Promise<void> {
'Content-Type': 'application/octet-stream' 'Content-Type': 'application/octet-stream'
} }
}) })
await writeFile(path.join(dataDir(), file), res.data) await writeFile(path.join(dataDir(), file), res.data as string | Buffer)
} }
if (file.endsWith('.exe')) { if (file.endsWith('.exe')) {
try { try {

View File

@ -9,7 +9,7 @@ import { floatingWindowLogger } from '../utils/logger'
export let floatingWindow: BrowserWindow | null = null export let floatingWindow: BrowserWindow | null = null
function logError(message: string, error?: any): void { function logError(message: string, error?: unknown): void {
floatingWindowLogger.log(`FloatingWindow Error: ${message}`, error).catch(() => {}) floatingWindowLogger.log(`FloatingWindow Error: ${message}`, error).catch(() => {})
} }

View File

@ -395,7 +395,7 @@ export async function createTray(): Promise<void> {
image.setTemplateImage(true) image.setTemplateImage(true)
tray?.setImage(image) tray?.setImage(image)
}) })
// macOS 默认行为: 左键显示窗口, 右键显示菜单 // macOS 默认行为:左键显示窗口,右键显示菜单
tray?.addListener('click', async () => { tray?.addListener('click', async () => {
if (swapTrayClick) { if (swapTrayClick) {
await updateTrayMenu() await updateTrayMenu()
@ -537,7 +537,7 @@ export function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: bo
tray.setImage(iconPath) tray.setImage(iconPath)
} }
} catch (error) { } catch (error) {
console.error('更新托盘图标失败:', error) console.error('更新托盘图标失败', error)
} }
}) })
} }
@ -560,6 +560,6 @@ export async function updateTrayIcon(): Promise<void> {
tray.setImage(iconPath) tray.setImage(iconPath)
} }
} catch (error) { } catch (error) {
console.error('更新托盘图标失败:', error) console.error('更新托盘图标失败', error)
} }
} }

View File

@ -2,7 +2,7 @@ import { triggerAutoProxy, triggerManualProxy } from '@mihomo-party/sysproxy'
import { getAppConfig, getControledMihomoConfig } from '../config' import { getAppConfig, getControledMihomoConfig } from '../config'
import { pacPort, startPacServer, stopPacServer } from '../resolve/server' import { pacPort, startPacServer, stopPacServer } from '../resolve/server'
import { promisify } from 'util' import { promisify } from 'util'
import { execFile } from 'child_process' import { exec, execFile } from 'child_process'
import path from 'path' import path from 'path'
import { resourcesFilesDir } from '../utils/dirs' import { resourcesFilesDir } from '../utils/dirs'
import { net } from 'electron' import { net } from 'electron'
@ -164,8 +164,6 @@ function isSocketFileExists(): boolean {
async function requestSocketRecreation(): Promise<void> { async function requestSocketRecreation(): Promise<void> {
try { try {
// Send SIGUSR1 signal to helper process to recreate socket // Send SIGUSR1 signal to helper process to recreate socket
const { exec } = require('child_process')
const { promisify } = require('util')
const execPromise = promisify(exec) const execPromise = promisify(exec)
// Use osascript with administrator privileges (same pattern as grantTunPermissions) // Use osascript with administrator privileges (same pattern as grantTunPermissions)

View File

@ -17,7 +17,7 @@ export interface RequestOptions {
maxRedirects?: number maxRedirects?: number
} }
export interface Response<T = any> { export interface Response<T = unknown> {
data: T data: T
status: number status: number
statusText: string statusText: string
@ -29,7 +29,7 @@ export interface Response<T = any> {
* Make HTTP request using Chromium's network stack (via electron.net) * Make HTTP request using Chromium's network stack (via electron.net)
* This provides better compatibility, HTTP/2 support, and system certificate integration * This provides better compatibility, HTTP/2 support, and system certificate integration
*/ */
export async function request<T = any>( export async function request<T = unknown>(
url: string, url: string,
options: RequestOptions = {} options: RequestOptions = {}
): Promise<Response<T>> { ): Promise<Response<T>> {
@ -45,7 +45,7 @@ export async function request<T = any>(
} = options } = options
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let sessionToUse = session.defaultSession let sessionToUse: Electron.Session | undefined = session.defaultSession
let tempPartition: string | null = null let tempPartition: string | null = null
// Set up proxy if specified // Set up proxy if specified
@ -64,7 +64,7 @@ export async function request<T = any>(
if (tempPartition) { if (tempPartition) {
// Note: Electron doesn't provide session.destroy(), but temporary sessions // Note: Electron doesn't provide session.destroy(), but temporary sessions
// will be garbage collected when no longer referenced // will be garbage collected when no longer referenced
sessionToUse = null as any sessionToUse = undefined
} }
} }
@ -125,7 +125,7 @@ export async function request<T = any>(
if (timeoutId) clearTimeout(timeoutId) if (timeoutId) clearTimeout(timeoutId)
const buffer = Buffer.concat(chunks) const buffer = Buffer.concat(chunks)
let data: any let data: unknown
try { try {
switch (responseType) { switch (responseType) {
@ -141,25 +141,25 @@ export async function request<T = any>(
} }
resolve({ resolve({
data, data: data as T,
status: statusCode, status: statusCode,
statusText: statusMessage, statusText: statusMessage,
headers: responseHeaders, headers: responseHeaders,
url: url url: url
}) })
} catch (error) { } catch (error: unknown) {
reject(new Error(`Failed to parse response: ${error}`)) reject(new Error(`Failed to parse response: ${String(error)}`))
} }
}) })
res.on('error', (error) => { res.on('error', (error: unknown) => {
cleanup() cleanup()
if (timeoutId) clearTimeout(timeoutId) if (timeoutId) clearTimeout(timeoutId)
reject(error) reject(error)
}) })
}) })
req.on('error', (error) => { req.on('error', (error: unknown) => {
cleanup() cleanup()
if (timeoutId) clearTimeout(timeoutId) if (timeoutId) clearTimeout(timeoutId)
reject(error) reject(error)
@ -182,9 +182,9 @@ export async function request<T = any>(
req.end() req.end()
}) })
.catch((error) => { .catch((error: unknown) => {
cleanup() cleanup()
reject(new Error(`Failed to setup proxy: ${error}`)) reject(new Error(`Failed to setup proxy: ${String(error)}`))
}) })
}) })
} }
@ -192,7 +192,7 @@ export async function request<T = any>(
/** /**
* Convenience method for GET requests * Convenience method for GET requests
*/ */
export const get = <T = any>( export const get = <T = unknown>(
url: string, url: string,
options?: Omit<RequestOptions, 'method' | 'body'> options?: Omit<RequestOptions, 'method' | 'body'>
): Promise<Response<T>> => request<T>(url, { ...options, method: 'GET' }) ): Promise<Response<T>> => request<T>(url, { ...options, method: 'GET' })
@ -200,9 +200,9 @@ export const get = <T = any>(
/** /**
* Convenience method for POST requests * Convenience method for POST requests
*/ */
export const post = <T = any>( export const post = <T = unknown>(
url: string, url: string,
data: any, data: unknown,
options?: Omit<RequestOptions, 'method' | 'body'> options?: Omit<RequestOptions, 'method' | 'body'>
): Promise<Response<T>> => { ): Promise<Response<T>> => {
const body = typeof data === 'string' ? data : JSON.stringify(data) const body = typeof data === 'string' ? data : JSON.stringify(data)
@ -216,9 +216,9 @@ export const post = <T = any>(
/** /**
* Convenience method for PUT requests * Convenience method for PUT requests
*/ */
export const put = <T = any>( export const put = <T = unknown>(
url: string, url: string,
data: any, data: unknown,
options?: Omit<RequestOptions, 'method' | 'body'> options?: Omit<RequestOptions, 'method' | 'body'>
): Promise<Response<T>> => { ): Promise<Response<T>> => {
const body = typeof data === 'string' ? data : JSON.stringify(data) const body = typeof data === 'string' ? data : JSON.stringify(data)
@ -232,7 +232,7 @@ export const put = <T = any>(
/** /**
* Convenience method for DELETE requests * Convenience method for DELETE requests
*/ */
export const del = <T = any>( export const del = <T = unknown>(
url: string, url: string,
options?: Omit<RequestOptions, 'method' | 'body'> options?: Omit<RequestOptions, 'method' | 'body'>
): Promise<Response<T>> => request<T>(url, { ...options, method: 'DELETE' }) ): Promise<Response<T>> => request<T>(url, { ...options, method: 'DELETE' })
@ -240,9 +240,9 @@ export const del = <T = any>(
/** /**
* Convenience method for PATCH requests * Convenience method for PATCH requests
*/ */
export const patch = <T = any>( export const patch = <T = unknown>(
url: string, url: string,
data: any, data: unknown,
options?: Omit<RequestOptions, 'method' | 'body'> options?: Omit<RequestOptions, 'method' | 'body'>
): Promise<Response<T>> => { ): Promise<Response<T>> => {
const body = typeof data === 'string' ? data : JSON.stringify(data) const body = typeof data === 'string' ? data : JSON.stringify(data)

View File

@ -141,7 +141,7 @@ export async function installMihomoCore(version: string): Promise<void> {
console.log(`[GitHub] Installing mihomo core version ${version}`) console.log(`[GitHub] Installing mihomo core version ${version}`)
const plat = platform() const plat = platform()
let arch = process.arch const arch = process.arch
// 映射平台和架构到 GitHub Release 文件名 // 映射平台和架构到 GitHub Release 文件名
const key = `${plat}-${arch}` const key = `${plat}-${arch}`

View File

@ -14,13 +14,13 @@ class Logger {
return new Date().toISOString() return new Date().toISOString()
} }
private formatLogMessage(level: LogLevel, message: string, error?: any): string { private formatLogMessage(level: LogLevel, message: string, error?: unknown): string {
const timestamp = this.formatTimestamp() const timestamp = this.formatTimestamp()
const errorStr = error ? `: ${error}` : '' const errorStr = error ? `: ${String(error)}` : ''
return `[${timestamp}] [${level.toUpperCase()}] [${this.moduleName}] ${message}${errorStr}\n` return `[${timestamp}] [${level.toUpperCase()}] [${this.moduleName}] ${message}${errorStr}\n`
} }
private async writeToFile(level: LogLevel, message: string, error?: any): Promise<void> { private async writeToFile(level: LogLevel, message: string, error?: unknown): Promise<void> {
try { try {
const appLogPath = logPath() const appLogPath = logPath()
const logMessage = this.formatLogMessage(level, message, error) const logMessage = this.formatLogMessage(level, message, error)
@ -35,7 +35,7 @@ class Logger {
} }
} }
private logToConsole(level: LogLevel, message: string, error?: any): void { private logToConsole(level: LogLevel, message: string, error?: unknown): void {
const prefix = `[${this.moduleName}] ${message}` const prefix = `[${this.moduleName}] ${message}`
switch (level) { switch (level) {
@ -54,28 +54,28 @@ class Logger {
} }
} }
async debug(message: string, error?: any): Promise<void> { async debug(message: string, error?: unknown): Promise<void> {
await this.writeToFile('debug', message, error) await this.writeToFile('debug', message, error)
this.logToConsole('debug', message, error) this.logToConsole('debug', message, error)
} }
async info(message: string, error?: any): Promise<void> { async info(message: string, error?: unknown): Promise<void> {
await this.writeToFile('info', message, error) await this.writeToFile('info', message, error)
this.logToConsole('info', message, error) this.logToConsole('info', message, error)
} }
async warn(message: string, error?: any): Promise<void> { async warn(message: string, error?: unknown): Promise<void> {
await this.writeToFile('warn', message, error) await this.writeToFile('warn', message, error)
this.logToConsole('warn', message, error) this.logToConsole('warn', message, error)
} }
async error(message: string, error?: any): Promise<void> { async error(message: string, error?: unknown): Promise<void> {
await this.writeToFile('error', message, error) await this.writeToFile('error', message, error)
this.logToConsole('error', message, error) this.logToConsole('error', message, error)
} }
// 兼容原有的 logFloatingWindow 函数签名 // 兼容原有的 logFloatingWindow 函数签名
async log(message: string, error?: any): Promise<void> { async log(message: string, error?: unknown): Promise<void> {
if (error) { if (error) {
await this.error(message, error) await this.error(message, error)
} else { } else {

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { GenIcon } from 'react-icons' import { GenIcon, IconBaseProps } from 'react-icons'
function MihomoIcon(props: any): React.JSX.Element { function MihomoIcon(props: IconBaseProps): React.JSX.Element {
return GenIcon({ return GenIcon({
tag: 'svg', tag: 'svg',
attr: { viewBox: '0 0 58 61.53' }, attr: { viewBox: '0 0 58 61.53' },

View File

@ -332,7 +332,7 @@ const ConnectionTable: React.FC<Props> = ({
const handleSort = useCallback( const handleSort = useCallback(
(columnKey: string) => { (columnKey: string) => {
let newDirection: 'asc' | 'desc' = 'asc' let newDirection: 'asc' | 'desc' = 'asc'
let newColumn = columnKey const newColumn = columnKey
if (sortColumn === columnKey) { if (sortColumn === columnKey) {
newDirection = sortDirection === 'asc' ? 'desc' : 'asc' newDirection = sortDirection === 'asc' ? 'desc' : 'asc'

View File

@ -140,7 +140,7 @@ const EditInfoModal: React.FC<Props> = (props) => {
value={values.interval?.toString() ?? ''} value={values.interval?.toString() ?? ''}
onValueChange={(v) => { onValueChange={(v) => {
// 输入限制 // 输入限制
if (/^[\d\s*\-,\/]*$/.test(v)) { if (/^[\d\s*\-,/]*$/.test(v)) {
// 纯数字 // 纯数字
if (/^\d+$/.test(v)) { if (/^\d+$/.test(v)) {
setValues({ ...values, interval: parseInt(v, 10) || 0 }) setValues({ ...values, interval: parseInt(v, 10) || 0 })

View File

@ -411,8 +411,7 @@ interface RuleListItemProps {
onRemove: (index: number) => void onRemove: (index: number) => void
} }
const RuleListItem = memo<RuleListItemProps>( const RuleListItemBase: React.FC<RuleListItemProps> = ({
({
rule, rule,
originalIndex, originalIndex,
isDeleted, isDeleted,
@ -496,8 +495,9 @@ const RuleListItem = memo<RuleListItemProps>(
</div> </div>
</div> </div>
) )
}, }
(prevProps, nextProps) => {
const RuleListItem = memo(RuleListItemBase, (prevProps, nextProps) => {
return ( return (
prevProps.rule === nextProps.rule && prevProps.rule === nextProps.rule &&
prevProps.originalIndex === nextProps.originalIndex && prevProps.originalIndex === nextProps.originalIndex &&
@ -505,8 +505,9 @@ const RuleListItem = memo<RuleListItemProps>(
prevProps.isPrependOrAppend === nextProps.isPrependOrAppend && prevProps.isPrependOrAppend === nextProps.isPrependOrAppend &&
prevProps.rulesLength === nextProps.rulesLength prevProps.rulesLength === nextProps.rulesLength
) )
} })
)
RuleListItem.displayName = 'RuleListItem'
const EditRulesModal: React.FC<Props> = (props) => { const EditRulesModal: React.FC<Props> = (props) => {
const { id, onClose } = props const { id, onClose } = props
@ -563,7 +564,7 @@ const EditRulesModal: React.FC<Props> = (props) => {
const content = await getProfileStr(id) const content = await getProfileStr(id)
setProfileContent(content) setProfileContent(content)
const parsed = yaml.load(content) as any const parsed = yaml.load(content) as Record<string, unknown> | undefined
let initialRules: RuleItem[] = [] let initialRules: RuleItem[] = []
if (parsed && parsed.rules && Array.isArray(parsed.rules)) { if (parsed && parsed.rules && Array.isArray(parsed.rules)) {
@ -593,11 +594,19 @@ const EditRulesModal: React.FC<Props> = (props) => {
// 添加代理组和代理名称 // 添加代理组和代理名称
if (Array.isArray(parsed['proxy-groups'])) { if (Array.isArray(parsed['proxy-groups'])) {
groups.push(...parsed['proxy-groups'].map((group: any) => group?.name).filter(Boolean)) groups.push(
...((parsed['proxy-groups'] as Array<Record<string, unknown>>)
.map((group) => (group && typeof group['name'] === 'string' ? (group['name'] as string) : ''))
.filter(Boolean) as string[])
)
} }
if (Array.isArray(parsed['proxies'])) { if (Array.isArray(parsed['proxies'])) {
groups.push(...parsed['proxies'].map((proxy: any) => proxy?.name).filter(Boolean)) groups.push(
...((parsed['proxies'] as Array<Record<string, unknown>>)
.map((proxy) => (proxy && typeof proxy['name'] === 'string' ? (proxy['name'] as string) : ''))
.filter(Boolean) as string[])
)
} }
// 预置出站 https://wiki.metacubex.one/config/proxies/built-in/ // 预置出站 https://wiki.metacubex.one/config/proxies/built-in/
@ -710,7 +719,7 @@ const EditRulesModal: React.FC<Props> = (props) => {
} }
} catch (ruleError) { } catch (ruleError) {
// 规则文件读取失败 // 规则文件读取失败
console.debug('规则文件读取失败:', ruleError) console.debug('规则文件读取失败', ruleError)
setRules(initialRules) setRules(initialRules)
// 清空规则标记 // 清空规则标记
setPrependRules(new Set()) setPrependRules(new Set())

View File

@ -22,8 +22,7 @@ function delayColor(delay: number): 'primary' | 'success' | 'warning' | 'danger'
return 'warning' return 'warning'
} }
const ProxyItem: React.FC<Props> = React.memo( const ProxyItemBase: React.FC<Props> = (props) => {
(props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { const {
mutateProxies, mutateProxies,
@ -61,10 +60,7 @@ const ProxyItem: React.FC<Props> = React.memo(
}) })
}, [proxy.name, group.testUrl, onProxyDelay, mutateProxies]) }, [proxy.name, group.testUrl, onProxyDelay, mutateProxies])
const fixed = useMemo( const fixed = useMemo(() => group.fixed && group.fixed === proxy.name, [group.fixed, proxy.name])
() => group.fixed && group.fixed === proxy.name,
[group.fixed, proxy.name]
)
return ( return (
<Card <Card
@ -177,8 +173,9 @@ const ProxyItem: React.FC<Props> = React.memo(
</CardBody> </CardBody>
</Card> </Card>
) )
}, }
(prevProps, nextProps) => {
const ProxyItem = React.memo(ProxyItemBase, (prevProps, nextProps) => {
// 必要时重新渲染 // 必要时重新渲染
return ( return (
prevProps.proxy.name === nextProps.proxy.name && prevProps.proxy.name === nextProps.proxy.name &&
@ -188,8 +185,7 @@ const ProxyItem: React.FC<Props> = React.memo(
prevProps.group.fixed === nextProps.group.fixed && prevProps.group.fixed === nextProps.group.fixed &&
prevProps.isGroupTesting === nextProps.isGroupTesting prevProps.isGroupTesting === nextProps.isGroupTesting
) )
} })
)
ProxyItem.displayName = 'ProxyItem' ProxyItem.displayName = 'ProxyItem'

View File

@ -208,7 +208,7 @@ const GeneralConfig: React.FC = () => {
type="number" type="number"
value={autoQuitWithoutCoreDelay.toString()} value={autoQuitWithoutCoreDelay.toString()}
onValueChange={async (v: string) => { onValueChange={async (v: string) => {
let num = parseInt(v) const num = parseInt(v)
await patchAppConfig({ autoQuitWithoutCoreDelay: num }) await patchAppConfig({ autoQuitWithoutCoreDelay: num })
}} }}
onBlur={async (e) => { onBlur={async (e) => {

View File

@ -59,7 +59,7 @@ const MihomoConfig: React.FC = () => {
type="number" type="number"
value={(subscriptionTimeout / 1000)?.toString()} value={(subscriptionTimeout / 1000)?.toString()}
onValueChange={async (v: string) => { onValueChange={async (v: string) => {
let num = parseInt(v) const num = parseInt(v)
await patchAppConfig({ subscriptionTimeout: num * 1000 }) await patchAppConfig({ subscriptionTimeout: num * 1000 })
}} }}
onBlur={async (e) => { onBlur={async (e) => {

View File

@ -1,6 +1,5 @@
import React, { createContext, ReactNode, useContext } from 'react' import React, { createContext, ReactNode, useContext } from 'react'
import { showError } from '@renderer/utils/error-display' import { showError } from '@renderer/utils/error-display'
import { toast } from '@renderer/components/base/toast'
import useSWR from 'swr' import useSWR from 'swr'
import { import {
addProfileItem as add, addProfileItem as add,
@ -104,7 +103,7 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
// 异步执行后台切换,不阻塞 UI // 异步执行后台切换,不阻塞 UI
await pendingTask.current await pendingTask.current
} catch (e) { } catch (e) {
const errorMsg = (e as any)?.message || String(e) const errorMsg = (e as { message?: string })?.message || String(e)
// 处理 IPC 超时错误 // 处理 IPC 超时错误
if (errorMsg.includes('reply was never sent')) { if (errorMsg.includes('reply was never sent')) {
setTimeout(() => mutateProfileConfig(), 1000) setTimeout(() => mutateProfileConfig(), 1000)

View File

@ -363,7 +363,7 @@
"sysproxy.pacEditor.title": "编辑 PAC 脚本", "sysproxy.pacEditor.title": "编辑 PAC 脚本",
"sysproxy.bypass.title": "代理绕过", "sysproxy.bypass.title": "代理绕过",
"sysproxy.bypass.addDefault": "添加默认代理绕过", "sysproxy.bypass.addDefault": "添加默认代理绕过",
"sysproxy.bypass.placeholder": "例: *.baidu.com", "sysproxy.bypass.placeholder": "例*.baidu.com",
"tun.title": "虚拟网卡", "tun.title": "虚拟网卡",
"tun.firewall.title": "重设防火墙", "tun.firewall.title": "重设防火墙",
"tun.firewall.reset": "重设防火墙", "tun.firewall.reset": "重设防火墙",
@ -378,7 +378,7 @@
"tun.autoDetectInterface": "自动选择流量出口接口", "tun.autoDetectInterface": "自动选择流量出口接口",
"tun.dnsHijack": "DNS 劫持", "tun.dnsHijack": "DNS 劫持",
"tun.excludeAddress.title": "排除自定义网段", "tun.excludeAddress.title": "排除自定义网段",
"tun.excludeAddress.placeholder": "例: 172.20.0.0/16", "tun.excludeAddress.placeholder": "例172.20.0.0/16",
"tun.notifications.coreAuthSuccess": "内核授权成功", "tun.notifications.coreAuthSuccess": "内核授权成功",
"tun.notifications.firewallResetSuccess": "防火墙重设成功", "tun.notifications.firewallResetSuccess": "防火墙重设成功",
"tun.permissions.title": "需要管理员权限", "tun.permissions.title": "需要管理员权限",

View File

@ -363,7 +363,7 @@
"sysproxy.pacEditor.title": "編輯 PAC 腳本", "sysproxy.pacEditor.title": "編輯 PAC 腳本",
"sysproxy.bypass.title": "代理繞過", "sysproxy.bypass.title": "代理繞過",
"sysproxy.bypass.addDefault": "添加默認代理繞過", "sysproxy.bypass.addDefault": "添加默認代理繞過",
"sysproxy.bypass.placeholder": "例: *.baidu.com", "sysproxy.bypass.placeholder": "例*.baidu.com",
"tun.title": "虛擬網卡", "tun.title": "虛擬網卡",
"tun.firewall.title": "重設防火牆", "tun.firewall.title": "重設防火牆",
"tun.firewall.reset": "重設防火牆", "tun.firewall.reset": "重設防火牆",
@ -378,7 +378,7 @@
"tun.autoDetectInterface": "自動選擇流量出口接口", "tun.autoDetectInterface": "自動選擇流量出口接口",
"tun.dnsHijack": "DNS 劫持", "tun.dnsHijack": "DNS 劫持",
"tun.excludeAddress.title": "排除自定義網段", "tun.excludeAddress.title": "排除自定義網段",
"tun.excludeAddress.placeholder": "例: 172.20.0.0/16", "tun.excludeAddress.placeholder": "例172.20.0.0/16",
"tun.notifications.coreAuthSuccess": "內核授權成功", "tun.notifications.coreAuthSuccess": "內核授權成功",
"tun.notifications.firewallResetSuccess": "防火牆重設成功", "tun.notifications.firewallResetSuccess": "防火牆重設成功",
"tun.permissions.title": "需要系統管理員權限", "tun.permissions.title": "需要系統管理員權限",

View File

@ -314,7 +314,7 @@ const Mihomo: React.FC = () => {
await restartCore() await restartCore()
} }
const handleConfigChangeWithRestart = async (key: string, value: any) => { const handleConfigChangeWithRestart = async (key: string, value: unknown) => {
try { try {
await patchAppConfig({ [key]: value }) await patchAppConfig({ [key]: value })
await restartCore() await restartCore()
@ -334,8 +334,8 @@ const Mihomo: React.FC = () => {
try { try {
const data = await fetchMihomoTags(forceRefresh) const data = await fetchMihomoTags(forceRefresh)
setTags(Array.isArray(data) ? data : []) setTags(Array.isArray(data) ? data : [])
} catch (error) { } catch (error: unknown) {
console.error('Failed to fetch tags:', error) console.error('Failed to fetch tags:', String(error))
setTags([]) setTags([])
toast.error(t('mihomo.error.fetchTagsFailed')) toast.error(t('mihomo.error.fetchTagsFailed'))
} finally { } finally {

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { getPlatform, getVersion } from './ipc' import { getPlatform, getVersion } from './ipc'
// const originError = console.error // const originError = console.error
// const originWarn = console.warn // const originWarn = console.warn

View File

@ -11,10 +11,10 @@
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"baseUrl": ".", "moduleResolution": "bundler",
"paths": { "paths": {
"@renderer/*": [ "@renderer/*": [
"src/renderer/src/*" "./src/renderer/src/*"
] ]
} }
} }