打印
This commit is contained in:
parent
23b6d65f0c
commit
2f325c122c
@ -410,6 +410,7 @@ aside {
|
||||
line-height: 30px;
|
||||
padding: 0 !important;
|
||||
background-image: none !important;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.vxe-footer--column {
|
||||
@ -430,8 +431,6 @@ aside {
|
||||
border-top: 1px solid #ccc !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.vxe-header--row th {
|
||||
height: 30px !important;
|
||||
line-height: 30px;
|
||||
|
||||
@ -188,7 +188,7 @@
|
||||
|
||||
.el-table__body td {
|
||||
font-size: .8125rem;
|
||||
|
||||
color: #000;
|
||||
}
|
||||
|
||||
|
||||
@ -376,6 +376,7 @@
|
||||
.col--ellipsis {
|
||||
height: 1.875rem !important;
|
||||
line-height: 1.875rem !important;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
@ -398,17 +399,17 @@
|
||||
}
|
||||
|
||||
// 滚动条
|
||||
.mytable-style ::-webkit-scrollbar {
|
||||
.vxe-table ::-webkit-scrollbar {
|
||||
width: .6rem;
|
||||
height: .5rem;
|
||||
}
|
||||
|
||||
.mytable-style ::-webkit-scrollbar-thumb {
|
||||
.vxe-table ::-webkit-scrollbar-thumb {
|
||||
background-color: #817e7e;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mytable-style ::-webkit-scrollbar-thumb:hover {
|
||||
.vxe-table ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #817e7e;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
109
src/components/PrintReportMask/index.vue
Normal file
109
src/components/PrintReportMask/index.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div v-if="visible" class="print-mask">
|
||||
<div class="mask-card">
|
||||
<div class="card-header">
|
||||
<div class="title">打印报告进度</div>
|
||||
<el-button icon="Close" circle size="small" v-if="progress == 100" @click="closeMask" />
|
||||
</div>
|
||||
<div class="tip-text">{{ ybh }} {{ desc }}</div>
|
||||
<el-progress :percentage="progress" :stroke-width="15" striped />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { classCom } from '@/utils/classCom';
|
||||
const props = defineProps<{
|
||||
// websocket原始data字符串
|
||||
wsData?: string
|
||||
}>()
|
||||
const visible = ref(false)
|
||||
const ybh = ref('')
|
||||
const desc = ref('')
|
||||
const progress = ref(0)
|
||||
const succCount = ref(0)
|
||||
const failCount = ref(0)
|
||||
const errorMsg = ref('')
|
||||
// 解析ws数据
|
||||
const parseWsData = (dataStr: string) => {
|
||||
try {
|
||||
const data = JSON.parse(dataStr)
|
||||
ybh.value = data.ybh
|
||||
desc.value = data.desc
|
||||
progress.value = data.progress
|
||||
succCount.value = data.succCount
|
||||
failCount.value = data.failCount
|
||||
errorMsg.value = data.errorMsg
|
||||
visible.value = true
|
||||
// 100%完成后打印PDF
|
||||
if (data.progress == 100) {
|
||||
classCom.printBase64PDF(data.pdfBase64)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('打印ws数据解析失败', err)
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => props.wsData,
|
||||
(val) => {
|
||||
if (val) parseWsData(val)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
// 对外暴露手动关闭方法
|
||||
const closeMask = () => {
|
||||
visible.value = false
|
||||
}
|
||||
defineExpose({ closeMask })
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.print-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
|
||||
.mask-card {
|
||||
background: #fff;
|
||||
width: 460px;
|
||||
padding: 24px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||
|
||||
// 标题行布局
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16;
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
color: #4e5969;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.7;
|
||||
font-size: 14px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.finish-text {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
color: #00b42a;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -40,7 +40,7 @@
|
||||
:row-config="{ isHover: true, keyField: 'value', height: 30, isCurrent: true }" :data="filterData"
|
||||
:current-row="currentRow" @cell-click="currentChange"
|
||||
:tooltip-config="{ appendToBody: true, zIndex: 3000 }" :show-overflow="true"
|
||||
:scroll-y="{ enabled: true, rSize: 30, height: '100%' }">
|
||||
:scroll-y="{ enabled: true, rSize: 30, height: '100%', oSize: 20, gt: 30, }">
|
||||
<vxe-table-column v-for="field in props.fields" :field="field.prop" :title="field.label" align="center"
|
||||
:width="field.width" min-width="150" />
|
||||
</vxe-table>
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<navbar @setLayout="setLayout" />
|
||||
<navbar @setLayout="setLayout" />
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main />
|
||||
@ -21,7 +21,10 @@ import defaultSettings from '@/settings'
|
||||
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import { initWebSocket } from '@/utils/webSocket'
|
||||
import { useCommonStore } from '@/store/modules/commonStore'
|
||||
|
||||
const mbStore = useCommonStore();
|
||||
const settingsStore = useSettingsStore()
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
@ -60,11 +63,17 @@ const settingRef = ref(null);
|
||||
function setLayout() {
|
||||
settingRef.value.openSetting();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化socket
|
||||
const url = `ws://47.97.125.165:8904/ws/msgserver/${mbStore.fingerprint}`
|
||||
initWebSocket({ fullUrl: url })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/styles/mixin.scss";
|
||||
@import "@/assets/styles/variables.module.scss";
|
||||
@import "@/assets/styles/mixin.scss";
|
||||
@import "@/assets/styles/variables.module.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
|
||||
@ -10,6 +10,9 @@ import {
|
||||
removeToken
|
||||
} from '@/utils/auth'
|
||||
import defAva from '@/assets/images/profile.jpg'
|
||||
import {
|
||||
closeWebSocket
|
||||
} from '@/utils/webSocket'
|
||||
|
||||
const useUserStore = defineStore(
|
||||
'user', {
|
||||
@ -96,6 +99,7 @@ const useUserStore = defineStore(
|
||||
this.roles = []
|
||||
this.permissions = []
|
||||
removeToken()
|
||||
closeWebSocket()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
// WebSocket连接配置
|
||||
type WebSocketOptions = {
|
||||
import emitter from '@/utils/mitt';
|
||||
|
||||
type WebSocketInternalOptions = {
|
||||
fullUrl: string;
|
||||
onMessage?: (data: string) => void;
|
||||
onOpen?: () => void;
|
||||
onError?: (error: Event) => void;
|
||||
onClose?: () => void;
|
||||
reconnectInterval?: number;
|
||||
};
|
||||
|
||||
@ -12,74 +9,71 @@ let websocket: WebSocket | null = null;
|
||||
let reconnectTimer: any = null;
|
||||
let isManualClose = false;
|
||||
|
||||
// 初始化WebSocket连接
|
||||
export function initWebSocket(options: WebSocketOptions) {
|
||||
const {
|
||||
fullUrl,
|
||||
onMessage,
|
||||
onOpen,
|
||||
onError,
|
||||
onClose,
|
||||
reconnectInterval = 3000
|
||||
} = options;
|
||||
/** 初始化WebSocket */
|
||||
export function initWebSocket(options: WebSocketInternalOptions) {
|
||||
const { fullUrl, reconnectInterval = 3000 } = options;
|
||||
|
||||
// 关闭旧连接
|
||||
// 关闭已有连接
|
||||
if (websocket) {
|
||||
websocket.close();
|
||||
}
|
||||
|
||||
// 检查浏览器支持性
|
||||
if (!('WebSocket' in window)) {
|
||||
console.error('当前浏览器不支持WebSocket');
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建WebSocket连接
|
||||
try {
|
||||
websocket = new WebSocket(fullUrl);
|
||||
|
||||
// 连接成功回调
|
||||
// 连接成功
|
||||
websocket.onopen = () => {
|
||||
clearTimeout(reconnectTimer!);
|
||||
isManualClose = false;
|
||||
onOpen?.();
|
||||
console.log('WebSocket 连接成功');
|
||||
};
|
||||
|
||||
// 接收消息回调
|
||||
// 接收消息,自动解析按 msgType 分发事件
|
||||
websocket.onmessage = (event) => {
|
||||
onMessage?.(event.data);
|
||||
try {
|
||||
const rawMsg = JSON.parse(event.data);
|
||||
const msgType = rawMsg.msgType;
|
||||
emitter.emit(msgType, rawMsg);
|
||||
} catch (err) {
|
||||
console.error('WebSocket消息解析失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 错误回调
|
||||
// 连接异常
|
||||
websocket.onerror = (error) => {
|
||||
onError?.(error);
|
||||
console.error('WebSocket 连接异常:', error);
|
||||
startReconnect(() => initWebSocket(options), reconnectInterval);
|
||||
};
|
||||
|
||||
// 关闭回调
|
||||
// 连接关闭
|
||||
websocket.onclose = () => {
|
||||
onClose?.();
|
||||
console.log('WebSocket 连接关闭');
|
||||
if (!isManualClose) {
|
||||
startReconnect(() => initWebSocket(options), reconnectInterval);
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('WebSocket连接创建失败:', error);
|
||||
console.error('WebSocket 创建失败:', error);
|
||||
startReconnect(() => initWebSocket(options), reconnectInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
/** 发送ws消息 */
|
||||
export function sendWebSocketMessage(message: any): boolean {
|
||||
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
||||
websocket.send(JSON.stringify(message));
|
||||
return true;
|
||||
}
|
||||
console.error('WebSocket未连接,无法发送消息');
|
||||
console.error('WebSocket未连接,发送失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 手动关闭WebSocket
|
||||
/** 手动关闭ws(登出时调用) */
|
||||
export function closeWebSocket(): void {
|
||||
isManualClose = true;
|
||||
if (reconnectTimer) {
|
||||
@ -89,15 +83,15 @@ export function closeWebSocket(): void {
|
||||
websocket = null;
|
||||
}
|
||||
|
||||
// 启动重连
|
||||
/** 获取当前连接状态 */
|
||||
export function getWebSocketState(): number | null {
|
||||
return websocket?.readyState || null;
|
||||
}
|
||||
|
||||
// 内部重连私有方法
|
||||
function startReconnect(connectCallback: () => void, interval: number): void {
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer);
|
||||
}
|
||||
reconnectTimer = setTimeout(connectCallback, interval);
|
||||
}
|
||||
|
||||
// 获取当前连接状态
|
||||
export function getWebSocketState(): number | null {
|
||||
return websocket?.readyState || null;
|
||||
}
|
||||
@ -131,7 +131,6 @@ import { listitemclass } from "@/api/regional/dict/itemclass";
|
||||
import { classCom } from '@/utils/classCom';
|
||||
import { useGroupTableData } from '@/utils/groupHelper'
|
||||
const { proxy } = getCurrentInstance();
|
||||
// import { initWebSocket, sendWebSocketMessage, closeWebSocket, getWebSocketState } from '@/utils/webSocket';
|
||||
const aotuSize = classCom.useAutoSize();
|
||||
|
||||
const labpat = ref({
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
</el-row>
|
||||
|
||||
<div class="bottom-card mt10">
|
||||
<el-table :data="tableData" border style="width: 100%" height="100%" v-loading="loading">
|
||||
<el-table :data="tableData" border style="width: 100%" height="100%" v-loading="loading" show-overflow-tooltip>
|
||||
<el-table-column label="样本号" prop="ybh" width="120" align="center" />
|
||||
<el-table-column label="病人姓名" prop="brxm" width="120" align="center" />
|
||||
<el-table-column label="病人类型" prop="brly" width="120" align="center">
|
||||
@ -92,6 +92,9 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 打印进度遮罩 -->
|
||||
<PrintReportMask ref="printMaskRef" :ws-data="printWsData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -103,6 +106,8 @@ import { queryBatchPrintService, batchPrintAll } from '@/api/liswork/batch/index
|
||||
import { getDictData, formatDict, dictData } from '@/hooks'
|
||||
import { useCommonStore } from '@/store/modules/commonStore';
|
||||
import { ElMessage, ElNotification } from 'element-plus';
|
||||
import PrintReportMask from '@/components/PrintReportMask/index.vue'
|
||||
import emitter from '@/utils/mitt';
|
||||
import { classCom } from '@/utils/classCom';
|
||||
const mbStore = useCommonStore();
|
||||
|
||||
@ -126,6 +131,8 @@ const queryForm = reactive({
|
||||
jgbz2: 1,
|
||||
});
|
||||
|
||||
const printWsData = ref('');
|
||||
|
||||
const printLoading = ref(false);
|
||||
const loading = ref(false);
|
||||
|
||||
@ -171,10 +178,8 @@ const printHandle = () => {
|
||||
printLoading.value = true
|
||||
batchPrintAll(queryForm).then((res) => {
|
||||
printLoading.value = false
|
||||
if (res.code == 0) {
|
||||
classCom.printBase64PDF(res.data.base64data)
|
||||
tableData.value = res.data.printflag
|
||||
}
|
||||
}).finally(() => {
|
||||
printLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
@ -198,6 +203,18 @@ const getYqConfig = () => {
|
||||
})
|
||||
}
|
||||
|
||||
// 监听打印消息处理函数
|
||||
const handlePrintMsg = (msg) => {
|
||||
console.log('打印进度消息', msg)
|
||||
const info = JSON.parse(msg.data)
|
||||
// 匹配表格,更新cp_msg
|
||||
const matchrow: any = tableData.value.find((item: any) => item.ybh == info.ybh)
|
||||
if (matchrow) {
|
||||
matchrow.cp_msg = info.errorMsg
|
||||
}
|
||||
// 传递给进度弹窗
|
||||
printWsData.value = msg.data
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getComDicts({ zdlb: 'GROUP' }).then((res: any) => {
|
||||
@ -210,8 +227,13 @@ onMounted(() => {
|
||||
|
||||
getDictData('PT', 'DP')
|
||||
});
|
||||
// 监听打印消息
|
||||
emitter.on('printReport', handlePrintMsg)
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
emitter.off('printReport', handlePrintMsg)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@ -185,7 +185,6 @@ import PatientQueryForm from './components/searchListFrom.vue'
|
||||
import { listUser } from '@/api/system/user.js'
|
||||
import dayjs from 'dayjs'
|
||||
const aotuSize = classCom.useAutoSize();
|
||||
import { initWebSocket, sendWebSocketMessage, closeWebSocket, getWebSocketState } from '@/utils/webSocket';
|
||||
|
||||
const previewShow = ref(false);
|
||||
const lbFlag = ref(false);
|
||||
@ -413,7 +412,6 @@ const yqHandleChange = () => {
|
||||
mbStore.setLxsrList([]);
|
||||
getSysList()
|
||||
fetchlabPatList()
|
||||
sendMessage()
|
||||
}
|
||||
const instrGroupFields = ref([
|
||||
{ prop: 'zddh', label: '代号', width: 80, enablePinyinSearch: true },
|
||||
@ -791,7 +789,6 @@ const getParams = () => {
|
||||
getSysList()
|
||||
getYqConfig()
|
||||
fetchlabPatList()
|
||||
sendMessage()
|
||||
// 读取后清空 state 刷新/回退不重复读取
|
||||
history.replaceState({}, document.title);
|
||||
}
|
||||
@ -827,43 +824,15 @@ onActivated(() => {
|
||||
getParams()
|
||||
})
|
||||
|
||||
const initSocket = () => {
|
||||
initWebSocket({
|
||||
fullUrl: `ws://47.97.125.165:8904/ws/instr/${mbStore.fingerprint}`,
|
||||
// fullUrl: `ws://192.168.1.151:9801/ws/instr/${mbStore.fingerprint}`,
|
||||
onOpen: () => {
|
||||
console.log('WebSocket连接成功');
|
||||
},
|
||||
onMessage: (data) => {
|
||||
console.log(`收到消息: `, JSON.parse(data));
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(`连接错误: ${error.type}`);
|
||||
},
|
||||
onClose: () => {
|
||||
console.log('WebSocket连接已关闭');
|
||||
},
|
||||
reconnectInterval: 3000 // 重连间隔(毫秒)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 页面卸载时清理
|
||||
onUnmounted(() => {
|
||||
// closeWebSocket();
|
||||
});
|
||||
|
||||
// 发送消息
|
||||
const sendMessage = () => {
|
||||
return
|
||||
const isSuccess = sendWebSocketMessage({ yq: queryParams.value.yq });
|
||||
if (isSuccess) {
|
||||
console.log(`发送消息`);
|
||||
} else {
|
||||
console.log('发送失败:连接未建立');
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@ -152,7 +152,6 @@ import BatchCode from './components/batchCode.vue';
|
||||
import { listUser } from '@/api/system/user.js'
|
||||
const mbStore = useCommonStore();
|
||||
const aotuSize = classCom.useAutoSize();
|
||||
import { initWebSocket, sendWebSocketMessage, closeWebSocket, getWebSocketState } from '@/utils/webSocket';
|
||||
import dayjs from 'dayjs';
|
||||
const previewShow = ref(false);
|
||||
const lbFlag = ref(false);
|
||||
@ -311,7 +310,6 @@ const yqHandleChange = () => {
|
||||
mbStore.setLxsrList([]);
|
||||
getSysList()
|
||||
fetchlabPatList()
|
||||
sendMessage()
|
||||
}
|
||||
const instrGroupFields = ref([
|
||||
{ prop: 'zddh', label: '代号', width: 80, enablePinyinSearch: true },
|
||||
@ -691,7 +689,6 @@ const getParams = () => {
|
||||
getSysList()
|
||||
getYqConfig()
|
||||
fetchlabPatList()
|
||||
sendMessage()
|
||||
// 读取后清空 state 刷新/回退不重复读取
|
||||
history.replaceState({}, document.title);
|
||||
}
|
||||
@ -716,50 +713,21 @@ onMounted(async () => {
|
||||
HOS: toRaw(dictRefs.HOS.value) || [],
|
||||
ST: toRaw(dictRefs.ST.value) || [],
|
||||
};
|
||||
// 初始化Socket
|
||||
// initSocket()
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
getParams()
|
||||
})
|
||||
|
||||
const initSocket = () => {
|
||||
initWebSocket({
|
||||
fullUrl: `ws://47.97.125.165:8904/ws/instr/${mbStore.fingerprint}`,
|
||||
onOpen: () => {
|
||||
console.log('WebSocket连接成功');
|
||||
},
|
||||
onMessage: (data) => {
|
||||
console.log(`收到消息: `, JSON.parse(data));
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(`连接错误: ${error.type}`);
|
||||
},
|
||||
onClose: () => {
|
||||
console.log('WebSocket连接已关闭');
|
||||
},
|
||||
reconnectInterval: 3000 // 重连间隔(毫秒)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 页面卸载时清理
|
||||
onUnmounted(() => {
|
||||
// closeWebSocket();
|
||||
});
|
||||
|
||||
// 发送消息
|
||||
const sendMessage = () => {
|
||||
return
|
||||
const isSuccess = sendWebSocketMessage({ yq: queryParams.value.yq });
|
||||
if (isSuccess) {
|
||||
console.log(`发送消息`);
|
||||
} else {
|
||||
console.log('发送失败:连接未建立');
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
<el-table-column :label="`${menuname}编号`" align="center" prop="paramId" v-if="false" />
|
||||
<el-table-column :label="`${menuname}类别`" align="center" prop="paramType" width="100" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span class="stats-type-cell" :style="{
|
||||
<span class="stats-type-cell" :style="{
|
||||
backgroundColor: getparamTypeColor(scope.row.paramType),
|
||||
color: '#fff'
|
||||
}">
|
||||
@ -124,19 +124,10 @@
|
||||
<el-input v-model="form.paramName" :placeholder="`请输入${menuname}名称`" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="`${menuname}默认值`" prop="paramDefvalue">
|
||||
<el-autocomplete
|
||||
v-model="form.paramDefvalue"
|
||||
:fetch-suggestions="queryDefaultSuggestions"
|
||||
placeholder="请输入/选择${menuname}默认值"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<!-- 下拉选项自定义展示 -->
|
||||
<template #default="{ item }">
|
||||
<span>{{ item.label }}</span>
|
||||
<span style="color:#999; margin-left:10px;">{{ item.value }}</span>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
<el-select v-model="form.paramDefvalue" :placeholder="`请选择${menuname}默认值`" style="width: 100%" clearable>
|
||||
<el-option v-for="item in defaultOptions" :key="item.value" :label="item.label + ' ' + item.value"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="`${menuname}框体类型`" prop="paramOptiontype">
|
||||
<el-select v-model="form.paramOptiontype" :placeholder="`请选择${menuname}筛选类型`" style="width: 100%">
|
||||
@ -225,13 +216,13 @@ const copyTemplateOptions = ref([])
|
||||
|
||||
// 报表类别选项
|
||||
const paramTypeOptions = ref([
|
||||
{ label: "统计条件", value: "0" ,color: "#3282F6"},
|
||||
{ label: "显示列", value: "1" , color: "#67C23A"},
|
||||
{ label: "副标题", value: "2" , color: "#FF443F"},
|
||||
{ label: "汇总栏", value: "3" , color: "#FF443F"},
|
||||
{ label: "子查询", value: "4" , color: "#FF443F"},
|
||||
{ label: "逻辑条件", value: "5" , color: "#909399"},
|
||||
{ label: "计算列", value: "6" , color: "#C2C024" }
|
||||
{ label: "统计条件", value: "0", color: "#3282F6" },
|
||||
{ label: "显示列", value: "1", color: "#67C23A" },
|
||||
{ label: "副标题", value: "2", color: "#FF443F" },
|
||||
{ label: "汇总栏", value: "3", color: "#FF443F" },
|
||||
{ label: "子查询", value: "4", color: "#FF443F" },
|
||||
{ label: "逻辑条件", value: "5", color: "#909399" },
|
||||
{ label: "计算列", value: "6", color: "#C2C024" }
|
||||
]);
|
||||
|
||||
|
||||
@ -252,7 +243,7 @@ const defaultOptions = ref([
|
||||
const queryDefaultSuggestions = (queryString, callback) => {
|
||||
// 根据输入文本过滤预设选项,无输入则展示全部
|
||||
const suggestions = defaultOptions.value.filter(item =>
|
||||
item.label.includes(queryString) || item.value.includes(queryString)
|
||||
item.label.includes(queryString) || item.value.includes(queryString)
|
||||
);
|
||||
callback(suggestions);
|
||||
};
|
||||
@ -567,21 +558,14 @@ function getTypes(statsCode0) {
|
||||
|
||||
.compact-form {
|
||||
--el-form-item-margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
/* line-height: 1.4; */
|
||||
|
||||
.el-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.compact-form .el-form-item__label) {
|
||||
padding: 0 8px 0 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:deep(.compact-form .el-form-item__content) {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 对话框底部按钮区 */
|
||||
:deep(.compact-dialog .dialog-footer) {
|
||||
@ -593,6 +577,7 @@ function getTypes(statsCode0) {
|
||||
:deep(.compact-form .el-form-item:last-of-type .el-textarea__inner) {
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.stats-type-cell {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@ -92,9 +92,9 @@ import { ref, watch, getCurrentInstance, onMounted } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { getCodeImg, getInfo, getLocalconfig, login } from "@/api/login";
|
||||
import Cookies from "js-cookie";
|
||||
import { useFingerprint } from '@/utils/useFingerprint';
|
||||
import { encrypt, decrypt } from "@/utils/jsencrypt";
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
import { useCommonStore } from '@/store/modules/commonStore'
|
||||
const mbStore = useCommonStore();
|
||||
// 初始化响应式变量
|
||||
@ -208,8 +208,8 @@ const getCookie = () => {
|
||||
};
|
||||
|
||||
// 获取本地配置
|
||||
const Localconfig = () => {
|
||||
getLocalconfig().then(res => {
|
||||
const Localconfig = (localid) => {
|
||||
getLocalconfig({ localid }).then(res => {
|
||||
if (res) {
|
||||
const { localconfig, hosplist, oem: oemData } = res;
|
||||
mbStore.setDefaultConfig(localconfig)
|
||||
@ -222,6 +222,19 @@ const Localconfig = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 获取指纹并加载配置
|
||||
const { getFingerprint } = useFingerprint();
|
||||
const refreshFingerprint = async () => {
|
||||
try {
|
||||
const res = await getFingerprint();
|
||||
loginForm.value.loginParam.localid = res.visitorId
|
||||
Localconfig(res.visitorId);
|
||||
} catch (err) {
|
||||
console.error("获取指纹失败:", err);
|
||||
Localconfig("default"); // 兜底使用默认ID
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Tab切换事件(移除activeName手动赋值,避免递归)
|
||||
const handleClick = (tab) => {
|
||||
@ -233,7 +246,7 @@ const handleClick = (tab) => {
|
||||
};
|
||||
|
||||
|
||||
Localconfig();
|
||||
refreshFingerprint();
|
||||
getCode();
|
||||
</script>
|
||||
|
||||
|
||||
@ -57,6 +57,10 @@
|
||||
<el-time-picker v-model="item.paramDefvalue" value-format="HH:mm:ss" format="HH:mm:ss"
|
||||
style="width:100px" />
|
||||
</template>
|
||||
<template v-if="item.paramOptiontype == '9'">
|
||||
<el-time-picker v-model="item.paramDefvalue" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss" style="width:100px" />
|
||||
</template>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
@ -86,46 +90,22 @@
|
||||
<div class="table-container bb_table" :style="{ height: `calc(100% - ${statsTitle ? '70px' : '15px'})` }">
|
||||
<vxe-table :data="tableData" border v-loading="loading" height="100%"
|
||||
:tooltip-config="{ appendToBody: true, zIndex: 3000 }" show-footer :footer-method="getSummaries"
|
||||
:scroll-y="{ enabled: true, rSize: 30, height: '100%', }" show-footer-overflow>
|
||||
<vxe-column v-for="col in tableColumns" :key="col.prop" :title="col.label" :field="col.prop" width="120"
|
||||
show-overflow align="center">
|
||||
:scroll-y="{ enabled: true, rSize: 30, height: '100%', oSize: 20, gt: 30, }" show-footer-overflow>
|
||||
<vxe-column v-for="col in tableColumns" :key="col.prop" :title="col.label" :field="col.prop"
|
||||
:width="col.paramWidth" show-overflow align="center">
|
||||
<template #default="scope">
|
||||
{{ col.dictType ? formatDict(scope.row[col.prop], col.dictType) : scope.row[col.prop] }}
|
||||
<template v-if="col.paramOptiontype == 0">
|
||||
<el-checkbox :model-value="scope.row[col.prop]" true-value="1" false-value="0" />
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ col.dictType ? formatDict(scope.row[col.prop], col.dictType) : scope.row[col.prop] }}
|
||||
</template>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视觉移出屏幕,页面看不见,但DOM完整存在用于iframe打印 -->
|
||||
<div class="print-only-wrap" id="printWrap">
|
||||
<div class="title" v-if="!statsTitle">{{ formatDict(userStore.sysLoginParam.loginYLJG, 'HOS') }}</div>
|
||||
<div class="content">{{ statsTitle }}</div>
|
||||
<!-- 原生完整table,无滚动、无虚拟DOM,所有数据全部渲染 -->
|
||||
<table class="print-table" border="1" cellpadding="0" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="col in tableColumns" :key="col.prop">{{ col.label }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIdx) in tableData" :key="rowIdx">
|
||||
<td v-for="col in tableColumns" :key="col.prop" align="center">
|
||||
{{ col.dictType ? formatDict(row[col.prop], col.dictType) : row[col.prop] }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<!-- 合计行,和vxe合计逻辑完全一致 -->
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td v-for="(col, colIdx) in tableColumns" :key="col.prop" align="center">
|
||||
{{ getPrintFooterText(col, colIdx) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -210,29 +190,44 @@ const getSummaries = ({ columns, data }) => {
|
||||
|
||||
return [footerData];
|
||||
};
|
||||
|
||||
// 记录统计项目勾选先后顺序,存储 paramCode
|
||||
const checkOrder = ref([])
|
||||
// 处理checkbox选中
|
||||
const handleCheckBoxChange = (val) => {
|
||||
// val:当前所有已勾选 paramCode 数组
|
||||
// 第一步:维护勾选顺序数组 checkOrder
|
||||
// 1. 过滤掉已取消勾选的项
|
||||
checkOrder.value = checkOrder.value.filter(code => val.includes(code))
|
||||
// 2. 新增本次刚勾选、不在顺序数组里的项,追加到末尾(记录点击先后)
|
||||
val.forEach(code => {
|
||||
if (!checkOrder.value.includes(code)) {
|
||||
checkOrder.value.push(code)
|
||||
}
|
||||
})
|
||||
|
||||
// 基础列:保留,不随勾选删除
|
||||
const baseCols = tableColumns.value.filter(col => {
|
||||
// 判断是否是统计项目复选框对应的列:checkBoxs里存在该paramCode就是可选列
|
||||
const isCheckItem = checkBoxs.value.some(item => item.paramCode === col.prop)
|
||||
return !isCheckItem
|
||||
})
|
||||
|
||||
// 2. 获取当前勾选的所有可选列配置
|
||||
const selectedCheckCols = checkBoxs.value
|
||||
.filter(item => val.includes(item.paramCode))
|
||||
// 2. 按【用户勾选先后顺序】生成选中列
|
||||
const selectedCheckCols = checkOrder.value
|
||||
.map(code => checkBoxs.value.find(item => item.paramCode === code))
|
||||
.filter(Boolean) // 过滤找不到的脏数据
|
||||
.map(item => ({
|
||||
key: item.paramCode,
|
||||
label: item.paramName,
|
||||
prop: item.paramCode,
|
||||
dictType: item.remark,
|
||||
paramSequence: 0
|
||||
paramSequence: 0,
|
||||
paramWidth: item.paramWidth,
|
||||
paramOptiontype: item.paramOptiontype
|
||||
}))
|
||||
|
||||
// 3. 合并数组:勾选项放最前面 + 原有基础列在后
|
||||
// 3. 合并数组:按勾选顺序的勾选项放最前面 + 原有基础列在后
|
||||
tableColumns.value = [...selectedCheckCols, ...baseCols]
|
||||
|
||||
let seq = 1
|
||||
tableColumns.value.forEach((col, idx) => {
|
||||
if (col.paramSequence === 0) {
|
||||
@ -240,7 +235,6 @@ const handleCheckBoxChange = (val) => {
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// 查询
|
||||
const search = () => {
|
||||
originalColumn.value.forEach(item => {
|
||||
@ -279,95 +273,7 @@ const getPrintFooterText = (col, idx) => {
|
||||
|
||||
// iframe打印核心方法
|
||||
const print = () => {
|
||||
return;
|
||||
// 获取隐藏打印容器完整HTML
|
||||
const printDom = document.getElementById('printWrap')
|
||||
const printHtml = printDom.outerHTML
|
||||
|
||||
// 打印页面内置样式(统一红边框、行高、打印A4横向、合计加粗)
|
||||
const printStyle = `
|
||||
<style>
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 8mm;
|
||||
}
|
||||
html,body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.content {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.print-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.print-table th,
|
||||
.print-table td {
|
||||
border: 1px solid #ccc;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
font-size: 12px;
|
||||
padding: 2px 4px;
|
||||
}
|
||||
.print-table tfoot td {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
tr {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
/* 打印渲染完整颜色边框 */
|
||||
* {
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
print-color-adjust: exact !important;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
|
||||
// 1. 创建隐藏iframe
|
||||
const iframe = document.createElement('iframe')
|
||||
iframe.style.width = '0px'
|
||||
iframe.style.height = '0px'
|
||||
iframe.style.position = 'absolute'
|
||||
iframe.style.left = '-9999px'
|
||||
document.body.appendChild(iframe)
|
||||
|
||||
// 2. 写入完整打印页面内容
|
||||
const iframeDoc = iframe.contentDocument
|
||||
iframeDoc.open()
|
||||
iframeDoc.write(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>报表打印</title>
|
||||
${printStyle}
|
||||
</head>
|
||||
<body>
|
||||
${printHtml}
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
iframeDoc.close()
|
||||
|
||||
// 3. 等待DOM渲染完成后打印
|
||||
setTimeout(() => {
|
||||
iframe.contentWindow.print()
|
||||
// 打印弹窗关闭后销毁iframe
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(iframe)
|
||||
}, 1000)
|
||||
}, 600)
|
||||
// classCom.printBase64PDF()
|
||||
}
|
||||
|
||||
|
||||
@ -386,6 +292,8 @@ const getParams = () => {
|
||||
prop: item.paramCode,
|
||||
dictType: item.remark,
|
||||
paramSequence: item.paramSequence,
|
||||
paramWidth: item.paramWidth,
|
||||
paramOptiontype: item.paramOptiontype,
|
||||
}
|
||||
}) //显示列 (item.paramType == '1' || item.paramType == '6') && item.paramSequence > 0
|
||||
|
||||
@ -416,22 +324,6 @@ onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 打印专用容器:屏幕视觉隐藏,DOM保留
|
||||
.print-only-wrap {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
top: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -525,6 +417,7 @@ onMounted(() => {
|
||||
|
||||
.table-container {
|
||||
padding: 10px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tips {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user