* добавил исход спец контингенту
* оптимизация обновления при редактировании спец контингента * добавил поддержку заключительных диагнозов * изменил определение законченной операции * добавил поддержку исхода операции * добавил определение отмены для операции через назначение * работа над диапазонами календарей, подсчет статистики * добавил статусы отчетов и подкорректировал привязку спец контингента к отчету * добавил новые сервисы для будущего кеширования * частичное разделение логики подсчета пациентов
This commit is contained in:
19
resources/js/Components/AppGrid.vue
Normal file
19
resources/js/Components/AppGrid.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import { NGrid } from 'naive-ui'
|
||||
const props = defineProps({
|
||||
cols: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NGrid :cols="cols">
|
||||
<slot />
|
||||
</NGrid>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
19
resources/js/Components/AppGridItem.vue
Normal file
19
resources/js/Components/AppGridItem.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
import { NGi } from 'naive-ui'
|
||||
const props = defineProps({
|
||||
span: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NGi :span="span">
|
||||
<slot />
|
||||
</NGi>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -6,6 +6,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
headerIncludeBody: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
feedback: {
|
||||
type: String,
|
||||
default: ''
|
||||
@@ -25,6 +29,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const hasHeader = computed(() => props.header.trim().length > 0)
|
||||
const hasHeaderInOutside = computed(() => hasHeader.value && !props.headerIncludeBody)
|
||||
const hasFeedback = computed(() => props.feedback.trim().length > 0)
|
||||
const hasMinH = computed(() => props.minH.trim().length > 0 || Number.isInteger(props.minH))
|
||||
const hasMaxH = computed(() => props.maxH.trim().length > 0 || Number.isInteger(props.maxH))
|
||||
@@ -55,8 +60,11 @@ watch(() => [props.minH, props.maxH], ([minH, maxH]) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFormItem :show-label="hasHeader" :label="header" :show-feedback="hasFeedback" :feedback="feedback">
|
||||
<NFormItem :show-label="hasHeaderInOutside" :label="header" :show-feedback="hasFeedback" :feedback="feedback">
|
||||
<NCard :class="noPadding ? 'no-padding h-full' : ''">
|
||||
<template v-if="!hasHeaderInOutside" #header>
|
||||
{{ header }}
|
||||
</template>
|
||||
<NScrollbar :style="styles">
|
||||
<slot />
|
||||
</NScrollbar>
|
||||
|
||||
@@ -27,6 +27,8 @@ const form = reactive({
|
||||
birth_date: null,
|
||||
admitted_at: null,
|
||||
patient_kind: 'plan',
|
||||
manual_status: 'current',
|
||||
outcome_at: null,
|
||||
diagnosis_code: '',
|
||||
diagnosis_name: '',
|
||||
})
|
||||
@@ -56,6 +58,12 @@ const patientKindOptions = [
|
||||
{label: 'Экстренно', value: 'emergency'},
|
||||
]
|
||||
|
||||
const manualStatusOptions = [
|
||||
{label: 'Состоит', value: 'current'},
|
||||
{label: 'Выписан', value: 'discharged'},
|
||||
{label: 'Умер', value: 'deceased'},
|
||||
]
|
||||
|
||||
const normalizeSpaces = (value) => value.replace(/\s+/g, ' ').trim()
|
||||
const pad = (value) => String(value).padStart(2, '0')
|
||||
const formatLocalDate = (timestamp) => {
|
||||
@@ -76,6 +84,11 @@ const parseDateToTimestamp = (value) => {
|
||||
}
|
||||
const parseDateTimeToTimestamp = (value) => {
|
||||
if (!value) return null
|
||||
const nativeDate = new Date(value)
|
||||
if (!Number.isNaN(nativeDate.getTime())) {
|
||||
return nativeDate.getTime()
|
||||
}
|
||||
|
||||
const [datePart, timePart] = String(value).split(' ')
|
||||
const dateTimestamp = parseDateToTimestamp(datePart)
|
||||
if (!dateTimestamp) return null
|
||||
@@ -90,6 +103,8 @@ const hydrateForm = () => {
|
||||
form.birth_date = parseDateToTimestamp(props.patient?.birth_date)
|
||||
form.admitted_at = parseDateTimeToTimestamp(props.patient?.admitted_at)
|
||||
form.patient_kind = props.patient?.patient_kind ?? 'plan'
|
||||
form.manual_status = props.patient?.outcome_type ?? 'current'
|
||||
form.outcome_at = parseDateTimeToTimestamp(props.patient?.outcome_date)
|
||||
form.diagnosis_code = props.patient?.mkb?.ds ?? ''
|
||||
form.diagnosis_name = props.patient?.mkb?.name ?? ''
|
||||
|
||||
@@ -179,11 +194,21 @@ const submit = async () => {
|
||||
birth_date: formatLocalDate(form.birth_date),
|
||||
admitted_at: form.admitted_at ? formatLocalDateTime(form.admitted_at) : null,
|
||||
patient_kind: form.patient_kind,
|
||||
manual_status: form.manual_status,
|
||||
outcome_at: form.manual_status === 'current'
|
||||
? null
|
||||
: (form.outcome_at ? formatLocalDateTime(form.outcome_at) : null),
|
||||
diagnosis_code: form.diagnosis_code ? normalizeSpaces(form.diagnosis_code).toUpperCase() : null,
|
||||
diagnosis_name: form.diagnosis_name ? normalizeSpaces(form.diagnosis_name) : null,
|
||||
},
|
||||
{
|
||||
reloadStatuses: [props.sourceStatus, `special-${form.patient_kind}`],
|
||||
reloadStatuses: [
|
||||
props.sourceStatus,
|
||||
`special-${form.patient_kind}`,
|
||||
'special-outcome-discharged',
|
||||
'special-outcome-deceased',
|
||||
'special-outcome-transferred',
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -204,12 +229,18 @@ const submit = async () => {
|
||||
<NFormItem label="Тип пациента" path="patient_kind">
|
||||
<NSelect v-model:value="form.patient_kind" :options="patientKindOptions" />
|
||||
</NFormItem>
|
||||
<NFormItem label="Статус">
|
||||
<NSelect v-model:value="form.manual_status" :options="manualStatusOptions" />
|
||||
</NFormItem>
|
||||
<NFormItem label="Дата рождения" path="birth_date">
|
||||
<NDatePicker v-model:value="form.birth_date" type="date" class="w-full" />
|
||||
</NFormItem>
|
||||
<NFormItem label="Дата и время поступления">
|
||||
<NDatePicker v-model:value="form.admitted_at" type="datetime" class="w-full" clearable />
|
||||
</NFormItem>
|
||||
<NFormItem v-if="form.manual_status !== 'current'" label="Дата и время исхода">
|
||||
<NDatePicker v-model:value="form.outcome_at" type="datetime" class="w-full" clearable />
|
||||
</NFormItem>
|
||||
<NFormItem label="Диагноз (МКБ)">
|
||||
<NSelect
|
||||
v-model:value="form.diagnosis_code"
|
||||
|
||||
@@ -134,6 +134,10 @@ const submit = async () => {
|
||||
try {
|
||||
await reportStore.createManualPatient({
|
||||
departmentId: reportStore.reportInfo.department.department_id,
|
||||
report_id: reportStore.reportInfo?.report?.report_id ?? null,
|
||||
startAt: reportStore.reportInfo?.dates?.startAt,
|
||||
endAt: reportStore.reportInfo?.dates?.endAt,
|
||||
user_id: reportStore.reportInfo?.report?.userId ?? null,
|
||||
full_name: normalizeSpaces(form.full_name),
|
||||
birth_date: formatLocalDate(form.birth_date),
|
||||
patient_kind: props.patientKind,
|
||||
|
||||
@@ -25,23 +25,44 @@ const onSubmit = () => {
|
||||
// reportStore.sendReportForm()
|
||||
}
|
||||
|
||||
const onPublish = () => {
|
||||
reportStore.reportFormRef?.validate((errors) => {
|
||||
if (!errors) reportStore.sendReportForm({ status: 'submitted' })
|
||||
else window.$message.error('Ошибка отправки отчета')
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex vertical class="max-w-6xl mx-auto mt-6 mb-4 w-full">
|
||||
<NAlert type="warning" v-if="reportStore.reportInfo.report?.message">
|
||||
{{ reportStore.reportInfo.report.message }}
|
||||
</NAlert>
|
||||
<NFlex v-if="reportStore.reportInfo.report?.message || reportStore.reportInfo.report?.statusMessage" :size="12">
|
||||
<NAlert class="flex-1" type="warning" v-if="reportStore.reportInfo.report?.message">
|
||||
{{ reportStore.reportInfo.report.message }}
|
||||
</NAlert>
|
||||
<NAlert class="flex-1" type="info" v-if="reportStore.reportInfo.report?.statusMessage">
|
||||
{{ reportStore.reportInfo.report.statusMessage }}
|
||||
</NAlert>
|
||||
</NFlex>
|
||||
<ReportHeader :mode="mode" />
|
||||
|
||||
<ReportFormInput />
|
||||
|
||||
<ReportSection label="Планово" />
|
||||
|
||||
<NButton v-if="reportStore.reportInfo?.report?.isActiveSendButton" secondary size="large" @click="onSubmit">
|
||||
Сохранить отчет
|
||||
</NButton>
|
||||
<NFlex v-if="reportStore.reportInfo?.report?.isActiveSendButton" :size="12">
|
||||
<NButton secondary size="large" @click="onSubmit">
|
||||
Сохранить отчет
|
||||
</NButton>
|
||||
<NButton
|
||||
v-if="reportStore.reportInfo?.report?.canPublish || !reportStore.reportInfo?.report?.report_id"
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="onPublish"
|
||||
>
|
||||
Опубликовать
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -217,14 +217,6 @@ watch(
|
||||
</NAlert>
|
||||
</NFlex>
|
||||
</NCollapseItem>
|
||||
<NCollapseItem name="4">
|
||||
<template #header>
|
||||
<ReportSectionHeader title="Находятся в реанимации" status="special-reanimation" />
|
||||
</template>
|
||||
<ReportSectionItem status="special-reanimation"
|
||||
:enabled="activeRootTab === 'special' && reportStore.openedCollapsible.includes('4')"
|
||||
/>
|
||||
</NCollapseItem>
|
||||
<NCollapseItem name="5">
|
||||
<template #header>
|
||||
<ReportSectionHeader title="Выбывшие" status="special-outcome" />
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
import {computed, h, ref, watch} from "vue";
|
||||
import {storeToRefs} from "pinia";
|
||||
import {TbEye, TbExternalLink, TbPencil} from "vue-icons-plus/tb";
|
||||
import {TbEye, TbExternalLink, TbPencil, TbTrash} from "vue-icons-plus/tb";
|
||||
import MoveModalComment from "./MoveModalComment.vue";
|
||||
import OperationInfoModal from "./OperationInfoModal.vue";
|
||||
import ManualPatientOutcomeModal from "./ManualPatientOutcomeModal.vue";
|
||||
@@ -90,7 +90,11 @@ const activePatient = ref(null)
|
||||
const hasDisabledEdit = computed(() => {
|
||||
return !Boolean(reportStore.reportInfo?.report?.isActiveSendButton)
|
||||
})
|
||||
const canEditSpecial = computed(() => isSpecialStatus.value && !hasDisabledEdit.value)
|
||||
const canEditSpecial = computed(() => (
|
||||
isSpecialStatus.value
|
||||
&& !hasDisabledEdit.value
|
||||
&& baseStatus.value !== 'observation'
|
||||
))
|
||||
const statusState = computed(() => statusStates.value[props.status] ?? {
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
@@ -214,7 +218,7 @@ const columns = computed(() => {
|
||||
}
|
||||
},
|
||||
[
|
||||
'Снять с наблюдения'
|
||||
h(NIcon, {size: '16'}, h(TbTrash))
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
29
resources/js/Pages/TestIndex.vue
Normal file
29
resources/js/Pages/TestIndex.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup>
|
||||
import AppLayout from "../Layouts/AppLayout.vue"
|
||||
import AppPanel from "../Components/AppPanel.vue";
|
||||
import AppGridItem from "../Components/AppGridItem.vue";
|
||||
import AppGrid from "../Components/AppGrid.vue";
|
||||
import {NGi} from 'naive-ui'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout>
|
||||
<AppGrid cols="5">
|
||||
<NGi>
|
||||
<AppPanel header="Текст" header-include-body>
|
||||
panel
|
||||
</AppPanel>
|
||||
</NGi>
|
||||
<NGi>
|
||||
<AppPanel header="Текст" header-include-body>
|
||||
panel
|
||||
</AppPanel>
|
||||
</NGi>
|
||||
|
||||
</AppGrid>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -14,7 +14,6 @@ const patientStatuses = [
|
||||
'special-plan',
|
||||
'special-emergency',
|
||||
'special-observation',
|
||||
'special-reanimation',
|
||||
'special-outcome-discharged',
|
||||
'special-outcome-deceased',
|
||||
'special-outcome-transferred',
|
||||
@@ -260,6 +259,17 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
})
|
||||
})
|
||||
|
||||
const getSpecialStatusByPatient = (patient) => {
|
||||
if (!patient) return null
|
||||
|
||||
if (patient.is_current) {
|
||||
return patient.patient_kind ? `special-${patient.patient_kind}` : null
|
||||
}
|
||||
|
||||
if (!patient.outcome_type) return null
|
||||
return `special-outcome-${patient.outcome_type}`
|
||||
}
|
||||
|
||||
const getDataOnReportDate = async () => {
|
||||
await reloadReportPage()
|
||||
}
|
||||
@@ -276,19 +286,30 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
userId: reportInfo.value.report.userId,
|
||||
departmentId: reportInfo.value.department.department_id,
|
||||
reportId: reportInfo.value.report.report_id,
|
||||
status: reportInfo.value?.report?.status ?? 'draft',
|
||||
...assignForm
|
||||
}
|
||||
|
||||
router.post('/report', form, {
|
||||
onSuccess: () => {
|
||||
window.$message.success('Отчет сохранен')
|
||||
window.$message.success(form.status === 'submitted' ? 'Отчет опубликован' : 'Черновик сохранен')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const createManualPatient = async (payload) => {
|
||||
await axios.post('/api/report/manual-patients', payload)
|
||||
await reloadReportPage()
|
||||
const response = await axios.post('/api/report/manual-patients', payload)
|
||||
const reportId = response.data?.report_id
|
||||
|
||||
if (reportId) {
|
||||
reportInfo.value = {
|
||||
...reportInfo.value,
|
||||
report: {
|
||||
...(reportInfo.value?.report ?? {}),
|
||||
report_id: reportId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const status = `special-${payload.patient_kind}`
|
||||
|
||||
@@ -299,9 +320,13 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
}
|
||||
|
||||
const setManualPatientOutcome = async (departmentPatientId, payload) => {
|
||||
await axios.post(`/api/report/manual-patients/${departmentPatientId}/outcome`, payload)
|
||||
await reloadReportPage()
|
||||
await loadAllStatusCounts(true)
|
||||
const response = await axios.post(`/api/report/manual-patients/${departmentPatientId}/outcome`, payload)
|
||||
const targetStatus = getSpecialStatusByPatient(response.data)
|
||||
|
||||
await Promise.all([
|
||||
...(targetStatus ? [loadPatientsByStatus(targetStatus, { resetPage: true })] : []),
|
||||
loadAllStatusCounts(true),
|
||||
])
|
||||
}
|
||||
|
||||
const updateManualPatient = async (departmentPatientId, payload, options = {}) => {
|
||||
@@ -313,8 +338,6 @@ export const useReportStore = defineStore('reportStore', () => {
|
||||
|
||||
const reloadStatuses = Array.from(new Set((options.reloadStatuses ?? []).filter(Boolean)))
|
||||
|
||||
await reloadReportPage()
|
||||
|
||||
await Promise.all([
|
||||
...reloadStatuses.map((status) => loadPatientsByStatus(status, { resetPage: true })),
|
||||
loadAllStatusCounts(true),
|
||||
|
||||
Reference in New Issue
Block a user