在线复诊挂号和订单校验
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
@@ -491,7 +491,13 @@ export const usePrescriptionStore = defineStore('prescription', () => {
|
|||||||
image: data.drug.image,
|
image: data.drug.image,
|
||||||
instruction: data.drug.instruction,
|
instruction: data.drug.instruction,
|
||||||
type: data.drug.type,
|
type: data.drug.type,
|
||||||
select_number: 1,
|
select_number: (() => {
|
||||||
|
const raw = Number(
|
||||||
|
data.select_number ?? data.purchase_quantity ?? data.purchase_number,
|
||||||
|
);
|
||||||
|
if (!Number.isFinite(raw) || raw < 1) return 1;
|
||||||
|
return Math.min(9999, Math.max(1, Math.floor(raw)));
|
||||||
|
})(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const newDrugs = [...currentDrugs.value, newProduct];
|
const newDrugs = [...currentDrugs.value, newProduct];
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
content: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = computed(() =>
|
||||||
|
typeof props.content === 'string'
|
||||||
|
? (() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(props.content);
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
: props.content || {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const answeredLabel = computed(() => {
|
||||||
|
const st = data.value.answer_status;
|
||||||
|
if (st === 'yes' || st === '1') return '是,曾使用过';
|
||||||
|
if (st === 'no' || st === '0') return '否,未使用过';
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const drugs = computed(() =>
|
||||||
|
Array.isArray(data.value.drugs) ? data.value.drugs : [],
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="follow-up-drug-card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div class="head-icon">
|
||||||
|
<i class="fas fa-user-md"></i>
|
||||||
|
</div>
|
||||||
|
<div class="head-text">
|
||||||
|
<div class="title">医生助理 · 用药确认</div>
|
||||||
|
<div v-if="data.flow === 'follow_up_drug'" class="sub">复诊用药</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="data.question" class="question-block">
|
||||||
|
{{ data.question }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="drugs.length" class="drug-list">
|
||||||
|
<div class="drug-list-title">涉及药品</div>
|
||||||
|
<ul>
|
||||||
|
<li v-for="(row, idx) in drugs" :key="idx">
|
||||||
|
<span class="name">{{ row.name || '药品' }}</span>
|
||||||
|
<span class="qty">×{{ row.quantity != null ? row.quantity : 1 }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="answeredLabel" class="answer-row">
|
||||||
|
<span class="label">患者回复</span>
|
||||||
|
<span class="value">{{ answeredLabel }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="data.answer_status === 'pending'"
|
||||||
|
class="pending-tip"
|
||||||
|
>
|
||||||
|
待患者在小程序端填写用药信息
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.follow-up-drug-card {
|
||||||
|
@apply w-full max-w-md rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-600 dark:bg-slate-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
@apply mb-3 flex items-center gap-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head-icon {
|
||||||
|
@apply flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full bg-slate-100 text-slate-600 dark:bg-slate-700 dark:text-slate-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head-text .title {
|
||||||
|
@apply text-base font-semibold text-slate-800 dark:text-slate-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.head-text .sub {
|
||||||
|
@apply mt-0.5 text-xs text-slate-500 dark:text-slate-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.question-block {
|
||||||
|
@apply mb-3 rounded-lg bg-slate-50 p-3 text-sm leading-relaxed text-slate-700 dark:bg-slate-900/60 dark:text-slate-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-list {
|
||||||
|
@apply mb-3 border-t border-slate-100 pt-3 dark:border-slate-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-list-title {
|
||||||
|
@apply mb-2 text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-list ul {
|
||||||
|
@apply m-0 list-none space-y-2 p-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-list li {
|
||||||
|
@apply flex items-center justify-between text-sm text-slate-800 dark:text-slate-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-list .name {
|
||||||
|
@apply min-w-0 flex-1 truncate pr-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drug-list .qty {
|
||||||
|
@apply flex-shrink-0 font-medium text-slate-600 dark:text-slate-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-row {
|
||||||
|
@apply flex items-center justify-between rounded-lg bg-emerald-50 px-3 py-2 text-sm dark:bg-emerald-900/30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-row .label {
|
||||||
|
@apply text-slate-600 dark:text-slate-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-row .value {
|
||||||
|
@apply font-medium text-emerald-800 dark:text-emerald-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pending-tip {
|
||||||
|
@apply rounded-lg border border-dashed border-amber-200 bg-amber-50 px-3 py-2 text-center text-xs text-amber-800 dark:border-amber-700 dark:bg-amber-900/20 dark:text-amber-200;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { useUserStore } from '@vben/stores';
|
import { useUserStore } from '@vben/stores';
|
||||||
@@ -55,6 +55,7 @@ import { useThemeStore } from '../stores/theme.ts';
|
|||||||
import { useUserStore as chatUseUserStore } from '../stores/user';
|
import { useUserStore as chatUseUserStore } from '../stores/user';
|
||||||
import AudioMessage from './AudioMessage.vue';
|
import AudioMessage from './AudioMessage.vue';
|
||||||
import PatientExperienceCard from '#/views/doctor/online-consultation/components/PatientExperienceCard.vue';
|
import PatientExperienceCard from '#/views/doctor/online-consultation/components/PatientExperienceCard.vue';
|
||||||
|
import FollowUpDrugAssistantCard from './FollowUpDrugAssistantCard.vue';
|
||||||
import ProductCard from '#/views/doctor/online-consultation/components/ProductCard.vue';
|
import ProductCard from '#/views/doctor/online-consultation/components/ProductCard.vue';
|
||||||
import EndConsultationCard from '#/views/doctor/online-consultation/components/EndConsultationCard.vue';
|
import EndConsultationCard from '#/views/doctor/online-consultation/components/EndConsultationCard.vue';
|
||||||
import TransferPrescriptionMessageCard from '#/views/doctor/online-consultation/components/TransferPrescriptionMessageCard.vue';
|
import TransferPrescriptionMessageCard from '#/views/doctor/online-consultation/components/TransferPrescriptionMessageCard.vue';
|
||||||
@@ -154,15 +155,76 @@ const messageTypeName = computed(() => {
|
|||||||
return getMessageTypeName(props.message.message_type);
|
return getMessageTypeName(props.message.message_type);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRegisterCardInfo = () => {
|
/** type10:接口补全的挂号卡字段(不写回 props,避免破坏响应式) */
|
||||||
const content = getParsedContent(props.message.message_type, props.message.message_content);
|
const registerCardPayload = ref(null);
|
||||||
getChatMessageRegisterInfoApi({ id: content.id }).then(
|
|
||||||
(res) => {
|
const registerCardDisplay = computed(() => {
|
||||||
// eslint-disable-next-line vue/no-mutating-props
|
if (props.message.message_type !== 10) {
|
||||||
props.message.message_content = JSON.stringify(res);
|
return {};
|
||||||
},
|
}
|
||||||
|
let base = {};
|
||||||
|
const raw = props.message.message_content || props.message.content || '';
|
||||||
|
try {
|
||||||
|
base =
|
||||||
|
typeof raw === 'string' && raw
|
||||||
|
? JSON.parse(raw)
|
||||||
|
: typeof raw === 'object' && raw
|
||||||
|
? { ...raw }
|
||||||
|
: {};
|
||||||
|
} catch {
|
||||||
|
base = {};
|
||||||
|
}
|
||||||
|
const enrich = registerCardPayload.value;
|
||||||
|
if (enrich && typeof enrich === 'object' && !Array.isArray(enrich)) {
|
||||||
|
return { ...base, ...enrich };
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
});
|
||||||
|
|
||||||
|
const registerDrugNamesLine = computed(() => {
|
||||||
|
const pc = registerCardDisplay.value;
|
||||||
|
if (!pc || typeof pc !== 'object') return '';
|
||||||
|
const arr = pc.selected_western_drugs;
|
||||||
|
if (Array.isArray(arr) && arr.length) {
|
||||||
|
return arr.map((d) => d?.name).filter(Boolean).join('、');
|
||||||
|
}
|
||||||
|
if (pc.drug?.name) return pc.drug.name;
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFollowUpDrugAssistant = computed(() => {
|
||||||
|
if (props.message.message_type !== 11) return false;
|
||||||
|
const pc = parsedContent.value;
|
||||||
|
return !!(pc && typeof pc === 'object' && pc.flow === 'follow_up_drug');
|
||||||
|
});
|
||||||
|
|
||||||
|
function fetchRegisterCard() {
|
||||||
|
if (props.message.message_type !== 10) return;
|
||||||
|
const content = getParsedContent(
|
||||||
|
props.message.message_type,
|
||||||
|
props.message.message_content,
|
||||||
);
|
);
|
||||||
};
|
const id = content && typeof content === 'object' ? content.id : undefined;
|
||||||
|
if (!id) return;
|
||||||
|
getChatMessageRegisterInfoApi({ id }).then((res) => {
|
||||||
|
const body = res?.data?.data ?? res?.data ?? res;
|
||||||
|
registerCardPayload.value =
|
||||||
|
body && typeof body === 'object' && !Array.isArray(body) ? body : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [
|
||||||
|
props.message.message_type,
|
||||||
|
props.message.id,
|
||||||
|
props.message.message_content,
|
||||||
|
],
|
||||||
|
() => {
|
||||||
|
registerCardPayload.value = null;
|
||||||
|
fetchRegisterCard();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
// 接诊操作
|
// 接诊操作
|
||||||
const acceptRegister = async (registerId, orderNo) => {
|
const acceptRegister = async (registerId, orderNo) => {
|
||||||
@@ -176,7 +238,7 @@ const acceptRegister = async (registerId, orderNo) => {
|
|||||||
receptionApi(registerId).then(async (value) => {
|
receptionApi(registerId).then(async (value) => {
|
||||||
// message.success('接诊成功');
|
// message.success('接诊成功');
|
||||||
// 更新消息状态
|
// 更新消息状态
|
||||||
getRegisterCardInfo();
|
fetchRegisterCard();
|
||||||
|
|
||||||
const messageObj = {
|
const messageObj = {
|
||||||
message_type: 0,
|
message_type: 0,
|
||||||
@@ -188,7 +250,7 @@ const acceptRegister = async (registerId, orderNo) => {
|
|||||||
read: true,
|
read: true,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
};
|
};
|
||||||
localStorage.setItem('currentRegisterId', reregisterId);
|
localStorage.setItem('currentRegisterId', String(registerId));
|
||||||
// chatStore.currentFriend?.register_id = reregisterId;
|
// chatStore.currentFriend?.register_id = reregisterId;
|
||||||
chatStore.addMessage(messageObj, chatUserStore.currentUser.doctor_id);
|
chatStore.addMessage(messageObj, chatUserStore.currentUser.doctor_id);
|
||||||
await sendMessage({
|
await sendMessage({
|
||||||
@@ -336,9 +398,6 @@ const getSexText = (sex) => {
|
|||||||
return sex === 1 ? '男' : '女';
|
return sex === 1 ? '男' : '女';
|
||||||
};
|
};
|
||||||
|
|
||||||
if (props.message.message_type === 10) {
|
|
||||||
getRegisterCardInfo();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -482,20 +541,20 @@ if (props.message.message_type === 10) {
|
|||||||
<div
|
<div
|
||||||
:class="{
|
:class="{
|
||||||
'bg-yellow-100 text-yellow-800':
|
'bg-yellow-100 text-yellow-800':
|
||||||
parsedContent.status === 0,
|
registerCardDisplay.status === 0,
|
||||||
'bg-blue-100 text-blue-800': parsedContent.status === 1,
|
'bg-blue-100 text-blue-800': registerCardDisplay.status === 1,
|
||||||
'bg-red-100 text-red-800': parsedContent.status === 2,
|
'bg-red-100 text-red-800': registerCardDisplay.status === 2,
|
||||||
'bg-red-100 text-red-800': parsedContent.status === 3,
|
'bg-red-100 text-red-800': registerCardDisplay.status === 3,
|
||||||
'bg-gray-100 text-gray-800': parsedContent.status === 4,
|
'bg-gray-100 text-gray-800': registerCardDisplay.status === 4,
|
||||||
'bg-purple-100 text-purple-800':
|
'bg-purple-100 text-purple-800':
|
||||||
parsedContent.status === 5,
|
registerCardDisplay.status === 5,
|
||||||
'bg-indigo-100 text-indigo-800':
|
'bg-indigo-100 text-indigo-800':
|
||||||
parsedContent.status === 6,
|
registerCardDisplay.status === 6,
|
||||||
'bg-pink-100 text-pink-800': parsedContent.status === 7,
|
'bg-pink-100 text-pink-800': registerCardDisplay.status === 7,
|
||||||
}"
|
}"
|
||||||
class="rounded-full px-2 py-1 text-xs font-medium"
|
class="rounded-full px-2 py-1 text-xs font-medium"
|
||||||
>
|
>
|
||||||
{{ registerStatusMap[parsedContent.status] || '未知状态' }}
|
{{ registerStatusMap[registerCardDisplay.status] || '未知状态' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -505,13 +564,13 @@ if (props.message.message_type === 10) {
|
|||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<i class="fas fa-hashtag text-xs"></i>
|
<i class="fas fa-hashtag text-xs"></i>
|
||||||
<span class="max-w-[120px] truncate">{{
|
<span class="max-w-[120px] truncate">{{
|
||||||
parsedContent.order_no
|
registerCardDisplay.order_no
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<i class="fas fa-list-ol text-xs"></i>
|
<i class="fas fa-list-ol text-xs"></i>
|
||||||
<span>第{{ parsedContent.order_number }}号</span>
|
<span>第{{ registerCardDisplay.order_number }}号</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -525,12 +584,12 @@ if (props.message.message_type === 10) {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:class="{
|
:class="{
|
||||||
'bg-red-100 text-red-800': parsedContent.is_pay === 0,
|
'bg-red-100 text-red-800': registerCardDisplay.is_pay === 0,
|
||||||
'bg-green-100 text-green-800': parsedContent.is_pay === 1,
|
'bg-green-100 text-green-800': registerCardDisplay.is_pay === 1,
|
||||||
}"
|
}"
|
||||||
class="rounded-full px-2 py-1 text-xs font-medium"
|
class="rounded-full px-2 py-1 text-xs font-medium"
|
||||||
>
|
>
|
||||||
{{ payStatusMap[parsedContent.is_pay] || '未知' }}
|
{{ payStatusMap[registerCardDisplay.is_pay] || '未知' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -539,43 +598,70 @@ if (props.message.message_type === 10) {
|
|||||||
>
|
>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<i class="fas fa-user text-xs"></i>
|
<i class="fas fa-user text-xs"></i>
|
||||||
<span>{{ parsedContent.user_patient?.name || '未知' }}</span>
|
<span>{{ registerCardDisplay.user_patient?.name || '未知' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<i class="fas fa-birthday-cake text-xs"></i>
|
<i class="fas fa-birthday-cake text-xs"></i>
|
||||||
<span>{{ parsedContent.user_patient?.age || 0 }}岁</span>
|
<span>{{ registerCardDisplay.user_patient?.age || 0 }}岁</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<i class="fas fa-venus-mars text-xs"></i>
|
<i class="fas fa-venus-mars text-xs"></i>
|
||||||
<span>{{ getSexText(parsedContent.user_patient?.sex) }}</span>
|
<span>{{ getSexText(registerCardDisplay.user_patient?.sex) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<i class="fas fa-phone text-xs"></i>
|
<i class="fas fa-phone text-xs"></i>
|
||||||
<span>{{
|
<span>{{
|
||||||
parsedContent.user_patient?.mobile ? '已绑定' : '未绑定'
|
registerCardDisplay.user_patient?.mobile ? '已绑定' : '未绑定'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 主诉 / 所选药品 / 数量(与小程序挂号卡对齐) -->
|
||||||
|
<div
|
||||||
|
v-if="registerCardDisplay.chief_complaint || registerDrugNamesLine || (registerCardDisplay.number != null && registerCardDisplay.number !== '')"
|
||||||
|
class="mt-3 space-y-2 rounded-lg border border-gray-100 p-3 text-sm dark:border-gray-600"
|
||||||
|
>
|
||||||
|
<div v-if="registerCardDisplay.chief_complaint">
|
||||||
|
<span class="font-medium text-gray-600 dark:text-gray-300">主诉:</span>
|
||||||
|
<span class="text-gray-800 dark:text-gray-100">{{
|
||||||
|
registerCardDisplay.chief_complaint
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="registerDrugNamesLine">
|
||||||
|
<span class="font-medium text-gray-600 dark:text-gray-300">所选药品:</span>
|
||||||
|
<span class="text-gray-800 dark:text-gray-100">{{
|
||||||
|
registerDrugNamesLine
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="registerCardDisplay.number != null && registerCardDisplay.number !== ''"
|
||||||
|
>
|
||||||
|
<span class="font-medium text-gray-600 dark:text-gray-300">数量:</span>
|
||||||
|
<span class="text-gray-800 dark:text-gray-100"
|
||||||
|
>各 {{ registerCardDisplay.number }} 盒</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮区域 -->
|
<!-- 操作按钮区域 -->
|
||||||
<div class="mt-3 border-t border-gray-200 pt-3 dark:border-gray-600">
|
<div class="mt-3 border-t border-gray-200 pt-3 dark:border-gray-600">
|
||||||
<div class="mb-2 flex items-center justify-between">
|
<div class="mb-2 flex items-center justify-between">
|
||||||
<div class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
<div class="text-sm font-medium text-gray-700 dark:text-gray-200">
|
||||||
挂号费用:
|
挂号费用:
|
||||||
<span class="text-green-500 dark:text-green-400">¥{{ parsedContent.price }}</span>
|
<span class="text-green-500 dark:text-green-400">¥{{ registerCardDisplay.price }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 接诊/拒诊按钮 - 只在待就诊状态显示 -->
|
<!-- 接诊/拒诊按钮 - 只在待就诊状态显示 -->
|
||||||
<div v-if="parsedContent.status === 1" class="mt-2 flex gap-2">
|
<div v-if="registerCardDisplay.status === 1" class="mt-2 flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
:loading="registerOperating"
|
:loading="registerOperating"
|
||||||
class="flex-1"
|
class="flex-1"
|
||||||
size="small"
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="
|
@click="
|
||||||
acceptRegister(parsedContent.id, parsedContent.order_no)
|
acceptRegister(registerCardDisplay.id, registerCardDisplay.order_no)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<i class="fas fa-check mr-1"></i>
|
<i class="fas fa-check mr-1"></i>
|
||||||
@@ -587,7 +673,7 @@ if (props.message.message_type === 10) {
|
|||||||
danger
|
danger
|
||||||
size="small"
|
size="small"
|
||||||
@click="
|
@click="
|
||||||
rejectRegister(parsedContent.id, parsedContent.order_no)
|
rejectRegister(registerCardDisplay.id, registerCardDisplay.order_no)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<i class="fas fa-times mr-1"></i>
|
<i class="fas fa-times mr-1"></i>
|
||||||
@@ -606,9 +692,9 @@ if (props.message.message_type === 10) {
|
|||||||
|
|
||||||
<!-- 时间信息 -->
|
<!-- 时间信息 -->
|
||||||
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||||
创建时间: {{ parsedContent.created_at }}
|
创建时间: {{ registerCardDisplay.created_at }}
|
||||||
<span v-if="parsedContent.pay_time" class="ml-2">
|
<span v-if="registerCardDisplay.pay_time" class="ml-2">
|
||||||
支付时间: {{ formatDateTime(parsedContent.pay_time) }}
|
支付时间: {{ formatDateTime(registerCardDisplay.pay_time) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -824,6 +910,12 @@ if (props.message.message_type === 10) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- type11:复诊助理用药确认(与药店患者就诊经历卡片分流) -->
|
||||||
|
<FollowUpDrugAssistantCard
|
||||||
|
v-else-if="message.message_type === 11 && isFollowUpDrugAssistant"
|
||||||
|
:content="parsedContent"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 患者就诊经历卡片 (type=11) -->
|
<!-- 患者就诊经历卡片 (type=11) -->
|
||||||
<PatientExperienceCard
|
<PatientExperienceCard
|
||||||
v-else-if="message.message_type === 11"
|
v-else-if="message.message_type === 11"
|
||||||
|
|||||||
@@ -93,3 +93,30 @@ export async function exportOrderApi() {
|
|||||||
export async function updateFreeShipping(data: { id: number; is_free_shipping: number }) {
|
export async function updateFreeShipping(data: { id: number; is_free_shipping: number }) {
|
||||||
return requestClient.post<any>(`${prefix}update-free-shipping`, data);
|
return requestClient.post<any>(`${prefix}update-free-shipping`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 近 N 分钟订单:表内金额与按 OrderItems 重算是否一致(调价/分账同口径)
|
||||||
|
*/
|
||||||
|
export async function getVerifyRecentOrderAmounts(params?: {
|
||||||
|
minutes?: number;
|
||||||
|
mismatch_only?: number;
|
||||||
|
}) {
|
||||||
|
return requestClient.get<{
|
||||||
|
count: number;
|
||||||
|
minutes: number;
|
||||||
|
orders: Array<{
|
||||||
|
expected_items_price: number;
|
||||||
|
expected_total_pay_price: number;
|
||||||
|
items_diff: number;
|
||||||
|
items_match: boolean;
|
||||||
|
items_price: number;
|
||||||
|
ok: boolean;
|
||||||
|
order_id: number;
|
||||||
|
order_no: string;
|
||||||
|
total_diff: number;
|
||||||
|
total_match: boolean;
|
||||||
|
total_pay_price: number;
|
||||||
|
}>;
|
||||||
|
since: number;
|
||||||
|
}>(`${prefix}verify-recent-order-amounts`, { params });
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
} from '@vben/common-ui';
|
} from '@vben/common-ui';
|
||||||
import { SvgCakeIcon } from '@vben/icons';
|
import { SvgCakeIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, Image, message, Switch, Tag } from 'ant-design-vue';
|
import { Button, Image, message, Modal as AntdModal, Space, Switch, Table, Tag } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { TableAction } from '#/components/table-action';
|
import { TableAction } from '#/components/table-action';
|
||||||
@@ -19,6 +19,7 @@ import { downloadByData } from '#/util/tool';
|
|||||||
import {
|
import {
|
||||||
exportOrderApi,
|
exportOrderApi,
|
||||||
getOrderList,
|
getOrderList,
|
||||||
|
getVerifyRecentOrderAmounts,
|
||||||
saleAmountApi,
|
saleAmountApi,
|
||||||
updateFreeShipping,
|
updateFreeShipping,
|
||||||
} from '#/views/business/order/product-order/api';
|
} from '#/views/business/order/product-order/api';
|
||||||
@@ -178,12 +179,103 @@ const toggleFreeShipping = async (row: any, checked: boolean) => {
|
|||||||
message.error(error.message || '更新失败');
|
message.error(error.message || '更新失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const verifyOpen = ref(false);
|
||||||
|
const verifyLoading = ref(false);
|
||||||
|
const verifyRows = ref<any[]>([]);
|
||||||
|
const verifyMeta = ref({ minutes: 10, count: 0, since: 0 });
|
||||||
|
const verifyMismatchOnly = ref(false);
|
||||||
|
|
||||||
|
const verifyColumns = [
|
||||||
|
{ title: '订单ID', dataIndex: 'order_id', key: 'order_id', width: 90 },
|
||||||
|
{ title: '订单号', dataIndex: 'order_no', key: 'order_no', width: 200, ellipsis: true },
|
||||||
|
{ title: '药品价(表)', dataIndex: 'items_price', key: 'items_price', width: 100 },
|
||||||
|
{ title: '药品价(算)', dataIndex: 'expected_items_price', key: 'exp_items', width: 100 },
|
||||||
|
{ title: '药品', key: 'items_match', width: 80 },
|
||||||
|
{ title: '应付(表)', dataIndex: 'total_pay_price', key: 'total_pay', width: 100 },
|
||||||
|
{ title: '应付(算)', dataIndex: 'expected_total_pay_price', key: 'exp_total', width: 100 },
|
||||||
|
{ title: '应付', key: 'total_match', width: 80 },
|
||||||
|
{ title: '全OK', key: 'ok', width: 70 },
|
||||||
|
{ title: '药品差', dataIndex: 'items_diff', key: 'items_diff', width: 80 },
|
||||||
|
{ title: '应付差', dataIndex: 'total_diff', key: 'total_diff', width: 80 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const fetchOrderAmountVerify = async () => {
|
||||||
|
verifyLoading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await getVerifyRecentOrderAmounts({
|
||||||
|
minutes: 10,
|
||||||
|
mismatch_only: verifyMismatchOnly.value ? 1 : 0,
|
||||||
|
});
|
||||||
|
verifyMeta.value = {
|
||||||
|
minutes: res.minutes,
|
||||||
|
count: res.count,
|
||||||
|
since: res.since,
|
||||||
|
};
|
||||||
|
verifyRows.value = res.orders ?? [];
|
||||||
|
verifyOpen.value = true;
|
||||||
|
} finally {
|
||||||
|
verifyLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const openOrderAmountVerify = () => {
|
||||||
|
verifyMismatchOnly.value = false;
|
||||||
|
fetchOrderAmountVerify();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height title="订单管理">
|
<Page auto-content-height title="订单管理">
|
||||||
<FormModal />
|
<FormModal />
|
||||||
<Modal />
|
<Modal />
|
||||||
|
<AntdModal
|
||||||
|
v-model:open="verifyOpen"
|
||||||
|
title="近10分钟订单金额校验"
|
||||||
|
width="min(1100px, 96vw)"
|
||||||
|
:footer="null"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<Space direction="vertical" style="width: 100%; margin-bottom: 12px">
|
||||||
|
<div>
|
||||||
|
与明细重算口径同调价/分账;已检 {{ verifyMeta.count }} 条;since={{ verifyMeta.since }}(Unix)
|
||||||
|
</div>
|
||||||
|
<Space wrap>
|
||||||
|
<span>仅显示不一致</span>
|
||||||
|
<Switch v-model:checked="verifyMismatchOnly" @update:checked="fetchOrderAmountVerify" />
|
||||||
|
<Button :loading="verifyLoading" type="primary" @click="fetchOrderAmountVerify">
|
||||||
|
重新检测
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
<Table
|
||||||
|
:columns="verifyColumns"
|
||||||
|
:data-source="verifyRows"
|
||||||
|
:loading="verifyLoading"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="{ x: 1200 }"
|
||||||
|
size="small"
|
||||||
|
row-key="order_id"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'items_match'">
|
||||||
|
<Tag :color="record.items_match ? 'success' : 'error'">
|
||||||
|
{{ record.items_match ? '一致' : '否' }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'total_match'">
|
||||||
|
<Tag :color="record.total_match ? 'success' : 'error'">
|
||||||
|
{{ record.total_match ? '一致' : '否' }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'ok'">
|
||||||
|
<Tag :color="record.ok ? 'success' : 'error'">
|
||||||
|
{{ record.ok ? '是' : '否' }}
|
||||||
|
</Tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</Table>
|
||||||
|
</AntdModal>
|
||||||
<RefundModal />
|
<RefundModal />
|
||||||
<PrescriptionDetailModal />
|
<PrescriptionDetailModal />
|
||||||
<AnalysisOverview :items="overviewItems" :my-card="false" />
|
<AnalysisOverview :items="overviewItems" :my-card="false" />
|
||||||
@@ -192,6 +284,12 @@ const toggleFreeShipping = async (row: any, checked: boolean) => {
|
|||||||
<TableAction
|
<TableAction
|
||||||
:flex="false"
|
:flex="false"
|
||||||
:actions="[
|
:actions="[
|
||||||
|
// {
|
||||||
|
// label: '校验近10分钟金额',
|
||||||
|
// type: 'default',
|
||||||
|
// icon: 'ix:search-check',
|
||||||
|
// onClick: openOrderAmountVerify.bind(null),
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
label: '导出',
|
label: '导出',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
|
|||||||
Reference in New Issue
Block a user