style: eslint config
This commit is contained in:
parent
b71a55d83f
commit
afa88d0396
|
@ -0,0 +1,10 @@
|
|||
# .eslintignore
|
||||
*.svg
|
||||
*.css
|
||||
*.less
|
||||
/dist
|
||||
*.scss
|
||||
*.css
|
||||
/node_modules
|
||||
/build
|
||||
vite.config.ts
|
|
@ -0,0 +1,74 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
es6: true,
|
||||
node: true,
|
||||
},
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:react/jsx-runtime',
|
||||
'plugin:react-hooks/recommended',
|
||||
'airbnb',
|
||||
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: [
|
||||
'react',
|
||||
'react-hooks',
|
||||
'@typescript-eslint',
|
||||
'eslint-plugin-import',
|
||||
],
|
||||
settings: {
|
||||
'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {},
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
moduleDirectory: ['node_modules', 'src/'],
|
||||
},
|
||||
alias: {
|
||||
map: [['@', './src']],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
rules: {
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/jsx-filename-extension': [1, { extensions: ['.ts', '.tsx'] }],
|
||||
'react-hooks/rules-of-hooks': 'error', // 检查 Hook 的规则
|
||||
'react-hooks/exhaustive-deps': 'warn', // 检查 effect 的依赖
|
||||
'no-console': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'no-shadow': 'off',
|
||||
'@typescript-eslint/no-shadow': ['error'],
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
{
|
||||
js: 'never',
|
||||
jsx: 'never',
|
||||
ts: 'never',
|
||||
tsx: 'never',
|
||||
},
|
||||
],
|
||||
|
||||
},
|
||||
};
|
|
@ -1 +1 @@
|
|||
module.exports = {extends: ['@commitlint/config-angular']}
|
||||
module.exports = { extends: ['@commitlint/config-angular'] };
|
||||
|
|
|
@ -1,96 +1,101 @@
|
|||
import { app, BrowserWindow, shell, ipcMain } from 'electron'
|
||||
import { release } from 'os'
|
||||
import { join } from 'path'
|
||||
/* eslint-disable import/prefer-default-export */
|
||||
import {
|
||||
app, BrowserWindow, shell, ipcMain,
|
||||
} from 'electron';
|
||||
import { release } from 'os';
|
||||
import { join } from 'path';
|
||||
import installExtension, {
|
||||
REACT_DEVELOPER_TOOLS,REDUX_DEVTOOLS
|
||||
REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS,
|
||||
} from 'electron-devtools-installer';
|
||||
// Disable GPU Acceleration for Windows 7
|
||||
if (release().startsWith('6.1')) app.disableHardwareAcceleration()
|
||||
if (release().startsWith('6.1')) app.disableHardwareAcceleration();
|
||||
|
||||
// Set application name for Windows 10+ notifications
|
||||
if (process.platform === 'win32') app.setAppUserModelId(app.getName())
|
||||
if (process.platform === 'win32') app.setAppUserModelId(app.getName());
|
||||
|
||||
if (!app.requestSingleInstanceLock()) {
|
||||
app.quit()
|
||||
process.exit(0)
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
||||
|
||||
export const ROOT_PATH = {
|
||||
// /dist
|
||||
dist: join(__dirname, '../..'),
|
||||
// /dist or /public
|
||||
public: join(__dirname, app.isPackaged ? '../..' : '../../../public'),
|
||||
}
|
||||
};
|
||||
|
||||
let win: BrowserWindow | null = null
|
||||
let win: BrowserWindow | null = null;
|
||||
// Here, you can also use other preload
|
||||
const preload = join(__dirname, '../preload/index.js')
|
||||
const preload = join(__dirname, '../preload/index.js');
|
||||
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin
|
||||
const url = `http://${process.env['VITE_DEV_SERVER_HOST']}:${process.env['VITE_DEV_SERVER_PORT']}`
|
||||
const indexHtml = join(ROOT_PATH.dist, 'index.html')
|
||||
// eslint-disable-next-line dot-notation
|
||||
const url = `http://${process.env['VITE_DEV_SERVER_HOST']}:${process.env['VITE_DEV_SERVER_PORT']}`;
|
||||
const indexHtml = join(ROOT_PATH.dist, 'index.html');
|
||||
|
||||
async function createWindow() {
|
||||
win = new BrowserWindow({
|
||||
title: 'Main window',
|
||||
icon: join(ROOT_PATH.public, 'favicon.svg'),frame:false,
|
||||
icon: join(ROOT_PATH.public, 'favicon.svg'),
|
||||
frame: false,
|
||||
webPreferences: {
|
||||
preload,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
if (app.isPackaged) {
|
||||
win.loadFile(indexHtml)
|
||||
win.loadFile(indexHtml);
|
||||
} else {
|
||||
win.loadURL(url)
|
||||
win.loadURL(url);
|
||||
// win.webContents.openDevTools()
|
||||
}
|
||||
|
||||
// Test actively push message to the Electron-Renderer
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
win?.webContents.send('main-process-message', new Date().toLocaleString())
|
||||
})
|
||||
win?.webContents.send('main-process-message', new Date().toLocaleString());
|
||||
});
|
||||
|
||||
// Make all links open with the browser, not with the application
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (url.startsWith('https:')) shell.openExternal(url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
win.webContents.setWindowOpenHandler((event) => {
|
||||
if (event.url.startsWith('https:')) shell.openExternal(event.url);
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
// open devtools
|
||||
win.webContents.openDevTools()
|
||||
win.webContents.openDevTools();
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow).then(()=>{
|
||||
app.whenReady().then(createWindow).then(() => {
|
||||
installExtension(REACT_DEVELOPER_TOOLS.id)
|
||||
.then((name) => console.log(`Added Extension: ${name}`))
|
||||
.catch((err) => console.log('An error occurred: ', err));
|
||||
})
|
||||
.then((name) => console.log(`Added Extension: ${name}`))
|
||||
.catch((err) => console.log('An error occurred: ', err));
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
win = null
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
win = null;
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
|
||||
app.on('second-instance', () => {
|
||||
if (win) {
|
||||
// Focus on the main window if the user tried to open another
|
||||
if (win.isMinimized()) win.restore()
|
||||
win.focus()
|
||||
if (win.isMinimized()) win.restore();
|
||||
win.focus();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
const allWindows = BrowserWindow.getAllWindows()
|
||||
const allWindows = BrowserWindow.getAllWindows();
|
||||
if (allWindows.length) {
|
||||
allWindows[0].focus()
|
||||
allWindows[0].focus();
|
||||
} else {
|
||||
createWindow()
|
||||
createWindow();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// new window example arg: new windows url
|
||||
ipcMain.handle('open-win', (event, arg) => {
|
||||
|
@ -98,24 +103,24 @@ ipcMain.handle('open-win', (event, arg) => {
|
|||
webPreferences: {
|
||||
preload,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
if (app.isPackaged) {
|
||||
childWindow.loadFile(indexHtml, { hash: arg })
|
||||
childWindow.loadFile(indexHtml, { hash: arg });
|
||||
} else {
|
||||
childWindow.loadURL(`${url}/#${arg}`)
|
||||
childWindow.loadURL(`${url}/#${arg}`);
|
||||
// childWindow.webContents.openDevTools({ mode: "undocked", activate: true })
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ipcMain.handle('close-win', (event) => {
|
||||
app.exit()
|
||||
})
|
||||
app.exit();
|
||||
});
|
||||
|
||||
ipcMain.handle('minimize-win', (event) => {
|
||||
win.minimize()
|
||||
})
|
||||
win.minimize();
|
||||
});
|
||||
|
||||
ipcMain.handle('maximize-win', (event) => {
|
||||
win.maximize()
|
||||
})
|
||||
win.maximize();
|
||||
});
|
||||
|
|
|
@ -1,28 +1,32 @@
|
|||
/* eslint-disable no-undef */
|
||||
/* eslint-disable no-unused-expressions */
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) {
|
||||
return new Promise(resolve => {
|
||||
return new Promise((resolve) => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true)
|
||||
resolve(true);
|
||||
} else {
|
||||
document.addEventListener('readystatechange', () => {
|
||||
if (condition.includes(document.readyState)) {
|
||||
resolve(true)
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const safeDOM = {
|
||||
append(parent: HTMLElement, child: HTMLElement) {
|
||||
if (!Array.from(parent.children).find(e => e === child)) {
|
||||
return parent.appendChild(child)
|
||||
class safeDOM {
|
||||
static append(parent: HTMLElement, child: HTMLElement) {
|
||||
if (!Array.from(parent.children).find((e) => e === child)) {
|
||||
parent.appendChild(child);
|
||||
}
|
||||
},
|
||||
remove(parent: HTMLElement, child: HTMLElement) {
|
||||
if (Array.from(parent.children).find(e => e === child)) {
|
||||
return parent.removeChild(child)
|
||||
}
|
||||
|
||||
static remove(parent: HTMLElement, child: HTMLElement) {
|
||||
if (Array.from(parent.children).find((e) => e === child)) {
|
||||
parent.removeChild(child);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,7 +36,7 @@ const safeDOM = {
|
|||
* https://matejkustec.github.io/SpinThatShit
|
||||
*/
|
||||
function useLoading() {
|
||||
const className = `loaders-css__square-spin`
|
||||
const className = 'loaders-css__square-spin';
|
||||
const styleContent = `
|
||||
@keyframes square-spin {
|
||||
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); }
|
||||
|
@ -59,34 +63,34 @@ function useLoading() {
|
|||
background: #282c34;
|
||||
z-index: 9;
|
||||
}
|
||||
`
|
||||
const oStyle = document.createElement('style')
|
||||
const oDiv = document.createElement('div')
|
||||
`;
|
||||
const oStyle = document.createElement('style');
|
||||
const oDiv = document.createElement('div');
|
||||
|
||||
oStyle.id = 'app-loading-style'
|
||||
oStyle.innerHTML = styleContent
|
||||
oDiv.className = 'app-loading-wrap'
|
||||
oDiv.innerHTML = `<div class="${className}"><div></div></div>`
|
||||
oStyle.id = 'app-loading-style';
|
||||
oStyle.innerHTML = styleContent;
|
||||
oDiv.className = 'app-loading-wrap';
|
||||
oDiv.innerHTML = `<div class="${className}"><div></div></div>`;
|
||||
|
||||
return {
|
||||
appendLoading() {
|
||||
safeDOM.append(document.head, oStyle)
|
||||
safeDOM.append(document.body, oDiv)
|
||||
safeDOM.append(document.head, oStyle);
|
||||
safeDOM.append(document.body, oDiv);
|
||||
},
|
||||
removeLoading() {
|
||||
safeDOM.remove(document.head, oStyle)
|
||||
safeDOM.remove(document.body, oDiv)
|
||||
safeDOM.remove(document.head, oStyle);
|
||||
safeDOM.remove(document.body, oDiv);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const { appendLoading, removeLoading } = useLoading()
|
||||
domReady().then(appendLoading)
|
||||
const { appendLoading, removeLoading } = useLoading();
|
||||
domReady().then(appendLoading);
|
||||
|
||||
window.onmessage = ev => {
|
||||
ev.data.payload === 'removeLoading' && removeLoading()
|
||||
}
|
||||
window.onmessage = (ev) => {
|
||||
ev.data.payload === 'removeLoading' && removeLoading();
|
||||
};
|
||||
|
||||
setTimeout(removeLoading, 4999)
|
||||
setTimeout(removeLoading, 4999);
|
||||
|
|
34
package.json
34
package.json
|
@ -10,31 +10,29 @@
|
|||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build && electron-builder",
|
||||
"commit": "cz"
|
||||
"commit": "cz",
|
||||
"prepare": "husky install",
|
||||
"postinstall": "pnpm prepare",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx --fix --ignore-path .gitignore"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-scroll": "^2.4.2",
|
||||
"classnames": "^2.3.1",
|
||||
"echarts": "^5.3.3",
|
||||
"electron-store": "^8.0.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
"@commitlint/cli": "^17.0.3",
|
||||
"@commitlint/config-angular": "^17.0.3",
|
||||
"@commitlint/config-conventional": "^17.0.3",
|
||||
"@iconify/json": "^2.1.81",
|
||||
"@koale/useworker": "^4.0.2",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/node": "^18.0.3",
|
||||
"@types/react": "^18.0.12",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/winston": "^2.4.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
||||
"@typescript-eslint/parser": "^5.31.0",
|
||||
"@unocss/preset-attributify": "^0.44.5",
|
||||
"@unocss/preset-icons": "^0.44.5",
|
||||
"@unocss/preset-uno": "^0.44.5",
|
||||
|
@ -42,24 +40,38 @@
|
|||
"@vitejs/plugin-react": "^1.3.2",
|
||||
"ahooks": "^3.5.2",
|
||||
"antd": "^4.21.5",
|
||||
"better-scroll": "^2.4.2",
|
||||
"classnames": "^2.3.1",
|
||||
"commitizen": "^4.2.4",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"d3": "^7.6.1",
|
||||
"dayjs": "^1.11.3",
|
||||
"echarts": "^5.3.3",
|
||||
"electron": "^19.0.3",
|
||||
"electron-builder": "^23.0.3",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-store": "^8.0.1",
|
||||
"eslint": "^7.32.0 || ^8.2.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"eslint-import-resolver-typescript": "^3.4.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"husky": "^8.0.1",
|
||||
"less": "^4.1.3",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-router-dom": "6",
|
||||
"typescript": "^4.7.3",
|
||||
"unocss": "^0.44.0",
|
||||
"unocss": "^0.44.1",
|
||||
"unocss-preset-scrollbar": "^0.2.1",
|
||||
"vite": "^2.9.10",
|
||||
"vite-plugin-electron": "^0.6.2"
|
||||
"vite-plugin-electron": "^0.6.2",
|
||||
"winston": "^3.8.1"
|
||||
},
|
||||
"env": {
|
||||
"VITE_DEV_SERVER_HOST": "127.0.0.1",
|
||||
|
|
62
src/App.tsx
62
src/App.tsx
|
@ -1,25 +1,29 @@
|
|||
import { useState } from 'react'
|
||||
import FileManage from '@/pages/fileStore'
|
||||
/* eslint-disable react/button-has-type */
|
||||
/* eslint-disable jsx-a11y/control-has-associated-label */
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
SettingOutlined,
|
||||
RadarChartOutlined,
|
||||
FileTextOutlined,
|
||||
MinusOutlined,
|
||||
FullscreenOutlined,
|
||||
CloseOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Layout, Menu, Space } from 'antd';
|
||||
import type { MenuProps } from 'antd';
|
||||
import { BrowserRouter, Route, Routes, useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
Route, Routes, useNavigate,
|
||||
} from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import Analysis from '@/pages/analysis';
|
||||
import Settings from '@/pages/settings';
|
||||
import styles from "./App.module.less"
|
||||
import classNames from 'classnames';
|
||||
const { Header, Footer, Sider, Content } = Layout;
|
||||
const { ipcRenderer } = require('electron')
|
||||
import FileManage from '@/pages/fileStore';
|
||||
import styles from './App.module.less';
|
||||
|
||||
const App: React.FC = () => {
|
||||
let navigate = useNavigate();
|
||||
const {
|
||||
Header, Footer, Sider, Content,
|
||||
} = Layout;
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
function App() {
|
||||
const navigate = useNavigate();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
type MenuItem = Required<MenuProps>['items'][number];
|
||||
function getItem(
|
||||
|
@ -45,26 +49,26 @@ const App: React.FC = () => {
|
|||
];
|
||||
|
||||
const closeApp = () => {
|
||||
ipcRenderer.invoke('close-win')
|
||||
}
|
||||
ipcRenderer.invoke('close-win');
|
||||
};
|
||||
const minimizeApp = () => {
|
||||
ipcRenderer.invoke('minimize-win')
|
||||
}
|
||||
ipcRenderer.invoke('minimize-win');
|
||||
};
|
||||
const maximizeApp = () => {
|
||||
ipcRenderer.invoke('maximize-win')
|
||||
}
|
||||
ipcRenderer.invoke('maximize-win');
|
||||
};
|
||||
return (
|
||||
<Layout>
|
||||
<Header style={{ height: 45 }} className={classNames('flex justify-between items-center text-white', styles['ant-layout-header'])}>
|
||||
<div className='text-xl text-black dark:text-white font-bold' >
|
||||
<div className="text-xl text-black dark:text-white font-bold">
|
||||
MIB
|
||||
</div>
|
||||
<div>
|
||||
<Space size='middle' className='text-md'>
|
||||
<button className="opBtn dark:text-white i-carbon-sun dark:i-carbon-moon" />
|
||||
<button className="opBtn dark:text-white i-codicon:chrome-minimize" onClick={() => minimizeApp()} />
|
||||
<button className="opBtn dark:text-white i-fluent:full-screen-maximize-20-filled" onClick={() => maximizeApp()} />
|
||||
<button className="opBtn dark:text-white i-eva:close-fill" onClick={() => closeApp()} />
|
||||
<Space size="middle" className="text-md">
|
||||
<button className="opBtn dark:text-white i-carbon-sun dark:i-carbon-moon" />
|
||||
<button className="opBtn dark:text-white i-codicon:chrome-minimize" onClick={() => minimizeApp()} />
|
||||
<button className="opBtn dark:text-white i-fluent:full-screen-maximize-20-filled" onClick={() => maximizeApp()} />
|
||||
<button className="opBtn dark:text-white i-eva:close-fill" onClick={() => closeApp()} />
|
||||
</Space>
|
||||
</div>
|
||||
</Header>
|
||||
|
@ -79,15 +83,15 @@ const App: React.FC = () => {
|
|||
</div>
|
||||
</Sider>
|
||||
<Content style={{ padding: '12px', minHeight: 'calc( 100vh - 50px )' }}>
|
||||
<Routes >
|
||||
<Route path='/analysis' element={<Analysis />} />
|
||||
<Route path='/fileManage' element={<FileManage />} />
|
||||
<Routes>
|
||||
<Route path="/analysis" element={<Analysis />} />
|
||||
<Route path="/fileManage" element={<FileManage />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
</Routes>
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { DriverType } from "@/types";
|
||||
import React, { forwardRef, ReactNode, Ref } from "react";
|
||||
import styles from './index.module.scss'
|
||||
import React, { forwardRef, ReactNode, Ref } from 'react';
|
||||
import { DriverType } from '@/types';
|
||||
|
||||
export interface ControlOptionType {
|
||||
label: string;
|
||||
|
@ -13,21 +12,24 @@ interface PropType {
|
|||
curType: DriverType,
|
||||
onRow: (e: any) => any
|
||||
}
|
||||
const controlPanel = ({ styles, options, curType, onRow }: PropType, ref: any) => {
|
||||
const controlPanel = ({
|
||||
styles, options, curType, onRow,
|
||||
}: PropType, ref: any) => {
|
||||
// 过滤
|
||||
const list = options.filter(i => i.show.includes(curType))
|
||||
const list = options.filter((i) => i.show.includes(curType));
|
||||
|
||||
return (
|
||||
<div ref={ref} style={styles} className='absolute rounded-md shadow-md overflow-hidden m-2 z-10 min-w-130px wa bg-white'>
|
||||
<div ref={ref} style={styles} className="absolute rounded-md shadow-md overflow-hidden m-2 z-10 min-w-130px wa bg-white">
|
||||
{
|
||||
|
||||
list.length !== 0 ? list.map(({ label, show,key}) => {
|
||||
const events = onRow({label,show,key})
|
||||
return (<div className="transition-colors-500 px-6 py-1 last-pb-2 first-pt-2 hover-bg-#eee cursor-pointer" {...events} key={key}>{label}</div>)
|
||||
list.length !== 0 ? list.map(({ label, show, key }) => {
|
||||
const events = onRow({ label, show, key });
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return (<div className="transition-colors-500 px-6 py-1 last-pb-2 first-pt-2 hover-bg-#eee cursor-pointer" {...events} key={key}>{label}</div>);
|
||||
}) : <div className="transition-colors-500 px-6 py-1 last-pb-2 first-pt-2 hover-bg-#eee cursor-pointer">暂无操作</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default forwardRef<HTMLDivElement, PropType>(controlPanel)
|
||||
);
|
||||
};
|
||||
export default forwardRef<HTMLDivElement, PropType>(controlPanel);
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
const { readJsonSync, pathExistsSync, outputJsonSync } =require("fs-extra") ;
|
||||
import { platform, env } from "process";
|
||||
import path from "path";
|
||||
import { ConfigType } from "@/types";
|
||||
import { platform, env } from 'process';
|
||||
import path from 'path';
|
||||
import { ConfigType } from '@/types';
|
||||
|
||||
export const home = platform === "win32" ? env.USERPROFILE : env.HOME;
|
||||
const { readJsonSync, pathExistsSync, outputJsonSync } = require('fs-extra');
|
||||
|
||||
const CONFIG_PATH: string = path.join(home || "~/", ".mibrc");
|
||||
export const home = platform === 'win32' ? env.USERPROFILE : env.HOME;
|
||||
|
||||
const CONFIG_PATH: string = path.join(home || '~/', '.mibrc');
|
||||
|
||||
const existConf = () => pathExistsSync(CONFIG_PATH);
|
||||
const createDefaultConfig = (): ConfigType => {
|
||||
const conf: ConfigType = {
|
||||
backups: [],
|
||||
output: "C:/",
|
||||
output: 'C:/',
|
||||
};
|
||||
outputJsonSync(CONFIG_PATH, conf);
|
||||
return readJsonSync(CONFIG_PATH);
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
export enum ThemeType {
|
||||
DARK = "dark",
|
||||
LIGHT = "light",
|
||||
DARK = 'dark',
|
||||
LIGHT = 'light',
|
||||
}
|
||||
|
||||
let theme = localStorage.getItem("theme") as ThemeType;
|
||||
const theme = localStorage.getItem('theme') as ThemeType;
|
||||
|
||||
export async function loadTheme(type:ThemeType=theme ?? ThemeType.LIGHT) {
|
||||
export async function loadTheme(type:ThemeType = theme ?? ThemeType.LIGHT) {
|
||||
// 获取主题
|
||||
if (type === ThemeType.LIGHT) {
|
||||
import('./light/index.less');
|
||||
localStorage.setItem('theme',ThemeType.LIGHT)
|
||||
document.documentElement.classList.add(ThemeType.LIGHT)
|
||||
localStorage.setItem('theme', ThemeType.LIGHT);
|
||||
document.documentElement.classList.add(ThemeType.LIGHT);
|
||||
}
|
||||
if (type === ThemeType.DARK) {
|
||||
import('./dark/index.less');
|
||||
localStorage.setItem('theme',ThemeType.DARK)
|
||||
document.documentElement.classList.add(ThemeType.DARK)
|
||||
localStorage.setItem('theme', ThemeType.DARK);
|
||||
document.documentElement.classList.add(ThemeType.DARK);
|
||||
}
|
||||
}
|
||||
|
|
27
src/main.tsx
27
src/main.tsx
|
@ -1,15 +1,14 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
// import './samples/node-api'
|
||||
// import 'normalize.css'
|
||||
import 'antd/dist/antd.less';
|
||||
await loadTheme()
|
||||
import 'styles/index.css'
|
||||
|
||||
import 'uno.css'
|
||||
/* eslint-disable import/no-unresolved */
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
import { loadTheme } from './lib/css/theme'
|
||||
import App from './App';
|
||||
import 'antd/dist/antd.less';
|
||||
import 'styles/index.css';
|
||||
import 'uno.css';
|
||||
import { loadTheme } from './lib/css/theme';
|
||||
|
||||
await loadTheme();
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
|
@ -18,7 +17,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||
<Route path="/*" element={<App />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
)
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
postMessage({ payload: 'removeLoading' }, '*')
|
||||
postMessage({ payload: 'removeLoading' }, '*');
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
import { BackItemType, ConfigType, DriverType, FileNodeType } from "@/types";
|
||||
import { createFileNodeWithADB, diff, execAdb, getFileNodeList, getFileSize, isPath, log, openNotification, pathRepair, speedReg } from "@/utils"
|
||||
import ignoreFileList from "@/utils/ignoreFileList";
|
||||
import { useMount } from "ahooks";
|
||||
import { Button, Card, Modal, Space, Tag } from "antd";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Button, Card, Modal, Space, Tag,
|
||||
} from 'antd';
|
||||
import {
|
||||
useEffect, useRef, useState, Key,
|
||||
} from 'react';
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption
|
||||
} from 'echarts/components';
|
||||
import { BarChart, BarSeriesOption } from 'echarts/charts';
|
||||
import { LabelLayout } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import styles from './index.module.less'
|
||||
import * as d3 from "d3";
|
||||
import Table, { ColumnsType } from "antd/lib/table";
|
||||
import * as d3 from 'd3';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import { getConfig } from '@/config';
|
||||
import {
|
||||
diff, execAdb, getFileNodeList, isPath, log, openNotification, pathRepair, speedReg,
|
||||
} from '@/utils';
|
||||
import {
|
||||
BackItemType, ConfigType, DriverType, FileNodeType,
|
||||
} from '@/types';
|
||||
import styles from './index.module.less';
|
||||
|
||||
const { confirm } = Modal;
|
||||
import { getConfig } from "@/config"
|
||||
import { ExclamationCircleOutlined } from "@ant-design/icons";
|
||||
|
||||
echarts.use([
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
BarChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout
|
||||
LabelLayout,
|
||||
]);
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
TooltipComponentOption | LegendComponentOption | BarSeriesOption
|
||||
>;
|
||||
|
||||
// 图标实例
|
||||
let Chart
|
||||
const backupNodeColumns: ColumnsType<BackItemType> = [
|
||||
{
|
||||
title: '节点描述',
|
||||
|
@ -53,89 +53,56 @@ const backupNodeColumns: ColumnsType<BackItemType> = [
|
|||
align: 'center',
|
||||
render: (_: any, record: BackItemType) => (
|
||||
<Space>
|
||||
<a>单独备份</a>
|
||||
<a>删除节点</a>
|
||||
<Button type="link">单独备份</Button>
|
||||
<Button type="link">删除节点</Button>
|
||||
</Space>
|
||||
)
|
||||
),
|
||||
},
|
||||
];
|
||||
export default () => {
|
||||
const test_path = '/sdcard/DCIM/Camera/'
|
||||
export default function Analysis() {
|
||||
const testPath = '/sdcard/DCIM/Camera/';
|
||||
// const test_path = '/sdcard/MIUI/sound_recorder/app_rec/'
|
||||
// 文件列表
|
||||
const [fileNodeList, setFileNodeList] = useState<FileNodeType[]>([])
|
||||
const [fileNodeList, setFileNodeList] = useState<FileNodeType[]>([]);
|
||||
// 备份状态
|
||||
const [backing, setBacking] = useState<boolean>(false)
|
||||
const [backing, setBacking] = useState<boolean>(false);
|
||||
// 备份目录
|
||||
const [config, setConfig] = useState<ConfigType>(getConfig())
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const chartRef = useRef<HTMLDivElement | null>(null)
|
||||
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
|
||||
const [config, setConfig] = useState<ConfigType>(getConfig());
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
|
||||
const chartRef = useRef<HTMLDivElement | null>(null);
|
||||
const onSelectChange = (newSelectedRowKeys: Key[]) => {
|
||||
setSelectedRowKeys(newSelectedRowKeys);
|
||||
};
|
||||
useEffect(() => {
|
||||
// 备份节点更改
|
||||
console.log(selectedRowKeys)
|
||||
}, [selectedRowKeys])
|
||||
console.log(selectedRowKeys);
|
||||
}, [selectedRowKeys]);
|
||||
const rowSelection = {
|
||||
selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
};
|
||||
// useMount(() => {
|
||||
// // readDir(test_path)
|
||||
// var svg = d3.select(chartRef.current)
|
||||
// .append("svg")
|
||||
// .attr("width", 300)
|
||||
// .attr("height", 120);
|
||||
// let contain = svg
|
||||
// .append("rect")
|
||||
// .attr("width", 300)
|
||||
// .attr("height", 50)
|
||||
// .attr("stroke", 'white')
|
||||
// .attr("fill", '#eee')
|
||||
// .attr("rx", 5)
|
||||
// .attr("yx", 5)
|
||||
// var imgRect = svg.append("rect")
|
||||
// .attr("id", 'img')
|
||||
|
||||
// .attr("x", 0)
|
||||
// .attr("y", 0)
|
||||
// .attr("width", 50)
|
||||
// .attr("height", 50)
|
||||
// .attr("rx", 2)
|
||||
// .attr("yx", 2)
|
||||
// .attr("fill", "red")
|
||||
// var videoRect = svg.append("rect") //添加一个矩形
|
||||
// .attr("id", 'video')
|
||||
// .attr("x", 120)
|
||||
// .attr("y", 0)
|
||||
// .attr("width", 50)
|
||||
// .attr("height", 50)
|
||||
// .attr("rx", 2)
|
||||
// .attr("yx", 2)
|
||||
// .attr("fill", "blue")
|
||||
// })
|
||||
function move(backupQueue: FileNodeType[], outputDir: string): void {
|
||||
if (backupQueue.length === 0) {
|
||||
log("无需备份");
|
||||
log('无需备份');
|
||||
return;
|
||||
}
|
||||
for (const fileN of backupQueue) {
|
||||
backupQueue.forEach((fileN) => {
|
||||
log(`正在备份${fileN.fileName}`);
|
||||
try {
|
||||
const out: string = execAdb(
|
||||
`pull "${fileN.filePath}" "${outputDir + fileN.fileName}"`,
|
||||
);
|
||||
const speed: string | null = out.match(speedReg) !== null ? out.match(speedReg)![0] : "读取速度失败";
|
||||
const speed: string | null = out.match(speedReg) !== null ? out.match(speedReg)![0] : '读取速度失败';
|
||||
log(`平均传输速度${speed}`);
|
||||
} catch (e: any) {
|
||||
log(`备份${fileN.fileName}失败 error:${e.message}`, "error");
|
||||
log(`备份${fileN.fileName}失败 error:${e.message}`, 'error');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function backup() {
|
||||
setBacking(true)
|
||||
setBacking(true);
|
||||
// 确定是否备份
|
||||
// confirm({
|
||||
// title: '',
|
||||
|
@ -149,83 +116,87 @@ export default () => {
|
|||
// },
|
||||
// });
|
||||
// 判断是否有选择的节点
|
||||
console.log('备份节点', selectedRowKeys)
|
||||
console.log('备份节点', selectedRowKeys);
|
||||
// 备份选择的节点
|
||||
// 获取节点完整信息
|
||||
let curBackupNode = config.backups.filter(item => selectedRowKeys.includes(item.comment))
|
||||
let outputRootDir = config.output
|
||||
const curBackupNode = config.backups.filter((item) => selectedRowKeys.includes(item.comment));
|
||||
const outputRootDir = config.output;
|
||||
// 判断根路径
|
||||
isPath(outputRootDir);
|
||||
for (const item of curBackupNode) {
|
||||
curBackupNode.forEach((item) => {
|
||||
// 依次读取对比
|
||||
// 获取指定目录下的文件、文件夹列表
|
||||
let waitBackupFileList: FileNodeType[] = []
|
||||
let dirPath = item.path
|
||||
let dirList = execAdb(`shell ls -l ${dirPath}`).toString().split("\r\n").filter(i => i !== '');
|
||||
const waitBackupFileList: FileNodeType[] = [];
|
||||
const dirPath = item.path;
|
||||
const dirList = execAdb(`shell ls -l ${dirPath}`).toString().split('\r\n').filter((i) => i !== '');
|
||||
// 去掉total
|
||||
dirList.shift()
|
||||
dirList.shift();
|
||||
dirList.forEach((i) => {
|
||||
const item: string[] = i.split(/\s+/);
|
||||
const fileName = item.slice(7).join(" ");
|
||||
const splitItem: string[] = i.split(/\s+/);
|
||||
const fileName = splitItem.slice(7).join(' ');
|
||||
const fileNode: FileNodeType = {
|
||||
fileName,
|
||||
fileSize: Number(item[4]) ?? 0,
|
||||
fileSize: Number(splitItem[4]) ?? 0,
|
||||
filePath: pathRepair(dirPath) + fileName,
|
||||
isDirectory: item[0].startsWith("d"),
|
||||
fileMTime: item.slice(5, 7).join(' ')
|
||||
}
|
||||
waitBackupFileList.push(fileNode)
|
||||
isDirectory: splitItem[0].startsWith('d'),
|
||||
fileMTime: splitItem.slice(5, 7).join(' '),
|
||||
};
|
||||
waitBackupFileList.push(fileNode);
|
||||
});
|
||||
// 判断导出路径是否存在
|
||||
const folderName = item.path.split("/").filter((i: string) => i !== "").at(-1);
|
||||
const folderName = item.path.split('/').filter((i: string) => i !== '').at(-1);
|
||||
|
||||
// 判断节点内是否有备份目录 // 拼接导出路径
|
||||
const itemRootPath = pathRepair(pathRepair(config.output) + folderName);
|
||||
const outputDir = item.output
|
||||
? item.output && pathRepair(item.output)
|
||||
: itemRootPath;
|
||||
isPath(outputDir)
|
||||
isPath(outputDir);
|
||||
// 获取当前目录下的文件
|
||||
// 获取当前存储空间
|
||||
const localFileNodeList = getFileNodeList(outputDir, DriverType.LOCAL)
|
||||
const localFileNodeList = getFileNodeList(outputDir, DriverType.LOCAL);
|
||||
// 对比文件
|
||||
|
||||
const diffList: FileNodeType[] = diff(localFileNodeList, waitBackupFileList);
|
||||
console.log(localFileNodeList)
|
||||
console.log(waitBackupFileList)
|
||||
console.log('diffList', diffList)
|
||||
console.log(localFileNodeList);
|
||||
console.log(waitBackupFileList);
|
||||
console.log('diffList', diffList);
|
||||
// 备份
|
||||
move(diffList, outputDir)
|
||||
}
|
||||
|
||||
move(diffList, outputDir);
|
||||
});
|
||||
|
||||
// 完成弹窗
|
||||
openNotification('提示', '备份完成')
|
||||
openNotification('提示', '备份完成');
|
||||
// 全部备份
|
||||
setBacking(false)
|
||||
setBacking(false);
|
||||
}
|
||||
const hasSelected = selectedRowKeys.length > 0;
|
||||
function tableFooter() {
|
||||
return (
|
||||
<div >
|
||||
{hasSelected ? <div>
|
||||
<Space>
|
||||
当前已选择 {selectedRowKeys.length} 个节点
|
||||
<Button onClick={()=>setSelectedRowKeys([])}>全部取消选择</Button>
|
||||
</Space>
|
||||
</div> : ''}
|
||||
<div>
|
||||
{hasSelected ? (
|
||||
<div>
|
||||
<Space>
|
||||
当前已选择
|
||||
{' '}
|
||||
{selectedRowKeys.length}
|
||||
{' '}
|
||||
个节点
|
||||
<Button onClick={() => setSelectedRowKeys([])}>全部取消选择</Button>
|
||||
</Space>
|
||||
</div>
|
||||
) : ''}
|
||||
</div>
|
||||
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
|
||||
<Card>
|
||||
{/* 节点 */}
|
||||
<Table rowSelection={rowSelection} footer={selectedRowKeys.length!==0?tableFooter:undefined} scroll={{ x: '100%', scrollToFirstRowOnChange: true, y: '300px' }} pagination={false} rowKey='comment' columns={backupNodeColumns} dataSource={config.backups} />
|
||||
<Table rowSelection={rowSelection} footer={selectedRowKeys.length !== 0 ? tableFooter : undefined} scroll={{ x: '100%', scrollToFirstRowOnChange: true, y: '300px' }} pagination={false} rowKey="comment" columns={backupNodeColumns} dataSource={config.backups} />
|
||||
{/* 图表 */}
|
||||
{/* <div className={styles.chartContain} ref={chartRef}>
|
||||
|
||||
|
@ -239,5 +210,5 @@ export default () => {
|
|||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
import { useClickAway, useMount, useSetState } from "ahooks"
|
||||
import path from 'path'
|
||||
import { AppstoreOutlined, DesktopOutlined, MobileOutlined, RetweetOutlined, RollbackOutlined, SearchOutlined } from '@ant-design/icons'
|
||||
import { createRef, useEffect, useRef, useState } from "react";
|
||||
import ignoreFileList from "@/utils/ignoreFileList";
|
||||
import Table, { ColumnsType } from "antd/lib/table";
|
||||
const { pathExistsSync, readdirSync, statSync } = require('fs-extra')
|
||||
import dayjs from 'dayjs'
|
||||
import { createFileNode, execAdb, openNotification, readablizeBytes } from "@/utils";
|
||||
import { DriverType, FileNodeType } from "@/types";
|
||||
import { Breadcrumb, Button, Card, ConfigProvider, Empty, Input, message, Switch } from "antd";
|
||||
import styles from './index.module.less'
|
||||
import classnames from "classnames";
|
||||
import Control, { ControlOptionType } from "@/components/control";
|
||||
import { exec } from "child_process";
|
||||
import { useClickAway, useMount } from 'ahooks';
|
||||
import path from 'path';
|
||||
import {
|
||||
DesktopOutlined, MobileOutlined, SearchOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect, useRef, useState,
|
||||
} from 'react';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
Breadcrumb, Button, Card, ConfigProvider, Empty, Input, message, Switch,
|
||||
} from 'antd';
|
||||
import classnames from 'classnames';
|
||||
import { exec } from 'child_process';
|
||||
import ignoreFileList from '@/utils/ignoreFileList';
|
||||
import {
|
||||
createFileNode, execAdb, openNotification, readablizeBytes,
|
||||
} from '@/utils';
|
||||
import { DriverType, FileNodeType } from '@/types';
|
||||
import Control, { ControlOptionType } from '@/components/control';
|
||||
import styles from './index.module.less';
|
||||
|
||||
const { pathExistsSync, readdirSync } = require('fs-extra');
|
||||
|
||||
const columns: ColumnsType<FileNodeType> = [
|
||||
{
|
||||
title: '文件名称',
|
||||
|
@ -23,153 +34,159 @@ const columns: ColumnsType<FileNodeType> = [
|
|||
title: '文件大小',
|
||||
dataIndex: 'fileSize',
|
||||
key: 'fileSize',
|
||||
render: val => readablizeBytes(val),
|
||||
render: (val) => readablizeBytes(val),
|
||||
sorter: (a, b) => a.fileSize - b.fileSize,
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'fileMTime',
|
||||
key: 'fileMTime',
|
||||
render: time => <span>{dayjs(time).format("YYYY-MM-DD hh:mm:ss")}</span>
|
||||
render: (time) => <span>{dayjs(time).format('YYYY-MM-DD hh:mm:ss')}</span>,
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export default function fileManage() {
|
||||
interface FileListEmptyProps {
|
||||
turnBack: () => void
|
||||
}
|
||||
function FileListEmpty({ turnBack }: FileListEmptyProps) {
|
||||
return (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="当前目录没有文件/文件夹哦">
|
||||
<Button onClick={() => turnBack()}>返回上一级</Button>
|
||||
</Empty>
|
||||
);
|
||||
}
|
||||
export default function FileManage() {
|
||||
// 本地文件列表
|
||||
const [localFileNodeList, setLocalFileNodeList] = useState<FileNodeType[]>([])
|
||||
const [localFileNodeList, setLocalFileNodeList] = useState<FileNodeType[]>([]);
|
||||
// 移动设备文件列表
|
||||
const [mobileFileNodeList, setMobileFileNodeList] = useState<FileNodeType[]>([])
|
||||
const [mobileFileNodeList, setMobileFileNodeList] = useState<FileNodeType[]>([]);
|
||||
// loading
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [loading, setLoading] = useState(true);
|
||||
// 当前文件路径
|
||||
const [localPathCollection, setLocalPathCollection] = useState(['d:/'])
|
||||
const [localPathCollection, setLocalPathCollection] = useState(['d:/']);
|
||||
// 当前路径
|
||||
const [MobilePathCollection, setMobilePathCollection] = useState(['sdcard/', 'DCIM', 'Camera'])
|
||||
const [MobilePathCollection, setMobilePathCollection] = useState(['sdcard/', 'DCIM', 'Camera']);
|
||||
// 搜索框
|
||||
const [searchVal, setSearchVal] = useState<string>('')
|
||||
const [searchVal, setSearchVal] = useState<string>('');
|
||||
|
||||
// 显示状态
|
||||
const [curDriType, setCurDriType] = useState<DriverType>(DriverType.LOCAL)
|
||||
const [curDriType, setCurDriType] = useState<DriverType>(DriverType.LOCAL);
|
||||
|
||||
// 右击
|
||||
const controlPanelRef = useRef<HTMLDivElement | null>(null)
|
||||
const controlPanelRef = useRef<HTMLDivElement | null>(null);
|
||||
// 控制面板坐标
|
||||
interface ControlPanelStyleType {
|
||||
left: number;
|
||||
top: number;
|
||||
visibility: 'visible' | 'hidden'
|
||||
}
|
||||
useClickAway(() => {
|
||||
setControlPanelStyle({ ...controlPanelStyle, visibility: 'hidden' })
|
||||
}, controlPanelRef);
|
||||
|
||||
const [controlPanelStyle, setControlPanelStyle] = useState<ControlPanelStyleType>({
|
||||
left: 0,
|
||||
top: 0,
|
||||
visibility: 'hidden'
|
||||
})
|
||||
visibility: 'hidden',
|
||||
});
|
||||
|
||||
useClickAway(() => {
|
||||
setControlPanelStyle({ ...controlPanelStyle, visibility: 'hidden' });
|
||||
}, controlPanelRef);
|
||||
|
||||
const rightDownOperations: ControlOptionType[] = [
|
||||
{
|
||||
label: '打开',
|
||||
show: [DriverType.LOCAL, DriverType.MOBILE],
|
||||
key: 'open'
|
||||
key: 'open',
|
||||
},
|
||||
{
|
||||
label: '添加到备份节点', show: [DriverType.MOBILE]
|
||||
, key: 'addBackNodeList'
|
||||
label: '添加到备份节点',
|
||||
show: [DriverType.MOBILE],
|
||||
key: 'addBackNodeList',
|
||||
},
|
||||
{
|
||||
label: '添加到忽略名单', show: [DriverType.LOCAL]
|
||||
, key: 'addIgnoreList'
|
||||
label: '添加到忽略名单',
|
||||
show: [DriverType.LOCAL],
|
||||
key: 'addIgnoreList',
|
||||
},
|
||||
{
|
||||
label: '刷新',
|
||||
show: [DriverType.LOCAL, DriverType.MOBILE],
|
||||
key: 'reload'
|
||||
key: 'reload',
|
||||
},
|
||||
{
|
||||
label: '重命名',
|
||||
show: [DriverType.LOCAL, DriverType.MOBILE],
|
||||
key: 'rename'
|
||||
key: 'rename',
|
||||
},
|
||||
]
|
||||
];
|
||||
// 读取移动设备目录
|
||||
function readMobileDriverDir(target: string) {
|
||||
// 清空原列表
|
||||
setMobileFileNodeList([])
|
||||
setMobileFileNodeList([]);
|
||||
console.log('trigger readDir');
|
||||
// todo 判断路径是否存在
|
||||
|
||||
// 获取指定目录下的文件、文件夹列表
|
||||
let dirList = execAdb(`shell ls -l ${target}`).toString().split("\r\n").filter(i => i !== '');
|
||||
const dirList = execAdb(`shell ls -l ${target}`).toString().split('\r\n').filter((i) => i !== '');
|
||||
// 去掉total
|
||||
dirList.shift()
|
||||
dirList.shift();
|
||||
dirList.forEach((i) => {
|
||||
const item: string[] = i.split(/\s+/);
|
||||
const fileName = item.slice(7).join(" ");
|
||||
const fileName = item.slice(7).join(' ');
|
||||
const fileNode: FileNodeType = {
|
||||
fileName,
|
||||
fileSize: Number(item[4]) ?? 0,
|
||||
filePath: target + fileName,
|
||||
isDirectory: item[0].startsWith("d"),
|
||||
fileMTime: item.slice(5, 7).join(' ')
|
||||
}
|
||||
isDirectory: item[0].startsWith('d'),
|
||||
fileMTime: item.slice(5, 7).join(' '),
|
||||
};
|
||||
|
||||
setMobileFileNodeList(fileNodeList => [...fileNodeList, fileNode])
|
||||
setMobileFileNodeList((fileNodeList) => [...fileNodeList, fileNode]);
|
||||
});
|
||||
setLoading(false)
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
// 读取本地目录
|
||||
function readDir(target: string) {
|
||||
// 清空原列表
|
||||
setLocalFileNodeList([])
|
||||
setLoading(true)
|
||||
setLocalFileNodeList([]);
|
||||
setLoading(true);
|
||||
console.log('trigger readDir');
|
||||
if (!pathExistsSync(target)) {
|
||||
console.log(target);
|
||||
|
||||
throw new Error('无效路径')
|
||||
throw new Error('无效路径');
|
||||
}
|
||||
const fileList = readdirSync(target)
|
||||
for (const item of fileList) {
|
||||
if (ignoreFileList.includes(item)) {
|
||||
// 在忽略名单中,跳过
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const node = createFileNode(path.join(target, item))
|
||||
setLocalFileNodeList(fileNodeList => [...fileNodeList, node])
|
||||
} catch (error) {
|
||||
openNotification(item, '生成节点出错啦')
|
||||
}
|
||||
}
|
||||
setLoading(false)
|
||||
// 读取文件名称
|
||||
const fileList: string[] = readdirSync(target);
|
||||
// 过滤不必要的文件名
|
||||
const filterFileList = fileList.filter((name) => !ignoreFileList.includes(name));
|
||||
const nodeList = filterFileList.map((name) => createFileNode(path.join(target, name)));
|
||||
setLocalFileNodeList(nodeList);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
useMount(() => {
|
||||
readDir(localPathCollection.join('/'))
|
||||
readDir(localPathCollection.join('/'));
|
||||
try {
|
||||
readMobileDriverDir(MobilePathCollection.join('/'))
|
||||
|
||||
readMobileDriverDir(MobilePathCollection.join('/'));
|
||||
} catch (error) {
|
||||
openNotification('error', '读取移动设备文件失败')
|
||||
openNotification('error', '读取移动设备文件失败');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// 更新本地文件列表
|
||||
useEffect(() => {
|
||||
console.log('trigger local path rejoin');
|
||||
setLoading(true)
|
||||
readDir(localPathCollection.join('/'))
|
||||
}, [localPathCollection])
|
||||
setLoading(true);
|
||||
readDir(localPathCollection.join('/'));
|
||||
}, [localPathCollection]);
|
||||
// 更新移动设备文件列表
|
||||
useEffect(() => {
|
||||
console.log('trigger mobile path rejoin');
|
||||
setLoading(true)
|
||||
readMobileDriverDir(MobilePathCollection.join('/'))
|
||||
}, [MobilePathCollection])
|
||||
setLoading(true);
|
||||
readMobileDriverDir(MobilePathCollection.join('/'));
|
||||
}, [MobilePathCollection]);
|
||||
|
||||
// 返回上一级
|
||||
const turnBack = () => {
|
||||
|
@ -177,69 +194,61 @@ export default function fileManage() {
|
|||
// 如果是最上一层,不处理
|
||||
if (localPathCollection.length === 1) {
|
||||
message.warning('当前位置处于根目录,无法再返回上一级了');
|
||||
return
|
||||
return;
|
||||
}
|
||||
setLocalPathCollection(path => path.slice(0, -1))
|
||||
setLocalPathCollection((cPath) => cPath.slice(0, -1));
|
||||
}
|
||||
if (curDriType === DriverType.MOBILE) {
|
||||
// 如果是最上一层,不处理
|
||||
if (MobilePathCollection.length === 1) {
|
||||
message.warning('当前位置处于根目录,无法再返回上一级了');
|
||||
return
|
||||
return;
|
||||
}
|
||||
setMobilePathCollection(path => path.slice(0, -1))
|
||||
setMobilePathCollection((cPath) => cPath.slice(0, -1));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const reload = () => {
|
||||
search()
|
||||
}
|
||||
const search = () => {
|
||||
if (searchVal.trim() === '') {
|
||||
readDir(localPathCollection.join('/'))
|
||||
return
|
||||
const search = (val:string) => {
|
||||
setSearchVal(val);
|
||||
if (val.trim() === '') {
|
||||
readDir(localPathCollection.join('/'));
|
||||
return;
|
||||
}
|
||||
if (curDriType === DriverType.LOCAL) {
|
||||
const filterList = localFileNodeList.filter(item => item.fileName.includes(searchVal))
|
||||
setLocalFileNodeList(filterList)
|
||||
const filterList = localFileNodeList.filter((item) => item.fileName.includes(val));
|
||||
setLocalFileNodeList(filterList);
|
||||
}
|
||||
if (curDriType === DriverType.MOBILE) {
|
||||
const filterList = mobileFileNodeList.filter(item => item.fileName.includes(searchVal))
|
||||
setMobileFileNodeList(filterList)
|
||||
const filterList = mobileFileNodeList.filter((item) => item.fileName.includes(val));
|
||||
setMobileFileNodeList(filterList);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
const reload = () => {
|
||||
search(searchVal);
|
||||
};
|
||||
// 切换状态
|
||||
const handleDriverStatus = (targetStatus: DriverType) => {
|
||||
setCurDriType(targetStatus)
|
||||
}
|
||||
useEffect(() => {
|
||||
console.log('search:', searchVal);
|
||||
search()
|
||||
}, [searchVal])
|
||||
function fileListEmpty(){
|
||||
return (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="当前目录没有文件/文件夹哦">
|
||||
<Button onClick={()=>turnBack()}>返回上一级</Button>
|
||||
</Empty>
|
||||
)
|
||||
}
|
||||
setCurDriType(targetStatus);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="overflow-hidden">
|
||||
{/* 右击菜单面板 */}
|
||||
<Control ref={controlPanelRef} options={rightDownOperations} onRow={
|
||||
({ label, key }) => {
|
||||
return {
|
||||
<Control
|
||||
ref={controlPanelRef}
|
||||
options={rightDownOperations}
|
||||
onRow={
|
||||
({ label, key }) => ({
|
||||
onClick: () => {
|
||||
console.log(key, label)
|
||||
setControlPanelStyle({ ...controlPanelStyle, visibility: 'hidden' })
|
||||
console.log(key, label);
|
||||
setControlPanelStyle({ ...controlPanelStyle, visibility: 'hidden' });
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
curType={curDriType} styles={controlPanelStyle}></Control>
|
||||
}
|
||||
curType={curDriType}
|
||||
styles={controlPanelStyle}
|
||||
/>
|
||||
<div className={classnames('flex', 'mb-4', 'justify-between')}>
|
||||
<div className={classnames('flex', 'items-center')}>
|
||||
<div className={styles.operationGroup}>
|
||||
|
@ -248,14 +257,21 @@ function fileListEmpty(){
|
|||
</div>
|
||||
<Breadcrumb>
|
||||
{
|
||||
curDriType === DriverType.LOCAL ?
|
||||
localPathCollection.map((item, index) => <Breadcrumb.Item key={item}>{
|
||||
index === 0 ? (item.slice(0, -2)).toUpperCase() : item
|
||||
}</Breadcrumb.Item>)
|
||||
:
|
||||
MobilePathCollection.map((item) => <Breadcrumb.Item key={item}>{
|
||||
item
|
||||
}</Breadcrumb.Item>)
|
||||
curDriType === DriverType.LOCAL
|
||||
? localPathCollection.map((item, index) => (
|
||||
<Breadcrumb.Item key={item}>
|
||||
{
|
||||
index === 0 ? (item.slice(0, -2)).toUpperCase() : item
|
||||
}
|
||||
</Breadcrumb.Item>
|
||||
))
|
||||
: MobilePathCollection.map((item) => (
|
||||
<Breadcrumb.Item key={item}>
|
||||
{
|
||||
item
|
||||
}
|
||||
</Breadcrumb.Item>
|
||||
))
|
||||
}
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
|
@ -271,66 +287,72 @@ function fileListEmpty(){
|
|||
allowClear
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchVal}
|
||||
onChange={(event) => setSearchVal(event.target.value)}
|
||||
onChange={(event) => search(event.target.value)}
|
||||
style={{ width: 304 }}
|
||||
/></div>
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<ConfigProvider renderEmpty={fileListEmpty}>
|
||||
<Table columns={columns} onRow={({ fileName, isDirectory }, rowIndex) => {
|
||||
return {
|
||||
onClick: e => {
|
||||
console.log('fileName', fileName);
|
||||
console.log('isDirectory', isDirectory);
|
||||
},
|
||||
onDoubleClick: event => {
|
||||
console.log(fileName);
|
||||
<ConfigProvider renderEmpty={() => <FileListEmpty turnBack={() => turnBack()} />}>
|
||||
<Table
|
||||
columns={columns}
|
||||
onRow={({ fileName, isDirectory }, rowIndex) => ({
|
||||
onClick: (e) => {
|
||||
console.log('fileName', fileName);
|
||||
console.log('isDirectory', isDirectory);
|
||||
},
|
||||
onDoubleClick: (event) => {
|
||||
console.log(fileName);
|
||||
|
||||
if (isDirectory) {
|
||||
setLoading(true)
|
||||
if (curDriType === DriverType.LOCAL) setLocalPathCollection(paths => [...paths, fileName])
|
||||
if (curDriType === DriverType.MOBILE) setMobilePathCollection(paths => [...paths, fileName])
|
||||
} else {
|
||||
// 打开文件
|
||||
console.log((localPathCollection.join('/') + fileName));
|
||||
// 处理开头 // 盘符为 /
|
||||
const filePath = [localPathCollection[0].slice(0, -1), ...localPathCollection.slice(1), fileName].join('/')
|
||||
|
||||
|
||||
exec(`start ${filePath}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
openNotification('error', error.message)
|
||||
console.log(`error: ${error.message}`);
|
||||
return;
|
||||
if (isDirectory) {
|
||||
setLoading(true);
|
||||
if (curDriType === DriverType.LOCAL) {
|
||||
setLocalPathCollection((paths) => [...paths, fileName]);
|
||||
}
|
||||
if (stderr) {
|
||||
openNotification('error', stderr)
|
||||
return;
|
||||
if (curDriType === DriverType.MOBILE) {
|
||||
setMobilePathCollection((paths) => [...paths, fileName]);
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 打开文件
|
||||
console.log((localPathCollection.join('/') + fileName));
|
||||
// 处理开头 // 盘符为 /
|
||||
const filePath = [localPathCollection[0].slice(0, -1), ...localPathCollection.slice(1), fileName].join('/');
|
||||
|
||||
exec(`start ${filePath}`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
openNotification('error', error.message);
|
||||
console.log(`error: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
if (stderr) {
|
||||
openNotification('error', stderr);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onMouseDown: (event) => {
|
||||
if (event.button === 2) {
|
||||
// 触发右击
|
||||
console.log(event);
|
||||
const { pageX, pageY } = event.nativeEvent;
|
||||
setControlPanelStyle({
|
||||
left: pageX - 98,
|
||||
top: pageY - 80,
|
||||
visibility: 'visible',
|
||||
});
|
||||
}
|
||||
},
|
||||
})}
|
||||
rowKey="fileName"
|
||||
loading={loading}
|
||||
scroll={{ x: '100%', scrollToFirstRowOnChange: true, y: '380px' }}
|
||||
dataSource={curDriType === DriverType.LOCAL ? localFileNodeList : mobileFileNodeList}
|
||||
/>
|
||||
|
||||
},
|
||||
onMouseDown: event => {
|
||||
if (event.button === 2) {
|
||||
// 触发右击
|
||||
console.log(event)
|
||||
const { pageX, pageY } = event.nativeEvent
|
||||
setControlPanelStyle({
|
||||
left: pageX - 98,
|
||||
top: pageY - 80,
|
||||
visibility: 'visible'
|
||||
})
|
||||
</ConfigProvider>
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
}} rowKey='fileName' loading={loading} scroll={{ x: '100%', scrollToFirstRowOnChange: true, y: '380px' }} dataSource={curDriType === DriverType.LOCAL ? localFileNodeList : mobileFileNodeList} /></ConfigProvider>
|
||||
|
||||
</ Card >
|
||||
|
||||
)
|
||||
</Card>
|
||||
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,42 +1,43 @@
|
|||
import { Button, Card, List, Radio, RadioChangeEvent, Space, Tag } from "antd"
|
||||
import { useState } from "react"
|
||||
import styles from './index.module.less'
|
||||
import {
|
||||
Button, Card, List, Radio, RadioChangeEvent, Space, Tag,
|
||||
} from 'antd';
|
||||
import { useState } from 'react';
|
||||
import { PlusCircleOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { useMount } from 'ahooks';
|
||||
import Table, { ColumnsType } from 'antd/lib/table';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import ignoreFileList from "@/utils/ignoreFileList"
|
||||
import { PlusCircleOutlined, DeleteOutlined } from "@ant-design/icons"
|
||||
import { useMount } from "ahooks"
|
||||
import { getConfig } from "@/config"
|
||||
import Table, { ColumnsType } from "antd/lib/table"
|
||||
import { BackItemType } from "@/types"
|
||||
import { loadTheme, ThemeType } from "@/lib/css/theme"
|
||||
import { useNavigate} from 'react-router-dom'
|
||||
import ignoreFileList from '@/utils/ignoreFileList';
|
||||
import { getConfig } from '@/config';
|
||||
import { BackItemType } from '@/types';
|
||||
import { loadTheme, ThemeType } from '@/lib/css/theme';
|
||||
import styles from './index.module.less';
|
||||
|
||||
const ignoreHeaderFn = () => {
|
||||
return (
|
||||
<div className="flex">
|
||||
<Button className="mr-3" type="primary" icon={<PlusCircleOutlined />} size='small'>新增</Button>
|
||||
<Button className="mr-3" type="primary" danger icon={<DeleteOutlined />} size='small'>清空</Button>
|
||||
<div>当前忽略文件个数:{ignoreFileList.length}</div>
|
||||
const ignoreHeaderFn = () => (
|
||||
<div className="flex">
|
||||
<Button className="mr-3" type="primary" icon={<PlusCircleOutlined />} size="small">新增</Button>
|
||||
<Button className="mr-3" type="primary" danger icon={<DeleteOutlined />} size="small">清空</Button>
|
||||
<div>
|
||||
当前忽略文件个数:
|
||||
{ignoreFileList.length}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default () => {
|
||||
const navigate =useNavigate()
|
||||
const [theme,setTheme]=useState<ThemeType>(localStorage.getItem('theme'))
|
||||
</div>
|
||||
);
|
||||
export default function Settings() {
|
||||
const navigate = useNavigate();
|
||||
const [theme, setTheme] = useState<ThemeType>(localStorage.getItem('theme') as ThemeType ?? ThemeType.LIGHT);
|
||||
|
||||
const [config, setConfig] = useState({
|
||||
color: 'auto',
|
||||
backupNode: getConfig()
|
||||
})
|
||||
|
||||
backupNode: getConfig(),
|
||||
});
|
||||
|
||||
const handleColor = (event: RadioChangeEvent) => {
|
||||
const theme = event.target.value
|
||||
loadTheme(theme)
|
||||
navigate(0)
|
||||
setTheme(theme)
|
||||
}
|
||||
|
||||
const targetTheme = event.target.value;
|
||||
loadTheme(targetTheme);
|
||||
navigate(0);
|
||||
setTheme(targetTheme);
|
||||
};
|
||||
|
||||
const backupNodeColumns: ColumnsType<BackItemType> = [
|
||||
{
|
||||
|
@ -54,15 +55,14 @@ export default () => {
|
|||
dataIndex: 'output',
|
||||
key: 'output',
|
||||
align: 'center',
|
||||
render: (i) => <Tag color={i ? 'gold' : ''}>{i ? i : '继承父级'}</Tag >
|
||||
render: (i) => <Tag color={i ? 'gold' : ''}>{i || '继承父级'}</Tag>,
|
||||
},
|
||||
{
|
||||
title: '全量备份',
|
||||
dataIndex: 'full',
|
||||
key: 'full',
|
||||
align: 'center',
|
||||
render: (i) =>
|
||||
<Tag color={i ? 'blue' : 'red'}>{i ? '是' : '否'}</Tag >
|
||||
render: (i) => <Tag color={i ? 'blue' : 'red'}>{i ? '是' : '否'}</Tag>,
|
||||
|
||||
},
|
||||
{
|
||||
|
@ -70,13 +70,12 @@ export default () => {
|
|||
dataIndex: 'actions',
|
||||
key: 'actions',
|
||||
align: 'center',
|
||||
render: (_: any, record: BackItemType) => (
|
||||
<a>Delete</a>
|
||||
)
|
||||
render: (_: any, record: BackItemType) => <Button type="link">Delete</Button>
|
||||
,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Card >
|
||||
<Card>
|
||||
<div className={styles.settingItem}>
|
||||
<div className="text-md font-bold mb-3">主题</div>
|
||||
<Radio.Group onChange={(event) => handleColor(event)} value={theme}>
|
||||
|
@ -91,10 +90,10 @@ export default () => {
|
|||
<List
|
||||
size="small"
|
||||
header={ignoreHeaderFn()}
|
||||
footer={<div></div>}
|
||||
footer={<div />}
|
||||
bordered
|
||||
dataSource={ignoreFileList}
|
||||
renderItem={item => <List.Item>{item}</List.Item>}
|
||||
renderItem={(item) => <List.Item>{item}</List.Item>}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.settingItem}>
|
||||
|
@ -103,5 +102,5 @@ export default () => {
|
|||
</div>
|
||||
|
||||
</Card>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { lstat } from 'fs/promises'
|
||||
import { cwd } from 'process'
|
||||
import { ipcRenderer } from 'electron'
|
||||
import { lstat } from 'fs/promises';
|
||||
import { cwd } from 'process';
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
ipcRenderer.on('main-process-message', (_event, ...args) => {
|
||||
console.log('[Receive Main-process message]:', ...args)
|
||||
})
|
||||
console.log('[Receive Main-process message]:', ...args);
|
||||
});
|
||||
|
||||
lstat(cwd()).then(stats => {
|
||||
console.log('[fs.lstat]', stats)
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
lstat(cwd()).then((stats) => {
|
||||
console.log('[fs.lstat]', stats);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ export interface devicesType {
|
|||
status: string;
|
||||
}
|
||||
|
||||
// 显示数据
|
||||
// 显示数据
|
||||
export enum DriverType {
|
||||
MOBILE,
|
||||
LOCAL
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default [
|
||||
'System Volume Information',
|
||||
'$RECYCLE.BIN',
|
||||
'Config.Msi'
|
||||
]
|
||||
'Config.Msi',
|
||||
];
|
||||
|
|
|
@ -1,32 +1,85 @@
|
|||
import { DriverType, FileNodeType } from "@/types";
|
||||
import { notification } from "antd";
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
/* eslint-disable no-continue */
|
||||
import { notification } from 'antd';
|
||||
// import winston, { format } from "winston";
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
import { DriverType, FileNodeType, devicesType } from '@/types';
|
||||
import ignoreFileList from './ignoreFileList';
|
||||
|
||||
const {
|
||||
statSync,
|
||||
pathExistsSync,
|
||||
ensureDirSync,
|
||||
readdirSync,
|
||||
} = require("fs-extra");
|
||||
// import winston, { format } from "winston";
|
||||
import path from "path";
|
||||
import { execSync } from "child_process";
|
||||
import { home } from "@/config";
|
||||
import { devicesType } from "@/types";
|
||||
import ignoreFileList from "./ignoreFileList";
|
||||
} = require('fs-extra');
|
||||
|
||||
type levelType = 'info' | 'error' | 'warn';
|
||||
// 记录输出
|
||||
export const log = (value: string, level: levelType = 'info'): void => {
|
||||
// logger.log(level, value);
|
||||
console.log(level, value);
|
||||
};
|
||||
|
||||
export function openNotification(
|
||||
message: string,
|
||||
description: string,
|
||||
onClick?: () => void,
|
||||
onClose?: () => void,
|
||||
) {
|
||||
notification.open({
|
||||
message,
|
||||
description,
|
||||
onClick,
|
||||
onClose,
|
||||
});
|
||||
}
|
||||
|
||||
// 执行adb shell命令
|
||||
const currentDeviceName: string = '';
|
||||
export const execAdb = (code: string) => {
|
||||
const command = `adb ${
|
||||
currentDeviceName ? `-s ${currentDeviceName}` : ''
|
||||
} ${code}`;
|
||||
try {
|
||||
const res = execSync(command).toString();
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
openNotification('error', `${code} 执行失败`);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
// 文件大小后缀转换
|
||||
export function readablizeBytes(bytes: number): string {
|
||||
if (bytes === 0) return "";
|
||||
const s = ["Bytes", "KB", "MB", "GB", "TB", "PB"];
|
||||
if (bytes === 0) return '';
|
||||
const s = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
const e = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, Math.floor(e))).toFixed(2) + " " + s[e] ?? 0;
|
||||
return `${(bytes / 1024 ** Math.floor(e)).toFixed(2)} ${s[e]}` ?? 0;
|
||||
}
|
||||
|
||||
// 替换字符
|
||||
export const replace = (str: string): string => {
|
||||
const reg = [
|
||||
{ reg: /[(]/g, val: '\\(' },
|
||||
{ reg: /[)]/g, val: '\\)' },
|
||||
];
|
||||
let res: string = str;
|
||||
for (let i = 0; i < reg.length; i += 1) {
|
||||
res = res.replace(reg[i].reg, reg[i].val);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
// 获取手机中文件的大小
|
||||
export const getFileSize = (path: string): number => {
|
||||
export const getFileSize = (adbPath: string): number => {
|
||||
try {
|
||||
const res = execAdb(`shell du -k '${replace(path)}'`).toString();
|
||||
const fileSize: string = res.split("\t")[0];
|
||||
const res = execAdb(`shell du -k '${replace(adbPath)}'`).toString();
|
||||
const fileSize: string = res.split('\t')[0];
|
||||
return Number(fileSize);
|
||||
} catch (error) {
|
||||
log(`获取文件大小失败-${path}`, "warn");
|
||||
log(`获取文件大小失败-${adbPath}`, 'warn');
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
@ -35,7 +88,7 @@ export function createFileNode(targetFilePath: string): FileNodeType {
|
|||
const detail = statSync(targetFilePath);
|
||||
return {
|
||||
fileSize: detail.size,
|
||||
fileName: targetFilePath.split("\\").at(-1) ?? "读取文件名错误",
|
||||
fileName: targetFilePath.split('\\').at(-1) ?? '读取文件名错误',
|
||||
filePath: targetFilePath,
|
||||
isDirectory: detail.isDirectory(),
|
||||
fileMTime: detail.mtime,
|
||||
|
@ -45,7 +98,7 @@ export function createFileNode(targetFilePath: string): FileNodeType {
|
|||
// 获取指定目录下文件节点
|
||||
export function getFileNodeList(
|
||||
targetPath: string,
|
||||
mode: DriverType
|
||||
mode: DriverType,
|
||||
): FileNodeType[] {
|
||||
const fileNodeList: FileNodeType[] = [];
|
||||
if (mode === DriverType.LOCAL) {
|
||||
|
@ -59,14 +112,14 @@ export function getFileNodeList(
|
|||
const node = createFileNode(path.join(targetPath, item));
|
||||
fileNodeList.push(node);
|
||||
} catch (error) {
|
||||
openNotification(item, "生成节点出错啦");
|
||||
openNotification(item, '生成节点出错啦');
|
||||
}
|
||||
}
|
||||
|
||||
return fileNodeList;
|
||||
}
|
||||
if (mode === DriverType.MOBILE) {
|
||||
}
|
||||
// if (mode === DriverType.MOBILE) {
|
||||
// }
|
||||
return fileNodeList;
|
||||
}
|
||||
// 通过adb方式获取文件路径生成文件节点
|
||||
|
@ -76,47 +129,33 @@ export function createFileNodeWithADB(targetFilePath: string): FileNodeType {
|
|||
// 文件名称 filename
|
||||
// 文件大小 filesize
|
||||
const fileType = execAdb(`shell stat -c '%F' '${targetFilePath}'`).replace(
|
||||
"\r\n",
|
||||
""
|
||||
'\r\n',
|
||||
'',
|
||||
);
|
||||
const fileName = execAdb(`shell stat -c '%n' '${targetFilePath}'`).replace(
|
||||
"\r\n",
|
||||
""
|
||||
'\r\n',
|
||||
'',
|
||||
);
|
||||
const fileSize = Number(
|
||||
execAdb(`shell stat -c '%s' '${targetFilePath}'`).replace("\r\n", "")
|
||||
execAdb(`shell stat -c '%s' '${targetFilePath}'`).replace('\r\n', ''),
|
||||
);
|
||||
return {
|
||||
fileName,
|
||||
fileSize,
|
||||
filePath: targetFilePath,
|
||||
isDirectory: fileType === "directory",
|
||||
isDirectory: fileType === 'directory',
|
||||
};
|
||||
}
|
||||
|
||||
export function openNotification(
|
||||
message: string,
|
||||
description: string,
|
||||
onClick?: () => void,
|
||||
onClose?: () => void
|
||||
) {
|
||||
notification.open({
|
||||
message,
|
||||
description,
|
||||
onClick,
|
||||
onClose,
|
||||
});
|
||||
}
|
||||
|
||||
type levelType = "info" | "error" | "warn";
|
||||
|
||||
export const diff = (
|
||||
localArr: FileNodeType[],
|
||||
remoteArr: FileNodeType[]
|
||||
remoteArr: FileNodeType[],
|
||||
): FileNodeType[] => {
|
||||
let names: { [key: string]: boolean } = {};
|
||||
localArr.forEach(i=> (names[i.fileName] = true));
|
||||
return remoteArr.filter(i =>!names[i.fileName] );
|
||||
const names: { [key: string]: boolean } = {};
|
||||
localArr.forEach((i) => {
|
||||
names[i.fileName] = true;
|
||||
});
|
||||
return remoteArr.filter((i) => !names[i.fileName]);
|
||||
};
|
||||
// const logger = winston.createLogger({
|
||||
// format: format.combine(
|
||||
|
@ -137,48 +176,29 @@ export const diff = (
|
|||
// }),
|
||||
// ],
|
||||
// });
|
||||
// 记录输出
|
||||
export const log = (value: string, level: levelType = "info"): void => {
|
||||
// logger.log(level, value);
|
||||
console.log(level, value);
|
||||
};
|
||||
|
||||
// 判断路径
|
||||
export const isPath = (dirPath: string): void => {
|
||||
if (!pathExistsSync(dirPath)) {
|
||||
log(`导出路径不存在-${dirPath}`, "warn");
|
||||
log(`导出路径不存在-${dirPath}`, 'warn');
|
||||
// 没有则创建文件夹
|
||||
ensureDirSync(dirPath);
|
||||
log(`已自动创建导出路径-${dirPath}`, "warn");
|
||||
log(`已自动创建导出路径-${dirPath}`, 'warn');
|
||||
}
|
||||
};
|
||||
|
||||
// 替换字符
|
||||
export const replace = (str: string): string => {
|
||||
const reg = [
|
||||
{ reg: /[(]/g, val: "\\(" },
|
||||
{ reg: /[)]/g, val: "\\)" },
|
||||
];
|
||||
let res: string = str;
|
||||
for (let i = 0; i < reg.length; i += 1) {
|
||||
res = res.replace(reg[i].reg, reg[i].val);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
// 获取设备
|
||||
export const devices = (): devicesType[] => {
|
||||
const res = execSync("adb devices").toString();
|
||||
const res = execSync('adb devices').toString();
|
||||
const arr = res
|
||||
.split(/\n/)
|
||||
.map((line) => line.split("\t"))
|
||||
.map((line) => line.split('\t'))
|
||||
.filter((line) => line.length > 1)
|
||||
.map((device) => ({ name: device[0].trim(), status: device[1].trim() }));
|
||||
|
||||
return arr;
|
||||
};
|
||||
|
||||
let currentDeviceName: string = "";
|
||||
// 指定设备
|
||||
// export const selectDevice = async (): Promise<string | false> => {
|
||||
// // 获取设备
|
||||
|
@ -207,21 +227,6 @@ let currentDeviceName: string = "";
|
|||
// return currentDeviceName;
|
||||
// };
|
||||
|
||||
// 执行adb shell命令
|
||||
export const execAdb = (code: string) => {
|
||||
const command = `adb ${
|
||||
currentDeviceName ? `-s ${currentDeviceName}` : ""
|
||||
} ${code}`;
|
||||
try {
|
||||
let res = execSync(command).toString();
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
openNotification("error", `${code} 执行失败`);
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
// 判断手机备份路径是否存在
|
||||
export const isPathAdb = (folderPath: string): boolean => {
|
||||
try {
|
||||
|
@ -235,5 +240,4 @@ export const isPathAdb = (folderPath: string): boolean => {
|
|||
export const speedReg: RegExp = /[0-9.]+\s(MB\/s)/;
|
||||
|
||||
// 路径后面补上斜杠
|
||||
export const pathRepair = (spath: string): string =>
|
||||
spath.at(-1) === "/" ? spath : `${spath}/`;
|
||||
export const pathRepair = (spath: string): string => (spath.at(-1) === '/' ? spath : `${spath}/`);
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
import { rmSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import electron from "vite-plugin-electron";
|
||||
import pkg from "./package.json";
|
||||
import Unocss from "unocss/vite";
|
||||
import presetUno from "@unocss/preset-uno";
|
||||
import presetIcons from "@unocss/preset-icons";
|
||||
import presetAttributify from "@unocss/preset-attributify";
|
||||
import transformerDirective from "@unocss/transformer-directives";
|
||||
import { presetScrollbar } from "unocss-preset-scrollbar";
|
||||
rmSync(join(__dirname, "dist"), { recursive: true, force: true }); // v14.14.0
|
||||
import { rmSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import electron from 'vite-plugin-electron';
|
||||
import Unocss from 'unocss/vite';
|
||||
import presetUno from '@unocss/preset-uno';
|
||||
import presetIcons from '@unocss/preset-icons';
|
||||
import presetAttributify from '@unocss/preset-attributify';
|
||||
import transformerDirective from '@unocss/transformer-directives';
|
||||
import { presetScrollbar } from 'unocss-preset-scrollbar';
|
||||
import pkg from './package.json';
|
||||
|
||||
rmSync(join(__dirname, 'dist'), { recursive: true, force: true }); // v14.14.0
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": join(__dirname, "src"),
|
||||
styles: join(__dirname, "src/assets/styles"),
|
||||
'@': join(__dirname, 'src'),
|
||||
styles: join(__dirname, 'src/assets/styles'),
|
||||
},
|
||||
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".less"],
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.less'],
|
||||
},
|
||||
|
||||
css: {
|
||||
|
@ -33,24 +34,24 @@ export default defineConfig({
|
|||
react(),
|
||||
electron({
|
||||
main: {
|
||||
entry: "electron/main/index.ts",
|
||||
entry: 'electron/main/index.ts',
|
||||
vite: {
|
||||
build: {
|
||||
sourcemap: false,
|
||||
outDir: "dist/electron/main",
|
||||
outDir: 'dist/electron/main',
|
||||
},
|
||||
},
|
||||
},
|
||||
preload: {
|
||||
input: {
|
||||
// You can configure multiple preload scripts here
|
||||
index: join(__dirname, "electron/preload/index.ts"),
|
||||
index: join(__dirname, 'electron/preload/index.ts'),
|
||||
},
|
||||
vite: {
|
||||
build: {
|
||||
// For debug
|
||||
sourcemap: "inline",
|
||||
outDir: "dist/electron/preload",
|
||||
sourcemap: 'inline',
|
||||
outDir: 'dist/electron/preload',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -60,7 +61,7 @@ export default defineConfig({
|
|||
|
||||
Unocss({
|
||||
presets: [
|
||||
presetUno({ dark: "class" }),
|
||||
presetUno({ dark: 'class' }),
|
||||
presetAttributify(),
|
||||
presetIcons({
|
||||
/* options */
|
||||
|
|
Loading…
Reference in New Issue