u
This commit is contained in:
1 parent
ce466130c0
commit
9c026f00ad
36 files changed
+2145
-951
No files matched your search
@@ -83,6 +83,17 @@
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -7,12 +7,14 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication(exclude = {
|
||||
DataSourceAutoConfiguration.class,
|
||||
DruidDataSourceAutoConfigure.class ,
|
||||
})
|
||||
@EnableAsync
|
||||
@EnableAsync // 异步
|
||||
@EnableScheduling // 定时任务
|
||||
public class ApiApplication {
|
||||
public static void main(String[] args){
|
||||
// 初始化雪花id
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.g3soft.config;
|
||||
|
||||
|
||||
import cn.g3soft.utils.PathUtils;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "file")
|
||||
public class FileConfig {
|
||||
private String baseDir;
|
||||
private String tempDir;
|
||||
private String containerDir;
|
||||
|
||||
private String staticPath;
|
||||
private String serverUrl;
|
||||
|
||||
public String getTempPath() {
|
||||
return PathUtils.joinFilePath(baseDir, tempDir);
|
||||
}
|
||||
|
||||
public String getContainerPath(){
|
||||
return PathUtils.joinFilePath(baseDir,containerDir);
|
||||
}
|
||||
|
||||
public String buildTempFilePath(String fileName) {
|
||||
return PathUtils.joinFilePath(baseDir, tempDir, fileName);
|
||||
}
|
||||
|
||||
public String getStaticUrlPrefix() {
|
||||
return PathUtils.joinUrl(serverUrl, staticPath);
|
||||
}
|
||||
|
||||
public String buildTempUrl(String fileName) {
|
||||
return PathUtils.joinUrl(serverUrl, staticPath, tempDir, fileName);
|
||||
}
|
||||
|
||||
public String buildFileUrl(String relativePath) {
|
||||
return PathUtils.joinUrl(serverUrl, staticPath, relativePath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -3,28 +3,38 @@ package cn.g3soft.config;
|
||||
import cn.g3soft.interceptor.AuthInterceptor;
|
||||
import cn.g3soft.interceptor.DataSourceInterceptor;
|
||||
import cn.g3soft.interceptor.RequestWrapperFilter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class WebMvcConfig implements WebMvcConfigurer {
|
||||
@Resource
|
||||
private FileConfig fileConfig;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new AuthInterceptor())
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
"/auth/login",
|
||||
"/error"
|
||||
"/error",
|
||||
"/static/**"
|
||||
);
|
||||
|
||||
registry.addInterceptor(new DataSourceInterceptor())
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
"/auth/login",
|
||||
"/error"
|
||||
"/error",
|
||||
"/static/**"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +43,13 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
||||
FilterRegistrationBean<RequestWrapperFilter> registration = new FilterRegistrationBean<>();
|
||||
registration.setFilter(new RequestWrapperFilter());
|
||||
registration.addUrlPatterns("/*");
|
||||
registration.setOrder(1); // 一定要靠前
|
||||
registration.setOrder(1);
|
||||
return registration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler(fileConfig.getStaticPath() + "/**")
|
||||
.addResourceLocations("file:" + fileConfig.getUploadDir() + "/");
|
||||
}
|
||||
}
|
||||
+19
-114
@@ -7,8 +7,10 @@ import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -18,123 +20,26 @@ import java.util.Map;
|
||||
public class FileController {
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
@Resource
|
||||
private FileUploadUtils fileUploadUtils;
|
||||
|
||||
@PostMapping("/getAliyunKey")
|
||||
public Result<Map<String,Object>> getAliyunKey(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "bucketName");
|
||||
|
||||
String bucketName = (String) params.get("bucketName");
|
||||
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
|
||||
String requestUrl = "http://g3oss.g3soft.cn:8082/upload?orgid=" + orgId + "&bucket=" + bucketName;
|
||||
String reqResult = HttpUtils.sendGet(requestUrl);
|
||||
|
||||
Map<String, Object> objectMap = JsonUtils.toMap(reqResult);
|
||||
|
||||
return Result.ok(objectMap);
|
||||
}
|
||||
|
||||
@PostMapping("/insert")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Long> insertFile(@RequestBody Map<String,Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "father", "mx_xh", "mx_filename", "mx_filesize", "mx_mapfilename", "mx_moduleid");
|
||||
|
||||
String userId = UserContext.get().getUserId();
|
||||
|
||||
long nextId = UniqueId.nextId();
|
||||
|
||||
params.put("subid", nextId);
|
||||
params.put("mx_inputuser_id",userId);
|
||||
params.put("mx_inputdatetime", TimeUtils.getDateTime());
|
||||
|
||||
dbUtils.insert("bf_file","subid",params);
|
||||
|
||||
return Result.ok(nextId);
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteFile(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "subid", "tableName", "bucketName");
|
||||
|
||||
String tableName = params.get("tableName").toString();
|
||||
String bucketName = params.get("bucketName").toString();
|
||||
String id = params.get("subid").toString();
|
||||
|
||||
// 查询数据
|
||||
List<Map<String, Object>> maps = dbUtils.loadData("SELECT * FROM " + tableName + " WHERE subid = ?", id);
|
||||
|
||||
// 如果没有数据,直接返回删除成功
|
||||
if (ObjectUtils.isEmpty(maps)) {
|
||||
return Result.ok("删除成功");
|
||||
}
|
||||
|
||||
String mxMapfilename = maps.get(0).get("mx_mapfilename").toString();
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
int index = mxMapfilename.indexOf(orgId + "/");
|
||||
|
||||
// 如果没找到,就转换orgId为大写
|
||||
if (index < 0) {
|
||||
orgId = orgId.toUpperCase();
|
||||
index = mxMapfilename.indexOf(orgId + "/");
|
||||
}
|
||||
|
||||
String objectName = mxMapfilename.substring(index);
|
||||
|
||||
// 删除记录
|
||||
Map<String, Object> deleteParams = new HashMap<>();
|
||||
deleteParams.put("subid", id);
|
||||
int deleteCount = dbUtils.delete(tableName, deleteParams);
|
||||
|
||||
// 如果删除成功,发送删除请求
|
||||
if (deleteCount > 0) {
|
||||
if(StringUtils.isBlank(objectName)){
|
||||
return Result.fail("删除失败,文件格式有误");
|
||||
}
|
||||
String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
|
||||
/**
|
||||
* 上传临时文件接口
|
||||
*/
|
||||
@PostMapping("/uploadTemp")
|
||||
public Result<Map<String, String>> uploadTemp(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "fileName") String fileName) {
|
||||
Map<String, String> response = new HashMap<>();
|
||||
try {
|
||||
HttpUtils.sendGet(requestUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("删除文件失败",e);
|
||||
// 调用工具类上传临时文件,返回临时 URL
|
||||
String tempUrl = fileUploadUtils.uploadTemp(file,fileName);
|
||||
|
||||
// 返回临时文件 URL
|
||||
response.put("tempUrl", tempUrl);
|
||||
return Result.ok(response);
|
||||
} catch (IOException e) {
|
||||
return Result.fail("文件上传失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/deleteAliyunFile")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteAliyunFile(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "bucketName", "mx_mapfilename");
|
||||
|
||||
String bucketName = params.get("bucketName").toString();
|
||||
String mxMapfilename = params.get("mx_mapfilename").toString();
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
|
||||
int index = mxMapfilename.indexOf(orgId + "/");
|
||||
// 如果没找到,就转换orgId为大写
|
||||
if (index < 0) {
|
||||
orgId = orgId.toUpperCase();
|
||||
index = mxMapfilename.indexOf(orgId + "/");
|
||||
}
|
||||
|
||||
String objectName = mxMapfilename.substring(index);
|
||||
if(StringUtils.isBlank(objectName)){
|
||||
return Result.fail("删除失败,文件格式有误");
|
||||
}
|
||||
|
||||
// 发送删除请求
|
||||
String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
|
||||
|
||||
System.out.println(requestUrl);
|
||||
|
||||
try {
|
||||
HttpUtils.sendGet(requestUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("删除文件失败",e);
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -33,7 +33,7 @@ public class OcrController {
|
||||
reqPayload.put("visualize", false);
|
||||
|
||||
try {
|
||||
String response = HttpUtils.sendPostJson(requestUrl, reqPayload);
|
||||
String response = HttpUtils.postJson(requestUrl, reqPayload);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode root = mapper.readTree(response);
|
||||
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package cn.g3soft.controller;
|
||||
|
||||
import cn.g3soft.context.UserContext;
|
||||
import cn.g3soft.utils.*;
|
||||
import cn.g3soft.utils.uniqueId.idgen.UniqueId;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/file")
|
||||
public class OssController {
|
||||
@Resource
|
||||
private DbUtils dbUtils;
|
||||
|
||||
@PostMapping("/getAliyunKey")
|
||||
public Result<Map<String,Object>> getAliyunKey(@RequestBody Map<String, Object> params) throws Exception {
|
||||
ValidationUtils.checkRequiredFields(params, "bucketName");
|
||||
|
||||
String bucketName = (String) params.get("bucketName");
|
||||
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
|
||||
String requestUrl = "http://g3oss.g3soft.cn:8082/upload?orgid=" + orgId + "&bucket=" + bucketName;
|
||||
String reqResult = HttpUtils.get(requestUrl);
|
||||
|
||||
Map<String, Object> objectMap = JsonUtils.toMap(reqResult);
|
||||
|
||||
return Result.ok(objectMap);
|
||||
}
|
||||
|
||||
@PostMapping("/insert")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Long> insertFile(@RequestBody Map<String,Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "father", "mx_xh", "mx_filename", "mx_filesize", "mx_mapfilename", "mx_moduleid");
|
||||
|
||||
String userId = UserContext.get().getUserId();
|
||||
|
||||
long nextId = UniqueId.nextId();
|
||||
|
||||
params.put("subid", nextId);
|
||||
params.put("mx_inputuser_id",userId);
|
||||
params.put("mx_inputdatetime", TimeUtils.getDateTime());
|
||||
|
||||
dbUtils.insert("bf_file","subid",params);
|
||||
|
||||
return Result.ok(nextId);
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteFile(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "subid", "tableName", "bucketName");
|
||||
|
||||
String tableName = params.get("tableName").toString();
|
||||
String bucketName = params.get("bucketName").toString();
|
||||
String id = params.get("subid").toString();
|
||||
|
||||
// 查询数据
|
||||
List<Map<String, Object>> maps = dbUtils.loadData("SELECT * FROM " + tableName + " WHERE subid = ?", id);
|
||||
|
||||
// 如果没有数据,直接返回删除成功
|
||||
if (ObjectUtils.isEmpty(maps)) {
|
||||
return Result.ok("删除成功");
|
||||
}
|
||||
|
||||
String mxMapfilename = maps.get(0).get("mx_mapfilename").toString();
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
int index = mxMapfilename.indexOf(orgId + "/");
|
||||
|
||||
// 如果没找到,就转换orgId为大写
|
||||
if (index < 0) {
|
||||
orgId = orgId.toUpperCase();
|
||||
index = mxMapfilename.indexOf(orgId + "/");
|
||||
}
|
||||
|
||||
String objectName = mxMapfilename.substring(index);
|
||||
|
||||
// 删除记录
|
||||
Map<String, Object> deleteParams = new HashMap<>();
|
||||
deleteParams.put("subid", id);
|
||||
int deleteCount = dbUtils.delete(tableName, deleteParams);
|
||||
|
||||
// 如果删除成功,发送删除请求
|
||||
if (deleteCount > 0) {
|
||||
if(StringUtils.isBlank(objectName)){
|
||||
return Result.fail("删除失败,文件格式有误");
|
||||
}
|
||||
String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
|
||||
try {
|
||||
HttpUtils.get(requestUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("删除文件失败",e);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
|
||||
@PostMapping("/deleteAliyunFile")
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteAliyunFile(@RequestBody Map<String, Object> params) {
|
||||
ValidationUtils.checkRequiredFields(params, "bucketName", "mx_mapfilename");
|
||||
|
||||
String bucketName = params.get("bucketName").toString();
|
||||
String mxMapfilename = params.get("mx_mapfilename").toString();
|
||||
String orgId = UserContext.get().getOrgId();
|
||||
|
||||
int index = mxMapfilename.indexOf(orgId + "/");
|
||||
// 如果没找到,就转换orgId为大写
|
||||
if (index < 0) {
|
||||
orgId = orgId.toUpperCase();
|
||||
index = mxMapfilename.indexOf(orgId + "/");
|
||||
}
|
||||
|
||||
String objectName = mxMapfilename.substring(index);
|
||||
if(StringUtils.isBlank(objectName)){
|
||||
return Result.fail("删除失败,文件格式有误");
|
||||
}
|
||||
|
||||
// 发送删除请求
|
||||
String requestUrl = String.format("http://g3oss.g3soft.cn:8082/delete?bucketName=%s&objectName=%s", bucketName, objectName);
|
||||
|
||||
System.out.println(requestUrl);
|
||||
|
||||
try {
|
||||
HttpUtils.get(requestUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("删除文件失败",e);
|
||||
}
|
||||
|
||||
return Result.ok();
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package cn.g3soft.schedule;
|
||||
|
||||
import cn.g3soft.config.FileConfig;
|
||||
import cn.g3soft.utils.PathUtils;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
|
||||
@Component
|
||||
public class TempFIleSchedule {
|
||||
@Resource
|
||||
private FileConfig fileConfig;
|
||||
|
||||
/**
|
||||
* 定时清理临时文件(临时文件超过1天后清理)
|
||||
*/
|
||||
@Scheduled(cron = "0 0 3 * * ?") // 每天3点清理
|
||||
public void cleanTempFiles() {
|
||||
File tempDir = new File(PathUtils.joinFilePath(fileConfig.getTempPath()));
|
||||
if (tempDir.exists()) {
|
||||
File[] files = tempDir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (isFileExpired(file)) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断文件是否过期(假设超过1天的文件删除)
|
||||
* @param file 文件
|
||||
* @return 是否过期
|
||||
*/
|
||||
private boolean isFileExpired(File file) {
|
||||
long lastModified = file.lastModified();
|
||||
long now = System.currentTimeMillis();
|
||||
return now - lastModified > 86400000L; // 1天的毫秒数
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import cn.g3soft.config.FileConfig;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 通用文件上传工具类
|
||||
* 规则:
|
||||
* - 文件名保持原始或指定
|
||||
* - 同一天同箱号重复 -> 自动生成 _1/_2/... 文件夹
|
||||
* - 使用 FileUtils 拼接路径
|
||||
*/
|
||||
@Component
|
||||
public class FileContainerUtils {
|
||||
|
||||
@Resource
|
||||
private FileConfig fileConfig;
|
||||
|
||||
/**
|
||||
* 单文件上传
|
||||
*/
|
||||
public String upload(MultipartFile file, String containerNo, String fileName) throws IOException {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new RuntimeException("文件不能为空");
|
||||
}
|
||||
|
||||
// 1. 日期路径 yyyy/MM/dd
|
||||
String datePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
|
||||
|
||||
// 2. 基础目录:上传目录 + 日期
|
||||
String basePath = PathUtils.joinFilePath(fileConfig.getContainerPath(), datePath);
|
||||
|
||||
// 3. 计算最终目录(箱号 + 重复批次)
|
||||
File finalDir = resolveContainerDir(basePath, containerNo);
|
||||
|
||||
if (!finalDir.exists()) {
|
||||
finalDir.mkdirs();
|
||||
}
|
||||
|
||||
// 4. 最终文件
|
||||
File dest = new File(finalDir, fileName);
|
||||
file.transferTo(dest);
|
||||
|
||||
// 5. 相对路径,用于生成URL
|
||||
String relativePath = PathUtils.joinFilePath(
|
||||
datePath,
|
||||
finalDir.getName(),
|
||||
fileName
|
||||
);
|
||||
|
||||
return fileConfig.buildFileUrl(relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多文件上传
|
||||
*/
|
||||
public List<String> uploadBatch(MultipartFile[] files, String containerNo, String[] fileNames) throws IOException {
|
||||
if (files == null || files.length == 0) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<String> urls = new ArrayList<>();
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
String name = (fileNames != null && i < fileNames.length) ? fileNames[i] : files[i].getOriginalFilename();
|
||||
urls.add(upload(files[i], containerNo, name));
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析容器目录
|
||||
* - 如果同一天已存在该箱号目录 -> 自动加 _1/_2/... 避免覆盖
|
||||
*/
|
||||
private File resolveContainerDir(String basePath, String containerNo) {
|
||||
File containerDir = new File(PathUtils.joinFilePath(basePath, containerNo));
|
||||
|
||||
if (!containerDir.exists()) {
|
||||
return containerDir;
|
||||
}
|
||||
|
||||
// 已存在 -> 找下一个可用序号
|
||||
int i = 1;
|
||||
while (true) {
|
||||
File dir = new File(PathUtils.joinFilePath(basePath, containerNo + "_" + i));
|
||||
if (!dir.exists()) {
|
||||
return dir;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import cn.g3soft.config.FileConfig;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class FileUploadUtils {
|
||||
|
||||
@Resource
|
||||
private FileConfig fileConfig;
|
||||
|
||||
/**
|
||||
* 上传文件到临时路径
|
||||
* @param file 上传的文件
|
||||
* @param fileName 如果前端传递了文件名,使用传递的文件名
|
||||
* @return 临时文件的URL
|
||||
*/
|
||||
public String uploadTemp(MultipartFile file, String fileName) throws IOException {
|
||||
// 1. 生成最终文件名
|
||||
String finalFileName = generateTempFileName(file, fileName);
|
||||
|
||||
// 2. 临时目录路径
|
||||
File tempDir = new File(fileConfig.getTempPath());
|
||||
|
||||
// 如果临时目录不存在,创建目录
|
||||
if (!tempDir.exists()) {
|
||||
tempDir.mkdirs();
|
||||
}
|
||||
|
||||
// 3. 保存文件到临时目录
|
||||
File tempFile = new File(tempDir, finalFileName);
|
||||
file.transferTo(tempFile);
|
||||
|
||||
// 4. 返回临时文件的 URL(供 OCR 使用)
|
||||
return fileConfig.buildTempUrl(finalFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件名生成临时文件名(带时间戳 + UUID + 文件后缀)
|
||||
* @param file 上传的文件
|
||||
* @param fileName 传递的文件名(可选)
|
||||
* @return 生成的最终文件名
|
||||
*/
|
||||
private String generateTempFileName(MultipartFile file, String fileName) {
|
||||
// 优先使用前端传递的 fileName
|
||||
String originalFilename = (fileName != null && !fileName.isEmpty())
|
||||
? fileName
|
||||
: file.getOriginalFilename();
|
||||
|
||||
// 如果没有获取到文件名,设置一个默认文件名
|
||||
if (originalFilename == null) {
|
||||
originalFilename = "file";
|
||||
}
|
||||
|
||||
// 获取文件后缀
|
||||
String extension = "";
|
||||
if (originalFilename.contains(".")) {
|
||||
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||
}
|
||||
|
||||
// 生成带时间戳和 UUID 的文件名
|
||||
return LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + "_"
|
||||
+ UUID.randomUUID().toString().replace("-", "") + extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除临时文件(成功识别后删除)
|
||||
* @param tempFileName 临时文件的文件名
|
||||
*/
|
||||
public void deleteTempFile(String tempFileName) {
|
||||
String filePath = PathUtils.joinFilePath(fileConfig.getBaseDir(), fileConfig.getTempDir(), tempFileName);
|
||||
|
||||
File tempFile = new File(tempFileName);
|
||||
if (tempFile.exists()) {
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
* =========================
|
||||
* 最简调用:自动生成文件名 + 不覆盖
|
||||
* =========================
|
||||
*/
|
||||
public static String upload(MultipartFile file, String uploadPath) throws IOException {
|
||||
return upload(file, uploadPath, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* =========================
|
||||
* 指定文件名 + 不覆盖
|
||||
* =========================
|
||||
*/
|
||||
public static String upload(MultipartFile file,
|
||||
String uploadPath,
|
||||
String fileName) throws IOException {
|
||||
return upload(file, uploadPath, fileName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* =========================
|
||||
* 控制是否覆盖 + 自动生成文件名
|
||||
* =========================
|
||||
*/
|
||||
public static String upload(MultipartFile file,
|
||||
String uploadPath,
|
||||
boolean overwrite) throws IOException {
|
||||
return upload(file, uploadPath, null, overwrite);
|
||||
}
|
||||
|
||||
/**
|
||||
* =========================
|
||||
* 核心方法(唯一执行逻辑)
|
||||
* =========================
|
||||
*/
|
||||
public static String upload(MultipartFile file,
|
||||
String uploadPath,
|
||||
String fileName,
|
||||
boolean overwrite) throws IOException {
|
||||
|
||||
// 1. 校验文件
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw new RuntimeException("上传文件不能为空");
|
||||
}
|
||||
|
||||
// 2. 创建目录
|
||||
File dir = new File(uploadPath);
|
||||
if (!dir.exists()) {
|
||||
boolean created = dir.mkdirs();
|
||||
if (!created) {
|
||||
throw new RuntimeException("目录创建失败: " + uploadPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 获取原始文件名
|
||||
String originalName = file.getOriginalFilename();
|
||||
|
||||
// 4. 处理文件名
|
||||
if (fileName == null || fileName.trim().isEmpty()) {
|
||||
fileName = generateFileName(originalName);
|
||||
} else {
|
||||
// 如果用户传了文件名,但没有后缀,则补上
|
||||
if (!fileName.contains(".") && originalName != null && originalName.contains(".")) {
|
||||
fileName = fileName + getSuffix(originalName);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 目标文件
|
||||
File dest = new File(dir, fileName);
|
||||
|
||||
// 6. 处理覆盖逻辑
|
||||
if (dest.exists() && !overwrite) {
|
||||
String newName = generateFileName(fileName);
|
||||
dest = new File(dir, newName);
|
||||
fileName = newName;
|
||||
}
|
||||
|
||||
// 7. 保存文件
|
||||
file.transferTo(dest);
|
||||
|
||||
// 8. 返回文件路径(你也可以改成返回 URL)
|
||||
return dest.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* =========================
|
||||
* 生成唯一文件名
|
||||
* =========================
|
||||
*/
|
||||
private static String generateFileName(String originalName) {
|
||||
return UUID.randomUUID().toString().replace("-", "")
|
||||
+ getSuffix(originalName);
|
||||
}
|
||||
|
||||
/**
|
||||
* =========================
|
||||
* 获取文件后缀
|
||||
* =========================
|
||||
*/
|
||||
private static String getSuffix(String fileName) {
|
||||
if (fileName == null || !fileName.contains(".")) {
|
||||
return "";
|
||||
}
|
||||
return fileName.substring(fileName.lastIndexOf("."));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,12 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.ConnectException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import okhttp3.*;
|
||||
|
||||
/**
|
||||
* 通用http发送方法
|
||||
@@ -32,287 +15,92 @@ import org.springframework.http.MediaType;
|
||||
*/
|
||||
public class HttpUtils
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* 向指定 URL 发送GET方法的请求
|
||||
*
|
||||
* @param url 发送请求的 URL
|
||||
* @return 所代表远程资源的响应结果
|
||||
*/
|
||||
public static String sendGet(String url)
|
||||
{
|
||||
return sendGet(url, StringUtils.EMPTY);
|
||||
}
|
||||
private static final OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(10, TimeUnit.SECONDS)
|
||||
.writeTimeout(10, TimeUnit.SECONDS)
|
||||
.connectionPool(new ConnectionPool(50, 5, TimeUnit.MINUTES))
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 向指定 URL 发送GET方法的请求
|
||||
*
|
||||
* @param url 发送请求的 URL
|
||||
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
|
||||
* @return 所代表远程资源的响应结果
|
||||
*/
|
||||
public static String sendGet(String url, String param)
|
||||
{
|
||||
return sendGet(url, param, "UTF-8");
|
||||
}
|
||||
private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||
|
||||
/**
|
||||
* 向指定 URL 发送GET方法的请求
|
||||
*
|
||||
* @param url 发送请求的 URL
|
||||
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
|
||||
* @param contentType 编码类型
|
||||
* @return 所代表远程资源的响应结果
|
||||
*/
|
||||
public static String sendGet(String url, String param, String contentType)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
BufferedReader in = null;
|
||||
try
|
||||
{
|
||||
String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
|
||||
log.info("sendGet - {}", urlNameString);
|
||||
URL realUrl = new URL(urlNameString);
|
||||
URLConnection connection = realUrl.openConnection();
|
||||
connection.setRequestProperty("accept", "*/*");
|
||||
connection.setRequestProperty("connection", "Keep-Alive");
|
||||
connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
||||
connection.connect();
|
||||
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
|
||||
String line;
|
||||
while ((line = in.readLine()) != null)
|
||||
{
|
||||
result.append(line);
|
||||
}
|
||||
log.info("recv - {}", result);
|
||||
}
|
||||
catch (ConnectException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (SocketTimeoutException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (in != null)
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
// ==================== GET ====================
|
||||
public static String get(String url) throws Exception {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 向指定 URL 发送POST方法的请求
|
||||
*
|
||||
* @param url 发送请求的 URL
|
||||
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
|
||||
* @return 所代表远程资源的响应结果
|
||||
*/
|
||||
public static String sendPost(String url, String param)
|
||||
{
|
||||
return sendPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定 URL 发送POST方法的请求
|
||||
*
|
||||
* @param url 发送请求的 URL
|
||||
* @param param 请求参数
|
||||
* @param contentType 内容类型
|
||||
* @return 所代表远程资源的响应结果
|
||||
*/
|
||||
public static String sendPost(String url, String param, String contentType)
|
||||
{
|
||||
PrintWriter out = null;
|
||||
BufferedReader in = null;
|
||||
StringBuilder result = new StringBuilder();
|
||||
try
|
||||
{
|
||||
log.info("sendPost - {}", url);
|
||||
URL realUrl = new URL(url);
|
||||
URLConnection conn = realUrl.openConnection();
|
||||
conn.setRequestProperty("accept", "*/*");
|
||||
conn.setRequestProperty("connection", "Keep-Alive");
|
||||
conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
||||
conn.setRequestProperty("Accept-Charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Type", contentType);
|
||||
conn.setDoOutput(true);
|
||||
conn.setDoInput(true);
|
||||
out = new PrintWriter(conn.getOutputStream());
|
||||
out.print(param);
|
||||
out.flush();
|
||||
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
|
||||
String line;
|
||||
while ((line = in.readLine()) != null)
|
||||
{
|
||||
result.append(line);
|
||||
}
|
||||
log.info("recv - {}", result);
|
||||
}
|
||||
catch (ConnectException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (SocketTimeoutException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (out != null)
|
||||
{
|
||||
out.close();
|
||||
}
|
||||
if (in != null)
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定 URL 发送 POST 请求,内容类型为 JSON
|
||||
*
|
||||
* @param url 请求的 URL
|
||||
* @param reqPayload 请求参数,Map 类型
|
||||
* @return 响应结果
|
||||
*/
|
||||
public static String sendPostJson(String url, Map<String, Object> reqPayload) {
|
||||
// 创建一个 ObjectMapper 实例,用于将 Map 转换为 JSON 字符串
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
String jsonParam = null;
|
||||
try {
|
||||
// 将 Map 转换为 JSON 字符串
|
||||
jsonParam = objectMapper.writeValueAsString(reqPayload);
|
||||
} catch (Exception e) {
|
||||
log.error("请求参数转换为 JSON 失败,url=" + url, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 调用 sendPost 方法,发送请求并返回响应
|
||||
return sendPost(url, jsonParam, "application/json");
|
||||
}
|
||||
|
||||
public static String sendSSLPost(String url, String param)
|
||||
{
|
||||
return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
}
|
||||
|
||||
public static String sendSSLPost(String url, String param, String contentType)
|
||||
{
|
||||
StringBuilder result = new StringBuilder();
|
||||
String urlNameString = url + "?" + param;
|
||||
try
|
||||
{
|
||||
log.info("sendSSLPost - {}", urlNameString);
|
||||
SSLContext sc = SSLContext.getInstance("SSL");
|
||||
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
|
||||
URL console = new URL(urlNameString);
|
||||
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
|
||||
conn.setRequestProperty("accept", "*/*");
|
||||
conn.setRequestProperty("connection", "Keep-Alive");
|
||||
conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
|
||||
conn.setRequestProperty("Accept-Charset", "utf-8");
|
||||
conn.setRequestProperty("Content-Type", contentType);
|
||||
conn.setDoOutput(true);
|
||||
conn.setDoInput(true);
|
||||
|
||||
conn.setSSLSocketFactory(sc.getSocketFactory());
|
||||
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
|
||||
conn.connect();
|
||||
InputStream is = conn.getInputStream();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(is));
|
||||
String ret = "";
|
||||
while ((ret = br.readLine()) != null)
|
||||
{
|
||||
if (ret != null && !"".equals(ret.trim()))
|
||||
{
|
||||
result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
log.info("recv - {}", result);
|
||||
conn.disconnect();
|
||||
br.close();
|
||||
}
|
||||
catch (ConnectException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (SocketTimeoutException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static class TrustAnyTrustManager implements X509TrustManager
|
||||
{
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers()
|
||||
{
|
||||
return new X509Certificate[] {};
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
return response.body() != null ? response.body().string() : null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TrustAnyHostnameVerifier implements HostnameVerifier
|
||||
{
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session)
|
||||
{
|
||||
return true;
|
||||
public static String get(String url, Map<String, Object> params, String contentType) throws Exception {
|
||||
HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder();
|
||||
|
||||
if (params != null) {
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
if (entry.getValue() != null) {
|
||||
urlBuilder.addQueryParameter(
|
||||
entry.getKey(),
|
||||
String.valueOf(entry.getValue())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Request.Builder builder = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.get();
|
||||
|
||||
if (contentType != null && !contentType.isEmpty()) {
|
||||
builder.addHeader("Content-Type", contentType);
|
||||
}
|
||||
|
||||
try (Response response = client.newCall(builder.build()).execute()) {
|
||||
return response.body() != null ? response.body().string() : null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== POST FORM ====================
|
||||
public static String postForm(String url, Map<String, String> params) throws Exception {
|
||||
FormBody.Builder builder = new FormBody.Builder();
|
||||
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
builder.add(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.post(builder.build())
|
||||
.build();
|
||||
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
return response.body() != null ? response.body().string() : null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== POST JSON ====================
|
||||
// ==================== POST JSON(Map版本)====================
|
||||
public static String postJson(String url, Map<String, Object> params) throws Exception {
|
||||
|
||||
// 1. Map 转 JSON 字符串
|
||||
String json = MAPPER.writeValueAsString(params);
|
||||
|
||||
// 2. 构建请求体
|
||||
RequestBody body = RequestBody.create(json, JSON);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
// 3. 执行请求
|
||||
try (Response response = client.newCall(request).execute()) {
|
||||
return response.body() != null ? response.body().string() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.g3soft.utils;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class PathUtils {
|
||||
public static String joinUrl(String... parts) {
|
||||
if (parts == null || parts.length == 0) return "";
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for (String part : parts) {
|
||||
if (part == null || part.trim().isEmpty()) continue;
|
||||
|
||||
part = part.trim();
|
||||
|
||||
if (sb.length() == 0) {
|
||||
// 第一段:保留协议(http://)
|
||||
sb.append(part.replaceAll("/+$", ""));
|
||||
} else {
|
||||
sb.append("/");
|
||||
sb.append(part.replaceAll("^/+", "").replaceAll("/+$", ""));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String joinFilePath(String... paths) {
|
||||
if (paths == null || paths.length == 0) return "";
|
||||
Path result = null;
|
||||
for (String p : paths) {
|
||||
if (p == null || p.trim().isEmpty()) continue;
|
||||
if (result == null) {
|
||||
result = Paths.get(p.trim());
|
||||
} else {
|
||||
result = result.resolve(p.trim());
|
||||
}
|
||||
}
|
||||
return (result == null) ? "" : result.normalize().toString();
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -7,6 +7,9 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AddressUtils
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(AddressUtils.class);
|
||||
@@ -28,7 +31,12 @@ public class AddressUtils
|
||||
|
||||
try
|
||||
{
|
||||
String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", "GBK");
|
||||
|
||||
Map<String,Object> params = new HashMap<>();
|
||||
params.put("ip",ip);
|
||||
params.put("json",true);
|
||||
|
||||
String rspStr = HttpUtils.get(IP_URL, params, "GBK");
|
||||
if (StringUtils.isEmpty(rspStr))
|
||||
{
|
||||
log.error("获取地理位置异常 {}", ip);
|
||||
|
||||
@@ -17,3 +17,11 @@ spring:
|
||||
jackson:
|
||||
time-zone: "Asia/Shanghai"
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
|
||||
file:
|
||||
static-path: /static/file
|
||||
server-url: http://192.168.1.137:80/api
|
||||
|
||||
base-dir: D:/data
|
||||
container-dir: container
|
||||
temp-dir: temp
|
||||
@@ -3,8 +3,8 @@ username=sa
|
||||
password=zhang520..
|
||||
driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
# url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3BoxOCR
|
||||
# username=sa
|
||||
# password=123456@aaa
|
||||
# driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
|
||||
#url=jdbc:sqlserver://127.0.0.1:1433;databaseName=G3BoxOCR
|
||||
#username=sa
|
||||
#password=123456@aaa
|
||||
#driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
|
||||
@@ -0,0 +1,84 @@
|
||||
package cn.g3soft;
|
||||
|
||||
|
||||
import cn.g3soft.utils.HttpUtils;
|
||||
import cn.g3soft.utils.Result;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@SpringBootTest
|
||||
public class DEMO {
|
||||
@Test
|
||||
public void testHello() throws Exception {
|
||||
long startTime = System.nanoTime(); // 开始时间
|
||||
|
||||
String filePath = "D:\\100全关门-3\\20241005082220A664.jpg"; // 你的图片路径
|
||||
|
||||
byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
|
||||
|
||||
// String fileUrl = Base64.getEncoder().encodeToString(fileBytes);
|
||||
|
||||
String fileUrl = "http://g3hd.oss-cn-hangzhou.aliyuncs.com/g3hd/2026/03/28/1774665878210_960.jpg";
|
||||
|
||||
String requestUrl = "http://127.0.0.1:8867/ocr";
|
||||
|
||||
Map<String, Object> reqPayload = new HashMap<>();
|
||||
reqPayload.put("file", fileUrl);
|
||||
reqPayload.put("fileType", 1);
|
||||
reqPayload.put("visualize", false);
|
||||
|
||||
try {
|
||||
String response = HttpUtils.postJson(requestUrl, reqPayload);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode root = mapper.readTree(response);
|
||||
|
||||
JsonNode ocrResults = root.path("result").path("ocrResults");
|
||||
if (!ocrResults.isArray() || ocrResults.isEmpty()) {
|
||||
System.out.println("空");
|
||||
}
|
||||
|
||||
String number = "";
|
||||
double maxScore = -1.0;
|
||||
|
||||
for (JsonNode item : ocrResults) {
|
||||
JsonNode pruned = item.path("prunedResult");
|
||||
JsonNode recTexts = pruned.path("rec_texts");
|
||||
JsonNode recScores = pruned.path("rec_scores");
|
||||
|
||||
for (int i = 0; i < recTexts.size(); i++) {
|
||||
double score = recScores.get(i).asDouble();
|
||||
if (score > maxScore) {
|
||||
maxScore = score;
|
||||
number = recTexts.get(i).asText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("boxNumber", number);
|
||||
result.put("score", maxScore);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
long endTime = System.nanoTime(); // 结束时间
|
||||
long durationMs = (endTime - startTime) / 1_000_000;
|
||||
|
||||
System.out.println("耗时: " + durationMs + " ms");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"editor.cursorSmoothCaretAnimation": "on",
|
||||
// Disable the default formatter, use eslint instead
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
VITE_APP_PORT = 8081
|
||||
VITE_APP_PORT = 8080
|
||||
|
||||
VITE_ALIYUN_BUCKET = 'g3hd'
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
||||
import { useUserStore } from './store'
|
||||
import PLATFORM from './utils/platform'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
|
||||
import { dialog, isImageUrl, isVideoUrl, toast } from 'sard-uniapp'
|
||||
import { aliyunpluploadApi, deleteAliyunFileApi, deleteFileApi, getUniqueIdApi, recognizeOcrBoxApi, saveObjectApi } from '@/request/api'
|
||||
import { downloadFile, getFileExtension, imageCompress, isImage, uploadFileToAliyun } from '@/utils/file'
|
||||
import { aliyunpluploadApi, deleteAliyunFileApi, deleteFileApi, getUniqueIdApi, recognizeOcrBoxApi, saveObjectApi, uploadTempFileApi } from '@/request/api'
|
||||
import { downloadFile, getFileExtension, imageCompress, isImage } from '@/utils/file'
|
||||
import { uploadToOss } from '@/utils/oss'
|
||||
|
||||
const boxImage = ref(null)
|
||||
|
||||
@@ -24,11 +25,17 @@ const subFileList = ref([])
|
||||
const tipContainerNo = ref('')
|
||||
|
||||
onLoad(async () => {
|
||||
toast.loading('正在初始化中...', {
|
||||
overlay: true,
|
||||
})
|
||||
|
||||
await getUniqueIdApi().then((res) => {
|
||||
if (res.success) {
|
||||
form.value.b_id = res.data
|
||||
}
|
||||
})
|
||||
|
||||
toast.hide()
|
||||
})
|
||||
|
||||
function handleChooseBox() {
|
||||
@@ -46,14 +53,11 @@ function handleChooseBox() {
|
||||
})
|
||||
|
||||
imageCompress(tempFile.path).then((compressRes) => {
|
||||
uploadFileToAliyun(compressRes.path, tempFile.name, 'image').then((res) => {
|
||||
if (res.url) {
|
||||
form.value.containerImageUrl = res.url
|
||||
|
||||
ocrFinish.value = false
|
||||
|
||||
console.log(compressRes)
|
||||
uploadTempFileApi(compressRes.path, tempFile.name).then((res) => {
|
||||
if (res.success) {
|
||||
recognizeOcrBoxApi({
|
||||
fileUrl: form.value.containerImageUrl,
|
||||
fileUrl: res.data.tempUrl,
|
||||
}).then((ocrRes) => {
|
||||
if (ocrRes.success) {
|
||||
toast.success('识别成功,请检查箱号是否正确')
|
||||
@@ -132,31 +136,6 @@ function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
|
||||
})
|
||||
}
|
||||
|
||||
async function afterRead(fileItem: UploadFileItem) {
|
||||
// 设置上传中状态
|
||||
fileItem.status = 'uploading'
|
||||
fileItem.message = '正在上传'
|
||||
|
||||
fileItem.isUpload = false
|
||||
|
||||
// 上传到阿里云
|
||||
const res = await uploadFileToAliyun(
|
||||
fileItem.file.path,
|
||||
fileItem.file.name,
|
||||
fileItem.file.type,
|
||||
)
|
||||
|
||||
if (!res.url)
|
||||
throw new Error('上传失败:未获取到文件 URL')
|
||||
|
||||
// 上传完成
|
||||
fileItem.status = 'done'
|
||||
fileItem.message = '上传成功'
|
||||
fileItem.url = res.url
|
||||
|
||||
subFileList.value = [...subFileList.value]
|
||||
}
|
||||
|
||||
function beforeRemove(index: number, fileItem: UploadFileItem): boolean | Promise<void> {
|
||||
console.log(fileItem)
|
||||
|
||||
@@ -172,15 +151,13 @@ function beforeRemove(index: number, fileItem: UploadFileItem): boolean | Promis
|
||||
}
|
||||
|
||||
function onRemove(index: number, item: UploadFileItem) {
|
||||
if (!item.isUpload) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item.status === 'done') {
|
||||
deleteAliyunFileApi(item.url).then((res) => {
|
||||
if (res.success) {
|
||||
toast.success('删除成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
@@ -204,74 +181,76 @@ async function handleConfirm() {
|
||||
transparent: false,
|
||||
})
|
||||
|
||||
// 图片已经上传成功,保存主表即可
|
||||
const mainTable = {
|
||||
b_id: form.value.b_id,
|
||||
b_container_no: form.value.containerNo,
|
||||
b_container_img: form.value.containerImageUrl,
|
||||
b_ocr_no: form.value.ocrNo,
|
||||
b_ocr_score: form.value.ocrScore || 0.00,
|
||||
}
|
||||
//
|
||||
|
||||
const fileInserts = []
|
||||
// // 图片已经上传成功,保存主表即可
|
||||
// const mainTable = {
|
||||
// b_id: form.value.b_id,
|
||||
// b_container_no: form.value.containerNo,
|
||||
// b_container_img: form.value.containerImageUrl,
|
||||
// b_ocr_no: form.value.ocrNo,
|
||||
// b_ocr_score: form.value.ocrScore || 0.00,
|
||||
// }
|
||||
|
||||
const containerNo = form.value.containerNo
|
||||
// const fileInserts = []
|
||||
|
||||
subFileList.value.forEach(async (fileItem, index) => {
|
||||
const uniqueRes = await getUniqueIdApi()
|
||||
const indexNumber = index + 2
|
||||
// const containerNo = form.value.containerNo
|
||||
|
||||
fileInserts.push({
|
||||
subid: uniqueRes.data,
|
||||
father: form.value.b_id,
|
||||
mx_xh: indexNumber,
|
||||
mx_filename: buildFileName(containerNo, indexNumber, fileItem.file.name),
|
||||
mx_filesize: fileItem.file.size,
|
||||
mx_fileext: getFileExtension(fileItem.file.name),
|
||||
mx_mapfilename: fileItem.url,
|
||||
mx_moduleid: 'b_container',
|
||||
mx_cate_id: '1',
|
||||
})
|
||||
})
|
||||
// subFileList.value.forEach(async (fileItem, index) => {
|
||||
// const uniqueRes = await getUniqueIdApi()
|
||||
// const indexNumber = index + 2
|
||||
|
||||
const uniqueRes = await getUniqueIdApi()
|
||||
// fileInserts.push({
|
||||
// subid: uniqueRes.data,
|
||||
// father: form.value.b_id,
|
||||
// mx_xh: indexNumber,
|
||||
// mx_filename: buildFileName(containerNo, indexNumber, fileItem.file.name),
|
||||
// mx_filesize: fileItem.file.size,
|
||||
// mx_fileext: getFileExtension(fileItem.file.name),
|
||||
// mx_mapfilename: fileItem.url,
|
||||
// mx_moduleid: 'b_container',
|
||||
// mx_cate_id: '1',
|
||||
// })
|
||||
// })
|
||||
|
||||
fileInserts.push({
|
||||
subid: uniqueRes.data,
|
||||
father: form.value.b_id,
|
||||
mx_xh: 0,
|
||||
mx_filename: buildFileName(containerNo, 1, boxImage.value.name),
|
||||
mx_filesize: boxImage.value.size,
|
||||
mx_fileext: getFileExtension(boxImage.value.name),
|
||||
mx_mapfilename: form.value.containerImageUrl,
|
||||
mx_moduleid: 'b_container',
|
||||
mx_cate_id: '0',
|
||||
})
|
||||
// const uniqueRes = await getUniqueIdApi()
|
||||
|
||||
const reqData = [
|
||||
{
|
||||
tableName: 'b_container',
|
||||
keyField: 'b_id',
|
||||
inserts: [mainTable],
|
||||
updates: [],
|
||||
deletes: [],
|
||||
},
|
||||
{
|
||||
tableName: 'bf_file',
|
||||
keyField: 'subid',
|
||||
inserts: fileInserts,
|
||||
updates: [],
|
||||
deletes: [],
|
||||
},
|
||||
]
|
||||
// fileInserts.push({
|
||||
// subid: uniqueRes.data,
|
||||
// father: form.value.b_id,
|
||||
// mx_xh: 0,
|
||||
// mx_filename: buildFileName(containerNo, 1, boxImage.value.name),
|
||||
// mx_filesize: boxImage.value.size,
|
||||
// mx_fileext: getFileExtension(boxImage.value.name),
|
||||
// mx_mapfilename: form.value.containerImageUrl,
|
||||
// mx_moduleid: 'b_container',
|
||||
// mx_cate_id: '0',
|
||||
// })
|
||||
|
||||
saveObjectApi(reqData).then((res) => {
|
||||
if (res.success) {
|
||||
toast.success('保存成功')
|
||||
tipContainerNo.value = form.value.containerNo
|
||||
showResult.value = true
|
||||
}
|
||||
})
|
||||
// const reqData = [
|
||||
// {
|
||||
// tableName: 'b_container',
|
||||
// keyField: 'b_id',
|
||||
// inserts: [mainTable],
|
||||
// updates: [],
|
||||
// deletes: [],
|
||||
// },
|
||||
// {
|
||||
// tableName: 'bf_file',
|
||||
// keyField: 'subid',
|
||||
// inserts: fileInserts,
|
||||
// updates: [],
|
||||
// deletes: [],
|
||||
// },
|
||||
// ]
|
||||
|
||||
// saveObjectApi(reqData).then((res) => {
|
||||
// if (res.success) {
|
||||
// toast.success('保存成功')
|
||||
// tipContainerNo.value = form.value.containerNo
|
||||
// showResult.value = true
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
function buildFileName(containerNo, index, fileName) {
|
||||
@@ -317,13 +296,15 @@ async function continueAdd() {
|
||||
<template>
|
||||
<view class="g3-container">
|
||||
<template v-if="!showResult">
|
||||
<view class="g3-content">
|
||||
<wd-cell-group custom-class="group" title="集装箱号(OCR识别)" border use-slot>
|
||||
<template #title>
|
||||
<sar-icon name="close" size="32rpx" style="margin-right: 16rpx;"/>
|
||||
|
||||
<view style="display:flex;align-items: center;">
|
||||
<sar-icon family="iconfont" name="iconyuanbanben_OCR" size="38rpx" color="var(--sar-primary)" style="margin-right: 16rpx;" />
|
||||
<text>
|
||||
集装箱号(OCR识别)
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
<view class="step-content box">
|
||||
<view v-if="ocrFinish" class="header">
|
||||
@@ -393,12 +374,19 @@ async function continueAdd() {
|
||||
</view>
|
||||
</wd-cell-group>
|
||||
|
||||
<wd-cell-group custom-class="group" title="其他图片" border style="margin-top: 32rpx;">
|
||||
<wd-cell-group custom-class="group" border use-slot style="margin-top: 32rpx;">
|
||||
<template #title>
|
||||
<view style="display:flex;align-items: center;">
|
||||
<sar-icon family="iconfont" name="tupian" size="38rpx" color="var(--sar-primary)" style="margin-right: 16rpx;" />
|
||||
<text>
|
||||
其他图片
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
<view class="step-content">
|
||||
<sar-upload
|
||||
v-model="subFileList"
|
||||
multiple
|
||||
:after-read="afterRead"
|
||||
:before-read="beforeRead"
|
||||
:before-remove="beforeRemove"
|
||||
@item-click="onItemClick"
|
||||
@@ -406,114 +394,14 @@ async function continueAdd() {
|
||||
/>
|
||||
</view>
|
||||
</wd-cell-group>
|
||||
<!-- <view class="g3-content">
|
||||
<view class="step">
|
||||
<view class="step-header">
|
||||
<sar-tag theme="primary" size="large">
|
||||
1
|
||||
</sar-tag>
|
||||
|
||||
<view>集装箱号识别(OCR自动识别)</view>
|
||||
</view>
|
||||
|
||||
<view class="step-content box">
|
||||
<view v-if="ocrFinish" class="header ">
|
||||
<sar-alert
|
||||
v-if="!isError"
|
||||
type="success"
|
||||
show-icon
|
||||
closable
|
||||
:root-style="{
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
识别成功,请检查箱号是否正确!
|
||||
<view>
|
||||
准确率:{{ computedOcrScore }}
|
||||
</view>
|
||||
</sar-alert>
|
||||
<sar-alert
|
||||
v-else
|
||||
type="danger"
|
||||
show-icon
|
||||
closable
|
||||
:root-style="{
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
识别失败,请检查手动填写集装箱号
|
||||
</sar-alert>
|
||||
</view>
|
||||
|
||||
<view class="box-main">
|
||||
<view v-if="!boxImage" class="box-placeholder" @click="handleChooseBox">
|
||||
<sar-icon name="image" size="56rpx" color="#999" />
|
||||
<text class="placeholder-text">
|
||||
上传箱号图片,自动识别箱号
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="box-image">
|
||||
<wd-img
|
||||
:src="boxImage.path"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:enable-preview="true"
|
||||
/>
|
||||
|
||||
<view class="delete-btn" @click="handleDeleteBoxImage()">
|
||||
<sar-icon name="close" size="20rpx" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="box-input">
|
||||
<view class="header">
|
||||
<view class="label-container">
|
||||
<view>集装箱号</view>
|
||||
<view class="required-asterisk">
|
||||
*
|
||||
</view>
|
||||
</view>
|
||||
<view class="format">
|
||||
格式: 4字母 + 7数字
|
||||
</view>
|
||||
</view>
|
||||
<sar-input
|
||||
v-model="form.containerNo"
|
||||
placeholder="请输入箱号"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="step">
|
||||
<view class="step-header">
|
||||
<sar-tag theme="primary" mark="right" size="large">
|
||||
2、其它照片录入
|
||||
</sar-tag>
|
||||
</view>
|
||||
<view class="step-content">
|
||||
<sar-upload
|
||||
v-model="subFileList"
|
||||
multiple
|
||||
:after-read="afterRead"
|
||||
:before-read="beforeRead"
|
||||
:before-remove="beforeRemove"
|
||||
@item-click="onItemClick"
|
||||
@remove="onRemove"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="g3-footer">
|
||||
<view class="footer-main">
|
||||
<view class="submit-btn" @click="handleConfirm">
|
||||
确认上传
|
||||
<sar-button round root-style="width:100%" icon-family="iconfont" icon="fasongtijiao" @click="handleConfirm">
|
||||
上传
|
||||
</sar-button>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</template>
|
||||
|
||||
<view v-else class="g3-result">
|
||||
@@ -526,7 +414,7 @@ async function continueAdd() {
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
<sar-button long style="width: 360rpx;font-size: 38rpx;" @click="continueAdd">
|
||||
<sar-button long style="font-size: 38rpx;" @click="continueAdd">
|
||||
继续录入
|
||||
</sar-button>
|
||||
</sar-result>
|
||||
|
||||
@@ -0,0 +1,627 @@
|
||||
<script lang="ts" setup>
|
||||
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
|
||||
import { dialog, isImageUrl, isVideoUrl, toast } from 'sard-uniapp'
|
||||
import { aliyunpluploadApi, deleteAliyunFileApi, deleteFileApi, getUniqueIdApi, recognizeOcrBoxApi, saveObjectApi, uploadTempFileApi } from '@/request/api'
|
||||
import { downloadFile, getFileExtension, imageCompress, isImage } from '@/utils/file'
|
||||
import { uploadToOss } from '@/utils/oss'
|
||||
|
||||
const boxImage = ref(null)
|
||||
|
||||
const ocrFinish = ref(false)
|
||||
const isError = ref(false)
|
||||
|
||||
const showResult = ref(false)
|
||||
|
||||
const form = ref({
|
||||
b_id: 0,
|
||||
containerNo: '',
|
||||
containerImageUrl: '',
|
||||
ocrScore: 0.00,
|
||||
ocrNo: '',
|
||||
})
|
||||
|
||||
const subFileList = ref([])
|
||||
|
||||
const tipContainerNo = ref('')
|
||||
|
||||
onLoad(async () => {
|
||||
toast.loading('正在初始化中...', {
|
||||
overlay: true,
|
||||
})
|
||||
|
||||
await getUniqueIdApi().then((res) => {
|
||||
if (res.success) {
|
||||
form.value.b_id = res.data
|
||||
}
|
||||
})
|
||||
|
||||
toast.hide()
|
||||
})
|
||||
|
||||
function handleChooseBox() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
if (Array.isArray(res.tempFiles) && res.tempFiles.length > 0) {
|
||||
const tempFile: any = res.tempFiles[0]
|
||||
|
||||
boxImage.value = tempFile
|
||||
|
||||
toast.loading('OCR识别中,请稍等 . . .', {
|
||||
overlay: true,
|
||||
transparent: false,
|
||||
})
|
||||
|
||||
imageCompress(tempFile.path).then((compressRes) => {
|
||||
console.log(compressRes)
|
||||
// uploadTempFileApi(compressRes.path, tempFile.name).then((res) => {
|
||||
// console.log(res)
|
||||
// })
|
||||
|
||||
uploadToOss(compressRes.path, tempFile.name, 'image').then((res) => {
|
||||
if (res.url) {
|
||||
form.value.containerImageUrl = res.url
|
||||
|
||||
ocrFinish.value = false
|
||||
|
||||
recognizeOcrBoxApi({
|
||||
fileUrl: form.value.containerImageUrl,
|
||||
}).then((ocrRes) => {
|
||||
if (ocrRes.success) {
|
||||
toast.success('识别成功,请检查箱号是否正确')
|
||||
|
||||
const score = Number(ocrRes?.data?.score ?? 0)
|
||||
form.value.ocrScore = Math.floor(score * 100) / 100
|
||||
|
||||
form.value.containerNo = ocrRes.data.boxNumber
|
||||
form.value.ocrNo = ocrRes.data.boxNumber
|
||||
|
||||
isError.value = false
|
||||
}
|
||||
}).catch(() => {
|
||||
isError.value = true
|
||||
}).finally(() => {
|
||||
ocrFinish.value = true
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
const computedOcrScore = computed(() => {
|
||||
return `${(Math.floor(form.value.ocrScore * 100)).toFixed(0)}%`
|
||||
})
|
||||
|
||||
function resetForm() {
|
||||
boxImage.value = null
|
||||
form.value.containerImageUrl = ''
|
||||
form.value.containerNo = ''
|
||||
form.value.ocrNo = ''
|
||||
form.value.ocrScore = 0.00
|
||||
}
|
||||
|
||||
function handleDeleteBoxImage() {
|
||||
if (!form.value.containerImageUrl) {
|
||||
resetForm()
|
||||
return
|
||||
}
|
||||
|
||||
toast.loading('正在删除中 . . .', {
|
||||
overlay: true,
|
||||
transparent: false,
|
||||
})
|
||||
|
||||
deleteAliyunFileApi(form.value.containerImageUrl).then((res) => {
|
||||
if (res.success) {
|
||||
resetForm()
|
||||
toast.success('删除成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onItemClick(fileItem: UploadFileItem) {
|
||||
console.log(fileItem)
|
||||
if (!isImageUrl(fileItem.mx_mapfilename) && !isVideoUrl(fileItem.mx_mapfilename)) {
|
||||
downloadFile(fileItem.mx_mapfilename, fileItem.name)
|
||||
}
|
||||
}
|
||||
|
||||
function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
|
||||
// 非图片直接允许上传
|
||||
if (!isImage(file.name))
|
||||
return true
|
||||
|
||||
// 图片异步压缩,返回修改后的 UploadFile 对象
|
||||
return imageCompress(file.path).then((res) => {
|
||||
file.path = res.path
|
||||
file.size = res.size
|
||||
return file
|
||||
}).catch((err) => {
|
||||
console.error('图片压缩失败:', err)
|
||||
return file
|
||||
})
|
||||
}
|
||||
|
||||
async function afterRead(fileItem: UploadFileItem) {
|
||||
// 设置上传中状态
|
||||
fileItem.status = 'uploading'
|
||||
fileItem.message = '正在上传'
|
||||
|
||||
fileItem.isUpload = false
|
||||
|
||||
try {
|
||||
// 上传到阿里云
|
||||
const res = await uploadToOss(
|
||||
fileItem.file.path,
|
||||
fileItem.file.name,
|
||||
fileItem.file.type,
|
||||
)
|
||||
|
||||
if (!res.url)
|
||||
throw new Error('上传失败:未获取到文件 URL')
|
||||
|
||||
// 上传完成
|
||||
fileItem.status = 'done'
|
||||
fileItem.message = '上传成功'
|
||||
fileItem.url = res.url
|
||||
|
||||
subFileList.value = [...subFileList.value]
|
||||
}
|
||||
catch (e) {
|
||||
// 上传完成
|
||||
fileItem.status = 'failed'
|
||||
fileItem.message = '上传失败'
|
||||
subFileList.value = [...subFileList.value]
|
||||
}
|
||||
}
|
||||
|
||||
function beforeRemove(index: number, fileItem: UploadFileItem): boolean | Promise<void> {
|
||||
console.log(fileItem)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
dialog.confirm({
|
||||
title: '提示',
|
||||
message: `确定删除该张图片吗?`,
|
||||
onConfirm: () => resolve(),
|
||||
onCancel: () => reject(),
|
||||
onClose: () => reject(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function onRemove(index: number, item: UploadFileItem) {
|
||||
if (item.status === 'done') {
|
||||
deleteAliyunFileApi(item.url).then((res) => {
|
||||
if (res.success) {
|
||||
toast.success('删除成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
if (form.value.containerImageUrl === '') {
|
||||
toast.fail('集装箱号图片不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (form.value.containerNo === '') {
|
||||
toast.fail('集装箱号不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!validateContainerNo(form.value.containerNo)) {
|
||||
toast.fail('集装箱号不符合规则,请检查')
|
||||
return
|
||||
}
|
||||
|
||||
toast.loading('正在保存中 . . .', {
|
||||
overlay: true,
|
||||
transparent: false,
|
||||
})
|
||||
|
||||
// 图片已经上传成功,保存主表即可
|
||||
const mainTable = {
|
||||
b_id: form.value.b_id,
|
||||
b_container_no: form.value.containerNo,
|
||||
b_container_img: form.value.containerImageUrl,
|
||||
b_ocr_no: form.value.ocrNo,
|
||||
b_ocr_score: form.value.ocrScore || 0.00,
|
||||
}
|
||||
|
||||
const fileInserts = []
|
||||
|
||||
const containerNo = form.value.containerNo
|
||||
|
||||
subFileList.value.forEach(async (fileItem, index) => {
|
||||
const uniqueRes = await getUniqueIdApi()
|
||||
const indexNumber = index + 2
|
||||
|
||||
fileInserts.push({
|
||||
subid: uniqueRes.data,
|
||||
father: form.value.b_id,
|
||||
mx_xh: indexNumber,
|
||||
mx_filename: buildFileName(containerNo, indexNumber, fileItem.file.name),
|
||||
mx_filesize: fileItem.file.size,
|
||||
mx_fileext: getFileExtension(fileItem.file.name),
|
||||
mx_mapfilename: fileItem.url,
|
||||
mx_moduleid: 'b_container',
|
||||
mx_cate_id: '1',
|
||||
})
|
||||
})
|
||||
|
||||
const uniqueRes = await getUniqueIdApi()
|
||||
|
||||
fileInserts.push({
|
||||
subid: uniqueRes.data,
|
||||
father: form.value.b_id,
|
||||
mx_xh: 0,
|
||||
mx_filename: buildFileName(containerNo, 1, boxImage.value.name),
|
||||
mx_filesize: boxImage.value.size,
|
||||
mx_fileext: getFileExtension(boxImage.value.name),
|
||||
mx_mapfilename: form.value.containerImageUrl,
|
||||
mx_moduleid: 'b_container',
|
||||
mx_cate_id: '0',
|
||||
})
|
||||
|
||||
const reqData = [
|
||||
{
|
||||
tableName: 'b_container',
|
||||
keyField: 'b_id',
|
||||
inserts: [mainTable],
|
||||
updates: [],
|
||||
deletes: [],
|
||||
},
|
||||
{
|
||||
tableName: 'bf_file',
|
||||
keyField: 'subid',
|
||||
inserts: fileInserts,
|
||||
updates: [],
|
||||
deletes: [],
|
||||
},
|
||||
]
|
||||
|
||||
saveObjectApi(reqData).then((res) => {
|
||||
if (res.success) {
|
||||
toast.success('保存成功')
|
||||
tipContainerNo.value = form.value.containerNo
|
||||
showResult.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function buildFileName(containerNo, index, fileName) {
|
||||
const formattedIndex = String(index).padStart(2, '0')
|
||||
|
||||
return `${containerNo}_${formattedIndex}.${fileName.split('.').pop()}`
|
||||
}
|
||||
|
||||
// 集装箱号校验
|
||||
function validateContainerNo(containerNo) {
|
||||
// 正则表达式: 4个字母 + 7个数字
|
||||
const pattern = /^[A-Z]{4}\d{7}$/i
|
||||
|
||||
// 使用正则进行匹配
|
||||
if (pattern.test(containerNo)) {
|
||||
return true
|
||||
}
|
||||
else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function continueAdd() {
|
||||
const idRes = await getUniqueIdApi()
|
||||
|
||||
if (idRes.success) {
|
||||
boxImage.value = null
|
||||
|
||||
ocrFinish.value = false
|
||||
isError.value = false
|
||||
|
||||
resetForm()
|
||||
form.value.b_id = idRes.data
|
||||
|
||||
subFileList.value = []
|
||||
showResult.value = false
|
||||
|
||||
tipContainerNo.value = ''
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="g3-container">
|
||||
<template v-if="!showResult">
|
||||
<view class="g3-content">
|
||||
<wd-cell-group custom-class="group" title="集装箱号(OCR识别)" border use-slot>
|
||||
<template #title>
|
||||
<view style="display:flex;align-items: center;">
|
||||
<sar-icon family="iconfont" name="iconyuanbanben_OCR" size="38rpx" color="var(--sar-primary)" style="margin-right: 16rpx;" />
|
||||
<text>
|
||||
集装箱号(OCR识别)
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
<view class="step-content box">
|
||||
<view v-if="ocrFinish" class="header">
|
||||
<sar-alert
|
||||
v-if="!isError"
|
||||
type="success"
|
||||
show-icon
|
||||
closable
|
||||
:root-style="{
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
识别成功,请检查箱号是否正确!
|
||||
<view>
|
||||
准确率:{{ computedOcrScore }}
|
||||
</view>
|
||||
</sar-alert>
|
||||
<sar-alert
|
||||
v-else
|
||||
type="danger"
|
||||
show-icon
|
||||
closable
|
||||
:root-style="{
|
||||
width: '100%',
|
||||
}"
|
||||
>
|
||||
识别失败,请检查手动填写集装箱号
|
||||
</sar-alert>
|
||||
</view>
|
||||
|
||||
<view class="box-main">
|
||||
<view v-if="!boxImage" class="box-placeholder" @click="handleChooseBox">
|
||||
<sar-icon name="plus" size="156rpx" color="#999" />
|
||||
</view>
|
||||
|
||||
<view v-else class="box-image">
|
||||
<wd-img
|
||||
:src="boxImage.path"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:enable-preview="true"
|
||||
/>
|
||||
|
||||
<view class="delete-btn" @click="handleDeleteBoxImage()">
|
||||
<sar-icon name="close" size="20rpx" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="box-input">
|
||||
<view class="header">
|
||||
<view class="label-container">
|
||||
<view>集装箱号</view>
|
||||
<view class="required-asterisk">
|
||||
*
|
||||
</view>
|
||||
</view>
|
||||
<view class="format">
|
||||
格式: 4字母 + 7数字
|
||||
</view>
|
||||
</view>
|
||||
<sar-input
|
||||
v-model="form.containerNo"
|
||||
placeholder="请输入箱号"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</wd-cell-group>
|
||||
|
||||
<wd-cell-group custom-class="group" border use-slot style="margin-top: 32rpx;">
|
||||
<template #title>
|
||||
<view style="display:flex;align-items: center;">
|
||||
<sar-icon family="iconfont" name="tupian" size="38rpx" color="var(--sar-primary)" style="margin-right: 16rpx;" />
|
||||
<text>
|
||||
其他图片
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
<view class="step-content">
|
||||
<sar-upload
|
||||
v-model="subFileList"
|
||||
multiple
|
||||
:after-read="afterRead"
|
||||
:before-read="beforeRead"
|
||||
:before-remove="beforeRemove"
|
||||
@item-click="onItemClick"
|
||||
@remove="onRemove"
|
||||
/>
|
||||
</view>
|
||||
</wd-cell-group>
|
||||
</view>
|
||||
<view class="g3-footer">
|
||||
<view class="footer-main">
|
||||
<sar-button round root-style="width:100%" icon-family="iconfont" icon="fasongtijiao" @click="handleConfirm">
|
||||
上传
|
||||
</sar-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view v-else class="g3-result">
|
||||
<sar-result status="success" title="操作成功" description="已成功录入箱号 为 xxx 的记录">
|
||||
<template #description>
|
||||
<view class="result-tip">
|
||||
已成功录入箱号
|
||||
<text class="container-no">
|
||||
"{{ tipContainerNo }}"
|
||||
</text>
|
||||
</view>
|
||||
</template>
|
||||
<sar-button long style="font-size: 38rpx;" @click="continueAdd">
|
||||
继续录入
|
||||
</sar-button>
|
||||
</sar-result>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.g3-result {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.result-tip {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.container-no {
|
||||
font-weight: 600;
|
||||
color: #4caf50;
|
||||
}
|
||||
}
|
||||
|
||||
.g3-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.g3-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16rpx 0rpx;
|
||||
}
|
||||
|
||||
.g3-footer {
|
||||
width: 100%;
|
||||
|
||||
.footer-main {
|
||||
padding: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step {
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.step-content {
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.box-main {
|
||||
height: 326rpx;
|
||||
}
|
||||
|
||||
.box-placeholder {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background: var(--sar-upload-select-bg);
|
||||
border-radius: var(--sar-upload-select-border-radius);
|
||||
|
||||
.placeholder-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.box-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
right: 12rpx;
|
||||
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.box-input {
|
||||
margin-top: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 12rpx;
|
||||
width: 100%;
|
||||
.label-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.required-asterisk {
|
||||
position: absolute;
|
||||
top: -5rpx;
|
||||
right: -12rpx;
|
||||
color: red;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.format {
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.tip {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
border-radius: 999px;
|
||||
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #3a6ee8, #5c8df6);
|
||||
box-shadow: 0 6rpx 12rpx rgba(58, 110, 232, 0.18);
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.submit-btn:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 3rpx 6rpx rgba(58, 110, 232, 0.15);
|
||||
}
|
||||
</style>
|
||||
@@ -60,7 +60,7 @@
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人中心",
|
||||
"navigationStyle": "default"
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,152 +1,269 @@
|
||||
<script setup lang="ts">
|
||||
import { deleteFileApi } from '@/request/api'
|
||||
import { toast } from 'sard-uniapp'
|
||||
import { ref } from 'vue'
|
||||
import { loadPageDataApi } from '@/request/api'
|
||||
import { useUserStore } from '@/store'
|
||||
import { deepEqual, groupFieldsBySpan } from '@/utils/common'
|
||||
import { getCurrentDate } from '@/utils/time'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
onShow(() => {
|
||||
userStore.requireLogin()
|
||||
const pagingRef = ref<any>(null)
|
||||
const dataList = ref<any[]>([])
|
||||
|
||||
const itemFields = [
|
||||
{ label: '录入时间', value: item => item.b_inputdatetime, span: 12 },
|
||||
{ label: '文件数量', value: item => item.v_file_count, span: 12 },
|
||||
]
|
||||
const groupedFields = groupFieldsBySpan(itemFields)
|
||||
|
||||
// 搜索关键字
|
||||
const searchValue = ref('')
|
||||
|
||||
// onShow(() => {
|
||||
// pagingRef.value?.reload()
|
||||
// })
|
||||
|
||||
onMounted(() => {
|
||||
pagingRef.value?.reload()
|
||||
})
|
||||
|
||||
const swiperCurrent = ref(0)
|
||||
const swiperList = [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
]
|
||||
|
||||
function handleToLogin() {
|
||||
uni.reLaunch({
|
||||
url: '/pages-sub/login/index',
|
||||
})
|
||||
// 搜索
|
||||
function handleSearch() {
|
||||
pagingRef.value.reload()
|
||||
}
|
||||
|
||||
function toLinkUrl(url) {
|
||||
if (!url) {
|
||||
return
|
||||
// 模拟分页请求
|
||||
function queryList(pageNo: number, pageSize: number) {
|
||||
toast.loading('正在加载中')
|
||||
|
||||
const userid = userStore.userInfo.userid
|
||||
|
||||
let searchCondition = `2 > 1 AND b_inputuser_id = '${userid}'`
|
||||
|
||||
if (searchValue.value) {
|
||||
searchCondition += `b_container_no LIKE '%${searchValue.value}%' `
|
||||
}
|
||||
uni.navigateTo({
|
||||
url,
|
||||
|
||||
loadPageDataApi({
|
||||
viewName: 'v_container',
|
||||
searchCondition,
|
||||
page: pageNo,
|
||||
size: pageSize,
|
||||
orderField: 'b_inputdatetime desc',
|
||||
})
|
||||
.then((res) => {
|
||||
const rows = res.data.rows || []
|
||||
console.log(rows)
|
||||
if (rows) {
|
||||
pagingRef.value.complete(rows)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
toast.hide()
|
||||
})
|
||||
}
|
||||
|
||||
function deleteAliyunFiles() {
|
||||
const subids = [
|
||||
787154320330821,
|
||||
787154320388165,
|
||||
]
|
||||
subids.forEach((subid) => {
|
||||
deleteFileApi(subid)
|
||||
function handleToFile(itemData) {
|
||||
const searchids = [itemData.b_id, itemData.father]
|
||||
.filter(Boolean)
|
||||
.join(',')
|
||||
uni.navigateTo({
|
||||
url: `/pages-sub/file/index?moduleid=xc_conyard_in&father=${itemData.father}&searchids=${encodeURIComponent(searchids)}&cateid=016&edit=1`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view>
|
||||
<view class="swiper-container">
|
||||
<swiper
|
||||
:current="swiperCurrent"
|
||||
autoplay
|
||||
:interval="3000"
|
||||
class="swiper"
|
||||
@change="swiperCurrent = $event.detail.current"
|
||||
<z-paging
|
||||
ref="pagingRef" v-model="dataList"
|
||||
auto-show-back-to-top
|
||||
@query="queryList"
|
||||
>
|
||||
<swiper-item>
|
||||
<image
|
||||
src="@/static/images/bg.jpg"
|
||||
style="width: 100%;height:100%"
|
||||
/>
|
||||
</swiper-item>
|
||||
<swiper-item>
|
||||
<image
|
||||
src="@/static/images/bg1.jpg"
|
||||
style="width: 100%;height:100%"
|
||||
/>
|
||||
</swiper-item>
|
||||
<swiper-item>
|
||||
<image
|
||||
src="@/static/images/bg2.jpg"
|
||||
style="width: 100%;height:100%"
|
||||
/>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<sar-swiper-dot
|
||||
:current="swiperCurrent"
|
||||
:list="swiperList"
|
||||
<template #top>
|
||||
<view style="border-bottom: 1px solid #ddd;">
|
||||
<sar-search
|
||||
v-model="searchValue"
|
||||
placeholder="请输入箱号搜索"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</view>
|
||||
<view v-if="userStore.isLogined" class="grid-container">
|
||||
<sar-grid :columns="1" gap="24rpx">
|
||||
<sar-grid-item
|
||||
@click="toLinkUrl('/pages-sub/upload/index')"
|
||||
</template>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<view
|
||||
v-for="(item, index) in dataList"
|
||||
:key="index"
|
||||
class="list-item"
|
||||
>
|
||||
<view class="grid-item">
|
||||
<image src="/static/images/home/waybill.png" />
|
||||
<text>集装箱装货采集</text>
|
||||
<view class="list-item__header">
|
||||
<view class="list-item__main">
|
||||
<sar-image
|
||||
:src="item.b_container_img"
|
||||
width="160rpx"
|
||||
height="160rpx"
|
||||
/>
|
||||
</view>
|
||||
</sar-grid-item>
|
||||
</sar-grid>
|
||||
</view>
|
||||
<sar-result v-else status="warning" title="尚未登陆" style="margin-top: 32rpx;">
|
||||
<sar-button
|
||||
@click="handleToLogin"
|
||||
>
|
||||
前去登录
|
||||
</sar-button>
|
||||
</sar-result>
|
||||
|
||||
<view class="list-item__body">
|
||||
<sar-row>
|
||||
<sar-col :span="12">
|
||||
<view class="list-item__info">
|
||||
<view class="list-item__label">
|
||||
集装箱号:
|
||||
</view>
|
||||
<view class="list-item__value" style="font-weight: bold;color:var(--sar-primary)">
|
||||
{{ item.b_container_no }}
|
||||
</view>
|
||||
</view>
|
||||
</sar-col>
|
||||
</sar-row>
|
||||
<sar-row>
|
||||
<sar-col :span="12">
|
||||
<view class="list-item__info">
|
||||
<view class="list-item__label">
|
||||
录入时间:
|
||||
</view>
|
||||
<view class="list-item__value">
|
||||
{{ item.b_inputdatetime }}
|
||||
</view>
|
||||
</view>
|
||||
</sar-col>
|
||||
</sar-row>
|
||||
<sar-row>
|
||||
<sar-col :span="12">
|
||||
<view class="list-item__info">
|
||||
<view class="list-item__label">
|
||||
照片数量:
|
||||
</view>
|
||||
<view class="list-item__value">
|
||||
{{ item.v_file_count }}
|
||||
</view>
|
||||
</view>
|
||||
</sar-col>
|
||||
</sar-row>
|
||||
</view>
|
||||
|
||||
<view class="list-item__footer">
|
||||
<!-- <sar-button inline size="small" square @click="handleToFile(item)">
|
||||
拍照
|
||||
</sar-button> -->
|
||||
</view>
|
||||
<view class="list-item__line" />
|
||||
</view>
|
||||
</z-paging>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style scoped lang="scss">
|
||||
@import '@/style/mixins.scss';
|
||||
page {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
.dialog-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200rpx;
|
||||
}
|
||||
|
||||
.swiper-container {
|
||||
position: relative;
|
||||
.swiper {
|
||||
height: 400rpx;
|
||||
width: 100%;
|
||||
/* tab 栏 */
|
||||
.tabs {
|
||||
display: flex;
|
||||
border-bottom: 2rpx solid #eee;
|
||||
background-color: #fff;
|
||||
|
||||
.tabs__item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 16rpx 0;
|
||||
font-size: 30rpx;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.tabs__item--active {
|
||||
color: var(--sar-primary);
|
||||
font-weight: bold;
|
||||
border-bottom: 4rpx solid var(--sar-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
padding-top: 32rpx;
|
||||
|
||||
:deep(.sar-grid__content) {
|
||||
/* 列表项 */
|
||||
.list-item {
|
||||
position: relative;
|
||||
padding: 16rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
:deep(.sar-grid) {
|
||||
row-gap: 0 !important;
|
||||
}
|
||||
:deep(.sar-grid__item) {
|
||||
margin-bottom: 24rpx;
|
||||
&.list-item--desktop {
|
||||
.list-item__header {
|
||||
max-width: 1000rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:deep(.sar-grid__wrapper) {
|
||||
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
.list-item__body {
|
||||
max-width: 1000rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
:deep(.sar-badge) {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
.list-item__footer {
|
||||
max-width: 1000rpx;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
.list-item__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
image {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
}
|
||||
text {
|
||||
margin-top: 16rpx;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
|
||||
.list-item__body {
|
||||
padding: 24rpx 0rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@include flexGap(16rpx, 'column');
|
||||
.list-item__info {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
font-size: 32rpx;
|
||||
line-height: 1;
|
||||
|
||||
.list-item__label {
|
||||
display: block;
|
||||
min-width: 192rpx;
|
||||
color: #4b5563;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-item__value {
|
||||
flex: 1;
|
||||
color: #111827;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-item__footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@include flexGap(36rpx, '');
|
||||
|
||||
:deep(.sar-button) {
|
||||
padding: 0rpx 48rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item__line {
|
||||
position: absolute;
|
||||
bottom: 0rpx;
|
||||
left: 0rpx;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: #dcdcdc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { httpPost } from './index'
|
||||
import { httpPost, httpUpload } from './index'
|
||||
|
||||
const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET
|
||||
|
||||
@@ -16,7 +16,7 @@ interface GetaliyunkeyParams {
|
||||
bucketName: string
|
||||
}
|
||||
|
||||
export function getaliyunkeyApi(params: GetaliyunkeyParams) {
|
||||
export function getAliyunkeyApi(params: GetaliyunkeyParams) {
|
||||
return httpPost('/file/getAliyunKey', params)
|
||||
}
|
||||
|
||||
@@ -92,3 +92,23 @@ interface UpdatePasswordParams {
|
||||
export function updatePasswordApi(params: UpdatePasswordParams) {
|
||||
return httpPost(`/auth/updatePassword`, params)
|
||||
}
|
||||
|
||||
interface PageParams {
|
||||
page: number
|
||||
size: number
|
||||
viewName: string
|
||||
searchCondition?: string
|
||||
orderField?: string
|
||||
searchColumns?: string[]
|
||||
}
|
||||
|
||||
export function loadPageDataApi(params: PageParams) {
|
||||
return httpPost(
|
||||
'/data/page',
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
export function uploadTempFileApi(filePath: string, fileName: string) {
|
||||
return httpUpload('/file/uploadTemp', filePath, fileName)
|
||||
}
|
||||
@@ -5,99 +5,109 @@ import { useUserStore } from '@/store'
|
||||
import { joinUrl } from '@/utils/common'
|
||||
import { isH5 } from '@/utils/platform'
|
||||
|
||||
function request<T = any>(option: UniApp.RequestOptions): Promise<IResData<T>> & { abort: () => void } {
|
||||
let task: UniApp.RequestTask | undefined
|
||||
let aborted = false
|
||||
|
||||
const promise = new Promise<IResData<T>>((resolve, reject) => {
|
||||
// 判断是否是登录请求
|
||||
const isLogin = option.url?.includes('login')
|
||||
|
||||
const readDbJson = new Promise<Record<string, string>>((res, rej) => {
|
||||
/**
|
||||
* ======================
|
||||
* 读取机构配置
|
||||
* ======================
|
||||
*/
|
||||
function readDbJson(): Promise<Record<string, string>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (isH5) {
|
||||
// H5:用 request 请求,带时间戳保证最新
|
||||
const timestamp = Date.now()
|
||||
uni.request({
|
||||
url: `/g3ocr-app/static/db.json?t=${timestamp}`,
|
||||
method: 'GET',
|
||||
success: r => res(r.data as Record<string, string>),
|
||||
fail: err => rej(err),
|
||||
success: r => resolve(r.data as Record<string, string>),
|
||||
fail: err => reject(err),
|
||||
})
|
||||
}
|
||||
else {
|
||||
// 非 H5 直接返回本地导入的 JSON
|
||||
res(dbJson)
|
||||
resolve(dbJson as Record<string, string>)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
readDbJson
|
||||
.then((dbRes) => {
|
||||
if (aborted)
|
||||
return reject(new Error('请求已取消'))
|
||||
/**
|
||||
* ======================
|
||||
* 获取 baseUrl + headers
|
||||
* ======================
|
||||
*/
|
||||
async function getBaseConfig(option: UniApp.RequestOptions, isLogin: boolean) {
|
||||
const dbRes = await readDbJson()
|
||||
|
||||
// 组织请求 URL
|
||||
let reqUrl = ''
|
||||
const userStore = useUserStore()
|
||||
const { token, userid, orgid } = userStore.userInfo || {}
|
||||
|
||||
if (!dbRes || Object.keys(dbRes).length === 0) {
|
||||
toast.fail('机构配置获取失败,请联系管理人员!')
|
||||
return reject(new Error('机构配置获取失败,请联系管理人员!'))
|
||||
throw new Error('机构配置获取失败')
|
||||
}
|
||||
|
||||
let reqUrl = ''
|
||||
|
||||
if (isLogin) {
|
||||
const dataObj: any = typeof option.data === 'string'
|
||||
? parseFormUrlencoded(option.data)
|
||||
: option.data || {}
|
||||
const orgidFromData = dataObj.orgid || ''
|
||||
|
||||
reqUrl = dbRes[orgidFromData]
|
||||
reqUrl = dbRes[dataObj.orgid || '']
|
||||
}
|
||||
else {
|
||||
reqUrl = dbRes[orgid || '']
|
||||
}
|
||||
|
||||
if (!reqUrl) {
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const currentRoute = `/${currentPage.route}`
|
||||
|
||||
if (currentRoute !== '/pages-sub/login/index') {
|
||||
uni.showToast({ title: '当前登录已过期,请重新登录', icon: 'error' })
|
||||
|
||||
uni.reLaunch({
|
||||
url: '/pages-sub/login/index',
|
||||
})
|
||||
}
|
||||
else {
|
||||
uni.showToast({ title: '机构码错误', icon: 'error' })
|
||||
handleInvalidOrg()
|
||||
throw new Error('机构码错误')
|
||||
}
|
||||
|
||||
return reject(new Error('机构码错误'))
|
||||
}
|
||||
|
||||
// 设置 headers
|
||||
const headers: Record<string, any> = { ...(option.header || {}) }
|
||||
|
||||
if (!isLogin) {
|
||||
headers.Authorization = `Bearer ${token}`
|
||||
headers.userid = encodeURIComponent(userid)
|
||||
headers.orgid = orgid
|
||||
}
|
||||
|
||||
// 拼接完整 URL
|
||||
option.url = joinUrl(reqUrl, option.url || '')
|
||||
option.header = headers
|
||||
return {
|
||||
baseUrl: reqUrl,
|
||||
headers,
|
||||
}
|
||||
}
|
||||
|
||||
// 发请求
|
||||
task = uni.request({
|
||||
...option,
|
||||
url: option.url,
|
||||
success: (res) => {
|
||||
/**
|
||||
* ======================
|
||||
* 统一错误处理
|
||||
* ======================
|
||||
*/
|
||||
function handleInvalidOrg() {
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const currentRoute = `/${currentPage.route}`
|
||||
|
||||
if (currentRoute !== '/pages-sub/login/index') {
|
||||
uni.showToast({ title: '当前登录已过期,请重新登录', icon: 'none' })
|
||||
uni.reLaunch({ url: '/pages-sub/login/index' })
|
||||
}
|
||||
else {
|
||||
uni.showToast({ title: '机构码错误', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ======================
|
||||
* 统一响应处理
|
||||
* ======================
|
||||
*/
|
||||
function handleResponse<T>(
|
||||
res: any,
|
||||
resolve: (data: IResData<T>) => void,
|
||||
reject: (err: any) => void,
|
||||
aborted: boolean,
|
||||
) {
|
||||
if (aborted)
|
||||
return reject(new Error('请求已取消'))
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
console.error('HTTP错误', res.statusCode, res.data)
|
||||
toast.fail(`请求失败:${res.statusCode}`)
|
||||
return reject(new Error(`HTTP错误: ${res.statusCode}`))
|
||||
}
|
||||
@@ -105,74 +115,150 @@ function request<T = any>(option: UniApp.RequestOptions): Promise<IResData<T>> &
|
||||
let data: IResData<T>
|
||||
|
||||
try {
|
||||
// 解析最外层响应
|
||||
data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
|
||||
data = typeof res.data === 'string'
|
||||
? JSON.parse(res.data)
|
||||
: res.data
|
||||
|
||||
// 如果 data.data 是字符串,则解析成对象
|
||||
if (data.data && typeof data.data === 'string') {
|
||||
try {
|
||||
data.data = JSON.parse(data.data)
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// 如果解析失败,保持原始字符串即可
|
||||
// console.warn('data.data 不是合法 JSON 字符串', data.data)
|
||||
toast.fail('响应解析失败')
|
||||
return reject(new Error('响应解析失败'))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(`解析响应JSON失败:${err}`)
|
||||
toast.fail('响应数据解析失败')
|
||||
return reject(new Error('响应数据解析失败'))
|
||||
|
||||
// 401 优先处理(避免被后面拦截)
|
||||
if (data.code === 401) {
|
||||
uni.showToast({ title: '请重新登录', icon: 'none' })
|
||||
handleInvalidOrg()
|
||||
return reject(new Error('未授权'))
|
||||
}
|
||||
|
||||
if (data.code === 200) {
|
||||
data.success = true
|
||||
|
||||
resolve(data)
|
||||
}
|
||||
else {
|
||||
toast.fail(data.message || '请求错误')
|
||||
reject(new Error(data.message || '请求错误'))
|
||||
toast.fail(data.message || '请求失败')
|
||||
reject(new Error(data.message || '请求失败'))
|
||||
}
|
||||
}
|
||||
|
||||
// 登录失效处理
|
||||
if (data.code === 401) {
|
||||
uni.showToast({ title: '请重新登录', icon: 'none' })
|
||||
/**
|
||||
* ======================
|
||||
* 主请求方法
|
||||
* ======================
|
||||
*/
|
||||
function request<T = any>(
|
||||
option: UniApp.RequestOptions,
|
||||
): Promise<IResData<T>> & { abort: () => void } {
|
||||
let task: UniApp.RequestTask | undefined
|
||||
let aborted = false
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const currentRoute = `/${currentPage.route}`
|
||||
if (currentRoute !== '/pages-sub/login/index') {
|
||||
uni.reLaunch({
|
||||
url: '/pages-sub/login/index',
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
const promise = (async () => {
|
||||
const isLogin = option.url?.includes('login')
|
||||
|
||||
const { baseUrl, headers } = await getBaseConfig(option, isLogin)
|
||||
|
||||
option.url = joinUrl(baseUrl, option.url || '')
|
||||
option.header = headers
|
||||
|
||||
return new Promise<IResData<T>>((resolve, reject) => {
|
||||
task = uni.request({
|
||||
...option,
|
||||
success: res =>
|
||||
handleResponse<T>(res, resolve, reject, aborted),
|
||||
fail: (err) => {
|
||||
if (aborted)
|
||||
return reject(new Error('请求已取消'))
|
||||
toast.fail('网络请求失败,请联系管理人员')
|
||||
|
||||
reject(new Error(err.errMsg || '网络请求失败'))
|
||||
toast.fail('网络错误')
|
||||
reject(err)
|
||||
},
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.fail('机构配置获取失败,请联系管理人员!')
|
||||
console.error(err)
|
||||
reject(new Error('机构配置获取失败,请联系管理人员!'))
|
||||
})
|
||||
}) as Promise<IResData<T>> & { abort: () => void }
|
||||
})()
|
||||
|
||||
promise.abort = () => {
|
||||
const finalPromise = promise as Promise<IResData<T>> & { abort: () => void }
|
||||
|
||||
finalPromise.abort = () => {
|
||||
aborted = true
|
||||
task?.abort()
|
||||
}
|
||||
|
||||
return promise
|
||||
return finalPromise
|
||||
}
|
||||
|
||||
/**
|
||||
* ======================
|
||||
* 上传文件
|
||||
* ======================
|
||||
*/
|
||||
export function httpUpload<T = any>(
|
||||
url: string,
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
formData?: Record<string, any>,
|
||||
option?: UniApp.UploadFileOption,
|
||||
) {
|
||||
let task: UniApp.UploadTask | undefined
|
||||
let aborted = false
|
||||
|
||||
const promise = (async () => {
|
||||
const isLogin = url.includes('login')
|
||||
|
||||
const { baseUrl, headers } = await getBaseConfig(
|
||||
{ url, data: formData } as any,
|
||||
isLogin,
|
||||
)
|
||||
|
||||
const fullUrl = joinUrl(baseUrl, url)
|
||||
|
||||
const mergedFormData = {
|
||||
...formData,
|
||||
fileName,
|
||||
}
|
||||
|
||||
return new Promise<IResData<T>>((resolve, reject) => {
|
||||
task = uni.uploadFile({
|
||||
url: fullUrl,
|
||||
filePath,
|
||||
name: 'file',
|
||||
formData: mergedFormData,
|
||||
header: {
|
||||
...headers,
|
||||
...(option?.header || {}),
|
||||
},
|
||||
success: res =>
|
||||
handleResponse<T>(res, resolve, reject, aborted),
|
||||
fail: (err) => {
|
||||
if (aborted)
|
||||
return reject(new Error('请求已取消'))
|
||||
toast.fail('网络错误')
|
||||
reject(err)
|
||||
},
|
||||
...option,
|
||||
})
|
||||
})
|
||||
})()
|
||||
|
||||
const finalPromise = promise as Promise<IResData<T>> & { abort: () => void }
|
||||
|
||||
finalPromise.abort = () => {
|
||||
aborted = true
|
||||
task?.abort()
|
||||
}
|
||||
|
||||
return finalPromise
|
||||
}
|
||||
/**
|
||||
* ======================
|
||||
* 工具函数
|
||||
* ======================
|
||||
*/
|
||||
function parseFormUrlencoded(str: string) {
|
||||
return str.split('&').reduce((acc, pair) => {
|
||||
const [key, value] = pair.split('=')
|
||||
@@ -181,39 +267,16 @@ function parseFormUrlencoded(str: string) {
|
||||
}, {} as Record<string, string>)
|
||||
}
|
||||
|
||||
// GET
|
||||
request.get = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
method: 'GET',
|
||||
url,
|
||||
data,
|
||||
...option,
|
||||
})
|
||||
}
|
||||
/**
|
||||
* ======================
|
||||
* 方法封装
|
||||
* ======================
|
||||
*/
|
||||
request.get = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) =>
|
||||
request<T>({ method: 'GET', url, data, ...option })
|
||||
|
||||
// PUT
|
||||
request.put = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
method: 'PUT',
|
||||
url,
|
||||
data,
|
||||
...option,
|
||||
})
|
||||
}
|
||||
|
||||
// DELETE
|
||||
request.delete = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
method: 'DELETE',
|
||||
url,
|
||||
data,
|
||||
...option,
|
||||
})
|
||||
}
|
||||
|
||||
// POST JSON
|
||||
request.postJson = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
return request<T>({
|
||||
request.postJson = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) =>
|
||||
request<T>({
|
||||
method: 'POST',
|
||||
url,
|
||||
data,
|
||||
@@ -223,17 +286,10 @@ request.postJson = <T = any>(url: string, data?: any, option?: UniApp.RequestOpt
|
||||
},
|
||||
...option,
|
||||
})
|
||||
}
|
||||
|
||||
// POST 表单
|
||||
|
||||
request.postForm = <T = any>(url: string, data?: any, option?: UniApp.RequestOptions) => {
|
||||
let payload: string
|
||||
if (data) {
|
||||
// 使用 qs.stringify 或 URLSearchParams
|
||||
payload = qs.stringify(data, { arrayFormat: 'repeat' })
|
||||
// 生成 tables=val1&tables=val2 格式
|
||||
}
|
||||
const payload = data ? qs.stringify(data, { arrayFormat: 'repeat' }) : undefined
|
||||
|
||||
return request<T>({
|
||||
method: 'POST',
|
||||
url,
|
||||
@@ -247,16 +303,7 @@ request.postForm = <T = any>(url: string, data?: any, option?: UniApp.RequestOpt
|
||||
}
|
||||
|
||||
export function httpPost<T = any>(url: string, data?: any, option?: UniApp.RequestOptions) {
|
||||
return request<T>({
|
||||
method: 'POST',
|
||||
url,
|
||||
data,
|
||||
header: {
|
||||
'Content-Type': 'application/json',
|
||||
...(option?.header || {}),
|
||||
},
|
||||
...option,
|
||||
})
|
||||
return request.postJson<T>(url, data, option)
|
||||
}
|
||||
|
||||
export default request
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"g3hd": "http://192.168.5.6:80/api"
|
||||
"g3hd": "http://192.168.1.137:80/api"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -1,9 +1,9 @@
|
||||
@font-face {
|
||||
font-family: 'iconfont'; /* Project id 5142758 */
|
||||
src:
|
||||
url('data:application/x-font-woff2;charset=utf-8;base64,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')
|
||||
url('data:application/x-font-woff2;charset=utf-8;base64,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')
|
||||
format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_5142758_pgr9uja0qt.ttf?t=1773887288142') format('truetype');
|
||||
url('//at.alicdn.com/t/c/font_5142758_32yu5am87gx.ttf?t=1774661340978') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -14,18 +14,18 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconfont-tupian:before {
|
||||
content: '\e865';
|
||||
}
|
||||
|
||||
.iconfont-iconyuanbanben_OCR:before {
|
||||
content: '\e6b6';
|
||||
}
|
||||
|
||||
.iconfont-fasongtijiao:before {
|
||||
content: '\e693';
|
||||
}
|
||||
|
||||
.iconfont-tupian:before {
|
||||
content: '\e8ba';
|
||||
}
|
||||
|
||||
.iconfont-OCRshibie:before {
|
||||
content: '\e658';
|
||||
}
|
||||
|
||||
.iconfont-save:before {
|
||||
content: '\e8c7';
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ page,
|
||||
.sar-portal {
|
||||
--sar-input-border-color: #ddd;
|
||||
// --sar-primary: #34495e;
|
||||
--sar-primary: #4d80f0;
|
||||
--sar-primary: #0052d9;
|
||||
--sar-rounded: 8rpx;
|
||||
--sar-dialog-title-font-size: 42rpx;
|
||||
--sar-dialog-message-font-size: 36rpx;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { toast } from 'sard-uniapp'
|
||||
import { getaliyunkeyApi } from '@/request/api'
|
||||
import { getOssKey } from './oss'
|
||||
import { isApp, isH5, isMpWeixin } from './platform'
|
||||
|
||||
const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET
|
||||
@@ -115,70 +115,12 @@ export function formatFileSize(size: number): string {
|
||||
else return `${(size / (1024 * 1024 * 1024)).toFixed(2)} GB`
|
||||
}
|
||||
|
||||
export interface AliyunUploadResult {
|
||||
url: string // 上传后的完整 URL
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件到阿里云 OSS
|
||||
* @param filePath 本地文件路径
|
||||
* @param fileName 文件名
|
||||
* @param fileType 文件类型,可选:image/video/file
|
||||
* @returns Promise<AliyunUploadResult>
|
||||
* 上传文件到阿里云 OSS(优化版)
|
||||
*/
|
||||
export async function uploadFileToAliyun(
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
fileType: any,
|
||||
): Promise<AliyunUploadResult> {
|
||||
// 获取 OSS 临时上传凭证
|
||||
const keyRes = await getaliyunkeyApi({
|
||||
bucketName: aliyunBucket,
|
||||
})
|
||||
const { accessid, callback, dir, host, policy, signature } = keyRes.data
|
||||
|
||||
// 构建文件在 OSS 的存储路径
|
||||
const fileExtension = fileName.split('.').pop()?.toLowerCase() || 'tmp'
|
||||
const timestamp = Date.now()
|
||||
const random = Math.floor(Math.random() * 1000)
|
||||
const key = `${dir}${timestamp + random}.${fileExtension}`
|
||||
|
||||
const formData: Record<string, any> = {
|
||||
key,
|
||||
OSSAccessKeyId: accessid,
|
||||
policy,
|
||||
signature,
|
||||
success_action_status: '200',
|
||||
name: fileName,
|
||||
}
|
||||
if (callback)
|
||||
formData.callback = callback
|
||||
|
||||
// 使用 Promise 包裹 uni.uploadFile
|
||||
return new Promise<AliyunUploadResult>((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: host,
|
||||
filePath,
|
||||
name: 'file',
|
||||
fileName,
|
||||
fileType,
|
||||
formData,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
resolve({
|
||||
url: `${host}/${key}`,
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject(new Error(`上传失败,状态码: ${res.statusCode}`))
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
/** =================== 工具函数 =================== */
|
||||
|
||||
/** 获取文件名:从 URL 提取最后一段 */
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// utils/ossManager.ts
|
||||
import { getAliyunkeyApi } from '@/request/api'
|
||||
|
||||
const aliyunBucket = import.meta.env.VITE_ALIYUN_BUCKET
|
||||
|
||||
interface OssKey {
|
||||
accessid: string
|
||||
callback?: string
|
||||
dir: string
|
||||
host: string
|
||||
policy: string
|
||||
signature: string
|
||||
expire: number
|
||||
}
|
||||
|
||||
interface OssUpdateResult {
|
||||
url: string // 上传后的完整 URL
|
||||
}
|
||||
|
||||
// ✅ 模块级缓存(天然单例)
|
||||
let ossKeyCache: OssKey | null = null
|
||||
let ossKeyPromise: Promise<OssKey> | null = null
|
||||
|
||||
/**
|
||||
* 获取 OSS Key(缓存 + 并发锁)
|
||||
*/
|
||||
export async function getOssKey(): Promise<OssKey> {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const SAFE_GAP = 10 // 提前10秒过期
|
||||
|
||||
// ✅ 命中缓存
|
||||
if (ossKeyCache && ossKeyCache.expire - SAFE_GAP > now) {
|
||||
console.log('===命中缓存===')
|
||||
return ossKeyCache
|
||||
}
|
||||
|
||||
// ✅ 并发锁
|
||||
if (ossKeyPromise) {
|
||||
return ossKeyPromise
|
||||
}
|
||||
|
||||
// ✅ 请求
|
||||
ossKeyPromise = getAliyunkeyApi({
|
||||
bucketName: aliyunBucket,
|
||||
})
|
||||
.then((res) => {
|
||||
console.log('===重新请求===')
|
||||
|
||||
const data = res.data
|
||||
|
||||
ossKeyCache = {
|
||||
accessid: data.accessid,
|
||||
callback: data.callback,
|
||||
dir: data.dir,
|
||||
host: data.host,
|
||||
policy: data.policy,
|
||||
signature: data.signature,
|
||||
expire: Number(data.expire),
|
||||
}
|
||||
|
||||
return ossKeyCache
|
||||
})
|
||||
.finally(() => {
|
||||
ossKeyPromise = null
|
||||
})
|
||||
|
||||
return ossKeyPromise
|
||||
}
|
||||
|
||||
export async function uploadToOss(
|
||||
filePath: string,
|
||||
fileName: string,
|
||||
fileType?: 'image' | 'video' | 'file',
|
||||
): Promise<OssUpdateResult> {
|
||||
// ✅ 使用缓存后的 key
|
||||
const { accessid, callback, dir, host, policy, signature } = await getOssKey()
|
||||
|
||||
// 生成文件路径
|
||||
const ext = fileName.split('.').pop()?.toLowerCase() || 'tmp'
|
||||
const key = `${dir}${Date.now()}_${Math.floor(Math.random() * 1000)}.${ext}`
|
||||
|
||||
const formData: Record<string, any> = {
|
||||
key,
|
||||
OSSAccessKeyId: accessid,
|
||||
policy,
|
||||
signature,
|
||||
success_action_status: '200',
|
||||
name: fileName,
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
formData.callback = callback
|
||||
}
|
||||
|
||||
return new Promise<OssUpdateResult>((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: host,
|
||||
filePath,
|
||||
name: 'file',
|
||||
fileName,
|
||||
fileType: 'image',
|
||||
formData,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
resolve({
|
||||
url: `${host}/${key}`,
|
||||
})
|
||||
}
|
||||
else {
|
||||
reject(new Error(`上传失败,状态码: ${res.statusCode}`))
|
||||
}
|
||||
},
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,3 @@
|
||||
/*
|
||||
* @Author: 菲鸽
|
||||
* @Date: 2024-03-28 19:13:55
|
||||
* @Last Modified by: 菲鸽
|
||||
* @Last Modified time: 2024-03-28 19:24:55
|
||||
*/
|
||||
export const platform = __UNI_PLATFORM__
|
||||
export const isH5 = __UNI_PLATFORM__ === 'h5'
|
||||
export const isApp = __UNI_PLATFORM__ === 'app'
|
||||
@@ -13,6 +7,11 @@ export const isMpAplipay = __UNI_PLATFORM__.startsWith('mp-alipay')
|
||||
export const isMpToutiao = __UNI_PLATFORM__.startsWith('mp-toutiao')
|
||||
export const isHarmony = __UNI_PLATFORM__.startsWith('app-harmony')
|
||||
|
||||
function isInWeChat() {
|
||||
const ua = navigator.userAgent.toLowerCase()
|
||||
return ua.includes('micromessenger')
|
||||
}
|
||||
|
||||
const PLATFORM = {
|
||||
platform,
|
||||
isH5,
|
||||
@@ -22,5 +21,6 @@ const PLATFORM = {
|
||||
isMpAplipay,
|
||||
isMpToutiao,
|
||||
isHarmony,
|
||||
isInWeChat
|
||||
}
|
||||
export default PLATFORM
|
||||
@@ -1,4 +1,6 @@
|
||||
import { httpPost } from ".";
|
||||
import { httpPost, httpUpload } from ".";
|
||||
|
||||
|
||||
|
||||
// 登录
|
||||
export function loginApi(orgid, userid, password) {
|
||||
|
||||
@@ -112,6 +112,41 @@ export function httpPost(url, data, config) {
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用文件上传
|
||||
* @param {string} url - 上传接口地址
|
||||
* @param {File | File[]} file - 单个或多个文件
|
||||
* @param {Object} extraData - 额外参数(可选)
|
||||
* @param {Object} config - axios 配置(可选)
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export function httpUpload(url, file, extraData = {}, config = {}) {
|
||||
const formData = new FormData();
|
||||
|
||||
// 兼容单文件 & 多文件
|
||||
if (Array.isArray(file)) {
|
||||
file.forEach((f) => {
|
||||
formData.append("file", f);
|
||||
});
|
||||
} else {
|
||||
formData.append("file", file);
|
||||
}
|
||||
|
||||
// 追加额外参数
|
||||
Object.keys(extraData).forEach((key) => {
|
||||
formData.append(key, extraData[key]);
|
||||
});
|
||||
|
||||
return axiosInstance
|
||||
.post(url, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
...config,
|
||||
})
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
export function reset401Flag() {
|
||||
hasShown401 = false;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,9 @@ async function loadTableColumns() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传到临时文件夹
|
||||
*/
|
||||
async function loadTableDatas() {
|
||||
loading.table = true;
|
||||
|
||||
|
||||
Reference in new issue
Block a user