|
@@ -0,0 +1,408 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <ContentWrap>
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ class="-mb-15px"
|
|
|
|
|
+ :model="queryParams"
|
|
|
|
|
+ ref="queryFormRef"
|
|
|
|
|
+ :inline="true"
|
|
|
|
|
+ label-width="80px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item prop="tenantIds">
|
|
|
|
|
+ <TenantSelect v-model="queryParams.tenantIds" placeholder="请选择机构名称" prop="tenantIds" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="长者名称" prop="elderName">
|
|
|
|
|
+ <TgInput @keyup.enter="handleQuery" v-model="queryParams.elderName" class="!w-160px" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="检查日期" prop="checkDate">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="queryParams.checkDate"
|
|
|
|
|
+ type="daterange"
|
|
|
|
|
+ range-separator="至"
|
|
|
|
|
+ start-placeholder="开始日期"
|
|
|
|
|
+ end-placeholder="结束日期"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ class="!w-280px"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
|
|
|
|
+ <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </ContentWrap>
|
|
|
|
|
+
|
|
|
|
|
+ <ContentWrap>
|
|
|
|
|
+ <Table2 v-loading="loading" :list="list" :columns="columns" :queryParams="queryParams">
|
|
|
|
|
+ <template #pre="{ scope }">
|
|
|
|
|
+ <el-button link type="primary" @click="openDetail(scope)">查看</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </Table2>
|
|
|
|
|
+
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ v-model:page="queryParams.pageNo"
|
|
|
|
|
+ v-model:limit="queryParams.pageSize"
|
|
|
|
|
+ @pagination="getList"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <Dialog
|
|
|
|
|
+ v-model="detailVisible"
|
|
|
|
|
+ width="90%"
|
|
|
|
|
+ title="安全检查日志详情"
|
|
|
|
|
+ class="form-tag-dialog"
|
|
|
|
|
+ scroll
|
|
|
|
|
+ @close="closeDetail"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div v-loading="detailLoading">
|
|
|
|
|
+ <div class="info-title">长者信息</div>
|
|
|
|
|
+ <div class="info-wrap mb-15px">
|
|
|
|
|
+ <el-row :gutter="20">
|
|
|
|
|
+ <el-col :span="8" :xs="24" class="header-item">长者姓名:{{ detailHeader.elderName || '-' }}</el-col>
|
|
|
|
|
+ <el-col :span="8" :xs="24" class="header-item">房间号:{{ detailHeader.roomName || '-' }}</el-col>
|
|
|
|
|
+ <el-col :span="8" :xs="24" class="header-item">床位号:{{ detailHeader.bedName || '-' }}</el-col>
|
|
|
|
|
+ </el-row>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="info-title">历史记录查询</div>
|
|
|
|
|
+ <el-form class="detail-query-form mb-15px" :inline="true" @submit.prevent>
|
|
|
|
|
+ <el-form-item label="检查日期">
|
|
|
|
|
+ <el-date-picker
|
|
|
|
|
+ v-model="detailCheckDateRange"
|
|
|
|
|
+ type="daterange"
|
|
|
|
|
+ range-separator="至"
|
|
|
|
|
+ start-placeholder="开始日期"
|
|
|
|
|
+ end-placeholder="结束日期"
|
|
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ class="!w-280px"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button type="primary" :loading="detailLoading" @click="handleDetailQuery">查询</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table :data="detailRecords" max-height="58vh">
|
|
|
|
|
+ <el-table-column type="expand">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <div class="expand-wrap">
|
|
|
|
|
+ <div class="expand-title">安全检查明细</div>
|
|
|
|
|
+ <el-descriptions :column="4" border size="small" class="mb-10px" label-width="130px">
|
|
|
|
|
+ <el-descriptions-item label="被检查人签字">
|
|
|
|
|
+ {{ scope.row.beCheckSign || '-' }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="长者自带电器设备">
|
|
|
|
|
+ {{ scope.row.elderSelfEquipment || '-' }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="存在问题及整改要求">
|
|
|
|
|
+ {{ scope.row.safeRemark || '-' }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ <el-descriptions-item label="其他">
|
|
|
|
|
+ {{ scope.row.other || '-' }}
|
|
|
|
|
+ </el-descriptions-item>
|
|
|
|
|
+ </el-descriptions>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table :data="scope.row.items || []" size="small" class="inner-table">
|
|
|
|
|
+ <el-table-column label="检查项" prop="label" min-width="220" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="结果" prop="status" width="96" align="center">
|
|
|
|
|
+ <template #default="s">
|
|
|
|
|
+ <el-tag v-if="Number(s.row.status) === 1" type="success" size="small">无问题</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="Number(s.row.status) === 0" type="danger" size="small">有问题</el-tag>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="检查时间" prop="checkTime" min-width="160" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatBackendDateTime(scope.row.checkTime, 'YYYY-MM-DD HH:mm') || '-' }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="状态" prop="overallStatus" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-tag v-if="Number(scope.row.overallStatus) === 1" type="success" size="small">正常</el-tag>
|
|
|
|
|
+ <el-tag v-else-if="Number(scope.row.overallStatus) === 0" type="danger" size="small">异常</el-tag>
|
|
|
|
|
+ <span v-else>-</span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+
|
|
|
|
|
+ <el-table-column label="异常项数" prop="failCount" align="center" />
|
|
|
|
|
+ <el-table-column label="记录人" prop="recorder" show-overflow-tooltip />
|
|
|
|
|
+ <!-- <el-table-column label="问题与要求" prop="question" min-width="180" show-overflow-tooltip /> -->
|
|
|
|
|
+ <el-table-column label="被检查人签字" prop="beCheckSign" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column label="创建时间" prop="createTime" min-width="160" show-overflow-tooltip>
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatBackendDateTime(scope.row.createTime, 'YYYY-MM-DD HH:mm') || '-' }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-if="detailTotal > 0" class="flex justify-end">
|
|
|
|
|
+ <Pagination
|
|
|
|
|
+ :total="detailTotal"
|
|
|
|
|
+ v-model:page="detailPageNo"
|
|
|
|
|
+ v-model:limit="detailPageSize"
|
|
|
|
|
+ @pagination="onDetailPagination"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="closeDetail">关闭</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </Dialog>
|
|
|
|
|
+ </ContentWrap>
|
|
|
|
|
+</template>
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { formatBackendDateTime } from '@/utils/formatTime'
|
|
|
|
|
+import dayjs from 'dayjs'
|
|
|
|
|
+import { useUserStore } from '@/store/modules/user'
|
|
|
|
|
+import { getElderlySafeCheckElderPage, getElderlySafeCheckRecordPage } from '@/api/elderly/nursing'
|
|
|
|
|
+
|
|
|
|
|
+defineOptions({ name: 'SafetyCheckLog' })
|
|
|
|
|
+
|
|
|
|
|
+type SafetyChecklistItem = {
|
|
|
|
|
+ key?: string
|
|
|
|
|
+ label?: string
|
|
|
|
|
+ status?: number | string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type SafetyCheckElderRow = {
|
|
|
|
|
+ elderId: number | string
|
|
|
|
|
+ elderName: string
|
|
|
|
|
+ roomName: string
|
|
|
|
|
+ bedName: string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type ElderlySafeCheckRespVO = {
|
|
|
|
|
+ id?: number | string
|
|
|
|
|
+ elderId?: number | string
|
|
|
|
|
+ elderName?: string
|
|
|
|
|
+ roomName?: string
|
|
|
|
|
+ bedName?: string
|
|
|
|
|
+ safeSituation?: string
|
|
|
|
|
+ safeRemark?: string
|
|
|
|
|
+ elderSelfEquipment?: string
|
|
|
|
|
+ question?: string
|
|
|
|
|
+ beCheckSign?: string
|
|
|
|
|
+ failCount?: number | string
|
|
|
|
|
+ creator?: string
|
|
|
|
|
+ createTime?: number | string
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+type SafetyCheckDetailRow = ElderlySafeCheckRespVO & {
|
|
|
|
|
+ checkTime?: number | string
|
|
|
|
|
+ items?: SafetyChecklistItem[]
|
|
|
|
|
+ other?: string
|
|
|
|
|
+ overallStatus: 0 | 1
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const userStore = useUserStore()
|
|
|
|
|
+const message = useMessage()
|
|
|
|
|
+
|
|
|
|
|
+const columns = reactive([
|
|
|
|
|
+ { label: '长者姓名', field: 'elderName' },
|
|
|
|
|
+ { label: '房间号', field: 'roomName' },
|
|
|
|
|
+ { label: '床位号', field: 'bedName' }
|
|
|
|
|
+])
|
|
|
|
|
+
|
|
|
|
|
+const queryParams = reactive({
|
|
|
|
|
+ pageNo: 1,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ elderName: '',
|
|
|
|
|
+ checkDate: undefined as string[] | undefined,
|
|
|
|
|
+ tenantIds: userStore.orgTenantId
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const loading = ref(true)
|
|
|
|
|
+const total = ref(0)
|
|
|
|
|
+const list = ref<SafetyCheckElderRow[]>([])
|
|
|
|
|
+const queryFormRef = ref()
|
|
|
|
|
+
|
|
|
|
|
+function buildPageParams() {
|
|
|
|
|
+ const p = { ...queryParams } as Recordable
|
|
|
|
|
+ if (!p.elderName) delete p.elderName
|
|
|
|
|
+ const cd = p.checkDate as string[] | undefined
|
|
|
|
|
+ if (!Array.isArray(cd) || cd.length < 2 || !cd[0] || !cd[1]) {
|
|
|
|
|
+ delete p.checkDate
|
|
|
|
|
+ }
|
|
|
|
|
+ return p
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleQuery = () => {
|
|
|
|
|
+ queryParams.pageNo = 1
|
|
|
|
|
+ getList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const resetQuery = () => {
|
|
|
|
|
+ queryFormRef.value?.resetFields?.()
|
|
|
|
|
+ handleQuery()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const getList = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = await getElderlySafeCheckElderPage(buildPageParams())
|
|
|
|
|
+ list.value = (data?.list ?? []) as SafetyCheckElderRow[]
|
|
|
|
|
+ total.value = Number(data?.total ?? 0)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const detailVisible = ref(false)
|
|
|
|
|
+const detailLoading = ref(false)
|
|
|
|
|
+const detailHeader = reactive({
|
|
|
|
|
+ elderId: undefined as number | string | undefined,
|
|
|
|
|
+ elderName: '',
|
|
|
|
|
+ roomName: '',
|
|
|
|
|
+ bedName: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const detailCheckDateRange = ref<string[] | undefined>(undefined)
|
|
|
|
|
+const detailRecords = ref<SafetyCheckDetailRow[]>([])
|
|
|
|
|
+const detailPageNo = ref(1)
|
|
|
|
|
+const detailPageSize = ref(10)
|
|
|
|
|
+const detailTotal = ref(0)
|
|
|
|
|
+
|
|
|
|
|
+function createDetailDefaultDateRange(): string[] {
|
|
|
|
|
+ const start = dayjs().startOf('month')
|
|
|
|
|
+ const end = dayjs().endOf('month')
|
|
|
|
|
+ return [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function safeParseJson<T>(raw: unknown): T | undefined {
|
|
|
|
|
+ if (raw == null || raw === '') return undefined
|
|
|
|
|
+ if (typeof raw === 'object') return raw as T
|
|
|
|
|
+ const s = String(raw)
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.parse(s) as T
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return undefined
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function parseSafeSituation(raw: unknown): { checkTime?: number | string; items: SafetyChecklistItem[]; other?: string } {
|
|
|
|
|
+ const parsed = safeParseJson<any>(raw) ?? {}
|
|
|
|
|
+ const items = Array.isArray(parsed.items) ? (parsed.items as SafetyChecklistItem[]) : []
|
|
|
|
|
+ return { checkTime: parsed.checkTime, items, other: parsed.other }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function computeOverallStatusByFailCountAndItems(failCount: unknown, items: SafetyChecklistItem[]): 0 | 1 {
|
|
|
|
|
+ if (failCount !== undefined && failCount !== null && failCount !== '') {
|
|
|
|
|
+ const n = Number(failCount)
|
|
|
|
|
+ if (!Number.isNaN(n)) return n > 0 ? 0 : 1
|
|
|
|
|
+ }
|
|
|
|
|
+ return items.some((x) => Number((x as any)?.status) === 0) ? 0 : 1
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function fetchDetailList() {
|
|
|
|
|
+ if (detailHeader.elderId == null) return
|
|
|
|
|
+ detailLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const params: Recordable = {
|
|
|
|
|
+ tenantIds: queryParams.tenantIds,
|
|
|
|
|
+ elderId: detailHeader.elderId,
|
|
|
|
|
+ pageNo: detailPageNo.value,
|
|
|
|
|
+ pageSize: detailPageSize.value
|
|
|
|
|
+ }
|
|
|
|
|
+ const cd = detailCheckDateRange.value
|
|
|
|
|
+ if (Array.isArray(cd) && cd.length >= 2 && cd[0] && cd[1]) {
|
|
|
|
|
+ params.checkDate = [cd[0], cd[1]]
|
|
|
|
|
+ }
|
|
|
|
|
+ const data = await getElderlySafeCheckRecordPage(params)
|
|
|
|
|
+ detailTotal.value = Number(data?.total ?? 0)
|
|
|
|
|
+ const rawList = (data?.list ?? []) as ElderlySafeCheckRespVO[]
|
|
|
|
|
+ detailRecords.value = rawList.map((r) => {
|
|
|
|
|
+ const parsed = parseSafeSituation(r.safeSituation)
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...r,
|
|
|
|
|
+ checkTime: parsed.checkTime,
|
|
|
|
|
+ items: parsed.items,
|
|
|
|
|
+ other: parsed.other,
|
|
|
|
|
+ overallStatus: computeOverallStatusByFailCountAndItems(r.failCount, parsed.items)
|
|
|
|
|
+ } as SafetyCheckDetailRow
|
|
|
|
|
+ })
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ detailLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onDetailPagination() {
|
|
|
|
|
+ fetchDetailList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleDetailQuery() {
|
|
|
|
|
+ detailPageNo.value = 1
|
|
|
|
|
+ fetchDetailList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resetDetailHeader() {
|
|
|
|
|
+ detailHeader.elderId = undefined
|
|
|
|
|
+ detailHeader.elderName = ''
|
|
|
|
|
+ detailHeader.roomName = ''
|
|
|
|
|
+ detailHeader.bedName = ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openDetail(row: SafetyCheckElderRow) {
|
|
|
|
|
+ if (!row?.elderId && row?.elderId !== 0) {
|
|
|
|
|
+ message.warning('该行缺少长者 id,无法查询详情')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ detailVisible.value = true
|
|
|
|
|
+ detailPageNo.value = 1
|
|
|
|
|
+ detailPageSize.value = 10
|
|
|
|
|
+ detailCheckDateRange.value = createDetailDefaultDateRange()
|
|
|
|
|
+ detailRecords.value = []
|
|
|
|
|
+
|
|
|
|
|
+ detailHeader.elderId = row.elderId
|
|
|
|
|
+ detailHeader.elderName = row.elderName || ''
|
|
|
|
|
+ detailHeader.roomName = row.roomName || ''
|
|
|
|
|
+ detailHeader.bedName = row.bedName || ''
|
|
|
|
|
+
|
|
|
|
|
+ fetchDetailList()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function closeDetail() {
|
|
|
|
|
+ detailVisible.value = false
|
|
|
|
|
+ resetDetailHeader()
|
|
|
|
|
+ detailCheckDateRange.value = undefined
|
|
|
|
|
+ detailRecords.value = []
|
|
|
|
|
+ detailTotal.value = 0
|
|
|
|
|
+ detailPageNo.value = 1
|
|
|
|
|
+ detailPageSize.value = 10
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ getList()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.info-title {
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+}
|
|
|
|
|
+.header-item {
|
|
|
|
|
+ margin-bottom: 6px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
|
|
+}
|
|
|
|
|
+.expand-wrap {
|
|
|
|
|
+ padding: 10px 10px 6px;
|
|
|
|
|
+}
|
|
|
|
|
+.expand-title {
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+}
|
|
|
|
|
+.inner-table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|