rev:拍照

This commit is contained in:
2025-12-06 15:33:06 +08:00
parent adbb171a73
commit d9210a08f4
6 changed files with 562 additions and 111 deletions

View File

@@ -42,8 +42,8 @@ public class DictConstantPool {
@PostConstruct
public void initHikvisonConfig(){
System.out.println("初始化海康威视配置:"+ this);
HikvisionSnapshotUtil.ins = this.ins;
HikvisionSnapshotUtil.path = this.path;
// HikvisionSnapshotUtil.ins = this.ins;
// HikvisionSnapshotUtil.path = this.path;
HikvisionSnapshotUtil.ip = this.ip;
HikvisionSnapshotUtil.port = this.port;
HikvisionSnapshotUtil.username = this.username;

View File

@@ -1,6 +1,5 @@
package org.nl.common.hikvision;
import cn.hutool.core.date.DateUtil;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
@@ -11,14 +10,21 @@ import org.springframework.stereotype.Service;
import java.io.File;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
@Service
@Slf4j
public class HikvisionSnapshotUtil {
public static String ins; //HCNetSDK.dll文件路径
public static String path; //文件存储位置
public static String ins = "C:\\Users\\Administrator\\Desktop\\hik\\HCNetSDK.dll"; //HCNetSDK.dll文件路径
public static String path = "C:\\software\\wms\\images"; //文件存储位置
public static String ip; //海康威视设备网络搜索应用中摄像头ip
public static Short port;//海康威视设备网络搜索应用中摄像头port
public static String username;//海康威视设备网络搜索应用中摄像头管理账号admin
@@ -26,27 +32,19 @@ public class HikvisionSnapshotUtil {
public static String channelHttp;//默认
public static Integer channel;//默认1
// SDK 初始化状态标记,确保全局只初始化一次
private static final AtomicBoolean sdkInitialized = new AtomicBoolean(false);
// 用于同步 SDK 初始化和清理操作的锁
private static final ReentrantLock sdkLock = new ReentrantLock();
// DLL 加载状态标记
private static final AtomicBoolean dllLoaded = new AtomicBoolean(false);
// DLL 实例,延迟加载
private static volatile HCNetSDK sdkInstance = null;
// 用于同步 DLL 加载的锁
private static final ReentrantLock dllLoadLock = new ReentrantLock();
// HCNetSDK 接口映射
public interface HCNetSDK extends Library {
HCNetSDK INSTANCE = Holder.INSTANCE;
class Holder {
private static final HCNetSDK INSTANCE;
static {
if (StringUtils.isBlank(HikvisionSnapshotUtil.ins)) {
throw new RuntimeException("HCNetSDK.dll 路径未设置,请检查 hikvison.ins 配置");
}
try {
System.out.println("加载 HCNetSDK.dll => " + HikvisionSnapshotUtil.ins);
INSTANCE = Native.load("C:\\Users\\Administrator\\Desktop\\hik\\HCNetSDK.dll", HCNetSDK.class);
System.out.println("加载 HCNetSDK.dll 成功");
} catch (Throwable e) {
System.err.println("加载 HCNetSDK.dll 失败,路径:" + HikvisionSnapshotUtil.ins + "异常信息:" + e.getMessage());
throw e;
}
}
}
//初始化 SDK 库
boolean NET_DVR_Init();
@@ -95,57 +93,272 @@ public class HikvisionSnapshotUtil {
public short wPicQuality;
}
public static void sync截图(String imageName, String taskCode) {
/**
* 超时保护 防止 JNA 调用永久阻塞
*/
private static boolean executeCaptureWithTimeout(HCNetSDK sdk, int userId, int channel,
NET_DVR_JPEGPARA jpegPara, String sFileUrl,
long timeoutSeconds) throws Throwable {
Future<Boolean> future = Executors.newSingleThreadExecutor().submit(() -> {
try {
return sdk.NET_DVR_CaptureJPEGPicture(userId, channel, jpegPara, sFileUrl);
} catch (Throwable e) {
log.info("[SDK] 抓图执行时发生异常", e);
throw new RuntimeException("抓图执行异常", e);
}
});
try {
return future.get(timeoutSeconds, TimeUnit.SECONDS);
} catch (TimeoutException e) {
future.cancel(true);
log.info("[SDK] 抓图操作超时({}秒已取消。userId={}, channel={}, file={}",
timeoutSeconds, userId, channel, sFileUrl);
throw new TimeoutException("抓图操作超时: " + timeoutSeconds + "");
} catch (ExecutionException e) {
log.info(e.getMessage());
Throwable cause = e.getCause();
if (cause != null) {
throw cause;
}
throw e;
}
}
/**
* 获取 HCNetSDK 实例
*/
private static HCNetSDK getSdkInstance() {
if (sdkInstance == null) {
dllLoadLock.lock();
try {
// 双重检查
if (sdkInstance == null) {
if (StringUtils.isBlank(ins)) {
throw new RuntimeException("HCNetSDK.dll 路径未设置,请检查 hikvison.ins 配置。可能原因:配置未加载或配置项缺失");
}
try {
log.info("[SDK] 开始加载 HCNetSDK.dll => {}", ins);
sdkInstance = Native.load(ins, HCNetSDK.class);
dllLoaded.set(true);
log.info("[SDK] 加载 HCNetSDK.dll 成功");
} catch (Throwable e) {
log.info("[SDK] 加载 HCNetSDK.dll 失败,路径:{},异常信息: {}", ins, e.getMessage(), e);
throw new RuntimeException("加载 HCNetSDK.dll 失败: " + e.getMessage() + ",路径: " + ins, e);
}
}
} finally {
dllLoadLock.unlock();
}
}
return sdkInstance;
}
/**
* 截图
*
* @param imageName 图片名称
* @param taskCode 任务编码
*/
public static void syncSnap(String imageName, String taskCode) {
if (StringUtils.isEmpty(imageName)) {
log.warn("图片名称未定义");
log.info("[SDK] 图片名称未定义,跳过截图");
return;
}
// 参数校验
if (StringUtils.isBlank(ip) || port == null || StringUtils.isBlank(username)
|| StringUtils.isBlank(password) || channel == null) {
log.info("[SDK] 海康威视配置参数不完整无法执行截图。ip={}, port={}, username={}, channel={}",
ip, port, username, channel);
return;
}
// 获取 SDK 实例
HCNetSDK sdk;
try {
sdk = getSdkInstance();
} catch (RuntimeException e) {
log.info("[SDK] 获取 SDK 实例失败,无法执行截图", e);
return;
}
LocalDate now = LocalDate.now();
String datePath = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String imagePath = "C:" + File.separator + "software" + File.separator + "wms" + File.separator + "images" + File.separator + datePath;
// 使用配置的路径,如果没有配置则使用默认路径
// String basePath = StringUtils.isNotBlank(path) ? path :
// "C:" + File.separator + "software" + File.separator + "wms" + File.separator + "images";
String imagePath = path + File.separator + datePath;
File dir = new File(imagePath);
if (!dir.exists()) {
dir.mkdirs();
boolean created = dir.mkdirs();
if (!created) {
log.info("[SDK] 创建图片目录失败: {}", imagePath);
return;
}
}
String sFileUrl = imagePath + File.separator + imageName + "_" + taskCode + ".jpg";
log.info("[SDK] 开始截图,保存路径: {}", sFileUrl);
if (!HCNetSDK.INSTANCE.NET_DVR_Init()) {
log.warn("[SDK] 初始化失败");
return;
}
NET_DVR_DEVICEINFO_V30 deviceInfo = new NET_DVR_DEVICEINFO_V30();
int userId = HCNetSDK.INSTANCE.NET_DVR_Login_V30(ip, port, username, password, deviceInfo);
if (userId < 0) {
log.warn("[SDK] 登录失败");
HCNetSDK.INSTANCE.NET_DVR_Cleanup();
return;
}
NET_DVR_JPEGPARA jpegPara = new NET_DVR_JPEGPARA();
jpegPara.wPicSize = 2;
jpegPara.wPicQuality = 0;
// 使用锁确保 SDK 操作的线程安全
boolean lockAcquired = false;
try {
boolean result = HCNetSDK.INSTANCE.NET_DVR_CaptureJPEGPicture(userId, channel, jpegPara, sFileUrl);
if (result) {
log.info("[SDK] 抓图成功: " + sFileUrl);
// 尝试获取锁,最多等待 5 秒,避免死锁
lockAcquired = sdkLock.tryLock(5, TimeUnit.SECONDS);
if (!lockAcquired) {
log.info("[SDK] 获取 SDK 操作锁超时,跳过本次截图操作。可能存在死锁或长时间占用锁的情况。");
return;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.info("[SDK] 获取锁时线程被中断", e);
return;
}
int userId = -1;
boolean initSuccess = false;
try {
log.info("[SDK] 已获取锁,开始执行 SDK 操作");
// 初始化 SDK只初始化一次
if (!sdkInitialized.get()) {
try {
initSuccess = sdk.NET_DVR_Init();
if (!initSuccess) {
log.info("[SDK] SDK 初始化失败可能原因SDK 已被初始化或初始化参数错误");
return;
}
sdkInitialized.set(true);
log.info("[SDK] SDK 初始化成功");
} catch (Throwable e) {
log.info("[SDK] SDK 初始化时发生异常", e);
return;
}
} else {
// 失败重试一次
result = HCNetSDK.INSTANCE.NET_DVR_CaptureJPEGPicture(userId, channel, jpegPara, sFileUrl);
initSuccess = true;
log.info("[SDK] SDK 已初始化,跳过初始化步骤");
}
// 登录设备
NET_DVR_DEVICEINFO_V30 deviceInfo = new NET_DVR_DEVICEINFO_V30();
try {
userId = sdk.NET_DVR_Login_V30(ip, port, username, password, deviceInfo);
if (userId < 0) {
log.info("[SDK] 登录设备失败userId={}请检查设备连接和账号密码。ip={}, port={}, username={}",
userId, ip, port, username);
return;
}
log.info("[SDK] 设备登录成功userId={}", userId);
} catch (Throwable e) {
log.info("[SDK] 登录设备时发生异常ip={}, port={}, username={}", ip, port, username, e);
return;
}
// 配置抓图参数
NET_DVR_JPEGPARA jpegPara = new NET_DVR_JPEGPARA();
jpegPara.wPicSize = 2;
jpegPara.wPicQuality = 0;
// 执行抓图
boolean result = false;
try {
log.info("[SDK] 开始执行抓图操作userId={}, channel={}", userId, channel);
// 记录 JNA 调用前的状态
try {
JnaCrashMonitor.logBeforeJnaCall("NET_DVR_CaptureJPEGPicture", userId, channel, sFileUrl);
} catch (Throwable e) {
// 忽略监控日志异常
}
// 执行抓图 设置超时保护,避免永久阻塞
result = executeCaptureWithTimeout(sdk, userId, channel, jpegPara, sFileUrl, 10);
// 记录 JNA 调用后的状态
try {
JnaCrashMonitor.logAfterJnaCall("NET_DVR_CaptureJPEGPicture", result);
} catch (Throwable e) {
}
if (result) {
log.info("[SDK] 抓图成功(第二次尝试): " + sFileUrl);
log.info("[SDK] 抓图成功: {}", sFileUrl);
} else {
log.warn("[SDK] 抓图失败: " + sFileUrl);
// 失败重试一次
log.info("[SDK] 第一次抓图失败,进行重试...");
try {
Thread.sleep(500); // 等待500ms后重试
result = executeCaptureWithTimeout(sdk, userId, channel, jpegPara, sFileUrl, 10);
if (result) {
log.info("[SDK] 抓图成功(第二次尝试): {}", sFileUrl);
} else {
log.info("[SDK] 抓图失败(重试后仍失败): {}请检查设备连接和通道配置。userId={}, channel={}",
sFileUrl, userId, channel);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.info("[SDK] 重试抓图时线程被中断", e);
} catch (Throwable e) {
log.info("[SDK] 重试抓图时发生异常: {}", sFileUrl, e);
try {
JnaCrashMonitor.logJnaCallError("NET_DVR_CaptureJPEGPicture(重试)", e, userId, channel, sFileUrl);
} catch (Throwable ignored) {
}
}
}
} catch (Throwable e) {
log.info("[SDK] 抓图过程中发生异常: {}, userId={}, channel={}", sFileUrl, userId, channel, e);
if (e instanceof Error) {
log.info("[SDK] 发生严重错误Error可能导致 JVM 不稳定", e);
}
try {
JnaCrashMonitor.logJnaCallError("NET_DVR_CaptureJPEGPicture", e, userId, channel, sFileUrl);
} catch (Throwable ignored) {
}
}
} catch (Throwable e) {
log.info("[SDK] 截图过程中发生未预期的异常", e);
if (e instanceof Error) {
log.info("[SDK] 发生严重错误Error可能导致 JVM 崩溃", e);
}
} finally {
HCNetSDK.INSTANCE.NET_DVR_Logout(userId);
HCNetSDK.INSTANCE.NET_DVR_Cleanup();
log.info("[SDK] 进入finally");
try {
if (userId >= 0 && sdk != null) {
try {
boolean logoutResult = sdk.NET_DVR_Logout(userId);
if (!logoutResult) {
log.info("[SDK] 注销登录失败userId={}", userId);
} else {
log.info("[SDK] 注销登录成功userId={}", userId);
}
} catch (Throwable e) {
log.info("[SDK] 注销登录时发生异常userId={}", userId, e);
}
} else {
log.info("[SDK] 无需注销登录userId={}", userId);
}
} catch (Throwable e) {
log.info("[SDK] 清理资源时发生异常", e);
}
// 确保锁被释放
if (lockAcquired) {
log.info("[SDK] 开始释放锁");
try {
sdkLock.unlock();
log.info("[SDK] 截图操作完成,已释放锁");
} catch (IllegalMonitorStateException e) {
log.info("[SDK] 释放锁时发生异常,可能锁状态异常", e);
} catch (Throwable e) {
log.info("[SDK] 释放锁时发生未知异常", e);
}
log.info("[SDK] 释放锁完成");
}
log.info("[SDK] 结束finally");
}
}

View File

@@ -0,0 +1,170 @@
package org.nl.common.hikvision;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.ThreadMXBean;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* JNA/DLL 崩溃监控和诊断工具
* 用于监控 JNA 调用本地 DLL 时的异常情况
*
* @author System
*/
@Component
@Slf4j
public class JnaCrashMonitor {
private static final String CRASH_LOG_DIR = "C:\\software\\wms\\logs\\crash";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@PostConstruct
public void init() {
// 创建崩溃日志目录
File logDir = new File(CRASH_LOG_DIR);
if (!logDir.exists()) {
logDir.mkdirs();
}
// 设置未捕获异常处理器
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
logCrashInfo("未捕获异常", thread, throwable);
});
// 添加 JVM 关闭钩子,用于记录崩溃信息
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("[JNA监控] JVM 正在关闭,记录系统状态...");
logSystemState("JVM关闭");
}));
log.info("[JNA监控] JNA 崩溃监控已初始化,日志目录: {}", CRASH_LOG_DIR);
}
/**
* 记录崩溃信息
*/
public static void logCrashInfo(String event, Thread thread, Throwable throwable) {
String timestamp = LocalDateTime.now().format(DATE_FORMATTER);
String logFile = CRASH_LOG_DIR + File.separator + "crash_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".log";
try (FileWriter fw = new FileWriter(logFile, true);
PrintWriter pw = new PrintWriter(fw)) {
pw.println(repeatString("=", 80));
pw.println("时间: " + timestamp);
pw.println("事件: " + event);
pw.println("线程: " + (thread != null ? thread.getName() : "未知"));
pw.println("异常类型: " + (throwable != null ? throwable.getClass().getName() : "未知"));
if (throwable != null) {
pw.println("异常消息: " + throwable.getMessage());
pw.println("堆栈跟踪:");
throwable.printStackTrace(pw);
}
// 记录系统状态
logSystemStateToFile(pw);
pw.println(repeatString("=", 80));
pw.println();
} catch (IOException e) {
log.info("[JNA监控] 写入崩溃日志失败", e);
}
// 同时记录到标准日志
log.info("[JNA监控] {} - 线程: {}, 异常: {}",
event,
thread != null ? thread.getName() : "未知",
throwable != null ? throwable.getMessage() : "未知",
throwable);
}
/**
* 记录系统状态
*/
public static void logSystemState(String event) {
String logFile = CRASH_LOG_DIR + File.separator + "system_state_" +
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) + ".log";
try (FileWriter fw = new FileWriter(logFile, true);
PrintWriter pw = new PrintWriter(fw)) {
pw.println(repeatString("=", 80));
pw.println("时间: " + LocalDateTime.now().format(DATE_FORMATTER));
pw.println("事件: " + event);
logSystemStateToFile(pw);
pw.println(repeatString("=", 80));
pw.println();
} catch (IOException e) {
log.info("[JNA监控] 写入系统状态日志失败", e);
}
}
/**
* 将系统状态写入文件
*/
private static void logSystemStateToFile(PrintWriter pw) {
// 内存信息
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
pw.println("堆内存使用: " + (memoryBean.getHeapMemoryUsage().getUsed() / 1024 / 1024) + " MB / " +
(memoryBean.getHeapMemoryUsage().getMax() / 1024 / 1024) + " MB");
pw.println("非堆内存使用: " + (memoryBean.getNonHeapMemoryUsage().getUsed() / 1024 / 1024) + " MB / " +
(memoryBean.getNonHeapMemoryUsage().getMax() / 1024 / 1024) + " MB");
// 线程信息
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
pw.println("活动线程数: " + threadBean.getThreadCount());
pw.println("峰值线程数: " + threadBean.getPeakThreadCount());
// JVM 信息
pw.println("JVM 名称: " + ManagementFactory.getRuntimeMXBean().getVmName());
pw.println("JVM 版本: " + ManagementFactory.getRuntimeMXBean().getVmVersion());
pw.println("运行时间: " + (ManagementFactory.getRuntimeMXBean().getUptime() / 1000) + "");
}
/**
* 重复字符串(兼容 Java 8
*/
private static String repeatString(String str, int count) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append(str);
}
return sb.toString();
}
/**
* 记录 JNA 调用前的状态
*/
public static void logBeforeJnaCall(String methodName, Object... params) {
log.info("[JNA监控] 准备调用 JNA 方法: {}, 参数: {}", methodName, params);
}
/**
* 记录 JNA 调用后的状态
*/
public static void logAfterJnaCall(String methodName, Object result) {
log.info("[JNA监控] JNA 方法调用完成: {}, 结果: {}", methodName, result);
}
/**
* 记录 JNA 调用异常
*/
public static void logJnaCallError(String methodName, Throwable error, Object... params) {
log.info("[JNA监控] JNA 方法调用异常: {}, 参数: {}", methodName, params, error);
logCrashInfo("JNA调用异常", Thread.currentThread(), error);
}
}

View File

@@ -194,12 +194,22 @@ public class AcsToWmsServiceImpl implements AcsToWmsService {
Param ParamDao = sysParamService.findByCode("is_pat");
if (ParamDao.getValue().equals(IOSConstant.IS_DELETE_YES)) {
CompletableFuture.runAsync(() -> {
String vehicleCode = vehicleDao.getStoragevehicle_code();
String taskCode = taskDao.getTask_code();
log.info("[拍照] 开始异步调用海康拍照vehicleCode={}, taskCode={}", vehicleCode, taskCode);
try {
HikvisionSnapshotUtil.sync截图(vehicleDao.getStoragevehicle_code(), taskDao.getTask_code());
} catch (Exception e) {
log.info("调用海康拍照失败:" + e);
HikvisionSnapshotUtil.syncSnap(vehicleCode, taskCode);
log.info("[拍照] 海康拍照调用完成vehicleCode={}, taskCode={}", vehicleCode, taskCode);
} catch (Throwable e) {
log.info("[拍照] 调用海康拍照失败vehicleCode={}, taskCode={}", vehicleCode, taskCode, e);
if (e instanceof Error) {
log.info("[拍照] 发生严重错误Error可能导致线程或 JVM 不稳定", e);
}
}
}, pool);
}, pool).exceptionally(throwable -> {
log.info("[拍照] CompletableFuture 执行异常", throwable);
return null;
});
}
log.info("ACS向WMS反馈重量返回参数--------------------------------------" + BaseResponse.responseOk(resultWeigh).toString());

View File

@@ -52,7 +52,7 @@
"jquery": "^3.6.0",
"js-beautify": "^1.10.2",
"js-cookie": "2.2.0",
"jsbarcode": "^3.11.5",
"jsbarcode": "^3.12.1",
"jsencrypt": "^3.0.0-rc.1",
"json-bigint": "^1.0.0",
"jszip": "3.1.5",

View File

@@ -53,6 +53,16 @@
>
打印
</el-button>
<el-button
slot="right"
class="filter-item"
type="success"
icon="el-icon-printer"
size="mini"
@click="printPreview"
>
打印预览
</el-button>
</crudOperation>
<!--表单组件-->
<el-dialog
@@ -79,13 +89,13 @@
</el-select>
</el-form-item>
<el-form-item label="载具编码" prop="storagevehicle_code">
<el-input v-model="form.storagevehicle_code" style="width: 200px;" :disabled="crud.status.edit > 0" />
<el-input v-model="form.storagevehicle_code" style="width: 200px;" :disabled="crud.status.edit > 0"/>
</el-form-item>
<el-form-item label="载具名称" prop="storagevehicle_name">
<el-input v-model="form.storagevehicle_name" style="width: 200px;" />
<el-input v-model="form.storagevehicle_name" style="width: 200px;"/>
</el-form-item>
<el-form-item label="载具重量" prop="weigth">
<el-input-number v-model="form.weigth" :precision="1" style="width: 200px;" />
<el-input-number v-model="form.weigth" :precision="1" style="width: 200px;"/>
</el-form-item>
<el-form-item label="是否启用">
<el-radio v-model="form.is_used" label="0">否</el-radio>
@@ -106,13 +116,13 @@
style="width: 100%;"
@selection-change="crud.selectionChangeHandler"
>
<el-table-column type="selection" width="55" />
<el-table-column v-if="false" prop="storagevehicle_id" label="载具标识" />
<el-table-column prop="storagevehicle_type_name" label="载具类型" :formatter="formattType" />
<el-table-column prop="storagevehicle_code" label="载具编码" />
<el-table-column prop="storagevehicle_name" label="载具名称" />
<el-table-column prop="pcsn" label="绑定物料" />
<el-table-column prop="weigth" label="托盘重量" />
<el-table-column type="selection" width="55"/>
<el-table-column v-if="false" prop="storagevehicle_id" label="载具标识"/>
<el-table-column prop="storagevehicle_type_name" label="载具类型" :formatter="formattType"/>
<el-table-column prop="storagevehicle_code" label="载具编码"/>
<el-table-column prop="storagevehicle_name" label="载具名称"/>
<el-table-column prop="pcsn" label="绑定物料"/>
<el-table-column prop="weigth" label="托盘重量"/>
<el-table-column label="是否启用" align="center" prop="is_used">
<template slot-scope="scope">
<el-switch
@@ -143,19 +153,21 @@
</el-table-column>
</el-table>
<!--分页组件-->
<pagination />
<pagination/>
</div>
</div>
</template>
<script>
import crudStoragevehicleinfo from '@/views/wms/basedata/storagevehicleinfo/storagevehicleinfo'
import CRUD, { crud, form, header, presenter } from '@crud/crud'
import CRUD, {crud, form, header, presenter} from '@crud/crud'
import rrOperation from '@crud/RR.operation'
import crudOperation from '@crud/CRUD.operation'
import udOperation from '@crud/UD.operation'
import pagination from '@crud/Pagination'
import { getLodop } from '@/assets/js/lodop/LodopFuncs'
import {getLodop} from '@/assets/js/lodop/LodopFuncs'
import JsBarcode from 'jsbarcode'
const defaultForm = {
storagevehicle_id: null,
@@ -186,7 +198,7 @@ const defaultForm = {
export default {
name: 'Storagevehicleinfo',
dicts: ['storagevehicle_type', 'is_used'],
components: { pagination, crudOperation, rrOperation, udOperation },
components: {pagination, crudOperation, rrOperation, udOperation},
mixins: [presenter(), header(), form(defaultForm), crud()],
cruds() {
return CRUD({
@@ -194,7 +206,7 @@ export default {
url: 'api/storagevehicleinfo',
idField: 'storagevehicle_id',
sort: 'storagevehicle_id,desc',
crudMethod: { ...crudStoragevehicleinfo },
crudMethod: {...crudStoragevehicleinfo},
optShow: {
add: true,
edit: false,
@@ -220,26 +232,26 @@ export default {
classes1: [],
rules: {
is_delete: [
{ required: true, message: '不能为空', trigger: 'blur' }
{required: true, message: '不能为空', trigger: 'blur'}
],
is_used: [
{ required: true, message: '不能为空', trigger: 'blur' }
{required: true, message: '不能为空', trigger: 'blur'}
],
storagevehicle_type: [
{ required: true, message: '不能为空', trigger: 'blur' }
{required: true, message: '不能为空', trigger: 'blur'}
],
storagevehicle_code: [
{ required: true, message: '不能为空', trigger: 'blur' }
{required: true, message: '不能为空', trigger: 'blur'}
],
storagevehicle_name: [
{ required: true, message: '不能为空', trigger: 'blur' }
{required: true, message: '不能为空', trigger: 'blur'}
],
overstruct_type: [
{ required: true, message: '不能为空', trigger: 'blur' }
{required: true, message: '不能为空', trigger: 'blur'}
],
num: [
{ required: true, message: '不能为空', trigger: 'blur' },
{ validator: numberOne }
{required: true, message: '不能为空', trigger: 'blur'},
{validator: numberOne}
]
}
}
@@ -283,48 +295,92 @@ export default {
formattType(row) {
return this.dict.label.storagevehicle_type[row.storagevehicle_type]
},
printPreview() {
const _selectData = this.$refs.table.selection
if (!_selectData || _selectData.length < 1) {
this.crud.notify('请选择一条记录', CRUD.NOTIFICATION_TYPE.INFO)
return
}
let container = document.getElementById('barcodePreview')
if (!container) {
container = document.createElement('div')
container.id = 'barcodePreview'
document.body.appendChild(container)
}
container.innerHTML = ''
container.style.display = 'flex'
container.style.flexDirection = 'column'
container.style.alignItems = 'center'
container.style.background = '#fff'
container.style.padding = '20px'
const mm2px = mm => mm * 3.78
const paperWidth = mm2px(80)
const paperHeight = mm2px(60)
const barcodeHeight = mm2px(40)
const textGap = mm2px(1)
const barcodeWidth = mm2px(75) // 条码更宽
_selectData.forEach(item => {
const code = item.storagevehicle_code || ''
const pageDiv = document.createElement('div')
pageDiv.style.width = `${paperWidth}px`
pageDiv.style.height = `${paperHeight}px`
pageDiv.style.display = 'flex'
pageDiv.style.flexDirection = 'column'
pageDiv.style.alignItems = 'center'
pageDiv.style.justifyContent = 'center'
pageDiv.style.marginBottom = '20px'
pageDiv.style.border = '1px solid #ccc'
container.appendChild(pageDiv)
const canvas = document.createElement('canvas')
canvas.width = barcodeWidth
canvas.height = barcodeHeight
pageDiv.appendChild(canvas)
JsBarcode(canvas, code, {
format: "CODE128",
width: 2, // 条码线宽适中
height: barcodeHeight,
displayValue: false
})
const textDiv = document.createElement('div')
textDiv.innerText = code
textDiv.style.fontSize = '14px'
textDiv.style.fontWeight = 'bold'
textDiv.style.textAlign = 'center'
textDiv.style.marginTop = `${textGap}px`
pageDiv.appendChild(textDiv)
})
this.crud.notify(`已生成 ${_selectData.length} 条预览`, CRUD.NOTIFICATION_TYPE.INFO)
},
print() {
const _selectData = this.$refs.table.selection
if (!_selectData || _selectData.length < 1) {
this.crud.notify('请选择一条记录', CRUD.NOTIFICATION_TYPE.INFO)
return
}
// 获取 LODOP 对象
const LODOP = getLodop()
if (!LODOP) {
// getLodop 内部已经处理了错误提示
return
}
if (!LODOP) return
try {
LODOP.PRINT_INIT('载具标签打印')
LODOP.SET_SHOW_MODE('HIDE_DISBUTTIN_SETUP', 1)
// 批量打印选中的记录
for (let i = 0; i < _selectData.length; i++) {
const code = _selectData[i].storagevehicle_code
const name = _selectData[i].storagevehicle_name || code
// 打印纸张大小60 * 80
// 参数说明SET_PRINT_PAGESIZE(方向, 宽度, 高度, 单位)
// 方向0-纵向1-横向
LODOP.SET_PRINT_PAGESIZE(0, '60mm', '80mm', '')
// 添加条码
// 条码位置:水平居中 (60-50)/2 = 5mm垂直居中 (80-30)/2 = 25mm
// 条码大小50mm宽 * 30mm高可根据实际需要调整
LODOP.ADD_PRINT_BARCODE('25mm', '5mm', '30mm', '50mm', '128Auto', code)
// 如果需要打印多份,使用 NEWPAGE 分隔
if (i < _selectData.length - 1) {
LODOP.NEWPAGE()
}
const code = _selectData[i].storagevehicle_code || ''
LODOP.SET_PRINT_PAGESIZE(1, `80mm`, `60mm`, '1')
LODOP.ADD_PRINT_BARCODE(`7.5mm`, `10mm`, `62.5mm`, `47.5mm`, '128Auto', code)
if (i < _selectData.length - 1) LODOP.NEWPAGE()
}
// 执行打印(会在客户端电脑上执行)
LODOP.PRINT()
this.crud.notify(`成功打印 ${_selectData.length} 条记录`, CRUD.NOTIFICATION_TYPE.SUCCESS)
this.crud.toQuery()
} catch (error) {
@@ -332,6 +388,8 @@ export default {
this.crud.notify('打印失败:' + (error.message || '未知错误'), CRUD.NOTIFICATION_TYPE.ERROR)
}
}
}
}
</script>