|
|
@@ -0,0 +1,840 @@
|
|
|
+<script lang="ts" setup>
|
|
|
+import type { VbenFormSchema } from '#/adapter/form';
|
|
|
+import type { VxeGridProps } from '#/adapter/vxe-table';
|
|
|
+import type { TreatmentModel } from '#/api/method/treatment';
|
|
|
+
|
|
|
+import {
|
|
|
+ computed,
|
|
|
+ nextTick,
|
|
|
+ onBeforeUnmount,
|
|
|
+ onMounted,
|
|
|
+ ref,
|
|
|
+ watch,
|
|
|
+} from 'vue';
|
|
|
+
|
|
|
+import { useVbenModal } from '@vben/common-ui';
|
|
|
+
|
|
|
+import { Image, notification } from 'ant-design-vue';
|
|
|
+
|
|
|
+import { useVbenForm } from '#/adapter/form';
|
|
|
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
|
+import {
|
|
|
+ endTreatmentMethod,
|
|
|
+ getAcupointDetailMethod,
|
|
|
+ getMeridianDetailMethod,
|
|
|
+ saveTreatmentMethod,
|
|
|
+} from '#/api/method/treatment';
|
|
|
+import { $t } from '#/locales';
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ countdown?: number;
|
|
|
+ index?: number;
|
|
|
+ patient?: TreatmentModel.Patient;
|
|
|
+ treatment?: TreatmentModel.TreatmentDetail;
|
|
|
+}>();
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ refreshData: [payload?: { minutes?: number; treatmentId?: string }];
|
|
|
+ startTreatment: [payload: { minutes: number; treatmentId: string }];
|
|
|
+}>();
|
|
|
+
|
|
|
+const treatmentDetail = computed<TreatmentModel.TreatmentDetail>(() => {
|
|
|
+ if (props.treatment) {
|
|
|
+ const result = props.treatment as unknown as TreatmentModel.TreatmentDetail;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ return {} as TreatmentModel.TreatmentDetail;
|
|
|
+});
|
|
|
+
|
|
|
+let registerStatusWatchOnce = false;
|
|
|
+
|
|
|
+const isInProgress = ref(false);
|
|
|
+const isCompleted = computed(() => treatmentDetail.value?.itemState === 2);
|
|
|
+
|
|
|
+// 操作记录数据
|
|
|
+const operatesData = computed(() => {
|
|
|
+ if (!props.treatment) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const data = props.treatment.operates || [];
|
|
|
+ return data;
|
|
|
+});
|
|
|
+
|
|
|
+// 穴位数据(包含说明行)
|
|
|
+const acupointData = computed(() => {
|
|
|
+ if (!props.treatment) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const acupoints = props.treatment.acupoints || [];
|
|
|
+ const data = acupoints.map((acupoint, index) => ({
|
|
|
+ id: acupoint.id,
|
|
|
+ index: (index + 1) as any,
|
|
|
+ acupointName: acupoint.acupointName,
|
|
|
+ acuType: acupoint.acuType, // 添加 acuType 字段
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 将穴位数据按4个一组重新组织,每行4个穴位
|
|
|
+ const groupedData = [];
|
|
|
+ for (let i = 0; i < data.length; i += 4) {
|
|
|
+ const rowData = data.slice(i, i + 4);
|
|
|
+ // 确保每行都有4个元素,不足的用空对象填充
|
|
|
+ while (rowData.length < 4) {
|
|
|
+ rowData.push({
|
|
|
+ id: `empty-${i + rowData.length}`,
|
|
|
+ index: '',
|
|
|
+ acupointName: '',
|
|
|
+ acuType: '',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ groupedData.push({
|
|
|
+ id: `row-${Math.floor(i / 4)}`,
|
|
|
+ acupoints: rowData,
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加说明行(永远在最后)
|
|
|
+ if (props.treatment.describe) {
|
|
|
+ groupedData.push({
|
|
|
+ id: 'describe',
|
|
|
+ acupoints: [
|
|
|
+ {
|
|
|
+ id: 'describe',
|
|
|
+ index: '说明',
|
|
|
+ acupointName: props.treatment.describe,
|
|
|
+ acuType: '',
|
|
|
+ },
|
|
|
+ { id: 'empty-1', index: '', acupointName: '', acuType: '' },
|
|
|
+ { id: 'empty-2', index: '', acupointName: '', acuType: '' },
|
|
|
+ { id: 'empty-3', index: '', acupointName: '', acuType: '' },
|
|
|
+ ],
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return groupedData;
|
|
|
+});
|
|
|
+
|
|
|
+// 操作记录表格配置
|
|
|
+const operateGridOptions = computed(
|
|
|
+ (): VxeGridProps<TreatmentModel.TreatmentDetail['operates'][0]> => ({
|
|
|
+ columns: [
|
|
|
+ {
|
|
|
+ field: 'operateDate',
|
|
|
+ title: $t('treatment.detail.operateDate'),
|
|
|
+ width: 160,
|
|
|
+ sortable: true,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ field: 'operateUserName',
|
|
|
+ title: $t('treatment.detail.operateUser'),
|
|
|
+ width: 100,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ field: 'remark',
|
|
|
+ title: $t('treatment.detail.treatmentRemark'),
|
|
|
+ minWidth: 200,
|
|
|
+ showOverflow: 'tooltip' as const,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ field: 'treatmentImageUrl',
|
|
|
+ title: $t('treatment.detail.treatmentPhoto'),
|
|
|
+ width: 120,
|
|
|
+ slots: { default: 'treatmentImage' },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ data: operatesData.value,
|
|
|
+ keepSource: false,
|
|
|
+ pagerConfig: {
|
|
|
+ enabled: false,
|
|
|
+ },
|
|
|
+ height: 'auto',
|
|
|
+ maxHeight: 300,
|
|
|
+ border: true,
|
|
|
+ showOverflow: 'tooltip' as const,
|
|
|
+ scrollY: { enabled: true },
|
|
|
+ }),
|
|
|
+);
|
|
|
+
|
|
|
+const [OperateGrid, operateGridApi] = useVbenVxeGrid({
|
|
|
+ gridOptions: operateGridOptions as any,
|
|
|
+});
|
|
|
+
|
|
|
+// 监听 props.treatment 变化,强制刷新表格
|
|
|
+watch(
|
|
|
+ () => props.treatment?.id,
|
|
|
+ (newId, oldId) => {
|
|
|
+ if (newId && newId !== oldId) {
|
|
|
+ nextTick(() => {
|
|
|
+ operateGridApi?.grid?.reloadData?.(operatesData.value);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: false },
|
|
|
+);
|
|
|
+
|
|
|
+const formatCountdown = (sec: number) => {
|
|
|
+ const m = Math.floor(sec / 60)
|
|
|
+ .toString()
|
|
|
+ .padStart(2, '0');
|
|
|
+ const s = Math.floor(sec % 60)
|
|
|
+ .toString()
|
|
|
+ .padStart(2, '0');
|
|
|
+ return `${m}:${s}`;
|
|
|
+};
|
|
|
+
|
|
|
+function toTsWithOffset(input?: Date | number | string) {
|
|
|
+ if (!input) return 0;
|
|
|
+ if (typeof input === 'number') return input;
|
|
|
+ if (input instanceof Date) return input.getTime();
|
|
|
+ // 兼容 2025-10-13T15:09:26.000+0800
|
|
|
+ const normalized = String(input).replace(/([+-]\d{2})(\d{2})$/, '$1:$2');
|
|
|
+ const t = new Date(normalized).getTime();
|
|
|
+ return Number.isFinite(t) ? t : 0;
|
|
|
+}
|
|
|
+
|
|
|
+function diffSeconds(
|
|
|
+ from?: Date | number | string,
|
|
|
+ to: Date | number | string = Date.now(),
|
|
|
+) {
|
|
|
+ const ms = toTsWithOffset(to) - toTsWithOffset(from);
|
|
|
+ return Math.max(0, Math.floor(ms / 1000));
|
|
|
+}
|
|
|
+
|
|
|
+const nowTick = ref(Date.now());
|
|
|
+let tickTimer: null | number = null;
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ tickTimer = window.setInterval(() => {
|
|
|
+ nowTick.value = Date.now();
|
|
|
+ }, 1000);
|
|
|
+});
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ if (tickTimer) {
|
|
|
+ clearInterval(tickTimer);
|
|
|
+ tickTimer = null;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const remainSeconds = computed(() => {
|
|
|
+ console.warn('props.countdown:', props.countdown);
|
|
|
+ if (props.countdown !== undefined) {
|
|
|
+ return Math.max(0, props.countdown);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有父组件倒计时,则使用本地计算
|
|
|
+ const d: any = treatmentDetail.value || {};
|
|
|
+ const cur: any = d.currentOperate || {};
|
|
|
+ console.warn('cur:', cur);
|
|
|
+ console.warn('d:', d);
|
|
|
+ const totalMin = cur.treatmentTime ?? d.treatmentTime;
|
|
|
+ const upd = cur.updateTime ?? d.updateTime ?? cur.operateDate;
|
|
|
+ const total = Math.max(0, Math.floor(Number(totalMin || 0) * 60));
|
|
|
+ const passed = diffSeconds(upd, nowTick.value);
|
|
|
+ return Math.max(0, total - passed);
|
|
|
+});
|
|
|
+
|
|
|
+const treatmentStartTime = ref<null | number>(null);
|
|
|
+const isEndingTreatment = ref(false);
|
|
|
+
|
|
|
+// 计算是否倒计时结束
|
|
|
+const isTimerFinished = computed(() => {
|
|
|
+ console.warn('isInProgress.value:', isInProgress.value);
|
|
|
+ console.warn('remainSeconds.value:', remainSeconds.value);
|
|
|
+ return isInProgress.value && remainSeconds.value <= 0;
|
|
|
+});
|
|
|
+
|
|
|
+// vben 弹窗 + 表单:还原“开始治疗”弹窗
|
|
|
+const startSchemas: VbenFormSchema[] = [
|
|
|
+ {
|
|
|
+ fieldName: 'remark',
|
|
|
+ label: $t('treatment.detail.treatmentRemark'),
|
|
|
+ component: 'Textarea',
|
|
|
+ componentProps: { rows: 4, placeholder: $t('treatment.detail.input') },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ fieldName: 'treatmentImageUrl',
|
|
|
+ label: $t('treatment.detail.treatmentPhoto'),
|
|
|
+ component: 'Avatar',
|
|
|
+ componentProps: {
|
|
|
+ accept: 'image/*',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ fieldName: 'treatmentTime',
|
|
|
+ label: $t('treatment.detail.treatmentTime'),
|
|
|
+ component: 'InputNumber',
|
|
|
+ componentProps: { min: 1, placeholder: $t('treatment.detail.input') },
|
|
|
+ suffix: $t('treatment.detail.minutes'),
|
|
|
+ rules: 'required',
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
+const [Form, formApi] = useVbenForm({
|
|
|
+ schema: startSchemas,
|
|
|
+ showDefaultActions: false,
|
|
|
+});
|
|
|
+
|
|
|
+const [Modal, modalApi] = useVbenModal({
|
|
|
+ title: $t('treatment.detail.startTreatment'),
|
|
|
+ closeOnClickModal: false,
|
|
|
+ async onConfirm() {
|
|
|
+ const { valid } = await formApi.validate();
|
|
|
+ if (!valid) return;
|
|
|
+ const values = await formApi.getValues();
|
|
|
+ const minutes = Number(values.treatmentTime);
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (treatmentDetail.value.itemState === 1) {
|
|
|
+ values.id = treatmentDetail.value.currentOperate.id;
|
|
|
+ }
|
|
|
+
|
|
|
+ values.recordId = treatmentDetail.value.id;
|
|
|
+ await saveTreatmentMethod(values);
|
|
|
+ notification.success({ message: $t('treatment.detail.saveSuccess') });
|
|
|
+
|
|
|
+ // 只有在保存成功后才设置治疗进行中状态
|
|
|
+ // 这样可以避免在数据未更新时立即触发倒计时结束
|
|
|
+ isInProgress.value = true;
|
|
|
+ treatmentStartTime.value = Date.now();
|
|
|
+
|
|
|
+ // 触发本地计时
|
|
|
+ emit('startTreatment', {
|
|
|
+ treatmentId: treatmentDetail.value.id,
|
|
|
+ minutes: Math.max(1, minutes),
|
|
|
+ });
|
|
|
+
|
|
|
+ // 触发刷新数据事件,更新标签页计数
|
|
|
+ emit('refreshData', { treatmentId: treatmentDetail.value.id });
|
|
|
+
|
|
|
+ await modalApi.close();
|
|
|
+ } catch (error) {
|
|
|
+ // 如果保存失败,确保不设置治疗进行中状态
|
|
|
+ isInProgress.value = false;
|
|
|
+ notification.error({
|
|
|
+ message:
|
|
|
+ error instanceof Error
|
|
|
+ ? error.message
|
|
|
+ : $t('treatment.detail.saveFailure'),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+// 穴位详情弹窗
|
|
|
+const selectedAcupoint = ref<any | null>(null);
|
|
|
+const [AcupointModal, acupointModalApi] = useVbenModal({
|
|
|
+ title: $t('treatment.detail.detail'),
|
|
|
+ footer: false, // 不显示确认/取消
|
|
|
+ closable: true,
|
|
|
+});
|
|
|
+
|
|
|
+const handleStartTreatment = (treatment: TreatmentModel.TreatmentDetail) => {
|
|
|
+ // 基础默认值
|
|
|
+ const baseValues: Record<string, any> = {
|
|
|
+ recordId: treatment.id,
|
|
|
+ treatmentTime: '',
|
|
|
+ remark: '',
|
|
|
+ treatmentImageUrl: '',
|
|
|
+ };
|
|
|
+
|
|
|
+ // 若处于治疗中,则回填当前操作项数据,供用户修改
|
|
|
+ const maybeCurrent = (treatmentDetail.value as any)?.currentOperate;
|
|
|
+ if (treatmentDetail.value?.itemState === 1 && maybeCurrent) {
|
|
|
+ formApi.setValues({
|
|
|
+ ...baseValues,
|
|
|
+ id: maybeCurrent.id,
|
|
|
+ remark: maybeCurrent.remark ?? '',
|
|
|
+ treatmentImageUrl: maybeCurrent.treatmentImageUrl ?? '',
|
|
|
+ treatmentTime: Number(
|
|
|
+ maybeCurrent.treatmentTime ??
|
|
|
+ maybeCurrent.minutes ??
|
|
|
+ baseValues.treatmentTime,
|
|
|
+ ),
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ formApi.setValues(baseValues);
|
|
|
+ }
|
|
|
+
|
|
|
+ modalApi.open();
|
|
|
+};
|
|
|
+
|
|
|
+if (!registerStatusWatchOnce) {
|
|
|
+ registerStatusWatchOnce = true;
|
|
|
+ watch(
|
|
|
+ () => props.treatment?.itemState,
|
|
|
+ (status) => {
|
|
|
+ console.warn(
|
|
|
+ '治疗状态变化:',
|
|
|
+ status,
|
|
|
+ '当前isInProgress:',
|
|
|
+ isInProgress.value,
|
|
|
+ );
|
|
|
+ if (status === 1) {
|
|
|
+ isInProgress.value = true;
|
|
|
+ treatmentStartTime.value = Date.now();
|
|
|
+ } else if (status === 2) {
|
|
|
+ isInProgress.value = false;
|
|
|
+ treatmentStartTime.value = null;
|
|
|
+ console.warn('治疗已完成,设置isInProgress为false');
|
|
|
+ } else {
|
|
|
+ isInProgress.value = false;
|
|
|
+ treatmentStartTime.value = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true },
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+// 监听计时结束,调用治疗结束接口
|
|
|
+watch(
|
|
|
+ () => isTimerFinished.value,
|
|
|
+ async (finished) => {
|
|
|
+ if (!finished) return;
|
|
|
+
|
|
|
+ // 防止重复调用
|
|
|
+ if (isEndingTreatment.value) {
|
|
|
+ console.warn('治疗结束接口正在调用中,跳过重复调用');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ isEndingTreatment.value = true;
|
|
|
+ console.warn('计时结束,开始调用治疗结束接口:', treatmentDetail.value.id);
|
|
|
+ try {
|
|
|
+ await endTreatmentMethod([treatmentDetail.value.id]);
|
|
|
+ notification.success({ message: $t('treatment.detail.endSuccess') });
|
|
|
+ // 刷新页面数据
|
|
|
+ emit('refreshData', { treatmentId: treatmentDetail.value.id });
|
|
|
+ console.warn('治疗结束接口调用成功');
|
|
|
+ } catch (error) {
|
|
|
+ console.error('治疗结束失败:', error);
|
|
|
+ } finally {
|
|
|
+ isEndingTreatment.value = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: false },
|
|
|
+);
|
|
|
+// 点击穴位:弹出详情弹窗
|
|
|
+const handleAcupointClick = async (
|
|
|
+ acupoint: TreatmentModel.TreatmentDetail['acupoints'][0],
|
|
|
+) => {
|
|
|
+ switch (Number(acupoint.acuType)) {
|
|
|
+ case 1: {
|
|
|
+ try {
|
|
|
+ // 获取穴位
|
|
|
+ const detail = await getAcupointDetailMethod(acupoint.id);
|
|
|
+ selectedAcupoint.value = { ...acupoint, ...detail } as any;
|
|
|
+ } catch {
|
|
|
+ notification.error({
|
|
|
+ message: $t('treatment.detail.getAcupointDetailFailure'),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case 2: {
|
|
|
+ // 获取经络
|
|
|
+ try {
|
|
|
+ const detail = await getMeridianDetailMethod(acupoint.id);
|
|
|
+ selectedAcupoint.value = { ...acupoint, ...detail } as any;
|
|
|
+ } catch {
|
|
|
+ notification.error({
|
|
|
+ message: $t('treatment.detail.getMeridianDetailFailure'),
|
|
|
+ });
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ acupointModalApi.open();
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="h-full flex-1 overflow-y-auto bg-white">
|
|
|
+ <div class="p-3">
|
|
|
+ <!-- 患者信息头部 -->
|
|
|
+ <div v-if="patient" class="mb-6 flex items-center justify-between">
|
|
|
+ <!-- 用户信息 -->
|
|
|
+ <div class="ml-4 flex items-center space-x-2">
|
|
|
+ <div
|
|
|
+ class="flex h-8 w-8 items-center justify-center rounded-full bg-gray-300"
|
|
|
+ v-if="patient.sex || patient.age || patient.phone"
|
|
|
+ >
|
|
|
+ <svg
|
|
|
+ class="h-5 w-5 text-gray-600"
|
|
|
+ fill="currentColor"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
+ >
|
|
|
+ <path
|
|
|
+ d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"
|
|
|
+ />
|
|
|
+ </svg>
|
|
|
+ </div>
|
|
|
+ <div class="flex text-sm">
|
|
|
+ <div class="font-medium">{{ patient?.name }}</div>
|
|
|
+ <div class="ml-4 mr-2 text-gray-500">
|
|
|
+ {{ patient?.sex }}
|
|
|
+ </div>
|
|
|
+ <div class="text-gray-500">{{ patient?.phone }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 治疗详情 -->
|
|
|
+ <div v-if="treatmentDetail" class="space-y-6"></div>
|
|
|
+ <!-- 治疗项目信息 -->
|
|
|
+ <div class="rounded-lg bg-gray-50 p-3">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <h3
|
|
|
+ class="text-lg font-semibold text-gray-800"
|
|
|
+ v-if="treatmentDetail?.itemName"
|
|
|
+ >
|
|
|
+ {{ (props?.index ?? 0) + 1 }}、{{ treatmentDetail?.itemName }}
|
|
|
+ </h3>
|
|
|
+ <div class="flex items-center space-x-3">
|
|
|
+ <div
|
|
|
+ v-if="isInProgress && !isTimerFinished"
|
|
|
+ class="text-sm text-yellow-600"
|
|
|
+ >
|
|
|
+ {{ $t('treatment.detail.remainingTime') }}:{{
|
|
|
+ formatCountdown(remainSeconds)
|
|
|
+ }}
|
|
|
+ </div>
|
|
|
+ <button
|
|
|
+ @click="handleStartTreatment(treatmentDetail)"
|
|
|
+ class="rounded-lg px-6 py-2 text-white transition-colors"
|
|
|
+ v-if="treatmentDetail?.itemName"
|
|
|
+ :class="[
|
|
|
+ isCompleted
|
|
|
+ ? 'cursor-not-allowed bg-green-600'
|
|
|
+ : isInProgress
|
|
|
+ ? 'bg-yellow-500 hover:bg-yellow-600'
|
|
|
+ : 'bg-blue-600 hover:bg-blue-700',
|
|
|
+ ]"
|
|
|
+ :disabled="isCompleted"
|
|
|
+ >
|
|
|
+ {{
|
|
|
+ isCompleted
|
|
|
+ ? $t('treatment.patient.complete')
|
|
|
+ : isInProgress
|
|
|
+ ? $t('treatment.patient.treatmenting')
|
|
|
+ : $t('treatment.detail.start')
|
|
|
+ }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mb-2 grid grid-cols-3">
|
|
|
+ <div v-if="treatmentDetail?.planType">
|
|
|
+ <span class="text-sm text-gray-600">
|
|
|
+ {{ $t('treatment.detail.schemeType') }}:
|
|
|
+ </span>
|
|
|
+ <span class="ml-2 font-medium">{{ treatmentDetail.planType }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="treatmentDetail?.frequency">
|
|
|
+ <span class="text-sm text-gray-600">
|
|
|
+ {{ $t('treatment.detail.frequency') }}:
|
|
|
+ </span>
|
|
|
+ <span class="ml-2 font-medium">
|
|
|
+ {{ treatmentDetail.frequency }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div v-if="treatmentDetail.totalNum">
|
|
|
+ <span class="text-sm text-gray-600">
|
|
|
+ {{ $t('treatment.detail.totalNum') }}:
|
|
|
+ </span>
|
|
|
+ <span class="ml-2 font-medium">
|
|
|
+ {{ treatmentDetail.completeNum }} /
|
|
|
+ {{ treatmentDetail.totalNum }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 穴位表格 -->
|
|
|
+ <div>
|
|
|
+ <div class="overflow-hidden rounded-sm border border-gray-300">
|
|
|
+ <!-- 表头 -->
|
|
|
+ <div
|
|
|
+ class="border-b border-gray-300"
|
|
|
+ style="background-color: #f5f4f5"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="grid"
|
|
|
+ style="
|
|
|
+ grid-template-columns: 80px 1fr 80px 1fr 80px 1fr 80px 1fr;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="border-r border-gray-300 px-4 py-3 text-center font-medium text-gray-700"
|
|
|
+ >
|
|
|
+ {{ $t('treatment.detail.sequence') }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="border-r border-gray-300 px-4 py-3 text-center font-medium text-gray-700"
|
|
|
+ >
|
|
|
+ {{ $t('treatment.detail.acupointOrMeridian') }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="border-r border-gray-300 px-4 py-3 text-center font-medium text-gray-700"
|
|
|
+ >
|
|
|
+ {{ $t('treatment.detail.sequence') }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="border-r border-gray-300 px-4 py-3 text-center font-medium text-gray-700"
|
|
|
+ >
|
|
|
+ {{ $t('treatment.detail.acupointOrMeridian') }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="border-r border-gray-300 px-4 py-3 text-center font-medium text-gray-700"
|
|
|
+ >
|
|
|
+ {{ $t('treatment.detail.sequence') }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="border-r border-gray-300 px-4 py-3 text-center font-medium text-gray-700"
|
|
|
+ >
|
|
|
+ {{ $t('treatment.detail.acupointOrMeridian') }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="border-r border-gray-300 px-4 py-3 text-center font-medium text-gray-700"
|
|
|
+ >
|
|
|
+ {{ $t('treatment.detail.sequence') }}
|
|
|
+ </div>
|
|
|
+ <div class="px-4 py-3 text-center font-medium text-gray-700">
|
|
|
+ {{ $t('treatment.detail.acupointOrMeridian') }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 表格内容 -->
|
|
|
+ <div
|
|
|
+ v-for="row in acupointData"
|
|
|
+ :key="row.id"
|
|
|
+ class="border-b border-gray-300 last:border-b-0"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-if="row.id === 'describe'"
|
|
|
+ class="grid"
|
|
|
+ style="grid-template-columns: 80px 1fr"
|
|
|
+ >
|
|
|
+ <!-- 说明标题列 -->
|
|
|
+ <div class="border-r border-gray-300 px-4 py-3 text-center">
|
|
|
+ <div class="text-sm font-medium text-gray-600">说明</div>
|
|
|
+ </div>
|
|
|
+ <!-- 说明内容列 -->
|
|
|
+ <div class="px-4 py-3">
|
|
|
+ <div
|
|
|
+ class="whitespace-pre-line text-sm leading-relaxed text-gray-700"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[0]?.acupointName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="grid"
|
|
|
+ style="
|
|
|
+ grid-template-columns: 80px 1fr 80px 1fr 80px 1fr 80px 1fr;
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <!-- 第一个穴位:序号列 -->
|
|
|
+ <div class="border-r border-gray-300 px-4 py-3 text-center">
|
|
|
+ <div
|
|
|
+ v-if="row.acupoints[0]?.acupointName"
|
|
|
+ class="text-sm font-medium text-gray-600"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[0].index }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 第一个穴位:穴位列 -->
|
|
|
+ <div class="border-r border-gray-300 px-4 py-3 text-center">
|
|
|
+ <div
|
|
|
+ v-if="row.acupoints[0]?.acupointName"
|
|
|
+ class="text-black-600 hover:text-black-800 cursor-pointer hover:underline"
|
|
|
+ @click.stop="handleAcupointClick(row.acupoints[0] as any)"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[0].acupointName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 第二个穴位:序号列 -->
|
|
|
+ <div class="border-r border-gray-300 px-4 py-3 text-center">
|
|
|
+ <div
|
|
|
+ v-if="row.acupoints[1]?.acupointName"
|
|
|
+ class="text-sm font-medium text-gray-600"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[1].index }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 第二个穴位:穴位列 -->
|
|
|
+ <div class="border-r border-gray-300 px-4 py-3 text-center">
|
|
|
+ <div
|
|
|
+ v-if="row.acupoints[1]?.acupointName"
|
|
|
+ class="text-black-600 hover:text-black-800 cursor-pointer hover:underline"
|
|
|
+ @click.stop="handleAcupointClick(row.acupoints[1] as any)"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[1].acupointName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 第三个穴位:序号列 -->
|
|
|
+ <div class="border-r border-gray-300 px-4 py-3 text-center">
|
|
|
+ <div
|
|
|
+ v-if="row.acupoints[2]?.acupointName"
|
|
|
+ class="text-sm font-medium text-gray-600"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[2].index }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 第三个穴位:穴位列 -->
|
|
|
+ <div class="border-r border-gray-300 px-4 py-3 text-center">
|
|
|
+ <div
|
|
|
+ v-if="row.acupoints[2]?.acupointName"
|
|
|
+ class="text-black-600 hover:text-black-800 cursor-pointer hover:underline"
|
|
|
+ @click.stop="handleAcupointClick(row.acupoints[2] as any)"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[2].acupointName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 第四个穴位:序号列 -->
|
|
|
+ <div class="border-r border-gray-300 px-4 py-3 text-center">
|
|
|
+ <div
|
|
|
+ v-if="row.acupoints[3]?.acupointName"
|
|
|
+ class="text-sm font-medium text-gray-600"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[3].index }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 第四个穴位:穴位列 -->
|
|
|
+ <div class="px-4 py-3 text-center">
|
|
|
+ <div
|
|
|
+ v-if="row.acupoints[3]?.acupointName"
|
|
|
+ class="text-black-600 hover:text-black-800 cursor-pointer hover:underline"
|
|
|
+ @click.stop="handleAcupointClick(row.acupoints[3] as any)"
|
|
|
+ >
|
|
|
+ {{ row.acupoints[3].acupointName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- end 穴位表格 -->
|
|
|
+
|
|
|
+ <!-- 开具信息 -->
|
|
|
+ <div class="flex flex-col items-end justify-between pt-3">
|
|
|
+ <!-- 诊断信息 -->
|
|
|
+ <div class="align-end flex flex-col items-end rounded-lg pt-5">
|
|
|
+ <div class="flex space-x-6 text-sm">
|
|
|
+ <div v-if="treatmentDetail.diagnosis">
|
|
|
+ <span class="text-gray-600"
|
|
|
+ >{{ $t('treatment.detail.diagnosis') }}:</span
|
|
|
+ >
|
|
|
+ <span class="ml-2">{{ treatmentDetail.diagnosis }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="treatmentDetail.behavior">
|
|
|
+ <span class="text-gray-600"
|
|
|
+ >{{ $t('treatment.detail.appearance') }}:</span
|
|
|
+ >
|
|
|
+ <span class="ml-2">{{ treatmentDetail.behavior }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="treatmentDetail.constitution">
|
|
|
+ <span class="text-gray-600"
|
|
|
+ >{{ $t('treatment.detail.constitution') }}:</span
|
|
|
+ >
|
|
|
+ <span class="ml-2">{{ treatmentDetail.constitution }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="mb-2 mt-3 flex space-x-4 text-sm">
|
|
|
+ <div v-if="treatmentDetail.issueInstitutionName">
|
|
|
+ <span class="text-gray-600"
|
|
|
+ >{{ $t('treatment.detail.openOrganization') }}:</span
|
|
|
+ >
|
|
|
+ <span class="ml-2">{{ treatmentDetail.issueInstitutionName }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="treatmentDetail.issueDoctorName">
|
|
|
+ <span class="text-gray-600"
|
|
|
+ >{{ $t('treatment.detail.openDoctor') }}:</span
|
|
|
+ >
|
|
|
+ <span class="ml-2">{{ treatmentDetail.issueDoctorName }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="treatmentDetail.issueDate">
|
|
|
+ <span class="text-gray-600"
|
|
|
+ >{{ $t('treatment.detail.openTime') }}:</span
|
|
|
+ >
|
|
|
+ <span class="ml-2">{{ treatmentDetail.issueDate }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 操作记录 -->
|
|
|
+ <div class="rounded-lg" v-if="operatesData.length > 0">
|
|
|
+ <OperateGrid>
|
|
|
+ <template #treatmentImage="{ row }">
|
|
|
+ <Image
|
|
|
+ :src="row.treatmentImageUrl"
|
|
|
+ height="30"
|
|
|
+ width="30"
|
|
|
+ v-if="row.treatmentImageUrl"
|
|
|
+ />
|
|
|
+ <span v-else class="text-gray-400">-</span>
|
|
|
+ </template>
|
|
|
+ </OperateGrid>
|
|
|
+ </div>
|
|
|
+ <!-- end 操作记录 -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- vben 弹窗:开始治疗 -->
|
|
|
+ <Modal>
|
|
|
+ <div class="px-4 pb-2">
|
|
|
+ <Form />
|
|
|
+ </div>
|
|
|
+ </Modal>
|
|
|
+
|
|
|
+ <!-- vben 弹窗:穴位详情 -->
|
|
|
+ <AcupointModal>
|
|
|
+ <div class="p-0">
|
|
|
+ <div class="p-2">
|
|
|
+ <div class="gap-6">
|
|
|
+ <div class="w-full space-y-3">
|
|
|
+ <div
|
|
|
+ class="flex rounded-xl pl-6"
|
|
|
+ v-if="selectedAcupoint?.acupointName"
|
|
|
+ >
|
|
|
+ <div class="mb-2 text-sm text-gray-600">
|
|
|
+ {{ $t('treatment.detail.acupointName') }}:
|
|
|
+ {{ selectedAcupoint?.acupointName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="flex rounded-xl pl-6"
|
|
|
+ v-for="item in selectedAcupoint?.attributes"
|
|
|
+ :key="item.title"
|
|
|
+ v-show="selectedAcupoint?.attributes.length > 0"
|
|
|
+ >
|
|
|
+ <div class="mb-2 text-sm text-gray-600">
|
|
|
+ {{ item.title }}: {{ item.content }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex rounded-xl pl-6" v-if="selectedAcupoint?.photo">
|
|
|
+ <div class="mb-2 text-sm text-gray-600">
|
|
|
+ {{ $t('treatment.detail.photo') }}:
|
|
|
+ </div>
|
|
|
+ <div class="relative overflow-hidden">
|
|
|
+ <Image
|
|
|
+ :src="selectedAcupoint?.photo"
|
|
|
+ v-show="selectedAcupoint?.photo"
|
|
|
+ class="object-contain"
|
|
|
+ alt=""
|
|
|
+ style="width: 100px; height: 100px"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </AcupointModal>
|
|
|
+</template>
|