Files
onboard/resources/js/Pages/Report/Components/ReportSectionItem.vue
2026-03-25 17:37:32 +09:00

410 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
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, 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']
},
status: {
type: String,
default: null // 'plan'
},
isRemovable: {
type: Boolean,
default: false
},
isDraggable: {
type: Boolean,
default: false
},
isDraggableDrop: {
type: Boolean,
default: false
},
accentIds: {
type: Array,
default: []
},
})
const isFillableMode = computed(() => props.mode.toLowerCase() === 'fillable')
const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
const tableRef = ref()
const emit = defineEmits(['item-dragged', 'item-dropped'])
const reportStore = useReportStore()
const {patientsData} = storeToRefs(reportStore)
// Получаем базовые колонки
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
const newColumns = []
const dragColumn = {
title: '',
key: 'goToMis',
width: 40,
render: (row) => h(
NTooltip,
{},
{
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: () => 'Перейти в карту'
}
)
}
const removeColumn = {
title: '',
key: 'remove',
render: (row) => h(
NButton,
{
text: true,
disabled: hasDisabledEdit.value,
onClick: () => {
axios.post('/api/report/observation/remove', {
id: row.id
}).then(async () => {
const indexRemove = patientsData.value['observation'].findIndex(itm => itm.id === row.id)
if (indexRemove !== -1) {
patientsData.value['observation'].splice(indexRemove, 1)
}
})
}
},
[
'Снять с наблюдения'
]
)
}
const expandColumn = {
title: '',
width: '30',
render: (rowData) => {
return h(
NIcon,
{
onClick: () => {
latestDropItem.value = rowData
showMoveModal.value = true
}
},
{
default: h(TbEye)
}
)
}
}
const fillableColumn = {
title: '',
key: 'fillable',
width: '20',
render: (row) => h(
NBadge,
{
dot: true,
color: (row.comment && row.comment.trim()) ? '#7fe7c4' : '#e88080'
}
)
}
if (props.status === 'observation') {
newColumns.push(expandColumn)
newColumns.push(fillableColumn)
}
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') {
const operationColumn = {
title: 'Операции',
key: 'operations',
render: (row) => row.operations?.length ?
h(
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)
}
if (props.status === 'outcome') {
const typeColumn = {
title: 'Причина',
key: 'outcome_type',
ellipsis: {
tooltip: true
}
}
newColumns.push(typeColumn)
}
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,
fromStatus: props.status
}))
e.dataTransfer.effectAllowed = 'copy'
const rowElement = e.target
// Устанавливаем флаг перетаскивания в store
reportStore.isDragActive = true
// Эмитим событие для родителя
emit('item-dragged', { row, fromStatus: props.status })
// Добавляем класс для визуальной обратной связи
e.target.classList.add('dragging')
}
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'))
// Эмитим событие для родителя
emit('item-dropped', {
item: dragData.row,
fromStatus: dragData.fromStatus,
toStatus: props.status
})
latestDropItem.value = dragData.row
showMoveModal.value = true
} catch (error) {
console.error('Drop error:', error)
}
}
const fetchPatients = async () => {
isLoading.value = true
const data = {
status: props.status,
startAt: reportStore.timestampCurrentRange[0],
endAt: reportStore.timestampCurrentRange[1],
departmentId: reportStore.reportInfo.department.department_id
}
await axios.post('/api/mis/patients', data).then((res) => {
patientsData.value[props.status] = reportStore.addRowNumbers(res.data)
}).finally(() => {
isLoading.value = false
})
}
function rowProps(row) {
const style = []
const classes = []
style.push(props.isDraggable ? 'cursor: grab;' : 'cursor: arrow;')
if (props.accentIds.length) {
if (props.accentIds.includes(row.id)) {
style.push('--n-merged-td-color: #047857')
}
}
return {
draggable: props.isDraggable,
style: style,
onDragstart: (e) => {
if (!props.isDraggable) return
handleDragStart(e, row)
},
onDragend: (e) => {
if (!props.isDraggableDrop) return
handleDragEnd(e)
},
onDragover: (e) => {
if (!props.isDraggableDrop) return
handleDragOver(e)
},
onDrop: (e) => {
if (!props.isDraggableDrop) return
handleDrop(e)
}
}
}
onMounted(async () => {
await fetchPatients()
})
</script>
<template>
<div>
<NDataTable :columns="columns"
ref="tableRef"
:data="patientsData[status]"
size="small"
@drop="handleDrop"
@dragover="handleDragOver"
:loading="isLoading"
max-height="200"
min-height="200"
:row-props="rowProps"
:row-key="(row, index) => row.id"
class="text-sm!">
</NDataTable>
<MoveModalComment v-model:show="showMoveModal" :patient-id="latestDropItem?.id" />
<OperationInfoModal v-model:show="showOperationInfoModal" :history-id="currentHistory" />
</div>
</template>
<style scoped>
:deep(.n-data-table-th),
:deep(.n-data-table-td) {
white-space: nowrap !important;
font-size: var(--n-font-size);
}
</style>