Роли, переделывание отчета, изменение на главной странице

This commit is contained in:
brusnitsyn
2026-01-11 23:37:18 +09:00
parent eb019504d7
commit d4f077cdaf
59 changed files with 2099 additions and 366 deletions

View File

@@ -0,0 +1,42 @@
<script setup>
import { NFlex, NButton } from 'naive-ui'
import ReportHeader from "./ReportHeader.vue";
import ReportFormInput from "./ReportFormInput.vue";
import ReportSection from "./ReportSection.vue";
import {useReportStore} from "../../../Stores/report.js";
import {useAuthStore} from "../../../Stores/auth.js";
const props = defineProps({
mode: {
type: String,
default: 'fillable' // 'fillable', 'readonly'
}
})
const authStore = useAuthStore()
const reportStore = useReportStore()
const onSubmit = () => {
reportStore.sendReportForm({
departmentId: authStore.userDepartment.department_id
})
}
</script>
<template>
<NFlex vertical class="max-w-6xl mx-auto mt-6 mb-4 w-full">
<ReportHeader :mode="mode" />
<ReportFormInput />
<ReportSection label="Планово" />
<NButton secondary size="large" @click="onSubmit">
Сохранить отчет
</NButton>
</NFlex>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,25 @@
<script setup>
import {NCard, NFlex, NFormItem, NForm, NInputNumber} from "naive-ui";
import {useReportStore} from "../../../Stores/report.js";
const reportStore = useReportStore()
</script>
<template>
<NCard>
<NForm>
<NFlex>
<template v-for="metrikaItem in reportStore.reportInfo?.metrikaItems">
<NFormItem :label="metrikaItem.name" :show-feedback="false">
<NInputNumber v-model:value="reportStore.reportForm[`metrika_item_${metrikaItem.metrika_item_id}`]"
:default-value="metrikaItem.default_value" />
</NFormItem>
</template>
</NFlex>
</NForm>
</NCard>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,88 @@
<script setup>
import {NStatistic, NRow, NCol, NCard, NButton, NTag, NDatePicker, NFlex, NSelect, NText, NH2} from "naive-ui";
import {computed, ref} from "vue";
import {format} from "date-fns";
import {useNow} from "@vueuse/core";
import {ru} from "date-fns/locale";
import {useAuthStore} from "../../../Stores/auth.js";
import {storeToRefs} from "pinia";
import {RiAddCircleLine} from 'vue-icons-plus/ri'
import {useReportStore} from "../../../Stores/report.js";
const props = defineProps({
mode: {
type: String,
default: 'fillable' // 'fillable', 'readonly'
},
isShowDepartmentSelect: {
type: Boolean,
default: false
},
})
const departments = [
{
label: 'Все отделения',
value: 0
}
]
const authStore = useAuthStore()
const reportStore = useReportStore()
const {user, availableDepartments} = storeToRefs(authStore)
const {reportInfo} = storeToRefs(reportStore)
const isFillableMode = computed(() => props.mode.toLowerCase() === 'fillable')
const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
const selectDepartment = ref(0)
const currentDate = computed(() => {
const formatted = format(useNow().value, 'PPPPpp', {
locale: ru
})
return formatted.charAt(0).toUpperCase() + formatted.slice(1)
})
</script>
<template>
<NCard>
<NFlex vertical>
<NFlex align="center" justify="space-between">
<NH2 v-if="isFillableMode" class="mb-0!">
{{ currentDate }}
</NH2>
<NDatePicker v-if="isReadonlyMode" />
<NFlex align="center" :wrap="false">
<NTag v-if="isFillableMode" type="info" :bordered="false">
{{ authStore.userDepartment.name_full }}
</NTag>
<NSelect v-if="isReadonlyMode" v-model:value="selectDepartment" :options="departments" />
</NFlex>
</NFlex>
<NFlex justify="space-between" align="center" :wrap="false">
<NRow class="grow">
<NCol :span="4">
<NStatistic label="Коек" :value="reportStore.reportInfo?.department.beds" />
</NCol>
<NCol :span="5">
<NStatistic label="Загруженность" :value="`${reportStore.reportInfo?.department.percentLoadedBeds}%`" />
</NCol>
</NRow>
<NButton type="primary" secondary>
<template #icon>
<RiAddCircleLine />
</template>
Нежелательное событие
</NButton>
</NFlex>
</NFlex>
</NCard>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,90 @@
<script setup>
import {NCard, NFlex, NAlert, NCollapse, NCollapseItem} from 'naive-ui'
import ReportSectionItem from "./ReportSectionItem.vue";
import {computed} from "vue";
import {useReportStore} from "../../../Stores/report.js";
import {storeToRefs} from "pinia";
import ReportSectionHeader from "./ReportSectionHeader.vue";
const props = defineProps({
mode: {
type: String,
default: 'fillable' // 'fillable', 'readonly'
}
})
const reportStore = useReportStore()
const {patientsData} = storeToRefs(reportStore)
const handleItemDragged = (event) => {
console.log('Начато перетаскивание:', event)
}
// Обработка события drop
const handleItemDropped = (event) => {
const { item, fromStatus, toStatus } = event
console.log(event)
// Добавляем в целевую таблицу
if (toStatus && patientsData.value[toStatus]) {
// Проверяем, нет ли уже такого элемента
if (!patientsData.value[toStatus].some(i => i.id === item.id)) {
patientsData.value[toStatus].push(item)
}
}
}
const isFillableMode = computed(() => props.mode.toLowerCase() === 'fillable')
const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
</script>
<template>
<NCard>
<NCollapse>
<NCollapseItem name="1">
<template #header>
<ReportSectionHeader title="Планово" status="plan" />
</template>
<ReportSectionItem status="plan"
@item-dragged="handleItemDragged"
/>
</NCollapseItem>
<NCollapseItem name="2">
<template #header>
<ReportSectionHeader title="Экстренно" status="emergency" />
</template>
<ReportSectionItem status="emergency"
@item-dragged="handleItemDragged"
/>
</NCollapseItem>
<NCollapseItem name="3">
<template #header>
<ReportSectionHeader title="Наблюдение" status="observation" />
</template>
<NFlex :size="12">
<ReportSectionItem status="observation"
@item-dragged="handleItemDragged"
@item-dropped="handleItemDropped"
/>
<NAlert v-if="isFillableMode" type="info" class="w-full">
Перетаскивайте строки из верхних таблиц, что бы добавить в наблюдение
</NAlert>
</NFlex>
</NCollapseItem>
<NCollapseItem name="4">
<template #header>
<ReportSectionHeader title="Умершие" status="deceased" />
</template>
<ReportSectionItem status="deceased"
@item-dragged="handleItemDragged"
@item-dropped="handleItemDropped"
/>
</NCollapseItem>
</NCollapse>
</NCard>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,55 @@
<script setup>
import {computed, onMounted, ref} from "vue";
import {NSkeleton, NText} from 'naive-ui'
const props = defineProps({
title: {
type: String,
default: null
},
status: {
type: String,
default: null
}
})
const isLoading = ref(true)
const countPatient = ref(null)
const fetchPatientCount = async () => {
if (props.status === 'plan' || props.status === 'emergency') {
isLoading.value = true
await axios.post('/api/mis/patients/count', {
status: props.status
}).then((res) => {
countPatient.value = res.data
}).finally(() => {
isLoading.value = false
})
} else {
isLoading.value = false
}
}
const computedHeader = computed(() => {
if (countPatient.value !== null) {
return `${props.title} (${countPatient.value})`
} else {
return props.title
}
})
onMounted(async () => {
await fetchPatientCount()
})
</script>
<template>
<NSkeleton text style="width: 128px; height: 22px" round v-if="isLoading" />
<NText v-else>
{{ computedHeader }}
</NText>
</template>
<style scoped>
</style>

View File

@@ -0,0 +1,166 @@
<script setup>
import {NDataTable} 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";
const props = defineProps({
mode: {
type: String,
default: 'fillable' // 'fillable', 'readonly'
},
keys: {
type: Array,
default: ['num', 'fullname', 'age', 'birth_date', 'ds']
},
status: {
type: String,
default: null // 'plan'
}
})
const isFillableMode = computed(() => props.mode.toLowerCase() === 'fillable')
const isReadonlyMode = computed(() => props.mode.toLowerCase() === 'readonly')
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)
// Добавляем drag колонку если режим fillable
const columns = computed(() => {
if (!isFillableMode.value) return baseColumns
const dragColumn = {
title: '',
key: 'drag',
width: 40,
render: (row) => h('div', {
style: {
cursor: 'grab',
color: '#666',
textAlign: 'center',
userSelect: 'none'
}
}, '⋮⋮')
}
return [dragColumn, ...baseColumns]
})
const handleDragStart = (e, row) => {
// Устанавливаем данные о перетаскиваемом элементе
e.dataTransfer.setData('application/json', JSON.stringify({
row: row,
fromStatus: props.status
}))
e.dataTransfer.effectAllowed = 'copy'
const rowElement = e.target
// Эмитим событие для родителя
emit('item-dragged', { row, fromStatus: props.status })
// Добавляем класс для визуальной обратной связи
e.target.classList.add('dragging')
}
const handleDragEnd = (e) => {
if (e.target) {
e.target.classList.remove('dragging')
}
}
const handleDragOver = (e) => {
e.preventDefault()
}
const handleDrop = (e) => {
e.preventDefault()
try {
const dragData = JSON.parse(e.dataTransfer.getData('application/json'))
// Эмитим событие для родителя
emit('item-dropped', {
item: dragData.row,
fromStatus: dragData.fromStatus,
toStatus: props.status
})
} catch (error) {
console.error('Drop error:', error)
}
}
const fetchPatients = async () => {
if (props.status === 'plan' || props.status === 'emergency') {
isLoading.value = true
await axios.post('/api/mis/patients', {
status: props.status
}).then((res) => {
patientsData.value[props.status] = res.data
}).finally(() => {
isLoading.value = false
})
} else {
isLoading.value = false
}
}
function rowProps(row) {
return {
draggable: true,
style: 'cursor: grab;',
onDragstart: (e) => {
handleDragStart(e, row)
},
onDragend: (e) => {
handleDragEnd(e)
},
onDragover: (e) => {
handleDragOver(e)
},
onDrop: (e) => {
handleDrop(e)
}
}
}
onMounted(async () => {
await fetchPatients()
})
</script>
<template>
<NDataTable :columns="columns"
:data="patientsData[status]"
size="small"
@drop="handleDrop"
@dragover="handleDragOver"
:loading="isLoading"
virtual-scroll
max-height="200"
min-height="200"
:row-props="rowProps"
class="text-sm!">
</NDataTable>
<!-- <NDataTable :columns="columns"-->
<!-- :data="data"-->
<!-- size="small"-->
<!-- max-height="200"-->
<!-- class="text-sm!">-->
<!-- </NDataTable>-->
</template>
<style scoped>
:deep(.n-data-table-th),
:deep(.n-data-table-td) {
white-space: nowrap !important;
font-size: var(--n-font-size);
}
</style>