modified: .gitignore
This commit is contained in:
@@ -0,0 +1,412 @@
|
||||
<script setup>
|
||||
import {computed, h, reactive, ref, watch} from "vue";
|
||||
import {
|
||||
NButton,
|
||||
NDataTable,
|
||||
NDatePicker,
|
||||
NEllipsis,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NIcon,
|
||||
NModal,
|
||||
NPopconfirm,
|
||||
NSelect,
|
||||
NSpin,
|
||||
NText,
|
||||
} from "naive-ui";
|
||||
import {TbPencil, TbTrash} from "vue-icons-plus/tb";
|
||||
import {useDebounceFn} from "@vueuse/core";
|
||||
import {useReportStore} from "../../../Stores/report.js";
|
||||
|
||||
const props = defineProps({
|
||||
patient: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
sourceStatus: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const show = defineModel('show', {type: Boolean, default: false})
|
||||
const reportStore = useReportStore()
|
||||
const formRef = ref(null)
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const operations = ref([])
|
||||
const serviceLoading = ref(false)
|
||||
const serviceOptions = ref([])
|
||||
const editingOperationId = ref(null)
|
||||
|
||||
const form = reactive({
|
||||
service_id: null,
|
||||
service_code: '',
|
||||
service_name: '',
|
||||
urgency: 'plan',
|
||||
period: null,
|
||||
})
|
||||
|
||||
const rules = {
|
||||
service_id: {
|
||||
required: true,
|
||||
message: 'Укажите услугу',
|
||||
trigger: ['change'],
|
||||
validator: (_rule, value) => {
|
||||
if (value === null || value === undefined || value === '') return false
|
||||
const id = Number(value)
|
||||
return Number.isInteger(id) && id > 0
|
||||
},
|
||||
},
|
||||
urgency: {
|
||||
required: true,
|
||||
message: 'Укажите срочность операции',
|
||||
trigger: ['change'],
|
||||
},
|
||||
period: {
|
||||
required: true,
|
||||
type: 'array',
|
||||
message: 'Укажите дату и время начала/окончания',
|
||||
trigger: ['change'],
|
||||
validator: (_rule, value) => {
|
||||
if (!Array.isArray(value) || value.length !== 2) return false
|
||||
if (!value[0] || !value[1]) return false
|
||||
return value[1] >= value[0]
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
editingOperationId.value = null
|
||||
form.service_id = null
|
||||
form.service_code = ''
|
||||
form.service_name = ''
|
||||
form.urgency = 'plan'
|
||||
form.period = null
|
||||
serviceOptions.value = []
|
||||
}
|
||||
|
||||
const urgencyOptions = [
|
||||
{ label: 'Плановая', value: 'plan' },
|
||||
{ label: 'Экстренная', value: 'emergency' },
|
||||
]
|
||||
|
||||
const pad = (value) => String(value).padStart(2, '0')
|
||||
const formatLocalDateTime = (timestamp) => {
|
||||
const date = new Date(timestamp)
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
||||
}
|
||||
|
||||
const loadOperations = async () => {
|
||||
if (!props.patient?.department_patient_id) {
|
||||
operations.value = []
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
operations.value = await reportStore.getManualPatientOperations(props.patient.department_patient_id)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadServiceOptions = useDebounceFn(async (query) => {
|
||||
if (!query || query.trim().length < 2) {
|
||||
serviceOptions.value = []
|
||||
return
|
||||
}
|
||||
|
||||
serviceLoading.value = true
|
||||
try {
|
||||
const items = await reportStore.searchMedicalServices(query.trim())
|
||||
serviceOptions.value = (items ?? []).map((item) => ({
|
||||
label: item.label,
|
||||
value: item.id,
|
||||
code: item.code,
|
||||
name: item.name,
|
||||
}))
|
||||
} finally {
|
||||
serviceLoading.value = false
|
||||
}
|
||||
}, 300)
|
||||
|
||||
const handleServiceSearch = async (query) => {
|
||||
await loadServiceOptions(query)
|
||||
}
|
||||
|
||||
const handleServiceChange = (value, option) => {
|
||||
const normalizedId = value === null || value === undefined || value === '' ? null : Number(value)
|
||||
form.service_id = Number.isInteger(normalizedId) && normalizedId > 0 ? normalizedId : null
|
||||
|
||||
if (!option && form.service_id === null) {
|
||||
form.service_id = null
|
||||
form.service_code = ''
|
||||
form.service_name = ''
|
||||
return
|
||||
}
|
||||
|
||||
if (!option || Array.isArray(option)) {
|
||||
return
|
||||
}
|
||||
|
||||
form.service_code = option.code ?? ''
|
||||
form.service_name = option.name ?? ''
|
||||
}
|
||||
|
||||
const toPeriod = (operation) => {
|
||||
if (!operation?.startAt || !operation?.endAt) return null
|
||||
return [new Date(operation.startAt).getTime(), new Date(operation.endAt).getTime()]
|
||||
}
|
||||
|
||||
const startEdit = (operation) => {
|
||||
editingOperationId.value = operation.id
|
||||
form.period = toPeriod(operation)
|
||||
form.service_id = operation.service?.id ? Number(operation.service.id) : null
|
||||
form.service_code = operation.service?.code ?? ''
|
||||
form.service_name = operation.service?.name ?? ''
|
||||
form.urgency = operation.urgency ?? 'plan'
|
||||
|
||||
if (operation.service?.label) {
|
||||
serviceOptions.value = [{
|
||||
label: operation.service.label,
|
||||
value: operation.service.id,
|
||||
code: operation.service.code,
|
||||
name: operation.service.name,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (!props.patient?.department_patient_id || submitting.value) return
|
||||
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
service_id: Number(form.service_id),
|
||||
urgency: form.urgency,
|
||||
started_at: formatLocalDateTime(form.period[0]),
|
||||
ended_at: formatLocalDateTime(form.period[1]),
|
||||
}
|
||||
|
||||
if (editingOperationId.value) {
|
||||
await reportStore.updateManualPatientOperation(
|
||||
props.patient.department_patient_id,
|
||||
editingOperationId.value,
|
||||
payload
|
||||
)
|
||||
window.$message.success('Операция обновлена')
|
||||
} else {
|
||||
await reportStore.createManualPatientOperation(props.patient.department_patient_id, payload)
|
||||
window.$message.success('Операция добавлена')
|
||||
}
|
||||
|
||||
await reportStore.reloadReportPage()
|
||||
|
||||
await Promise.all([
|
||||
loadOperations(),
|
||||
props.sourceStatus
|
||||
? reportStore.loadPatientsByStatus(props.sourceStatus, { resetPage: true })
|
||||
: Promise.resolve(),
|
||||
])
|
||||
|
||||
resetForm()
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const remove = async (operation) => {
|
||||
if (!props.patient?.department_patient_id) return
|
||||
|
||||
await reportStore.deleteManualPatientOperation(props.patient.department_patient_id, operation.id)
|
||||
window.$message.success('Операция удалена')
|
||||
|
||||
await reportStore.reloadReportPage()
|
||||
|
||||
await Promise.all([
|
||||
loadOperations(),
|
||||
props.sourceStatus
|
||||
? reportStore.loadPatientsByStatus(props.sourceStatus, { resetPage: true })
|
||||
: Promise.resolve(),
|
||||
])
|
||||
|
||||
if (editingOperationId.value === operation.id) {
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
|
||||
const columns = computed(() => ([
|
||||
{
|
||||
title: 'Услуга',
|
||||
key: 'service',
|
||||
width: 320,
|
||||
render: (row) => h(
|
||||
NEllipsis,
|
||||
{
|
||||
tooltip: true,
|
||||
},
|
||||
{
|
||||
default: () => row.service?.label || 'Без услуги',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Срочность',
|
||||
key: 'urgency',
|
||||
width: 110,
|
||||
render: (row) => row.urgency === 'emergency' ? 'Э' : 'П',
|
||||
},
|
||||
{
|
||||
title: 'Начало',
|
||||
key: 'startAt',
|
||||
width: 165,
|
||||
render: (row) => row.startAt ? new Date(row.startAt).toLocaleString('ru-RU') : '-',
|
||||
},
|
||||
{
|
||||
title: 'Окончание',
|
||||
key: 'endAt',
|
||||
width: 165,
|
||||
render: (row) => row.endAt ? new Date(row.endAt).toLocaleString('ru-RU') : '-',
|
||||
},
|
||||
{
|
||||
title: 'Мин.',
|
||||
key: 'duration',
|
||||
width: 80,
|
||||
render: (row) => {
|
||||
const duration = Number(row.duration)
|
||||
return Number.isFinite(duration) ? Math.round(duration) : '-'
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
key: 'actions',
|
||||
width: 92,
|
||||
render: (row) => h(
|
||||
'div',
|
||||
{ style: { display: 'flex', gap: '8px', justifyContent: 'center' } },
|
||||
[
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
quaternary: true,
|
||||
size: 'small',
|
||||
onClick: () => startEdit(row),
|
||||
},
|
||||
{
|
||||
icon: () => h(NIcon, {}, { default: () => h(TbPencil) }),
|
||||
}
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () => remove(row),
|
||||
},
|
||||
{
|
||||
trigger: () => h(
|
||||
NButton,
|
||||
{ quaternary: true, type: 'error', size: 'small' },
|
||||
{ icon: () => h(NIcon, {}, { default: () => h(TbTrash) }) }
|
||||
),
|
||||
default: () => 'Удалить операцию?',
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
]))
|
||||
|
||||
watch(() => show.value, async (opened) => {
|
||||
if (!opened) {
|
||||
resetForm()
|
||||
return
|
||||
}
|
||||
|
||||
await loadOperations()
|
||||
}, {immediate: false})
|
||||
|
||||
watch(() => props.patient?.department_patient_id, async () => {
|
||||
if (show.value) {
|
||||
resetForm()
|
||||
await loadOperations()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="show"
|
||||
preset="card"
|
||||
title="Операции спецконтингента"
|
||||
class="max-w-5xl"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<NText class="mb-3 block">
|
||||
{{ patient?.fullname }}
|
||||
</NText>
|
||||
|
||||
<NForm ref="formRef" :model="form" :rules="rules" label-placement="top" class="mb-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
<NFormItem label="Услуга" path="service_id" class="mb-0">
|
||||
<NSelect
|
||||
v-model:value="form.service_id"
|
||||
filterable
|
||||
clearable
|
||||
remote
|
||||
:options="serviceOptions"
|
||||
:loading="serviceLoading"
|
||||
placeholder="Начните ввод кода или названия услуги"
|
||||
@search="handleServiceSearch"
|
||||
@update:value="handleServiceChange"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="Срочность" path="urgency" class="mb-0">
|
||||
<NSelect
|
||||
v-model:value="form.urgency"
|
||||
:options="urgencyOptions"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="Начало и окончание" path="period" class="mb-0 md:col-span-1">
|
||||
<NDatePicker
|
||||
v-model:value="form.period"
|
||||
type="datetimerange"
|
||||
class="w-full"
|
||||
clearable
|
||||
/>
|
||||
</NFormItem>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center gap-2 justify-end">
|
||||
<NButton v-if="editingOperationId" @click="resetForm">Отмена редактирования</NButton>
|
||||
<NButton type="primary" :loading="submitting" @click="submit">
|
||||
{{ editingOperationId ? 'Сохранить изменения' : 'Добавить операцию' }}
|
||||
</NButton>
|
||||
</div>
|
||||
</NForm>
|
||||
|
||||
<NSpin :show="loading">
|
||||
<NDataTable
|
||||
:columns="columns"
|
||||
:data="operations"
|
||||
size="small"
|
||||
max-height="320"
|
||||
:row-key="(row) => row.id"
|
||||
class="text-sm!"
|
||||
/>
|
||||
</NSpin>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.n-data-table-th),
|
||||
:deep(.n-data-table-td) {
|
||||
font-size: var(--n-font-size);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user