|
|
@@ -0,0 +1,158 @@
|
|
|
+<template>
|
|
|
+ <Dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ width="800px"
|
|
|
+ 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"
|
|
|
+ >
|
|
|
+ {{ 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>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="handleClosed">关闭</el-button>
|
|
|
+ </template>
|
|
|
+ </Dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { getElderlyItemsRound } from '@/api/elderly/nursing'
|
|
|
+import { formatBackendDateTime } from '@/utils/formatTime'
|
|
|
+import { getRoleSubmitLabel } from './roleSubmitLabel'
|
|
|
+
|
|
|
+defineOptions({ name: 'ElderlyItemsRoundDetail' })
|
|
|
+
|
|
|
+export type ElderlyItemsRoundDetailVO = {
|
|
|
+ id?: number
|
|
|
+ recorder?: string
|
|
|
+ role?: string
|
|
|
+ elderId?: number
|
|
|
+ elderName?: string
|
|
|
+ buildId?: number
|
|
|
+ floorId?: number
|
|
|
+ roomId?: number
|
|
|
+ roomName?: string
|
|
|
+ items?: string | string[]
|
|
|
+ voice?: string
|
|
|
+ remark?: string
|
|
|
+ tenantId?: number
|
|
|
+ /** 巡房时间(时间戳或与后端约定格式) */
|
|
|
+ roundTime?: string | number
|
|
|
+ createTime?: string | number
|
|
|
+ updateTime?: string | number
|
|
|
+ creator?: string
|
|
|
+ updater?: string
|
|
|
+}
|
|
|
+
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const loading = ref(false)
|
|
|
+const detail = reactive<ElderlyItemsRoundDetailVO>({})
|
|
|
+
|
|
|
+function emptyDetail() {
|
|
|
+ Object.keys(detail).forEach((k) => delete (detail as Recordable)[k])
|
|
|
+}
|
|
|
+
|
|
|
+/** items:按中英文逗号拆分;数组则每项再拆分。voice 仍用本函数(同分隔规则) */
|
|
|
+function splitDelimited(raw: unknown): string[] {
|
|
|
+ if (raw == null || raw === '') return []
|
|
|
+ if (Array.isArray(raw)) {
|
|
|
+ return raw.flatMap((x) => splitDelimited(String(x)))
|
|
|
+ }
|
|
|
+ return String(raw)
|
|
|
+ .split(/[,,]+/)
|
|
|
+ .map((s) => s.trim())
|
|
|
+ .filter(Boolean)
|
|
|
+}
|
|
|
+
|
|
|
+function formatItems(items: string | string[] | undefined) {
|
|
|
+ return splitDelimited(items)
|
|
|
+}
|
|
|
+
|
|
|
+const voiceParts = computed(() => {
|
|
|
+ const parts = splitDelimited(detail.voice)
|
|
|
+ const audios: string[] = []
|
|
|
+ const texts: string[] = []
|
|
|
+ for (const p of parts) {
|
|
|
+ if (/^https?:\/\//i.test(p)) {
|
|
|
+ audios.push(p)
|
|
|
+ } else {
|
|
|
+ texts.push(p)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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()
|
|
|
+ try {
|
|
|
+ const data = (await getElderlyItemsRound(id)) as ElderlyItemsRoundDetailVO
|
|
|
+ Object.assign(detail, data ?? {})
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+defineExpose({ open })
|
|
|
+
|
|
|
+const handleClosed = () => {
|
|
|
+ emptyDetail()
|
|
|
+ dialogVisible.value = false
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.voice-audio {
|
|
|
+ max-width: 100%;
|
|
|
+ height: 32px;
|
|
|
+ vertical-align: middle;
|
|
|
+}
|
|
|
+.voice-row + .voice-row {
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+.voice-text {
|
|
|
+ margin-top: 6px;
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+</style>
|