fix: resolve lint and typecheck issues
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
* - Display main content after successful authentication
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
LayoutDashboard,
|
||||
Download,
|
||||
@@ -77,54 +77,21 @@ function App(): React.JSX.Element {
|
||||
setTimeout(() => setErrorMessage(''), 3000)
|
||||
}
|
||||
|
||||
// Initialize authentication on mount
|
||||
useEffect(() => {
|
||||
logger.info('=== Initializing auth... ===')
|
||||
initializeAuth()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.update.onStatusChanged((status) => {
|
||||
setUpdateStatus(status)
|
||||
if (status.phase === 'available' || status.phase === 'downloaded' || status.phase === 'idle') {
|
||||
void refreshUpdateCatalog()
|
||||
}
|
||||
})
|
||||
|
||||
void refreshUpdateState()
|
||||
|
||||
return unsubscribe
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
void refreshUpdateState()
|
||||
void refreshUpdateCatalog()
|
||||
return
|
||||
}
|
||||
|
||||
setUpdateStatus(null)
|
||||
setUpdateCatalog(null)
|
||||
setShowUpdateDialog(false)
|
||||
}, [isAuthenticated])
|
||||
|
||||
const refreshUpdateState = async () => {
|
||||
const refreshUpdateState = useCallback(async () => {
|
||||
const result = await window.electron.update.getStatus()
|
||||
if (result.success && result.data) {
|
||||
setUpdateStatus(result.data)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const refreshUpdateCatalog = async () => {
|
||||
const refreshUpdateCatalog = useCallback(async () => {
|
||||
const result = await window.electron.update.getCatalog()
|
||||
if (result.success && result.data) {
|
||||
setUpdateCatalog(result.data)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const initializeAuth = async () => {
|
||||
const initializeAuth = useCallback(async () => {
|
||||
logger.info('=== Starting initializeAuth ===')
|
||||
try {
|
||||
// Get computer name
|
||||
@@ -174,7 +141,42 @@ function App(): React.JSX.Element {
|
||||
setIsAuthenticating(false)
|
||||
}
|
||||
logger.info('=== Auth initialization complete ===')
|
||||
}
|
||||
}, [logger])
|
||||
|
||||
// Initialize authentication on mount
|
||||
useEffect(() => {
|
||||
logger.info('=== Initializing auth... ===')
|
||||
void initializeAuth()
|
||||
}, [initializeAuth, logger])
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.update.onStatusChanged((status) => {
|
||||
setUpdateStatus(status)
|
||||
if (
|
||||
status.phase === 'available' ||
|
||||
status.phase === 'downloaded' ||
|
||||
status.phase === 'idle'
|
||||
) {
|
||||
void refreshUpdateCatalog()
|
||||
}
|
||||
})
|
||||
|
||||
void refreshUpdateState()
|
||||
|
||||
return unsubscribe
|
||||
}, [refreshUpdateCatalog, refreshUpdateState])
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
void refreshUpdateState()
|
||||
void refreshUpdateCatalog()
|
||||
return
|
||||
}
|
||||
|
||||
setUpdateStatus(null)
|
||||
setUpdateCatalog(null)
|
||||
setShowUpdateDialog(false)
|
||||
}, [isAuthenticated, refreshUpdateCatalog, refreshUpdateState])
|
||||
|
||||
// Handle login dialog submit
|
||||
const handleLogin = async (username: string, password: string): Promise<boolean> => {
|
||||
@@ -266,7 +268,9 @@ function App(): React.JSX.Element {
|
||||
}
|
||||
|
||||
if (updateStatus?.phase !== 'downloaded') {
|
||||
const downloadResult = await window.electron.update.downloadRelease(updateCatalog.recommendedRelease)
|
||||
const downloadResult = await window.electron.update.downloadRelease(
|
||||
updateCatalog.recommendedRelease
|
||||
)
|
||||
if (!downloadResult.success) {
|
||||
showError(downloadResult.error || '下载更新失败')
|
||||
return
|
||||
@@ -554,9 +558,7 @@ function App(): React.JSX.Element {
|
||||
catalog={updateCatalog}
|
||||
onClose={() => setShowUpdateDialog(false)}
|
||||
onInstallUserRelease={handleInstallUserRelease}
|
||||
onDownloadAndInstallAdminRelease={async (release) =>
|
||||
handleAdminDownloadAndInstall(release)
|
||||
}
|
||||
onDownloadAndInstallAdminRelease={async (release) => handleAdminDownloadAndInstall(release)}
|
||||
onRefreshCatalog={async () => {
|
||||
await window.electron.update.checkNow()
|
||||
await refreshUpdateCatalog()
|
||||
|
||||
@@ -10,8 +10,8 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { Plus, Trash2, Save, RotateCcw, Users } from 'lucide-react'
|
||||
import { Modal } from './ui/Modal'
|
||||
import { showSuccess, showError, showInfo } from '../stores/useAppStore'
|
||||
import { useConfirmDialog } from './ui/ConfirmDialog'
|
||||
import { ConfirmDialog } from './ui/ConfirmDialog'
|
||||
import { useConfirmDialog } from './ui/useConfirmDialog'
|
||||
|
||||
interface MaterialTypeRecord {
|
||||
id?: number
|
||||
@@ -51,6 +51,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
|
||||
const tableRef = useRef<HTMLTableElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const selectRef = useRef<HTMLSelectElement>(null)
|
||||
|
||||
// Confirmation dialog hook
|
||||
const { confirm, dialog: confirmDialog } = useConfirmDialog()
|
||||
@@ -60,22 +61,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
(r) => r.state === 'new' || r.state === 'modified' || r.state === 'deleted'
|
||||
).length
|
||||
|
||||
// Load data when dialog opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
loadData()
|
||||
}
|
||||
}, [isOpen, isAdmin, currentUsername])
|
||||
|
||||
// Focus input when editing starts
|
||||
useEffect(() => {
|
||||
if (editingCell && inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
inputRef.current.select()
|
||||
}
|
||||
}, [editingCell])
|
||||
|
||||
const loadData = async () => {
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
// Load managers list
|
||||
@@ -114,7 +100,25 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
}, [currentUsername, isAdmin])
|
||||
|
||||
// Load data when dialog opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
void loadData()
|
||||
}
|
||||
}, [isOpen, loadData])
|
||||
|
||||
// Focus input when editing starts
|
||||
useEffect(() => {
|
||||
const activeElement = inputRef.current ?? selectRef.current
|
||||
if (editingCell && activeElement) {
|
||||
activeElement.focus()
|
||||
if (activeElement instanceof HTMLInputElement) {
|
||||
activeElement.select()
|
||||
}
|
||||
}
|
||||
}, [editingCell])
|
||||
|
||||
// Filter rows by selected managers (admin only)
|
||||
const filteredRows = React.useMemo(() => {
|
||||
@@ -296,7 +300,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
variant: 'warning'
|
||||
})
|
||||
if (confirmed) {
|
||||
loadData()
|
||||
void loadData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,7 +447,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
{filteredRows.filter((r) => r.state !== 'deleted').length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={2} className="px-4 py-8 text-center text-slate-400">
|
||||
暂无数据,点击"新增"按钮添加物料类型关键词
|
||||
暂无数据,点击"新增"按钮添加物料类型关键词
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
@@ -495,7 +499,7 @@ export const MaterialTypeManagementDialog: React.FC<MaterialTypeManagementDialog
|
||||
{isEditingManager ? (
|
||||
isAdmin ? (
|
||||
<select
|
||||
ref={inputRef as any}
|
||||
ref={selectRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onBlur={saveEdit}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { X, FileText, Loader2, ChevronDown } from 'lucide-react'
|
||||
import { Combobox, Transition } from '@headlessui/react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
@@ -38,19 +38,7 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
loadReports()
|
||||
} else {
|
||||
// Reset state when closed
|
||||
setReports([])
|
||||
setSelectedReport(null)
|
||||
setReportContent('')
|
||||
setError(null)
|
||||
}
|
||||
}, [isOpen, isAdmin, currentUsername])
|
||||
|
||||
const loadReports = async () => {
|
||||
const loadReports = useCallback(async () => {
|
||||
setIsLoadingList(true)
|
||||
setError(null)
|
||||
try {
|
||||
@@ -66,12 +54,24 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
} else {
|
||||
setError(result.error || '无法获取报告列表')
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setError('获取报告列表时发生错误')
|
||||
} finally {
|
||||
setIsLoadingList(false)
|
||||
}
|
||||
}
|
||||
}, [currentUsername, isAdmin])
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
void loadReports()
|
||||
} else {
|
||||
// Reset state when closed
|
||||
setReports([])
|
||||
setSelectedReport(null)
|
||||
setReportContent('')
|
||||
setError(null)
|
||||
}
|
||||
}, [isOpen, loadReports])
|
||||
|
||||
const handleReportChange = async (report: ReportMetadata | null) => {
|
||||
setSelectedReport(report)
|
||||
@@ -91,7 +91,7 @@ export const ReportViewerDialog: React.FC<ReportViewerDialogProps> = ({
|
||||
setError(result.error || '无法获取报告内容')
|
||||
setReportContent('')
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setError('获取报告内容时发生错误')
|
||||
setReportContent('')
|
||||
} finally {
|
||||
|
||||
@@ -86,7 +86,9 @@ export default function UpdateDialog({
|
||||
}, [isOpen, selectedRelease])
|
||||
|
||||
const isBusy =
|
||||
status?.phase === 'downloading' || status?.phase === 'installing' || status?.phase === 'checking'
|
||||
status?.phase === 'downloading' ||
|
||||
status?.phase === 'installing' ||
|
||||
status?.phase === 'checking'
|
||||
|
||||
const renderReleaseList = (title: string, releases: UpdateRelease[]) => {
|
||||
if (releases.length === 0) {
|
||||
@@ -233,7 +235,9 @@ export default function UpdateDialog({
|
||||
{userType === 'Admin' ? (
|
||||
<button
|
||||
onClick={() =>
|
||||
selectedRelease ? void onDownloadAndInstallAdminRelease(selectedRelease) : undefined
|
||||
selectedRelease
|
||||
? void onDownloadAndInstallAdminRelease(selectedRelease)
|
||||
: undefined
|
||||
}
|
||||
className="inline-flex items-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-blue-300"
|
||||
disabled={!selectedRelease || isBusy}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* - Return selected user info
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import React, { useState, useRef } from 'react'
|
||||
import { Modal } from './ui/Modal'
|
||||
|
||||
export interface UserInfo {
|
||||
@@ -37,13 +37,6 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
const [selectedUserId, setSelectedUserId] = useState<number | null>(null)
|
||||
const dialogRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Reset selection when dialog opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setSelectedUserId(null)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (selectedUserId === null) {
|
||||
return
|
||||
@@ -51,14 +44,21 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
|
||||
const selectedUser = users.find((u) => u.id === selectedUserId)
|
||||
if (selectedUser) {
|
||||
setSelectedUserId(null)
|
||||
onSelectUser(selectedUser)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDoubleClick = (user: UserInfo) => {
|
||||
setSelectedUserId(null)
|
||||
onSelectUser(user)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setSelectedUserId(null)
|
||||
onCancel()
|
||||
}
|
||||
|
||||
const userTypeStyles: Record<string, string> = {
|
||||
Admin: 'bg-amber-50 text-amber-600',
|
||||
User: 'bg-blue-50 text-blue-600',
|
||||
@@ -70,7 +70,7 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onCancel}
|
||||
onClose={handleCancel}
|
||||
title="选择用户"
|
||||
size="md"
|
||||
triggerRef={triggerRef}
|
||||
@@ -129,7 +129,7 @@ export const UserSelectionDialog: React.FC<UserSelectionDialogProps> = ({
|
||||
</button>
|
||||
<button
|
||||
className="px-6 py-2 rounded-md bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium transition-colors"
|
||||
onClick={onCancel}
|
||||
onClick={handleCancel}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Extends the Modal component with consistent styling and behavior.
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback, useEffect } from 'react'
|
||||
import React, { useCallback, useEffect } from 'react'
|
||||
import { AlertTriangle, Info, AlertCircle } from 'lucide-react'
|
||||
import { Modal } from './Modal'
|
||||
import { Button } from './Button'
|
||||
@@ -115,54 +115,4 @@ export function ConfirmDialog({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for using confirmation dialogs
|
||||
* Returns a confirm function that shows a dialog and resolves with user's choice
|
||||
*/
|
||||
export function useConfirmDialog() {
|
||||
const [config, setConfig] = useState<
|
||||
| (Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'> & {
|
||||
resolve: (value: boolean) => void
|
||||
})
|
||||
| null
|
||||
>(null)
|
||||
|
||||
const confirm = useCallback(
|
||||
(options: Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'>): Promise<boolean> => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
setConfig({
|
||||
...options,
|
||||
resolve
|
||||
})
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (config) {
|
||||
config.resolve(true)
|
||||
setConfig(null)
|
||||
}
|
||||
}, [config])
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
if (config) {
|
||||
config.resolve(false)
|
||||
setConfig(null)
|
||||
}
|
||||
}, [config])
|
||||
|
||||
const dialog = config
|
||||
? {
|
||||
...config,
|
||||
isOpen: true,
|
||||
onConfirm: handleConfirm,
|
||||
onCancel: handleCancel
|
||||
}
|
||||
: null
|
||||
|
||||
return { confirm, dialog }
|
||||
}
|
||||
|
||||
export default ConfirmDialog
|
||||
|
||||
60
src/renderer/src/components/ui/useConfirmDialog.ts
Normal file
60
src/renderer/src/components/ui/useConfirmDialog.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import type { ConfirmDialogProps } from './ConfirmDialog'
|
||||
|
||||
type ConfirmDialogConfig = Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'> & {
|
||||
resolve: (value: boolean) => void
|
||||
}
|
||||
|
||||
export function useConfirmDialog(): {
|
||||
confirm: (
|
||||
options: Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'>
|
||||
) => Promise<boolean>
|
||||
dialog:
|
||||
| (Omit<ConfirmDialogProps, 'isOpen'> & {
|
||||
isOpen: true
|
||||
})
|
||||
| null
|
||||
} {
|
||||
const [config, setConfig] = useState<ConfirmDialogConfig | null>(null)
|
||||
|
||||
const confirm = useCallback(
|
||||
(options: Omit<ConfirmDialogProps, 'isOpen' | 'onConfirm' | 'onCancel'>): Promise<boolean> => {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
setConfig({
|
||||
...options,
|
||||
resolve
|
||||
})
|
||||
})
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (!config) {
|
||||
return
|
||||
}
|
||||
|
||||
config.resolve(true)
|
||||
setConfig(null)
|
||||
}, [config])
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
if (!config) {
|
||||
return
|
||||
}
|
||||
|
||||
config.resolve(false)
|
||||
setConfig(null)
|
||||
}, [config])
|
||||
|
||||
const dialog = config
|
||||
? {
|
||||
...config,
|
||||
isOpen: true as const,
|
||||
onConfirm: handleConfirm,
|
||||
onCancel: handleCancel
|
||||
}
|
||||
: null
|
||||
|
||||
return { confirm, dialog }
|
||||
}
|
||||
@@ -7,6 +7,21 @@ import type {
|
||||
} from './types'
|
||||
import type { CleanerExportItem, MaterialBatchChange } from './helpers'
|
||||
|
||||
interface CleanerDataPayload {
|
||||
success?: boolean
|
||||
orderNumbers?: string[]
|
||||
materialCodes?: string[]
|
||||
}
|
||||
|
||||
interface CleanerRunPayload {
|
||||
ordersProcessed: number
|
||||
materialsDeleted: number
|
||||
materialsSkipped: number
|
||||
errors: string[]
|
||||
retriedOrders: number
|
||||
successfulRetries: number
|
||||
}
|
||||
|
||||
export async function initializeCleanerPage(): Promise<CleanerInitializationResult> {
|
||||
const adminResult = await window.electron.auth.isAdmin()
|
||||
const userResult = await window.electron.auth.getCurrentUser()
|
||||
@@ -105,7 +120,9 @@ export async function runCleanerExecution(params: {
|
||||
processConcurrency: number
|
||||
}): Promise<CleanerReportData> {
|
||||
const cleanerDataResult = await window.electron.validation.getCleanerData()
|
||||
const cleanerData = cleanerDataResult.success ? (cleanerDataResult.data as any) : null
|
||||
const cleanerData = cleanerDataResult.success
|
||||
? (cleanerDataResult.data as CleanerDataPayload | null)
|
||||
: null
|
||||
|
||||
if (!cleanerDataResult.success || cleanerData?.success === false) {
|
||||
throw new Error(cleanerDataResult.error || '获取清理数据失败')
|
||||
@@ -130,7 +147,7 @@ export async function runCleanerExecution(params: {
|
||||
processConcurrency: params.processConcurrency
|
||||
})
|
||||
|
||||
const cleanerRunData = response.success ? (response.data as any) : null
|
||||
const cleanerRunData = response.success ? (response.data as CleanerRunPayload | null) : null
|
||||
if (!response.success || !cleanerRunData) {
|
||||
throw new Error(response.error || '清理失败')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { useEffect } from 'react'
|
||||
import type { LogLevel } from '../stores/extractorStore'
|
||||
import { useExtractorStore } from '../stores/extractorStore'
|
||||
|
||||
function isLogLevel(value: string): value is LogLevel {
|
||||
return ['info', 'success', 'warning', 'error', 'system'].includes(value)
|
||||
}
|
||||
|
||||
export function useExtractor() {
|
||||
const {
|
||||
isRunning,
|
||||
@@ -30,7 +35,7 @@ export function useExtractor() {
|
||||
})
|
||||
|
||||
const unsubscribeLog = window.electron.extractor.onLog((data) => {
|
||||
addLog(data.level as any, data.message)
|
||||
addLog(isLogLevel(data.level) ? data.level : 'info', data.message)
|
||||
})
|
||||
|
||||
return () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef, useCallback } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import type { LogLevel } from '../../../shared/ipc-channels'
|
||||
|
||||
/**
|
||||
@@ -50,26 +50,24 @@ export interface RendererLogger {
|
||||
* ```
|
||||
*/
|
||||
export function useLogger(context: string): RendererLogger {
|
||||
// Use ref to store context to avoid recreating logger on re-renders
|
||||
const contextRef = useRef(context)
|
||||
contextRef.current = context
|
||||
|
||||
// Create stable logger instance using useCallback
|
||||
const logger = useCallback((level: LogLevel, message: string, meta?: Record<string, unknown>) => {
|
||||
// Check if window.electron is available (safety check)
|
||||
if (typeof window !== 'undefined' && window.electron?.logger?.log) {
|
||||
window.electron.logger.log(level, message, {
|
||||
...meta,
|
||||
context: contextRef.current
|
||||
})
|
||||
} else {
|
||||
// Fallback to console in development if IPC not available
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const consoleMethod = console[level] || console.log
|
||||
consoleMethod(`[${contextRef.current}] ${message}`, meta || '')
|
||||
const logger = useCallback(
|
||||
(level: LogLevel, message: string, meta?: Record<string, unknown>) => {
|
||||
// Check if window.electron is available (safety check)
|
||||
if (typeof window !== 'undefined' && window.electron?.logger?.log) {
|
||||
window.electron.logger.log(level, message, {
|
||||
...meta,
|
||||
context
|
||||
})
|
||||
} else {
|
||||
// Fallback to console in development if IPC not available
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const consoleMethod = console[level] || console.log
|
||||
consoleMethod(`[${context}] ${message}`, meta || '')
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
},
|
||||
[context]
|
||||
)
|
||||
|
||||
// Return memoized logger methods
|
||||
return {
|
||||
|
||||
@@ -38,7 +38,7 @@ const SettingsPage: React.FC = () => {
|
||||
showError(response.error || '加载 ERP 配置失败')
|
||||
}
|
||||
setIsModified(false)
|
||||
} catch (error) {
|
||||
} catch {
|
||||
showError('加载 ERP 配置失败')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
@@ -64,7 +64,7 @@ const SettingsPage: React.FC = () => {
|
||||
} else {
|
||||
showError(result.error || saveData?.error || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
showError('保存配置时发生错误')
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user