重新初始化项目
This commit is contained in:
122
src/main/java/com/ecep/contract/manager/util/AsyncUtils.java
Normal file
122
src/main/java/com/ecep/contract/manager/util/AsyncUtils.java
Normal file
@@ -0,0 +1,122 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.ecep.contract.manager.Desktop;
|
||||
|
||||
/**
|
||||
* 异步任务工具类 - 提供超时处理等增强功能
|
||||
*/
|
||||
public class AsyncUtils {
|
||||
/**
|
||||
* 执行带超时的CompletableFuture任务
|
||||
*/
|
||||
public static <T> CompletableFuture<T> supplyAsyncWithTimeout(Supplier<T> supplier, long timeout, TimeUnit unit) {
|
||||
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier);
|
||||
return withTimeout(future, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行带超时的CompletableFuture任务,并指定线程池
|
||||
*/
|
||||
public static <T> CompletableFuture<T> supplyAsyncWithTimeout(Supplier<T> supplier, Executor executor, long timeout,
|
||||
TimeUnit unit) {
|
||||
CompletableFuture<T> future = CompletableFuture.supplyAsync(supplier, executor);
|
||||
return withTimeout(future, timeout, unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为已有的CompletableFuture添加超时处理
|
||||
*/
|
||||
public static <T> CompletableFuture<T> withTimeout(CompletableFuture<T> future, long timeout, TimeUnit unit) {
|
||||
CompletableFuture<T> timeoutFuture = new CompletableFuture<>();
|
||||
|
||||
ScheduledExecutorService executor = Desktop.instance.getExecutorService();
|
||||
|
||||
ScheduledFuture<?> schedule = executor.schedule(() -> {
|
||||
TimeoutException timeoutException = new TimeoutException(
|
||||
"Task timed out after " + timeout + " " + unit.name());
|
||||
timeoutFuture.completeExceptionally(timeoutException);
|
||||
}, timeout, unit);
|
||||
;
|
||||
|
||||
return future.applyToEither(timeoutFuture, t -> t)
|
||||
.whenComplete((t, ex) -> schedule.cancel(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* 带重试机制的CompletableFuture任务
|
||||
*/
|
||||
public static <T> CompletableFuture<T> supplyAsyncWithRetry(Supplier<T> supplier, int maxRetries,
|
||||
long delayBetweenRetries, TimeUnit unit) {
|
||||
return supplyAsyncWithRetry(supplier, maxRetries, delayBetweenRetries, unit, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 带重试机制的CompletableFuture任务,并指定哪些异常需要重试
|
||||
*/
|
||||
public static <T> CompletableFuture<T> supplyAsyncWithRetry(
|
||||
Supplier<T> supplier,
|
||||
int maxRetries,
|
||||
long delayBetweenRetries,
|
||||
TimeUnit unit,
|
||||
Class<? extends Exception>[] retryableExceptions) {
|
||||
|
||||
CompletableFuture<T> resultFuture = new CompletableFuture<>();
|
||||
|
||||
// 执行任务的方法
|
||||
executeWithRetry(supplier, resultFuture, 0, maxRetries, delayBetweenRetries, unit, retryableExceptions);
|
||||
|
||||
return resultFuture;
|
||||
}
|
||||
|
||||
private static <T> void executeWithRetry(
|
||||
Supplier<T> supplier,
|
||||
CompletableFuture<T> resultFuture,
|
||||
int currentAttempt,
|
||||
int maxRetries,
|
||||
long delayBetweenRetries,
|
||||
TimeUnit unit,
|
||||
Class<? extends Exception>[] retryableExceptions) {
|
||||
|
||||
CompletableFuture.supplyAsync(supplier)
|
||||
.thenAccept(resultFuture::complete)
|
||||
.exceptionally(ex -> {
|
||||
if (currentAttempt < maxRetries && shouldRetry(ex, retryableExceptions)) {
|
||||
// 延迟后重试
|
||||
try {
|
||||
Thread.sleep(unit.toMillis(delayBetweenRetries));
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
resultFuture.completeExceptionally(ie);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 递归调用,增加尝试次数
|
||||
executeWithRetry(supplier, resultFuture, currentAttempt + 1,
|
||||
maxRetries, delayBetweenRetries, unit, retryableExceptions);
|
||||
} else {
|
||||
// 达到最大重试次数或异常不可重试,完成Future
|
||||
resultFuture.completeExceptionally(ex);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean shouldRetry(Throwable ex, Class<? extends Exception>[] retryableExceptions) {
|
||||
// 如果未指定重试异常类型,则所有异常都重试
|
||||
if (retryableExceptions == null || retryableExceptions.length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查异常是否属于可重试类型
|
||||
for (Class<? extends Exception> exceptionClass : retryableExceptions) {
|
||||
if (exceptionClass.isInstance(ex.getCause())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
172
src/main/java/com/ecep/contract/manager/util/ExcelUtils.java
Normal file
172
src/main/java/com/ecep/contract/manager/util/ExcelUtils.java
Normal file
@@ -0,0 +1,172 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.CellAddress;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ExcelUtils {
|
||||
public static Row getRow(Sheet sheet, int rowIndex, boolean autoCreate) {
|
||||
Row row = sheet.getRow(rowIndex);
|
||||
if (row == null) {
|
||||
if (autoCreate) {
|
||||
return sheet.createRow(rowIndex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
public static Cell getCell(Sheet sheet, String cellAddress) {
|
||||
return getCell(sheet, cellAddress, false);
|
||||
}
|
||||
|
||||
public static Cell getCell(Sheet sheet, String cellAddress, boolean autoCreate) {
|
||||
CellAddress address = new CellAddress(cellAddress);
|
||||
Row row = getRow(sheet, address.getRow(), autoCreate);
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
return getCell(row, address.getColumn(), autoCreate);
|
||||
}
|
||||
|
||||
public static Cell getCell(Sheet sheet, int rowIndex, int columnIndex, boolean autoCreate) {
|
||||
Row row = getRow(sheet, rowIndex, autoCreate);
|
||||
if (row == null) {
|
||||
return null;
|
||||
}
|
||||
return getCell(row, columnIndex, autoCreate);
|
||||
}
|
||||
|
||||
public static Cell getCell(Row row, int columnIndex, boolean autoCreate) {
|
||||
Cell cell = row.getCell(columnIndex);
|
||||
if (cell == null) {
|
||||
if (autoCreate) {
|
||||
return row.createCell(columnIndex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static String getCellValue(Sheet sheet, String cellAddress) {
|
||||
Cell cell = getCell(sheet, cellAddress);
|
||||
if (cell == null) {
|
||||
return null;
|
||||
}
|
||||
return cell.getStringCellValue();
|
||||
}
|
||||
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, String cellAddress, String text) {
|
||||
CellAddress address = new CellAddress(cellAddress);
|
||||
return setCellValue(sheet, address.getRow(), address.getColumn(), text);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, String cellAddress, double value) {
|
||||
CellAddress address = new CellAddress(cellAddress);
|
||||
return setCellValue(sheet, address.getRow(), address.getColumn(), value);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, String cellAddress, LocalDate date) {
|
||||
CellAddress address = new CellAddress(cellAddress);
|
||||
return setCellValue(sheet, address.getRow(), address.getColumn(), date);
|
||||
}
|
||||
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, String text) {
|
||||
Row row = getRow(sheet, rowIndex, true);
|
||||
return setCellValue(row, columnIndex, text);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Row row, int columnIndex, String text) {
|
||||
assert row != null;
|
||||
Cell cell = getCell(row, columnIndex, true);
|
||||
assert cell != null;
|
||||
cell.setCellValue(text);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, double value) {
|
||||
Row row = getRow(sheet, rowIndex, true);
|
||||
return setCellValue(row, columnIndex, value);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Row row, int columnIndex, double value) {
|
||||
assert row != null;
|
||||
Cell cell = getCell(row, columnIndex, true);
|
||||
assert cell != null;
|
||||
cell.setCellValue(value);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, LocalDate date) {
|
||||
Row row = getRow(sheet, rowIndex, true);
|
||||
assert row != null;
|
||||
return setCellValue(row, columnIndex, date);
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Row row, int columnIndex, LocalDate date) {
|
||||
assert row != null;
|
||||
Cell cell = getCell(row, columnIndex, true);
|
||||
assert cell != null;
|
||||
cell.setCellValue(date);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static Cell setCellValue(Sheet sheet, int rowIndex, int columnIndex, Double value) {
|
||||
Row row = getRow(sheet, rowIndex, true);
|
||||
assert row != null;
|
||||
Cell cell = getCell(row, columnIndex, true);
|
||||
assert cell != null;
|
||||
if (value == null) {
|
||||
cell.setBlank();
|
||||
} else {
|
||||
cell.setCellValue(value);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
public static void setBorder(CellStyle cellStyle, BorderStyle borderStyle) {
|
||||
cellStyle.setBorderTop(borderStyle);
|
||||
cellStyle.setBorderBottom(borderStyle);
|
||||
cellStyle.setBorderLeft(borderStyle);
|
||||
cellStyle.setBorderRight(borderStyle);
|
||||
}
|
||||
|
||||
public static void mergedCells(Sheet sheet, String address) {
|
||||
CellRangeAddress cellAddresses = CellRangeAddress.valueOf(address);
|
||||
sheet.addMergedRegion(cellAddresses);
|
||||
}
|
||||
|
||||
public static void mergedCells(Cell cell, int rows, int cols) {
|
||||
Sheet sheet = cell.getSheet();
|
||||
CellRangeAddress region = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex() + rows, cell.getColumnIndex(), cell.getColumnIndex() + cols);
|
||||
sheet.addMergedRegion(region);
|
||||
}
|
||||
|
||||
public static void copyStyle(Sheet source, String sourceCellAddress, Sheet target, String targetCellAddress) {
|
||||
Cell sourceCell = getCell(source, sourceCellAddress);
|
||||
Cell targetCell = getCell(target, targetCellAddress);
|
||||
copyStyle(sourceCell, targetCell);
|
||||
}
|
||||
|
||||
public static void copyStyle(Sheet source, String sourceCellAddress, Cell targetCell) {
|
||||
Cell sourceCell = getCell(source, sourceCellAddress);
|
||||
copyStyle(sourceCell, targetCell);
|
||||
}
|
||||
|
||||
public static void copyStyle(Cell source, Cell target) {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
target.setCellStyle(source.getCellStyle());
|
||||
}
|
||||
}
|
||||
87
src/main/java/com/ecep/contract/manager/util/FxmlUtils.java
Normal file
87
src/main/java/com/ecep/contract/manager/util/FxmlUtils.java
Normal file
@@ -0,0 +1,87 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import com.ecep.contract.manager.AppV2;
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class FxmlUtils {
|
||||
|
||||
public static FXMLLoader newLoader(String path) {
|
||||
FXMLLoader loader = new FXMLLoader();
|
||||
URL location = AppV2.class.getResource(path);
|
||||
if (location == null) {
|
||||
throw new RuntimeException("无法找到窗口资源文件 " + path);
|
||||
}
|
||||
loader.setLocation(location);
|
||||
loader.setControllerFactory(SpringApp::getBean);
|
||||
return loader;
|
||||
}
|
||||
|
||||
|
||||
public static CompletableFuture<FXMLLoader> newLoaderAsync(String path, java.util.function.Consumer<FXMLLoader> consumer) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
FXMLLoader loader = newLoader(path);
|
||||
try {
|
||||
loader.load();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable open " + path, e);
|
||||
}
|
||||
consumer.accept(loader);
|
||||
return loader;
|
||||
}).whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
UITools.showExceptionAndWait("无法打开 " + path + " 界面", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步载入显示界面
|
||||
*
|
||||
* @param path fxml文件路径,类地址 / 开头 根路径
|
||||
* @param initializeLoader fxml 文件加载完毕后,回调函数
|
||||
* @param runLater 在JavaFx线程中执行的回调函数
|
||||
* @return CompletableFuture
|
||||
*/
|
||||
public static CompletableFuture<Void> newLoaderAsyncWithRunLater(
|
||||
String path,
|
||||
java.util.function.Consumer<FXMLLoader> initializeLoader,
|
||||
java.util.function.Consumer<FXMLLoader> runLater
|
||||
) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
FXMLLoader loader = newLoader(path);
|
||||
if (initializeLoader != null) {
|
||||
initializeLoader.accept(loader);
|
||||
}
|
||||
loader.load();
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
runLater.accept(loader);
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
future.completeExceptionally(new RuntimeException("Unable open " + path, e));
|
||||
}
|
||||
}).whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
future.completeExceptionally(ex);
|
||||
}
|
||||
});
|
||||
future.whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
UITools.showExceptionAndWait("无法打开 " + path + " 界面", ex);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import javafx.beans.property.Property;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class HibernateUtils {
|
||||
/**
|
||||
*
|
||||
* @param bean
|
||||
* @param repositoryClass
|
||||
* @return
|
||||
* @param <T>
|
||||
* @param <ID>
|
||||
*/
|
||||
public static <T, ID> T initialize(
|
||||
T bean,
|
||||
Class<? extends CrudRepository<T, ID>> repositoryClass
|
||||
) {
|
||||
if (Hibernate.isInitialized(bean)) {
|
||||
return bean;
|
||||
}
|
||||
PropertyDescriptor idDescriptor = BeanUtils.getPropertyDescriptor(bean.getClass(), "id");
|
||||
if (idDescriptor != null) {
|
||||
try {
|
||||
ID id = (ID) idDescriptor.getReadMethod().invoke(bean);
|
||||
return SpringApp.getBean(repositoryClass).findById(id).orElse(bean);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
public static <T, ID> Property<T> initialize(
|
||||
Property<T> property,
|
||||
Class<? extends CrudRepository<T, ID>> repositoryClass
|
||||
) {
|
||||
T value = property.getValue();
|
||||
if (value == null) {
|
||||
return property;
|
||||
}
|
||||
if (Hibernate.isInitialized(value)) {
|
||||
return property;
|
||||
}
|
||||
try {
|
||||
PropertyDescriptor idDescriptor = BeanUtils.getPropertyDescriptor(value.getClass(), "id");
|
||||
ID id = (ID) idDescriptor.getReadMethod().invoke(value);
|
||||
Optional<T> optional = SpringApp.getBean(repositoryClass).findById(id);
|
||||
T t = optional.get();
|
||||
property.setValue(t);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
|
||||
public static <T, ID> T initializePropertyValue(
|
||||
Property<T> property,
|
||||
CrudRepository<T, ID> repository
|
||||
) {
|
||||
Property<T> prop = initializeProxyBean(property, repository);
|
||||
return prop.getValue();
|
||||
}
|
||||
|
||||
public static <T, ID> Property<T> initializeProxyBean(
|
||||
Property<T> property,
|
||||
CrudRepository<T, ID> repository
|
||||
) {
|
||||
T value = property.getValue();
|
||||
if (value == null) {
|
||||
return property;
|
||||
}
|
||||
if (Hibernate.isInitialized(value)) {
|
||||
return property;
|
||||
}
|
||||
try {
|
||||
PropertyDescriptor idDescriptor = BeanUtils.getPropertyDescriptor(value.getClass(), "id");
|
||||
ID id = (ID) idDescriptor.getReadMethod().invoke(value);
|
||||
Optional<T> optional = repository.findById(id);
|
||||
T t = optional.get();
|
||||
property.setValue(t);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class HttpJsonUtils {
|
||||
public static void post(String url,
|
||||
Consumer<Map<String, Object>> data,
|
||||
Consumer<JsonNode> consumer,
|
||||
ObjectMapper objectMapper, Proxy proxy) throws IOException {
|
||||
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
data.accept(params);
|
||||
URI uri = URI.create(url);
|
||||
HttpURLConnection conn = (HttpURLConnection) (proxy == null ? uri.toURL().openConnection() : uri.toURL().openConnection(proxy));
|
||||
conn.usingProxy();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setDoOutput(true);
|
||||
conn.setDoInput(true);
|
||||
PrintWriter writer = new PrintWriter(conn.getOutputStream());
|
||||
objectMapper.writeValue(writer, params);
|
||||
writer.flush();
|
||||
conn.connect();
|
||||
try (InputStream is = conn.getInputStream()) {
|
||||
JsonNode json = objectMapper.readTree(is);
|
||||
consumer.accept(json);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void get(String url, Consumer<JsonNode> consumer, ObjectMapper objectMapper, Proxy proxy) throws IOException {
|
||||
URI uri = URI.create(url);
|
||||
HttpURLConnection conn = (HttpURLConnection) (proxy == null ? uri.toURL().openConnection() : uri.toURL().openConnection(proxy));
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Accept", "application/json, text/plain, */*");
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36");
|
||||
try (InputStream is = conn.getInputStream()) {
|
||||
JsonNode json = objectMapper.readTree(is);
|
||||
consumer.accept(json);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MyDateTimeUtils {
|
||||
/**
|
||||
* 日期 yyyy-MM-dd 的正则表达式
|
||||
*/
|
||||
public static final String REGEX_DATE = "\\d{4}-\\d{2}-\\d{2}";
|
||||
/**
|
||||
* 默认日期的格式
|
||||
*/
|
||||
public static final String DEFAULT_DATE_FORMAT_PATTERN = "yyyy-MM-dd";
|
||||
/**
|
||||
* 默认时间的格式
|
||||
*/
|
||||
public static final String DEFAULT_TIME_FORMAT_PATTERN = "HH:mm:ss";
|
||||
/**
|
||||
* 默认日期时间的格式
|
||||
*/
|
||||
public static final String DEFAULT_DATETIME_FORMAT_PATTERN = DEFAULT_DATE_FORMAT_PATTERN + " "
|
||||
+ DEFAULT_TIME_FORMAT_PATTERN;
|
||||
|
||||
/**
|
||||
* 判断 verifyDate 是否在 startDate 和 endDate 之间,
|
||||
* 当 graceDays 大于 0 时,startDate 往前移 graceDays 天,endDate 往后移 graceDays 天
|
||||
*
|
||||
* @param verifyDate 判断的日期
|
||||
* @param startDate 起始日期(包含)
|
||||
* @param endDate 截至日期(包含)
|
||||
* @param graceDays 宽限日期
|
||||
* @return 在 startDate 和 endDate 之间返回true,否则返回false
|
||||
*/
|
||||
public static boolean dateValidFilter(LocalDate verifyDate, LocalDate startDate, LocalDate endDate, int graceDays) {
|
||||
// 截至日期 endDate 在 验证日期 verifyDate 前, 表示 verifyDate 已经超出 endDate 范围
|
||||
if (endDate.isBefore(verifyDate)) {
|
||||
// 如果允许宽限期
|
||||
if (graceDays > 0) {
|
||||
// 截至日期宽限至
|
||||
LocalDate graceDate = endDate.plusDays(graceDays);
|
||||
if (graceDate.isBefore(verifyDate)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 起始日期 startDate 在 验证日期 verifyDate 后, 表示 verifyDate 已经超出 startDate 范围
|
||||
if (startDate.isAfter(verifyDate)) {
|
||||
// 如果允许宽限期
|
||||
if (graceDays > 0) {
|
||||
LocalDate graceDate = startDate.minusDays(graceDays);
|
||||
if (graceDate.isAfter(verifyDate)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期,默认格式 yyyy-MM-dd
|
||||
*
|
||||
* @param date 要格式化的日期对象
|
||||
* @return 格式化后的日期字符串
|
||||
*/
|
||||
public static String format(LocalDate date) {
|
||||
return format(date, DEFAULT_DATE_FORMAT_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期,使用指定的格式
|
||||
*
|
||||
* @param date 要格式化的日期对象
|
||||
* @param pattern 日期格式
|
||||
* @return 格式化后的日期字符串
|
||||
*/
|
||||
public static String format(LocalDate date, String pattern) {
|
||||
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
|
||||
return date.format(fmt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间,默认格式 HH:mm:ss
|
||||
*
|
||||
* @param dateTime 要格式化的时间对象
|
||||
* @return 格式化后的时间字符串
|
||||
*/
|
||||
public static String format(LocalDateTime dateTime) {
|
||||
return format(dateTime, DEFAULT_DATETIME_FORMAT_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间,使用指定的格式
|
||||
*
|
||||
* @param dateTime 要格式化的时间对象
|
||||
* @param pattern 时间格式
|
||||
* @return 格式化后的时间字符串
|
||||
*/
|
||||
public static String format(LocalDateTime dateTime, String pattern) {
|
||||
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
|
||||
return dateTime.format(fmt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间,默认格式 HH:mm:ss
|
||||
*
|
||||
* @param time 要格式化的时间对象
|
||||
* @return 格式化后的时间字符串
|
||||
*/
|
||||
public static String format(LocalTime time) {
|
||||
return format(time, DEFAULT_TIME_FORMAT_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间,使用指定的格式
|
||||
*
|
||||
* @param time 要格式化的时间对象
|
||||
* @param pattern 时间格式
|
||||
* @return 格式化后的时间字符串
|
||||
*/
|
||||
public static String format(LocalTime time, String pattern) {
|
||||
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pattern);
|
||||
return time.format(fmt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字符串中提取日期, 匹配日期未 yyyy-MM-dd
|
||||
*
|
||||
* @param string 要日期提取的字符串
|
||||
* @return 提取的日期, 如果没有匹配到则返回null
|
||||
*/
|
||||
public static LocalDate pickLocalDate(String string) {
|
||||
return pickLocalDate(string, REGEX_DATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字符串中提取日期, 使用正则表达式匹配,如果字符串中有多个匹配,只提取第一个
|
||||
*
|
||||
* @param string 要日期提取的字符串
|
||||
* @param regex 日期的正则表达式
|
||||
* @return 提取的日期, 如果没有匹配到则返回null
|
||||
*/
|
||||
public static LocalDate pickLocalDate(String string, String regex) {
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(string);
|
||||
while (matcher.find()) {
|
||||
String date = matcher.group();
|
||||
try {
|
||||
return LocalDate.parse(date);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String format(long startTime) {
|
||||
return format(new Date(startTime));
|
||||
|
||||
}
|
||||
|
||||
public static String format(Date date) {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(DEFAULT_DATETIME_FORMAT_PATTERN);
|
||||
return sdf.format(date);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 我的字符串工具箱
|
||||
*/
|
||||
public class MyStringUtils {
|
||||
/**
|
||||
* 把 append 拼接到 description 后面,如果 append 不在 description 内时
|
||||
*
|
||||
* @param description 被拼接的字符串
|
||||
* @param append 要拼接的内容
|
||||
* @return 拼接后的字符串
|
||||
*/
|
||||
public static String appendIfAbsent(String description, String append) {
|
||||
if (StringUtils.hasText(append)) {
|
||||
if (description != null && !description.contains(append)) {
|
||||
return description + ", " + description;
|
||||
}
|
||||
}
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查字符串中的每个字符,是否都是数字 {@link Character#isDigit(char)}
|
||||
*
|
||||
* @param str 要检查的字符串
|
||||
* @return 含有非字符串返回false,否则返回true
|
||||
*/
|
||||
public static boolean isAllDigit(String str) {
|
||||
if (!StringUtils.hasText(str)) {
|
||||
return false;
|
||||
}
|
||||
for (byte aByte : str.getBytes()) {
|
||||
if (!Character.isDigit(aByte)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static long toLong(String string, long defaultValue) {
|
||||
if (!StringUtils.hasText(string)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
try {
|
||||
return Long.parseLong(string);
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分割字符串
|
||||
*
|
||||
* @param str 要分割的字符串
|
||||
* @param regex 要分割的字符
|
||||
* @return 分割后的字符集合
|
||||
*/
|
||||
public static List<String> split(String str, String regex) {
|
||||
List<String> list = new ArrayList<>();
|
||||
// 未含有字符时,直接返回空集合
|
||||
if (!StringUtils.hasText(str)) {
|
||||
return list;
|
||||
}
|
||||
// 按照指定字符分割字符串
|
||||
String[] parts = str.split(regex);
|
||||
for (String part : parts) {
|
||||
if (StringUtils.hasText(part)) {
|
||||
list.add(part);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
/**
|
||||
* 数字工具类
|
||||
*/
|
||||
public class NumberUtils {
|
||||
/**
|
||||
* 判断两个double是否相等
|
||||
*
|
||||
* @param a a
|
||||
* @param b b
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean equals(double a, double b) {
|
||||
return Math.abs(a - b) < 0.01;
|
||||
}
|
||||
|
||||
public static boolean equals(float a, float b) {
|
||||
return Math.abs(a - b) < 0.01;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SpecificationUtils {
|
||||
public static <T> Specification<T> and(Specification<T> one, Specification<T> two) {
|
||||
if (one == null) {
|
||||
return two;
|
||||
}
|
||||
if (two == null) {
|
||||
return one;
|
||||
}
|
||||
return one.and(two);
|
||||
}
|
||||
|
||||
/**
|
||||
* 逻辑或
|
||||
*/
|
||||
public static <T> Specification<T> or(Specification<T> one, Specification<T> two) {
|
||||
if (one == null) {
|
||||
return two;
|
||||
}
|
||||
if (two == null) {
|
||||
return one;
|
||||
}
|
||||
return one.or(two);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 搜索条件,搜索字符串中按 “ ” 分割多个搜索条件,每个搜索条件使用 and 连接
|
||||
*
|
||||
* @param searchText 搜索的字符串
|
||||
* @param buildSearchSpecification 创建搜索条件的方法
|
||||
* @param <T> 泛型
|
||||
* @return 搜索条件为空时返回null,否则返回一个Specification对象
|
||||
*/
|
||||
public static <T> Specification<T> andWith(String searchText, Function<String, Specification<T>> buildSearchSpecification) {
|
||||
Specification<T> spec = null;
|
||||
String[] split = null;
|
||||
do {
|
||||
split = StringUtils.split(searchText, " ");
|
||||
String finalSearchText = split == null ? searchText : split[0];
|
||||
spec = SpecificationUtils.and(spec, buildSearchSpecification.apply(finalSearchText));
|
||||
if (split != null) {
|
||||
searchText = split[1];
|
||||
}
|
||||
} while (split != null);
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableRow;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.PickResult;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TableViewUtils {
|
||||
|
||||
/**
|
||||
* 获取 Table 的排序
|
||||
*/
|
||||
public static <T> List<Sort.Order> getOrders(TableView<T> table) {
|
||||
return table.getSortOrder().stream().filter(c -> {
|
||||
if (!c.isSortable()) {
|
||||
return false;
|
||||
}
|
||||
String id = c.getId();
|
||||
return id.endsWith("Column");
|
||||
}).map(c -> {
|
||||
String id = c.getId();
|
||||
id = id.replace(table.getId() + "_", "");
|
||||
id = id.replace("Column", "");
|
||||
Sort.Direction dir = c.getSortType() == TableColumn.SortType.ASCENDING ? Sort.Direction.ASC : Sort.Direction.DESC;
|
||||
return new Sort.Order(dir, id);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
public static <T> void bindDoubleClicked(TableView<T> table, Consumer<T> consumer) {
|
||||
table.setOnMouseClicked(event -> {
|
||||
if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
|
||||
PickResult pickResult = event.getPickResult();
|
||||
Node intersectedNode = pickResult.getIntersectedNode();
|
||||
if (intersectedNode instanceof TableRow) {
|
||||
// 鼠标点击的地方没有定义列,只返回了行,跳过不做处理
|
||||
TableRow<T> row = (TableRow<T>) intersectedNode;
|
||||
row.getItem();
|
||||
return;
|
||||
}
|
||||
|
||||
T selectedItem = table.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem != null) {
|
||||
try {
|
||||
consumer.accept(selectedItem);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
UITools.showExceptionAndWait("当前操作不支持", e);
|
||||
} catch (Exception e) {
|
||||
UITools.showExceptionAndWait(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static <T> void bindEnterPressed(TableView<T> table, Consumer<T> consumer) {
|
||||
table.setOnKeyPressed(event -> {
|
||||
if (event.getCode() == KeyCode.ENTER) {
|
||||
T selectedItem = table.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem != null) {
|
||||
consumer.accept(selectedItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算表格的可视行数
|
||||
*
|
||||
* @param table 表格视图
|
||||
* @param <T> T
|
||||
* @return 可视行数
|
||||
*/
|
||||
public static <T> int getTableViewVisibleRows(TableView<T> table) {
|
||||
int rows = 0;
|
||||
Set<Node> tableRow = table.lookupAll("TableRow");
|
||||
for (Node node : tableRow) {
|
||||
if (node instanceof TableRow) {
|
||||
if (!node.isVisible()) {
|
||||
continue;
|
||||
}
|
||||
rows++;
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class TaxRateUtils {
|
||||
|
||||
/**
|
||||
* 13% 一般税率(销售货物)
|
||||
*/
|
||||
public final static int TAX_RATE_GENERAL = 13;
|
||||
/**
|
||||
* 16% 一般税率(销售货物),2018 年 5 月 1 日 至 2019 年 4 月 1 日
|
||||
*/
|
||||
public final static int TAX_RATE_GENERAL_OLD = 16;
|
||||
/**
|
||||
* 17% 一般税率(销售货物),2018 年 5 月 1 日之前
|
||||
*/
|
||||
public final static int TAX_RATE_GENERAL_OLDEST = 17;
|
||||
/**
|
||||
* 9% 交通运输服务
|
||||
*/
|
||||
public final static int TAX_RATE_TRANSPORT = 9;
|
||||
/**
|
||||
* 10% 交通运输服务,2018 年 5 月 1 日 至 2019 年 4 月 1 日
|
||||
*/
|
||||
public final static int TAX_RATE_TRANSPORT_OLD = 10;
|
||||
/**
|
||||
* 11% 交通运输服务,2018 年 5 月 1 日之前
|
||||
*/
|
||||
public final static int TAX_RATE_TRANSPORT_OLDEST = 11;
|
||||
/**
|
||||
* 6% 技术服务税率
|
||||
*/
|
||||
public final static int TAX_RATE_TECH = 6;
|
||||
/**
|
||||
* 3% 小规模纳税人税率
|
||||
*/
|
||||
public final static int TAX_RATE_SMALL_SCALE = 3;
|
||||
|
||||
|
||||
/**
|
||||
* 判断日期是否在税率修改未13%之后
|
||||
*
|
||||
* @param theDateTime 日期
|
||||
* @return 是否在13%之后
|
||||
*/
|
||||
public static boolean isAfterTaxRateChangeTo13(LocalDateTime theDateTime) {
|
||||
if (theDateTime == null) {
|
||||
return false;
|
||||
}
|
||||
return isAfterTaxRateChangeTo13(theDateTime.toLocalDate());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断日期是否在税率修改未13%之后
|
||||
*
|
||||
* @param theDate 日期
|
||||
* @return 是否在13%之后
|
||||
*/
|
||||
public static boolean isAfterTaxRateChangeTo13(LocalDate theDate) {
|
||||
if (theDate == null) {
|
||||
return false;
|
||||
}
|
||||
return theDate.isAfter(LocalDate.of(2019, 4, 1));
|
||||
}
|
||||
|
||||
|
||||
public static double toExclusiveTax(double value, double taxRate) {
|
||||
return value / (100 + taxRate) / 100;
|
||||
}
|
||||
|
||||
public static double toInclusiveTax(double value, double taxRate) {
|
||||
return value * (100 + taxRate) / 100;
|
||||
}
|
||||
}
|
||||
313
src/main/java/com/ecep/contract/manager/util/UITools.java
Normal file
313
src/main/java/com/ecep/contract/manager/util/UITools.java
Normal file
@@ -0,0 +1,313 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
|
||||
import com.ecep.contract.manager.Desktop;
|
||||
import com.ecep.contract.manager.ds.other.EntityStringConverter;
|
||||
import com.ecep.contract.manager.ds.other.model.Entity;
|
||||
import com.ecep.contract.manager.ui.Message;
|
||||
import com.ecep.contract.manager.ui.Tasker;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Rectangle2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.util.StringConverter;
|
||||
import org.controlsfx.control.textfield.AutoCompletionBinding;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class UITools {
|
||||
private static final Logger logger = LoggerFactory.getLogger(UITools.class);
|
||||
|
||||
public static void showExceptionAndWait(String title, Throwable e) {
|
||||
String stackTrace = printStackTrace(e);
|
||||
Label label = new Label("异常堆栈信息如下:");
|
||||
TextArea textArea = new TextArea(stackTrace);
|
||||
textArea.setEditable(false);
|
||||
textArea.setWrapText(true);
|
||||
|
||||
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
||||
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
||||
|
||||
GridPane expContent = new GridPane();
|
||||
expContent.setMaxWidth(Double.MAX_VALUE);
|
||||
expContent.add(label, 0, 0);
|
||||
expContent.add(textArea, 0, 1);
|
||||
|
||||
String message = null;
|
||||
if (e instanceof org.springframework.orm.ObjectOptimisticLockingFailureException) {
|
||||
message = "锁冲突, 数据可能已经被修改, 请重新加载, 再次重试.";
|
||||
} else {
|
||||
message = e.getMessage();
|
||||
}
|
||||
|
||||
showAndWait(Alert.AlertType.ERROR, "程序异常", title, message, expContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示 Alert 窗口
|
||||
*
|
||||
* @param alertType alert 类型
|
||||
* @param title 标题
|
||||
* @param header 头
|
||||
* @param message 消息内容
|
||||
* @param content 扩张节点
|
||||
*/
|
||||
public static CompletableFuture<ButtonType> showAndWait(Alert.AlertType alertType, String title, String header,
|
||||
String message, Node content) {
|
||||
CompletableFuture<ButtonType> future = new CompletableFuture<>();
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = new Alert(alertType);
|
||||
if (content != null) {
|
||||
alert.getDialogPane().setExpandableContent(content);
|
||||
alert.getDialogPane().setExpanded(true);
|
||||
}
|
||||
alert.setTitle(title);
|
||||
if (header != null)
|
||||
alert.setHeaderText(header);
|
||||
if (message != null)
|
||||
alert.setContentText(message);
|
||||
|
||||
Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
|
||||
alert.getDialogPane().setMaxWidth(visualBounds.getWidth() * 0.9);
|
||||
alert.showAndWait();
|
||||
future.complete(alert.getResult());
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
public static CompletableFuture<ButtonType> showConfirmation(String title, String message) {
|
||||
return showAndWait(Alert.AlertType.CONFIRMATION, "请选择", title, message, null);
|
||||
}
|
||||
|
||||
public static <T> Dialog<T> createDialog() {
|
||||
Dialog<T> dialog = new Dialog<>();
|
||||
VBox box = new VBox();
|
||||
Label headerLabel = new Label();
|
||||
headerLabel.setId("header");
|
||||
|
||||
VBox.setMargin(headerLabel, new Insets(5, 0, 8, 0));
|
||||
|
||||
ListView<T> listView = new ListView<>();
|
||||
listView.setMinHeight(50);
|
||||
listView.setId("list-view");
|
||||
ObservableList<T> listViewDataSet = FXCollections.observableArrayList();
|
||||
listView.setItems(listViewDataSet);
|
||||
VBox.setVgrow(listView, Priority.ALWAYS);
|
||||
|
||||
box.getChildren().setAll(headerLabel, listView);
|
||||
|
||||
dialog.getDialogPane().setContent(box);
|
||||
dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
|
||||
dialog.setResizable(true);
|
||||
dialog.setWidth(600);
|
||||
dialog.setHeight(500);
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示一个对话框, 并等待用户关闭对话框
|
||||
* <p>
|
||||
* 对话框内嵌一个 ListView,可在 consumer 中向 ListView 添加内容
|
||||
*
|
||||
* @param title 对话框标题
|
||||
* @param header 对话框头
|
||||
* @param consumer 对话框内容生成器
|
||||
*/
|
||||
public static void showDialogAndWait(String title, String header, Consumer<ObservableList<String>> consumer) {
|
||||
Dialog<String> dialog = createDialog();
|
||||
dialog.setTitle(title);
|
||||
|
||||
DialogPane dialogPane = dialog.getDialogPane();
|
||||
Node content = dialogPane.getContent();
|
||||
|
||||
Label headerLabel = (Label) content.lookup("#header");
|
||||
ListView<String> listView = (ListView<String>) content.lookup("#list-view");
|
||||
headerLabel.setText(header);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
consumer.accept(listView.getItems());
|
||||
}).exceptionally(e -> {
|
||||
Platform.runLater(() -> {
|
||||
listView.getItems().add(e.getMessage());
|
||||
});
|
||||
if (logger.isErrorEnabled()) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
dialog.showAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示一个对话框, 并等待用户关闭对话框
|
||||
*
|
||||
* @param title 对话框标题
|
||||
* @param task 任务
|
||||
* @param init 初始化
|
||||
*/
|
||||
public static void showTaskDialogAndWait(String title, javafx.concurrent.Task<?> task,
|
||||
Consumer<Consumer<Message>> init) {
|
||||
Dialog<Message> dialog = createDialog();
|
||||
dialog.getDialogPane().getScene().getStylesheets().add("/ui/dialog.css");
|
||||
dialog.setTitle(title);
|
||||
|
||||
DialogPane dialogPane = dialog.getDialogPane();
|
||||
VBox box = (VBox) dialogPane.getContent();
|
||||
Label headerLabel = (Label) box.lookup("#header");
|
||||
ListView<Message> listView = (ListView<Message>) box.lookup("#list-view");
|
||||
listView.setCellFactory(param -> new Tasker.MessageListCell());
|
||||
|
||||
headerLabel.textProperty().bind(task.titleProperty());
|
||||
|
||||
java.util.function.Predicate<Message> consumer = message -> {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
listView.getItems().add(message);
|
||||
} else {
|
||||
Platform.runLater(() -> listView.getItems().add(message));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if (task instanceof Tasker<?> tasker) {
|
||||
tasker.setMessageHandler(consumer);
|
||||
} else {
|
||||
task.messageProperty().addListener((observable, oldValue, newValue) -> {
|
||||
consumer.test(Message.info(newValue));
|
||||
});
|
||||
}
|
||||
|
||||
// 加一个进度条
|
||||
ProgressBar progressBar = new ProgressBar(0);
|
||||
progressBar.setPrefHeight(12);
|
||||
progressBar.setPrefWidth(200);
|
||||
progressBar.prefWidthProperty().bind(box.widthProperty().subtract(10));
|
||||
progressBar.setMinHeight(12);
|
||||
progressBar.setVisible(true);
|
||||
VBox.setMargin(progressBar, new Insets(6, 0, 6, 0));
|
||||
|
||||
Platform.runLater(() -> {
|
||||
box.getChildren().add(progressBar);
|
||||
});
|
||||
|
||||
// progressBar.disabledProperty().bind(task.runningProperty());
|
||||
progressBar.visibleProperty().bind(task.runningProperty());
|
||||
progressBar.progressProperty().bind(task.progressProperty());
|
||||
|
||||
if (task instanceof Tasker<?> tasker) {
|
||||
// 提交任务
|
||||
Desktop.instance.getExecutorService().submit(tasker);
|
||||
}
|
||||
if (init != null) {
|
||||
init.accept(msg -> consumer.test(msg));
|
||||
}
|
||||
dialog.showAndWait();
|
||||
if (task.isRunning()) {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private static String printStackTrace(Throwable e) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
e.printStackTrace(pw);
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
public static void showAlertAndWait(String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
public static <T> AutoCompletionBinding<T> autoCompletion(
|
||||
TextField textField,
|
||||
SimpleObjectProperty<T> property,
|
||||
EntityStringConverter<T> converter) {
|
||||
return autoCompletion(textField, property, converter::suggest, converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给 TextField 增加一个 自动补全功能
|
||||
*
|
||||
* @param textField 要补全的文本框
|
||||
* @param property 绑定的属性
|
||||
* @param func 获取补全数据的方法
|
||||
* @param converter 对象转换器
|
||||
* @param <T> 对象类型
|
||||
* @return AutoCompletionBinding
|
||||
*/
|
||||
public static <T> AutoCompletionBinding<T> autoCompletion(
|
||||
TextField textField,
|
||||
SimpleObjectProperty<T> property,
|
||||
Function<AutoCompletionBinding.ISuggestionRequest, List<T>> func,
|
||||
StringConverter<T> converter) {
|
||||
// 先赋值
|
||||
textField.textProperty().set(converter.toString(property.get()));
|
||||
// 监听 property 变化
|
||||
property.addListener((observable, oldValue, newValue) -> {
|
||||
textField.textProperty().set(converter.toString(newValue));
|
||||
});
|
||||
|
||||
// 关联一个 自动完成
|
||||
AutoCompletionBinding<T> completionBinding = TextFields.bindAutoCompletion(textField, p -> {
|
||||
if (p.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return func.apply(p);
|
||||
} catch (Exception e) {
|
||||
textField.setText(e.getMessage());
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}, converter);
|
||||
|
||||
// 设置自动补全选中值到 property
|
||||
completionBinding.setOnAutoCompleted(event -> {
|
||||
property.set(event.getCompletion());
|
||||
});
|
||||
|
||||
// fixed 当输入框丢失焦点时,输入框内容为空时,设置property 为null
|
||||
textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (!newValue) {
|
||||
if (!StringUtils.hasText(textField.getText())) {
|
||||
property.set(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return completionBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认对话框
|
||||
*
|
||||
* @param title
|
||||
* @param text
|
||||
* @return
|
||||
*/
|
||||
public static boolean showConfirmDialog(String title, String text) {
|
||||
// TODO
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(text);
|
||||
if (alert.showAndWait().get() == ButtonType.OK) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user