消息服务
This commit is contained in:
parent
d924d02e71
commit
e05e4c6556
42
src/api/msg.ts
Normal file
42
src/api/msg.ts
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file msg.ts
|
||||
* @description: 消息系统
|
||||
* @author: w
|
||||
* @since: 2026-09-01
|
||||
*/
|
||||
|
||||
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询消息列表
|
||||
export function queryMsgList(query?: Object) {
|
||||
return request({
|
||||
url: '/bus/bloodbank/msg/queryMsgList',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
// 获取某个消息详情
|
||||
export function getMsgInfo(query?: Object) {
|
||||
return request({
|
||||
url: '/bus/bloodbank/msg/queryOne',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
// 更新消息状态
|
||||
export function updateMsgFlag(query?: Object) {
|
||||
return request({
|
||||
url: '/bus/bloodbank/msg/updateFlag',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
// 获取未读消息数量
|
||||
export function queryNoReadCount(query?: Object) {
|
||||
return request({
|
||||
url: '/bus/bloodbank/msg/queryNoReadCount',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
61
src/components/WsMsgDetails/index.vue
Normal file
61
src/components/WsMsgDetails/index.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="dialogVisible" title="消息详情" width="600px" append-to-body :close-on-click-modal="false">
|
||||
<div class="header">
|
||||
<div class="title">{{ currentMsg.xkMsgMain.msg_Title || '系统消息' }}</div>
|
||||
<div class="time"> {{ currentMsg.xkMsgMain.write_Date || '' }} </div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ currentMsg.xkMsgDetailList[0].msg_Body || '暂无内容' }}</p>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getMsgInfo } from '@/api/msg'
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const currentMsg = ref({})
|
||||
|
||||
const open = (id) => {
|
||||
getMsgInfo({ msgId: id }).then(res => {
|
||||
if (res.code == 0) {
|
||||
currentMsg.value = res.data
|
||||
dialogVisible.value = true
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 2px solid #eee;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 200px;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
133
src/components/WsMsgList/index.vue
Normal file
133
src/components/WsMsgList/index.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog v-model="dialogVisible" title="消息列表" width="1000px" append-to-body :close-on-click-modal="false"
|
||||
@close="close">
|
||||
<el-form :model="searchForm" inline>
|
||||
<el-form-item label="时间:">
|
||||
<div class="date-range">
|
||||
<el-date-picker v-model="searchForm.swriteDate" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
|
||||
style="width: 150px" />
|
||||
<span class="separator" style="width: 10px;display: inline-block;text-align: center;">-</span>
|
||||
<el-date-picker v-model="searchForm.ewriteDate" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
|
||||
style="width: 150px" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="消息类型:">
|
||||
<el-select v-model="searchForm.msg_Type" placeholder="请选择" clearable>
|
||||
<el-option label="取血通知" value="BLOOD_OUT_NOTICE" />
|
||||
<el-option label="输血申请单保存消息" value="APPLY_NOTICE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否已读:">
|
||||
<el-select v-model="searchForm.read_Flag" placeholder="请选择" clearable>
|
||||
<el-option label="已读" value="Y" />
|
||||
<el-option label="未读" value="N" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div>
|
||||
<el-button type="primary" @click="getList" icon="Search">查询</el-button>
|
||||
<!-- <el-button type="warning" @click="markAllAsRead" icon="Check">一键已读</el-button> -->
|
||||
</div>
|
||||
|
||||
<div class="mt10">
|
||||
<el-table :data="msgList" border height="450" show-overflow-tooltip highlight-current-row>
|
||||
<el-table-column label="消息标题" prop="Msg_Title" width="200" align="center" />
|
||||
<el-table-column label="消息内容" prop="Msg_Body" align="center" />
|
||||
<el-table-column label="消息时间" prop="Write_Date" align="center" width="200" />
|
||||
<el-table-column label="状态" prop="Read_Flag" align="center" width="80">
|
||||
<template v-slot="{ row }">
|
||||
<el-tag v-if="row.Read_Flag == 'Y'" type="success">已读</el-tag>
|
||||
<el-tag v-else type="danger">未读</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" align="center" :show-overflow-tooltip="false">
|
||||
<template v-slot="{ row }">
|
||||
<el-button type="primary" text @click="handleDetail(row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<pagination :total="total" v-model:page="searchForm.pageNum" v-model:limit="searchForm.pageSize"
|
||||
@pagination="getList" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
<WsMsgDetails ref="msgDetailsRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { queryMsgList, updateMsgFlag } from '@/api/msg'
|
||||
import emitter from '@/utils/mitt'
|
||||
import dayjs from 'dayjs'
|
||||
import WsMsgDetails from "@/components/WsMsgDetails/index.vue"
|
||||
import useUserStore from '@/store/modules/user'
|
||||
const userInfo = useUserStore()
|
||||
|
||||
const msgDetailsRef = useTemplateRef('msgDetailsRef')
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const searchForm = ref({
|
||||
swriteDate: dayjs().format('YYYY-MM-DD'),
|
||||
ewriteDate: dayjs().format('YYYY-MM-DD'),
|
||||
pageNum: 1,
|
||||
pageSize: 20
|
||||
})
|
||||
const msgList = ref([])
|
||||
const total = ref(0)
|
||||
const open = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
const getList = () => {
|
||||
searchForm.value.swriteDate = dayjs(searchForm.value.swriteDate).format('YYYY-MM-DD') + ' 00:00:00'
|
||||
searchForm.value.ewriteDate = dayjs(searchForm.value.ewriteDate).format('YYYY-MM-DD') + ' 23:59:59'
|
||||
queryMsgList(searchForm.value).then(res => {
|
||||
if (res.code == 200) {
|
||||
msgList.value = res.rows
|
||||
total.value = res.total
|
||||
dialogVisible.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const markAllAsRead = () => { }
|
||||
|
||||
const handleDetail = (row) => {
|
||||
console.log("🚀 ~ handleDetail ~ row:", row)
|
||||
msgDetailsRef.value.open(row.msgid)
|
||||
return
|
||||
const data = {
|
||||
accept_Date: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
accept_Person: userInfo.name,
|
||||
msgId: row.msgid,
|
||||
}
|
||||
updateMsgFlag(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
emitter.emit('resetCount')
|
||||
getList()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
searchForm.value = {
|
||||
swriteDate: dayjs().format('YYYY-MM-DD'),
|
||||
ewriteDate: dayjs().format('YYYY-MM-DD'),
|
||||
pageNum: 1,
|
||||
pageSize: 20
|
||||
}
|
||||
msgList.value = []
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.el-form-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
145
src/components/WsNotifyPopup/index.vue
Normal file
145
src/components/WsNotifyPopup/index.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
|
||||
<div v-show="showNotify" class="ws-notify-popup">
|
||||
<div class="notify-header">
|
||||
<span class="notify-title">{{ currentMsg.title || '系统消息' }}</span>
|
||||
<span class="close-btn" @click="showNotify = false">×</span>
|
||||
</div>
|
||||
<div class="notify-body">
|
||||
<p class="notify-text" @click="viewHandle">{{ currentMsg.content || '暂无消息内容' }}</p>
|
||||
<el-button type="primary" size="small" @click="handleRead">已读</el-button>
|
||||
</div>
|
||||
|
||||
<WsMsgDetails ref="msgDetailsRef" />
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { updateMsgFlag } from '@/api/msg'
|
||||
import dayjs from 'dayjs'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import WsMsgDetails from '@/components/WsMsgDetails/index.vue'
|
||||
import emitter from '@/utils/mitt'
|
||||
|
||||
const userInfo = useUserStore()
|
||||
const showNotify = ref(false)
|
||||
const currentMsg = ref({})
|
||||
|
||||
const msgDetailsRef = useTemplateRef('msgDetailsRef')
|
||||
const open = (data) => {
|
||||
currentMsg.value = data
|
||||
showNotify.value = true
|
||||
}
|
||||
|
||||
|
||||
const handleRead = () => {
|
||||
const data = {
|
||||
accept_Date: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
accept_Person: userInfo.name,
|
||||
msgId: currentMsg.value.extra.msgId,
|
||||
}
|
||||
updateMsgFlag(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
emitter.emit('resetCount')
|
||||
showNotify.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const viewHandle = () => {
|
||||
msgDetailsRef.value.open(currentMsg.value.extra.msgId || '')
|
||||
handleRead()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ws-notify-popup {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
z-index: 99999;
|
||||
width: 340px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
/* 医疗风柔和阴影,不轻浮 */
|
||||
box-shadow: 0 6px 20px rgba(26, 88, 145, 0.18);
|
||||
overflow: hidden;
|
||||
animation: popIn 0.25s ease-out;
|
||||
border: 1px solid #e8f1f8;
|
||||
}
|
||||
|
||||
/* 左侧医疗蓝标识条(医院系统常用蓝 #1677ff),伪元素,不改动模板 */
|
||||
.ws-notify-popup::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
background: #1677ff;
|
||||
}
|
||||
|
||||
.notify-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px 12px 22px;
|
||||
/* 浅医疗蓝背景,淡雅 */
|
||||
background: #f0f7ff;
|
||||
}
|
||||
|
||||
.notify-title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
color: #1a4b7a;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
color: #748494;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
.notify-body {
|
||||
padding: 16px 16px 18px 22px;
|
||||
}
|
||||
|
||||
.notify-text {
|
||||
margin: 0 0 14px 0;
|
||||
font-size: 14px;
|
||||
color: #33465b;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
/* 展示2行 */
|
||||
-webkit-box-orient: vertical;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #1677ff;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -32,7 +32,17 @@
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div class="msg_box" @click="openHandle">
|
||||
<el-badge :value="count" :max="99" class="item" :show-zero="false">
|
||||
<el-icon color="#FFF" size="25" class="icon">
|
||||
<BellFilled />
|
||||
</el-icon>
|
||||
</el-badge>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<WsMsgList ref="wsMsgListRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -52,16 +62,25 @@ import useAppStore from '@/store/modules/app'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import { queryHosList, addHos, upadteHos } from '@/api/system/medicalInstitutions'
|
||||
import { queryNoReadCount } from '@/api/msg'
|
||||
import WsMsgList from '@/components/WsMsgList/index.vue'
|
||||
import emitter from '@/utils/mitt.js'
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
|
||||
const count = ref(0)
|
||||
const wsMsgListRef = useTemplateRef('wsMsgListRef')
|
||||
const yljgName = ref('输血平台')
|
||||
function toggleSideBar(): void {
|
||||
appStore.toggleSideBar()
|
||||
}
|
||||
|
||||
|
||||
const openHandle = () => {
|
||||
wsMsgListRef.value.open()
|
||||
}
|
||||
function handleCommand(command: string): void {
|
||||
switch (command) {
|
||||
case "setLayout":
|
||||
@ -132,10 +151,19 @@ async function toggleTheme(event?: MouseEvent): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
const getCount = () => {
|
||||
queryNoReadCount().then(res => {
|
||||
count.value = res.data || 0
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
queryHosList().then((resp) => {
|
||||
yljgName.value = resp.data.find((item) => item.hospital_code == userStore.sysLoginParam.loginYLJG)?.hospital_name || '输血平台'
|
||||
})
|
||||
getCount()
|
||||
emitter.on("resetCount", getCount)
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -286,6 +314,15 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.msg_box {
|
||||
width: 50px;
|
||||
padding-top: 15px;
|
||||
|
||||
.item {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -13,6 +13,8 @@
|
||||
|
||||
<!-- 库存预警组件 -->
|
||||
<BloodStockNotify />
|
||||
<!-- websocket消息通知组件 -->
|
||||
<WsNotifyPopup ref="msgRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -23,8 +25,13 @@ import { AppMain, Navbar, Settings, TagsView } from './components'
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import { useSysParam } from '@/hooks/useSysParam'
|
||||
import { initWebSocket } from '@/utils/webSocket'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import emitter from '@/utils/mitt.js'
|
||||
const userInfo = useUserStore()
|
||||
// 异步懒加载,首屏不阻塞
|
||||
const BloodStockNotify = defineAsyncComponent(() => import('@/components/BloodStockNotify/index.vue'))
|
||||
const WsNotifyPopup = defineAsyncComponent(() => import('@/components/WsNotifyPopup/index.vue'))
|
||||
|
||||
const { initAllParams } = useSysParam()
|
||||
|
||||
@ -78,10 +85,37 @@ function setLayout() {
|
||||
settingRef.value.openSetting()
|
||||
}
|
||||
|
||||
const msgRef = useTemplateRef('msgRef')
|
||||
|
||||
const onNewMessage = (newMsg) => {
|
||||
console.log("🚀 ~ onNewMessage ~ newMsg:", newMsg)
|
||||
if (newMsg.to == userInfo.name || newMsg.to == 'ALL') {
|
||||
msgRef.value.open(newMsg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
// 获取参数值
|
||||
initAllParams(GLOBAL_PARAM_KEYS)
|
||||
|
||||
// 初始化socket
|
||||
const url = `ws://47.97.125.165:8907/ws/msgserver/${userInfo.name}`
|
||||
initWebSocket({ fullUrl: url })
|
||||
|
||||
|
||||
emitter.on("APPLY_NOTICE", onNewMessage)
|
||||
|
||||
// 读取缓存,防止ws消息比组件挂载早,丢失消息
|
||||
const cacheRaw = emitter.getCache("APPLY_NOTICE")
|
||||
if (cacheRaw) {
|
||||
onNewMessage(cacheRaw)
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
emitter.off("APPLY_NOTICE", onNewMessage)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -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,75 @@ 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.type;
|
||||
//根据消息类型发送事件
|
||||
//APPLY_NOTICE 输血申请保存
|
||||
emitter.emit(msgType, rawMsg);
|
||||
// 重置未读消息数
|
||||
emitter.emit('resetCount')
|
||||
} 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 +87,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;
|
||||
}
|
||||
@ -11,9 +11,9 @@
|
||||
<el-button type="primary" plain @click="openJsHandle">检索</el-button>
|
||||
<el-button type="primary" plain @click="addMatch">新增单据</el-button>
|
||||
<el-button type="success" plain :disabled="bloodInfo.bill_status && bloodInfo.bill_status != 'A'"
|
||||
@click="saveBloodApply" :loading="saveLoading">保存</el-button>
|
||||
@click="saveBloodApply(1)" :loading="saveLoading">保存</el-button>
|
||||
<el-button type="primary" plain @click="tzHandle">通知取血</el-button>
|
||||
<el-button type="primary" plain @click="auditHandle">审核</el-button>
|
||||
<el-button type="primary" plain :loading="auditLoading" @click="auditHandle">审核</el-button>
|
||||
<el-button type="primary" plain @click="openDcHandle">多次发血</el-button>
|
||||
<el-button type="warning" plain :loading="printLoading" @click="printHandle">打印</el-button>
|
||||
<el-button type="warning" plain>打印标签</el-button>
|
||||
@ -21,7 +21,7 @@
|
||||
|
||||
<!-- 主体左右分栏容器 -->
|
||||
<el-form :model="formData" label-width="auto" class="form_box"
|
||||
:disabled="bloodInfo.bill_status && bloodInfo.bill_status != 'A'">
|
||||
:disabled="bloodInfo.check_sign && bloodInfo.check_sign == 'Y'">
|
||||
<div class="card-box">
|
||||
<div class="section-title">
|
||||
<div> 申请单信息 </div>
|
||||
@ -38,7 +38,8 @@
|
||||
<el-form-item label="申请单号">
|
||||
<DropdownTableSelect v-model="formData.bill_no" :column-list="applyColumns" tableWidth="800px"
|
||||
:showSearch="false" labelField="bill_no" :table-data="applyTableData" @open="loadApplyData"
|
||||
@search="searchApply" @change="searchApply" :disabled="!!(bloodInfo.bill_no)">
|
||||
@search="searchApply" @change="searchApply"
|
||||
:disabled="bloodInfo.check_sign && bloodInfo.check_sign == 'Y'">
|
||||
<template #dept_id="{ row }">
|
||||
{{ formatDict(row.dept_id, 'ks') }}
|
||||
</template>
|
||||
@ -205,14 +206,16 @@
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="发血经手人">
|
||||
<SelectTable v-model:data="bloodInfo.handle_person" :tableData="userList" placeholder="" />
|
||||
<SelectTable v-model:data="bloodInfo.handle_person" :tableData="userList" placeholder=""
|
||||
:disabled="bloodInfo.check_sign && bloodInfo.check_sign == 'Y'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="取血人">
|
||||
<SelectTable v-model:data="bloodInfo.take_person" :tableData="userList" placeholder="" />
|
||||
<SelectTable v-model:data="bloodInfo.take_person" :tableData="userList" placeholder=""
|
||||
:disabled="bloodInfo.check_sign && bloodInfo.check_sign == 'Y'" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
@ -436,11 +439,7 @@ const bloodInfo = ref({
|
||||
})
|
||||
|
||||
const saveLoading = ref(false)
|
||||
const billStatus = ref([
|
||||
{ label: '未发血', value: 'A' },
|
||||
{ label: '已发血', value: 'B' },
|
||||
{ label: '已作废', value: 'ZF' },
|
||||
])
|
||||
const auditLoading = ref(false)
|
||||
const searchForm = ref({
|
||||
stime: dayjs().format('YYYY-MM-DD'),
|
||||
etime: dayjs().format('YYYY-MM-DD'),
|
||||
@ -566,7 +565,7 @@ const bloodEnter = () => {
|
||||
if (res.data.length == 1) {
|
||||
const row = res.data[0]
|
||||
checkBlood(row)
|
||||
} else if (res.data.length > 1) {
|
||||
} else if (res.data.length > 1) { //多条血液数据进行弹窗选择
|
||||
maskRef.value.open(res.data)
|
||||
}
|
||||
})
|
||||
@ -680,11 +679,13 @@ const handleSearch = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const saveBloodApply = () => {
|
||||
const saveBloodApply = (type: number) => { // 1保存 2审核
|
||||
//判断发血时间、通知取血时间大于当前时间7天
|
||||
const isFlag = checkTimeTip(bloodInfo.value.occur_date, bloodInfo.value.notify_time)
|
||||
if (!isFlag) return
|
||||
saveLoading.value = true
|
||||
if (type == 1) {
|
||||
saveLoading.value = true
|
||||
}
|
||||
const data = {
|
||||
moreSign: moreSign.value,
|
||||
xkOutMain: {
|
||||
@ -715,10 +716,16 @@ const saveBloodApply = () => {
|
||||
}
|
||||
}
|
||||
|
||||
saveBloodInfo(data).then(res => {
|
||||
return saveBloodInfo(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
ElMessage.success('保存成功!')
|
||||
addMatch()
|
||||
if (type == 1) {
|
||||
ElMessage.success('保存成功!')
|
||||
addMatch()
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
}).finally(() => {
|
||||
saveLoading.value = false
|
||||
@ -737,14 +744,14 @@ function checkTimeTip(sendBloodTime, notifyGetBloodTime) {
|
||||
if (notifyDiff > 7) {
|
||||
msg.push('通知取血时间与当前时间超过七天;')
|
||||
}
|
||||
if (msg) {
|
||||
if (msg.length) {
|
||||
ElMessageBox.alert(msg.join('<br/>'), '提示', { type: 'warning', dangerouslyUseHTMLString: true })
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
//通知取血
|
||||
const tzHandle = () => {
|
||||
if (!formData.value.bill_no) return ElMessage.warning('请选择配血单!')
|
||||
notifyMessage({ billNo: bloodInfo.value.bill_no }).then(res => {
|
||||
@ -755,22 +762,33 @@ const tzHandle = () => {
|
||||
}
|
||||
|
||||
const auditHandle = () => {
|
||||
const data = {
|
||||
applyNo: formData.value.bill_no,
|
||||
billNo: bloodInfo.value.bill_no,
|
||||
takePerson: bloodInfo.value.take_person,
|
||||
userId: userInfo.name
|
||||
}
|
||||
bloodOutAudit(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
ElMessage.success(res.msg)
|
||||
if (!bloodInfo.value.bill_no) return ElMessage.warning("该单据还未保存!")
|
||||
//审核之前保存 防止修改未保存审核单据
|
||||
saveBloodApply(2).then(saveFlag => {
|
||||
if (saveFlag) {
|
||||
const data = {
|
||||
applyNo: formData.value.bill_no,
|
||||
billNo: bloodInfo.value.bill_no,
|
||||
takePerson: bloodInfo.value.take_person,
|
||||
userId: userInfo.name
|
||||
}
|
||||
auditLoading.value = true
|
||||
bloodOutAudit(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
ElMessage.success(res.msg)
|
||||
//审核成功后通知取血操作
|
||||
tzHandle()
|
||||
}
|
||||
}).finally(() => {
|
||||
auditLoading.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const printHandle = () => {
|
||||
if (!bloodInfo.value.bill_no) return ElMessage.warning('该单据还未保存!')
|
||||
printLoading.value = true
|
||||
if (!bloodInfo.value.bill_no) return ElMessage.warning('请选择配血单!')
|
||||
printReport({ billNo: bloodInfo.value.bill_no, templateName: 'BLOOD_OUT' }).then(res => {
|
||||
if (res.data) {
|
||||
printBase64PDF(res.data)
|
||||
|
||||
@ -522,10 +522,10 @@
|
||||
{{ formatDict(scope.row.dept_id, 'ks') }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="abo_type" label="血型" align="center" />
|
||||
<el-table-column prop="rh_type" label="Rh(D)" align="center" />
|
||||
<el-table-column prop="abo_type" label="血型" align="center" width="60" />
|
||||
<el-table-column prop="rh_type" label="Rh(D)" align="center" width="60" />
|
||||
<el-table-column prop="req_breed" label="申请品种" align="center" />
|
||||
<el-table-column prop="apply_date" label="申请日期" width="120" align="center" />
|
||||
<el-table-column prop="apply_date" label="申请日期" width="150" align="center" />
|
||||
<el-table-column prop="apply_medic" label="申请医师" align="center">
|
||||
<template #default="scope">
|
||||
{{userList.find(item => item.value == scope.row.apply_medic)?.label || ''}}
|
||||
@ -590,6 +590,7 @@ import CheckUser from '@/components/CheckUser/index.vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import router from '@/router/index.js'
|
||||
import { printBase64PDF } from '@/utils/classCom.js'
|
||||
import { sendWebSocketMessage } from '@/utils/webSocket.js'
|
||||
const userInfo = useUserStore()
|
||||
|
||||
|
||||
@ -873,7 +874,7 @@ const reviewCommentsRef = useTemplateRef('reviewCommentsRef')
|
||||
// 如果是001 代表是临床科室,此时需要调用“上级医生审核” 审核接口,
|
||||
// 如果是002 代表是输血科室,调用“申请单审核前校验”
|
||||
const reviewHandle = () => {
|
||||
if (!xkTransfuseApply.value.bill_no) return ElMessage.warning('请选择申请单')
|
||||
if (!xkTransfuseApply.value.bill_no) return ElMessage.warning('该单据还未保存!')
|
||||
shLoading.value = true
|
||||
getDeptType({ userId: userInfo.name }).then(res => {
|
||||
if (res.data == '001') {
|
||||
@ -901,7 +902,7 @@ const handleSh = () => {
|
||||
}
|
||||
|
||||
const cancelreviewHandle = () => {
|
||||
if (!xkTransfuseApply.value.bill_no) return ElMessage.warning('请选择申请单')
|
||||
if (!xkTransfuseApply.value.bill_no) return ElMessage.warning('该单据还未保存!')
|
||||
getDeptType({ userId: userInfo.name }).then(res => {
|
||||
ElMessageBox.confirm(
|
||||
`申请单号${xkTransfuseApply.value.bill_no}确定取消审核吗?`,
|
||||
@ -935,7 +936,7 @@ const reasonText = ref('')
|
||||
const zfShow = ref(false)
|
||||
const invalidateHandle = () => {
|
||||
// sqdzfyy
|
||||
if (!xkTransfuseApply.value.bill_no) return ElMessage.warning('请选择申请单')
|
||||
if (!xkTransfuseApply.value.bill_no) return ElMessage.warning('该单据还未保存!')
|
||||
zfShow.value = true
|
||||
}
|
||||
const handleZf = () => {
|
||||
@ -975,7 +976,6 @@ const handleReviewConfirm = () => {
|
||||
}
|
||||
|
||||
//审核意见
|
||||
|
||||
const shClose = () => {
|
||||
shShow.value = false
|
||||
shForm.value = {
|
||||
@ -1103,7 +1103,12 @@ const handleSubmit = async () => {
|
||||
xkPatientInfo: tempPatient,
|
||||
xkTransfuseApply: xkTransfuseApply.value
|
||||
},
|
||||
xkTransfuseApplyBloodbreedList: bloodList.value,
|
||||
xkTransfuseApplyBloodbreedList: bloodList.value.map(item => {
|
||||
return {
|
||||
...item,
|
||||
bill_no: xkTransfuseApply.value.bill_no
|
||||
}
|
||||
}),
|
||||
xkTransfuseApplyTestitemList: lisProject.value,
|
||||
}
|
||||
|
||||
@ -1118,12 +1123,11 @@ const handleSubmit = async () => {
|
||||
if (res2.code == 0) {
|
||||
// 保存申请单
|
||||
saveApplyInfo(data).then((res3) => {
|
||||
if (res3.data) {
|
||||
if (res3.code == 0 && res3.data) {
|
||||
getQueryOneInfo(res3.data)
|
||||
getSqdList()
|
||||
}
|
||||
if (res3.code == 0) {
|
||||
ElMessage.success('保存成功')
|
||||
sendMsg(res3.data)
|
||||
}
|
||||
})
|
||||
} else if (res2.code == 1) {
|
||||
@ -1145,12 +1149,11 @@ const handleSubmit = async () => {
|
||||
}).catch(() => {
|
||||
// 保存申请单
|
||||
saveApplyInfo(data).then((res3) => {
|
||||
if (res3.data) {
|
||||
if (res3.code == 0 && res3.data) {
|
||||
getQueryOneInfo(res3.data)
|
||||
getSqdList()
|
||||
}
|
||||
if (res3.code == 0) {
|
||||
ElMessage.success('保存成功')
|
||||
sendMsg(res3.data)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -1178,21 +1181,43 @@ const indicationComfirm = (arr) => {
|
||||
xkPatientInfo: tempPatient,
|
||||
xkTransfuseApply: xkTransfuseApply.value
|
||||
},
|
||||
xkTransfuseApplyBloodbreedList: bloodList.value,
|
||||
xkTransfuseApplyBloodbreedList: bloodList.value.map(item => {
|
||||
return {
|
||||
...item,
|
||||
bill_no: xkTransfuseApply.value.bill_no
|
||||
}
|
||||
}),
|
||||
xkTransfuseApplyTestitemList: lisProject.value,
|
||||
xkTransfuseApplyCausalisList: arr
|
||||
}
|
||||
|
||||
saveApplyInfo(data).then((res) => {
|
||||
if (res.data) {
|
||||
if (res.code == 0 && res.data) {
|
||||
getQueryOneInfo(res.data)
|
||||
getSqdList()
|
||||
}
|
||||
if (res.code == 0) {
|
||||
sendMsg(res.data)
|
||||
ElMessage.success('保存成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const sendMsg = (billNo: string) => {
|
||||
const arr = bloodList.value.map(item => item.blood_breed)
|
||||
const breedMap = new Map(bloodBreed.value.map(v => [v.value, v.label]));
|
||||
|
||||
const bloodText = arr.map(item => breedMap.get(item) ?? item).join('、');
|
||||
const data = {
|
||||
type: "APPLY_NOTICE",
|
||||
from: userInfo.name,
|
||||
to: "ALL",
|
||||
title: "你好",
|
||||
content: `${xkPatientInfo.value.patient_name}病人的申请单:${billNo},申请品种:${bloodText}, 保存成功, 请及时审核!!!`,
|
||||
data: {
|
||||
billNo,
|
||||
}
|
||||
}
|
||||
sendWebSocketMessage(data)
|
||||
}
|
||||
//获取申请单信息
|
||||
const getQueryOneInfo = (bill_no: string) => {
|
||||
queryApplyOne({ billNo: bill_no }).then((res) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user