123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- <script setup lang="ts">
- import { useTitle } from '@/hooks/useTitle';
- import { Dialog, Toast } from '@/platform';
- import { saveFileMethod, uploadFileMethod } from '@/request/api/camera.api';
- import { useVisitor } from '@/stores';
- import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
- import { useForm, useRequest } from 'alova/client';
- import Segmented, { type ConfigProps } from './camera.config';
- import Camera from './camera.vue';
- let audio: HTMLAudioElement | void;
- const router = useRouter();
- const title = useTitle();
- const visitor = useVisitor();
- const { form: dataset, loading: submitting, send: submit } = useForm(data => saveFileMethod(data), {
- initialForm: { patientId: visitor.patientId } as Record<string, any>,
- }).onSuccess(({ data }) => {
- visitor.resultId = data.resultId;
- router.replace(data.route);
- }).onError(() => {
- handle();
- step.value = 1;
- });
- const step = ref(0);
- const snapshot = ref<string | void>();
- const config = shallowRef<ConfigProps>();
- const showExample = ref(false);
- watch([ step, snapshot ], ([ step, snapshot ], old, onCleanup) => {
- const { before, after, ..._config } = Segmented[ step - 1 ];
- const old_audio = config.value?.audio;
- config.value = Object.assign(_config, snapshot ? after : before);
- if ( old_audio !== config.value.audio ) {
- audio?.pause();
- if ( config.value.audio && audio ) {
- audio.src = config.value.audio;
- audio.play()
- .catch(() => Dialog.show({ message: '开始拍摄', theme: 'round-button' }))
- .then(() => audio!.play());
- }
- }
- title.value = config.value.title;
- });
- const cameraRef = useTemplateRef<InstanceType<typeof Camera>>('camera');
- const { loading: uploading, send: update, abort: stop } = useRequest((file: File) => uploadFileMethod(file), {
- immediate: false,
- });
- const handle = () => {
- if ( submitting.value ) return;
- if ( !showExample.value ) { snapshot.value = cameraRef.value?.handle(); }
- showExample.value = false;
- stop();
- };
- const next = async () => {
- if ( uploading.value || submitting.value ) return;
- if ( snapshot.value ) {
- uploading.value = true;
- const key = config.value!.key;
- const toast = Toast.loading(50, { message: '上传中...' });
- const response = await fetch(snapshot.value);
- const blob = await response.blob();
- const file = new File(
- [ blob ],
- `${ dataset.value.patientId }_${ key?.replace?.(/img/ig, '') }`,
- { type: blob.type },
- );
- try {
- const { url } = await update(file);
- dataset.value[ key ] = url;
- } finally {
- toast.close();
- }
- }
- if ( step.value === Segmented.length ) {
- submit();
- } else {
- handle();
- step.value += 1;
- }
- };
- tryOnMounted(() => {
- audio = document.createElement('audio');
- document.body.appendChild(audio);
- });
- tryOnUnmounted(() => {
- audio?.pause();
- audio = void 0;
- stop();
- });
- </script>
- <template>
- <div class="flex flex-col">
- <header class="flex flex-col justify-center px-24">
- <div class="text-3xl text-center" :class="{ required: config?.required }">{{ config?.label }}</div>
- <div class="mt-8 text-lg text-center tracking-wider leading-10">{{ config?.description }}</div>
- </header>
- <main class="flex justify-center items-center">
- <Camera ref="camera" v-bind="config?.video" @loaded="step = 1;">
- <template #shade="{scale}">
- <component :is="config?.shade" :scale="scale"></component>
- <img v-if="showExample && config?.example" :src="config.example" alt="示例" @click="showExample = false" />
- </template>
- </Camera>
- <div v-if="config?.example"
- class="size-40 absolute -top-8 right-2 cursor-pointer hover:text-primary"
- @click="showExample = !showExample"
- >
- <img class="size-full object-scale-down" :src="config?.example" alt="示例" />
- <div class="mt-2 text-xl text-center">示例</div>
- </div>
- </main>
- <footer class="flex flex-col justify-center items-center">
- <div v-if="snapshot" class="flex justify-evenly w-full cursor-pointer">
- <div class="">
- <img class="h-20" src="@/assets/images/button-cancel.png" alt="重拍" @click="handle()" /></div>
- <div class="cursor-pointer">
- <img class="h-20" src="@/assets/images/button-confirm.png" alt="确认" @click="next()" />
- </div>
- </div>
- <div v-else-if="step" class="h-min text-center cursor-pointer hover:text-primary" @click="handle()">
- <button class="size-28 border-8 rounded-full hover:border-primary"></button>
- <div class="mt-8 text-3xl">{{ showExample ? '开始拍照' : '点击拍照' }}</div>
- </div>
- </footer>
- </div>
- </template>
- <style scoped lang="scss">
- header, footer {
- flex: 1 1 20%;
- }
- main {
- position: relative;;
- flex: 1 1 40%;
- }
- .required {
- &::before {
- content: "*";
- margin-right: 4px;
- color: #f53030;
- }
- }
- </style>
|