HiprintPrinter打印

This commit is contained in:
wyuu 2025-08-26 17:31:31 +08:00
parent 3ddb325953
commit ac9d30434b
8 changed files with 344 additions and 146 deletions

View File

@ -30,8 +30,10 @@
"pinia": "2.1.7",
"pinyin-pro": "^3.26.0",
"select2": "^4.1.0-rc.0",
"socket.io-client": "^4.8.1",
"vue": "3.4.0",
"vue-cropper": "1.1.1",
"vue-plugin-hiprint": "^0.0.60",
"vue-router": "4.2.5",
"vue3-print-nb": "^0.1.4",
"vue3-select2-component": "^0.1.7",

View File

@ -17,24 +17,23 @@
:width="config.indexWidth || 60" :fixed="config.indexFixed" align="center" :index="getIndex" />
<!-- 动态列 -->
<template v-for="column in columns" :key="column.prop || column.key">
<template v-for="item in columns" :key="item.prop || item.key">
<!-- 自定义插槽列 -->
<el-table-column v-if="column.slot && column.visible" :prop="column.prop" :label="column.label"
:width="column.width" :min-width="column.minWidth" :fixed="column.fixed" :align="column.align || 'left'"
:sortable="column.sortable" :show-overflow-tooltip="column.showOverflowTooltip !== false">
<el-table-column v-if="item.slot && item.visible" :prop="item.prop" :label="item.label" :width="item.width"
:min-width="item.minWidth" :fixed="item.fixed" :align="item.align || 'left'" :sortable="item.sortable"
:show-overflow-tooltip="item.showOverflowTooltip !== false">
<template #default="scope">
<slot :name="column.slot" :row="scope.row" :column="scope.column" :$index="scope.$index" />
<slot :name="item.slot" :row="scope.row" :column="scope.column" :$index="scope.$index" />
</template>
</el-table-column>
<!-- 普通列 -->
<el-table-column v-if="!column.slot && column.visible" :prop="column.prop" :label="column.label"
:width="column.width" :min-width="column.minWidth" :fixed="column.fixed" :align="column.align || 'left'"
:sortable="column.sortable" :show-overflow-tooltip="column.showOverflowTooltip !== false"
:formatter="column.formatter">
<el-table-column v-if="!item.slot && item.visible" :prop="item.prop" :label="item.label" :width="item.width"
:min-width="item.minWidth" :fixed="item.fixed" :align="item.align || 'left'" :sortable="item.sortable"
:show-overflow-tooltip="item.showOverflowTooltip !== false" :formatter="item.formatter">
<!-- 表头插槽 -->
<template v-if="column.headerSlot" #header="scope">
<slot :name="column.headerSlot" :column="scope.column" :$index="scope.$index" />
<template v-if="item.headerSlot" #header="scope">
<slot :name="item.headerSlot" :column="scope.column" :$index="scope.$index" />
</template>
</el-table-column>
</template>
@ -70,7 +69,7 @@ import { computed, ref, watch, PropType } from "vue";
import { TableColumnCtx } from 'element-plus';
interface TableColumn {
prop?: string;
key?: string;
key?: string | number;
label?: string;
width?: string | number;
minWidth?: string | number;

View File

@ -0,0 +1,26 @@
// 打印状态管理
import { defineStore } from 'pinia'
export const usePrintStore = defineStore('printStore', {
state: () => ({
isConnected: false, // 存储连接状态
clientInfo: null, // 存储客户端信息
printerList: [] as any[], // 存储打印机列表
defaultPrinter: '' as string, // 存储默认打印机名称
}),
actions: {
setConnected(status: boolean) {
this.isConnected = status
},
setClientInfo(info: any) {
this.clientInfo = info
},
setPrinterList(list: any[]) {
this.printerList = list
this.defaultPrinter = list.find((p) => p.isDefault)?.name || ''
},
setDefaultPrinter(printerName: string) {
this.defaultPrinter = printerName
}
}
})

146
src/utils/print-utils.ts Normal file
View File

@ -0,0 +1,146 @@
// src/utils/print-utils.ts
import { io, Socket } from "socket.io-client";
import { ElMessage, ElMessageBox } from 'element-plus'
import { usePrintStore } from '@/store/modules/printStore'
// ... 类型定义 ...
type PrinterInfo = {
name: string;
isDefault?: boolean;
[key: string]: any;
};
type ClientInfo = any;
type OnPrinterUpdate = (printerList: PrinterInfo[]) => void;
let socket: Socket | null = null;
let clientInfo: ClientInfo = null;
// 初始化 Socket 连接
function initSocket(onPrinterUpdate?: OnPrinterUpdate) {
const printStore = usePrintStore();
if (socket) {
socket.disconnect();
}
socket = io("http://localhost:17521", {
transports: ["websocket"],
reconnection: false, //闭自动重连
auth: {
token: "vue-plugin-hiprint",
},
});
// 连接成功
socket.on("connect", () => {
printStore.setConnected(true);
console.log("✅ 已连接 electron-hiprint 客户端");
socket?.emit("getClientInfo");
socket?.emit("refreshPrinterList");
});
// 客户端信息
socket.on("clientInfo", (info: ClientInfo) => {
printStore.setClientInfo(info);
console.log("📱 客户端信息:", info);
});
// 打印机列表 (更新默认打印机)
socket.on("printerList", (list: PrinterInfo[]) => {
printStore.setPrinterList(list);
onPrinterUpdate?.(list);
console.log("🖨️ 打印机列表更新:", list);
});
// 连接断开
socket.on("disconnect", (reason: string) => {
printStore.setConnected(false);
console.error("❌ 客户端连接断开:", reason);
if (reason === "io server disconnect") {
socket?.connect();
}
});
socket.on("connect_error", (err: Error) => {
console.log('连接错误', err);
ElMessageBox.alert(
`连接失败!<br>请确保目标服务器已<a style="color: #1f79db" href="https://gitee.com/CcSimple/electron-hiprint/releases" target="_blank"> 下载 </a> 并运行 打印服务!`,
"客户端未连接",
{
dangerouslyUseHTMLString: true,
}
)
printStore.setConnected(false);
socket?.close();
});
}
function silentPrintPdf(url: string, printOptions?: object) {
const printStore = usePrintStore();
if (!url) {
console.log("请传入有效的PDF文件路径!");
return;
}
const templateId = `pdf-print-${Date.now()}`;
// 检查是否连接客户端
if (!printStore.isConnected) {
initSocket((printerList) => {
if (printerList.length > 0) {
socket?.emit("news", {
client: clientInfo,
printer: targetPrinter,
type: "url_pdf",
templateId: templateId,
pdf_path: url,
});
ElMessage.success('正在打印文件,请稍候...')
}
return
});
}
const targetPrinter = printStore.defaultPrinter;
if (!targetPrinter) {
console.log("未检测到打印机,请先连接打印机!");
return;
}
socket?.emit("news", {
client: clientInfo,
printer: targetPrinter,
type: "url_pdf",
templateId: templateId,
pdf_path: url,
});
ElMessage.success('正在打印文件,请稍候...')
// console.log(`🚀 PDF打印请求已发送,任务ID:${templateId},打印机:${targetPrinter}`);
}
// 销毁
function destroy() {
const printStore = usePrintStore();
if (socket) {
socket.disconnect();
socket = null;
printStore.setConnected(false);
console.log("🔌 已断开与 electron-hiprint 的连接");
}
}
// 更新 getter 函数以从 store 获取值
export const HiprintPrinter = {
initSocket,
silentPrintPdf,
destroy,
get isConnected() {
const printStore = usePrintStore();
return printStore.isConnected;
},
get clientInfo() {
const printStore = usePrintStore();
return printStore.clientInfo;
},
get printerList() {
const printStore = usePrintStore();
return printStore.printerList;
},
get defaultPrinter() {
const printStore = usePrintStore();
return printStore.defaultPrinter;
},
};

View File

@ -44,13 +44,13 @@
<div class="title"> 条码生成 </div>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus">生成条码</el-button>
<el-button type="primary" plain icon="Plus" @click="printHandle">生成条码</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Edit">选中所有未打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain>打印</el-button>
<el-button type="success" plain @click="printHandle1">打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="openBlock">采样登记</el-button>
@ -143,11 +143,15 @@
</template>
<script setup lang="ts">
import { ref, reactive, toRaw, computed, watch, nextTick, onMounted } from 'vue';
import { ref, reactive, toRaw, computed, watch, nextTick, onMounted, onUnmounted } from 'vue';
import { HiprintPrinter } from '@/utils/print-utils';
import CustomTable from '@/components/tableCom/index.vue'
import { useRouter } from 'vue-router'
import { usePrintStore } from '@/store/modules/printStore' // 引入 Pinia store
// import { download } from '@/api/liswork/dict/LabInstr.js'
import { ElMessage, ElMessageBox } from 'element-plus'
const router = useRouter()
const printStore = usePrintStore();
const compactForm = ref<HTMLElement | null>(null);
const heightForm = ref(90);
const dialogVisible = ref(false)
@ -354,6 +358,40 @@ function adjustTableHeight() {
rightTableConfig.value.height = `calc(75vh - ${heightForm.value}px)`; // 设置表格容器的高度
}
}
const printHandle = () => {
const url = 'http://47.97.125.165:8904/lis.pdf'
HiprintPrinter.silentPrintPdf(
url,
{
// 自定义打印参数(可选)
orientation: "portrait", // 纵向(landscape=横向)
copies: 1,
monochrome: false, // 是否黑白打印
},
);
}
const printHandle1 = () => {
const url = 'http://47.97.125.165:8904/lis.pdf'
HiprintPrinter.silentPrintPdf(
url,
{
// 自定义打印参数(可选)
orientation: "portrait", // 纵向(landscape=横向)
copies: 1,
monochrome: false, // 是否黑白打印
},
);
}
</script>
<style scoped lang="scss">

View File

@ -7,12 +7,7 @@
<el-col :span="6">
<div> 就诊卡号/病历号/磁卡号: </div>
<el-input
v-model="queryParams.brdh"
placeholder="就诊卡号/病历号/磁卡号"
clearable
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.brdh" placeholder="就诊卡号/病历号/磁卡号" clearable @keyup.enter="handleQuery" />
</el-col>
<el-col :span="2">
<el-radio-group v-model="queryParams.zt" size="default">
@ -20,7 +15,9 @@
:disabled="item.disabled">{{ item.label }}</el-radio>
</el-radio-group>
</el-col>
<el-col :span="1"> <div> 申请获取期限: </div></el-col>
<el-col :span="1">
<div> 申请获取期限: </div>
</el-col>
<el-col :span="6">
<el-radio-group v-model="queryParams.subday" size="default">
@ -30,8 +27,8 @@
</el-col>
<el-col :span="2">
<el-select v-model="queryParams.cardtype" placeholder="卡类型" clearable :style="{ width: '100%' }">
<el-option v-for="(item, index) in cardtypeOptions" :key="index" :label="item.label"
:value="item.value" :disabled="item.disabled"></el-option>
<el-option v-for="(item, index) in cardtypeOptions" :key="index" :label="item.label" :value="item.value"
:disabled="item.disabled"></el-option>
</el-select>
</el-col>
<el-col :span="1.5">
@ -47,70 +44,36 @@
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Search"
@click="handleQuery"
v-hasPermi="['system:reqmain:Search']"
>查询</el-button>
<el-button type="primary" plain icon="Search" @click="handleQuery"
v-hasPermi="['system:reqmain:Search']">查询</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Printer"
:disabled="single"
@click="printAll"
v-hasPermi="['system:reqmain:add']"
>打印</el-button>
<el-button type="success" plain icon="Printer" :disabled="single" @click="printAll"
v-hasPermi="['system:reqmain:add']">打印</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Printer"
:disabled="freesingle"
@click="printSigne"
v-hasPermi="['system:reqmain:edit']"
>单打条码</el-button>
<el-button type="success" plain icon="Printer" :disabled="freesingle" @click="printSigne"
v-hasPermi="['system:reqmain:edit']">单打条码</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Printer"
:disabled="freesingle"
@click="printBackpaper"
v-hasPermi="['system:reqmain:edit']"
>单打回单</el-button>
<el-button type="danger" plain icon="Printer" :disabled="freesingle" @click="printBackpaper"
v-hasPermi="['system:reqmain:edit']">单打回单</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Delete"
@click="cancel"
v-hasPermi="['system:reqmain:export']"
>取消采样</el-button>
<el-button type="warning" plain icon="Delete" @click="cancel"
v-hasPermi="['system:reqmain:export']">取消采样</el-button>
</el-col>
<el-col :span="3">
<barcode-printer
:printData="printBarcodeData"
:disabled="!hasSelectedRows"
type="success"
icon="Printer"
@print-start="showLoading"
@print-success="handlePrintSuccess"
@print-error="handlePrintError"
>
<barcode-printer :printData="printBarcodeData" :disabled="!hasSelectedRows" type="success" icon="Printer"
@print-start="showLoading" @print-success="handlePrintSuccess" @print-error="handlePrintError">
打印条码
</barcode-printer>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table ref="tableRef" class="compact-form my-table" :data="reqmainList" @selection-change="handleSelectionChange" @select="handleSelect" height="530px" :cell-style="tableCellStyle">
<el-table ref="tableRef" class="compact-form my-table" :data="reqmainList" @selection-change="handleSelectionChange"
@select="handleSelect" height="530px" :cell-style="tableCellStyle">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目名称" align="center" prop="sqxmmc" width="200" show-overflow-tooltip />
<el-table-column label="项目代码" align="center" prop="sqxmdh" width="100" show-overflow-tooltip />
@ -179,7 +142,7 @@
<script setup>
import { ElMessage } from 'element-plus'; // 引入 Element Plus 消息组件
import { querySQD, getReqmain, delReqmain, addReqmain, updateReqmain, printBarcode } from "@/api/mzcx/cydj.js";
import BarcodePrinter from './components/BarcodePrinter.vue';
import BarcodePrinter from './BarcodePrinter.vue';
const comDict = inject('comDict');
// 字典数据存储
const dictData = ref({});
@ -562,22 +525,32 @@ const handlePrintError = (errorMsg) => {
.compact-form .el-form-item__label {
padding-bottom: 0px;
}
::v-deep .my-table .el-table__row td {
padding: 1px 0; /* 减小行高 */
padding: 1px 0;
/* 减小行高 */
}
::v-deep .my-table .el-table__header-wrapper th {
padding: 6px 0; /* 表头内边距 */
background-color: #b3d8ff !important; /* 表头背景色(保留之前的设置) */
color: #333; /* 文字颜色加深,提升可读性 */
font-weight: 500; /* 文字加粗 */
padding: 6px 0;
/* 表头内边距 */
background-color: #b3d8ff !important;
/* 表头背景色(保留之前的设置) */
color: #333;
/* 文字颜色加深,提升可读性 */
font-weight: 500;
/* 文字加粗 */
}
::v-deep .my-table .el-table__cell {
padding: 0 2px; /* 减小列间距 */
padding: 0 2px;
/* 减小列间距 */
}
/* 可选:调整表格整体样式 */
::v-deep .my-table {
font-size: 13px; /* 适当减小字体 */
font-size: 13px;
/* 适当减小字体 */
}
::v-deep .my-table .el-table__cell,
@ -585,17 +558,25 @@ const handlePrintError = (errorMsg) => {
border-width: 1px;
border-color: #ebeef5;
}
.card-content {
max-height: 450px; /* 设置最大高度 */
overflow-y: auto; /* 超出高度时显示垂直滚动条 */
padding: 5px; /* 保持与卡片默认一致的内边距 */
max-height: 450px;
/* 设置最大高度 */
overflow-y: auto;
/* 超出高度时显示垂直滚动条 */
padding: 5px;
/* 保持与卡片默认一致的内边距 */
}
.clickable:hover {
cursor: pointer; /* 鼠标悬停时显示手型 */
transition: all 0.3s; /* 平滑过渡效果 */
cursor: pointer;
/* 鼠标悬停时显示手型 */
transition: all 0.3s;
/* 平滑过渡效果 */
color: blue;
}
/* 汇总行样式 */
.table-summary {
display: flex;
@ -607,7 +588,9 @@ const handlePrintError = (errorMsg) => {
border: 1px solid #ebeef5;
border-radius: 4px;
font-weight: 500;
line-height: 24px; /* 汇总行文字行高 */
line-height: 24px;
/* 汇总行文字行高 */
.summary-item {
display: flex;
align-items: center;
@ -621,12 +604,16 @@ const handlePrintError = (errorMsg) => {
.summary-value {
color: #303133;
min-width: 40px;
text-align: left;/* 核心:数值左对齐 */
text-align: left;
/* 核心:数值左对齐 */
}
/* 单独设置"单价合计"的值为红色 */
&:nth-child(3) .summary-value {
color: #f56c6c; /* Element UI 红色主题色 */
font-weight: 600; /* 可选:加粗字体 */
color: #f56c6c;
/* Element UI 红色主题色 */
font-weight: 600;
/* 可选:加粗字体 */
}
}
}

View File

@ -13,7 +13,7 @@
"moduleResolution": "node" /* 指定模块解析策略:'node'(Node.js)或'classic'(TypeScript 1.6之前版本)。*/,
"baseUrl": "." /* 用于解析非绝对模块名称的基准目录。 */,
"paths": {
"/@/*": ["src/*"]
"@/*": ["src/*"]
} /* 一系列条目,这些条目将导入重新映射到相对于“baseUrl”的查找位置。*/,
"types": ["vite/client"] /* 要包含在编译中的类型声明文件。 */,
"allowSyntheticDefaultImports": true /*允许从没有默认导出的模块进行默认导入。这不会影响代码生成,只会影响类型检查。*/,
@ -32,6 +32,6 @@
// "allowUnknownInTemplate": true // 允许模板中使用未知属性
// }
},
"include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "auto-imports.d.ts"], // **表示任意目录,而 * 表示任意文件。这表明 src 目录中的所有文件都将被编译
"include": ["src/**/*","src/**/*.ts", "src/**/*.vue", "src/**/*.tsx", "src/**/*.d.ts", "auto-imports.d.ts"], // **表示任意目录,而 * 表示任意文件。这表明 src 目录中的所有文件都将被编译
"exclude": ["node_modules", "dist"] ,// 指示不需要编译的文件目录
}

View File

@ -26,14 +26,14 @@ export default defineConfig(({
// 设置路径
'~': path.resolve(__dirname, './'),
// 设置别名
'@': path.resolve(__dirname, './src')
'@': path.resolve(__dirname, 'src')
},
// https://cn.vitejs.dev/config/#resolve-extensions
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
},
// vite 相关配置
server: {
port: 5173,
port: 8888,
host: true,
open: true,
proxy: {