소스 검색

添加上架模块

cc12458 2 주 전
부모
커밋
b273192036
6개의 변경된 파일371개의 추가작업 그리고 5개의 파일을 삭제
  1. 1 0
      model/account.model.ts
  2. 1 1
      request/pharmacy.request.ts
  3. 1 1
      request/system.request.ts
  4. 95 2
      src/api/pda.api.ts
  5. 46 0
      src/model/step.model.ts
  6. 227 1
      src/module/step/StepPutaway.vue

+ 1 - 0
model/account.model.ts

@@ -7,6 +7,7 @@ export interface AccountModel {
   gender?: string;
   age?: string;
   ageUnit?: string;
+  birthdate?: string;
   phone?: string;
 }
 

+ 1 - 1
request/pharmacy.request.ts

@@ -38,5 +38,5 @@ async function analysisResponseBody(response: Response, method: Method) {
   }
   if (code !== 200) throw method.meta?.exception?.(msg, response, method) ?? msg;
   ['success', 'warn', 'error'].forEach((key) => Reflect.deleteProperty(external, key));
-  return Object.assign({}, external, data);
+  return Array.isArray(data) ? Object.assign(data, external) : Object.assign({}, external, typeof data === 'string' ? { message: data } : data);
 }

+ 1 - 1
request/system.request.ts

@@ -28,5 +28,5 @@ async function analysisResponseBody(response: Response, method: Method) {
   const { code, msg, data, ...external } = body;
   if (code !== 200) throw method.meta?.exception?.(msg, response, method) ?? msg;
   ['success', 'warn', 'error'].forEach((key) => Reflect.deleteProperty(external, key));
-  return Array.isArray(data) ? Object.assign(data, external) : Object.assign({}, external, data);
+  return Array.isArray(data) ? Object.assign(data, external) : Object.assign({}, external, typeof data === 'string' ? { message: data } : data);
 }

+ 95 - 2
src/api/pda.api.ts

@@ -2,8 +2,8 @@ import type { ResponseData } from 'alova';
 import PharmacyHttpClient from '@request/pharmacy.request.ts';
 import { requestMethodFactory } from '@/platform/request.ts';
 
-import type { DecoctionModel, PackModel, SoakModel, StepModel } from '@/model/step.model.ts';
-import { fromDecoctionModel, fromPackModel, fromSoakModel } from '@/model/step.model.ts';
+import type { DecoctionModel, PackModel, PutawayModel, SoakModel, StepModel } from '@/model/step.model.ts';
+import { fromDecoctionModel, fromPackModel, fromPutawayModel, fromSoakModel } from '@/model/step.model.ts';
 
 export function getDataMethod(no: string, mode?: string) {
   return requestMethodFactory(
@@ -156,3 +156,96 @@ export function setPackDataMethod(model: Partial<PackModel>, picture?: string[])
 export function setPackRecheckDataMethod(model: Partial<PackModel>, picture?: string[]) {
   return requestMethodFactory(PharmacyHttpClient.Put('/web/pda/Pack/addPackReview', { ...model, image: picture?.join(',') ?? '' }, { name: 'pack-edit-recheck' }));
 }
+
+/**
+ * 上架节点获取
+ * @param no 订单号
+ */
+export function getPutawayDataMethod (no: string) {
+  return PharmacyHttpClient.Post<PutawayModel, ResponseData>(
+    `/prescription/medicineChestAPI/scanLabel`,
+    {},
+    {
+      params: {
+        mode: 'putaway',
+        orderNo: no,
+        /**
+         * @deprecated 历史遗留
+         */
+        preNo: no
+      },
+      transform (data) {
+        data.orderNo = no;
+        return fromPutawayModel(data);
+      },
+      hitSource: /^(putaway|getaway)/,
+      cacheFor: null
+    }
+  );
+}
+
+/**
+ * 上架节点操作
+ */
+export function setPutawayDataMethod (model: { no: string; cabinet?: string }, picture?: string[]) {
+  return PharmacyHttpClient.Post<PutawayModel, ResponseData>(
+    '/prescription/medicineChestAPI/putaway',
+    {},
+    {
+      params: {
+        preNo: model.no,
+        containerNo: model.cabinet
+      },
+      transform (data) {
+        return fromPutawayModel({
+          ...data,
+          inBox: '1'
+        });
+      },
+      name: 'putaway-edit'
+    }
+  );
+}
+
+/**
+ * 取药节点操作
+ * @param model 处方数据,使用订单号
+ */
+export function setGetawayDataMethod (model?: PutawayModel) {
+  return requestMethodFactory(
+    PharmacyHttpClient.Post<{ message: string }, ResponseData>(
+      `/prescription/medicineChestAPI/getawayVerify`,
+      {},
+      {
+        params: {
+          orderNo: model?.no,
+          /**
+           * @deprecated 历史遗留
+           */
+          preNo: model?.prescription?.no
+        },
+        name: 'getaway-edit'
+      }
+    )
+  );
+}
+
+/**
+ * 通知取药
+ * @param model 处方数据,使用订单号
+ */
+export function notifyGetawayDataMethod (model?: PutawayModel) {
+  return PharmacyHttpClient.Post<{ message: string }, ResponseData>(
+    `/prescription/medicineChestAPI/callForMedicationCollection`,
+    {},
+    {
+      params: {
+        orderNo: model?.no,
+        /**
+         * @deprecated 历史遗留
+         */
+        preNo: model?.prescription?.no
+      },
+    }
+  );
+}

+ 46 - 0
src/model/step.model.ts

@@ -11,7 +11,16 @@ export interface StepModel {
   prescription: {
     diagnose: string;
 
+    /**
+     *  1: '中药处方',
+     *  2: '中药制剂'
+     */
     category: string;
+    /**
+     * 散装颗粒
+     * ...
+     */
+    classes?: string;
     dosageForm: string;
     totalPrice: string;
 
@@ -111,3 +120,40 @@ export function fromPackModel(data?: ResponseData) {
     data,
   ) as PackModel;
 }
+
+export interface PutawayModel extends ResponseData {
+  no: string;
+  patient: {
+    birthdate?: string;
+  };
+  prescription: {
+    classes?: string;
+    no?: string;
+  };
+  cabinet?: {
+    row: string;
+    col: string;
+    no: string;
+  };
+}
+
+export function fromPutawayModel(data?: ResponseData) {
+  return Object.assign(
+    {
+      no: data?.orderNo,
+      patient: {
+        birthdate: data?.dateOfBirth ?? '',
+      },
+      prescription: {
+        classes: data?.type,
+        no: data?.preNo,
+      },
+      cabinet: data?.inBox === '1' ? {
+        row: data?.lineNumber,
+        col: data?.columnNumber,
+        no: data?.containerNumber,
+      } : void 0,
+    },
+    data,
+  ) as PutawayModel;
+}

+ 227 - 1
src/module/step/StepPutaway.vue

@@ -1,11 +1,237 @@
 <script setup lang="ts">
+import { createReusableTemplate } from '@vueuse/core';
 
+import { useForm, useRequest, useWatcher } from 'alova/client';
+import { getPutawayDataMethod, notifyGetawayDataMethod, setGetawayDataMethod, setPutawayDataMethod } from '@/api/pda.api.ts';
+
+import { useStepStore } from '@/stores';
+
+import { type ScanData, scanKey } from '@/core/hook/useScan.ts';
+import { showConfirmDialog, showFailToast, showSuccessToast } from 'vant';
+
+const emits = defineEmits<{ back: [delta?: number] }>();
+
+const storeStore = useStepStore();
+const { dataset } = storeToRefs(storeStore);
+
+const {
+  loading: submitting,
+  form: model,
+  send, reset,
+} = useForm(setPutawayDataMethod, { immediate: false, initialForm: { no: '', cabinet: '' } })
+  .onSuccess(({ data: { cabinet } }) => {
+    data.value.cabinet = cabinet;
+    showSuccessToast(`操作成功`);
+  })
+  .onError(({ error: message }) => {
+    model.value.cabinet = '';
+    showFailToast({ message });
+  })
+  .onComplete(() => {
+    panelHeight.value = panelAnchors[panelAnchors.length - 1];
+  });
+
+const { data, loading } = useWatcher(() => getPutawayDataMethod(dataset.value?.no!), [dataset], {
+  immediate: true,
+  initialData: {},
+  middleware: (_, next) => dataset.value?.no && next(),
+})
+  .onSuccess(({ data }) => {
+    const patient = dataset.value?.patient;
+    const prescription = dataset.value?.prescription;
+    if (patient) Object.assign(patient, data.patient);
+    if (prescription) Object.assign(prescription, data.prescription);
+
+    model.value.no = data.no;
+  })
+  .onError(({ error: message }) => {
+    showDialog({ title: '温馨提示', message, closeOnClickOverlay: true }).then(() => emits('back'));
+  });
+
+const { loading: getawayLoading, send: _getaway } = useRequest(() => setGetawayDataMethod(data.value), { immediate: false }).onSuccess(({ data }) => {
+  showSuccessToast(data);
+  emits('back');
+});
+const { loading: broadcastLoading, send: _broadcast } = useRequest(() => notifyGetawayDataMethod(data.value), { immediate: false }).onSuccess(({ data }) => {
+  showSuccessToast(data);
+});
+
+const getaway = () => {
+  showConfirmDialog({
+    title: '确认取药吗?',
+    confirmButtonText: '取药',
+  }).then(_getaway)
+}
+const broadcast = () => {
+  showConfirmDialog({
+    title: '确认通知取药吗?',
+    confirmButtonText: '通知',
+  }).then(_broadcast)
+}
+
+/* 可上架 */
+const setable = computed(() => data.value?.cabinet == null);
+/* 可扫描 */
+const scannable = computed(() => setable && panelHeight.value > 0);
+const scan = inject(scanKey, void 0);
+
+defineExpose({
+  getScan: () =>
+    scannable.value
+      ? (data: ScanData) => {
+          model.value.cabinet = data.code;
+          send();
+        }
+      : void 0,
+  reset,
+});
+
+const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{ show?: boolean }>();
+
+const panelHeight = ref(0);
+const panelAnchors = reactive([0, 100, Math.min(window.innerHeight * 0.8, 480)]);
+
+const start = () => {
+  if (!data.value.cabinet) {
+    panelHeight.value = 100;
+    nextTick(() => scan?.());
+  }
+};
 </script>
 
 <template>
-  $END$
+  <van-toast :show="loading" type="loading" forbid-click />
+
+  <DefineTemplate v-slot="{ show }">
+    <div class="card-wrapper">
+      <div class="flex" v-if="data?.cabinet || show">
+        <div class="cell"></div>
+        <div class="row flex-auto text-center">行{{ data?.cabinet?.row }}</div>
+      </div>
+      <div class="flex">
+        <div v-if="data?.cabinet || show" class="col flex flex-col justify-center text-center">列{{ data?.cabinet?.col }}</div>
+        <div class="flex-auto">
+          <van-button v-if="data?.cabinet" type="success" block @click="getaway()">柜号:{{ data?.cabinet?.no }}</van-button>
+          <van-cell-group>
+            <van-cell title="患者">
+              <template #value>
+                <div>
+                  <span v-if="dataset?.patient?.name" class="font-semibold" data-snippet=",">{{ dataset?.patient?.name }}</span>
+                  <span v-if="dataset?.patient?.gender" class="font-semibold" data-snippet=",">{{ dataset?.patient?.gender }}</span>
+                  <span v-if="dataset?.patient?.age" class="font-semibold" data-snippet=",">{{ dataset?.patient?.age }}</span>
+                </div>
+              </template>
+            </van-cell>
+            <van-cell title="出生日期" :value="dataset?.patient?.birthdate" />
+            <van-cell title="类型" :value="dataset?.prescription?.classes ?? dataset?.prescription?.category" />
+            <van-cell title="剂数" :value="dataset?.prescription?.count" />
+            <van-cell title="是否代煎" :value="dataset?.prescription?.decoction" />
+            <van-cell title="开方医生" :value="dataset?.doctor?.name" />
+          </van-cell-group>
+        </div>
+      </div>
+    </div>
+    <van-cell v-if="data?.cabinet" title="温馨提示:" label="若需要解绑处方,请通过 PC 端处理!" />
+  </DefineTemplate>
+
+  <div class="wrapper">
+    <ReuseTemplate />
+
+    <template v-if="!loading">
+      <div class="flex my-4 px-4 gap-4">
+        <van-button type="warning" block :loading="broadcastLoading" @click="broadcast()">通知取药</van-button>
+        <van-button type="success" block :loading="getawayLoading" @click="getaway()">取药</van-button>
+      </div>
+      <div v-if="setable" class="flex my-4 px-4 gap-4">
+        <van-button type="primary" block @click="start()">上柜</van-button>
+      </div>
+    </template>
+
+    <van-overlay :show="scannable" @click.self="panelHeight = 0">
+      <van-floating-panel lock-scroll v-model:height="panelHeight" :anchors="panelAnchors">
+        <template #header>
+          <div class="van-floating-panel__header flex-col">
+            <div class="van-floating-panel__header-bar"></div>
+            <div class="mt-2">上柜</div>
+          </div>
+        </template>
+
+        <van-field
+          v-if="setable"
+          label="扫描柜号"
+          :readonly="submitting || !!data.cabinet"
+          placeholder="请使用设备按钮进行扫码"
+          input-align="center"
+          v-model="model.cabinet"
+          enterkeyhint="done"
+          @keydown.enter="send()"
+          :right-icon="setable ? 'scan' : ''"
+          @click-right-icon="scan && scan()"
+        />
+        <div class="mt-4 px-4">
+          <ReuseTemplate :show="true" />
+          <div v-if="setable" class="mt-4">
+            <van-button class="mt-2" type="primary" block :loading="submitting" @click="send()">上柜</van-button>
+          </div>
+        </div>
+      </van-floating-panel>
+    </van-overlay>
+  </div>
 </template>
 
 <style scoped lang="scss">
+span[data-snippet] + span[data-snippet]::before {
+  content: attr(data-snippet);
+}
+
+.wrapper {
+  padding: var(--van-cell-vertical-padding) var(--van-cell-horizontal-padding);
+}
+
+.card-wrapper {
+  $border: 1px solid var(--van-cell-border-color);
+
+  --p: 8px;
+  --s: calc(var(--p) * 2 + 1em);
+  --van-cell-border-color: var(--van-border-color);
+
+  .cell {
+    width: var(--s);
+    height: var(--s);
+    border-right: $border;
+    border-bottom: $border;
+  }
+
+  .row {
+    padding: calc(var(--p) - 1px) 0;
+    font-weight: 600;
+    line-height: 1em;
+    letter-spacing: 0.5em;
+  }
+
+  .col {
+    padding: 0 calc(var(--p) - 1px);
+    width: calc(1em + 1px);
+    font-weight: 600;
+    line-height: 2em;
+    box-sizing: content-box;
+    border-right: $border;
+    border-bottom: $border;
+  }
+
+  :deep(.van-cell) {
+    .van-cell__title {
+      color: var(--van-cell-value-color);
+    }
+
+    .van-cell__value {
+      color: var(--van-cell-text-color);
+    }
+  }
+}
 
+.van-floating-panel__header {
+  --van-floating-panel-header-height: 50px;
+  color: var(--van-text-color-2);
+}
 </style>