commit fbfed7311648d70c844ba371e134a0cd375c5837 Author: LAN Date: Fri Dec 13 14:36:03 2024 +0800 minio diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..ce358e4 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9c80558 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,68 @@ + + + + + + + + + JVM 语言 + + + Java + + + Java 10Java 语言级别迁移帮助Java + + + Java 11Java 语言级别迁移帮助Java + + + Java 14Java 语言级别迁移帮助Java + + + Java 15Java 语言级别迁移帮助Java + + + Java 5Java 语言级别迁移帮助Java + + + Java 7Java 语言级别迁移帮助Java + + + Java 8Java 语言级别迁移帮助Java + + + Java 9Java 语言级别迁移帮助Java + + + Java 互操作问题Kotlin + + + Java 语言级别迁移帮助Java + + + JavaScript and TypeScript + + + Kotlin + + + 有效性问题JavaScript and TypeScript + + + 模块化问题Java + + + 迁移Kotlin + + + + + Android + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a953099 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# SpringBoot3+Minio+Vue3+ElementPlus实现断点续传/分片上传/文件秒传 diff --git a/minio-admin/.gitignore b/minio-admin/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/minio-admin/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/minio-admin/logs/error.log b/minio-admin/logs/error.log new file mode 100644 index 0000000..e69de29 diff --git a/minio-admin/logs/info.log b/minio-admin/logs/info.log new file mode 100644 index 0000000..40d5d16 --- /dev/null +++ b/minio-admin/logs/info.log @@ -0,0 +1,48 @@ +11:56:44.842 [background-preinit] INFO o.h.v.i.util.Version - [,21] - HV000001: Hibernate Validator 8.0.0.Final +11:56:44.859 [restartedMain] INFO c.m.MinioUploadFileApplication - [logStarting,51] - Starting MinioUploadFileApplication using Java 19.0.1 with PID 18717 (/Users/lan/minio-project-master/minio-admin/target/classes started by lan in /Users/lan/minio-project-master/minio-admin) +11:56:44.861 [restartedMain] INFO c.m.MinioUploadFileApplication - [logStartupProfileInfo,630] - No active profile set, falling back to 1 default profile: "default" +11:56:47.883 [restartedMain] INFO o.a.c.h.Http11NioProtocol - [log,173] - Initializing ProtocolHandler ["http-nio-9090"] +11:56:47.885 [restartedMain] INFO o.a.c.c.StandardService - [log,173] - Starting service [Tomcat] +11:56:47.885 [restartedMain] INFO o.a.c.c.StandardEngine - [log,173] - Starting Servlet engine: [Apache Tomcat/10.1.5] +11:56:47.954 [restartedMain] INFO o.a.c.c.C.[.[.[/] - [log,173] - Initializing Spring embedded WebApplicationContext +11:56:49.612 [restartedMain] INFO o.a.c.h.Http11NioProtocol - [log,173] - Starting ProtocolHandler ["http-nio-9090"] +11:56:49.642 [restartedMain] INFO c.m.MinioUploadFileApplication - [logStarted,57] - Started MinioUploadFileApplication in 5.269 seconds (process running for 7.524) +12:14:01.766 [http-nio-9090-exec-1] INFO o.a.c.c.C.[.[.[/] - [log,173] - Initializing Spring DispatcherServlet 'dispatcherServlet' +12:14:01.881 [http-nio-9090-exec-1] INFO c.m.c.FileMinioController - [checkFileUploadedByMd5,74] - REST: 通过查询 <036ee500fdb624314479780c6daf547d> 文件是否存在、是否进行断点续传 +12:14:01.882 [http-nio-9090-exec-1] INFO c.m.s.i.UploadServiceImpl - [getByFileMD5,48] - tip message: 通过 <036ee500fdb624314479780c6daf547d> 查询redis是否存在 +12:14:02.490 [http-nio-9090-exec-1] INFO c.m.s.i.UploadServiceImpl - [getByFileMD5,59] - tip message: 通过 <036ee500fdb624314479780c6daf547d> 查询mysql是否存在 +12:14:02.681 [http-nio-9090-exec-1] INFO c.z.h.HikariDataSource - [getConnection,110] - HikariPool-1 - Starting... +12:14:02.962 [http-nio-9090-exec-1] INFO c.z.h.p.HikariPool - [checkFailFast,565] - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@1b0794cd +12:14:02.964 [http-nio-9090-exec-1] INFO c.z.h.HikariDataSource - [getConnection,123] - HikariPool-1 - Start completed. +12:14:03.297 [http-nio-9090-exec-2] INFO c.m.c.FileMinioController - [initMultiPartUpload,90] - REST: 通过 初始化上传任务 +12:14:03.301 [http-nio-9090-exec-2] INFO c.m.s.i.UploadServiceImpl - [initMultiPartUpload,85] - tip message: 通过 开始初始化<分片上传>任务 +12:14:03.834 [http-nio-9090-exec-2] INFO c.m.s.i.UploadServiceImpl - [initMultiPartUpload,91] - tip message: 当前分片数量 <1> 进行单文件上传 +12:14:03.847 [http-nio-9090-exec-2] INFO c.m.c.MybatisPlusConfig - [insertFill,37] - start insert fill .... +12:14:03.941 [http-nio-9090-exec-2] INFO c.m.utils.MinioUtils - [getUploadObjectUrl,64] - tip message: 通过 <036ee500fdb624314479780c6daf547d.png-image> 开始单文件上传 +12:14:03.947 [http-nio-9090-exec-2] INFO c.m.utils.MinioUtils - [getUploadObjectUrl,74] - tip message: 单个文件上传、成功 +12:18:57.075 [SpringApplicationShutdownHook] INFO c.z.h.HikariDataSource - [close,350] - HikariPool-1 - Shutdown initiated... +12:18:57.082 [SpringApplicationShutdownHook] INFO c.z.h.HikariDataSource - [close,352] - HikariPool-1 - Shutdown completed. +12:19:02.637 [background-preinit] INFO o.h.v.i.util.Version - [,21] - HV000001: Hibernate Validator 8.0.0.Final +12:19:02.653 [restartedMain] INFO c.m.MinioUploadFileApplication - [logStarting,51] - Starting MinioUploadFileApplication using Java 19.0.1 with PID 20456 (/Users/lan/minio-project-master/minio-admin/target/classes started by lan in /Users/lan/minio-project-master/minio-admin) +12:19:02.654 [restartedMain] INFO c.m.MinioUploadFileApplication - [logStartupProfileInfo,630] - No active profile set, falling back to 1 default profile: "default" +12:19:04.851 [restartedMain] INFO o.a.c.h.Http11NioProtocol - [log,173] - Initializing ProtocolHandler ["http-nio-9090"] +12:19:04.851 [restartedMain] INFO o.a.c.c.StandardService - [log,173] - Starting service [Tomcat] +12:19:04.852 [restartedMain] INFO o.a.c.c.StandardEngine - [log,173] - Starting Servlet engine: [Apache Tomcat/10.1.5] +12:19:04.915 [restartedMain] INFO o.a.c.c.C.[.[.[/] - [log,173] - Initializing Spring embedded WebApplicationContext +12:19:06.455 [restartedMain] INFO o.a.c.h.Http11NioProtocol - [log,173] - Starting ProtocolHandler ["http-nio-9090"] +12:19:06.558 [restartedMain] INFO c.m.MinioUploadFileApplication - [logStarted,57] - Started MinioUploadFileApplication in 4.334 seconds (process running for 5.695) +12:19:30.858 [http-nio-9090-exec-1] INFO o.a.c.c.C.[.[.[/] - [log,173] - Initializing Spring DispatcherServlet 'dispatcherServlet' +12:19:30.918 [http-nio-9090-exec-1] INFO c.m.c.FileMinioController - [checkFileUploadedByMd5,74] - REST: 通过查询 文件是否存在、是否进行断点续传 +12:19:30.919 [http-nio-9090-exec-1] INFO c.m.s.i.UploadServiceImpl - [getByFileMD5,48] - tip message: 通过 查询redis是否存在 +12:19:31.237 [http-nio-9090-exec-1] INFO c.m.s.i.UploadServiceImpl - [getByFileMD5,59] - tip message: 通过 查询mysql是否存在 +12:19:31.343 [http-nio-9090-exec-1] INFO c.z.h.HikariDataSource - [getConnection,110] - HikariPool-1 - Starting... +12:19:31.551 [http-nio-9090-exec-1] INFO c.z.h.p.HikariPool - [checkFailFast,565] - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@42d42417 +12:19:31.552 [http-nio-9090-exec-1] INFO c.z.h.HikariDataSource - [getConnection,123] - HikariPool-1 - Start completed. +12:19:31.708 [http-nio-9090-exec-2] INFO c.m.c.FileMinioController - [initMultiPartUpload,90] - REST: 通过 初始化上传任务 +12:19:31.712 [http-nio-9090-exec-2] INFO c.m.s.i.UploadServiceImpl - [initMultiPartUpload,85] - tip message: 通过 开始初始化<分片上传>任务 +12:19:32.184 [http-nio-9090-exec-2] INFO c.m.s.i.UploadServiceImpl - [initMultiPartUpload,91] - tip message: 当前分片数量 <1> 进行单文件上传 +12:19:32.202 [http-nio-9090-exec-2] INFO c.m.c.MybatisPlusConfig - [insertFill,37] - start insert fill .... +12:19:32.279 [http-nio-9090-exec-2] INFO c.m.utils.MinioUtils - [getUploadObjectUrl,64] - tip message: 通过 开始单文件上传 +12:19:32.287 [http-nio-9090-exec-2] INFO c.m.utils.MinioUtils - [getUploadObjectUrl,74] - tip message: 单个文件上传、成功 +12:35:11.549 [SpringApplicationShutdownHook] INFO c.z.h.HikariDataSource - [close,350] - HikariPool-1 - Shutdown initiated... +12:35:11.575 [SpringApplicationShutdownHook] INFO c.z.h.HikariDataSource - [close,352] - HikariPool-1 - Shutdown completed. diff --git a/minio-admin/pom.xml b/minio-admin/pom.xml new file mode 100644 index 0000000..3f9e4ee --- /dev/null +++ b/minio-admin/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + com.mmg + minio-admin + 0.0.1-SNAPSHOT + minio-admin + minio-admin + + 17 + UTF-8 + UTF-8 + 3.0.2 + + + + org.springframework.boot + spring-boot-starter-web + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.0 + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.mysql + mysql-connector-j + runtime + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + com.alibaba + fastjson + 1.2.50 + + + com.baomidou + mybatis-plus-boot-starter + 3.5.5 + + + io.minio + minio + 8.3.1 + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-validation + + + cn.hutool + hutool-all + 5.7.20 + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 17 + 17 + UTF-8 + + + + + diff --git a/minio-admin/src/main/java/com/mmg/MinioUploadFileApplication.java b/minio-admin/src/main/java/com/mmg/MinioUploadFileApplication.java new file mode 100644 index 0000000..0982f3f --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/MinioUploadFileApplication.java @@ -0,0 +1,11 @@ +package com.mmg; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MinioUploadFileApplication { + public static void main(String[] args) { + SpringApplication.run(MinioUploadFileApplication.class, args); + } +} diff --git a/minio-admin/src/main/java/com/mmg/config/CustomMinioClient.java b/minio-admin/src/main/java/com/mmg/config/CustomMinioClient.java new file mode 100644 index 0000000..40171e4 --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/config/CustomMinioClient.java @@ -0,0 +1,72 @@ +package com.mmg.config; + + +import com.google.common.collect.Multimap; +import io.minio.CreateMultipartUploadResponse; +import io.minio.ListPartsResponse; +import io.minio.MinioClient; +import io.minio.ObjectWriteResponse; +import io.minio.messages.Part; + +public class CustomMinioClient extends MinioClient { + + /** + * 继承父类 + * @param client + */ + public CustomMinioClient(MinioClient client) { + super(client); + } + + + /** + * 初始化分片上传、获取 uploadId + * + * @param bucket String 存储桶名称 + * @param region String + * @param object String 文件名称 + * @param headers Multimap 请求头 + * @param extraQueryParams Multimap + * @return String + */ + public String initMultiPartUpload(String bucket, String region, String object, Multimap headers, Multimap extraQueryParams) throws Exception { + CreateMultipartUploadResponse response = this.createMultipartUpload(bucket, region, object, headers, extraQueryParams); + return response.result().uploadId(); + } + + /** + * 合并分片 + * + * @param bucketName String 桶名称 + * @param region String + * @param objectName String 文件名称 + * @param uploadId String 上传的 uploadId + * @param parts Part[] 分片集合 + * @param extraHeaders Multimap + * @param extraQueryParams Multimap + * @return ObjectWriteResponse + */ + public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap extraHeaders, Multimap extraQueryParams) throws Exception { + return this.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams); + } + + /** + * 查询当前上传后的分片信息 + * + * @param bucketName String 桶名称 + * @param region String + * @param objectName String 文件名称 + * @param maxParts Integer 分片数量 + * @param partNumberMarker Integer 分片起始值 + * @param uploadId String 上传的 uploadId + * @param extraHeaders Multimap + * @param extraQueryParams Multimap + * @return ListPartsResponse + */ + public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap extraHeaders, Multimap extraQueryParams) throws Exception { + return this.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams); + } + + +} + diff --git a/minio-admin/src/main/java/com/mmg/config/MybatisPlusConfig.java b/minio-admin/src/main/java/com/mmg/config/MybatisPlusConfig.java new file mode 100644 index 0000000..652637a --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/config/MybatisPlusConfig.java @@ -0,0 +1,52 @@ +package com.mmg.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.LocalDateTime; + +/** + * MybatisPlus配置类 + * + */ +@Slf4j +@Configuration +@MapperScan("com.mmg.mapper") +public class MybatisPlusConfig implements MetaObjectHandler { + /** + * 新的分页插件 + * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false + * 避免缓存出现问题(该属性会在旧插件移除后一同移除) + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + + @Override + public void insertFill(MetaObject metaObject) { + log.info("start insert fill ...."); + this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用) + this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`) + /* 上面选其一使用,下面的已过时(注意 strictInsertFill 有多个方法,详细查看源码) */ + + } + + @Override + public void updateFill(MetaObject metaObject) { + log.info("start update fill ...."); + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用) + this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`) + /* 上面选其一使用,下面的已过时(注意 strictUpdateFill 有多个方法,详细查看源码) */ + } + +} diff --git a/minio-admin/src/main/java/com/mmg/config/RedisConfig.java b/minio-admin/src/main/java/com/mmg/config/RedisConfig.java new file mode 100644 index 0000000..4c295ab --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/config/RedisConfig.java @@ -0,0 +1,48 @@ +package com.mmg.config; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +//编写我们自己的RedisTemplate +@Configuration +public class RedisConfig { + //固定redis模板,在工作中,拿去就可以用 + @Bean + @SuppressWarnings("all") + //改成String,Object类型 + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + //我们为了自己开发方便,一般直接使用 + RedisTemplate template = new RedisTemplate(); + template.setConnectionFactory(factory); + + //Json序列化配置 + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); +// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(om); + //String 的序列化 + StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + + //key采用string的序列化方式 + template.setKeySerializer(stringRedisSerializer); + //value的序列化方式采用jackson + template.setValueSerializer(jackson2JsonRedisSerializer); + //hash的key也采用String的序列化方式 + template.setHashKeySerializer(stringRedisSerializer); + //hash的value序列化方式采用jackson + template.setHashValueSerializer(jackson2JsonRedisSerializer); + template.afterPropertiesSet(); + + return template; + } +} diff --git a/minio-admin/src/main/java/com/mmg/config/WebAppConfigurer.java b/minio-admin/src/main/java/com/mmg/config/WebAppConfigurer.java new file mode 100644 index 0000000..836f451 --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/config/WebAppConfigurer.java @@ -0,0 +1,44 @@ +package com.mmg.config; + + +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + + +/** + * @description Web应用程序配置 + * @author LGY + * @date 2023/03/14 20:09 + * @version 1.0.0 + */ +@Configuration +public class WebAppConfigurer implements WebMvcConfigurer { + + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowCredentials(true) + .allowedOriginPatterns("*") + .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") + .allowedHeaders("*"); + } + + //实体类属性为空时不进行序列化返回给前端 + @Bean + public HttpMessageConverters fastJsonHttpMessageConverters() { + FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); + fastConverter.setFastJsonConfig(fastJsonConfig); + HttpMessageConverter converter = fastConverter; + return new HttpMessageConverters(converter); + } +} diff --git a/minio-admin/src/main/java/com/mmg/controller/FileMinioController.java b/minio-admin/src/main/java/com/mmg/controller/FileMinioController.java new file mode 100644 index 0000000..7e55b2f --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/controller/FileMinioController.java @@ -0,0 +1,131 @@ +package com.mmg.controller; + + +import com.mmg.model.vo.FileUploadInfo; +import com.mmg.service.UploadService; +import com.mmg.utils.MinioUtils; +import com.mmg.utils.R; +import com.mmg.utils.RedisUtil; +import com.mmg.utils.RespEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +/** + * minio上传流程 + *

+ * 1.检查数据库中是否存在上传文件 + *

+ * 2.根据文件信息初始化,获取分片预签名url地址,前端根据url地址上传文件 + *

+ * 3.上传完成后,将分片上传的文件进行合并 + *

+ * 4.保存文件信息到数据库 + */ +@RestController +@Slf4j +@RequestMapping("upload") +public class FileMinioController { + + @Resource + private UploadService uploadService; + + @Resource + private RedisUtil redisUtil; + + @Resource + private MinioUtils minioUtils; + + /** + * @param fileMD5 文件md5 + * @return {@link R } + * @description 获取上传文件 + * @author LGY + * @date 2023/04/26 16:00 + */ + @GetMapping("/getUploadingFile/{fileMD5}") + public R getUploadingFile(@PathVariable String fileMD5) { + try { + if (StringUtils.isEmpty(fileMD5)) { + return R.error(); + } + FileUploadInfo fileUploadInfo = (FileUploadInfo) redisUtil.get(fileMD5); + if (fileUploadInfo != null) { + // 查询上传后的分片数据 + fileUploadInfo.setChunkUploadedList(minioUtils.getChunkByFileMD5(fileUploadInfo.getFileName(), fileUploadInfo.getUploadId(), fileUploadInfo.getFileType())); + return R.ok().setData(fileUploadInfo); + } + return R.error(); + } catch (Exception e) { + return R.error(e.getMessage()); + } + } + + /** + * 校验文件是否存在 + * + * @param md5 String + * @return ResponseResult + */ + @GetMapping("/multipart/check") + public R checkFileUploadedByMd5(@RequestParam("md5") String md5) { + log.info("REST: 通过查询 <{}> 文件是否存在、是否进行断点续传", md5); + if (StringUtils.isEmpty(md5)) { + log.error("查询文件是否存在、入参无效"); + return R.error(RespEnum.ACCESS_PARAMETER_INVALID); + } + return uploadService.getByFileMD5(md5); + } + + /** + * 分片初始化 + * + * @param fileUploadInfo 文件信息 + * @return ResponseResult + */ + @PostMapping("/multipart/init") + public R initMultiPartUpload(@RequestBody FileUploadInfo fileUploadInfo) { + log.info("REST: 通过 <{}> 初始化上传任务", fileUploadInfo); + return R.ok().setData(uploadService.initMultiPartUpload(fileUploadInfo)); + } + + /** + * 完成上传 + * + * @param fileUploadInfo 文件信息 + * @return ResponseResult + */ + @PostMapping("/multipart/merge") + public R completeMultiPartUpload(@RequestBody FileUploadInfo fileUploadInfo) { + log.info("REST: 通过 {} 合并上传任务", fileUploadInfo); + //合并文件 + String url = uploadService.mergeMultipartUpload(fileUploadInfo); + //获取上传文件地址 + if (!StringUtils.isEmpty(url)) { + return R.ok().setData(url); + } + return R.error(); + } + + @PostMapping("/multipart/uploadScreenshot") + public R uploaduploadScreenshot(@RequestPart("photos") MultipartFile[] photos, + @RequestParam("buckName") String buckName) { + log.info("REST: 上传文件信息 <{}> ", photos); + + for (MultipartFile photo : photos) { + if (!photo.isEmpty()) { + uploadService.upload(photo, buckName); + } + } + return R.ok(); + } + + + @RequestMapping("/createBucket") + public void createBucket(@RequestParam("bucketName") String bucketName) { + String bucket = minioUtils.createBucket(bucketName); + } + +} diff --git a/minio-admin/src/main/java/com/mmg/mapper/FilesMapper.java b/minio-admin/src/main/java/com/mmg/mapper/FilesMapper.java new file mode 100644 index 0000000..ad967f2 --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/mapper/FilesMapper.java @@ -0,0 +1,15 @@ +package com.mmg.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.mmg.model.po.Files; + +/** + *

+ * Mapper 接口 + *

+ * + * @author LGY + */ +public interface FilesMapper extends BaseMapper { + +} diff --git a/minio-admin/src/main/java/com/mmg/model/po/Files.java b/minio-admin/src/main/java/com/mmg/model/po/Files.java new file mode 100644 index 0000000..d0ef78f --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/model/po/Files.java @@ -0,0 +1,65 @@ +package com.mmg.model.po; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + *

+ * + *

+ * + * @author LGY + */ +@Data +@TableName("files") +public class Files implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + @TableField("upload_id") + private String uploadId; + + @TableField("file_md5") + private String fileMd5; + + @TableField("url") + private String url; + + @TableField("file_name") + private String fileName; + + @TableField("bucket_name") + private String bucketName; + + @TableField("file_type") + private String fileType; + + @TableField("file_size") + private Long fileSize; + + @TableField("chunk_size") + private Long chunkSize; + + @TableField("chunk_num") + private Integer chunkNum; + + @TableField("is_delete") + @TableLogic(value = "0",delval = "1") + private Boolean isDelete; + + @TableField("enable") + private Boolean enable; + + @TableField(value = "create_time", fill = FieldFill.INSERT) + private LocalDateTime createTime; + + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + +} diff --git a/minio-admin/src/main/java/com/mmg/model/vo/FileUploadInfo.java b/minio-admin/src/main/java/com/mmg/model/vo/FileUploadInfo.java new file mode 100644 index 0000000..87c5461 --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/model/vo/FileUploadInfo.java @@ -0,0 +1,45 @@ +package com.mmg.model.vo; + + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.experimental.Accessors; + +import java.util.List; + +@Data +@Accessors(chain = true) +public class FileUploadInfo { + + @NotBlank(message = "文件名不能为空") + private String fileName; + + @NotNull(message = "文件大小不能为空") + private Long fileSize; + + @NotBlank(message = "Content-Type不能为空") + private String contentType; + + @NotNull(message = "分片数量不能为空") + private Integer chunkNum; + + @NotBlank(message = "uploadId 不能为空") + private String uploadId; + + private Long chunkSize; + + // 桶名称 + //private String bucketName; + + //md5 + private String fileMd5; + + //文件类型 + private String fileType; + + //已上传的分片索引+1 + private List chunkUploadedList; + +} + diff --git a/minio-admin/src/main/java/com/mmg/service/UploadService.java b/minio-admin/src/main/java/com/mmg/service/UploadService.java new file mode 100644 index 0000000..3f6f55a --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/service/UploadService.java @@ -0,0 +1,50 @@ +package com.mmg.service; + + +import com.mmg.model.vo.FileUploadInfo; +import com.mmg.utils.R; +import org.springframework.web.multipart.MultipartFile; +import java.util.Map; + +public interface UploadService { + /** + * 分片上传初始化 + * + * @param fileUploadInfo + * @return Map + */ + Map initMultiPartUpload(FileUploadInfo fileUploadInfo); + + /** + * 完成分片上传 + * + * @param fileUploadInfo + * @return String + */ + String mergeMultipartUpload(FileUploadInfo fileUploadInfo); + + /** + * 通过 md5 获取已上传的数据 + * @param md5 String + * @return Mono> + */ + R getByFileMD5(String md5); + + /** + * 获取文件地址 + * @param bucketName + * @param fileName + * + */ + String getFliePath(String bucketName, String fileName); + + + /** + * 单文件上传 + * @param file + * @param bucketName + * @return + */ + String upload(MultipartFile file, String bucketName); + +} diff --git a/minio-admin/src/main/java/com/mmg/service/impl/UploadServiceImpl.java b/minio-admin/src/main/java/com/mmg/service/impl/UploadServiceImpl.java new file mode 100644 index 0000000..e0627c8 --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/service/impl/UploadServiceImpl.java @@ -0,0 +1,155 @@ +package com.mmg.service.impl; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.mmg.mapper.FilesMapper; +import com.mmg.model.po.Files; +import com.mmg.model.vo.FileUploadInfo; +import com.mmg.service.UploadService; +import com.mmg.utils.MinioUtils; +import com.mmg.utils.R; +import com.mmg.utils.RedisUtil; +import com.mmg.utils.RespEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +public class UploadServiceImpl implements UploadService { + + @Resource + private FilesMapper filesMapper; + + @Resource + private MinioUtils minioUtils; + + @Resource + private RedisUtil redisUtil; + + @Value("${minio.breakpoint-time}") + private Integer breakpointTime; + + /** + * 通过 md5 获取已上传的数据(断点续传) + * + * @param md5 String + * @return Mono> + */ + @Override + public R getByFileMD5(String md5) { + + log.info("tip message: 通过 <{}> 查询redis是否存在", md5); + // 从redis获取文件名称和id + FileUploadInfo fileUploadInfo = (FileUploadInfo) redisUtil.get(md5); + if (fileUploadInfo != null) { + // 正在上传,查询上传后的分片数据 + List chunkList = minioUtils.getChunkByFileMD5(fileUploadInfo.getFileName(), fileUploadInfo.getUploadId(), fileUploadInfo.getFileType()); + fileUploadInfo.setChunkUploadedList(chunkList); + return R.ok(RespEnum.UPLOADING).setData(fileUploadInfo); + + } + + log.info("tip message: 通过 <{}> 查询mysql是否存在", md5); + // 查询数据库是否上传成功 + Files one = filesMapper.selectOne(new LambdaQueryWrapper().eq(Files::getFileMd5, md5)); + if (one != null) { + FileUploadInfo mysqlsFileUploadInfo = new FileUploadInfo(); + BeanUtils.copyProperties(one, mysqlsFileUploadInfo); + return R.ok(RespEnum.UPLOADSUCCESSFUL).setData(mysqlsFileUploadInfo); + } + + return R.ok(RespEnum.NOT_UPLOADED); + } + + + /** + * 文件分片上传 + * + * @param fileUploadInfo + * @return Mono> + */ + @Override + public Map initMultiPartUpload(FileUploadInfo fileUploadInfo) { + + FileUploadInfo redisFileUploadInfo = (FileUploadInfo) redisUtil.get(fileUploadInfo.getFileMd5()); + if (redisFileUploadInfo != null) { + fileUploadInfo = redisFileUploadInfo; + } + log.info("tip message: 通过 <{}> 开始初始化<分片上传>任务", fileUploadInfo); + // 获取桶 + String bucketName = minioUtils.getBucketName(fileUploadInfo.getFileType()); + + // 单文件上传 + if (fileUploadInfo.getChunkNum() == 1) { + log.info("tip message: 当前分片数量 <{}> 进行单文件上传", fileUploadInfo.getChunkNum()); + Files files = saveFileToDB(fileUploadInfo); + String fileName = files.getUrl().substring(files.getUrl().lastIndexOf("/") + 1); + return minioUtils.getUploadObjectUrl(fileName, bucketName); + } + // 分片上传 + else { + log.info("tip message: 当前分片数量 <{}> 进行分片上传", fileUploadInfo.getChunkNum()); + Map map = minioUtils.initMultiPartUpload(fileUploadInfo, fileUploadInfo.getFileName(), fileUploadInfo.getChunkNum(), fileUploadInfo.getContentType(), bucketName); + String uploadId = (String) map.get("uploadId"); + fileUploadInfo.setUploadId(uploadId); + redisUtil.set(fileUploadInfo.getFileMd5(), fileUploadInfo, breakpointTime * 60 * 60 * 24); + return map; + } + } + + + /** + * 文件合并 + * + * @param + * @return String + */ + @Override + public String mergeMultipartUpload(FileUploadInfo fileUploadInfo) { + log.info("tip message: 通过 <{}> 开始合并<分片上传>任务", fileUploadInfo); + FileUploadInfo redisFileUploadInfo = (FileUploadInfo) redisUtil.get(fileUploadInfo.getFileMd5()); + if (redisFileUploadInfo != null) { + fileUploadInfo.setFileName(redisFileUploadInfo.getFileName()); + } + boolean result = minioUtils.mergeMultipartUpload(fileUploadInfo.getFileName(), fileUploadInfo.getUploadId(), fileUploadInfo.getFileType()); + + //合并成功 + if (result) { + //存入数据库 + Files files = saveFileToDB(fileUploadInfo); + redisUtil.del(fileUploadInfo.getFileMd5()); + return files.getUrl(); + } + return null; + } + + @Override + public String getFliePath(String bucketName, String fileName) { + return minioUtils.getFliePath(bucketName, fileName); + } + + @Override + public String upload(MultipartFile file, String bucketName) { + minioUtils.upload(file, bucketName); + return getFliePath(bucketName, file.getName()); + } + + private Files saveFileToDB(FileUploadInfo fileUploadInfo) { + String suffix = fileUploadInfo.getFileName().substring(fileUploadInfo.getFileName().lastIndexOf(".")); + String url = this.getFliePath(fileUploadInfo.getFileType().toLowerCase(), fileUploadInfo.getFileMd5() + suffix); + //存入数据库 + Files files = new Files(); + BeanUtils.copyProperties(fileUploadInfo, files); + files.setBucketName(fileUploadInfo.getFileType()); + files.setUrl(url); + filesMapper.insert(files); + return files; + } +} diff --git a/minio-admin/src/main/java/com/mmg/utils/MinioUtils.java b/minio-admin/src/main/java/com/mmg/utils/MinioUtils.java new file mode 100644 index 0000000..fbdf51a --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/utils/MinioUtils.java @@ -0,0 +1,306 @@ +package com.mmg.utils; + + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.HashMultimap; +import com.mmg.config.CustomMinioClient; +import com.mmg.model.vo.FileUploadInfo; +import io.minio.*; +import io.minio.http.Method; +import io.minio.messages.Part; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + + +@Slf4j +@Component +public class MinioUtils { + + @Value(value = "${minio.endpoint}") + private String endpoint; + @Value(value = "${minio.accesskey}") + private String accesskey; + @Value(value = "${minio.secretkey}") + private String secretkey; + + @Value(value = "${minio.expiry}") + private Integer expiry; + + private CustomMinioClient customMinioClient; + + /** + * 用spring的自动注入会注入失败 + */ + @PostConstruct + public void init() { + MinioClient minioClient = MinioClient.builder() + .endpoint(endpoint) + .credentials(accesskey, secretkey) + .build(); + customMinioClient = new CustomMinioClient(minioClient); + } + + + /** + * 单文件签名上传 + * + * @param objectName 文件全路径名称 + * @param bucketName 桶名称 + * @return / + */ + public Map getUploadObjectUrl(String objectName, String bucketName) { + try { + log.info("tip message: 通过 <{}-{}> 开始单文件上传", objectName, bucketName); + Map resMap = new HashMap(); + List partList = new ArrayList<>(); + String url = customMinioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.PUT) + .bucket(bucketName) + .object(objectName) + .expiry(expiry, TimeUnit.DAYS) + .build()); + log.info("tip message: 单个文件上传、成功"); + partList.add(url); + resMap.put("uploadId", "SingleFileUpload"); + resMap.put("urlList", partList); + return resMap; + } catch (Exception e) { + log.error("error message: 单个文件上传失败、原因:", e); + // 返回 文件上传失败 + return null; + } + } + + /** + * 初始化分片上传 + * + * @param fileUploadInfo + * @param objectName 文件全路径名称 + * @param chunkNum 分片数量 + * @param contentType 类型,如果类型使用默认流会导致无法预览 + * @param bucketName 桶名称 + * @return Mono> + */ + public Map initMultiPartUpload(FileUploadInfo fileUploadInfo, String objectName, int chunkNum, String contentType, String bucketName) { + log.info("tip message: 通过 <{}-{}-{}-{}> 开始初始化<分片上传>数据", objectName, chunkNum, contentType, bucketName); + Map resMap = new HashMap<>(); + try { + if (CharSequenceUtil.isBlank(contentType)) { + contentType = "application/octet-stream"; + } + HashMultimap headers = HashMultimap.create(); + + headers.put("Content-Type", contentType); + + //获取uploadId + String uploadId = null; + if (StringUtils.isEmpty(fileUploadInfo.getUploadId())) { + uploadId = customMinioClient.initMultiPartUpload(bucketName, null, objectName, headers, null); + } else { + uploadId = fileUploadInfo.getUploadId(); + } + + resMap.put("uploadId", uploadId); + + fileUploadInfo.setUploadId(uploadId); + fileUploadInfo.setChunkNum(chunkNum); + + List partList = new ArrayList<>(); + + Map reqParams = new HashMap<>(); + reqParams.put("uploadId", uploadId); + for (int i = 1; i <= chunkNum; i++) { + reqParams.put("partNumber", String.valueOf(i)); + String uploadUrl = customMinioClient.getPresignedObjectUrl( + GetPresignedObjectUrlArgs.builder() + .method(Method.PUT) + .bucket(bucketName) + .object(objectName) + .expiry(1, TimeUnit.DAYS) + .extraQueryParams(reqParams) + .build()); + partList.add(uploadUrl); + } + log.info("tip message: 文件初始化<分片上传>、成功"); + resMap.put("urlList", partList); + return resMap; + } catch (Exception e) { + log.error("error message: 初始化分片上传失败、原因:", e); + // 返回 文件上传失败 + return R.error(RespEnum.UPLOAD_FILE_FAILED); + } + } + + /** + * 分片上传完后合并 + * + * @param objectName 文件全路径名称 + * @param uploadId 返回的uploadId + * @param bucketName 桶名称 + * @return boolean + */ + public boolean mergeMultipartUpload(String objectName, String uploadId, String bucketName) { + try { + log.info("tip message: 通过 <{}-{}-{}> 合并<分片上传>数据", objectName, uploadId, bucketName); + //目前仅做了最大1000分片 + Part[] parts = new Part[1000]; + // 查询上传后的分片数据 + ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null); + int partNumber = 1; + for (Part part : partResult.result().partList()) { + parts[partNumber - 1] = new Part(partNumber, part.etag()); + partNumber++; + } + // 合并分片 + customMinioClient.mergeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null); + + } catch (Exception e) { + log.error("error message: 合并失败、原因:", e); + //TODO删除redis的数据 + return false; + } + return true; + } + + /** + * 通过 sha256 获取上传中的分片信息 + * + * @param objectName 文件全路径名称 + * @param uploadId 返回的uploadId + * @param bucketName 桶名称 + * @return Mono> + */ + public List getChunkByFileMD5(String objectName, String uploadId, String bucketName) { + log.info("通过 <{}-{}-{}> 查询上传分片数据", objectName, uploadId, bucketName); + try { + // 查询上传后的分片数据 + ListPartsResponse partResult = customMinioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null); + return partResult.result().partList().stream().map(Part::partNumber).collect(Collectors.toList()); + } catch (Exception e) { + log.error("error message: 查询上传后的分片信息失败、原因:", e); + return null; + } + } + + /** + * 获取文件下载地址 + * + * @param bucketName 桶名称 + * @param fileName 文件名 + * @return + */ + public String getFliePath(String bucketName, String fileName) { + return StrUtil.format("{}/{}/{}", endpoint, bucketName, fileName);//文件访问路径 + } + + /** + * 创建一个桶 + * + * @return + */ + public String createBucket(String bucketName) { + try { + BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(bucketName).build(); + //如果桶存在 + if (customMinioClient.bucketExists(bucketExistsArgs)) { + return bucketName; + } + MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build(); + customMinioClient.makeBucket(makeBucketArgs); + return bucketName; + } catch (Exception e) { + log.error("创建桶失败:{}", e.getMessage()); + throw new RuntimeException(e); + } + } + + + /** + * 根据文件类型获取minio桶名称 + * + * @param fileType + * @return + */ + public String getBucketName(String fileType) { + try { + //String bucketName = getProperty(fileType.toLowerCase()); + + if (fileType != null && !fileType.equals("")) { + //判断桶是否存在 + String bucketName2 = createBucket(fileType.toLowerCase()); + if (bucketName2 != null && !bucketName2.equals("")) { + return bucketName2; + } else { + return fileType; + } + } + + } catch (Exception e) { + + log.error("Error reading bucket name "); + } + return fileType; + } + + /** + * 读取配置文件 + * + * @param fileType + * @return + * @throws IOException + */ + private String getProperty(String fileType) throws IOException { + Properties SysLocalPropObject = new Properties(); + //判断桶关系配置文件是否为空 + if (SysLocalPropObject.isEmpty()) { + InputStream is = getClass().getResourceAsStream("/BucketRelation.properties"); + SysLocalPropObject.load(is); + is.close(); + } + return SysLocalPropObject.getProperty("bucket." + fileType); + } + + /** + * 文件上传 + * + * @param file 文件 + * @return Boolean + */ + public String upload(MultipartFile file, String bucketName) { + String originalFilename = file.getOriginalFilename(); + if (StringUtils.isEmpty(originalFilename)) { + throw new RuntimeException(); + } + String objectName = file.getName(); + try { + PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName) + .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build(); + //文件名称相同会覆盖 + customMinioClient.putObject(objectArgs); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + // 查看文件地址 + GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(bucketName).object(objectName).method(Method.GET).build(); + String url = null; + try { + url = customMinioClient.getPresignedObjectUrl(build); + } catch (Exception e) { + e.printStackTrace(); + } + return url; + } +} diff --git a/minio-admin/src/main/java/com/mmg/utils/R.java b/minio-admin/src/main/java/com/mmg/utils/R.java new file mode 100644 index 0000000..38705ec --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/utils/R.java @@ -0,0 +1,82 @@ +package com.mmg.utils; + +import com.alibaba.fastjson.JSON; +import java.util.HashMap; +import java.util.Map; + +/** + * 返回数据 + * + * @author Mark sunlightcs@gmail.com + */ +public class R extends HashMap { + private static final long serialVersionUID = 1L; + + public R setData(Object data) { + put("data",data); + return this; + } + + //利用fastjson进行反序列化 + public T getData(Class typeReference) { + Object data = get("data"); //默认是map + String jsonString = JSON.toJSONString(data); + T t = JSON.parseObject(jsonString, typeReference); + return t; + } + + public R() { + put("code", 200); + put("msg", "success"); + } + + public static R error() { + return error(5000, "未知异常,请联系管理员"); + } + + public static R error(String msg) { + return error(500, msg); + } + public static R error(RespEnum respEnum) { + return error(respEnum.getCode(), respEnum.getMessage()); + } + public static R error(int code, String msg) { + R r = new R(); + r.put("code", code); + r.put("msg", msg); + return r; + } + public static R ok(RespEnum respEnum) { + return ok(respEnum.getCode(), respEnum.getMessage()); + } + public static R ok(String msg) { + R r = new R(); + r.put("msg", msg); + return r; + } + public static R ok(int code, String msg) { + R r = new R(); + r.put("code", code); + r.put("msg", msg); + return r; + } + public static R ok(Map map) { + R r = new R(); + r.putAll(map); + return r; + } + + public static R ok() { + return new R(); + } + + public R put(String key, Object value) { + super.put(key, value); + return this; + } + public Integer getCode() { + + return (Integer) this.get("code"); + } + +} diff --git a/minio-admin/src/main/java/com/mmg/utils/RedisUtil.java b/minio-admin/src/main/java/com/mmg/utils/RedisUtil.java new file mode 100644 index 0000000..6ba71bf --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/utils/RedisUtil.java @@ -0,0 +1,509 @@ +package com.mmg.utils; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +@Component +public class RedisUtil { + + @Autowired + private RedisTemplate redisTemplate; + /** + * 指定缓存失效时间 + * @param key 键 + * @param time 时间(秒) + * @return + */ + public boolean expire(String key, long time) { + try { + if (time > 0) { + redisTemplate.expire(key, time, TimeUnit.SECONDS); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 根据key 获取过期时间 + * @param key 键 不能为null + * @return 时间(秒) 返回0代表为永久有效 + */ + public long getExpire(String key) { + return redisTemplate.getExpire(key, TimeUnit.SECONDS); + } + /** + * 判断key是否存在 + * @param key 键 + * @return true 存在 false不存在 + */ + public boolean hasKey(String key) { + try { + return redisTemplate.hasKey(key); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 删除缓存 + * @param key 可以传一个值 或多个 + */ + @SuppressWarnings("unchecked") + public void del(String... key) { + if (key != null && key.length > 0) { + if (key.length == 1) { + redisTemplate.delete(key[0]); + } else { + redisTemplate.delete((Collection) CollectionUtils.arrayToList(key)); + } + } + } + // ============================String============================= + /** + * 普通缓存获取 + * @param key 键 + * @return 值 + */ + public Object get(String key) { + return key == null ? null : redisTemplate.opsForValue().get(key); + } + /** + * 普通缓存放入 + * @param key 键 + * @param value 值 + * @return true成功 false失败 + */ + public boolean set(String key, Object value) { + try { + redisTemplate.opsForValue().set(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 普通缓存放入并设置时间 + * @param key 键 + * @param value 值 + * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 + * @return true成功 false 失败 + */ + public boolean set(String key, Object value, long time) { + try { + if (time > 0) { + redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); + } else { + set(key, value); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 递增 + * @param key 键 + * @param delta 要增加几(大于0) + * @return + */ + public long incr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递增因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, delta); + } + /** + * 递减 + * @param key 键 + * @param delta 要减少几(小于0) + * @return + */ + public long decr(String key, long delta) { + if (delta < 0) { + throw new RuntimeException("递减因子必须大于0"); + } + return redisTemplate.opsForValue().increment(key, -delta); + } + // ================================Map================================= + /** + * 获取list缓存的长度 + * @param key 键 + * @return + */ + public long hGetMapSize(String key) { + try { + return redisTemplate.opsForHash().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + /** + * HashGet + * @param key 键 不能为null + * @param item 项 不能为null + * @return 值 + */ + public Object hget(String key, String item) { + return redisTemplate.opsForHash().get(key, item); + } + /** + * 获取hashKey对应的所有键值 + * @param key 键 + * @return 对应的多个键值 + */ + public Map hmget(String key) { + return redisTemplate.opsForHash().entries(key); + } + /** + * HashSet + * @param key 键 + * @param map 对应多个键值 + * @return true 成功 false 失败 + */ + public boolean hmset(String key, Map map) { + try { + redisTemplate.opsForHash().putAll(key, map); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * HashSet 并设置时间 + * @param key 键 + * @param map 对应多个键值 + * @param time 时间(秒) + * @return true成功 false失败 + */ + public boolean hmset(String key, Map map, long time) { + try { + redisTemplate.opsForHash().putAll(key, map); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 向一张hash表中放入数据,如果不存在将创建 + * @param key 键 + * @param item 项 + * @param value 值 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value) { + try { + redisTemplate.opsForHash().put(key, item, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 向一张hash表中放入数据,如果不存在将创建 + * @param key 键 + * @param item 项 + * @param value 值 + * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 + * @return true 成功 false失败 + */ + public boolean hset(String key, String item, Object value, long time) { + try { + redisTemplate.opsForHash().put(key, item, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 删除hash表中的值 + * @param key 键 不能为null + * @param item 项 可以使多个 不能为null + */ + public void hdel(String key, Object... item) { + redisTemplate.opsForHash().delete(key, item); + } + /** + * 判断hash表中是否有该项的值 + * @param key 键 不能为null + * @param item 项 不能为null + * @return true 存在 false不存在 + */ + public boolean hHasKey(String key, String item) { + return redisTemplate.opsForHash().hasKey(key, item); + } + /** + * hash递增 如果不存在,就会创建一个 并把新增后的值返回 + * @param key 键 + * @param item 项 + * @param by 要增加几(大于0) + * @return + */ + public double hincr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, by); + } + /** + * hash递减 + * @param key 键 + * @param item 项 + * @param by 要减少记(小于0) + * @return + */ + public double hdecr(String key, String item, double by) { + return redisTemplate.opsForHash().increment(key, item, -by); + } + // ============================set============================= + /** + * 根据key获取Set中的所有值 + * @param key 键 + * @return + */ + public Set sGet(String key) { + try { + return redisTemplate.opsForSet().members(key); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + /** + * 根据value从一个set中查询,是否存在 + * @param key 键 + * @param value 值 + * @return true 存在 false不存在 + */ + public boolean sHasKey(String key, Object value) { + try { + return redisTemplate.opsForSet().isMember(key, value); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 将数据放入set缓存 + * @param key 键 + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSet(String key, Object... values) { + try { + return redisTemplate.opsForSet().add(key, values); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + /** + * 将set数据放入缓存 + * @param key 键 + * @param time 时间(秒) + * @param values 值 可以是多个 + * @return 成功个数 + */ + public long sSetAndTime(String key, long time, Object... values) { + try { + Long count = redisTemplate.opsForSet().add(key, values); + if (time > 0) { + expire(key, time); + } + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + /** + * 获取set缓存的长度 + * @param key 键 + * @return + */ + public long sGetSetSize(String key) { + try { + return redisTemplate.opsForSet().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + /** + * 移除值为value的 + * @param key 键 + * @param values 值 可以是多个 + * @return 移除的个数 + */ + public long setRemove(String key, Object... values) { + try { + Long count = redisTemplate.opsForSet().remove(key, values); + return count; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + // ===============================list================================= + /** + * 获取list缓存的内容 + * @param key 键 + * @param start 开始 + * @param end 结束 0 到 -1代表所有值 + * @return + */ + public List lGet(String key, long start, long end) { + try { + return redisTemplate.opsForList().range(key, start, end); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + /** + * 获取list缓存的长度 + * @param key 键 + * @return + */ + public long lGetListSize(String key) { + try { + return redisTemplate.opsForList().size(key); + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } + /** + * 通过索引 获取list中的值 + * @param key 键 + * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 + * @return + */ + public Object lGetIndex(String key, long index) { + try { + return redisTemplate.opsForList().index(key, index); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + /** + * 将list放入缓存 + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, Object value) { + try { + redisTemplate.opsForList().rightPush(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 将list放入缓存 + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, Object value, long time) { + try { + redisTemplate.opsForList().rightPush(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 将list放入缓存 + * @param key 键 + * @param value 值 + * @return + */ + public boolean lSet(String key, List value) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 将list放入缓存 + * + * @param key 键 + * @param value 值 + * @param time 时间(秒) + * @return + */ + public boolean lSet(String key, List value, long time) { + try { + redisTemplate.opsForList().rightPushAll(key, value); + if (time > 0) { + expire(key, time); + } + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 根据索引修改list中的某条数据 + * @param key 键 + * @param index 索引 + * @param value 值 + * @return + */ + public boolean lUpdateIndex(String key, long index, Object value) { + try { + redisTemplate.opsForList().set(key, index, value); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + /** + * 移除N个值为value + * @param key 键 + * @param count 移除多少个 + * @param value 值 + * @return 移除的个数 + */ + public long lRemove(String key, long count, Object value) { + try { + Long remove = redisTemplate.opsForList().remove(key, count, value); + return remove; + } catch (Exception e) { + e.printStackTrace(); + return 0; + } + } +} diff --git a/minio-admin/src/main/java/com/mmg/utils/RespEnum.java b/minio-admin/src/main/java/com/mmg/utils/RespEnum.java new file mode 100644 index 0000000..543bdf3 --- /dev/null +++ b/minio-admin/src/main/java/com/mmg/utils/RespEnum.java @@ -0,0 +1,28 @@ +package com.mmg.utils; + +public enum RespEnum { + + UPLOADSUCCESSFUL(1, "上传成功"), + UPLOADING(2, "上传中"), + NOT_UPLOADED(3, "未上传"), + ACCESS_PARAMETER_INVALID(1001,"访问参数无效"), + UPLOAD_FILE_FAILED(1002,"文件上传失败"), + DATA_NOT_EXISTS(1003,"数据不存在"), + ; + + private final Integer code; + private final String message; + + public Integer getCode() { + return code; + } + + public String getMessage() { + return message; + } + + RespEnum(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/minio-admin/src/main/resources/application.yml b/minio-admin/src/main/resources/application.yml new file mode 100644 index 0000000..778441c --- /dev/null +++ b/minio-admin/src/main/resources/application.yml @@ -0,0 +1,35 @@ +server: + port: 9090 +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/minio_upload_file?serverTimezone=Asia/Shanghai&userUnicode=true&useSSL=false&allowPublicKeyRetrieval=true + username: root + password: 123456 + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + data: + redis: + host: 127.0.0.1 + port: 6379 + database: 0 + #password: 123456 +mybatis-plus: + mapper-locations: classpath:mapper/*.xml + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +minio: + endpoint: http://47.103.114.59:9000 + accesskey: OliveSmart + secretkey: OliveSmartTmzl + bucket: playedu + + expiry: 1 #分片对象过期时间 单位(天) + breakpoint-time: 1 #断点续传有效时间,在redis存储任务的时间 单位(天) + +logging: + level: + com.mmg: debug + org.springframework: warn diff --git a/minio-admin/src/main/resources/logback.xml b/minio-admin/src/main/resources/logback.xml new file mode 100644 index 0000000..aa93765 --- /dev/null +++ b/minio-admin/src/main/resources/logback.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + ${log.pattern} + + + + + %d{yyyy-MM-dd HH:mm:ss} [%thread] %magenta(%-5level) %green([%-50.50class]) >>> %cyan(%msg) %n + + + + + + + ${log.path}/info.log + + + + ${log.path}/info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/error.log + + + + ${log.path}/error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/minio-admin/src/test/java/com/mmg/MinioFileUploadApplicationTests.java b/minio-admin/src/test/java/com/mmg/MinioFileUploadApplicationTests.java new file mode 100644 index 0000000..f2e5409 --- /dev/null +++ b/minio-admin/src/test/java/com/mmg/MinioFileUploadApplicationTests.java @@ -0,0 +1,13 @@ +package com.mmg; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class MinioFileUploadApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/minio-fornt/.gitignore b/minio-fornt/.gitignore new file mode 100644 index 0000000..8ee54e8 --- /dev/null +++ b/minio-fornt/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +/cypress/videos/ +/cypress/screenshots/ + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo diff --git a/minio-fornt/.vscode/extensions.json b/minio-fornt/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/minio-fornt/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/minio-fornt/README.md b/minio-fornt/README.md new file mode 100644 index 0000000..a2ff75b --- /dev/null +++ b/minio-fornt/README.md @@ -0,0 +1,29 @@ +# minio-fornt + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Customize configuration + +See [Vite Configuration Reference](https://vitejs.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Compile and Minify for Production + +```sh +npm run build +``` diff --git a/minio-fornt/index.html b/minio-fornt/index.html new file mode 100644 index 0000000..99f583a --- /dev/null +++ b/minio-fornt/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/minio-fornt/jsconfig.json b/minio-fornt/jsconfig.json new file mode 100644 index 0000000..5a1f2d2 --- /dev/null +++ b/minio-fornt/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "exclude": ["node_modules", "dist"] +} diff --git a/minio-fornt/package-lock.json b/minio-fornt/package-lock.json new file mode 100644 index 0000000..b00a8f0 --- /dev/null +++ b/minio-fornt/package-lock.json @@ -0,0 +1,2064 @@ +{ + "name": "minio-fornt", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "minio-fornt", + "version": "0.0.0", + "dependencies": { + "axios": "^1.7.2", + "element-plus": "^2.7.4", + "spark-md5": "^3.0.2", + "vue": "^3.4.21", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "vite": "^5.2.8" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz", + "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", + "dependencies": { + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz", + "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", + "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", + "dependencies": { + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.27", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", + "dependencies": { + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", + "dependencies": { + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", + "dependencies": { + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.2.tgz", + "integrity": "sha512-134clD8u7cBBXdmBbXI282gHGF7T/eAbD/G7mAK2llQF62IbI4ny28IVamZVMoJSvfImC2Xxnj732hXkJvUj6g==" + }, + "node_modules/@vue/reactivity": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", + "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", + "dependencies": { + "@vue/shared": "3.4.27" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", + "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", + "dependencies": { + "@vue/reactivity": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", + "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", + "dependencies": { + "@vue/runtime-core": "3.4.27", + "@vue/shared": "3.4.27", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", + "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27" + }, + "peerDependencies": { + "vue": "3.4.27" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/element-plus": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.7.4.tgz", + "integrity": "sha512-ADBN3zHdhg8R9m6IXR2B5txSWvCn1+nAD+aA9kaJ4rZHMr37DVX6EOdwUjqAMPKz2xC0tculgkJ5rh5zVNiDNQ==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.3", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" + }, + "node_modules/vite": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", + "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", + "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", + "dependencies": { + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-sfc": "3.4.27", + "@vue/runtime-dom": "3.4.27", + "@vue/server-renderer": "3.4.27", + "@vue/shared": "3.4.27" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz", + "integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==", + "dependencies": { + "@vue/devtools-api": "^6.5.1" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + } + }, + "dependencies": { + "@babel/parser": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz", + "integrity": "sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==" + }, + "@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==" + }, + "@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "requires": {} + }, + "@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "dev": true, + "optional": true + }, + "@floating-ui/core": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz", + "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", + "requires": { + "@floating-ui/utils": "^0.2.0" + } + }, + "@floating-ui/dom": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz", + "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==", + "requires": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "@floating-ui/utils": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "@popperjs/core": { + "version": "npm:@sxzz/popperjs-es@2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==" + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "dev": true, + "optional": true + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "@types/lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==" + }, + "@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" + }, + "@vitejs/plugin-vue": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", + "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", + "dev": true, + "requires": {} + }, + "@vue/compiler-core": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", + "requires": { + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.27", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "@vue/compiler-dom": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", + "requires": { + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "@vue/compiler-sfc": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", + "requires": { + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "@vue/compiler-ssr": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", + "requires": { + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "@vue/devtools-api": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.2.tgz", + "integrity": "sha512-134clD8u7cBBXdmBbXI282gHGF7T/eAbD/G7mAK2llQF62IbI4ny28IVamZVMoJSvfImC2Xxnj732hXkJvUj6g==" + }, + "@vue/reactivity": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", + "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", + "requires": { + "@vue/shared": "3.4.27" + } + }, + "@vue/runtime-core": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", + "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", + "requires": { + "@vue/reactivity": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "@vue/runtime-dom": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", + "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", + "requires": { + "@vue/runtime-core": "3.4.27", + "@vue/shared": "3.4.27", + "csstype": "^3.1.3" + } + }, + "@vue/server-renderer": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", + "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", + "requires": { + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "@vue/shared": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==" + }, + "@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "requires": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "requires": {} + } + } + }, + "@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==" + }, + "@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "requires": { + "vue-demi": "*" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", + "requires": {} + } + } + }, + "async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "element-plus": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.7.4.tgz", + "integrity": "sha512-ADBN3zHdhg8R9m6IXR2B5txSWvCn1+nAD+aA9kaJ4rZHMr37DVX6EOdwUjqAMPKz2xC0tculgkJ5rh5zVNiDNQ==", + "requires": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.3", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + } + }, + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, + "esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "requires": {} + }, + "magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + }, + "normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==" + }, + "picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@types/estree": "1.0.5", + "fsevents": "~2.3.2" + } + }, + "source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" + }, + "spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" + }, + "vite": { + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", + "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", + "dev": true, + "requires": { + "esbuild": "^0.20.1", + "fsevents": "~2.3.3", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + } + }, + "vue": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", + "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", + "requires": { + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-sfc": "3.4.27", + "@vue/runtime-dom": "3.4.27", + "@vue/server-renderer": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "vue-router": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz", + "integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==", + "requires": { + "@vue/devtools-api": "^6.5.1" + } + } + } +} diff --git a/minio-fornt/package.json b/minio-fornt/package.json new file mode 100644 index 0000000..f35155b --- /dev/null +++ b/minio-fornt/package.json @@ -0,0 +1,22 @@ +{ + "name": "minio-fornt", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.7.2", + "element-plus": "^2.7.4", + "spark-md5": "^3.0.2", + "vue": "^3.4.21", + "vue-router": "^4.3.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "vite": "^5.2.8" + } +} diff --git a/minio-fornt/public/favicon.ico b/minio-fornt/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/minio-fornt/public/favicon.ico differ diff --git a/minio-fornt/src/App.vue b/minio-fornt/src/App.vue new file mode 100644 index 0000000..5ea4555 --- /dev/null +++ b/minio-fornt/src/App.vue @@ -0,0 +1,10 @@ + + + + + diff --git a/minio-fornt/src/api/upload.js b/minio-fornt/src/api/upload.js new file mode 100644 index 0000000..68ffd6b --- /dev/null +++ b/minio-fornt/src/api/upload.js @@ -0,0 +1,49 @@ +import request from '@/utils/request' + +//上传信息 +export function uploadScreenshot(data){ + return request({ + url:'upload/multipart/uploadScreenshot', + method:'post', + data + }) +} + +//上传信息 +export function uploadFileInfo(data){ + return request({ + url:'upload/multipart/uploadFileInfo', + method:'post', + data + }) +} + +// 上传校验 +export function checkUpload(MD5) { + return request({ + url: `upload/multipart/check?md5=${MD5}`, + method: 'get', + }) +}; + + +// 初始化上传 +export function initUpload(data) { + return request({ + url: `upload/multipart/init`, + method: 'post', + data + }) +}; + + +// 初始化上传 +export function mergeUpload(data) { + return request({ + url: `upload/multipart/merge`, + method: 'post', + data + }) +}; + + diff --git a/minio-fornt/src/assets/logo.svg b/minio-fornt/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/minio-fornt/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/minio-fornt/src/components/icons/IconCommunity.vue b/minio-fornt/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/minio-fornt/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/minio-fornt/src/components/icons/IconDocumentation.vue b/minio-fornt/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/minio-fornt/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/minio-fornt/src/components/icons/IconEcosystem.vue b/minio-fornt/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/minio-fornt/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/minio-fornt/src/components/icons/IconSupport.vue b/minio-fornt/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/minio-fornt/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/minio-fornt/src/components/icons/IconTooling.vue b/minio-fornt/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/minio-fornt/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/minio-fornt/src/main.js b/minio-fornt/src/main.js new file mode 100644 index 0000000..d930802 --- /dev/null +++ b/minio-fornt/src/main.js @@ -0,0 +1,13 @@ +import {createApp} from 'vue' +import App from './App.vue' +import router from './router' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' + +const app = createApp(App) + + +app.use(router) +app.use(ElementPlus) + +app.mount('#app') diff --git a/minio-fornt/src/router/index.js b/minio-fornt/src/router/index.js new file mode 100644 index 0000000..7c594b4 --- /dev/null +++ b/minio-fornt/src/router/index.js @@ -0,0 +1,15 @@ +import { createRouter, createWebHistory } from 'vue-router' +import FileView from "@/views/FileView.vue"; + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'home', + component: FileView + }, + ] +}) + +export default router diff --git a/minio-fornt/src/utils/FileUtil.js b/minio-fornt/src/utils/FileUtil.js new file mode 100644 index 0000000..ba3a5f9 --- /dev/null +++ b/minio-fornt/src/utils/FileUtil.js @@ -0,0 +1,109 @@ + +/** + * @param: fileName - 文件名称 + * @param: 数据返回 1) 无后缀匹配 - false + * @param: 数据返回 2) 匹配图片 - image + * @param: 数据返回 3) 匹配 txt - txt + * @param: 数据返回 4) 匹配 excel - excel + * @param: 数据返回 5) 匹配 word - word + * @param: 数据返回 6) 匹配 pdf - pdf + * @param: 数据返回 7) 匹配 ppt - ppt + * @param: 数据返回 8) 匹配 视频 - video + * @param: 数据返回 9) 匹配 音频 - radio + * @param: 数据返回 10) 其他匹配项 - other + * @author: ljw + **/ + +export function fileSuffixTypeUtil(fileName){ + // 后缀获取 + var suffix = ""; + // 获取类型结果 + var result = ""; + try { + var flieArr = fileName.split("."); + suffix = flieArr[flieArr.length - 1]; + } catch (err) { + suffix = ""; + } + // fileName无后缀返回 false + if (!suffix) { + result = false; + return result; + } + // 图片格式 + var imglist = ["png", "jpg", "jpeg", "bmp", "gif"]; + // 进行图片匹配 + result = imglist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "image"; + return result; + } + // 匹配txt + var txtlist = ["txt"]; + result = txtlist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "txt"; + return result; + } + // 匹配 excel + var excelist = ["xls", "xlsx"]; + result = excelist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "excel"; + return result; + } + // 匹配 word + var wordlist = ["doc", "docx"]; + result = wordlist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "word"; + return result; + } + // 匹配 pdf + var pdflist = ["pdf"]; + result = pdflist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "pdf"; + return result; + } + // 匹配 ppt + var pptlist = ["ppt"]; + result = pptlist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "ppt"; + return result; + } + // 匹配 视频 + var videolist = ["mp4", "m2v", "mkv","ogg", "flv", "avi", "wmv", "rmvb"]; + result = videolist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "video"; + return result; + } + // 匹配 音频 + var radiolist = ["mp3", "wav", "wmv"]; + result = radiolist.some(function (item) { + return item == suffix; + }); + if (result) { + result = "radio"; + return result; + } + // 其他 文件类型 + result = "other"; + return result; +}; diff --git a/minio-fornt/src/utils/request.js b/minio-fornt/src/utils/request.js new file mode 100644 index 0000000..18929ea --- /dev/null +++ b/minio-fornt/src/utils/request.js @@ -0,0 +1,42 @@ +import axios from 'axios' + +const request = axios.create({ + baseURL: `http://localhost:9090`, + timeout: 30000 +}) + +// request 拦截器 +// 可以自请求发送前对请求做一些处理 +// 比如统一加token,对请求参数统一加密 +request.interceptors.request.use(config => { + config.headers['Content-Type'] = 'application/json;charset=utf-8'; + return config +}, error => { + return Promise.reject(error) +}); + +// response 拦截器 +// 可以在接口响应后统一处理结果 +request.interceptors.response.use( + response => { + let res = response.data; + // 如果是返回的文件 + if (response.headers === 'blob') { + return res + } + // 兼容服务端返回的字符串数据 + if (typeof res === 'string') { + res = res ? JSON.parse(res) : res + console.log(res) + } + return res; + }, + error => { + console.log('err' + error) // for debug + return Promise.reject(error) + } +) + + +export default request + diff --git a/minio-fornt/src/views/FileView.vue b/minio-fornt/src/views/FileView.vue new file mode 100644 index 0000000..ed5931d --- /dev/null +++ b/minio-fornt/src/views/FileView.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/minio-fornt/vite.config.js b/minio-fornt/vite.config.js new file mode 100644 index 0000000..5c45e1d --- /dev/null +++ b/minio-fornt/vite.config.js @@ -0,0 +1,16 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +}) diff --git a/sql/minio_upload_file.sql b/sql/minio_upload_file.sql new file mode 100644 index 0000000..546379c --- /dev/null +++ b/sql/minio_upload_file.sql @@ -0,0 +1,42 @@ +/* + Navicat Premium Data Transfer + + Source Server : Local + Source Server Type : MySQL + Source Server Version : 80027 (8.0.27) + Source Host : 127.0.0.1:3306 + Source Schema : minio_upload_file + + Target Server Type : MySQL + Target Server Version : 80027 (8.0.27) + File Encoding : 65001 + + Date: 04/06/2024 13:41:50 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for files +-- ---------------------------- +DROP TABLE IF EXISTS `files`; +CREATE TABLE `files` ( + `id` int NOT NULL AUTO_INCREMENT COMMENT 'id', + `upload_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '分片上传uploadId', + `file_md5` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件md5', + `url` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '下载链接', + `file_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件名称', + `bucket_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '桶名', + `file_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件类型', + `file_size` bigint NULL DEFAULT NULL COMMENT '文件大小(byte)', + `chunk_size` bigint NULL DEFAULT NULL COMMENT '每个分片的大小(byte)', + `chunk_num` int NULL DEFAULT NULL COMMENT '分片数量', + `is_delete` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除', + `enable` tinyint(1) NULL DEFAULT 1 COMMENT '是否禁用链接(0 禁用 1启用)', + `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', + `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 309 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件表' ROW_FORMAT = DYNAMIC; + +SET FOREIGN_KEY_CHECKS = 1;