Профиль хирургии

This commit is contained in:
brusnitsyn
2026-03-25 17:37:32 +09:00
parent 52a80ccd3b
commit f566ab96df
75 changed files with 3841 additions and 1009 deletions

View File

@@ -0,0 +1,98 @@
<script setup>
import {NModal, NThing, NTag, NButton, NSpace, NDrawer, NDrawerContent, NInput} from "naive-ui";
import {ref, watch} from "vue";
import {TbClockCheck, TbCalendar, TbTag} from 'vue-icons-plus/tb'
import {format} from "date-fns";
import AppPanel from "../../../Components/AppPanel.vue";
const props = defineProps({
historyId: {
type: Number,
required: true
}
})
const show = defineModel('show')
const operations = ref(null)
const showInfoDrawer = ref(false)
const showedOperation = ref(null)
const onShowInfoDrawer = (operation) => {
showedOperation.value = {...operation}
showInfoDrawer.value = true
}
watch(() => props.historyId, (value) => {
console.log(value)
axios.get(`/api/mis/operations`, {
params: {
historyId: value
}
})
.then(response => {
operations.value = response.data
})
})
</script>
<template>
<NModal v-model:show="show"
title="Операции"
:mask-closable="false"
class="max-w-xl overflow-clip min-h-[420px] max-h-[600px]"
preset="card"
id="modal-operation"
>
<div class="h-full flex flex-col gap-y-4">
<NThing v-for="operation in operations">
<template #header>
Операция {{operation.num}}
</template>
<template #header-extra>
<NTag size="small" type="info" round :bordered="false">
<template #icon>
<TbClockCheck size="16" />
</template>
{{operation.duration}} мин.
</NTag>
</template>
<template #description>
<NSpace align="center" size="small">
<NTag type="success" round :bordered="false">
<template #icon>
<TbCalendar size="18" />
</template>
{{format(operation.startAt, 'dd.MM.yyyy')}}
</NTag>
<NTag type="success" round :bordered="false">
<template #icon>
<TbTag size="18" />
</template>
{{operation.service.ServiceMedicalCode}}
</NTag>
</NSpace>
</template>
{{operation.service.ServiceMedicalName}}
<template #action>
<NButton type="primary" size="small" @click="onShowInfoDrawer(operation)">
Просмотреть
</NButton>
</template>
</NThing>
</div>
<NDrawer v-model:show="showInfoDrawer" width="100%" height="100%" to="#modal-operation" placement="bottom">
<NDrawerContent closable>
<template #header>
Операция {{showedOperation.num}}
</template>
<AppPanel no-padding header="Описание" class="h-full!">
<NInput type="textarea" :resizable="false" class="h-full" readonly v-model:value="showedOperation.description" />
</AppPanel>
</NDrawerContent>
</NDrawer>
</NModal>
</template>
<style scoped>
</style>

View File

@@ -1,5 +1,5 @@
<script setup>
import {NCard, NSkeleton, NSpace, NFlex, NFormItem, NForm, NInputNumber, NStatistic} from "naive-ui";
import {NCard, NSkeleton, NSpace, NFlex, NFormItem, NForm, NInputNumber, NStatistic, NNumberAnimation} from "naive-ui";
import {useReportStore} from "../../../Stores/report.js";
import {useAuthStore} from "../../../Stores/auth.js";
import {computed, onMounted, ref, watch} from "vue";
@@ -58,17 +58,20 @@ watch(() => formRef.value, (nv) => {
</NForm>
</NCard>
<NSpace :wrap="false">
<NCard class="min-w-[120px] max-w-[120px] min-h-[100px] max-h-[102px] h-full"
<NCard class="min-w-[120px] min-h-[100px] max-h-[102px] h-full"
style="--n-padding-top: 0; --n-padding-left: 0; --n-padding-bottom: 0; --n-padding-right: 0;"
>
<div class="w-full h-full flex flex items-center justify-center">
<NStatistic label="Умерло" :value="reportStore.reportInfo?.department?.deadCount" />
<NStatistic label="Умерло">
<NNumberAnimation :from="0" :to="reportStore.reportInfo?.department?.deadCount" />
</NStatistic>
</div>
</NCard>
<NCard class="min-w-[120px] max-w-[120px] min-h-[100px] max-h-[102px] h-full"
<NCard class="min-w-[120px] min-h-[100px] max-h-[102px] h-full"
style="--n-padding-top: 0; --n-padding-bottom: 0; --n-padding-left: 8px; --n-padding-right: 8px;">
<div class="w-full h-full flex flex items-center justify-center">
<NStatistic :value="reportStore.reportInfo.department.percentDead">
<NStatistic>
<NNumberAnimation :from="0" :to="reportStore.reportInfo?.department?.percentDead" />
<template #label>
<div class="flex flex-col">
<span>Летальность</span>
@@ -78,10 +81,11 @@ watch(() => formRef.value, (nv) => {
</NStatistic>
</div>
</NCard>
<NCard class="min-w-[120px] max-w-[120px] min-h-[100px] max-h-[102px] h-full"
<NCard class="min-w-[120px] min-h-[100px] max-h-[102px] h-full"
style="--n-padding-top: 0; --n-padding-bottom: 0; --n-padding-left: 8px; --n-padding-right: 8px;">
<div class="w-full h-full flex flex items-center justify-center">
<NStatistic :value="reportStore.reportInfo?.department?.surgicalCount[1]">
<NStatistic>
<NNumberAnimation :from="0" :to="reportStore.reportInfo?.department?.surgicalCount[1]" />
<template #label>
<div class="flex flex-col">
<span>Операций</span>
@@ -89,7 +93,7 @@ watch(() => formRef.value, (nv) => {
</div>
</template>
<template #suffix>
/ {{ reportStore.reportInfo?.department?.surgicalCount[0] }}
/ <NNumberAnimation :from="0" :to="reportStore.reportInfo?.department?.surgicalCount[0]" />
</template>
</NStatistic>
</div>

View File

@@ -1,5 +1,5 @@
<script setup>
import {NSkeleton, NStatistic, NRow, NCol, NSpace, NCard, NButton, NTag, NDatePicker, NFlex, NSelect, NText, NH2} from "naive-ui";
import {NSkeleton, NStatistic, NRow, NCol, NSpace, NCard, NButton, NTag, NDatePicker, NFlex, NSelect, NText, NNumberAnimation} from "naive-ui";
import {computed, ref} from "vue";
import {format} from "date-fns";
import {useNow} from "@vueuse/core";
@@ -57,11 +57,11 @@ const currentDate = computed(() => {
<NFlex vertical>
<div class="grid grid-cols-[auto_1fr_auto] items-center">
<NSpace align="center">
<NTag v-if="isFillableMode" type="info" :bordered="false">
<NTag type="info" :bordered="false">
{{ reportStore.reportInfo.department.department_name }}
</NTag>
<DepartmentSelect v-if="isReadonlyMode" />
<NTag v-if="reportStore.reportInfo.report.userName" type="warning">
<!-- <DepartmentSelect v-if="isReadonlyMode" />-->
<NTag v-if="authStore.isDoctor && reportStore.reportInfo.report.isActiveSendButton" type="warning">
Ответственный: {{ reportStore.reportInfo.report.userName }}
</NTag>
</NSpace>
@@ -69,6 +69,7 @@ const currentDate = computed(() => {
<div class="col-3 w-full">
<DatePickerQuery class="text-lg!"
:is-head-or-admin="reportStore.reportInfo.report?.isHeadOrAdmin"
:is-show-current-date-switch="reportStore.reportInfo.report?.isHeadOrAdmin"
v-model:date="reportStore.timestampCurrentRange"
:is-one-day="reportStore.reportInfo.report?.isOneDay" />
</div>
@@ -80,7 +81,8 @@ const currentDate = computed(() => {
<NStatistic label="Коек">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department?.beds }}</span>
<NNumberAnimation v-else :from="0" :to="reportStore.reportInfo?.department?.beds" />
<!-- <span v-else>{{ reportStore.reportInfo?.department?.beds }}</span>-->
</template>
</NStatistic>
</NCol>
@@ -88,7 +90,10 @@ const currentDate = computed(() => {
<NStatistic label="Загруженность">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department?.percentLoadedBeds }}%</span>
<div v-else>
<NNumberAnimation :from="0" :to="reportStore.reportInfo?.department?.percentLoadedBeds" />
<span>%</span>
</div>
</template>
</NStatistic>
</NCol>
@@ -96,7 +101,7 @@ const currentDate = computed(() => {
<NStatistic label="Поступило">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department?.recipientCount }}</span>
<NNumberAnimation v-else :from="0" :to="reportStore.reportInfo?.department?.recipientCount" />
</template>
</NStatistic>
</NCol>
@@ -104,7 +109,7 @@ const currentDate = computed(() => {
<NStatistic label="Выбыло">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department?.extractCount }}</span>
<NNumberAnimation v-else :from="0" :to="reportStore.reportInfo?.department?.extractCount" />
</template>
</NStatistic>
</NCol>
@@ -112,7 +117,7 @@ const currentDate = computed(() => {
<NStatistic label="Состоит">
<template #default>
<NSkeleton v-if="reportStore.isLoadReportInfo" round class="w-[70px]! mt-2 h-[29px]!" />
<span v-else>{{ reportStore.reportInfo?.department?.currentCount }}</span>
<NNumberAnimation v-else :from="0" :to="reportStore.reportInfo?.department?.currentCount" />
</template>
</NStatistic>
</NCol>

View File

@@ -20,6 +20,17 @@ const handleItemDragged = (event) => {
// console.log('Начато перетаскивание:', event)
}
const handleCollapseItemDragEnter = (e, itemName) => {
// Проверяем, активно ли перетаскивание и наведение происходит на нужную секцию ("3")
if (reportStore.isDragActive && itemName === '3') {
// Открываем секцию, если она еще не открыта
if (!reportStore.openedCollapsible.includes(itemName)) {
// Добавляем имя в массив открытых, если используется массив
reportStore.openedCollapsible = [...reportStore.openedCollapsible, itemName];
}
}
}
// Обработка события drop
const handleItemDropped = (event) => {
const { item, fromStatus, toStatus } = event
@@ -62,15 +73,17 @@ const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
@item-dragged="handleItemDragged"
/>
</NCollapseItem>
<NCollapseItem name="3">
<NCollapseItem name="3" @dragenter="(e) => handleCollapseItemDragEnter(e, '3')">
<template #header>
<ReportSectionHeader title="Находятся на контроле" status="observation" />
</template>
<NFlex :size="12">
<ReportSectionItem status="observation"
id="observation-table"
@item-dragged="handleItemDragged"
@item-dropped="handleItemDropped"
is-removable
is-draggable-drop
/>
<NAlert v-if="isFillableMode" type="info" class="w-full">
Перетаскивайте строки из верхних таблиц, что бы добавить в наблюдение

View File

@@ -1,20 +1,25 @@
<script setup>
import {NIcon, NText, NDataTable, NButton, NBadge, NTabs, NTabPane} from "naive-ui";
import {NIcon, NText, NDataTable, NButton, NBadge, NTabs, NTabPane, NPopover, NEllipsis, NTooltip} from "naive-ui";
import {useReportStore} from "../../../Stores/report.js";
import {computed, h, onMounted, ref, watch} from "vue";
import { VueDraggableNext } from 'vue-draggable-next'
import {storeToRefs} from "pinia";
import {TbGripVertical, TbEye} from "vue-icons-plus/tb";
import {TbGripVertical, TbEye, TbExternalLink} from "vue-icons-plus/tb";
import MoveModalComment from "./MoveModalComment.vue";
import OperationInfoModal from "./OperationInfoModal.vue";
const props = defineProps({
id: {
type: String,
default: null
},
mode: {
type: String,
default: 'fillable' // 'fillable', 'readonly'
},
keys: {
type: Array,
default: ['num', 'fullname', 'age', 'birth_date', 'mkb.ds']
default: ['num', 'fullname', 'age', 'birth_date']
},
status: {
type: String,
@@ -28,6 +33,10 @@ const props = defineProps({
type: Boolean,
default: false
},
isDraggableDrop: {
type: Boolean,
default: false
},
accentIds: {
type: Array,
default: []
@@ -48,9 +57,15 @@ const baseColumns = reportStore.getColumnsByKey(props.keys)
const data = ref([])
const isLoading = ref(true)
const showMoveModal = ref(false)
const showOperationInfoModal = ref(false)
const currentHistory = ref(null)
const latestDropItem = ref(null)
const activePatient = ref(null)
const hasDisabledEdit = computed(() => {
return reportStore.reportInfo.report.isActiveSendButton === false
})
// Добавляем drag колонку если режим fillable
const columns = computed(() => {
// if (!isFillableMode.value) return baseColumns
@@ -59,23 +74,29 @@ const columns = computed(() => {
const dragColumn = {
title: '',
key: 'drag',
key: 'goToMis',
width: 40,
render: (row) => h(
'div',
NTooltip,
{},
{
style: {
cursor: 'grab',
textAlign: 'center',
userSelect: 'none'
}
},
[
h(NIcon, {
depth: '2',
component: TbGripVertical
}, [])
]
trigger: () => h('div',
{
style: {
cursor: 'hand',
textAlign: 'center',
userSelect: 'none'
},
onClick: () => {
window.open(`https://stationar.amurzdrav.ru/prod/statist/edit/card/${row.id}`, '_blank')
}
},
[
h(NIcon, { depth: 2 }, h(TbExternalLink))
]
),
default: () => 'Перейти в карту'
}
)
}
@@ -86,11 +107,15 @@ const columns = computed(() => {
NButton,
{
text: true,
disabled: hasDisabledEdit.value,
onClick: () => {
axios.post('/api/report/observation/remove', {
id: row.id
}).then(async () => {
await fetchPatients()
const indexRemove = patientsData.value['observation'].findIndex(itm => itm.id === row.id)
if (indexRemove !== -1) {
patientsData.value['observation'].splice(indexRemove, 1)
}
})
}
},
@@ -137,8 +162,38 @@ const columns = computed(() => {
newColumns.push(fillableColumn)
}
if (props.isDraggable) newColumns.push(dragColumn)
newColumns.push(dragColumn)
newColumns.push(...baseColumns)
newColumns.push({
title: 'Диагноз',
key: 'ds',
render: (row) => {
if (row.mkb.ds !== null && row.mkb.name !== null) {
return h(NPopover, {
placement: 'top',
arrow: false
}, {
trigger: h(
'div',
{
class: 'cursor-help w-full',
style: 'padding: 8px;'
},
row.mkb.ds
),
default: row.mkb.name
})
} else {
return row.mkb.ds
}
},
cellProps: (rowData, rowIndex) => {
const styles = ['--n-td-padding: 0;']
return {
'style': styles
}
}
})
if (props.isRemovable) newColumns.push(removeColumn)
if (props.status === 'emergency' || props.status === 'plan') {
@@ -147,16 +202,32 @@ const columns = computed(() => {
key: 'operations',
render: (row) => row.operations?.length ?
h(
NText,
{},
[
row.operations.map(itm => {
return `${itm.code}; `
})
]
) : h('div', {}, '-'),
ellipsis: {
tooltip: true
NPopover,
{
placement: 'top',
arrow: false
},
{
trigger: () => h(
'div',
{
class: 'underline decoration-dashed cursor-pointer',
style: 'padding: 8px;',
onClick: () => {
currentHistory.value = row.id
showOperationInfoModal.value = true
},
},
h(NEllipsis, {tooltip: false}, row.operations.map(itm => `${itm.code}; `).join(''))
),
default: () => row.operations.map(itm =>`${itm.name}; `).join(''),
}
) : h('div', {style: 'padding: 8px;',}, '-'),
cellProps: (rowData, rowIndex) => {
const styles = ['--n-td-padding: 0;']
return {
'style': styles
}
}
}
newColumns.push(operationColumn)
@@ -176,7 +247,19 @@ const columns = computed(() => {
return newColumns
})
const isCursorOverTable = (e) => {
const rect = tableRef.value.$el.getBoundingClientRect()
return (
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom
)
}
const handleDragStart = (e, row) => {
if (hasDisabledEdit.value) return
// Устанавливаем данные о перетаскиваемом элементе
e.dataTransfer.setData('application/json', JSON.stringify({
row: row,
@@ -186,6 +269,9 @@ const handleDragStart = (e, row) => {
const rowElement = e.target
// Устанавливаем флаг перетаскивания в store
reportStore.isDragActive = true
// Эмитим событие для родителя
emit('item-dragged', { row, fromStatus: props.status })
@@ -194,17 +280,33 @@ const handleDragStart = (e, row) => {
}
const handleDragEnd = (e) => {
if (hasDisabledEdit.value) return
// Устанавливаем флаг перетаскивания в store
reportStore.isDragActive = false
if (e.target) {
e.target.classList.remove('dragging')
}
}
const handleDragOver = (e) => {
if (hasDisabledEdit.value) return
// console.log(e)
e.preventDefault()
}
const handleDrop = (e) => {
e.preventDefault()
if (hasDisabledEdit.value) return
if (props.isDraggableDrop === false) return
// Проверяем, действительно ли курсор над таблицей
if (!isCursorOverTable(e)) {
return
}
try {
const dragData = JSON.parse(e.dataTransfer.getData('application/json'))
@@ -258,15 +360,15 @@ function rowProps(row) {
handleDragStart(e, row)
},
onDragend: (e) => {
if (!props.isDraggable) return
if (!props.isDraggableDrop) return
handleDragEnd(e)
},
onDragover: (e) => {
if (!props.isDraggable) return
if (!props.isDraggableDrop) return
handleDragOver(e)
},
onDrop: (e) => {
if (!props.isDraggable) return
if (!props.isDraggableDrop) return
handleDrop(e)
}
}
@@ -278,6 +380,7 @@ onMounted(async () => {
</script>
<template>
<div>
<NDataTable :columns="columns"
ref="tableRef"
:data="patientsData[status]"
@@ -293,6 +396,8 @@ onMounted(async () => {
</NDataTable>
<MoveModalComment v-model:show="showMoveModal" :patient-id="latestDropItem?.id" />
<OperationInfoModal v-model:show="showOperationInfoModal" :history-id="currentHistory" />
</div>
</template>
<style scoped>

View File

@@ -3,7 +3,7 @@ import {NModal, NList, NListItem, NThing, NAvatar, NIcon, NDrawer, NDrawerConten
NText, NDivider, NForm, NFormItem, NInput, NFlex, NButton, NScrollbar, NEmpty
} from 'naive-ui'
import {useReportStore} from "../../../Stores/report.js";
import {ref} from "vue";
import {computed, ref} from "vue";
const open = defineModel('open')
import { TbAlertCircle, TbPencil, TbTrashX, TbCirclePlus, TbCheck, TbX } from 'vue-icons-plus/tb'
import {format, isValid} from "date-fns";
@@ -43,6 +43,10 @@ const onEditEvent = (event) => {
createDrawerShow.value = true
}
const hasDisableAddButton = computed(() => {
return reportStore.reportInfo.report.isActiveSendButton === false
})
const onDeleteEvent = (event) => {
const indexOfDelete = reportStore.unwantedEvents.findIndex(itm => itm === event)
@@ -115,14 +119,14 @@ const onBeforeLeaveModal = () => {
</NText>
<template #action>
<NFlex align="center">
<NButton secondary size="small" @click="onEditEvent(event)">
<NButton secondary size="small" @click="onEditEvent(event)" :disabled="hasDisableAddButton">
<template #icon>
<TbPencil />
</template>
Редактировать
</NButton>
<NDivider vertical />
<NButton type="error" secondary size="small" @click="onDeleteEvent(event)">
<NButton type="error" secondary size="small" @click="onDeleteEvent(event)" :disabled="hasDisableAddButton">
<template #icon>
<TbTrashX />
</template>
@@ -139,7 +143,7 @@ const onBeforeLeaveModal = () => {
<div class="h-full flex items-center justify-center">
<NEmpty description="Нежелательных событий не найдено!">
<template #extra>
<NButton type="primary" secondary @click="onCreateEvent()" size="small">
<NButton type="primary" secondary @click="onCreateEvent()" size="small" :disabled="hasDisableAddButton">
<template #icon>
<TbCirclePlus />
</template>
@@ -152,7 +156,7 @@ const onBeforeLeaveModal = () => {
<template #action>
<NFlex id="modal-action" align="center" justify="space-between">
<NButton type="primary" secondary @click="onCreateEvent()">
<NButton type="primary" secondary @click="onCreateEvent()" :disabled="hasDisableAddButton">
<template #icon>
<TbCirclePlus />
</template>