Compare commits

..

32 Commits

Author SHA1 Message Date
616ab06f0e 后端 2025-12-30 00:56:14 +08:00
49a8256638 1 2025-12-29 01:19:33 +08:00
29cca0e4dd 保存进度! 2025-12-26 21:06:55 +08:00
5c410e3837 保存进度! 2025-12-25 23:37:43 +08:00
534d7c4d01 xxx 2025-12-01 18:42:37 +08:00
2ae751bc4c 保存进度! 2025-12-01 12:26:55 +08:00
cdc0f5b51b 保存进度 2025-11-30 23:32:12 +08:00
8e97044faa 保存进度 2025-11-30 14:59:27 +08:00
835e812d4e 保存进度! 2025-11-27 18:18:22 +08:00
bf6e78b423 保存进度 2025-11-26 09:11:13 +08:00
88ba348242 保存进度 2025-11-24 21:43:08 +08:00
51cf0da474 保存进度 2025-11-24 21:40:18 +08:00
de68525230 保存进度 2025-11-24 21:33:33 +08:00
207e5aeb0c 保存进度 2025-11-24 21:30:20 +08:00
91adce0fef 保存进度 2025-11-23 18:06:00 +08:00
1e20eeb0d1 保存进度! 2025-11-21 18:01:17 +08:00
d22b29f45d baocun 2025-11-20 00:21:22 +08:00
c9c2493671 保存进度 2025-11-19 01:13:28 +08:00
e73ce4b4b0 保存进度! 2025-11-18 19:37:20 +08:00
9e1fdcdd6a 保存进度! 2025-11-18 15:30:57 +08:00
de0b396bcb Merge branch 'feature-制度管理界面' of http://222.76.244.118:3000/CPICXIM/regulatory-management-system into feature-制度管理界面 2025-11-17 19:01:01 +08:00
5d7aee0e7c 保存进度! 2025-11-17 19:00:39 +08:00
f2bc8d14ab 保存进度 2025-11-15 22:54:57 +08:00
1b40137cf9 修改接受文件的控制器 2025-11-13 15:48:36 +08:00
524ea68d89 保存进度! 2025-11-12 20:13:56 +08:00
32b40f700d 11 2025-11-07 18:11:49 +08:00
b04152c5c5 。。。 2025-11-05 18:02:00 +08:00
af3442f276 保存进度! 2025-10-31 19:08:12 +08:00
390a5efbb8 编写上传界面 2025-10-30 18:19:51 +08:00
72a4895328 保存进度! 2025-10-30 16:02:54 +08:00
ef3b31c9e4 编写制度管理。 2025-10-28 23:51:14 +08:00
a11c6ada3d 编写制度文件管理view 2025-10-24 18:07:49 +08:00
50 changed files with 2631 additions and 541 deletions

View File

@@ -1,4 +1,4 @@
{ {
"java.configuration.updateBuildConfiguration": "interactive", "java.configuration.updateBuildConfiguration": "automatic",
"java.compile.nullAnalysis.mode": "automatic" "java.compile.nullAnalysis.mode": "automatic"
} }

View File

@@ -16,11 +16,11 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>18</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>18</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
<spring.version>6.2.11</spring.version> <spring.version>6.2.11</spring.version>
<log4j.version>2.24.3</log4j.version> <log4j.version>2.25.3</log4j.version>
<jackson.version>2.18.3</jackson.version> <jackson.version>2.20.1</jackson.version>
</properties> </properties>
<dependencies> <dependencies>
@@ -76,6 +76,7 @@
<artifactId>log4j-api</artifactId> <artifactId>log4j-api</artifactId>
<version>${log4j.version}</version> <version>${log4j.version}</version>
</dependency> </dependency>
<!-- jackson --> <!-- jackson -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
@@ -90,7 +91,7 @@
<dependency> <dependency>
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId> <artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version> <version>2.20</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -107,7 +108,7 @@
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>2.18.0</version> <version>2.21.0</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -0,0 +1,178 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-16 09:46:42
* @LastEditors: Kane Wang
* @LastModified: 2025-12-01 18:42:31
* @FilePath: src/main/java/com/cpic/xim/utils/files/SaveUploadFile.java
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.utils.files;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Vector;
import org.springframework.web.multipart.MultipartFile;
import com.cpic.xim.utils.files.exceptions.MoveUploadedFileException;
import com.cpic.xim.utils.files.exceptions.ProcessUploadedFileException;
public class SaveUploadFile
{
/**
*
* @param files
* @param tempFilePath
* @return
* @throws ProcessUploadedFileException
*/
public static HashMap<String, String> saveUploadFile(
MultipartFile[] files,
String tempFilePath
)
throws ProcessUploadedFileException
{
HashMap<String, String> savedFiles = new HashMap<>();
// File dir = new File( tempFilePath );
String fileName = "";
String fullPath;
if ( !(tempFilePath.endsWith( "/" ) || tempFilePath.endsWith( "\\" )) )
{
fullPath = tempFilePath + "/";
}
else
{
fullPath = tempFilePath;
}
try
{
for ( MultipartFile file : files )
{
// 空文件跳过
if ( file.isEmpty() )
{
continue;
}
// 文件名前加上时间戳,避免覆盖
Long milliSecond = LocalDateTime.now().toInstant( ZoneOffset.of( "+8" ) ).toEpochMilli();
fileName = String.valueOf( milliSecond ) + " - " + file.getOriginalFilename();
File destFile = new File( tempFilePath, fileName );
file.transferTo( destFile );
savedFiles.put( fileName, fullPath + fileName );
}
}
catch ( IOException error )
{
throw new ProcessUploadedFileException( "临时目录" + tempFilePath + "保存文件" + fileName + "失败!" );
}
return savedFiles;
}
/**
* 将files参数中上传的文件保存到tempFilePath指定的路径文件名加上毫秒数避免重复。
*
* @param files
* @param absoluteFilePath
* @return 返回UploadedFile对象数组
* @throws ProcessUploadedFileException
*/
public static Vector<UploadedFile> saveUploadFiles(
MultipartFile[] files,
String absoluteFilePath,
String relativeFilePath
)
throws ProcessUploadedFileException
{
Vector<UploadedFile> savedFiles = new Vector<>();
UploadedFile uploadedFile = null;
// File dir = new File( tempFilePath );
String fileName = "";
String fullPath = null;
if ( !(absoluteFilePath.endsWith( "/" ) || absoluteFilePath.endsWith( "\\" )) )
{
fullPath = absoluteFilePath + "/";
}
else
{
fullPath = absoluteFilePath;
}
try
{
for ( MultipartFile file : files )
{
// 空文件跳过
if ( file.isEmpty() )
{
continue;
}
// 文件名前加上时间戳,避免覆盖
Long milliSecond = LocalDateTime.now().toInstant( ZoneOffset.of( "+8" ) ).toEpochMilli();
fileName = String.valueOf( milliSecond ) + "-" + file.getOriginalFilename();
File destFile = new File( fullPath, fileName );
file.transferTo( destFile );
uploadedFile = new UploadedFile( fileName, fullPath, relativeFilePath );
savedFiles.add( uploadedFile );
}
}
catch ( IOException error )
{
throw new ProcessUploadedFileException( "临时目录" + absoluteFilePath + "保存文件" + fileName + "失败!" );
}
return savedFiles;
}
/**
*
* @param originFilePath 文件路径
* @param newFilePath 新文件存放路径
* @throws MoveUploadedFileException
*/
public static void MoveUploadedFile(
String originFilePath,
String newFilePath
)
throws MoveUploadedFileException
{
// 防御性验证
File originFile = new File( originFilePath );
File dest = new File( newFilePath );
// 文件如果不存在就抛出异常
if ( originFile.exists() == false )
{
throw new MoveUploadedFileException( "文件不存在!" );
}
// 目的路径有同名文件
if ( dest.exists() == true )
{
throw new MoveUploadedFileException( "目的路径已存在同名文件!" );
}
// 不知道具体原因
if ( originFile.renameTo( dest ) == false )
{
throw new MoveUploadedFileException( "移动文件失败!" );
}
}
}

View File

@@ -0,0 +1,76 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-31 17:33:13
* @LastEditors: Kane Wang
* @LastModified: 2025-12-26 21:58:32
* @FilePath: src/main/java/com/cpic/xim/utils/files/UploadedFile.java
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.utils.files;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* 描述上传文件的类。
*
*/
public class UploadedFile
{
@JsonProperty( "fileName" )
private String fileName;
@JsonProperty( "absoluteFilePath" )
private String absoluteFilePath;
@JsonProperty( "relativeFilePath" )
private String relativeFilePath;
public String getFileName()
{
return fileName;
}
public void setFileName( String fileName )
{
this.fileName = fileName;
}
public String getAbsoluteFilePath()
{
return absoluteFilePath;
}
public void setAbsoluteFilePath( String absoluteFilePath )
{
this.absoluteFilePath = absoluteFilePath;
}
public String getRelativeFilePath()
{
return relativeFilePath;
}
public void setRelativeFilePath( String relativeFilePath )
{
this.relativeFilePath = relativeFilePath;
}
public UploadedFile()
{}
public UploadedFile( String fileName, String absoluteFilePath, String relativeFilePath )
{
this.fileName = fileName;
this.absoluteFilePath = absoluteFilePath;
this.relativeFilePath = relativeFilePath;
}
public UploadedFile( String fileName, String absoluteFilePath )
{
this.fileName = fileName;
this.absoluteFilePath = absoluteFilePath;
this.relativeFilePath = "";
}
}

View File

@@ -0,0 +1,26 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-11-13 14:44:42
* @LastEditors: Kane Wang
* @LastModified: 2025-11-13 14:45:56
* @FilePath: src/main/java/com/cpic/xim/utils/files/exceptions/MoveUploadedFileException.java
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.utils.files.exceptions;
import java.io.IOException;
public class MoveUploadedFileException extends IOException
{
public MoveUploadedFileException()
{
super("移动文件失败。");
}
public MoveUploadedFileException( String message )
{
super( message );
}
}

View File

@@ -2,24 +2,24 @@
* @Author: Kane Wang <wangkane@qq.com> * @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-15 12:06:26 * @Date: 2025-10-15 12:06:26
* @LastEditors: Kane Wang * @LastEditors: Kane Wang
* @LastModified: 2025-10-16 09:59:39 * @LastModified: 2025-11-13 10:36:12
* @FilePath: src/main/java/com/cpic/xim/web/controllers/fileupload/ProcessUploadedFileException.java * @FilePath: src/main/java/com/cpic/xim/utils/files/exceptions/ProcessUploadedFileException.java
* @Description: * @Description:
* *
* Copyright (c) 2025 by Kane All rights reserved * Copyright (c) 2025 by Kane All rights reserved
*/ */
package com.cpic.xim.web.controllers.fileupload; package com.cpic.xim.utils.files.exceptions;
import java.io.IOException; import java.io.IOException;
public class ProcessUploadedFileException extends IOException public class ProcessUploadedFileException extends IOException
{ {
ProcessUploadedFileException() public ProcessUploadedFileException()
{ {
super("上传文件失败!"); super("上传文件失败!");
} }
ProcessUploadedFileException( String message ) public ProcessUploadedFileException( String message )
{ {
super(message); super(message);
} }

View File

@@ -1,154 +0,0 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-15 11:42:56
* @LastEditors: Kane Wang
* @LastModified: 2025-10-15 17:48:52
* @FilePath: src/main/java/com/cpic/xim/web/controllers/fileupload/FileUpload.java
* @Description: 用于接受上传文件的Controller。
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.web.controllers.fileupload;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Vector;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
// @SuppressWarnings( "unused" )
@Controller
@RequestMapping( path = "/file" )
public class FileUpload
{
/**
* 接收上传文件,并保存到临时目录:
* 1、临时目录下再用sessionID作为子目录保存文件。
* 2、保存时不更改文件名会覆盖同名文件。
* 3、MultipartFile参数形参名称必须和请求form中file标签的name属性一致否则值为null。
* 4、返回值为接收结果和文件保存绝对路径。
*
* @param taskName 任务名称字符串
* @param files MultipartFile结构的文件对象
* @param request HttpServletRequest对象实例
* @return 返回一个FileUploadResult对象包含上传结果。
*/
// @RequestMapping( path = "/file-upload.do" )
// @ResponseBody
public FileUploadResult getUploadFile(
@RequestParam( "task-name" ) String taskName,
@RequestParam( "files" ) MultipartFile file,
HttpServletRequest request
)
{
// session id用来创建临时目录避免重复
String sessionID = request.getSession().getId();
FileUploadResult result = new FileUploadResult();
Vector<String> fileNames = new Vector<String>();
result.setSuccess( true );
result.setMessage( "上传成功!" );
String filePath = request.getServletContext().getRealPath( "/temp/upload/" + sessionID );
File dir = new File( filePath );
if ( !dir.mkdirs() )
{
}
// 检查文件长度如果为0则跳过
if ( file.isEmpty() )
{
result.setSuccess( false );
result.setMessage( "不允许上传空文件。" );
}
else
{
// 保存文件到临时目录
Long milliSecond = LocalDateTime.now().toInstant( ZoneOffset.of( "+8" ) ).toEpochMilli();
String fileName = String.valueOf( milliSecond ) + file.getOriginalFilename();
File destFile = new File( filePath, fileName );
// String fileName = file.getOriginalFilename();
try
{
file.transferTo( destFile );
// 把上传文件的绝对路径保存,返回给前端
fileNames.add( destFile.getAbsolutePath() );
result.setSuccess( true );
result.setMessage( "上传成功" );
result.setFileList( fileNames );
}
catch ( IOException error )
{
result.setSuccess( false );
result.setMessage( "上传失败,原因:" + error.getMessage() );
}
}
return result;
}
/**
* 接收上传文件,并保存到临时目录:
* 1、临时目录下再用sessionID作为子目录保存文件。
* 2、保存时不更改文件名会覆盖同名文件。
* 3、MultipartFile参数形参名称必须和请求form中file标签的name属性一致否则值为null。
* 4、返回值为接收结果和文件保存绝对路径。
* @param fileName
* @param file
* @param request
* @return
*/
@RequestMapping( path = "/file-upload.do" )
@ResponseBody
public FileUploadResult saveUploadFile(
@RequestParam( "file-name" ) String fileName,
@RequestParam( "files" ) MultipartFile file,
HttpServletRequest request
)
{
// session id用来创建临时目录避免重复
String sessionID = request.getSession().getId();
FileUploadResult result = new FileUploadResult();
MultipartFile[] files = new MultipartFile[1];
result.setSuccess( true );
result.setMessage( "上传成功!" );
String filePath = request.getServletContext().getRealPath( "/temp/upload/" + sessionID );
File dir = new File( filePath );
// 创建临时目录
if ( (!dir.exists()) && (!dir.mkdirs()) )
{
result.setSuccess( false );
result.setMessage( "创建临时目录失败:" + filePath );
return result;
}
try
{
files[0] = file;
SaveUploadFile.saveUploadFile( files, filePath );
}
catch ( ProcessUploadedFileException error )
{
result.setSuccess( false );
result.setMessage( error.getMessage() );
}
return result;
}
}

View File

@@ -0,0 +1,51 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-11-17 17:49:58
* @LastEditors: Kane Wang
* @LastModified: 2025-11-18 15:19:46
* @FilePath: src/main/java/com/cpic/xim/web/controllers/fileupload/MoveFileRequest.java
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.web.controllers.fileupload;
import com.fasterxml.jackson.annotation.JsonProperty;
public class MoveFileRequest
{
@JsonProperty("originFilePath")
private String originFilePath;
@JsonProperty("destFilePath")
private String destFilePath;
public MoveFileRequest()
{}
public MoveFileRequest( String originFilePath, String destFilePath )
{
this.originFilePath = originFilePath;
this.destFilePath = destFilePath;
}
public String getOriginFilePath()
{
return originFilePath;
}
public void setOriginFilePath( String originFilePath )
{
this.originFilePath = originFilePath;
}
public String getDestFilePath()
{
return destFilePath;
}
public void setDestFilePath( String destFilePath )
{
this.destFilePath = destFilePath;
}
}

View File

@@ -0,0 +1,48 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-11-17 17:31:15
* @LastEditors: Kane Wang
* @LastModified: 2025-11-17 17:31:16
* @FilePath: src/main/java/com/cpic/xim/web/controllers/fileupload/MoveFileResult.java
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.web.controllers.fileupload;
import com.cpic.xim.web.controllers.QueryResponse;
import com.fasterxml.jackson.annotation.JsonProperty;
public class MoveFileResponse extends QueryResponse
{
@JsonProperty("new-file-path")
private String newFilePath;
public MoveFileResponse()
{
super();
}
public MoveFileResponse(
boolean success,
String message,
String newFilePath
)
{
super( success, message );
this.newFilePath = newFilePath;
}
public String getNewFilePath()
{
return newFilePath;
}
public void setNewFilePath( String newFilePath )
{
this.newFilePath = newFilePath;
}
}

View File

@@ -1,71 +0,0 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-16 09:46:42
* @LastEditors: Kane Wang
* @LastModified: 2025-10-16 15:16:57
* @FilePath: src/main/java/com/cpic/xim/web/controllers/fileupload/SaveUploadFile.java
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.web.controllers.fileupload;
import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import org.springframework.web.multipart.MultipartFile;
public class SaveUploadFile
{
public static HashMap<String, String> saveUploadFile(
MultipartFile[] files,
String tempFilePath
)
throws ProcessUploadedFileException
{
HashMap<String, String> savedFiles = new HashMap<>();
// File dir = new File( tempFilePath );
String fileName = "";
String fullPath;
if ( !(tempFilePath.endsWith( "/" ) || tempFilePath.endsWith( "\\" )) )
{
fullPath = tempFilePath + "/";
}
else
{
fullPath = tempFilePath;
}
try
{
for ( MultipartFile file : files )
{
// 空文件跳过
if ( file.isEmpty() )
{
continue;
}
// 文件名前加上时间戳,避免覆盖
Long milliSecond = LocalDateTime.now().toInstant( ZoneOffset.of( "+8" ) ).toEpochMilli();
fileName = String.valueOf( milliSecond ) + " - " + file.getOriginalFilename();
File destFile = new File( tempFilePath, fileName );
file.transferTo( destFile );
savedFiles.put( fileName, fullPath + fileName );
}
}
catch ( IOException error )
{
throw new ProcessUploadedFileException( "临时目录" + tempFilePath + "保存文件" + fileName + "失败!" );
}
return savedFiles;
}
}

View File

@@ -0,0 +1,118 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-15 11:42:56
* @LastEditors: Kane Wang
* @LastModified: 2025-10-15 17:48:52
* @FilePath: src/main/java/com/cpic/xim/web/controllers/fileupload/FileUpload.java
* @Description: 用于接受上传文件的Controller。
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.web.controllers.fileupload;
import java.io.File;
import java.util.Vector;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.cpic.xim.utils.files.SaveUploadFile;
import com.cpic.xim.utils.files.UploadedFile;
import com.cpic.xim.utils.files.exceptions.MoveUploadedFileException;
import com.cpic.xim.utils.files.exceptions.ProcessUploadedFileException;
import jakarta.servlet.http.HttpServletRequest;
// @SuppressWarnings( "unused" )
@Controller
@RequestMapping( path = "/file" )
public class UploadFileController
{
/**
* 接收上传文件,并保存到临时目录:
* 1、临时目录下再用sessionID作为子目录保存文件。
* 2、保存时不更改文件名会覆盖同名文件。
* 3、MultipartFile参数形参名称必须和请求form中file标签的name属性一致否则值为null。
* 4、返回值为接收结果和文件保存绝对路径。
*
* @param fileName
* @param file
* @param request
* @return FileUploadResult 文件上传结果对象
*/
@RequestMapping( path = "/file-upload.do" )
@ResponseBody
public UploadFileResponse saveUploadFile(
@RequestParam( "fileName" ) String fileName,
@RequestParam( "files" ) MultipartFile file,
HttpServletRequest request
)
{
// session id用来创建临时目录避免重复
String sessionID = request.getSession().getId();
UploadFileResponse result = new UploadFileResponse();
MultipartFile[] files = new MultipartFile[1];
result.setSuccess( true );
result.setMessage( "上传成功!" );
String relativeFilePath = "/temp/upload/" + sessionID;
String absolutefilePath = request.getServletContext().getRealPath( relativeFilePath );
File dir = new File( absolutefilePath );
// 创建临时目录
if ( (!dir.exists()) && (!dir.mkdirs()) )
{
result.setSuccess( false );
result.setMessage( "创建临时目录失败:" + absolutefilePath );
return result;
}
try
{
files[0] = file;
Vector<UploadedFile> uploadFiles = SaveUploadFile.saveUploadFiles( files,
absolutefilePath,
relativeFilePath );
result.setFileList( uploadFiles );
}
catch ( ProcessUploadedFileException error )
{
result.setSuccess( false );
result.setMessage( error.getMessage() );
}
return result;
}
@RequestMapping( path = "/move-file.do" )
@ResponseBody
public MoveFileResponse moveFile( @RequestBody MoveFileRequest request )
{
MoveFileResponse response = new MoveFileResponse( false, null, null );
try
{
SaveUploadFile.MoveUploadedFile( request.getOriginFilePath(), request.getDestFilePath() );
response.setSuccess( true );
response.setMessage( "移动文件路径完成!" );
response.setNewFilePath( request.getDestFilePath() );
}
catch ( MoveUploadedFileException error )
{
response.setSuccess( false );
response.setMessage( error.getMessage() );
response.setNewFilePath( request.getDestFilePath() );
}
return response;
}
}

View File

@@ -17,13 +17,15 @@
package com.cpic.xim.web.controllers.fileupload; package com.cpic.xim.web.controllers.fileupload;
import java.util.Vector; import java.util.Vector;
import com.cpic.xim.utils.files.UploadedFile;
import com.cpic.xim.web.controllers.QueryResponse; import com.cpic.xim.web.controllers.QueryResponse;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@SuppressWarnings( "unused" ) @SuppressWarnings( "unused" )
public class FileUploadResult extends QueryResponse public class UploadFileResponse extends QueryResponse
{ {
public FileUploadResult() public UploadFileResponse()
{ {
super(); super();
} }
@@ -35,10 +37,10 @@ public class FileUploadResult extends QueryResponse
* @param message 消息字符串 * @param message 消息字符串
* @param fileList 文件绝对路径字符串数组 * @param fileList 文件绝对路径字符串数组
*/ */
public FileUploadResult( public UploadFileResponse(
boolean success, boolean success,
String message, String message,
Vector<String> fileList Vector<UploadedFile> fileList
) )
{ {
super( success, message ); super( success, message );
@@ -46,17 +48,17 @@ public class FileUploadResult extends QueryResponse
this.fileList = fileList; this.fileList = fileList;
} }
public Vector<String> getFileList() public Vector<UploadedFile> getFileList()
{ {
return fileList; return fileList;
} }
public void setFileList( Vector<String> fileList ) public void setFileList( Vector<UploadedFile> fileList )
{ {
this.fileList = fileList; this.fileList = fileList;
} }
@JsonProperty( "fileList" ) @JsonProperty( "fileList" )
private Vector<String> fileList; private Vector<UploadedFile> fileList;
} }

View File

@@ -0,0 +1,69 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-12-26 17:16:22
* @LastEditors: Kane Wang
* @LastModified: 2025-12-26 17:16:22
* @FilePath: src/main/java/com/cpic/xim/web/controllers/regulatory/AddNewRegulatoryController.java
* @Description: 新增制度的控制器
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.web.controllers.regulatory;
import java.io.File;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.cpic.xim.utils.files.UploadedFile;
@Controller
@RequestMapping( "/regulatory" )
public class AddNewRegulatoryController
{
// 制度库文件的基础目录
private static final String baseDirectory = "D:/制度库/";
@RequestMapping( path = "/add-new-regulatory.do", method = RequestMethod.POST )
@ResponseBody
public static AddNewRegulatoryResponse addNewRegulatory( @RequestBody AddNewRegulatoryRequest request )
{
AddNewRegulatoryResponse response = new AddNewRegulatoryResponse();
// 验证文件是否存在,并判断目睹路径是否有同名文件存在
for ( UploadedFile file : request.getRegulatoryFiles() )
{
String destFilePath = baseDirectory + "/" + request.getRegulatoryName() + "/" + file.getFileName();
File uploadFile = new File( file.getAbsoluteFilePath() );
File dest = new File( destFilePath );
if ( uploadFile.exists() == false )
{
response.setSuccess( false );
response.setMessage( "文件" + file.getAbsoluteFilePath() + "不存在!" );
return response;
}
// 判断目的路径是否有同名文件
if ( dest.exists() == true )
{
response.setSuccess( false );
response.setMessage( "文件" + file.getAbsoluteFilePath() + "存在同名文件!" );
return response;
}
}
// 复制文件
for ( UploadedFile file : request.getRegulatoryFiles() )
{
File uploadFile = new File( file.getAbsoluteFilePath() );
}
return response;
}
}

View File

@@ -0,0 +1,108 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-12-26 17:25:13
* @LastEditors: Kane Wang
* @LastModified: 2025-12-26 21:23:04
* @FilePath: src/main/java/com/cpic/xim/web/controllers/regulatory/AddNewRegulatoryRequest.java
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.web.controllers.regulatory;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.cpic.xim.utils.files.UploadedFile;
import java.util.Vector;
public class AddNewRegulatoryRequest
{
public AddNewRegulatoryRequest()
{
this.regulatoryName = "";
this.departmentName = "";
this.releaseYear = "";
this.comment = "";
this.regulatoryFiles = new Vector<UploadedFile>();
}
public AddNewRegulatoryRequest(
String regulatoryName,
String departmentName,
String releaseYear,
String comment,
Vector<UploadedFile> regulatoryFiles
)
{
this.regulatoryName = regulatoryName;
this.departmentName = departmentName;
this.releaseYear = releaseYear;
this.comment = comment;
this.regulatoryFiles = regulatoryFiles;
}
public String getRegulatoryName()
{
return regulatoryName;
}
public void setRegulatoryName( String regulatoryName )
{
this.regulatoryName = regulatoryName;
}
public String getDepartmentName()
{
return departmentName;
}
public void setDepartmentName( String departmentName )
{
this.departmentName = departmentName;
}
public String getReleaseYear()
{
return releaseYear;
}
public void setReleaseYear( String releaseYear )
{
this.releaseYear = releaseYear;
}
public String getComment()
{
return comment;
}
public void setComment( String comment )
{
this.comment = comment;
}
public Vector<UploadedFile> getRegulatoryFiles()
{
return regulatoryFiles;
}
public void setRegulatoryFiles( Vector<UploadedFile> regulatoryFiles )
{
this.regulatoryFiles = regulatoryFiles;
}
@JsonProperty("regulatory_name")
private String regulatoryName;
@JsonProperty("department_name")
private String departmentName;
@JsonProperty("release_year")
private String releaseYear;
@JsonProperty("comment")
private String comment;
@JsonProperty("regulatory_files")
private Vector<UploadedFile> regulatoryFiles;
}

View File

@@ -0,0 +1,22 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-12-26 21:42:12
* @LastEditors: Kane Wang
* @LastModified: 2025-12-26 21:43:54
* @FilePath: src/main/java/com/cpic/xim/web/controllers/regulatory/AddNewRegulatoryResponse.java
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
package com.cpic.xim.web.controllers.regulatory;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.cpic.xim.web.controllers.QueryResponse;
public class AddNewRegulatoryResponse extends QueryResponse
{
public AddNewRegulatoryResponse()
{
super( false, "");
}
}

156
code/tomcat/server.xml Normal file
View File

@@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Note: A "Server" is not itself a "Container", so you may not
define subcomponents such as "Valves" at this level.
Documentation at /docs/config/server.html
-->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!-- OpenSSL support using Tomcat Native -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" />
<!-- OpenSSL support using FFM API from Java 22 -->
<!-- <Listener className="org.apache.catalina.core.OpenSSLLifecycleListener" /> -->
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">
<!--The
connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
HTTP Connector: /docs/config/http.html
AJP Connector: /docs/config/ajp.html
Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
This connector uses the NIO implementation. The default
SSLImplementation will depend on the presence of the APR/native
library and the useOpenSSL attribute of the AprLifecycleListener.
Either JSSE or OpenSSL style configuration may be used regardless of
the SSLImplementation selected. JSSE style configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
certificateKeystorePassword="changeit" type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an AJP 1.3 Connector on port 8009 -->
<!--
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443" />
-->
<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
Documentation at /docs/config/engine.html -->
<!-- You should set jvmRoute to support load-balancing via AJP ie :
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
-->
<Engine name="Catalina" defaultHost="localhost">
<!--For
clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->
<!-- Use the LockOutRealm to prevent attempts to guess user passwords
via a brute-force attack -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase" />
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="/regulatory" docBase="D:/数据/制度库/文件" debug="0" privileged="true" />
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
</Engine>
</Service>
</Server>

View File

@@ -13,4 +13,9 @@ VITE_APP_MOCK_DATA=true
VITE_APP_CORS_ORIGIN=http://localhost:3000 VITE_APP_CORS_ORIGIN=http://localhost:3000
VITE_APP_TIMEOUT=5000 VITE_APP_TIMEOUT=5000
VITE_APP_RETRY_ATTEMPTS=3 VITE_APP_RETRY_ATTEMPTS=3
VITE_APP_CACHE_ENABLED=true VITE_APP_CACHE_ENABLED=true
#上传文件
VITE_APP_URL_UPLOAD_FILE=http://222.76.244.118:8081/RegulatoryManagementBackend/file/file-upload.do
VITE_APP_URL_MOVE_FILE=http://222.76.244.118:8081/RegulatoryManagementBackend/file/move-file.do
VITE_APP_URL_PREFIX=http://222.76.244.118:8081/RegulatoryManagementBackend/

View File

@@ -21,7 +21,7 @@ module.exports = {
}, },
extends:["eslint:recommended",], extends:["eslint:recommended",],
rules:{ rules:{
indent: ["warn", 4,], // indent: ["warn", 4,],
// 圆括号中的空格,为空不加空格,紧跟花括号、方括号、圆括号时也不加入空格 // 圆括号中的空格,为空不加空格,紧跟花括号、方括号、圆括号时也不加入空格
"space-in-parens": ["error", "always", { exceptions: ["{}", "[]", "()", "empty",], },], "space-in-parens": ["error", "always", { exceptions: ["{}", "[]", "()", "empty",], },],
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off", "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
@@ -81,7 +81,7 @@ module.exports = {
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
], ],
rules: { rules: {
indent: ["warn", 4,], // indent: ["warn", 4,],
"no-trailing-spaces": ["error", {"ignoreComments": true,},], "no-trailing-spaces": ["error", {"ignoreComments": true,},],
// 圆括号中的空格,为空不加空格,紧跟花括号、方括号、圆括号时也不加入空格 // 圆括号中的空格,为空不加空格,紧跟花括号、方括号、圆括号时也不加入空格
"space-in-parens": ["error", "always", { exceptions: ["{}", "[]", "()", "empty",], },], "space-in-parens": ["error", "always", { exceptions: ["{}", "[]", "()", "empty",], },],
@@ -121,7 +121,7 @@ module.exports = {
},], },],
// typescript // typescript
// "@typescript-eslint/indent": ["warn", 4,], // "@typescript-eslint/indent": ["warn", 4,],
"@stylistic/indent": ["warn", 4,], "@stylistic/indent": ["warn", 4, { "SwitchCase": 1, },],
"@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-explicit-any": "warn",
// "@typescript-eslint/no-unsafe-argument": "warn", // "@typescript-eslint/no-unsafe-argument": "warn",
"@typescript-eslint/no-extra-semi": "off", "@typescript-eslint/no-extra-semi": "off",
@@ -173,7 +173,7 @@ module.exports = {
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": "off",
// "@typescript-eslint/no-unsafe-argument": "warn", // "@typescript-eslint/no-unsafe-argument": "warn",
// "@typescript-eslint/indent": ["error", 4,], // "@typescript-eslint/indent": ["error", 4,],
"@stylistic/indent": ["warn", 4,], "@stylistic/indent": ["warn", 4, { "SwitchCase": 1, },],
"@typescript-eslint/no-extra-semi": "off", "@typescript-eslint/no-extra-semi": "off",
"@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-unused-vars": "warn", "@typescript-eslint/no-unused-vars": "warn",

File diff suppressed because it is too large Load Diff

View File

@@ -10,25 +10,31 @@
}, },
"dependencies": { "dependencies": {
"scss": "^0.2.4", "scss": "^0.2.4",
"vue": "^3.5.22", "vue": "^3.5.24",
"vue-router": "^4.6.3" "vue-router": "^4.6.3"
}, },
"devDependencies": { "devDependencies": {
"@element-plus/icons-vue": "^2.3.2", "@element-plus/icons-vue": "^2.3.2",
"@stylistic/eslint-plugin": "^5.5.0", "@stylistic/eslint-plugin": "^5.6.1",
"@types/node": "^24.9.1", "@types/node": "^24.10.1",
"@typescript-eslint/eslint-plugin": "^8.46.2", "@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.46.2", "@typescript-eslint/parser": "^8.47.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.2",
"@vue-office/docx": "^1.6.3",
"@vue-office/excel": "^1.7.14",
"@vue-office/pdf": "^2.0.10",
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
"element-plus": "^2.11.5", "axios": "^1.13.2",
"eslint": "^9.38.0", "element-plus": "^2.11.8",
"eslint-plugin-vue": "^10.5.1", "eslint": "^9.39.1",
"eslint-plugin-vue": "^10.6.0",
"path": "^0.12.7", "path": "^0.12.7",
"sass": "^1.93.2", "sass": "^1.94.2",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"vite": "^7.1.12", "vite": "^7.2.4",
"vue-demi": "^0.14.10",
"vue-eslint-parser": "^10.2.0", "vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.1.1" "vue-pdf-embed": "^2.1.3",
"vue-tsc": "^3.1.5"
} }
} }

View File

@@ -1,21 +1,8 @@
<!-- <!--
* @Author: Kane author: Kane Wang <wangkane@qq.com>
* @Date: 2023-03-23 15:44:52 date: 2025-10-23 17:52:01
* @LastEditors: Kane component: Main
* @FilePath: /task_schedule/src/layout/components/Main.vue Copyright © CPIC All rights reserved
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
-->
<!--
* @Author: Kane
* @Date: 2023-01-04 11:40:03
* @LastEditors: Kane
* @LastEditTime: 2023-09-27 11:19:05
* @FilePath: /it-console/src/layout/components/Main.vue
* @Description:
*
* Copyright (c) ${2022} by Kane, All Rights Reserved.
--> -->
<template> <template>
<el-scrollbar> <el-scrollbar>

View File

@@ -2,7 +2,7 @@
* @Author: Kane Wang <wangkane@qq.com> * @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-13 15:31:41 * @Date: 2025-10-13 15:31:41
* @LastEditors: Kane Wang * @LastEditors: Kane Wang
* @LastModified: 2025-10-24 10:03:13 * @LastModified: 2025-11-05 11:20:46
* @FilePath: src/router/index.ts * @FilePath: src/router/index.ts
* @Description: * @Description:
* *
@@ -71,6 +71,26 @@ const routes = [
}, },
component: ()=> import( "@/views/console/data/RegulatoryManagement.vue" ), component: ()=> import( "@/views/console/data/RegulatoryManagement.vue" ),
}, },
{
path: "/new-regulatory",
name: "NewRegulatory",
meta: {
title: "测试用 - 新建",
icon: "OfficeBuilding",
},
hidden: false,
component: () => import( "@/views/console/data/NewRegulatory.vue" ),
},
{
path: "/upload-regulatory",
name: "UploadRegulatory",
meta: {
title: "测试用 - 查看制度",
icon: "OfficeBuilding",
},
hidden: false,
component: () => import( "@/views/console/data/UploadRegulatory.vue" ),
},
], ],
}, },
]; ];

View File

@@ -0,0 +1,27 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-28 10:13:04
* @LastEditors: Kane Wang
* @LastModified: 2025-10-28 10:13:04
* @FilePath: src/types/regulatory.ts
* @Description: 和制度相关的类型定义
*
* Copyright (c) 2025 by Kane All rights reserved
*/
interface RegulatoryData {
department_name: string;
release_year: string;
regulatory_name: string;
comment: string;
regulatory_files: RegulatoryFile[];
}
interface RegulatoryFile {
regulatory_file_name: string;
file_url: string;
local_file_path: string;
file_type: string;
}
export { type RegulatoryData, type RegulatoryFile };

View File

@@ -0,0 +1,29 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-11-21 10:19:11
* @LastEditors: Kane Wang
* @LastModified: 2025-11-27 17:41:28
* @FilePath: src/types/upload_file.ts
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
interface UploadFileResponse
{
success: boolean;
message: string;
fileList: UploadedFile[];
}
interface UploadedFile
{
fileName: string;
absoluteFilePath: string;
relativeFilePath: string;
}
export {
type UploadedFile,
type UploadFileResponse
};

View File

@@ -0,0 +1,59 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-12-18 10:02:42
* @LastEditors: Kane Wang
* @LastModified: 2025-12-25 18:01:56
* @FilePath: src/utils/api/request.ts
* @Description:
*
* Copyright (c) 2025 by Kane All rights reserved
*/
import axios, {type AxiosInstance } from "axios";
const service: AxiosInstance = axios.create(
{
baseURL: "",
timeout: 30000,
}
);
// #region 1111
// #endregion
// 请求拦截
service.interceptors.request.use(
/**
* 请求拦截器config对象用于给开发人员配置请求
* @param config 配置对象在配置中加入需要的token
* @returns 返回配置好的config
*/
( config ) =>
{
config.headers.token = "123";
return config;
},
async ( error: any ) =>
{
console.log( `配置请求拦截器错误:${error}` );
return await Promise.reject( error );
}
);
/**
* 响应拦截器
*/
service.interceptors.response.use(
( response ) =>
{
return response;
},
async ( error ) =>
{
console.log( `配置响应拦截器失败:${error}` );
return await Promise.reject( error );
}
);
export { service };

View File

@@ -0,0 +1,19 @@
/**
* @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-11-21 09:39:59
* @LastEditors: Kane Wang
* @LastModified: 2025-11-21 09:40:00
* @FilePath: src/utils/config.ts
* @Description: 保存应用的配置参数。
*
* Copyright (c) 2025 by Kane All rights reserved
*/
const API_URL= {
URL_UPLOAD_FILE: import.meta.env.VITE_APP_URL_UPLOAD_FILE,
URL_MOVE_FILE: import.meta.env.VITE_APP_URL_MOVE_FILE,
URL_PREFIX: import.meta.env.VITE_APP_URL_PREFIX,
};
console.log( API_URL );
export { API_URL };

View File

@@ -2,9 +2,55 @@
* @Author: Kane Wang <wangkane@qq.com> * @Author: Kane Wang <wangkane@qq.com>
* @Date: 2025-10-23 16:52:10 * @Date: 2025-10-23 16:52:10
* @LastEditors: Kane Wang * @LastEditors: Kane Wang
* @LastModified: 2025-10-23 17:04:54 * @LastModified: 2025-11-21 11:17:40
* @FilePath: src/utils/utils.ts * @FilePath: src/utils/utils.ts
* @Description: * @Description: 提供 一些功能性的函数
* *
* Copyright (c) 2025 by Kane All rights reserved * Copyright (c) 2025 by Kane All rights reserved
*/ */
/**
* 取文件路径末尾的扩展名作为文件类型
* @param filePath 文件路径
* @returns 文件类型字符串
*/
function getFileType( filePath: string ): string
{
let type = "未知类型";
if ( filePath == null || filePath.length == 0 )
{
return type;
}
const startIndex = filePath.lastIndexOf( "." );
const fileType = filePath.slice( startIndex + 1 ).toUpperCase();
// ignore-eslint-next-line
switch( fileType )
{
case "DOCX":
type = "WPS文档";
break;
case "XLSX":
type = "WPS表格";
break;
case "PDF":
type = "PDF文档";
break;
case "JPG":
case "PNG":
case "BMP":
case "GIF":
type = "图片文件";
break;
default:
type = "未知文件类型";
}
// type = fileType.length != 0 ? fileType + "文件" : "未知类型";
return type;
}
export { getFileType };

View File

@@ -0,0 +1,407 @@
<!--
author: Kane Wang <wangkane@qq.com>
date: 2025-10-30 15:06:18
component: NewRegulatory
Copyright © CPIC All rights reserved
-->
<template>
<div class="wrapper">
<div class="query-box-wrapper">
<el-row :gutter="10">
<el-col :span="1">
<span>名称</span>
</el-col>
<el-col :span="10">
<el-input v-model.trim.lazy="ui.newRegulatory.regulatory_name" style="text-align:center;" />
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="1">
<span>部门</span>
</el-col>
<el-col :span="4">
<el-input v-model.trim="ui.newRegulatory.department_name" />
</el-col>
<el-col :span="2">
<span>发布修订年份</span>
</el-col>
<el-col :span="4">
<el-input v-model.lazy.number.trim="ui.newRegulatory.release_year" />
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="1">
<span>备注</span>
</el-col>
<el-col :span="10">
<el-input v-model.lazy.trim="ui.newRegulatory.comment" type="textarea" :rows="3" />
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="3">
<div class="button-wrapper-left">
<el-button type="primary" icon="document" @click="showUploadFileDialog">
新增文档
</el-button>
</div>
</el-col>
<el-col :span="5" />
<el-col :span="3">
<div class="button-wrapper-right">
<el-button
type="primary" icon="document"
@click="onPreviewUploadedFile(1)"
>
提交
</el-button>
</div>
</el-col>
</el-row>
</div>
<el-table
width="100%" stripe
border="true"
:head-cell-style="headerCellStyle"
:row-class-name="tableRowClassName"
empty-text="请上传文件"
:data="ui.newRegulatory.regulatory_files"
>
<el-table-column label="文件名" align="center">
<template #default="file">
<span>{{ file.row.regulatory_file_name }}</span>
</template>
</el-table-column>
<el-table-column label="文件类型" align="center" width="200px">
<template #default="file">
<span>{{ file.row.file_type }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200px">
<template #default="file">
<el-button
type="primary" icon="search"
circle
@click="onPreviewUploadedFile(file.row.rowIndex)"
/>
<el-button
type="danger" icon="delete"
circle
@click="onDeleteUploadedFile(file.row.rowIndex)"
/>
</template>
</el-table-column>
</el-table>
<div class="upload-dialog-wrapper">
<el-dialog
v-model="ui.showUploadDialog" title="上传文件"
width="600px"
:close-on-click-model="false" :close-on-press-escape="false"
:show-close="true"
>
<el-upload
drag
:action="ui.urlFileUpload"
name="files"
:show-file-list="false"
:data="ui.uploadParameters"
:on-success="onUploadSuccess"
>
<el-icon class="el-icon--upload">
<upload-filled />
</el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
</el-upload>
</el-dialog>
<el-dialog
v-model="ui.isPDF" :close-on-click-model="false"
:close-on-press-escape="false"
:show-close="true"
align-center
title="文件预览"
width="1024px"
>
<VueOfficePdf
v-if="ui.isPDF" :src="ui.fileURL"
style="height:calc(100vh - 100px);"
@error="errorHandle"
/>
</el-dialog>
<el-dialog
v-model="ui.isDOCX" :close-on-click-model="false"
:close-on-press-escape="false"
:show-close="true"
align-center
title="文件预览"
width="1024px"
>
<VueOfficeDocx
v-if="ui.isDOCX" :src="ui.fileURL"
style="height:calc(100vh - 100px);"
@error="errorHandle"
/>
</el-dialog>
<el-dialog
v-model="ui.isXLSX" :close-on-click-model="false"
:close-on-press-escape="false"
:show-close="true"
align-center
title="文件预览"
width="1024px"
>
<VueOfficeExcel
:src="ui.fileURL"
style="height:calc(100vh - 100px);"
@error="errorHandle"
/>
</el-dialog>
</div>
</div>
</template>
<script lang="ts">
import {reactive, ref} from "vue";
import { type UploadProps, type UploadFile, type UploadFiles, ElMessage, ElMessageBox } from "element-plus";
import {type RegulatoryData, type RegulatoryFile} from "@/types/regulatory/regulatory.ts";
import { type UploadedFile, type UploadFileResponse } from "@/types/upload_file.ts";
import {API_URL} from "@/utils/config.ts";
import { getFileType } from "@/utils/utils";
import VueOfficePdf from "@vue-office/pdf";
import VueOfficeDocx from "@vue-office/docx";
import VueOfficeExcel from "@vue-office/excel";
//引入相关样式
import "@vue-office/docx/lib/index.css";
import "@vue-office/excel/lib/index.css";
interface UI{
showUI: boolean;
showUploadDialog: boolean;
showPreviewDialog: boolean;
urlFileUpload: string;
uploadParameters: {
fileName: string;
};
newRegulatory: RegulatoryData;
showFileList: boolean;
isPDF: boolean;
isDOCX: boolean;
isXLSX: boolean;
fileURL: string;
};
export default {
name: "NewRegulatory",
components: {VueOfficePdf, VueOfficeDocx, VueOfficeExcel,},
setup()
{
const ui: UI = reactive({
showUI: true,
showUploadDialog: false,
showPreviewDialog: false,
urlFileUpload: API_URL.URL_UPLOAD_FILE,
uploadParameters: {
fileName: "1234",
},
newRegulatory: {
department_name: "",
release_year: "",
regulatory_name: "",
comment: "",
regulatory_files: [],
},
showFileList: false,
isPDF: false,
isDOCX: false,
isXLSX: false,
fileURL: "",
});
const headerCellStyle = reactive(
{
textAlign: "center",
}
);
const cellStyle = reactive({
textAlign: "center",
});
const showUploadFileDialog = (): void =>
{
ui.showUploadDialog = true;
};
/*表格操作相关 */
/**
* 删除对应的上传文件记录。
* @param rowId 行号
*/
const onDeleteUploadedFile = ( rowId: number ): void =>
{
console.log( `点击的rowid${rowId}` );
ElMessage.info( `选取的行号:${rowId}` );
ElMessageBox.confirm(
"确认删除文件?",
"删除文件",
{
confirmButtonText: "确定",
type: "warning",
center: true,
}
)
.then(()=>
{
ui.newRegulatory.regulatory_files?.splice( rowId, 1 );
})
.catch(()=>{});
};
const onPreviewUploadedFile = ( rowId: number ): void =>
{
// ui.showPreviewDialog = true;
ui.fileURL = ui.newRegulatory.regulatory_files[rowId]?.file_url + "/" + ui.newRegulatory.regulatory_files[rowId]?.regulatory_file_name;
console.log( "完整路径:", ui.fileURL );
ui.isDOCX = false;
ui.isPDF = false;
ui.isXLSX = false;
switch ( ui.newRegulatory.regulatory_files[rowId].file_type )
{
case "WPS文档":
ui.isDOCX = true;
break;
case "WPS表格":
ui.isXLSX = true;
break;
case "PDF文档":
ui.isPDF = true;
break;
default:
break;
}
};
/**
* 用来给 el-table 行设置样式。
* 其中给row加一个参数rowIndex用行号赋值。
* @param element-plus给于的参数
*/
const tableRowClassName = ({row, rowIndex,}: {row:any, rowIndex:number}): void =>
{
console.log( `${row}设置行号${rowIndex}` );
row.rowIndex = rowIndex;
};
/**
* 上传成功的响应函数。
* @param response
* @param uploadFile
* @param uploadFiles
*/
const onUploadSuccess: UploadProps["onSuccess"] = ( response: UploadFileResponse, uploadFile: UploadFile, uploadFiles: UploadFiles ): void =>
{
console.log( `上传制度文件响应:${response}` );
console.log( `上传文件:${uploadFile}` );
console.log( `上传多文件:${uploadFiles}` );
ui.showUploadDialog = false;
// 先判断成功标志位
if ( response.success )
{
// 成功,把文件写入清单
if ( response.fileList === null || response.fileList.length === 0 )
{
// 上传文件路径有问题,提示一下
ElMessageBox.confirm(
"上传文件的保存路径有误,请联系开发人员。",
"上传文件错误",
{
confirmButtonText: "确定",
type: "warning",
center: true,
}
)
.then((): void => {})
.catch((): void => {});
}
const uploadedFile: RegulatoryFile = {
regulatory_file_name: response.fileList[0].fileName ?? "",
local_file_path: response.fileList[0]?.absoluteFilePath ?? "",
file_type: getFileType( response.fileList[0]?.fileName ),
file_url: API_URL.URL_PREFIX + "/" + response.fileList[0]?.relativeFilePath,
};
ui.newRegulatory.regulatory_files?.push( uploadedFile );
console.log( "文件列表", ui.newRegulatory.regulatory_files );
}
else
{
// 失败了,提示一下
ElMessageBox.confirm(
response.message,
"上传文件错误",
{
confirmButtonText: "确定",
type: "warning",
center: true,
}
)
.then((): void => {})
.catch((): void => {});
}
};
const errorHandle = ()=>
{
ElMessage.error( "渲染文档出错!" );
};
return {
ui,
headerCellStyle,
cellStyle,
onUploadSuccess,
tableRowClassName,
showUploadFileDialog,
onDeleteUploadedFile,
onPreviewUploadedFile,
errorHandle,
};
},
};
</script>
<style lang="scss" scoped>
@import "@/assets/css/public/_public.scss";
.wrapper {
// max-width: 800px;
min-width: 1024px;
margin: 10px 10px 10px 10px;
>*+* {
margin-top: 10px;
}
}
.query-box-wrapper
{
@include query-box-wrap;
}
:deep(.el-input) .el-input__inner {
text-align: center;
}
:deep(.el-dialog) {
min-width: calc(100wh - 100px);
max-width: calc(100wh - 100px);
}
</style>

View File

@@ -6,17 +6,106 @@ Copyright © CPIC All rights reserved
--> -->
<template> <template>
<div class="wrapper"> <div class="wrapper">
111 <el-row :gutter="10">
<el-col span="24">
<el-button
type="warning"
icon="upload"
>
上传
</el-button>
<el-button
type="primary"
icon="refresh"
>
刷新
</el-button>
</el-col>
</el-row>
<el-table
width="100%" stripe
:header-cell-style="headerCellStyle"
border
:data="ui.regulatoryData"
>
<el-table-column label="部门" align="center" width="200px">
<template #default="regulatory">
<span>{{ regulatory.row.department_name }}</span>
</template>
</el-table-column>
<el-table-column width="150px" label="发布、修订年份" align="center">
<template #default="regulatory">
<span>{{ regulatory.row.release_year }}</span>
</template>
</el-table-column>
<el-table-column label="制度名称" align="center" :cell-style="cellStyle">
<template #default="regulatory">
<span>{{ regulatory.row.regulatory_name }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200px">
<el-button
type="primary"
icon="search"
>
查看
</el-button>
<el-button type="warning" icon="document">
编辑
</el-button>
</el-table-column>
</el-table>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { reactive } from "vue";
import { type RegulatoryData } from "@/types/regulatory/regulatory.ts";
interface UI
{
showUI: boolean,
showUploadDialog: boolean,
tablePageSize: number,
tableCurrentPageIndex: number,
regulatoryData: RegulatoryData[],
}
export default { export default {
name: "RegulatoryManagement", name: "RegulatoryManagement",
setup() setup()
{ {
const ui = {}; const headerCellStyle = reactive(
{
textAlign: "center",
}
);
const cellStyle = reactive({
textAlign: "center",
});
const ui:UI = reactive({
showUI: true,
showUploadDialog: false,
tablePageSize: 10,
tableCurrentPageIndex: 1,
regulatoryData: [
{
department_name: "信息技术部",
release_year: "2019",
regulatory_name: "关于印发修订后的《太平洋产险厦门分公司信息系统账号权限管理实施细则》的通知",
comment:"",
regulatory_files: [],
},
{
department_name: "信息技术部",
release_year: "2019",
regulatory_name: "关于印发修订后的《太平洋产险厦门分公司个人信息保护管理实施细则》的通知",
comment:"",
regulatory_files: [],
},
],
});
return { ui, }; return { ui, headerCellStyle, cellStyle, };
}, },
}; };
</script> </script>
@@ -24,9 +113,15 @@ export default {
@import "@/assets/css/public/mixin.scss"; @import "@/assets/css/public/mixin.scss";
.wrapper { .wrapper {
@include no-select; margin: 10px 10px 10px 10px;
margin: 5px; >*+* {
border: 1px solid red; margin-top: 10px;
}
}
.pagination_wrapper {
display: flex;
justify-content: flex-end;
} }
</style> </style>

View File

@@ -0,0 +1,78 @@
<!--
author: Kane Wang <wangkane@qq.com>
date: 2025-10-28 11:18:51
component: UploadRegulatory
Copyright © CPIC All rights reserved
-->
<template>
<div class="wrapper">
<!-- <VuePdfEmbed v-if="ui.isPDF" :source="ui.fileURL" text-layer annotation-layer /> -->
<VueOfficePdf v-if="ui.isPDF" :src="ui.fileURLPDF" @error="errorHandle" />
<VueOfficeDocx v-if="ui.isDocx" :src="ui.fileURLDocx" @error="errorHandle" />
<VueOfficeExcel
v-if="ui.isXlsx"
:src="ui.fileURLXlsx"
@error="errorHandle"
/>
</div>
</template>
<script lang="ts">
import {reactive, ref } from "vue";
import { ElMessage } from "element-plus";
// import VuePdfEmbed, { useVuePdfEmbed } from "vue-pdf-embed";
import VueOfficePdf from "@vue-office/pdf";
import VueOfficeDocx from "@vue-office/docx";
import VueOfficeExcel from "@vue-office/excel";
import "vue-pdf-embed/dist/styles/annotationLayer.css";
import "vue-pdf-embed/dist/styles/textLayer.css";
import "@vue-office/excel/lib/index.css";
export default {
name:"UploadRegulatory",
components: {
VueOfficePdf,
VueOfficeDocx,
VueOfficeExcel,
},
setup()
{
const ui = reactive({
showUI: true,
isPDF: false,
isDocx: false,
isXlsx: true,
fileURLPDF: "http://10.39.0.1:8080/regulatory/%E4%BF%A1%E6%81%AF%E6%8A%80%E6%9C%AF%E9%83%A8/%E5%85%B3%E4%BA%8E%E5%8D%B0%E5%8F%91%E3%80%8A%E5%A4%AA%E5%B9%B3%E6%B4%8B%E4%BA%A7%E9%99%A9%E5%8E%A6%E9%97%A8%E5%88%86%E5%85%AC%E5%8F%B8IT%E8%B5%84%E4%BA%A7%E7%AE%A1%E7%90%86%E5%8A%9E%E6%B3%95%E3%80%8B%EF%BC%882019%E5%B9%B4%E4%BF%AE%E8%AE%A2%EF%BC%89%E7%9A%84%E9%80%9A%E7%9F%A5/%E5%8E%A6%E5%A4%AA%E4%BF%9D%E4%BA%A7%E5%8F%91%E3%80%902019%E3%80%9146%E5%8F%B7%E5%85%B3%E4%BA%8E%E5%8D%B0%E5%8F%91%E3%80%8A%E5%A4%AA%E5%B9%B3%E6%B4%8B%E4%BA%A7%E9%99%A9%E5%8E%A6%E9%97%A8%E5%88%86%E5%85%AC%E5%8F%B8IT%E8%B5%84%E4%BA%A7%E7%AE%A1%E7%90%86%E5%8A%9E%E6%B3%95%E3%80%8B%EF%BC%882019%E5%B9%B4%E4%BF%AE%E8%AE%A2%EF%BC%89%E7%9A%84%E9%80%9A%E7%9F%A5.pdf",
fileURLDocx: "http://10.39.0.1:8080/regulatory/%E4%BF%A1%E6%81%AF%E6%8A%80%E6%9C%AF%E9%83%A8/%E5%85%B3%E4%BA%8E%E5%8D%B0%E5%8F%91%E3%80%8A%E5%A4%AA%E5%B9%B3%E6%B4%8B%E4%BA%A7%E9%99%A9%E5%8E%A6%E9%97%A8%E5%88%86%E5%85%AC%E5%8F%B8%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E9%9C%80%E6%B1%82%E7%AE%A1%E7%90%86%E5%8A%9E%E6%B3%95%EF%BC%882020%E5%B9%B4%E4%BF%AE%E8%AE%A2%EF%BC%89%E3%80%8B%E7%9A%84%E9%80%9A%E7%9F%A5/%E9%99%84%E4%BB%B62%EF%BC%9A%E4%B8%9A%E5%8A%A1%E9%9C%80%E6%B1%82%E8%AF%B4%E6%98%8E%E4%B9%A6.docx",
fileURLXlsx: "http://10.39.0.1:8081/regulatory/%E4%BF%A1%E6%81%AF%E6%8A%80%E6%9C%AF%E9%83%A8/%E5%85%B3%E4%BA%8E%E5%8D%B0%E5%8F%91%E3%80%8A%E5%A4%AA%E5%B9%B3%E6%B4%8B%E4%BA%A7%E9%99%A9%E5%8E%A6%E9%97%A8%E5%88%86%E5%85%AC%E5%8F%B8%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E9%9C%80%E6%B1%82%E7%AE%A1%E7%90%86%E5%8A%9E%E6%B3%95%EF%BC%882020%E5%B9%B4%E4%BF%AE%E8%AE%A2%EF%BC%89%E3%80%8B%E7%9A%84%E9%80%9A%E7%9F%A5/%E9%99%84%E4%BB%B64%EF%BC%9A%E5%88%86%E5%85%AC%E5%8F%B8%E8%87%AA%E5%BB%BA%E7%B3%BB%E7%BB%9F%E9%83%A8%E7%BD%B2%E6%83%85%E5%86%B5%E7%99%BB%E8%AE%B0%E8%A1%A8.xlsx",
});
const errorHandle = ()=>
{
ElMessage.error( "渲染文档出错!" );
};
return {
ui,
errorHandle, };
},
};
</script>
<style lang="scss">
.wrapper {
margin: 10px 10px 10px 10px;
width: calc(100% - 20px);
height: calc( 100vh - 70px);
// .VueOfficeExcel {
// height: calc( 100vh - 70px);
// }
>*+* {
margin-top: 10px;
}
}
</style>

View File

@@ -31,8 +31,132 @@ resolve: {
}, },
``` ```
### tomcat 跨域下载文件
tomcat 默认是不支持跨域的不做配置使用vue下载文件会有Access-Control-Allow-Origin问题。
解决方法: 编辑 web.xml 文件,找到
```xml
<!-- The mapping for the HTTP header security Filter -->
```
在下面添加
```xml
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```
重启 tomcat 解决。
## 组件 ## 组件
### pdf预览组件 ### pdf预览组件
使用 使用
## 技术
### element-plus 文件上传
element-plus 文件上传组件的响应函数,作为:on-success属性值。要传递的参数作为:data属性值。
```typescript
import { type FileUploadResponse } from "@/utils/fileUpload.js";
import { type UploadProps, type UploadFile, type UploadFiles, ElMessage, ElMessageBox } from "element-plus";
const onUploadSuccess: UploadProps["onSuccess"] = ( response: FileUploadResponse, uploadFile: UploadFile, uploadFiles: UploadFiles ): void =>
{
// 先判断成功标志位
if ( response.success )
{
// 成功,发出导入报表请求
if ( response.fileList.length === 0 )
{
// 上传文件路径有问题,提示一下
ElMessageBox.confirm(
"上传文件的保存路径有误,请联系开发人员。",
"上传文件错误",
{
confirmButtonText: "确定",
type: "warning",
center: true,
}
)
.then((): void => {})
.catch((): void => {});
}
const request: ImportBIReportRequest = {
filePath: response.fileList[0],
reportType: ui.selectedReportType,
firstRow: ui.firstRow,
sheetIndex: ui.sheetIndex,
};
console.log( "请求参数", request );
// 发出请求
importBIReport( request, importResponseHandler );
}
else
{
// 失败了,提示一下
ElMessageBox.confirm(
response.message,
"上传文件错误",
{
confirmButtonText: "确定",
type: "warning",
center: true,
}
)
.then((): void => {})
.catch((): void => {});
}
};
```
### typescript 的安全链式调用 和 强制链式调用
在链式调用时,在成员访问操作符前加上?,表示安全链式调用;加上!表示强制链式调用。
如果前的属性存在则正常调用否则返回null。
```typescript
// 这里 Error对象定义的stack是可选参数如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
return new Error().stack.split('\n');
// 我们可以添加?操作符当stack属性存在时调用 stack.split。
// 若stack不存在则返回空
return new Error().stack?.split('\n');
// 以上代码等同以下代码, 感谢 @dingyanhe 的监督
const err = new Error();
return err.stack && err.stack.split('\n');
```
强制链式调用表示!前的属性一定会存在。
```typescript
// 这里 Error对象定义的stack是可选参数如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
new Error().stack.split('\n');
// 我们确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在
new Error().stack!.split('\n');
```