You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_web/src/views/mes/printconfig/index.vue

589 lines
18 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<ContentWrap>
<div class="print-config-header">
<div class="print-config-title-row">
<div class="print-config-title">{{ t('TemplateManagement.PrintConfig.moduleName') }}</div>
<el-tag
class="print-config-host-tag"
:type="currentHostName ? 'primary' : 'info'"
effect="plain"
>
<span class="print-config-host-label">
{{ t('TemplateManagement.PrintConfig.currentHostName') }}
</span>
<span>{{ currentHostName || t('TemplateManagement.PrintConfig.unknownHostName') }}</span>
</el-tag>
</div>
<div class="print-config-tip">{{ t('TemplateManagement.PrintConfig.currentHostTip') }}</div>
</div>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="120px"
>
<el-form-item :label="t('TemplateManagement.PrintConfig.hostName')" prop="hostName">
<el-input
v-model="queryParams.hostName"
:placeholder="t('TemplateManagement.PrintConfig.placeholderHostName')"
:clearable="!currentHostName"
:disabled="!!currentHostName"
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('TemplateManagement.PrintConfig.systemPrinterName')" prop="systemPrinterName">
<el-input
v-model="queryParams.systemPrinterName"
:placeholder="t('TemplateManagement.PrintConfig.placeholderSystemPrinterName')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['printer:config:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('action.add') }}
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['printer:config:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<!-- <el-table-column label="主键ID" align="center" prop="id" />-->
<!-- <el-table-column type="selection" width="55" fixed="left" reserve-selection />-->
<el-table-column :label="t('TemplateManagement.PrintConfig.hostName')" align="center" prop="hostName" />
<el-table-column :label="t('TemplateManagement.PrintConfig.systemPrinterName')" align="center" prop="systemPrinterName" min-width="180px"/>
<el-table-column :label="t('TemplateManagement.PrintConfig.defaultStatus')" align="center" prop="isDefault" width="90px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IS_DEFAULT" :value="scope.row.isDefault" />
</template>
</el-table-column>
<el-table-column :label="t('TemplateManagement.PrintConfig.isEnabled')" align="center" prop="isEnabled" width="90px">
<template #default="scope">
<dict-tag :type="DICT_TYPE.IS_ENABLED" :value="scope.row.isEnabled" />
</template>
</el-table-column>
<el-table-column :label="t('TemplateManagement.PrintConfig.businessScenarioCode')" align="center" prop="businessScenarioCode" min-width="180px"/>
<el-table-column :label="t('TemplateManagement.PrintConfig.businessScenario')" align="center" prop="businessScenario" min-width="180px"/>
<el-table-column :label="t('TemplateManagement.PrintConfig.scenarioCount')" align="center" prop="scenarioCount" width="70px" />
<el-table-column :label="t('TemplateManagement.PrintConfig.remark')" align="center" prop="remark" />
<el-table-column
:label="t('TemplateManagement.PrintConfig.createTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column
:label="t('TemplateManagement.PrintConfig.updateTime')"
align="center"
prop="updateTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column :label="t('TemplateManagement.PrintConfig.operate')" align="center" min-width="120px">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['printer:config:update']"
>
{{ t('action.edit') }}
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['printer:config:delete']"
>
{{ t('action.del') }}
</el-button>
<el-button
link
type="success"
@click="handleTestPrint(scope.row)"
v-hasPermi="['printer:config:update']"
>
<Icon icon="ep:printer" class="mr-2px" />
{{ t('TemplateManagement.PrintConfig.testPrint') }}
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<ConfigForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { autoConnect, defaultElementTypeProvider, hiprint, hiwebSocket } from 'vue-plugin-hiprint'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ConfigApi, ConfigVO } from '@/api/mes/printconfig'
import ConfigForm from './ConfigForm.vue'
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
/** 打印机配置 列表 */
defineOptions({ name: 'PrinterConfig' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<ConfigVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
interface QueryParams {
pageNo: number
pageSize: number
hostName?: string
systemPrinterName?: string
isDefault?: boolean
isEnabled?: boolean
remark?: string
}
const currentHostName = ref('')
const queryParams = reactive<QueryParams>({
pageNo: 1,
pageSize: 10,
hostName: undefined,
systemPrinterName: undefined,
isDefault: undefined,
isEnabled: undefined,
remark: undefined,
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
const clientHostStorageKey = 'hiprint-client-host'
const hostNameStorageKey = 'hiprint-connected-hostname'
const defaultClientHost = 'http://127.0.0.1:17521'
let hiprintInited = false
const normalizeClientHost = () => {
const host = (localStorage.getItem(clientHostStorageKey) || defaultClientHost).trim()
if (!host) {
return defaultClientHost
}
if (/^https?:\/\//i.test(host)) {
return host
}
return `http://${host}`
}
const getHiwebSocket = () => {
return ((hiwebSocket as any) || (window as any).hiwebSocket) as any
}
const ensureHiprintInit = () => {
if (hiprintInited) {
return
}
// Edge <20><><EFBFBD>ݣ<EFBFBD>Ϊ hiwebSocket <20><><EFBFBD>?polling <20><><EFBFBD><EFBFBD><EFBFBD><E3BDB5>? // ԭ<><D4AD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?transports:["websocket"]<5D><>Edge <20><>ȫ<EFBFBD><C8AB><EFBFBD><EFBFBD><EFBFBD><EFBFBD> WebSocket <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>
const socket = getHiwebSocket()
if (socket?.start && !socket.__transportPatched) {
const originalStart = socket.start.bind(socket)
socket.start = function (callback?: any) {
const originalIo = window.io
window.io = function (url: string, opts?: any) {
opts = opts || {}
opts.transports = ['websocket', 'polling']
return originalIo(url, opts)
}
originalStart(callback)
window.io = originalIo
}
socket.__transportPatched = true
}
hiprint.init({
host: normalizeClientHost(),
providers: [defaultElementTypeProvider()]
})
hiprintInited = true
}
const applyClientHost = () => {
const socket = getHiwebSocket()
if (!socket) {
return
}
const host = normalizeClientHost()
if (socket.host !== host) {
socket.stop?.()
socket.host = host
}
}
const sanitizeHostName = (hostName?: unknown) => {
if (typeof hostName !== 'string') {
return ''
}
const value = hostName.trim()
if (!value || /^https?:\/\//i.test(value) || /[/:\\]/.test(value)) {
return ''
}
return value
}
const extractHostName = (source?: any) => {
if (!source || typeof source !== 'object') {
return ''
}
const fields = [
'hostName',
'hostname',
'computerName',
'machineName',
'pcName',
'clientName',
'name'
]
for (const field of fields) {
const hostName = sanitizeHostName(source[field])
if (hostName) {
return hostName
}
}
return ''
}
const getActiveXComputerName = () => {
try {
const ActiveXObjectCtor = (window as any).ActiveXObject
if (!ActiveXObjectCtor) {
return ''
}
const network = new ActiveXObjectCtor('WScript.Network')
return sanitizeHostName(network?.ComputerName)
} catch {
return ''
}
}
const getSocketHostName = () => {
const socket = getHiwebSocket()
// <20><><EFBFBD>ȼ<EFBFBD><C8BC> socket.clientInfo<66><6F><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0><EFBFBD>ӿͻ<D3BF><CDBB><EFBFBD><EFBFBD><EFBFBD>Ϣ<EFBFBD><CFA2>
const hostNameFromInfo = extractHostName(socket?.clientInfo)
if (hostNameFromInfo) {
return hostNameFromInfo
}
const clients = socket?.clients
const clientList = Array.isArray(clients) ? clients : Object.values(clients || {})
for (const client of clientList) {
const hostName =
extractHostName(client) ||
extractHostName(client?.client) ||
extractHostName(client?.clientInfo) ||
extractHostName(client?.info)
if (hostName) {
return hostName
}
}
const printerList = Array.isArray(socket?.printerList) ? socket.printerList : []
for (const printer of printerList) {
const hostName =
extractHostName(printer?.server) ||
extractHostName(printer?.client) ||
extractHostName(printer?.clientInfo) ||
extractHostName(clients?.[printer?.clientId]) ||
extractHostName(clients?.[printer?.server?.clientId])
if (hostName) {
return hostName
}
}
return ''
}
const resolveCurrentHostName = async () => {
const activeXHostName = getActiveXComputerName()
if (activeXHostName) {
return activeXHostName
}
try {
ensureHiprintInit()
applyClientHost()
const socketHostName = getSocketHostName()
if (socketHostName) {
return socketHostName
}
return await new Promise<string>((resolve) => {
let settled = false
const finish = (hostName?: string) => {
if (settled) {
return
}
settled = true
window.clearTimeout(timer)
// 缓存主机名到 localStorage下次刷新立即显示
const result = hostName || getSocketHostName()
if (result) {
localStorage.setItem(hostNameStorageKey, result)
}
resolve(result)
}
const timer = window.setTimeout(() => finish(), 3000)
const socket = getHiwebSocket()
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> socket.io ʵ<><CAB5><EFBFBD><EFBFBD>ֱ<EFBFBD>Ӽ<EFBFBD><D3BC><EFBFBD> clients/clientInfo <20>¼<EFBFBD>
if (socket?.socket?.on) {
const onClients = () => {
const hn = getSocketHostName()
if (hn) finish(hn)
}
const onClientInfo = () => {
const hn = extractHostName(socket.clientInfo) || getSocketHostName()
if (hn) finish(hn)
}
socket.socket.on('clients', onClients)
socket.socket.on('clientInfo', onClientInfo)
}
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (socket) {
if (typeof socket.getClients === 'function') socket.getClients()
if (typeof socket.getClientInfo === 'function') socket.getClientInfo()
if (typeof socket.refreshPrinterList === 'function') socket.refreshPrinterList()
}
if (typeof hiprint.getClientInfo === 'function') {
hiprint.getClientInfo((clientInfo: any) => {
const hn = extractHostName(clientInfo)
if (hn) finish(hn)
})
}
// autoConnect <20><><EFBFBD><EFBFBD> socket<65><74><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD><D8B5><EFBFBD> false<73><65><EFBFBD>ȴ<EFBFBD><C8B4><EFBFBD><EFBFBD>ӳɹ<D3B3><C9B9><EFBFBD><EFBFBD> true<75><65>
autoConnect((status: boolean) => {
if (!status) {
return
}
getHiwebSocket()?.refreshPrinterList?.()
window.setTimeout(() => finish(getSocketHostName()), 300)
})
})
} catch {
return ''
}
}
const initCurrentHostName = async () => {
// 先从缓存读取,立即显示
const cachedHostName = localStorage.getItem(hostNameStorageKey)
if (cachedHostName) {
currentHostName.value = cachedHostName
queryParams.hostName = cachedHostName
}
// 再通过 WebSocket 刷新获取最新主机名
const hostName = await resolveCurrentHostName()
if (hostName && hostName !== cachedHostName) {
currentHostName.value = hostName
queryParams.hostName = hostName
}
}
const getList = async () => {
loading.value = true
try {
const data = await ConfigApi.getConfigPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
if (currentHostName.value) {
queryParams.hostName = currentHostName.value
}
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.hostName = currentHostName.value || undefined
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
let host = currentHostName.value;
formRef.value.open(type,host, id)
}
/** 测试打印按钮操作 */
const handleTestPrint = async (row: any) => {
const printerName = row.systemPrinterName
if (!printerName) {
message.warning('该配置未选择打印机,无法测试打印')
return
}
ensureHiprintInit()
const testData = {
orderNo: 'TEST-' + Date.now(),
customerName: '测试客户',
amount: 199.99,
address: '北京市朝阳区测试街道123号'
}
const printElements = [
{ options: { left: 10, top: 10, height: 10, width: 200, title: '订单号', field: 'orderNo', testData: testData.orderNo, fontSize: 14, textAlign: 'center' }, printElementType: { title: '文本', type: 'text' } },
{ options: { left: 10, top: 28, height: 10, width: 200, title: '客户名', field: 'customerName', testData: testData.customerName, fontSize: 14, textAlign: 'center' }, printElementType: { title: '文本', type: 'text' } },
{ options: { left: 10, top: 46, height: 14, width: 200, title: '金额', field: 'amount', testData: '199.99', fontSize: 18, textAlign: 'center', fontWeight: 'bold' }, printElementType: { title: '文本', type: 'text' } },
{ options: { left: 10, top: 68, height: 10, width: 200, title: '地址', field: 'address', testData: testData.address, fontSize: 12, textAlign: 'center' }, printElementType: { title: '文本', type: 'text' } }
]
const templateJson = { panels: [{ index: 0, height: 100, width: 220, paperHeader: 5, paperFooter: 5, printElements }] }
applyClientHost()
const socket = getHiwebSocket()
if (!socket) {
message.warning('打印客户端未初始化')
return
}
if (socket.opened) {
doTestPrint(socket, printerName, testData, templateJson)
return
}
socket.stop()
socket.start((status: boolean) => {
if (!status) {
message.warning('未连接到本地打印客户端,请先确认 electron-hiprint 已启动')
return
}
doTestPrint(socket, printerName, testData, templateJson)
})
}
/** 删除按钮操作 */
const doTestPrint = (socket: any, printerName: string, testData: any, templateJson: any) => {
const printTemplate = new hiprint.PrintTemplate({ template: templateJson })
printTemplate.on('printSuccess', () => {
message.success('测试打印已发送到: ' + printerName)
})
printTemplate.on('printError', (data: any) => {
message.error(data?.msg || data?.message || '测试打印失败')
})
try {
printTemplate.print2(testData, { printer: printerName, title: '测试打印' })
} catch (error: any) {
message.error(error?.message || '测试打印失败')
}
}
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ConfigApi.deleteConfig(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await ConfigApi.exportConfig(queryParams)
download.excel(data, '打印机配置.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
await initCurrentHostName()
getList()
})
</script>
<style scoped lang="scss">
.print-config-header {
margin-bottom: 22px;
}
.print-config-title-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.print-config-title {
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1.25;
}
.print-config-host-tag {
height: 30px;
padding: 0 10px;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
}
.print-config-host-label {
font-weight: 700;
}
.print-config-tip {
margin-top: 8px;
color: var(--el-text-color-secondary);
font-size: 14px;
line-height: 1.5;
}
</style>