diff --git a/package.json b/package.json index e32b2a7a..fe60e5e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "filecat", - "version": "1.0.10", + "version": "1.0.11", "description": "filecat 文件管理器", "author": "xiaobaidadada", "scripts": { @@ -73,7 +73,7 @@ "dependencies": { "@xiaobaidadada/node-pty-prebuilt": "^1.0.4", "@xiaobaidadada/node-tuntap2-wintun": "^1.0.6", - "node-process-watcher": "^1.0.2", + "node-process-watcher": "^1.0.3", "node-unrar-js": "^2.0.2", "ssh2": "^1.15.0" }, diff --git a/src/common/ListUtil.ts b/src/common/ListUtil.ts index b9701a1a..016ddc3a 100644 --- a/src/common/ListUtil.ts +++ b/src/common/ListUtil.ts @@ -30,6 +30,28 @@ export function getByList(list, value) { return list[index]; } +// 获取数组元素的最大值和最小值 +export function getMaxByList(list?:number[]) { + if(!list) { + return {}; + } + let max; + let min; + for (let v of list) { + if(max === undefined) { + max = v; + } else if (max < v) { + max = v; + } + if (min === undefined) { + min = v; + } else if (min > v) { + min = v; + } + } + return {max,min}; +} + // 删除数组中的一个元素,并返回新的数组 export function getNewDeleteByList(list, value) { const index = list.indexOf(value); diff --git a/src/main/domain/data/DataUtil.ts b/src/main/domain/data/DataUtil.ts index 74ed0b1d..4f5c17f2 100644 --- a/src/main/domain/data/DataUtil.ts +++ b/src/main/domain/data/DataUtil.ts @@ -63,10 +63,12 @@ export class DataUtil { } // 上传到临时目录下,并返回文件路径 - public static writeFileSyncTemp(filename: string,dir, data: any) { + public static writeFileSyncTemp(filename: string,dir, data?: any) { const p = path.join(Env.work_dir, dir, filename); this.checkFile(filename,dir); - fs.writeFileSync(p, data); + if (data) { + fs.writeFileSync(p, data); + } return p; } } diff --git a/src/main/domain/file/file.controller.ts b/src/main/domain/file/file.controller.ts index ef97b711..3f6eb84a 100644 --- a/src/main/domain/file/file.controller.ts +++ b/src/main/domain/file/file.controller.ts @@ -21,13 +21,13 @@ import { } from "../../../common/file.pojo"; import {FileServiceImpl} from "./file.service"; import {Result, Sucess} from "../../other/Result"; -import multer from 'multer'; import {cutCopyReq, fileInfoReq, fileReq, saveTxtReq} from "../../../common/req/file.req"; import {Cache} from "../../other/cache"; import {msg} from "../../../common/frame/router"; import {CmdType, WsData} from "../../../common/frame/WsData"; import {settingService} from "../setting/setting.service"; -import { Request } from 'express'; +import {Request, Response} from 'express'; + @JsonController("/file") export class FileController { @@ -35,40 +35,50 @@ export class FileController { @Get() async getRootFile(@Req() ctx): Promise> { - return await FileServiceImpl.getFile('',ctx.headers.authorization); + return await FileServiceImpl.getFile('', ctx.headers.authorization); } @Get('/:path([^"]{0,})') - async getFile(@Req() ctx,@Param("path") path?: string,@QueryParam("is_sys_path",{required:false}) is_sys_path?: number): Promise> { - return await FileServiceImpl.getFile(path,ctx.headers.authorization,is_sys_path); + async getFile(@Req() ctx, @Param("path") path?: string, @QueryParam("is_sys_path", {required: false}) is_sys_path?: number): Promise> { + return await FileServiceImpl.getFile(path, ctx.headers.authorization, is_sys_path); } @Post('/file/info') - async getFileInfo(@Req() ctx, @Body() data: {type:FileTypeEnum,path:string}) { - return Sucess(await FileServiceImpl.getFileInfo(data.type,data.path,ctx.headers.authorization)); + async getFileInfo(@Req() ctx, @Body() data: { type: FileTypeEnum, path: string }) { + return Sucess(await FileServiceImpl.getFileInfo(data.type, data.path, ctx.headers.authorization)); } + // @Put('/:path([^"]{0,})') + // async uploadFile(@Req() ctx, @Param("path") path?: string, @UploadedFile('file', {options: FileServiceImpl.fileUploadOptions}) file?: any) { + // // await FileServiceImpl.uploadFile(path, file,ctx.headers.authorization); + // return Sucess("1"); + // } + @Put('/:path([^"]{0,})') - async uploadFile(@Req() ctx,@Param("path") path?: string, @UploadedFile('file') file?: multer.File) { - await FileServiceImpl.uploadFile(path, file,ctx.headers.authorization); + async uploadFile(@Req() req: Request, @Res() res: Response, @Param("path") path?: string) { + await FileServiceImpl.uploadFile(path, req, res, req.headers.authorization); return Sucess("1"); } + @Delete('/:path([^"]{0,})') - async deletes(@Req() ctx,@Param("path") path?: string) { - return await FileServiceImpl.deletes(ctx.headers.authorization,path); + async deletes(@Req() ctx, @Param("path") path?: string) { + return await FileServiceImpl.deletes(ctx.headers.authorization, path); } @Post('/save/:path([^"]{0,})') - async save(@Req() ctx,@Param("path") path?: string, @Body() data?: saveTxtReq,@QueryParam("is_sys_path",{required:false}) is_sys_path?: number) { - await FileServiceImpl.save(ctx.headers.authorization,data?.context, path,is_sys_path); + async save(@Req() ctx, @Param("path") path?: string, @Body() data?: saveTxtReq, @QueryParam("is_sys_path", {required: false}) is_sys_path?: number) { + await FileServiceImpl.save(ctx.headers.authorization, data?.context, path, is_sys_path); return Sucess("1"); } // base保存支持分片 这个框架post默认最大只支持1mb @Post('/base64/save/:path([^"]{0,})') - async common_base64_save(@Req() ctx,@Param("path") path?: string,@Body() data?: {base64_context:string,type:base64UploadType}) { - await FileServiceImpl.common_base64_save(ctx.headers.authorization,path,data.base64_context,data.type); + async common_base64_save(@Req() ctx, @Param("path") path?: string, @Body() data?: { + base64_context: string, + type: base64UploadType + }) { + await FileServiceImpl.common_base64_save(ctx.headers.authorization, path, data.base64_context, data.type); return Sucess("1"); } @@ -81,38 +91,38 @@ export class FileController { @Post('/cut') - async cut(@Req() ctx,@Body() data?: cutCopyReq) { - await FileServiceImpl.cut(ctx.headers.authorization,data); + async cut(@Req() ctx, @Body() data?: cutCopyReq) { + await FileServiceImpl.cut(ctx.headers.authorization, data); return Sucess("1"); } @Post('/copy') - async copy(@Req() ctx,@Body() data?: cutCopyReq) { - await FileServiceImpl.copy(ctx.headers.authorization,data); + async copy(@Req() ctx, @Body() data?: cutCopyReq) { + await FileServiceImpl.copy(ctx.headers.authorization, data); return Sucess("1"); } @Post('/new/file') - async newFile(@Req() ctx,@Body() data?: fileInfoReq) { - await FileServiceImpl.newFile(ctx.headers.authorization,data); + async newFile(@Req() ctx, @Body() data?: fileInfoReq) { + await FileServiceImpl.newFile(ctx.headers.authorization, data); return Sucess("1"); } @Post('/new/dir') - async newDir(@Req() ctx ,@Body() data?: fileInfoReq) { - await FileServiceImpl.newDir(ctx.headers.authorization,data); + async newDir(@Req() ctx, @Body() data?: fileInfoReq) { + await FileServiceImpl.newDir(ctx.headers.authorization, data); return Sucess("1"); } @Post('/rename') - async rename(@Req() ctx ,@Body() data?: fileInfoReq) { - await FileServiceImpl.rename(ctx.headers.authorization,data); + async rename(@Req() ctx, @Body() data?: fileInfoReq) { + await FileServiceImpl.rename(ctx.headers.authorization, data); return Sucess("1"); } // 切换路径 @Post('/base_switch') - async switchBasePath(@Body() data:{root_index:number},@Req() ctx ) { + async switchBasePath(@Body() data: { root_index: number }, @Req() ctx) { const obj = Cache.getTokenMap().get(ctx.headers.authorization); if (obj) { obj["root_index"] = data.root_index; @@ -122,14 +132,14 @@ export class FileController { // 获取主根位置 @Post('/base_switch/get') - async switchGetBasePath(@Req() req: Request ) { + async switchGetBasePath(@Req() req: Request) { const obj = Cache.getTokenMap().get(req.headers.authorization); let index; - if (obj["root_index"]!==undefined) { + if (obj["root_index"] !== undefined) { index = obj["root_index"]; } else { const list = settingService.getFilesSetting(); - for (let i=0;i) { + async file_video_trans(data: WsData) { FileServiceImpl.file_video_trans(data); return "" } @msg(CmdType.file_uncompress) - async uncompress(data:WsData) { + async uncompress(data: WsData) { FileServiceImpl.uncompress(data); return "" } @msg(CmdType.file_compress) - async compress(data:WsData) { + async compress(data: WsData) { FileServiceImpl.FileCompress(data); return "" } // 获取studio路径 @Post("/studio/get/item") - async studio_get_item(@Body() data:{path:string},@Req() ctx ) { - return Sucess(await FileServiceImpl.studio_get_item(data.path,ctx.headers.authorization)); + async studio_get_item(@Body() data: { path: string }, @Req() ctx) { + return Sucess(await FileServiceImpl.studio_get_item(data.path, ctx.headers.authorization)); } } diff --git a/src/main/domain/file/file.service.ts b/src/main/domain/file/file.service.ts index 9007f925..b7efba86 100644 --- a/src/main/domain/file/file.service.ts +++ b/src/main/domain/file/file.service.ts @@ -11,7 +11,6 @@ import fs, {Stats} from "fs"; import fse from 'fs-extra' import path from "path"; import {Fail, Result, Sucess} from "../../other/Result"; -import multer from "multer"; import {rimraf} from "rimraf"; import {cutCopyReq, fileInfoReq} from "../../../common/req/file.req"; import {formatFileSize, getShortTime} from "../../../common/ValueUtil"; @@ -27,36 +26,39 @@ import {getFfmpeg} from "../bin/bin"; import {getFileFormat} from "../../../common/FileMenuType"; import {getEditModelType} from "../../../common/StringUtil"; import si from "systeminformation"; + const archiver = require('archiver'); const mime = require('mime-types'); - +import multer from 'multer'; +import {Request, Response} from "express"; const MAX_SIZE_TXT = 20 * 1024 * 1024; -class FileService extends FileCompress{ - public async getFile(param_path,token,is_sys_path?: number):Promise> { - const result:GetFilePojo = { - files:[], - folders:[], +class FileService extends FileCompress { + + public async getFile(param_path, token, is_sys_path?: number): Promise> { + const result: GetFilePojo = { + files: [], + folders: [], }; - const sysPath = is_sys_path===1?`/${decodeURIComponent(param_path)}`:path.join(settingService.getFileRootPath(token),param_path?decodeURIComponent(param_path):""); + const sysPath = is_sys_path === 1 ? `/${decodeURIComponent(param_path)}` : path.join(settingService.getFileRootPath(token), param_path ? decodeURIComponent(param_path) : ""); if (!fs.existsSync(sysPath)) { - return Fail("路径不存在",RCode.Fail); + return Fail("路径不存在", RCode.Fail); } const stats = fs.statSync(sysPath); if (stats.isFile()) { // 单个文件 if (stats.size > MAX_SIZE_TXT) { - return Fail("超过20MB",RCode.File_Max); + return Fail("超过20MB", RCode.File_Max); } const name = path.basename(sysPath); const buffer = fs.readFileSync(sysPath); - const pojo = Sucess(buffer.toString(),RCode.PreFile); + const pojo = Sucess(buffer.toString(), RCode.PreFile); pojo.message = name; return pojo; } else { if (!stats.isDirectory()) { - return Fail("不是文件",RCode.Fail); + return Fail("不是文件", RCode.Fail); } } @@ -64,48 +66,48 @@ class FileService extends FileCompress{ for (const item of items) { const filePath = path.join(sysPath, item); // 获取文件或文件夹的元信息 - let stats: null| Stats= null; + let stats: null | Stats = null; try { stats = fs.statSync(filePath); } catch (e) { - console.log("读取错误",e); + console.log("读取错误", e); } - const formattedCreationTime = stats?getShortTime(new Date(stats.mtime).getTime()):""; - const size = stats?formatFileSize(stats.size):""; + const formattedCreationTime = stats ? getShortTime(new Date(stats.mtime).getTime()) : ""; + const size = stats ? formatFileSize(stats.size) : ""; if (stats && stats.isFile()) { const type = getFileFormat(item); result.files?.push({ - type:type, - name:item, - mtime:formattedCreationTime, + type: type, + name: item, + mtime: formattedCreationTime, size, - isLink:stats.isSymbolicLink(), - path:path.join(param_path,item) + isLink: stats.isSymbolicLink(), + path: path.join(param_path, item) }) } else if (stats && stats.isDirectory()) { result.folders?.push({ - type:FileTypeEnum.folder, - name:item, - mtime:formattedCreationTime, - isLink:stats.isSymbolicLink(), - path:param_path + type: FileTypeEnum.folder, + name: item, + mtime: formattedCreationTime, + isLink: stats.isSymbolicLink(), + path: param_path }) - } else { + } else { result.files?.push({ - type:FileTypeEnum.dev, - name:item, - mtime:formattedCreationTime, + type: FileTypeEnum.dev, + name: item, + mtime: formattedCreationTime, size, - path:path.join(param_path,item) + path: path.join(param_path, item) }) } } return Sucess(result); } - public async getFileInfo(type:FileTypeEnum,fpath:string,token) { - let info:FileInfoItemData = {}; - const sysPath = path.join(settingService.getFileRootPath(token),decodeURIComponent(fpath)); + public async getFileInfo(type: FileTypeEnum, fpath: string, token) { + let info: FileInfoItemData = {}; + const sysPath = path.join(settingService.getFileRootPath(token), decodeURIComponent(fpath)); switch (type) { case FileTypeEnum.folder: info = await this.getDiskSizeForPath(sysPath); @@ -117,27 +119,56 @@ class FileService extends FileCompress{ return info; } - public uploadFile(filePath,file: multer.File,token) { - const sysPath = path.join(settingService.getFileRootPath(token),filePath?decodeURIComponent(filePath):""); - if (!file) { - // 目录 - if (!fs.existsSync(sysPath)) { - // 目录不存在,创建目录 - fs.mkdirSync(sysPath, { recursive: true }); + + fileUploadOptions = { + storage: multer.diskStorage({ + destination: (req: any, file: any, cb: any) => { + // return cb(new Error("Custom error: Path issue")); + cb(null, req.fileDir); // 存储路径 + }, + filename: (req: any, file: any, cb: any) => { + // file.originalname + cb(null, req.fileName); } - return; - } + }) + }; + + upload = multer({ + storage: this.fileUploadOptions.storage, + // limits: { fileSize: 1024 * 1024 * 2 }, // 限制文件大小为 2MB 无限制 + }).single('file'); + + public uploadFile(filePath, req: Request,res:Response, token) { + + const sysPath = path.join(settingService.getFileRootPath(token), filePath ? decodeURIComponent(filePath) : ""); + // if (!file) { + // // 目录 + // if (!fs.existsSync(sysPath)) { + // // 目录不存在,创建目录 + // fs.mkdirSync(sysPath, {recursive: true}); + // } + // return; + // } + req['fileDir'] = path.dirname(sysPath); + req['fileName'] = path.basename(sysPath); + this.upload(req, res, (err) => { + if (err) { + + } + // 成功上传 + }); // 写入文件 - fs.writeFileSync(sysPath, file.buffer); + // fs.writeFileSync(sysPath, file.buffer); + //multer 默认使用 return new Multer({}) 默认memoryStorage 这种方式 buffer 不属于v8内存管理 所以内存释放的比较慢 } - public deletes(token,filePath?:string) { + public deletes(token, filePath?: string) { if (!filePath) { return Sucess("1"); } - const sysPath = path.join(settingService.getFileRootPath(token),decodeURIComponent(filePath)); + const sysPath = path.join(settingService.getFileRootPath(token), decodeURIComponent(filePath)); if (settingService.protectionCheck(sysPath)) { - return Fail("1",RCode.PROTECT_FILE); + return Fail("1", RCode.PROTECT_FILE); } const stats = fs.statSync(sysPath); if (stats.isFile()) { @@ -148,11 +179,11 @@ class FileService extends FileCompress{ return Sucess("1"); } - public save(token,context?:string,filePath?:string,is_sys_path?: number) { - if (context===null || context===undefined) { + public save(token, context?: string, filePath?: string, is_sys_path?: number) { + if (context === null || context === undefined) { return; } - const sysPath = is_sys_path===1?`/${decodeURIComponent(filePath)}`:path.join(settingService.getFileRootPath(token),filePath?decodeURIComponent(filePath):""); + const sysPath = is_sys_path === 1 ? `/${decodeURIComponent(filePath)}` : path.join(settingService.getFileRootPath(token), filePath ? decodeURIComponent(filePath) : ""); // const sysPath = path.join(settingService.getFileRootPath(token),filePath?decodeURIComponent(filePath):""); // 写入文件 @@ -163,80 +194,80 @@ class FileService extends FileCompress{ // fs.writeFileSync(path, context); // } - public common_base64_save(token:string,filepath:string,base64_context:string,type:base64UploadType) { - const sysPath = path.join(settingService.getFileRootPath(token),decodeURIComponent(filepath)); + public common_base64_save(token: string, filepath: string, base64_context: string, type: base64UploadType) { + const sysPath = path.join(settingService.getFileRootPath(token), decodeURIComponent(filepath)); const binaryData = Buffer.from(base64_context, 'base64'); if (type === base64UploadType.all || type === base64UploadType.start) { fs.writeFileSync(sysPath, binaryData); - } else if (type === base64UploadType.part){ + } else if (type === base64UploadType.part) { fs.appendFileSync(sysPath, binaryData); } } - public cut(token,data?: cutCopyReq) { + public cut(token, data?: cutCopyReq) { if (!data) { return; } const root_path = settingService.getFileRootPath(token); const sysPath = path.join(root_path); - const toSysPath = path.join(root_path,data.to?decodeURIComponent(data.to):""); + const toSysPath = path.join(root_path, data.to ? decodeURIComponent(data.to) : ""); for (const file of data.files) { const filePath = decodeURIComponent(path.join(sysPath, file)); - fs.renameSync(filePath,decodeURIComponent(path.join(toSysPath,path.basename(file)))); + fs.renameSync(filePath, decodeURIComponent(path.join(toSysPath, path.basename(file)))); rimraf(filePath); } } - public async copy(token,data?: cutCopyReq) { + public async copy(token, data?: cutCopyReq) { if (!data) { return; } const root_path = settingService.getFileRootPath(token); const sysPath = path.join(root_path); - const toSysPath = path.join(root_path,data.to?decodeURIComponent(data.to):""); + const toSysPath = path.join(root_path, data.to ? decodeURIComponent(data.to) : ""); for (const file of data.files) { const filePath = decodeURIComponent(path.join(sysPath, file)); // 覆盖 - await fse.copy(filePath,decodeURIComponent(path.join(toSysPath,path.basename(file))),{overwrite: true}); + await fse.copy(filePath, decodeURIComponent(path.join(toSysPath, path.basename(file))), {overwrite: true}); } } - public async newFile(index,data?: fileInfoReq) { - await this.todoNew(index,2,data) + public async newFile(index, data?: fileInfoReq) { + await this.todoNew(index, 2, data) } - public async newDir(index,data?: fileInfoReq) { - await this.todoNew(index,1,data) + public async newDir(index, data?: fileInfoReq) { + await this.todoNew(index, 1, data) } - public async todoNew(index,type,data?: fileInfoReq) { - if (data===null || data===undefined) { + public async todoNew(index, type, data?: fileInfoReq) { + if (data === null || data === undefined) { return } - const sysPath = path.join(settingService.getFileRootPath(index),decodeURIComponent(data.name)); + const sysPath = path.join(settingService.getFileRootPath(index), decodeURIComponent(data.name)); if (fs.existsSync(sysPath)) { return; } - if (type===1) { + if (type === 1) { // 创建目录 - fs.mkdirSync(sysPath, { recursive: true }); + fs.mkdirSync(sysPath, {recursive: true}); } else { - fs.writeFileSync(sysPath,data.context ?? ""); + fs.writeFileSync(sysPath, data.context ?? ""); } settingService.protectionInit(); } - public async rename(token,data?: fileInfoReq) { + public async rename(token, data?: fileInfoReq) { if (!data) { return; } const root_path = settingService.getFileRootPath(token); - const sysPath = path.join(root_path,decodeURIComponent(data.name)); - const sysPathNew = path.join(root_path,decodeURIComponent(data.newName)); - await fse.rename(sysPath,sysPathNew); + const sysPath = path.join(root_path, decodeURIComponent(data.name)); + const sysPathNew = path.join(root_path, decodeURIComponent(data.newName)); + await fse.rename(sysPath, sysPathNew); } @@ -249,7 +280,7 @@ class FileService extends FileCompress{ const token = ctx.query['token']; if (!Array.isArray(file)) { - const sysPath = path.join(settingService.getFileRootPath(token),decodeURIComponent(file)); + const sysPath = path.join(settingService.getFileRootPath(token), decodeURIComponent(file)); const fileName = path.basename(sysPath) const stats = fs.statSync(sysPath); ctx.res.set('Content-Type', mime.lookup(fileName) || 'application/octet-stream'); @@ -263,23 +294,23 @@ class FileService extends FileCompress{ readStream.pipe(ctx.res); // ctx.res.body = fs.createReadStream(sysPath); } else { - ctx.res.attachment(fileName+".zip"); + ctx.res.attachment(fileName + ".zip"); const archive = archiver('zip', { - zlib: { level: 5 } // 设置压缩级别 + zlib: {level: 5} // 设置压缩级别 }); // const stream = new Stream.PassThrough() // stream.pipe(ctx.res) // ctx.res.body = stream // 将压缩后的文件流发送给客户端 archive.pipe(ctx.res); - archive.directory(sysPath,path.basename(sysPath)); + archive.directory(sysPath, path.basename(sysPath)); archive.finalize(); } } else { - const files:string [] = file; + const files: string [] = file; const archive = archiver('zip', { - zlib: { level: 5 } // 设置压缩级别 + zlib: {level: 5} // 设置压缩级别 }); ctx.res.attachment("output.zip"); ctx.res.set('Content-Type', 'application/octet-stream'); @@ -288,45 +319,45 @@ class FileService extends FileCompress{ // const stream = new Stream.PassThrough() // ctx.res.body = stream // 将压缩后的文件流发送给客户端 - archive.pipe( ctx.res) + archive.pipe(ctx.res) for (const file of files) { - const sysPath = path.join(settingService.getFileRootPath(token),decodeURIComponent(file)); + const sysPath = path.join(settingService.getFileRootPath(token), decodeURIComponent(file)); const stats = fs.statSync(sysPath); if (stats.isFile()) { - archive.file(sysPath, { name: path.basename(sysPath) }); + archive.file(sysPath, {name: path.basename(sysPath)}); } else { - archive.directory(sysPath,path.basename(sysPath)); + archive.directory(sysPath, path.basename(sysPath)); } } archive.finalize(); } } - file_video_trans(data:WsData) { + file_video_trans(data: WsData) { const pojo = data.context as FileVideoFormatTransPojo; const wss = data.wss as Wss; const root_path = settingService.getFileRootPath(pojo.token); - const sysPath = path.join(root_path,decodeURIComponent(pojo.source_filename)); - const sysPathNew = path.join(root_path,decodeURIComponent(pojo.to_filename)); + const sysPath = path.join(root_path, decodeURIComponent(pojo.source_filename)); + const sysPathNew = path.join(root_path, decodeURIComponent(pojo.to_filename)); getFfmpeg()(sysPath) .toFormat(pojo.to_format) // .videoCodec('libx264') // .audioCodec('aac') - .on('start', function(commandLine) { + .on('start', function (commandLine) { const result = new WsData(CmdType.file_video_trans_progress); result.context = 0; wss.sendData(result.encode()); }) - .on('progress', function(progress) { + .on('progress', function (progress) { const result = new WsData(CmdType.file_video_trans_progress); result.context = progress.percent.toFixed(0); wss.sendData(result.encode()); }) - .on('error', function(err, stdout, stderr) { + .on('error', function (err, stdout, stderr) { wss.ws.close(); }) - .on('end', function() { + .on('end', function () { const result = new WsData(CmdType.file_video_trans_progress); result.context = 100; wss.sendData(result.encode()); @@ -334,19 +365,19 @@ class FileService extends FileCompress{ .save(sysPathNew); } - async uncompress(data:WsData) { + async uncompress(data: WsData) { const pojo = data.context as FileCompressPojo; const source_file = decodeURIComponent(pojo.source_file); - const tar_dir = decodeURIComponent(pojo.tar_dir??""); + const tar_dir = decodeURIComponent(pojo.tar_dir ?? ""); const directoryPath = path.dirname(source_file); const root_path = settingService.getFileRootPath(pojo.token); - const targetFolder = path.join(root_path,directoryPath,tar_dir); + const targetFolder = path.join(root_path, directoryPath, tar_dir); const wss = data.wss as Wss; - if(tar_dir) { - fs.mkdirSync(path.join(targetFolder), { recursive: true }); + if (tar_dir) { + fs.mkdirSync(path.join(targetFolder), {recursive: true}); } - const sysSourcePath = path.join(root_path,source_file); - const outHanle = (value)=>{ + const sysSourcePath = path.join(root_path, source_file); + const outHanle = (value) => { if (value === -1) { wss.ws.close(); return; @@ -356,14 +387,14 @@ class FileService extends FileCompress{ wss.sendData(result.encode()); }; if (pojo.format === FileCompressType.tar) { - this.unTar(sysSourcePath,targetFolder,outHanle) + this.unTar(sysSourcePath, targetFolder, outHanle) } else if (pojo.format === FileCompressType.zip) { - this.unZip(sysSourcePath,targetFolder,outHanle) - } else if (pojo.format === FileCompressType.gzip) { - this.unTar(sysSourcePath,targetFolder,outHanle,true) + this.unZip(sysSourcePath, targetFolder, outHanle) + } else if (pojo.format === FileCompressType.gzip) { + this.unTar(sysSourcePath, targetFolder, outHanle, true) } else if (pojo.format === FileCompressType.rar) { try { - await this.unRar(sysSourcePath,targetFolder,outHanle) + await this.unRar(sysSourcePath, targetFolder, outHanle) } catch (e) { wss.ws.close(); } @@ -372,14 +403,14 @@ class FileService extends FileCompress{ } } - FileCompress(data:WsData) { + FileCompress(data: WsData) { const pojo = data.context as FileCompressPojo; const files = pojo.filePaths; const root_path = settingService.getFileRootPath(pojo.token); const wss = data.wss as Wss; - const filePaths:string[] = [],directorys:string[]= []; + const filePaths: string[] = [], directorys: string[] = []; for (const file of files) { - const name = path.join(root_path,decodeURIComponent(file)); + const name = path.join(root_path, decodeURIComponent(file)); try { const stats = fs.statSync(name); if (stats.isFile()) { @@ -398,8 +429,8 @@ class FileService extends FileCompress{ default: format = pojo.format; } - const targerFilePath = path.join(root_path,decodeURIComponent(pojo.tar_filename)); - this.compress(format,pojo.compress_level,targerFilePath,filePaths,directorys,(value)=>{ + const targerFilePath = path.join(root_path, decodeURIComponent(pojo.tar_filename)); + this.compress(format, pojo.compress_level, targerFilePath, filePaths, directorys, (value) => { if (value === -1) { wss.ws.close(); return; @@ -407,10 +438,10 @@ class FileService extends FileCompress{ const result = new WsData(CmdType.file_compress_progress); result.context = value; wss.sendData(result.encode()); - },pojo.format === FileCompressType.gzip); + }, pojo.format === FileCompressType.gzip); } - getTotalFile(data:{files:string[],total:number},filepath:string) { + getTotalFile(data: { files: string[], total: number }, filepath: string) { try { const stats = fs.statSync(filepath); if (stats.isFile()) { @@ -424,42 +455,42 @@ class FileService extends FileCompress{ const items = fs.readdirSync(filepath);// 读取目录内容 for (const item of items) { const p = path.join(filepath, item); - this.getTotalFile(data,p); + this.getTotalFile(data, p); } } - async studio_get_item(param_path:string,token:string) { - const result:{list:FileTreeList} = { - list:[] + async studio_get_item(param_path: string, token: string) { + const result: { list: FileTreeList } = { + list: [] }; - const sysPath = path.join(settingService.getFileRootPath(token),param_path?decodeURIComponent(param_path):""); + const sysPath = path.join(settingService.getFileRootPath(token), param_path ? decodeURIComponent(param_path) : ""); if (!fs.existsSync(sysPath)) { - return Fail("路径不存在",RCode.Fail); + return Fail("路径不存在", RCode.Fail); } const stats = fs.statSync(sysPath); if (stats.isFile()) { - return Fail("是文件",RCode.Fail); + return Fail("是文件", RCode.Fail); } const items = fs.readdirSync(sysPath);// 读取目录内容 for (const item of items) { const filePath = path.join(sysPath, item); // 获取文件或文件夹的元信息 - let stats: null| Stats= null; + let stats: null | Stats = null; try { stats = fs.statSync(filePath); } catch (e) { continue; } result.list.push({ - type:stats.isFile()?"file":"folder", - name:item + type: stats.isFile() ? "file" : "folder", + name: item }) } return result; } public async getDiskSizeForPath(fpath) { - const pojo:FileInfoItemData = {}; + const pojo: FileInfoItemData = {}; try { // 获取磁盘信息 const diskData = await si.fsSize(); @@ -472,7 +503,7 @@ class FileService extends FileCompress{ targetDisk = disk; return; } else { - if(disk.mount.length > targetDisk.mount.length) { + if (disk.mount.length > targetDisk.mount.length) { targetDisk = disk; } } @@ -491,7 +522,7 @@ class FileService extends FileCompress{ console.error('Error fetching disk information:', error); return pojo; } - return pojo; + return pojo; } } diff --git a/src/main/domain/ssh/ssh.controller.ts b/src/main/domain/ssh/ssh.controller.ts index 14e48727..732f8067 100644 --- a/src/main/domain/ssh/ssh.controller.ts +++ b/src/main/domain/ssh/ssh.controller.ts @@ -12,13 +12,14 @@ import { Param, Post, Put, - Req, + Req, Res, UploadedFile } from "routing-controllers"; import {Sucess} from "../../other/Result"; import {NavIndexItem} from "../../../common/req/common.pojo"; import {DataUtil} from "../data/DataUtil"; import multer from "multer"; +import {Request, Response} from "express"; @JsonController("/ssh") export class SSHController { @@ -81,8 +82,8 @@ export class SSHController { // 上传文件 @Put("/") - async uploadFile(@Req() ctx: any ,@UploadedFile('file') file?: multer.File) { - await sshService.uploadFile(ctx, file); + async uploadFile(@Req() req: Request, @Res() res: Response,) { + await sshService.uploadFile(req, res); return Sucess("1"); } @@ -92,6 +93,7 @@ export class SSHController { sshService.open(data); return "" } + @msg(CmdType.remote_shell_send) async send(data: WsData) { sshService.send(data); diff --git a/src/main/domain/ssh/ssh.service.ts b/src/main/domain/ssh/ssh.service.ts index c986ce31..2cc2e9bd 100644 --- a/src/main/domain/ssh/ssh.service.ts +++ b/src/main/domain/ssh/ssh.service.ts @@ -12,10 +12,12 @@ import {Env} from "../../../common/Env"; import fs from "fs"; import archiver from "archiver"; import Stream from "node:stream"; -import multer from "multer"; import {DataUtil} from "../data/DataUtil"; import {Fail, Sucess} from "../../other/Result"; import {RCode} from "../../../common/Result.pojo"; +import {settingService} from "../setting/setting.service"; +import multer from 'multer'; +import {Request, Response} from "express"; export const navindex_remote_ssh_key = "navindex_remote_ssh_key"; @@ -221,58 +223,77 @@ export class SshService extends SshSsh2 { readStream.pipe(ctx.res); } - public uploadFile(ctx, file: multer.File) { - const client = this.lifeGetData(SshPojo.getKey(ctx.query)) as Client; - const sftp = this.sftGet(client); - const remoteFilePath = ctx.query.target; - - const temp = "tempfile"; + fileUploadOptions = { + storage: multer.diskStorage({ + destination: (req: any, file: any, cb: any) => { + // return cb(new Error("Custom error: Path issue")); + cb(null, req.fileDir); // 存储路径 + }, + filename: (req: any, file: any, cb: any) => { + // file.originalname + cb(null, req.fileName); + } + }) + }; - const localFilePath = DataUtil.writeFileSyncTemp(path.basename(remoteFilePath),temp,file.buffer); + upload = multer({ + storage: this.fileUploadOptions.storage, + // limits: { fileSize: 1024 * 1024 * 2 }, // 限制文件大小为 2MB 无限制 + }).single('file'); + public uploadFileAsync(req: Request, res: Response): Promise { + return new Promise((resolve, reject) => { + this.upload(req, res, (err) => { + if (err) { + return reject(err); // 如果出错,拒绝 Promise + } + resolve(); // 如果上传成功,解析 Promise + }); + }); + } - const readStream = fs.createReadStream(localFilePath); - const writeStream = sftp.createWriteStream(remoteFilePath); - // const stats = fs.statSync(localFilePath); - // const totalSize = stats.size; - // let uploadedSize = 0; + public async uploadFile(req:any,res:Response) { + const client = this.lifeGetData(SshPojo.getKey(req.query)) as Client; + const sftp = this.sftGet(client); + const remoteFilePath = req.query.target; - readStream.on('data', (chunk) => { - // uploadedSize += chunk.length; - // const percent = Math.floor((uploadedSize / totalSize) * 100); - // console.log(`Progress: ${percent}%`); - }); + const temp = "tempfile"; + const localFilePath = DataUtil.writeFileSyncTemp(path.basename(remoteFilePath),temp); + req['fileDir'] = path.dirname(localFilePath); + req['fileName'] = path.basename(localFilePath); + await this.uploadFileAsync(req, res); return new Promise((resolve, reject) => { - readStream.pipe(writeStream); - writeStream.on('close', () => { - resolve(1); - readStream.close(); + // 上传文件 + sftp.fastPut(localFilePath, remoteFilePath, (err) => { // 比下面的更快 + if (err) { + reject(err); + } else { + resolve(1); + } fs.unlinkSync(localFilePath); }); - writeStream.on('error', (err) => { - reject(err); - }); - }) + }); + // + // const readStream = fs.createReadStream(localFilePath); + // const writeStream = sftp.createWriteStream(remoteFilePath); + // readStream.on('data', (chunk) => { + // // 这种方式可以检测到进度 但是会慢点 fastPut 会并发 上传占用的内存都差不多 + // // uploadedSize += chunk.length; + // // const percent = Math.floor((uploadedSize / totalSize) * 100); + // // console.log(`Progress: ${percent}%`); + // }); // return new Promise((resolve, reject) => { - // sftp.fastPut(localFilePath, remoteFilePath, (err) => { - // if (err) { - // reject(err); - // } else { - // resolve(1); - // } + // readStream.pipe(writeStream); + // writeStream.on('close', () => { + // resolve(1); + // readStream.close(); + // fs.unlinkSync(localFilePath); + // }); + // writeStream.on('error', (err) => { + // reject(err); // }); // }) - // const writeStream = sftp.createWriteStream(remoteFilePath); - // // 将流数据写入远程文件 - // // 将文件数据转换为可读流 - // const fileStream = Readable.from(file.buffer); - // fileStream.pipe(writeStream); - - // sftp.fastPut(file.buffer, remoteFilePath, (err) => { - // if (err) throw err; - // }); - } } diff --git a/src/main/domain/ssh/ssh.ssh2.ts b/src/main/domain/ssh/ssh.ssh2.ts index 22ac1dc1..4d7ee17b 100644 --- a/src/main/domain/ssh/ssh.ssh2.ts +++ b/src/main/domain/ssh/ssh.ssh2.ts @@ -50,11 +50,11 @@ export class SshSsh2 extends LifecycleRecordService { resolve(false); console.log("ssh关闭") this.lifeClose(SshPojo.getKey(req)); - try { - client[sftp_client].end(); - } catch (e) { - console.log(e); - } + // try { + // client[sftp_client].end(); + // } catch (e) { + // console.log(e); + // } }); }) } diff --git a/src/main/domain/sys/sys.process.service.ts b/src/main/domain/sys/sys.process.service.ts index 77e87a19..aa4f0b41 100644 --- a/src/main/domain/sys/sys.process.service.ts +++ b/src/main/domain/sys/sys.process.service.ts @@ -148,11 +148,12 @@ export class SysProcessService { return; } const sys = getSys(); - if (sys === SysEnum.linux || sys == SysEnum.win) { - this.winAndLinuxGetProcess(); - } else { - this.linuxGetProcess(); - } + this.winAndLinuxGetProcess(); + // if (sys === SysEnum.linux || sys == SysEnum.win) { + // this.winAndLinuxGetProcess(); + // } else { + // this.linuxGetProcess(); + // } } catch (e) { console.log(e) this.clear(); diff --git a/src/main/server.ts b/src/main/server.ts index ab767fe6..631e25f3 100644 --- a/src/main/server.ts +++ b/src/main/server.ts @@ -33,7 +33,7 @@ const WebSocket = require('ws'); async function start() { await Env.parseArgs(); - + // console.log(process.pid); // 环境变量加载 // dotenv.config({ override: true }); // Object.keys(config).forEach(key => { diff --git a/src/web/project/component/file/FileItem.tsx b/src/web/project/component/file/FileItem.tsx index 1915a173..a4ed6ee4 100644 --- a/src/web/project/component/file/FileItem.tsx +++ b/src/web/project/component/file/FileItem.tsx @@ -3,7 +3,7 @@ import {FileItemData, FileTypeEnum} from "../../../../common/file.pojo"; import {useRecoilState} from "recoil"; import {$stroe} from "../../util/store"; import {useLocation, useNavigate} from "react-router-dom"; -import {getByList, getNewDeleteByList, webPathJoin} from "../../../../common/ListUtil"; +import {getByList, getMaxByList, getNewDeleteByList, webPathJoin} from "../../../../common/ListUtil"; import {fileHttp} from "../../util/config"; import {getRouterAfter} from "../../util/WebPath"; import {saveTxtReq} from "../../../../common/req/file.req"; @@ -21,6 +21,7 @@ import {user_click_file} from "../../util/store.util"; export function FileItem(props: FileItemData & { index?: number, itemWidth?: string }) { const [selectList, setSelectList] = useRecoilState($stroe.selectedFileList); const [clickList, setClickList] = useRecoilState($stroe.clickFileList); + const [enterKey,setEnterKey] = useRecoilState($stroe.enterKey); const { click_file } = user_click_file(); const {t} = useTranslation(); @@ -34,8 +35,25 @@ export function FileItem(props: FileItemData & { index?: number, itemWidth?: str setSelectList(getNewDeleteByList(selectList, index)) // console.log('取消') } else { - // @ts-ignore 选中 - setSelectList([...selectList, index]) + if (enterKey ==="ctrl") { + // @ts-ignore 选中 + setSelectList([...selectList, index]) + } else if(enterKey ==="shift") { + const {max,min} = getMaxByList(selectList); + const list:number[] = []; + if(index >= max) { + for(let i=max; i<=index; i++) { + list.push(i); + } + } else { + for(let i=min; i >= index; i--) { + list.push(i); + } + } + setSelectList(list); + } else { + setSelectList([index]) + } } // @ts-ignore 点击 diff --git a/src/web/project/component/file/FileList.tsx b/src/web/project/component/file/FileList.tsx index adb0a3c0..1749c160 100644 --- a/src/web/project/component/file/FileList.tsx +++ b/src/web/project/component/file/FileList.tsx @@ -7,7 +7,7 @@ import {fileHttp} from "../../util/config"; import {useLocation, useNavigate} from "react-router-dom"; import {ActionButton} from "../../../meta/component/Button"; import Header from "../../../meta/component/Header"; -import {getNextByLoop} from "../../../../common/ListUtil"; +import {getNewDeleteByList, getNextByLoop} from "../../../../common/ListUtil"; import {scanFiles} from "../../util/file"; import {PromptEnum} from "../prompts/Prompt"; import {getRouterAfter} from "../../util/WebPath"; @@ -53,6 +53,7 @@ export default function FileList() { const [showPrompt, setShowPrompt] = useRecoilState($stroe.showPrompt); const [selectedFile, setSelectedFile] = useRecoilState($stroe.selectedFileList); const [copyedFileList,setCopyedFileList] = useRecoilState($stroe.copyedFileList); + const [enterKey,setEnterKey] = useRecoilState($stroe.enterKey); const [cutedFileList,setCutedFileList] = useRecoilState($stroe.cutedFileList); const [selectList, setSelectList] = useRecoilState($stroe.selectedFileList); const [clickList, setClickList] = useRecoilState($stroe.clickFileList); @@ -269,8 +270,51 @@ export default function FileList() { } + // 快捷键 + useEffect(() => { + const handleKeyDown = (event) => { + if(!event.ctrlKey) { + if(event.key === 'Escape') { + setSelectList([]) + } else if(event.key === 'Shift') { + setEnterKey("shift") + } + return; + } + if(event.key === 'a' || event.key === 'A') { + const len = nowFileList.files.length; + const len2 = nowFileList.folders.length; + const list = []; + for (let i= 0; i < len+len2; i++) { + list.push(i); + } + setSelectList(list) + } else { + setEnterKey("ctrl") + } + }; + const handleKeyUp = (event) => { + if (!event.ctrlKey) { + setEnterKey(""); + } + }; + // 添加全局键盘事件监听 + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + // 在组件卸载时移除事件监听 + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + }; + }, [nowFileList]); + const clickBlank = (event) => { + if (event.target === event.currentTarget) { + setSelectList([]) + } + } + return ( -
+
{setSearch(v)}} max_width={"25em"}/> }> {/**/} {selectedFile.length > 0 && { @@ -299,12 +343,12 @@ export default function FileList() { {(nowFileList.folders && nowFileList.folders.length > 0) &&

{t("文件夹")}

} {(nowFileList.folders) && // @ts-ignore - (
{nowFileList.folders.map((v, index) => ())}
) + (
{nowFileList.folders.map((v, index) => ())}
) } - {(nowFileList.files && nowFileList.files.length > 0) &&

{t("文件")}

} + {(nowFileList.files && nowFileList.files.length > 0) &&

{t("文件")}

} {(nowFileList.files) && // @ts-ignore - (
+ (
{nowFileList.files.map((v, index) => ( // @ts-ignore ))} diff --git a/src/web/project/component/prompts/FilesUpload.tsx b/src/web/project/component/prompts/FilesUpload.tsx index 3cb9552e..d71cddc2 100644 --- a/src/web/project/component/prompts/FilesUpload.tsx +++ b/src/web/project/component/prompts/FilesUpload.tsx @@ -7,6 +7,7 @@ import {fileHttp} from "../../util/config"; import {getNewDeleteByList} from "../../../../common/ListUtil"; import {getRouterAfter} from "../../util/WebPath"; import {useTranslation} from "react-i18next"; +import {NotyFail} from "../../util/noty"; export function FilesUpload() { const { t } = useTranslation(); @@ -28,25 +29,31 @@ export function FilesUpload() { const newList: any = Array.from(uploadFiles); for (let index = 0; index < newList.length; ) { let value: any = newList[index]; - // console.log(`${getRouterAfter('file',location.pathname)}${value.fullPath}`) - const rsp = await fileHttp.put(`${getRouterAfter('file',location.pathname)}${value.fullPath}`, value, (progressEvent) => { - const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); - setNowProgress({ - name: value.name, - value: percentCompleted, - index: index + try { + // console.log(`${getRouterAfter('file',location.pathname)}${value.fullPath}`) + const rsp = await fileHttp.put(`${getRouterAfter('file',location.pathname)}${value.fullPath}`, value, (progressEvent) => { + const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); + setNowProgress({ + name: value.name, + value: percentCompleted, + index: index + }) }) - }) - if (rsp.code === 0) { - // @ts-ignore - // setUploadFiles(getNewDeleteByList(newList, value)) + if (rsp.code === 0) { + // @ts-ignore + // setUploadFiles(getNewDeleteByList(newList, value)) + } + if (index === newList.length - 1) { + navigate(location.pathname); + setUploadFiles([]) + setShowPrompt({show: false,type: '',overlay: false,data:{}}) + } + index++; + } catch (e) { + NotyFail(`上传:${value.name} 文件出错!`) + return; } - if (index === newList.length - 1) { - navigate(location.pathname); - setUploadFiles([]) - setShowPrompt({show: false,type: '',overlay: false,data:{}}) - } - index++; + } // console.log(uploadFiles) })(); diff --git a/src/web/project/component/toolbox/remotelinux/RemoteLinuxFileItem.tsx b/src/web/project/component/toolbox/remotelinux/RemoteLinuxFileItem.tsx index f45415d6..137f50e9 100644 --- a/src/web/project/component/toolbox/remotelinux/RemoteLinuxFileItem.tsx +++ b/src/web/project/component/toolbox/remotelinux/RemoteLinuxFileItem.tsx @@ -3,7 +3,7 @@ import {FileItemData, FileTypeEnum} from "../../../../../common/file.pojo"; import {useRecoilState} from "recoil"; import {$stroe} from "../../../util/store"; import {useLocation, useMatch, useNavigate} from "react-router-dom"; -import {getByList, getNewDeleteByList, joinPaths, webPathJoin} from "../../../../../common/ListUtil"; +import {getByList, getMaxByList, getNewDeleteByList, joinPaths, webPathJoin} from "../../../../../common/ListUtil"; import {fileHttp, sshHttp} from "../../../util/config"; import {getRouterAfter} from "../../../util/WebPath"; import Noty from "noty"; @@ -26,7 +26,7 @@ export function RemoteLinuxFileItem(props: FileItemData & { index?: number,itemW const [sshInfo,setSSHInfo] = useRecoilState($stroe.sshInfo); const [nowFileList, setNowFileList] = useRecoilState($stroe.nowFileList); const [shellShow,setShellShow] = useRecoilState($stroe.remoteShellShow); - + const [enterKey,setEnterKey] = useRecoilState($stroe.enterKey); // const match = useMatch('/:pre/file/*'); const clickHandler = async (index, name) => { @@ -36,8 +36,25 @@ export function RemoteLinuxFileItem(props: FileItemData & { index?: number,itemW setSelectList(getNewDeleteByList(selectList, index)) // console.log('取消') } else { - // @ts-ignore 选中 - setSelectList([...selectList, index]) + if (enterKey ==="ctrl") { + // @ts-ignore 选中 + setSelectList([...selectList, index]) + } else if(enterKey ==="shift") { + const {max,min} = getMaxByList(selectList); + const list:number[] = []; + if(index >= max) { + for(let i=max; i<=index; i++) { + list.push(i); + } + } else { + for(let i=min; i >= index; i--) { + list.push(i); + } + } + setSelectList(list); + } else { + setSelectList([index]) + } } // @ts-ignore 点击 diff --git a/src/web/project/component/toolbox/remotelinux/RemoteLinuxFileList.tsx b/src/web/project/component/toolbox/remotelinux/RemoteLinuxFileList.tsx index 18151d83..bcce3b36 100644 --- a/src/web/project/component/toolbox/remotelinux/RemoteLinuxFileList.tsx +++ b/src/web/project/component/toolbox/remotelinux/RemoteLinuxFileList.tsx @@ -59,6 +59,7 @@ export function RemoteLinuxFileList(props: RemoteLinuxFileListProps) { const [uploadFiles, setUploadFiles] = useRecoilState($stroe.uploadFiles); const [showPrompt, setShowPrompt] = useRecoilState($stroe.showPrompt); const [selectedFile, setSelectedFile] = useRecoilState($stroe.selectedFileList); + const [enterKey,setEnterKey] = useRecoilState($stroe.enterKey); const [copyedFileList, setCopyedFileList] = useRecoilState($stroe.copyedFileList); const [cutedFileList, setCutedFileList] = useRecoilState($stroe.cutedFileList); const [selectList, setSelectList] = useRecoilState($stroe.selectedFileList); @@ -257,6 +258,50 @@ export function RemoteLinuxFileList(props: RemoteLinuxFileListProps) { setNowFileList({files, folders}); } + + // 快捷键 + useEffect(() => { + const handleKeyDown = (event) => { + if(!event.ctrlKey) { + if(event.key === 'Escape') { + setSelectList([]) + } else if(event.key === 'Shift') { + setEnterKey("shift") + } + return; + } + if(event.key === 'a' || event.key === 'A') { + const len = nowFileList.files.length; + const len2 = nowFileList.folders.length; + const list = []; + for (let i= 0; i < len+len2; i++) { + list.push(i); + } + setSelectList(list) + } else { + setEnterKey("ctrl") + } + }; + const handleKeyUp = (event) => { + if (!event.ctrlKey) { + setEnterKey(""); + } + }; + // 添加全局键盘事件监听 + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + // 在组件卸载时移除事件监听 + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + }; + }, [nowFileList]); + const clickBlank = (event) => { + if (event.target === event.currentTarget) { + setSelectList([]) + } + } + return (
{(!!nowFileList && !!nowFileList.folders && nowFileList.folders.length > 0) &&

文件夹

} {(!!nowFileList && !!nowFileList.folders) && - (
{nowFileList.folders.map((v, index) => ( + (
{nowFileList.folders.map((v, index) => ( // @ts-ignore ))}
) } - {(!!nowFileList && !!nowFileList.folders && nowFileList.files.length > 0) &&

文件

} + {(!!nowFileList && !!nowFileList.folders && nowFileList.files.length > 0) &&

文件

} {(!!nowFileList && !!nowFileList.folders) && // @ts-ignore - (
{nowFileList.files.map((v, index) => ( + (
{nowFileList.files.map((v, index) => ( ))}
) diff --git a/src/web/project/util/store.ts b/src/web/project/util/store.ts index 91887b93..2ab8fa4e 100644 --- a/src/web/project/util/store.ts +++ b/src/web/project/util/store.ts @@ -6,7 +6,7 @@ import {Confirm} from "../component/prompts/Confirm"; import {FileMenuData} from "../../../common/FileMenuType"; import {DiskDevicePojo} from "../../../common/req/sys.pojo"; -const localStorageEffect = key => ({ setSelf, onSet }) => { +const localStorageEffect = key => ({setSelf, onSet}) => { const savedValue = localStorage.getItem(key); if (savedValue != null) { setSelf(JSON.parse(savedValue)); @@ -18,10 +18,10 @@ const localStorageEffect = key => ({ setSelf, onSet }) => { }; export class ShowPromptData { - show:boolean; + show: boolean; type: string; - overlay:boolean; - data:FileMenuData; + overlay: boolean; + data: FileMenuData; } export const $stroe = { @@ -33,6 +33,11 @@ export const $stroe = { files: [{name: "文件1", type: FileTypeEnum.text}, {name: "文件2", type: FileTypeEnum.text}] } as GetFilePojo // 初始值 }), + // 按下的键盘按键 + enterKey: atom({ + key: "enterKey", + default: "" + }), // 选中的文件 下标 selectedFileList: atom({ key: 'selectedFileList', // 唯一标识符,用于区分不同的原子状态 @@ -85,146 +90,146 @@ export const $stroe = { } ), // 通用确认 - confirm:atom({ + confirm: atom({ key: 'confirm', default: { - open:false, - handle:null + open: false, + handle: null } as { - open:boolean, - handle:()=>void, - title?:string, - sub_title?:string, + open: boolean, + handle: () => void, + title?: string, + sub_title?: string, } }), // 通用卡片 - prompt_card:atom({ - key:"prompt_card" , - default:{open:false} as { - context_div?:any, - open:boolean, - title?:string + prompt_card: atom({ + key: "prompt_card", + default: {open: false} as { + context_div?: any, + open: boolean, + title?: string } }), // 编辑器设置 - editorSetting:atom({ + editorSetting: atom({ key: 'editorSetting', - default:{ - model:'text', - open:false, - fileName:'', - save:null, + default: { + model: 'text', + open: false, + fileName: '', + save: null, } as { - menu_list?:any[], - model?:string, - open?:boolean, - fileName?:string, - save?:any, + menu_list?: any[], + model?: string, + open?: boolean, + fileName?: string, + save?: any, } }), // shell是否开启 并传递初始目录 - fileShellShow:atom({ - key:"shellShow", - default:{ - show:false, - path:'' + fileShellShow: atom({ + key: "shellShow", + default: { + show: false, + path: '' } }), // 只是隐藏不消失 - file_shell_hidden:atom({ + file_shell_hidden: atom({ key: 'file_shell_hidden', - default:undefined + default: undefined }), // 远程shell是否开启 - remoteShellShow:atom({ - key:"remoteShellShow", - default:{ - show:false, - path:'' + remoteShellShow: atom({ + key: "remoteShellShow", + default: { + show: false, + path: '' } }), // docker 的shell是否开启 - dockerShellShow:atom({ + dockerShellShow: atom({ key: 'dockerShellShow', - default:{ - type:"", // print exec - show:false, - dockerId:"" + default: { + type: "", // print exec + show: false, + dockerId: "" } }), // systemd 的shell是否开启 - systemd_shell_show:atom({ + systemd_shell_show: atom({ key: 'systemd_shell_show', - default:{ - show:false, - unit_name:"" + default: { + show: false, + unit_name: "" } }), // ssh工具连接信息 - sshInfo:atom({ + sshInfo: atom({ key: 'sshInfo', - default:{} + default: {} }), // 文件根路径主 - file_root_index:atom({ + file_root_index: atom({ key: 'file_root_index', - default:null, - effects:[ + default: null, + effects: [ localStorageEffect("file_root_index") ] }), // root根路径 - file_root_list:atom({ + file_root_list: atom({ key: 'file_root_list', - default:[], - effects:[ + default: [], + effects: [ localStorageEffect("file_root_list") ] }), // 用户基本信息 - user_base_info:atom({ + user_base_info: atom({ key: 'user_base_info', - default:{} as UserBaseInfo, - effects:[ + default: {} as UserBaseInfo, + effects: [ localStorageEffect("user_base_info") ] }), // 头部菜单状态 - header_min:atom({ + header_min: atom({ key: 'header_min', - default:false + default: false }), // 文件预览 - file_preview:atom({ + file_preview: atom({ key: 'file_preview', - default:{open:false} as {open:boolean,type?:FileTypeEnum,name?:string,url?:string,context?:string}, + default: {open: false} as { open: boolean, type?: FileTypeEnum, name?: string, url?: string, context?: string }, }), // md预览 - markdown:atom({ + markdown: atom({ key: 'markdown', - default:{} as {filename?:string,context?:string}, + default: {} as { filename?: string, context?: string }, }), // 编辑器 - studio:atom({ + studio: atom({ key: 'studio', - default:{} as {folder_path?:string,name?:string} + default: {} as { folder_path?: string, name?: string } }), // 图片编辑器 - image_editor:atom({ - key:'image_editor', - default:{} as {path?:string,name?:string} + image_editor: atom({ + key: 'image_editor', + default: {} as { path?: string, name?: string } }), // excalidraw编辑器 - excalidraw_editor:atom({ - key:'excalidraw_editor', - default:{} as {path?:string,name?:string} + excalidraw_editor: atom({ + key: 'excalidraw_editor', + default: {} as { path?: string, name?: string } }), // 磁盘 - disk:atom({ - key:"disk", + disk: atom({ + key: "disk", default: { - type:"" - } as {type?:string,data?:DiskDevicePojo} + type: "" + } as { type?: string, data?: DiskDevicePojo } }) }