123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- <script setup lang="ts">
- import NavMiniProgram from '@/assets/images/mini-program.svg?url';
- import NavHomeSelect from '@/assets/images/nav-home.select.png?url';
- import NavPrint from '@/assets/images/nav-print.png?url';
- import NavScheme from '@/assets/images/nav-scheme.png?url';
- import PhysiqueChart from '@/modules/report/PhysiqueChart.vue';
- import SyndromeChart from '@/modules/report/SyndromeChart.vue';
- import { Notify, Toast } from '@/platform';
- import { getReportMethod, updateReportMethod } from '@/request/api';
- import { useRouteParams } from '@vueuse/router';
- import { useRequest, useWatcher } from 'alova/client';
- import { useRouter } from 'vue-router';
- const id = useRouteParams<string>('id');
- const { data, loading } = useWatcher(() => getReportMethod(id.value), [ id ], {
- initialData: {
- descriptionsTable: { column: [], data: [] },
- tongue: {},
- face: {},
- },
- immediate: true,
- }).onSuccess(({ data }) => {
- if ( data?.miniProgramURL && data.payLock ) panelOpen(100);
- })
- const { loading: uploading, send: upload } = useRequest(() => updateReportMethod(id.value, data.value), {
- immediate: false,
- middleware(_, next) {
- const hasConstitutionGroupImg = data.value.constitutionGroupImg;
- const hasFactorItemRadarImg = data.value[ '中医证素' ]?.length ? data.value.factorItemRadarImg : true;
- if ( hasConstitutionGroupImg && hasFactorItemRadarImg ) { next(); }
- },
- }).onSuccess(({ data: url }) => { data.value.reportURL = url; });
- let ReportPreview: Component;
- const reportPreviewProps = reactive({
- show: false,
- title: '',
- url: '',
- mode: '' as 'img' | 'qr',
- });
- async function print() {
- let url = data.value.reportURL;
- if ( !url ) url = await upload();
- if (!url) {
- Notify.warning(`未获取到报告地址,请联系管理员或重试`);
- return;
- }
- try {
- // @ts-ignore
- window.AIO.print(url);
- Toast.success(`开始打印`);
- } catch ( e ) {
- Notify.warning(`打印失败 (${ e.message })`, { duration: 1500 });
- setTimeout(() => {
- ReportPreview = defineAsyncComponent(() => import('./ReportPreview.vue'));
- reportPreviewProps.mode = 'qr';
- reportPreviewProps.title = '扫一扫 获取报告';
- reportPreviewProps.url = url;
- reportPreviewProps.show = true;
- }, 1500);
- }
- }
- async function miniProgram() {
- let url = data.value.miniProgramURL;
- if ( !url ) {
- Notify.warning(`未获取到小程序地址,请联系管理员或重试`);
- return;
- }
- panelOpen();
- }
- const router = useRouter();
- function toggle() {
- const path = `${ router.currentRoute.value.fullPath }/scheme`.replace(/\/{2,}/g, '/');
- router.replace({ path });
- }
- const panelHeight = ref(0);
- const panelProps = reactive({
- anchors: [0, window.innerWidth],
- contentDraggable: false,
- lockScroll: true,
- });
- const panelOpen = (min?: number) => {
- if ( min ) panelProps.anchors[ 0 ] = min;
- panelHeight.value = panelProps.anchors[1];
- };
- const scrollable = computed(() => !data.value.payLock &&
- panelHeight.value < panelProps.anchors[ 1 ] || panelHeight.value === 0,
- );
- </script>
- <template>
- <div class="report-wrapper">
- <div class="page-header flex py-4 px-4">
- <div class="grow shrink-0 h-full min-w-16"></div>
- <div class="grow-[3] shrink mx-2 flex flex-col justify-center overflow-hidden">
- <div class="font-bold text-3xl text-nowrap text-center tracking-wide overflow-ellipsis overflow-hidden">
- 健康分析报告
- </div>
- </div>
- <div class="grow shrink-0 h-full min-w-16">
- <router-link :to="{ path: '/screen' }" replace>
- <img class="size-8 object-scale-down" :src="NavHomeSelect" alt="返回首页" />
- </router-link>
- </div>
- </div>
- <div class="page-content flex flex-col overflow-hidden">
- <van-skeleton class="flex-auto" title :row="3" :loading>
- <div class="flex-auto" :class="{ 'overflow-y-auto': scrollable }">
- <div class="my-6 text-primary text-2xl text-center">报告日期:{{ data.date }}</div>
- <div class="card m-6 text-lg">
- <div class="card__title text-primary text-3xl font-bold"></div>
- <div class="card__content flex">
- <div class="flex-auto">
- <div class="flex items-center my-2">
- <span class="text-primary">结果显示您是:</span>
- <van-button class="decorate !text-primary-400">{{ data[ '结果' ] }}</van-button>
- </div>
- <div class="flex items-center my-2" v-if="data[ '程度' ]">
- <span class="text-grey">程度:</span>
- <span class="px-4 py-2 rounded-lg border border-primary-400 text-primary">{{ data[ '程度' ] }}</span>
- </div>
- <div class="my-2 text-grey" v-if="data[ '表现' ]">表现:{{ data[ '表现' ] }}</div>
- <div class="my-2 text-grey" v-if="data[ '体质' ]">体质:{{ data[ '体质' ] }}</div>
- </div>
- <div class="flex-none size-48 ml-4">
- <img class="size-full object-cover" src="@/assets/images/report-cover.png" alt="封面">
- </div>
- </div>
- </div>
- <div class="card m-6 text-lg">
- <div class="card__title mb-3 text-primary text-2xl font-bold">体质分析</div>
- <div class="card__content">
- <PhysiqueChart
- :dataset="data['体质图表']"
- v-model:snapshot="data.constitutionGroupImg" @update:snapshot="upload()"
- />
- <div class="my-2 text-primary" v-if="data[ '体质描述' ]">{{ data[ '体质描述' ] }}</div>
- <table class="mt-8 mb-2 w-full table-auto border border-collapse border-primary">
- <thead>
- <tr>
- <th class="border border-primary min-w-[140px]"
- v-for="(value, i) in data.descriptionsTable.column" :key="i"
- v-html="value"
- ></th>
- </tr>
- </thead>
- <tbody>
- <tr v-for="item in data.descriptionsTable?.data" :key="item[0]">
- <td class="py-4 px-2 border border-primary min-w-[140px]"
- :class="{'text-grey': i, 'text-primary': !i}"
- v-for="(value, i) in item" :key="i"
- v-html="value"
- ></td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- <AnalysisComponent title="舌象分析" v-bind="data.tongue" :cover="[]"></AnalysisComponent>
- <AnalysisComponent title="面象分析" v-bind="data.face">
- <template #content="{result, cover}">
- <div class="card__content flex">
- <div class="flex-auto text-grey mt-6">{{ result }}</div>
- <div class="flex-none w-2/4 max-h-96 ml-4">
- <img class="size-full object-scale-down" v-for="src in cover" :key="src" :src="src" alt="面象">
- </div>
- </div>
- </template>
- <template #exception><div><!--空占位符--></div></template>
- </AnalysisComponent>
- <div class="card m-6 text-lg" v-if="data['中医证素']?.length">
- <div class="card__title mb-3 text-primary text-2xl font-bold">中医证素</div>
- <div class="card__content">
- <SyndromeChart
- :dataset="data['中医证素']"
- v-model:snapshot="data.factorItemRadarImg" @update:snapshot="upload()"
- />
- <table class="mt-8 mb-2 w-full table-auto border border-collapse border-primary">
- <tbody>
- <tr v-for="item in data['中医证素']" :key="item.label">
- <td class="py-4 px-2 border border-primary text-primary text-center" v-html="item.label"></td>
- <td class="py-4 px-2 border border-primary text-grey" v-html="item.value"></td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- <div class="card m-6 text-lg" v-if="data['中医证型']?.length">
- <div class="card__title mb-3 text-primary text-2xl font-bold">中医证型</div>
- <div class="card__content">
- <div class="my-6 text-grey" v-for="item in data['中医证型']" :key="item.label">
- <div class="my-2 text-primary" v-html="item.label"></div>
- <div style="text-indent: 2em;" v-html="item.value"></div>
- </div>
- </div>
- </div>
- </div>
- </van-skeleton>
- <div class="flex-none flex justify-between py-2 nav-wrapper" style="background-color: #12312c;">
- <div class="m-auto min-w-16 text-center hover:text-primary" @click="toggle()">
- <img :src="NavScheme" alt="调理方案">
- <div class="mt-2">调理方案</div>
- </div>
- <div class="m-auto min-w-16 text-center hover:text-primary" v-if="data.miniProgramURL" @click="miniProgram()">
- <img :src="NavMiniProgram" alt="小程序">
- <div class="mt-2">小程序</div>
- </div>
- <div class="m-auto min-w-16 text-center hover:text-primary" @click="print()">
- <van-loading v-if="uploading" color="#38ff6e" style="font-size: 24px;" />
- <img v-else :src="NavPrint" alt="打印">
- <div class="mt-2">打印</div>
- </div>
- </div>
- <Component :is="ReportPreview" v-bind="reportPreviewProps" v-model:show="reportPreviewProps.show"></Component>
- <van-floating-panel v-model:height="panelHeight" v-bind="panelProps">
- <Transition>
- <div class="panel-content">
- <img
- class="size-full object-contain"
- v-if="panelHeight === panelProps.anchors[1] || panelHeight > panelProps.anchors[0] * 1.5"
- :src="data.miniProgramURL"
- alt="小程序码"
- />
- <div class="flex justify-center items-center" v-else @click="panelOpen()">
- <img class="h-10 w-10" src="@/assets/images/mini-program.svg" alt="小程序" />
- <span class="text-lg ml-2">小程序</span>
- </div>
- </div>
- </Transition>
- </van-floating-panel>
- </div>
- </div>
- </template>
- <style scoped lang="scss">
- .van-button.decorate {
- font-size: 20px;
- height: 62px;
- width: 240px;
- background-size: 80%;
- letter-spacing: 2px;
- }
- .text-grey {
- color: #e3e3e3;
- }
- .report-wrapper {
- .panel-content {
- padding: 0 var(--van-floating-panel-header-height) var(--van-floating-panel-header-height);
- }
- .v-enter-active,
- .v-leave-active {
- transition: opacity 0.5s ease;
- }
- .v-enter-from,
- .v-leave-to {
- opacity: 0;
- }
- }
- .overflow-y-auto {
- overflow-y: auto;
- }
- </style>
- <style lang="scss">
- .report-wrapper .card {
- padding: 24px;
- border-radius: 24px;
- box-shadow: inset 0 0 80px 0 #34a76b60;
- }
- .nav-wrapper {
- img {
- margin: auto;
- width: 36px;
- height: 36px;
- object-fit: scale-down;
- }
- }
- </style>
|