180 lines
4.1 KiB
Vue
180 lines
4.1 KiB
Vue
<template>
|
||
<div class="tracking-timeline-wrap">
|
||
<!-- 编辑模式:输入新备注 -->
|
||
<div v-if="!readonly && diagnosisId" class="timeline-input">
|
||
<el-input
|
||
v-model="draft"
|
||
type="textarea"
|
||
:rows="2"
|
||
placeholder="输入跟踪备注,回车换行;保存后将以「[HH:MM] 内容」追加到当天记录"
|
||
maxlength="1000"
|
||
show-word-limit
|
||
resize="none"
|
||
:disabled="saving"
|
||
/>
|
||
<div class="input-actions">
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
:loading="saving"
|
||
:disabled="!draft.trim()"
|
||
@click="handleSave"
|
||
>
|
||
添加
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 时间轴 -->
|
||
<div v-if="notes.length" class="tracking-timeline">
|
||
<div v-for="note in notes" :key="note.id" class="timeline-node">
|
||
<div class="timeline-dot"></div>
|
||
<div class="timeline-date">{{ note.note_date }}</div>
|
||
<div class="timeline-body">
|
||
<div v-if="note.content" class="timeline-content">
|
||
<div
|
||
v-for="(line, idx) in splitLines(note.content)"
|
||
:key="idx"
|
||
class="content-line"
|
||
>
|
||
{{ line }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<el-empty v-if="!notes.length && readonly" description="暂无跟踪备注" :image-size="48" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { diagnosisAddTrackingNote } from '@/api/tcm'
|
||
import feedback from '@/utils/feedback'
|
||
|
||
export interface TrackingNoteItem {
|
||
id: number
|
||
note_date: string
|
||
content: string | null
|
||
}
|
||
|
||
const props = defineProps<{
|
||
notes: TrackingNoteItem[]
|
||
diagnosisId?: number
|
||
readonly?: boolean
|
||
}>()
|
||
|
||
const emit = defineEmits<{ refresh: [] }>()
|
||
|
||
const draft = ref('')
|
||
const saving = ref(false)
|
||
|
||
const splitLines = (content: string | null): string[] => {
|
||
if (!content) return []
|
||
return content.split('\n').filter(Boolean)
|
||
}
|
||
|
||
const handleSave = async () => {
|
||
if (!props.diagnosisId) return
|
||
const content = draft.value.trim()
|
||
if (!content) return
|
||
saving.value = true
|
||
try {
|
||
await diagnosisAddTrackingNote({
|
||
diagnosis_id: props.diagnosisId,
|
||
tracking_content: content,
|
||
})
|
||
feedback.msgSuccess('已添加')
|
||
draft.value = ''
|
||
emit('refresh')
|
||
} finally {
|
||
saving.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.tracking-timeline-wrap {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 14px;
|
||
}
|
||
|
||
.timeline-input {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px dashed #ebeef5;
|
||
}
|
||
|
||
.input-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.tracking-timeline {
|
||
position: relative;
|
||
padding-left: 22px;
|
||
}
|
||
|
||
.tracking-timeline::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 6px;
|
||
top: 4px;
|
||
bottom: 4px;
|
||
width: 2px;
|
||
background: #ebeef5;
|
||
border-radius: 1px;
|
||
}
|
||
|
||
.timeline-node {
|
||
position: relative;
|
||
padding-bottom: 16px;
|
||
}
|
||
|
||
.timeline-node:last-child {
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.timeline-dot {
|
||
position: absolute;
|
||
left: -19px;
|
||
top: 5px;
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 50%;
|
||
background: #16a34a;
|
||
border: 2px solid #dcfce7;
|
||
}
|
||
|
||
.timeline-date {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 6px;
|
||
font-family: 'IBM Plex Mono', Consolas, monospace;
|
||
}
|
||
|
||
.timeline-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.timeline-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.content-line {
|
||
font-size: 12.5px;
|
||
color: #606266;
|
||
line-height: 1.6;
|
||
word-break: break-word;
|
||
}
|
||
</style>
|