| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- <script setup lang="ts">
- import type { ReportModel } from '@/model';
- import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons-vue';
- import { h } from 'vue';
- const props = withDefaults(defineProps<{
- dataset: ReportModel;
- loading?: boolean;
- title?: string;
- collapsible?: boolean;
- hideTitle?: boolean;
- }>(), {
- title: '健康分析报告',
- collapsible: true,
- hideTitle: false,
- });
- const container = ref<HTMLDivElement | null>(null);
- const collapsed = defineModel('collapsed', { required: false, default: false });
- const collapse = (value: boolean) => {
- collapsed.value = value;
- container.value?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
- };
- const tongueExceptionPanelHeight = window.innerHeight * 0.8;
- const openTongueExceptionAnalysis = ref(false);
- const tongueExceptionPanelKeys = ref<string[]>([]);
- const tongueExceptionData = computed(() => {
- const keys = [ 'tongueColor', 'tongueCoatingColor', 'tongueShape', 'tongueCoating', 'bodyFluid', 'sublingualVein' ];
- const data: Record<string, string>[] = [];
- for ( const key of keys ) {
- const list = props.dataset?.[ key ]?.actualList ?? [];
- for ( const item of list ) if ( item?.contrast && item.contrast !== 's' ) data.push(item);
- }
- tongueExceptionPanelKeys.value = data.map(t => t.actualValue);
- return data;
- });
- defineExpose({
- elementTarget: container,
- });
- </script>
- <template>
- <div class="card report-card" ref="container">
- <div v-if="!props.hideTitle" class="card__header sticky flex justify-between items-center">
- <div class="card__title">
- <span>{{ props.title }}</span>
- <a-spin v-if="props.loading" size="small" style="margin-left: 4px;" />
- <a-space style="margin-left: 8px" v-if="collapsible">
- <a-tooltip :title="collapsed ? '展开' : '收起'">
- <a-button
- shape="circle" size="small" :icon="collapsed ? h(FullscreenOutlined) : h(FullscreenExitOutlined)"
- @click="collapse(!collapsed)"
- />
- </a-tooltip>
- </a-space>
- <a-space style="margin-left: 8px">
- <slot name="tool-bar" :reportId="props.dataset.id"></slot>
- </a-space>
- </div>
- </div>
- <div class="card__content" v-if="props.dataset.id">
- <div style="padding: 8px 0;background-color:#fff;" class="sticky top-0 z-10">
- <span>报告日期:</span>
- <slot name="select">
- <span style="margin-right: 8px;">{{ props.dataset.time }}</span>
- </slot>
- <slot name="analysis"></slot>
- <a-spin v-if="props.hideTitle && props.loading" size="small" style="margin-left: 4px;" />
- </div>
- <a-card class="card no-bordered background" size="small">
- <a-descriptions :column="3">
- <a-descriptions-item v-if="props.dataset.willillStateName" label="健康状态">
- {{ props.dataset.willillStateName }}
- </a-descriptions-item>
- <a-descriptions-item v-if="props.dataset.willillDegreeName" label="程度" :span="2">
- {{ props.dataset.willillDegreeName }}
- </a-descriptions-item>
- <a-descriptions-item v-if="props.dataset.willillFunctionName" label="表现">
- {{ props.dataset.willillFunctionName }}
- </a-descriptions-item>
- <a-descriptions-item v-if="props.dataset.constitutionGroupName" label="体质" :span="2">
- {{ props.dataset.constitutionGroupName }}
- </a-descriptions-item>
- </a-descriptions>
- </a-card>
- <template v-if="!props.collapsible || !collapsed">
- <a-card class="card background symptom-card" size="small">
- <p v-if="props.dataset.constitutionGroupDefinition">体质: {{ props.dataset.constitutionGroupDefinition }}</p>
- <a-descriptions :column="1" bordered size="small" :label-style="{width: '120px'}">
- <a-descriptions-item v-if="props.dataset.constitutionGroupGeneralCharacteristics" label="总体特征">
- {{ props.dataset.constitutionGroupGeneralCharacteristics }}
- </a-descriptions-item>
- <a-descriptions-item v-if="props.dataset.constitutionGroupPhysicalCharacteristics" label="形体特征">
- {{ props.dataset.constitutionGroupPhysicalCharacteristics }}
- </a-descriptions-item>
- <a-descriptions-item v-if="props.dataset.constitutionGroupPsychicCharacteristics" label="精神特征">
- {{ props.dataset.constitutionGroupPsychicCharacteristics }}
- </a-descriptions-item>
- <a-descriptions-item v-if="props.dataset.constitutionGroupCommonManifestations" label="常见表现">
- {{ props.dataset.constitutionGroupCommonManifestations }}
- </a-descriptions-item>
- <a-descriptions-item v-if="props.dataset.constitutionGroupDiseaseTendency" label="发病倾向">
- {{ props.dataset.constitutionGroupDiseaseTendency }}
- </a-descriptions-item>
- <a-descriptions-item v-if="props.dataset.constitutionGroupAdaptability" label="环境适应能力">
- {{ props.dataset.constitutionGroupAdaptability }}
- </a-descriptions-item>
- </a-descriptions>
- </a-card>
- <a-card class="card background" size="small" title="舌象分析" :loading="props.loading">
- <template #extra v-if="tongueExceptionData.length">
- <a-button type="dashed" size="small" danger @click="openTongueExceptionAnalysis=true">异常舌象分析
- </a-button>
- <vxe-modal
- title="异常舌象分析" v-model="openTongueExceptionAnalysis"
- resize show-maximize :width="600" :height="tongueExceptionPanelHeight"
- >
- <a-collapse :bordered="false" v-model:activeKey="tongueExceptionPanelKeys">
- <a-collapse-panel
- v-for="(panel, index) in tongueExceptionData" :key="panel.actualValue"
- :header="panel.actualValue"
- >
- <a-descriptions :column="1" bordered size="small" :label-style="{width: '120px',textAlign:'center' }">
- <a-descriptions-item>
- <div class="flex flex-row">
- <a-image v-if="panel.splitImage" :src="panel.splitImage" :width="200" :preview="true" />
- <a-space v-if="panel.attrs" direction="vertical">
- <a-tag v-for="v in panel.attrs" color="#cd201f" style="margin-left: 8px;margin-right: 0;">
- {{ v }}
- </a-tag>
- </a-space>
- </div>
- </a-descriptions-item>
- <a-descriptions-item label="特征">{{ panel.features }}</a-descriptions-item>
- <a-descriptions-item label="临床意义">{{ panel.clinicalSignificance }}</a-descriptions-item>
- </a-descriptions>
- </a-collapse-panel>
- </a-collapse>
- </vxe-modal>
- </template>
- <a-space class="w-full analysis-wrapper">
- <a-descriptions :column="3" bordered size="small">
- <a-descriptions-item style="font-size: 14px;font-weight: 500;">舌象维度</a-descriptions-item>
- <a-descriptions-item style="font-size: 14px;font-weight: 500;">检测结果</a-descriptions-item>
- <a-descriptions-item style="font-size: 14px;font-weight: 500;">标准值</a-descriptions-item>
- <template v-if="props.dataset.tongueColor?.actualList">
- <a-descriptions-item>舌色</a-descriptions-item>
- <a-descriptions-item>
- <span class="tongue-value" v-for="item in props.dataset.tongueColor.actualList" :key="item?.actualValue">
- <span>{{ item.actualValue }}</span>
- <span v-if="item.contrast !== 's'">({{ item.contrast }})</span>
- </span>
- </a-descriptions-item>
- <a-descriptions-item>{{ props.dataset.tongueColor.standardValue }}</a-descriptions-item>
- </template>
- <template v-if="props.dataset.tongueCoatingColor?.actualList">
- <a-descriptions-item>苔色</a-descriptions-item>
- <a-descriptions-item>
- <span class="tongue-value" v-for="item in props.dataset.tongueCoatingColor.actualList"
- :key="item?.actualValue"
- >
- <span>{{ item.actualValue }}</span>
- <span v-if="item.contrast !== 's'">({{ item.contrast }})</span>
- </span>
- </a-descriptions-item>
- <a-descriptions-item>{{ props.dataset.tongueCoatingColor.standardValue }}</a-descriptions-item>
- </template>
- <template v-if="props.dataset.tongueShape?.actualList">
- <a-descriptions-item>舌形</a-descriptions-item>
- <a-descriptions-item>
- <span class="tongue-value" v-for="item in props.dataset.tongueShape.actualList" :key="item?.actualValue">
- <span>{{ item.actualValue }}</span>
- <span v-if="item.contrast !== 's'">({{ item.contrast }})</span>
- </span>
- </a-descriptions-item>
- <a-descriptions-item>{{ props.dataset.tongueShape.standardValue }}</a-descriptions-item>
- </template>
- <template v-if="props.dataset.tongueCoating?.actualList">
- <a-descriptions-item>苔质</a-descriptions-item>
- <a-descriptions-item>
- <span class="tongue-value" v-for="item in props.dataset.tongueCoating.actualList" :key="item?.actualValue">
- <span>{{ item.actualValue }}</span>
- <span v-if="item.contrast !== 's'">({{ item.contrast }})</span>
- </span>
- </a-descriptions-item>
- <a-descriptions-item>{{ props.dataset.tongueCoating.standardValue }}</a-descriptions-item>
- </template>
- <template v-if="props.dataset.bodyFluid?.actualList">
- <a-descriptions-item>津液</a-descriptions-item>
- <a-descriptions-item>
- <span class="tongue-value" v-for="item in props.dataset.bodyFluid.actualList" :key="item?.actualValue">
- <span>{{ item.actualValue }}</span>
- <span v-if="item.contrast !== 's'">({{ item.contrast }})</span>
- </span>
- </a-descriptions-item>
- <a-descriptions-item>{{ props.dataset.bodyFluid.standardValue }}</a-descriptions-item>
- </template>
- <template v-if="props.dataset.sublingualVein?.actualList">
- <a-descriptions-item>舌下</a-descriptions-item>
- <a-descriptions-item>
- <span class="" v-for="item in props.dataset.sublingualVein.actualList" :key="item?.actualValue">
- <span>{{ item.actualValue }}</span>
- <span v-if="item.contrast !== 's'">({{ item.contrast }})</span>
- </span>
- </a-descriptions-item>
- <a-descriptions-item>{{ props.dataset.sublingualVein.standardValue }}</a-descriptions-item>
- </template>
- </a-descriptions>
- <a-image :width="200" :height="200" :src="props.dataset.upImg" :preview="true" />
- <a-image :width="200" :height="200" :src="props.dataset.downImg" :preview="true" />
- </a-space>
- </a-card>
- <template v-if="props.dataset?.faceAnalysisResult">
- <a-card class="card background" size="small" title="面象分析" :loading="props.loading">
- <a-space align="start" class="w-full analysis-wrapper">
- <div>{{ props.dataset.faceAnalysisResult }}</div>
- <a-image :width="200" :height="200" :src="props.dataset.faceImg" :preview="true" />
- </a-space>
- </a-card>
- </template>
- <a-card class="card background symptom-card" size="small" title="中医证素"
- v-if="props.dataset.factorItems?.length"
- >
- <a-descriptions :column="1" bordered size="small">
- <a-descriptions-item
- v-for="item in props.dataset.factorItems" :key="item.factorItemName"
- :label="item.factorItemName"
- >
- {{ item.factorItemDescription }}
- </a-descriptions-item>
- </a-descriptions>
- </a-card>
- <a-card class="card background symptom-card" size="small" title="中医证型"
- v-if="props.dataset.diagnoseSyndromes?.length"
- >
- <a-descriptions :column="1" bordered size="small">
- <a-descriptions-item v-for="item in props.dataset.diagnoseSyndromes" :key="item.diagnoseSyndromeName"
- :label="item.diagnoseSyndromeName"
- >
- {{ item.diagnoseSyndromeAnalysis }}
- </a-descriptions-item>
- </a-descriptions>
- </a-card>
- <slot></slot>
- </template>
- </div>
- <div v-else style="height: 40px;"></div>
- </div>
- </template>
- <style scoped lang="scss">
- @import "@/themes/report-card";
- .card__content {
- > .card {
- transition: all 200ms ease;
- &:not(:last-of-type) {
- margin-bottom: 12px;
- }
- }
- }
- .tongue-value {
- margin: 0 4px;
- &:first-of-type {
- margin-left: 0;
- }
- &:last-of-type {
- margin-right: 0;
- }
- }
- .symptom-card {
- :deep(.ant-descriptions-item-label) {
- padding: 8px 12px;
- width: 120px;
- text-align: center;
- }
- :deep(.ant-descriptions-item-content) {
- background-color: #fff;
- }
- }
- .analysis-wrapper {
- :deep(.ant-space-item) {
- &:first-of-type {
- flex: auto;
- }
- }
- }
- </style>
|