|
|
@@ -1,51 +1,136 @@
|
|
|
<template>
|
|
|
<Dialog
|
|
|
v-model="dialogVisible"
|
|
|
- width="800px"
|
|
|
+ width="90%"
|
|
|
title="巡房项目详情"
|
|
|
class="form-tag-dialog"
|
|
|
scroll
|
|
|
@close="handleClosed"
|
|
|
>
|
|
|
- <el-descriptions v-loading="loading" :column="2" border>
|
|
|
- <el-descriptions-item label="记录人">{{ detail.recorder || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="角色">{{ getRoleSubmitLabel(detail.role) }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="长者姓名">{{ detail.elderName || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="房间号">{{ detail.roomName ?? '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="巡房时间" :span="2">{{
|
|
|
- formatBackendDateTime(detail.roundTime) || '-'
|
|
|
- }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="巡视项目组" :span="2">
|
|
|
- <template v-if="formatItems(detail.items).length">
|
|
|
- <el-tag
|
|
|
- v-for="(it, i) in formatItems(detail.items)"
|
|
|
- :key="i"
|
|
|
- class="mr-5px mb-5px"
|
|
|
- size="small"
|
|
|
- effect="plain"
|
|
|
+ <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"
|
|
|
+ >长者姓名:{{ header.elderName || '-' }}</el-col
|
|
|
>
|
|
|
- {{ it }}
|
|
|
- </el-tag>
|
|
|
- </template>
|
|
|
- <span v-else>-</span>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="语音" :span="2">
|
|
|
- <template v-if="voiceParts.hasAny">
|
|
|
- <div v-for="(url, i) in voiceParts.audios" :key="'audio-' + i" class="voice-row">
|
|
|
- <audio :src="url" controls class="voice-audio"></audio>
|
|
|
- </div>
|
|
|
- <div v-for="(t, i) in voiceParts.texts" :key="'text-' + i" class="voice-text">
|
|
|
- {{ t }}
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <span v-else>-</span>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="备注" :span="2">{{ detail.remark || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="创建时间">{{ formatBackendDateTime(detail.createTime) || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="更新时间">{{ formatBackendDateTime(detail.updateTime) || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="创建人">{{ detail.creator || '-' }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="更新人">{{ detail.updater || '-' }}</el-descriptions-item>
|
|
|
- </el-descriptions>
|
|
|
+ <el-col :span="8" :xs="24" class="header-item"
|
|
|
+ >楼栋:{{ header.buildName || '-' }}</el-col
|
|
|
+ >
|
|
|
+ <el-col :span="8" :xs="24" class="header-item"
|
|
|
+ >楼层:{{ header.floorName || '-' }}</el-col
|
|
|
+ >
|
|
|
+ <el-col :span="8" :xs="24" class="header-item"
|
|
|
+ >房间号:{{ header.roomName || '-' }}</el-col
|
|
|
+ >
|
|
|
+ <el-col :span="8" :xs="24" class="header-item"
|
|
|
+ >床位号:{{ header.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="detailRoundTime"
|
|
|
+ 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="records"
|
|
|
+ :header-cell-style="(data) => tableHeaderColor(data) || {}"
|
|
|
+ max-height="58vh"
|
|
|
+ >
|
|
|
+ <el-table-column label="巡房时间" prop="roundTime" min-width="160" show-overflow-tooltip>
|
|
|
+ <template #default="scope">
|
|
|
+ {{ formatBackendDateTime(scope.row.roundTime) || '-' }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="记录人" prop="recorder" width="100" show-overflow-tooltip />
|
|
|
+ <el-table-column label="角色" prop="role" width="100" show-overflow-tooltip>
|
|
|
+ <template #default="scope">
|
|
|
+ {{ getRoleSubmitLabel(scope.row.role) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" prop="status" width="96" align="center">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag v-if="isAbnormalStatus(scope.row.status)" type="danger" size="small"
|
|
|
+ >异常</el-tag
|
|
|
+ >
|
|
|
+ <el-tag v-else-if="isNormalStatus(scope.row.status)" type="success" size="small"
|
|
|
+ >正常</el-tag
|
|
|
+ >
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="巡视项目组" min-width="160">
|
|
|
+ <template #default="scope">
|
|
|
+ <template v-if="splitItemLabels(scope.row.items).length">
|
|
|
+ <el-tag
|
|
|
+ v-for="(t, i) in splitItemLabels(scope.row.items)"
|
|
|
+ :key="i"
|
|
|
+ class="mr-5px mb-5px"
|
|
|
+ size="small"
|
|
|
+ effect="plain"
|
|
|
+ >
|
|
|
+ {{ t }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="语音" min-width="200">
|
|
|
+ <template #default="scope">
|
|
|
+ <template v-if="getVoiceParts(scope.row.voice).hasAny">
|
|
|
+ <div
|
|
|
+ v-for="(url, i) in getVoiceParts(scope.row.voice).audios"
|
|
|
+ :key="'a-' + scope.$index + '-' + i"
|
|
|
+ class="voice-row"
|
|
|
+ >
|
|
|
+ <audio :src="url" controls class="voice-audio"></audio>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-for="(t, i) in getVoiceParts(scope.row.voice).texts"
|
|
|
+ :key="'t-' + scope.$index + '-' + i"
|
|
|
+ class="voice-text"
|
|
|
+ >
|
|
|
+ {{ t }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="备注" prop="remark" min-width="140" show-overflow-tooltip />
|
|
|
+ <el-table-column label="创建时间" prop="createTime" min-width="160" show-overflow-tooltip>
|
|
|
+ <template #default="scope">
|
|
|
+ {{ formatBackendDateTime(scope.row.createTime) || '-' }}
|
|
|
+ </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="handleClosed">关闭</el-button>
|
|
|
</template>
|
|
|
@@ -53,43 +138,69 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { getElderlyItemsRound } from '@/api/elderly/nursing'
|
|
|
+import { getElderlyItemsRoundDetail } from '@/api/elderly/nursing'
|
|
|
import { formatBackendDateTime } from '@/utils/formatTime'
|
|
|
+import { tableHeaderColor } from '@/utils/table'
|
|
|
import { getRoleSubmitLabel } from './roleSubmitLabel'
|
|
|
+import dayjs from 'dayjs'
|
|
|
|
|
|
defineOptions({ name: 'ElderlyItemsRoundDetail' })
|
|
|
|
|
|
-export type ElderlyItemsRoundDetailVO = {
|
|
|
+/** 巡房记录明细(PageResultRecord.list 单项) */
|
|
|
+export type ElderlyItemsRoundRecordVO = Recordable & {
|
|
|
id?: number
|
|
|
recorder?: string
|
|
|
role?: string
|
|
|
- elderId?: number
|
|
|
- elderName?: string
|
|
|
- buildId?: number
|
|
|
- floorId?: number
|
|
|
- roomId?: number
|
|
|
- roomName?: string
|
|
|
- items?: string | string[]
|
|
|
+ items?: string
|
|
|
voice?: string
|
|
|
remark?: string
|
|
|
- tenantId?: number
|
|
|
- /** 巡房时间(时间戳或与后端约定格式) */
|
|
|
+ status?: number | string
|
|
|
roundTime?: string | number
|
|
|
createTime?: string | number
|
|
|
- updateTime?: string | number
|
|
|
- creator?: string
|
|
|
- updater?: string
|
|
|
+}
|
|
|
+
|
|
|
+const message = useMessage()
|
|
|
+
|
|
|
+/** 详情内默认巡房时间:本月起止 */
|
|
|
+function createDetailDefaultRoundTime(): string[] {
|
|
|
+ const start = dayjs().startOf('month')
|
|
|
+ const end = dayjs().endOf('month')
|
|
|
+ return [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')]
|
|
|
}
|
|
|
|
|
|
const dialogVisible = ref(false)
|
|
|
-const loading = ref(false)
|
|
|
-const detail = reactive<ElderlyItemsRoundDetailVO>({})
|
|
|
+const detailLoading = ref(false)
|
|
|
+/** 传给接口 roundTime:[开始,结束],与列表页同款 YYYY-MM-DD */
|
|
|
+const detailRoundTime = ref<string[] | undefined>(undefined)
|
|
|
+
|
|
|
+const header = reactive({
|
|
|
+ elderId: undefined as number | undefined,
|
|
|
+ elderName: '',
|
|
|
+ buildName: '',
|
|
|
+ floorName: '',
|
|
|
+ roomName: '',
|
|
|
+ bedName: ''
|
|
|
+})
|
|
|
+
|
|
|
+const records = ref<ElderlyItemsRoundRecordVO[]>([])
|
|
|
+const detailPageNo = ref(1)
|
|
|
+const detailPageSize = ref(10)
|
|
|
+const detailTotal = ref(0)
|
|
|
+/** 详情 total 缺失时服务端一次返回全集,前端切片分页 */
|
|
|
+const detailRecordsAll = ref<ElderlyItemsRoundRecordVO[]>([])
|
|
|
|
|
|
-function emptyDetail() {
|
|
|
- Object.keys(detail).forEach((k) => delete (detail as Recordable)[k])
|
|
|
+let listFiltersSnapshot: Recordable = {}
|
|
|
+let elderIdSnapshot: number | undefined
|
|
|
+
|
|
|
+function resetHeader() {
|
|
|
+ header.elderId = undefined
|
|
|
+ header.elderName = ''
|
|
|
+ header.buildName = ''
|
|
|
+ header.floorName = ''
|
|
|
+ header.roomName = ''
|
|
|
+ header.bedName = ''
|
|
|
}
|
|
|
|
|
|
-/** items:按中英文逗号拆分;数组则每项再拆分。voice 仍用本函数(同分隔规则) */
|
|
|
function splitDelimited(raw: unknown): string[] {
|
|
|
if (raw == null || raw === '') return []
|
|
|
if (Array.isArray(raw)) {
|
|
|
@@ -101,12 +212,12 @@ function splitDelimited(raw: unknown): string[] {
|
|
|
.filter(Boolean)
|
|
|
}
|
|
|
|
|
|
-function formatItems(items: string | string[] | undefined) {
|
|
|
+function splitItemLabels(items: unknown) {
|
|
|
return splitDelimited(items)
|
|
|
}
|
|
|
|
|
|
-const voiceParts = computed(() => {
|
|
|
- const parts = splitDelimited(detail.voice)
|
|
|
+function getVoiceParts(raw: unknown) {
|
|
|
+ const parts = splitDelimited(raw)
|
|
|
const audios: string[] = []
|
|
|
const texts: string[] = []
|
|
|
for (const p of parts) {
|
|
|
@@ -117,41 +228,165 @@ const voiceParts = computed(() => {
|
|
|
}
|
|
|
}
|
|
|
return { audios, texts, hasAny: audios.length > 0 || texts.length > 0 }
|
|
|
-})
|
|
|
+}
|
|
|
|
|
|
-const open = async (row: Recordable) => {
|
|
|
- const id = row?.id as number | undefined
|
|
|
- if (id == null) return
|
|
|
- dialogVisible.value = true
|
|
|
- loading.value = true
|
|
|
- emptyDetail()
|
|
|
+/** 接口约定:status 0 异常,1 正常 */
|
|
|
+function isAbnormalStatus(s: unknown) {
|
|
|
+ return s !== undefined && s !== '' && Number(s) === 0
|
|
|
+}
|
|
|
+
|
|
|
+function isNormalStatus(s: unknown) {
|
|
|
+ return s !== undefined && s !== '' && Number(s) === 1
|
|
|
+}
|
|
|
+
|
|
|
+function syncDetailPagedSlice() {
|
|
|
+ const all = detailRecordsAll.value
|
|
|
+ if (!all.length) {
|
|
|
+ records.value = []
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const start = (detailPageNo.value - 1) * detailPageSize.value
|
|
|
+ records.value = all.slice(start, start + detailPageSize.value)
|
|
|
+}
|
|
|
+
|
|
|
+/** 解析 elderly-items-round/get 返回 ElderlyItemsRoundRespVO */
|
|
|
+function parseDetailResponse(res: Recordable) {
|
|
|
+ const pageResult = res.pageResult as Recordable | undefined
|
|
|
+ let list: ElderlyItemsRoundRecordVO[] = []
|
|
|
+ let total: number | undefined
|
|
|
+ if (pageResult && typeof pageResult === 'object' && !Array.isArray(pageResult)) {
|
|
|
+ const rawList = pageResult.list
|
|
|
+ list = Array.isArray(rawList) ? (rawList as ElderlyItemsRoundRecordVO[]) : []
|
|
|
+ const t = pageResult.total
|
|
|
+ if (t !== undefined && t !== null && t !== '') {
|
|
|
+ total = Number(t)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ elderId: res.elderId as number | undefined,
|
|
|
+ elderName: (res.elderName as string) || '',
|
|
|
+ buildName: (res.buildName as string) || '',
|
|
|
+ floorName: (res.floorName as string) || '',
|
|
|
+ roomName: (res.roomName as string) || '',
|
|
|
+ bedName: (res.bedName as string) || '',
|
|
|
+ list,
|
|
|
+ total
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function buildDetailRequestParams(): Recordable {
|
|
|
+ const params: Recordable = {
|
|
|
+ ...listFiltersSnapshot,
|
|
|
+ elderId: elderIdSnapshot,
|
|
|
+ pageNo: detailPageNo.value,
|
|
|
+ pageSize: detailPageSize.value
|
|
|
+ }
|
|
|
+ const rt = detailRoundTime.value
|
|
|
+ if (Array.isArray(rt) && rt.length >= 2 && rt[0] && rt[1]) {
|
|
|
+ params.roundTime = [rt[0], rt[1]]
|
|
|
+ }
|
|
|
+ return params
|
|
|
+}
|
|
|
+
|
|
|
+async function fetchDetailList() {
|
|
|
+ if (elderIdSnapshot == null) return
|
|
|
+ detailLoading.value = true
|
|
|
try {
|
|
|
- const data = (await getElderlyItemsRound(id)) as ElderlyItemsRoundDetailVO
|
|
|
- Object.assign(detail, data ?? {})
|
|
|
+ const raw = (await getElderlyItemsRoundDetail(buildDetailRequestParams())) as Recordable
|
|
|
+ const parsed = parseDetailResponse(raw ?? {})
|
|
|
+
|
|
|
+ header.elderId = parsed.elderId
|
|
|
+ header.elderName = parsed.elderName
|
|
|
+ header.buildName = parsed.buildName
|
|
|
+ header.floorName = parsed.floorName
|
|
|
+ header.roomName = parsed.roomName
|
|
|
+ header.bedName = parsed.bedName
|
|
|
+
|
|
|
+ if (parsed.total !== undefined && !Number.isNaN(parsed.total)) {
|
|
|
+ detailRecordsAll.value = []
|
|
|
+ records.value = parsed.list
|
|
|
+ detailTotal.value = parsed.total
|
|
|
+ } else {
|
|
|
+ detailRecordsAll.value = parsed.list
|
|
|
+ detailTotal.value = detailRecordsAll.value.length
|
|
|
+ syncDetailPagedSlice()
|
|
|
+ }
|
|
|
} finally {
|
|
|
- loading.value = false
|
|
|
+ detailLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function onDetailPagination() {
|
|
|
+ if (detailRecordsAll.value.length > 0) {
|
|
|
+ syncDetailPagedSlice()
|
|
|
+ } else {
|
|
|
+ fetchDetailList()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function handleDetailQuery() {
|
|
|
+ detailPageNo.value = 1
|
|
|
+ detailRecordsAll.value = []
|
|
|
+ fetchDetailList()
|
|
|
+}
|
|
|
+
|
|
|
+const open = async (row: Recordable, listFilters: Recordable) => {
|
|
|
+ const eid = row?.elderId as number | undefined
|
|
|
+ if (eid == null) {
|
|
|
+ message.warning('该行缺少长者 id,无法查询巡房明细')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ elderIdSnapshot = eid
|
|
|
+ listFiltersSnapshot = { ...(listFilters || {}) }
|
|
|
+ dialogVisible.value = true
|
|
|
+ detailRoundTime.value = createDetailDefaultRoundTime()
|
|
|
+ detailPageNo.value = 1
|
|
|
+ detailPageSize.value = 10
|
|
|
+ detailTotal.value = 0
|
|
|
+ detailRecordsAll.value = []
|
|
|
+ records.value = []
|
|
|
+ resetHeader()
|
|
|
+
|
|
|
+ await fetchDetailList()
|
|
|
+}
|
|
|
+
|
|
|
defineExpose({ open })
|
|
|
|
|
|
const handleClosed = () => {
|
|
|
- emptyDetail()
|
|
|
dialogVisible.value = false
|
|
|
+ elderIdSnapshot = undefined
|
|
|
+ listFiltersSnapshot = {}
|
|
|
+ detailRoundTime.value = undefined
|
|
|
+ detailRecordsAll.value = []
|
|
|
+ records.value = []
|
|
|
+ detailTotal.value = 0
|
|
|
+ detailPageNo.value = 1
|
|
|
+ resetHeader()
|
|
|
}
|
|
|
</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);
|
|
|
+}
|
|
|
.voice-audio {
|
|
|
- max-width: 100%;
|
|
|
+ max-width: 220px;
|
|
|
height: 32px;
|
|
|
vertical-align: middle;
|
|
|
}
|
|
|
.voice-row + .voice-row {
|
|
|
- margin-top: 8px;
|
|
|
+ margin-top: 6px;
|
|
|
}
|
|
|
.voice-text {
|
|
|
- margin-top: 6px;
|
|
|
+ margin-top: 4px;
|
|
|
+ font-size: 13px;
|
|
|
color: var(--el-text-color-regular);
|
|
|
word-break: break-all;
|
|
|
}
|